cellGem: hook up camera

This commit is contained in:
Megamouse 2021-10-20 19:47:19 +02:00
parent 00c87a8fc9
commit be972f04ac
4 changed files with 343 additions and 73 deletions

View File

@ -36,6 +36,27 @@ void fmt_class_string<CellCameraError>::format(std::string& out, u64 arg)
});
}
template <>
void fmt_class_string<CellCameraFormat>::format(std::string& out, u64 arg)
{
format_enum(out, arg, [](CellCameraFormat value)
{
switch (value)
{
STR_CASE(CELL_CAMERA_FORMAT_UNKNOWN);
STR_CASE(CELL_CAMERA_JPG);
STR_CASE(CELL_CAMERA_RAW8);
STR_CASE(CELL_CAMERA_YUV422);
STR_CASE(CELL_CAMERA_RAW10);
STR_CASE(CELL_CAMERA_RGBA);
STR_CASE(CELL_CAMERA_YUV420);
STR_CASE(CELL_CAMERA_V_Y1_U_Y0);
}
return unknown;
});
}
// Temporarily
#ifndef _MSC_VER
#pragma GCC diagnostic ignored "-Wunused-parameter"
@ -259,14 +280,10 @@ std::pair<u32, u32> get_video_resolution(const CellCameraInfoEx& info)
}
}
u32 get_video_buffer_size(const CellCameraInfoEx& info)
u32 get_buffer_size_by_format(s32 format, s32 width, s32 height)
{
u32 width, height;
std::tie(width, height) = get_video_resolution(info);
double bytes_per_pixel;
switch (info.format)
double bytes_per_pixel = 0.0;
switch (format)
{
case CELL_CAMERA_RAW8:
bytes_per_pixel = 1.0;
@ -292,6 +309,14 @@ u32 get_video_buffer_size(const CellCameraInfoEx& info)
return width * height * bytes_per_pixel;
}
u32 get_video_buffer_size(const CellCameraInfoEx& info)
{
u32 width, height;
std::tie(width, height) = get_video_resolution(info);
return get_buffer_size_by_format(info.format, width, height);
}
// ************************
// * cellCamera functions *
// ************************
@ -563,12 +588,14 @@ error_code cellCameraOpenEx(s32 dev_num, vm::ptr<CellCameraInfoEx> info)
g_camera.is_open = true;
g_camera.info = *info;
cellCamera.notice("cellCameraOpen info: format=%d, resolution=%d, framerate=%d, bytesize=%d, width=%d, height=%d, dev_num=%d, guid=%d",
cellCamera.notice("cellCameraOpen info: format=%s, resolution=%d, framerate=%d, bytesize=%d, width=%d, height=%d, dev_num=%d, guid=%d",
info->format, info->resolution, info->framerate, info->bytesize, info->width, info->height, info->dev_num, info->guid);
auto& shared_data = g_fxo->get<gem_camera_shared>();
shared_data.width = info->width > 0 ? +info->width : 640;
shared_data.height = info->height > 0 ? +info->height : 480;
shared_data.size = vbuf_size;
shared_data.format = info->format;
return CELL_OK;
}
@ -1301,7 +1328,11 @@ error_code cellCameraReadEx(s32 dev_num, vm::ptr<CellCameraReadEx> read)
// can call cellCameraReset() and cellCameraStop() in some cases
const bool has_new_frame = g_camera.has_new_frame.exchange(false);
if (g_camera.handler)
{
if (has_new_frame)
{
u32 width{};
u32 height{};
@ -1313,29 +1344,31 @@ error_code cellCameraReadEx(s32 dev_num, vm::ptr<CellCameraReadEx> read)
return CELL_CAMERA_ERROR_DEVICE_NOT_FOUND;
}
if (read)
{
read->frame = frame_number;
read->bytesread = bytes_read;
}
g_camera.bytes_read = bytes_read;
cellCamera.trace("cellCameraRead: frame_number=%d, width=%d, height=%d. bytes_read=%d (passed to game: frame=%d, bytesread=%d)",
frame_number, width, height, bytes_read, read ? read->frame.get() : 0, read ? read->bytesread.get() : 0);
}
}
else
{
g_camera.bytes_read = g_camera.is_streaming ? get_video_buffer_size(g_camera.info) : 0;
}
if (has_new_frame)
{
g_camera.frame_timestamp = (get_guest_system_time() - g_camera.start_timestamp);
}
if (read) // NULL returns CELL_OK
{
read->timestamp = (get_guest_system_time() - g_camera.start_timestamp);
if (!g_camera.handler)
{
read->timestamp = g_camera.frame_timestamp;
read->frame = g_camera.frame_num;
read->bytesread = g_camera.is_streaming ? get_video_buffer_size(g_camera.info) : 0;
}
read->bytesread = g_camera.bytes_read;
auto& shared_data = g_fxo->get<gem_camera_shared>();
shared_data.frame_timestamp.exchange(read->timestamp);
shared_data.frame_timestamp.store(read->timestamp);
}
return CELL_OK;
@ -1627,9 +1660,9 @@ void camera_context::operator()()
data3 = 0; // unused
}
if (queue->send(evt_data.source, CELL_CAMERA_FRAME_UPDATE, data2, data3) == 0) [[likely]]
if (queue->send(evt_data.source, CELL_CAMERA_FRAME_UPDATE, data2, data3) != 0) [[unlikely]]
{
++frame_num;
cellCamera.warning("Failed to send frame update event");
}
frame_update_event_sent = true;
@ -1637,6 +1670,9 @@ void camera_context::operator()()
}
}
++frame_num;
has_new_frame = true;
if (read_mode.load() == CELL_CAMERA_READ_DIRECT && frame_update_event_sent)
{
std::lock_guard lock(mutex);
@ -1645,7 +1681,7 @@ void camera_context::operator()()
lock.unlock();
for (const u64 frame_target_time = 1000000u / fps;;)
for (const u64 frame_target_time = 1000000u / fps; !Emu.IsStopped();)
{
const u64 time_passed = get_guest_system_time() - frame_start;
if (time_passed >= frame_target_time)
@ -1788,6 +1824,9 @@ void camera_context::reset_state()
pbuf_write_index = 0;
pbuf_locked[0] = false;
pbuf_locked[1] = false;
has_new_frame = false;
frame_timestamp = 0;
bytes_read = 0;
if (info.buffer)
{

View File

@ -360,7 +360,7 @@ struct CellCameraInfo
struct CellCameraInfoEx
{
be_t<s32> format; // CellCameraFormat
be_t<CellCameraFormat> format; // CellCameraFormat
be_t<s32> resolution; // CellCameraResolution
be_t<s32> framerate;
@ -435,7 +435,10 @@ public:
u32 v1, v2;
};
attr_t attr[500]{};
atomic_t<bool> has_new_frame = false;
atomic_t<u32> frame_num = 0;
atomic_t<u32> frame_timestamp = 0;
atomic_t<u32> bytes_read = 0;
atomic_t<u32> init = 0;
@ -458,4 +461,8 @@ struct gem_camera_shared
atomic_t<s64> frame_timestamp{}; // latest read timestamp from cellCamera (cellCameraRead(Ex))
atomic_t<s32> width{640};
atomic_t<s32> height{480};
atomic_t<s32> size{0};
atomic_t<CellCameraFormat> format{CELL_CAMERA_RAW8};
};
static inline s32 get_video_buffer_size(s32 width, s32 height);

View File

@ -2,10 +2,12 @@
#include "cellGem.h"
#include "cellCamera.h"
#include "Emu/Cell/lv2/sys_event.h"
#include "Emu/Cell/PPUModule.h"
#include "Emu/Cell/timers.hpp"
#include "Emu/Io/MouseHandler.h"
#include "Emu/system_config.h"
#include "Emu/System.h"
#include "Emu/IdManager.h"
#include "Input/pad_thread.h"
@ -59,12 +61,39 @@ void fmt_class_string<CellGemStatus>::format(std::string& out, u64 arg)
});
}
template <>
void fmt_class_string<CellGemVideoConvertFormatEnum>::format(std::string& out, u64 arg)
{
format_enum(out, arg, [](auto format)
{
switch (format)
{
STR_CASE(CELL_GEM_NO_VIDEO_OUTPUT);
STR_CASE(CELL_GEM_RGBA_640x480);
STR_CASE(CELL_GEM_YUV_640x480);
STR_CASE(CELL_GEM_YUV422_640x480);
STR_CASE(CELL_GEM_YUV411_640x480);
STR_CASE(CELL_GEM_RGBA_320x240);
STR_CASE(CELL_GEM_BAYER_RESTORED);
STR_CASE(CELL_GEM_BAYER_RESTORED_RGGB);
STR_CASE(CELL_GEM_BAYER_RESTORED_RASTERIZED);
}
return unknown;
});
}
// **********************
// * HLE helper structs *
// **********************
struct gem_config
struct gem_config_data
{
public:
void operator()();
static constexpr auto thread_name = "Gem Thread"sv;
atomic_t<u32> state = 0;
struct gem_color
@ -106,14 +135,16 @@ struct gem_config
CellGemAttribute attribute = {};
CellGemVideoConvertAttribute vc_attribute = {};
s32 video_data_out_size = -1;
std::vector<u8> video_data_in;
u64 status_flags = 0;
bool enable_pitch_correction = false;
u32 inertial_counter = 0;
std::array<gem_controller, CELL_GEM_MAX_NUM> controllers;
u32 connected_controllers = 0;
bool video_conversion_started{};
bool update_started{};
atomic_t<bool> video_conversion_in_progress{false};
atomic_t<bool> update_started{false};
u32 camera_frame{};
u32 memory_ptr{};
@ -173,6 +204,186 @@ struct gem_config
}
};
static inline int32_t cellGemGetVideoConvertSize(s32 output_format)
{
switch (output_format)
{
case CELL_GEM_RGBA_320x240: // RGBA output; 320*240*4-byte output buffer required
return 320 * 240 * 4;
case CELL_GEM_RGBA_640x480: // RGBA output; 640*480*4-byte output buffer required
return 640 * 480 * 4;
case CELL_GEM_YUV_640x480: // YUV output; 640*480+640*480+640*480-byte output buffer required (contiguous)
return 640 * 480 + 640 * 480 + 640 * 480;
case CELL_GEM_YUV422_640x480: // YUV output; 640*480+320*480+320*480-byte output buffer required (contiguous)
return 640 * 480 + 320 * 480 + 320 * 480;
case CELL_GEM_YUV411_640x480: // YUV411 output; 640*480+320*240+320*240-byte output buffer required (contiguous)
return 640 * 480 + 320 * 240 + 320 * 240;
case CELL_GEM_BAYER_RESTORED: // Bayer pattern output, 640x480, gamma and white balance applied, output buffer required
case CELL_GEM_BAYER_RESTORED_RGGB: // Restored Bayer output, 2x2 pixels rearranged into 320x240 RG1G2B
case CELL_GEM_BAYER_RESTORED_RASTERIZED: // Restored Bayer output, R,G1,G2,B rearranged into 4 contiguous 320x240 1-channel rasters
return 640 * 480;
case CELL_GEM_NO_VIDEO_OUTPUT: // Disable video output
return 0;
default:
return -1;
}
}
void gem_config_data::operator()()
{
cellGem.notice("Starting thread");
while (thread_ctrl::state() != thread_state::aborting && !Emu.IsStopped())
{
while (!video_conversion_in_progress && thread_ctrl::state() != thread_state::aborting && !Emu.IsStopped())
{
thread_ctrl::wait_for(1000);
}
if (thread_ctrl::state() == thread_state::aborting || Emu.IsStopped())
{
return;
}
CellGemVideoConvertAttribute vc;
{
std::scoped_lock lock(mtx);
vc = vc_attribute;
}
if (g_cfg.io.camera != camera_handler::qt)
{
video_conversion_in_progress = false;
continue;
}
const auto& shared_data = g_fxo->get<gem_camera_shared>();
if (vc.output_format != CELL_GEM_NO_VIDEO_OUTPUT && !vc_attribute.video_data_out)
{
video_conversion_in_progress = false;
continue;
}
extern u32 get_buffer_size_by_format(s32, s32, s32);
const u32 required_in_size = get_buffer_size_by_format(static_cast<s32>(shared_data.format), shared_data.width, shared_data.height);
const s32 required_out_size = cellGemGetVideoConvertSize(vc.output_format);
if (video_data_in.size() != required_in_size)
{
cellGem.error("convert: in_size mismatch: required=%d, actual=%d", required_in_size, video_data_in.size());
video_conversion_in_progress = false;
continue;
}
if (required_out_size < 0 || video_data_out_size != required_out_size)
{
cellGem.error("convert: out_size unknown: required=%d, format %d", required_out_size, vc.output_format);
video_conversion_in_progress = false;
continue;
}
if (required_out_size == 0)
{
video_conversion_in_progress = false;
continue;
}
switch (vc.output_format)
{
case CELL_GEM_RGBA_640x480: // RGBA output; 640*480*4-byte output buffer required
{
if (shared_data.format == CELL_CAMERA_RAW8)
{
constexpr u32 in_pitch = 640;
constexpr u32 out_pitch = 640 * 4;
for (u32 y = 0; y < 480 - 1; y += 2)
{
for (u32 x = 0; x < 640 - 1; x += 2)
{
const u32 in_offset = 1 * (y * 640 + x);
const u32 out_offset = 4 * (y * 640 + x);
const u8 b = video_data_in[in_offset + 0];
const u8 g0 = video_data_in[in_offset + 1];
const u8 g1 = video_data_in[in_offset + in_pitch + 0];
const u8 r = video_data_in[in_offset + in_pitch + 1];
// Top-Left
vc_attribute.video_data_out[out_offset + 0] = r; // R
vc_attribute.video_data_out[out_offset + 1] = g0; // G
vc_attribute.video_data_out[out_offset + 2] = b; // B
vc_attribute.video_data_out[out_offset + 3] = 255; // A
// Top-Right Pixel
vc_attribute.video_data_out[out_offset + 4] = r; // R
vc_attribute.video_data_out[out_offset + 5] = g0; // G
vc_attribute.video_data_out[out_offset + 6] = b; // B
vc_attribute.video_data_out[out_offset + 7] = 255; // A
// Bottom-Left Pixel
vc_attribute.video_data_out[out_offset + out_pitch + 0] = r; // R
vc_attribute.video_data_out[out_offset + out_pitch + 1] = g1; // G
vc_attribute.video_data_out[out_offset + out_pitch + 2] = b; // B
vc_attribute.video_data_out[out_offset + out_pitch + 3] = 255; // A
// Bottom-Right Pixel
vc_attribute.video_data_out[out_offset + out_pitch + 4] = r; // R
vc_attribute.video_data_out[out_offset + out_pitch + 5] = g1; // G
vc_attribute.video_data_out[out_offset + out_pitch + 6] = b; // B
vc_attribute.video_data_out[out_offset + out_pitch + 7] = 255; // A
}
}
}
else
{
cellGem.error("Unimplemented: Converting %s to %s", shared_data.format.load(), vc.output_format);
std::memcpy(vc_attribute.video_data_out.get_ptr(), video_data_in.data(), std::min<usz>(required_in_size, required_out_size));
}
break;
}
case CELL_GEM_BAYER_RESTORED: // Bayer pattern output, 640x480, gamma and white balance applied, output buffer required
{
if (shared_data.format == CELL_CAMERA_RAW8)
{
std::memcpy(vc_attribute.video_data_out.get_ptr(), video_data_in.data(), std::min<usz>(required_in_size, required_out_size));
}
else
{
cellGem.error("Unimplemented: Converting %s to %s", shared_data.format.load(), vc.output_format);
}
break;
}
case CELL_GEM_RGBA_320x240: // RGBA output; 320*240*4-byte output buffer required
case CELL_GEM_YUV_640x480: // YUV output; 640*480+640*480+640*480-byte output buffer required (contiguous)
case CELL_GEM_YUV422_640x480: // YUV output; 640*480+320*480+320*480-byte output buffer required (contiguous)
case CELL_GEM_YUV411_640x480: // YUV411 output; 640*480+320*240+320*240-byte output buffer required (contiguous)
case CELL_GEM_BAYER_RESTORED_RGGB: // Restored Bayer output, 2x2 pixels rearranged into 320x240 RG1G2B
case CELL_GEM_BAYER_RESTORED_RASTERIZED: // Restored Bayer output, R,G1,G2,B rearranged into 4 contiguous 320x240 1-channel rasters
{
cellGem.error("Unimplemented: Converting %s to %s", shared_data.format.load(), vc.output_format);
break;
}
case CELL_GEM_NO_VIDEO_OUTPUT: // Disable video output
{
cellGem.trace("Ignoring frame conversion for CELL_GEM_NO_VIDEO_OUTPUT");
break;
}
default:
{
cellGem.error("Trying to convert %s to %s", shared_data.format.load(), vc.output_format);
break;
}
}
cellGem.notice("Converted video frame of format %s to %s", shared_data.format.load(), vc.output_format.get());
video_conversion_in_progress = false;
}
}
using gem_config = named_thread<gem_config_data>;
/**
* \brief Verifies that a Move controller id is valid
* \param gem_num Move controler ID to verify
@ -528,12 +739,15 @@ error_code cellGemConvertVideoFinish()
return CELL_GEM_ERROR_UNINITIALIZED;
}
if (!std::exchange(gem.video_conversion_started, false))
if (!gem.video_conversion_in_progress)
{
return CELL_GEM_ERROR_CONVERT_NOT_STARTED;
}
// TODO: wait until image is converted
while (gem.video_conversion_in_progress && !Emu.IsStopped())
{
thread_ctrl::wait_for(100);
}
return CELL_OK;
}
@ -555,17 +769,20 @@ error_code cellGemConvertVideoStart(vm::cptr<void> video_frame)
}
// TODO: The alignment checks seem to break Time Crisis Razing Storm [BLUS30528]
//if (!video_frame.aligned(128))
//{
// return CELL_GEM_ERROR_INVALID_ALIGNMENT;
//}
if (!video_frame.aligned(128))
{
return CELL_GEM_ERROR_INVALID_ALIGNMENT;
}
if (std::exchange(gem.video_conversion_started, true))
if (gem.video_conversion_in_progress)
{
return CELL_GEM_ERROR_CONVERT_NOT_FINISHED;
}
// TODO: start image conversion of video_frame async to gem.vc_attribute.video_data_out
const auto& shared_data = g_fxo->get<gem_camera_shared>();
gem.video_data_in.resize(shared_data.size);
std::memcpy(gem.video_data_in.data(), video_frame.get_ptr(), gem.video_data_in.size());
gem.video_conversion_in_progress = true;
return CELL_OK;
}
@ -753,11 +970,6 @@ error_code cellGemGetCameraState(vm::ptr<CellGemCameraState> camera_state)
auto& gem = g_fxo->get<gem_config>();
if (!gem.state)
{
return CELL_GEM_ERROR_UNINITIALIZED;
}
if (!camera_state)
{
return CELL_GEM_ERROR_INVALID_PARAMETER;
@ -879,7 +1091,7 @@ error_code cellGemGetInertialState(u32 gem_num, u32 state_flag, u64 timestamp, v
return CELL_GEM_ERROR_UNINITIALIZED;
}
if (!check_gem_num(gem_num) || state_flag > CELL_GEM_INERTIAL_STATE_FLAG_NEXT || !inertial_state || !gem.is_controller_ready(gem_num))
if (!check_gem_num(gem_num) || !inertial_state || !gem.is_controller_ready(gem_num))
{
return CELL_GEM_ERROR_INVALID_PARAMETER;
}
@ -1303,12 +1515,17 @@ s32 cellGemIsTrackableHue(u32 hue)
auto& gem = g_fxo->get<gem_config>();
if (!gem.state || hue > 359)
if (!gem.state)
{
return false;
return CELL_GEM_ERROR_UNINITIALIZED;
}
return true;
if (hue > 359)
{
return CELL_GEM_ERROR_INVALID_PARAMETER;
}
return 1;
}
error_code cellGemPrepareCamera(s32 max_exposure, f32 image_quality)
@ -1353,27 +1570,34 @@ error_code cellGemPrepareVideoConvert(vm::cptr<CellGemVideoConvertAttribute> vc_
const CellGemVideoConvertAttribute vc = *vc_attribute;
if (!vc_attribute || vc.version != CELL_GEM_VERSION)
if (vc.version != CELL_GEM_VERSION)
{
return CELL_GEM_ERROR_INVALID_PARAMETER;
}
if (vc.output_format != CELL_GEM_NO_VIDEO_OUTPUT)
{
if (!vc.video_data_out || ((vc.conversion_flags & CELL_GEM_COMBINE_PREVIOUS_INPUT_FRAME) && !vc.buffer_memory))
if (!vc.video_data_out)
{
return CELL_GEM_ERROR_INVALID_PARAMETER;
}
}
// TODO: The alignment checks seem to break Time Crisis Razing Storm [BLUS30528]
//if (!vc.video_data_out.aligned(16) || !vc.buffer_memory.aligned(128))
//{
// return CELL_GEM_ERROR_INVALID_ALIGNMENT;
//}
if ((vc.conversion_flags & CELL_GEM_COMBINE_PREVIOUS_INPUT_FRAME) && !vc.buffer_memory)
{
return CELL_GEM_ERROR_INVALID_PARAMETER;
}
if (!vc.video_data_out.aligned(128) || !vc.buffer_memory.aligned(16))
{
return CELL_GEM_ERROR_INVALID_ALIGNMENT;
}
gem.vc_attribute = vc;
const s32 buffer_size = cellGemGetVideoConvertSize(vc.output_format);
gem.video_data_out_size = buffer_size;
return CELL_OK;
}
@ -1468,7 +1692,7 @@ error_code cellGemSetYaw(u32 gem_num, vm::ptr<f32> z_direction)
return CELL_GEM_ERROR_UNINITIALIZED;
}
if (!z_direction)
if (!z_direction || !check_gem_num(gem_num))
{
return CELL_GEM_ERROR_INVALID_PARAMETER;
}
@ -1571,7 +1795,7 @@ error_code cellGemUpdateFinish()
std::scoped_lock lock(gem.mtx);
if (!std::exchange(gem.update_started, false))
if (!gem.update_started.exchange(false))
{
return CELL_GEM_ERROR_UPDATE_NOT_STARTED;
}
@ -1598,16 +1822,16 @@ error_code cellGemUpdateStart(vm::cptr<void> camera_frame, u64 timestamp)
std::scoped_lock lock(gem.mtx);
// Update is starting even when camera_frame is null
if (std::exchange(gem.update_started, true))
if (gem.update_started.exchange(true))
{
return CELL_GEM_ERROR_UPDATE_NOT_FINISHED;
}
// TODO: The alignment checks seem to break Time Crisis Razing Storm [BLUS30528]
//if (!camera_frame.aligned(128))
//{
// return CELL_GEM_ERROR_INVALID_ALIGNMENT;
//}
if (!camera_frame.aligned(128))
{
return CELL_GEM_ERROR_INVALID_ALIGNMENT;
}
gem.camera_frame = camera_frame.addr();
if (!camera_frame)

View File

@ -145,7 +145,7 @@ enum
};
// Video conversion output formats
enum
enum CellGemVideoConvertFormatEnum : s32
{
CELL_GEM_NO_VIDEO_OUTPUT = 1,
CELL_GEM_RGBA_640x480 = 2,
@ -261,13 +261,13 @@ struct CellGemState
struct CellGemVideoConvertAttribute
{
be_t<s32> version;
be_t<s32> output_format;
be_t<CellGemVideoConvertFormatEnum> output_format;
be_t<s32> conversion_flags;
be_t<f32> gain;
be_t<f32> red_gain;
be_t<f32> green_gain;
be_t<f32> blue_gain;
vm::ptr<void> buffer_memory;
vm::ptr<void> video_data_out;
vm::bptr<u8> buffer_memory;
vm::bptr<u8> video_data_out;
u8 alpha;
};