Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion rpcs3/Emu/Cell/Modules/cellGem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1331,8 +1331,15 @@ void gem_config_data::operator()()
vc = vc_attribute;
}

if (g_cfg.io.camera != camera_handler::qt)
switch (g_cfg.io.camera)
{
#ifdef HAVE_SDL3
case camera_handler::sdl:
#endif
case camera_handler::qt:
break;
case camera_handler::fake:
case camera_handler::null:
video_conversion_in_progress = false;
done();
continue;
Expand Down
33 changes: 21 additions & 12 deletions rpcs3/Emu/Io/camera_config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,32 +36,38 @@ void cfg_camera::save() const
}
}

cfg_camera::camera_setting cfg_camera::get_camera_setting(const std::string& camera, bool& success)
cfg_camera::camera_setting cfg_camera::get_camera_setting(const std::string& handler, const std::string& camera, bool& success)
{
camera_setting setting;
const std::string value = cameras.get_value(camera);
camera_setting setting {};
const std::string value = cameras.get_value(handler + "-" + camera);
success = !value.empty();
if (success)
{
setting.from_string(cameras.get_value(camera));
setting.from_string(value);
}
return setting;
}

void cfg_camera::set_camera_setting(const std::string& camera, const camera_setting& setting)
void cfg_camera::set_camera_setting(const std::string& handler, const std::string& camera, const camera_setting& setting)
{
if (handler.empty())
{
camera_log.error("String '%s' cannot be used as handler key.", handler);
return;
}

if (camera.empty())
{
camera_log.error("String '%s' cannot be used as camera key.", camera);
return;
}

cameras.set_value(camera, setting.to_string());
cameras.set_value(handler + "-" + camera, setting.to_string());
}

std::string cfg_camera::camera_setting::to_string() const
{
return fmt::format("%d,%d,%f,%f,%d", width, height, min_fps, max_fps, format);
return fmt::format("%d,%d,%f,%f,%d,%d", width, height, min_fps, max_fps, format, colorspace);
}

void cfg_camera::camera_setting::from_string(const std::string& text)
Expand Down Expand Up @@ -102,16 +108,19 @@ void cfg_camera::camera_setting::from_string(const std::string& text)
return true;
};

if (!to_integer(::at32(list, 0), width) ||
!to_integer(::at32(list, 1), height) ||
!to_double(::at32(list, 2), min_fps) ||
!to_double(::at32(list, 3), max_fps) ||
!to_integer(::at32(list, 4), format))
usz pos = 0;
if (!to_integer(::at32(list, pos++), width) ||
!to_integer(::at32(list, pos++), height) ||
!to_double(::at32(list, pos++), min_fps) ||
!to_double(::at32(list, pos++), max_fps) ||
!to_integer(::at32(list, pos++), format) ||
!to_integer(::at32(list, pos++), colorspace))
{
width = 0;
height = 0;
min_fps = 0;
max_fps = 0;
format = 0;
colorspace = 0;
}
}
9 changes: 5 additions & 4 deletions rpcs3/Emu/Io/camera_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,19 @@ struct cfg_camera final : cfg::node
double min_fps = 0;
double max_fps = 0;
int format = 0;
int colorspace = 0;

static constexpr u32 member_count = 5;
static constexpr u32 member_count = 6;

std::string to_string() const;
void from_string(const std::string& text);
};
camera_setting get_camera_setting(const std::string& camera, bool& success);
void set_camera_setting(const std::string& camera, const camera_setting& setting);
camera_setting get_camera_setting(const std::string& handler, const std::string& camera, bool& success);
void set_camera_setting(const std::string& handler, const std::string& camera, const camera_setting& setting);

const std::string path;

cfg::map_entry cameras{ this, "Cameras" }; // <camera>: <width>,<height>,<min_fps>,<max_fps>,<format>
cfg::map_entry cameras{ this, "Cameras" }; // <handler-camera>: <width>,<height>,<min_fps>,<max_fps>,<format>,<colorspace>
};

extern cfg_camera g_cfg_camera;
10 changes: 9 additions & 1 deletion rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_settings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,17 @@ namespace rsx
add_checkbox(&g_cfg.io.keep_pads_connected, localized_string_id::HOME_MENU_SETTINGS_INPUT_KEEP_PADS_CONNECTED);
add_checkbox(&g_cfg.io.show_move_cursor, localized_string_id::HOME_MENU_SETTINGS_INPUT_SHOW_PS_MOVE_CURSOR);

if (g_cfg.io.camera == camera_handler::qt)
switch (g_cfg.io.camera)
{
#ifdef HAVE_SDL3
case camera_handler::sdl:
#endif
case camera_handler::qt:
add_dropdown(&g_cfg.io.camera_flip_option, localized_string_id::HOME_MENU_SETTINGS_INPUT_CAMERA_FLIP);
break;
case camera_handler::fake:
case camera_handler::null:
break;
}

