From be972f04ac0eeec85ff69ad063acfaa1dfbed429 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Wed, 20 Oct 2021 19:47:19 +0200 Subject: [PATCH] cellGem: hook up camera --- rpcs3/Emu/Cell/Modules/cellCamera.cpp | 107 +++++++--- rpcs3/Emu/Cell/Modules/cellCamera.h | 9 +- rpcs3/Emu/Cell/Modules/cellGem.cpp | 292 +++++++++++++++++++++++--- rpcs3/Emu/Cell/Modules/cellGem.h | 8 +- 4 files changed, 343 insertions(+), 73 deletions(-) diff --git a/rpcs3/Emu/Cell/Modules/cellCamera.cpp b/rpcs3/Emu/Cell/Modules/cellCamera.cpp index eef3e83201..7f372b3c7c 100644 --- a/rpcs3/Emu/Cell/Modules/cellCamera.cpp +++ b/rpcs3/Emu/Cell/Modules/cellCamera.cpp @@ -36,6 +36,27 @@ void fmt_class_string::format(std::string& out, u64 arg) }); } +template <> +void fmt_class_string::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 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 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(); 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,41 +1328,47 @@ error_code cellCameraReadEx(s32 dev_num, vm::ptr 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) { - u32 width{}; - u32 height{}; - u64 frame_number{}; - u64 bytes_read{}; - - if (!g_camera.get_camera_frame(g_camera.info.buffer.get_ptr(), width, height, frame_number, bytes_read)) + if (has_new_frame) { - return CELL_CAMERA_ERROR_DEVICE_NOT_FOUND; - } + u32 width{}; + u32 height{}; + u64 frame_number{}; + u64 bytes_read{}; - if (read) - { - read->frame = frame_number; - read->bytesread = bytes_read; - } + if (!g_camera.get_camera_frame(g_camera.info.buffer.get_ptr(), width, height, frame_number, bytes_read)) + { + return CELL_CAMERA_ERROR_DEVICE_NOT_FOUND; + } - 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); + 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->frame = g_camera.frame_num; - read->bytesread = g_camera.is_streaming ? get_video_buffer_size(g_camera.info) : 0; - } + read->timestamp = g_camera.frame_timestamp; + read->frame = g_camera.frame_num; + read->bytesread = g_camera.bytes_read; auto& shared_data = g_fxo->get(); - 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) { diff --git a/rpcs3/Emu/Cell/Modules/cellCamera.h b/rpcs3/Emu/Cell/Modules/cellCamera.h index 902c490803..7a9ec60c7c 100644 --- a/rpcs3/Emu/Cell/Modules/cellCamera.h +++ b/rpcs3/Emu/Cell/Modules/cellCamera.h @@ -360,7 +360,7 @@ struct CellCameraInfo struct CellCameraInfoEx { - be_t format; // CellCameraFormat + be_t format; // CellCameraFormat be_t resolution; // CellCameraResolution be_t framerate; @@ -435,7 +435,10 @@ public: u32 v1, v2; }; attr_t attr[500]{}; + atomic_t has_new_frame = false; atomic_t frame_num = 0; + atomic_t frame_timestamp = 0; + atomic_t bytes_read = 0; atomic_t init = 0; @@ -458,4 +461,8 @@ struct gem_camera_shared atomic_t frame_timestamp{}; // latest read timestamp from cellCamera (cellCameraRead(Ex)) atomic_t width{640}; atomic_t height{480}; + atomic_t size{0}; + atomic_t format{CELL_CAMERA_RAW8}; }; + +static inline s32 get_video_buffer_size(s32 width, s32 height); diff --git a/rpcs3/Emu/Cell/Modules/cellGem.cpp b/rpcs3/Emu/Cell/Modules/cellGem.cpp index aba700babe..c156bb524e 100644 --- a/rpcs3/Emu/Cell/Modules/cellGem.cpp +++ b/rpcs3/Emu/Cell/Modules/cellGem.cpp @@ -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::format(std::string& out, u64 arg) }); } +template <> +void fmt_class_string::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 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 video_data_in; u64 status_flags = 0; bool enable_pitch_correction = false; u32 inertial_counter = 0; std::array controllers; u32 connected_controllers = 0; - bool video_conversion_started{}; - bool update_started{}; + atomic_t video_conversion_in_progress{false}; + atomic_t 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(); + + 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(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(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(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; + /** * \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 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.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 camera_state) auto& gem = g_fxo->get(); - 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(); - 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 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 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 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) diff --git a/rpcs3/Emu/Cell/Modules/cellGem.h b/rpcs3/Emu/Cell/Modules/cellGem.h index e0d11712e8..9867ec9f8e 100644 --- a/rpcs3/Emu/Cell/Modules/cellGem.h +++ b/rpcs3/Emu/Cell/Modules/cellGem.h @@ -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 version; - be_t output_format; + be_t output_format; be_t conversion_flags; be_t gain; be_t red_gain; be_t green_gain; be_t blue_gain; - vm::ptr buffer_memory; - vm::ptr video_data_out; + vm::bptr buffer_memory; + vm::bptr video_data_out; u8 alpha; };