add_dropdown(&g_cfg.io.pad_mode, localized_string_id::HOME_MENU_SETTINGS_INPUT_PAD_MODE);
Expand Down
1 change: 1 addition & 0 deletions rpcs3/Emu/system_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,7 @@ struct cfg_root : cfg::node
cfg::_enum<fake_camera_type> camera_type{ this, "Camera type", fake_camera_type::unknown };
cfg::_enum<camera_flip> camera_flip_option{ this, "Camera flip", camera_flip::none, true };
cfg::string camera_id{ this, "Camera ID", "Default", true };
cfg::string sdl_camera_id{ this, "SDL Camera ID", "Default", true };
cfg::_enum<move_handler> move{ this, "Move", move_handler::null, true };
cfg::_enum<buzz_handler> buzz{ this, "Buzz emulated controller", buzz_handler::null };
cfg::_enum<turntable_handler> turntable{this, "Turntable emulated controller", turntable_handler::null};
Expand Down
3 changes: 3 additions & 0 deletions rpcs3/Emu/system_config_types.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,9 @@ void fmt_class_string<camera_handler>::format(std::string& out, u64 arg)
case camera_handler::null: return "Null";
case camera_handler::fake: return "Fake";
case camera_handler::qt: return "Qt";
#ifdef HAVE_SDL3
case camera_handler::sdl: return "SDL";
#endif
}

return unknown;
Expand Down
5 changes: 4 additions & 1 deletion rpcs3/Emu/system_config_types.h
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,10 @@ enum class camera_handler
{
null,
fake,
qt
qt,
#ifdef HAVE_SDL3
sdl,
#endif
};

enum class camera_flip
Expand Down
230 changes: 230 additions & 0 deletions rpcs3/Input/camera_video_sink.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
#include "stdafx.h"
#include "camera_video_sink.h"

#include "Emu/Cell/Modules/cellCamera.h"
#include "Emu/system_config.h"

LOG_CHANNEL(camera_log, "Camera");

camera_video_sink::camera_video_sink(bool front_facing)
: m_front_facing(front_facing)
{
}

camera_video_sink::~camera_video_sink()
{
}

bool camera_video_sink::present(u32 src_width, u32 src_height, u32 src_pitch, u32 src_bytes_per_pixel, std::function<const u8*(u32)> src_line_ptr)
{
ensure(!!src_line_ptr);

const u64 new_size = m_bytesize;
image_buffer& image_buffer = m_image_buffer[m_write_index];

// Reset buffer if necessary
if (image_buffer.data.size() != new_size)
{
image_buffer.data.clear();
}

// Create buffer if necessary
if (image_buffer.data.empty() && new_size > 0)
{
image_buffer.data.resize(new_size);
image_buffer.width = m_width;
image_buffer.height = m_height;
}

if (!image_buffer.data.empty() && src_width && src_height)
{
// Convert image to proper layout
// TODO: check if pixel format and bytes per pixel match and convert if necessary
// TODO: implement or improve more conversions

const u32 width = std::min<u32>(image_buffer.width, src_width);
const u32 height = std::min<u32>(image_buffer.height, src_height);

switch (m_format)
{
case CELL_CAMERA_RAW8: // The game seems to expect BGGR
{
// Let's use a very simple algorithm to convert the image to raw BGGR
u8* dst = image_buffer.data.data();

for (u32 y = 0; y < height; y++)
{
const u8* src = src_line_ptr(y);
const bool is_top_pixel = (y % 2) == 0;

// Split loops (roughly twice the performance by removing one condition)
if (is_top_pixel)
{
for (u32 x = 0; x < width; x++, dst++, src += 4)
{
const bool is_left_pixel = (x % 2) == 0;

if (is_left_pixel)
{
*dst = src[2]; // Blue
}
else
{
*dst = src[1]; // Green
}
}
}
else
{
for (u32 x = 0; x < width; x++, dst++, src += 4)
{
const bool is_left_pixel = (x % 2) == 0;

if (is_left_pixel)
{
*dst = src[1]; // Green
}
else
{
*dst = src[0]; // Red
}
}
}
}
break;
}
//case CELL_CAMERA_YUV422:
case CELL_CAMERA_Y0_U_Y1_V:
case CELL_CAMERA_V_Y1_U_Y0:
{
// Simple RGB to Y0_U_Y1_V conversion from stackoverflow.
constexpr int yuv_bytes_per_pixel = 2;
const int yuv_pitch = image_buffer.width * yuv_bytes_per_pixel;

const int y0_offset = (m_format == CELL_CAMERA_Y0_U_Y1_V) ? 0 : 3;
const int u_offset = (m_format == CELL_CAMERA_Y0_U_Y1_V) ? 1 : 2;
const int y1_offset = (m_format == CELL_CAMERA_Y0_U_Y1_V) ? 2 : 1;
const int v_offset = (m_format == CELL_CAMERA_Y0_U_Y1_V) ? 3 : 0;

for (u32 y = 0; y < height; y++)
{
const u8* src = src_line_ptr(y);
u8* yuv_row_ptr = &image_buffer.data[y * yuv_pitch];

for (u32 x = 0; x < width - 1; x += 2, src += 8)
{
const f32 r1 = src[0];
const f32 g1 = src[1];
const f32 b1 = src[2];
const f32 r2 = src[4];
const f32 g2 = src[5];
const f32 b2 = src[6];

const f32 y0 = (0.257f * r1) + (0.504f * g1) + (0.098f * b1) + 16.0f;
const f32 u = -(0.148f * r1) - (0.291f * g1) + (0.439f * b1) + 128.0f;
const f32 v = (0.439f * r1) - (0.368f * g1) - (0.071f * b1) + 128.0f;
const f32 y1 = (0.257f * r2) + (0.504f * g2) + (0.098f * b2) + 16.0f;

const int yuv_index = x * yuv_bytes_per_pixel;
yuv_row_ptr[yuv_index + y0_offset] = static_cast<u8>(std::clamp(y0, 0.0f, 255.0f));
yuv_row_ptr[yuv_index + u_offset] = static_cast<u8>(std::clamp( u, 0.0f, 255.0f));
yuv_row_ptr[yuv_index + y1_offset] = static_cast<u8>(std::clamp(y1, 0.0f, 255.0f));
yuv_row_ptr[yuv_index + v_offset] = static_cast<u8>(std::clamp( v, 0.0f, 255.0f));
}
}
break;
}
case CELL_CAMERA_JPG:
case CELL_CAMERA_RGBA:
case CELL_CAMERA_RAW10:
case CELL_CAMERA_YUV420:
case CELL_CAMERA_FORMAT_UNKNOWN:
default:
const u32 bytes_per_line = src_bytes_per_pixel * src_width;
if (src_pitch == bytes_per_line)
{
std::memcpy(image_buffer.data.data(), src_line_ptr(0), std::min<usz>(image_buffer.data.size(), src_height * bytes_per_line));
}
else
{
for (u32 y = 0, pos = 0; y < src_height && pos < image_buffer.data.size(); y++, pos += bytes_per_line)
{
std::memcpy(&image_buffer.data[pos], src_line_ptr(y), std::min<usz>(image_buffer.data.size() - pos, bytes_per_line));
}
}
break;
}
}

camera_log.trace("Wrote image to video surface. index=%d, m_frame_number=%d, width=%d, height=%d, bytesize=%d",
m_write_index, m_frame_number.load(), m_width, m_height, m_bytesize);

// Toggle write/read index
std::lock_guard lock(m_mutex);
image_buffer.frame_number = m_frame_number++;
m_write_index = read_index();

return true;
}

void camera_video_sink::set_format(s32 format, u32 bytesize)
{
camera_log.notice("Setting format: format=%d, bytesize=%d", format, bytesize);

m_format = format;
m_bytesize = bytesize;
}

void camera_video_sink::set_resolution(u32 width, u32 height)
{
camera_log.notice("Setting resolution: width=%d, height=%d", width, height);

m_width = width;
m_height = height;
}

void camera_video_sink::set_mirrored(bool mirrored)
{
camera_log.notice("Setting mirrored: mirrored=%d", mirrored);

m_mirrored = mirrored;
}

u64 camera_video_sink::frame_number() const
{
return m_frame_number.load();
}

void camera_video_sink::get_image(u8* buf, u64 size, u32& width, u32& height, u64& frame_number, u64& bytes_read)
{
// Lock read buffer
std::lock_guard lock(m_mutex);
const image_buffer& image_buffer = m_image_buffer[read_index()];

width = image_buffer.width;
height = image_buffer.height;
frame_number = image_buffer.frame_number;

// Copy to out buffer
if (buf && !image_buffer.data.empty())
{
bytes_read = std::min<u64>(image_buffer.data.size(), size);
std::memcpy(buf, image_buffer.data.data(), bytes_read);

if (image_buffer.data.size() != size)
{
camera_log.error("Buffer size mismatch: in=%d, out=%d. Cropping to incoming size. Please contact a developer.", size, image_buffer.data.size());
}
}
else
{
bytes_read = 0;
}
}

u32 camera_video_sink::read_index() const
{
// The read buffer index cannot be the same as the write index
return (m_write_index + 1u) % ::narrow<u32>(m_image_buffer.size());
}

Loading