From 504e3112ddc440f1d4eb4f236d4d39ab0f016513 Mon Sep 17 00:00:00 2001 From: Nick Renieris Date: Fri, 2 Mar 2018 18:51:21 +0200 Subject: [PATCH] [HLE] First steps to Playstation Move (#4083) * [sysutil] Add Magnetometer system param * [ui] Add UI for Move handler Current options are "Null" and "Fake". * cellGem: Improvements * cellCamera: Improvements --- rpcs3/Emu/Cell/Modules/cellCamera.cpp | 555 ++++++++++++++++++--- rpcs3/Emu/Cell/Modules/cellCamera.h | 83 +++- rpcs3/Emu/Cell/Modules/cellGem.cpp | 662 +++++++++++++++++++++++-- rpcs3/Emu/Cell/Modules/cellGem.h | 21 +- rpcs3/Emu/Cell/Modules/cellSysutil.cpp | 12 +- rpcs3/Emu/Cell/Modules/cellSysutil.h | 1 + rpcs3/Emu/System.h | 7 + rpcs3/Json/tooltips.json | 3 +- rpcs3/rpcs3qt/emu_settings.h | 4 +- rpcs3/rpcs3qt/settings_dialog.cpp | 3 + rpcs3/rpcs3qt/settings_dialog.ui | 22 +- 11 files changed, 1218 insertions(+), 155 deletions(-) diff --git a/rpcs3/Emu/Cell/Modules/cellCamera.cpp b/rpcs3/Emu/Cell/Modules/cellCamera.cpp index 0276f198ac..3c147c4414 100644 --- a/rpcs3/Emu/Cell/Modules/cellCamera.cpp +++ b/rpcs3/Emu/Cell/Modules/cellCamera.cpp @@ -1,12 +1,26 @@ #include "stdafx.h" -#include "Emu/IdManager.h" -#include "Emu/System.h" -#include "Emu/Cell/PPUModule.h" - #include "cellCamera.h" +#include "Emu/Cell/PPUModule.h" +#include "Emu/Cell/lv2/sys_event.h" +#include "Emu/IdManager.h" +#include "Emu/Io/PadHandler.h" +#include "Emu/System.h" + +#include + logs::channel cellCamera("cellCamera"); +// ************** +// * Prototypes * +// ************** + +s32 cellCameraSetAttribute(s32 dev_num, s32 attrib, u32 arg1, u32 arg2); + +// ************************ +// * HLE helper functions * +// ************************ + template <> void fmt_class_string::format(std::string& out, u64 arg) { @@ -97,71 +111,126 @@ static const char* get_camera_attr_name(s32 value) return nullptr; } -// Custom struct to keep track of cameras -struct camera_t +static bool check_dev_num(u32 dev_num) { - struct attr_t - { - u32 v1, v2; - }; + return dev_num == 0; +} - attr_t attr[500]{}; -}; +/** + * \brief Sets read mode attribute (used for deciding how image data is passed to games) + * Also sends it to the camera thread + * NOTE: thread-safe (uses camera_thread::mutex) + * \param dev_num Device number (always 0) + * \param read_mode Either CELL_CAMERA_READ_FUNCCALL or CELL_CAMERA_READ_DIRECT + * \return CELL error code or CELL_OK + */ +u32 set_and_send_read_mode(s32 dev_num, const s32 read_mode) +{ + if (read_mode == CELL_CAMERA_READ_FUNCCALL || + read_mode == CELL_CAMERA_READ_DIRECT) + { + if (const auto status = cellCameraSetAttribute(dev_num, CELL_CAMERA_READMODE, read_mode, 0)) + { + return status; + } + } + else + { + cellCamera.error("Unknown read mode set: %d", read_mode); + } + + // Send read mode to camera thread + const auto g_camera = fxm::get(); + + g_camera->read_mode.exchange(read_mode); + + return CELL_OK; +} + +std::pair get_video_resolution(const CellCameraInfoEx& info) +{ + std::pair res; + switch (info.resolution) + { + case CELL_CAMERA_VGA: return{ 640, 480 }; + case CELL_CAMERA_QVGA: return { 320, 240 }; + case CELL_CAMERA_WGA: return{ 640, 360 }; + case CELL_CAMERA_SPECIFIED_WIDTH_HEIGHT: return{ info.width, info.height }; + case CELL_CAMERA_RESOLUTION_UNKNOWN: + default: return{ 0, 0 }; + } +} + +u32 get_video_buffer_size(const CellCameraInfoEx& info) +{ + u32 width, height; + std::tie(width, height) = get_video_resolution(info); + + const auto bpp = 4; + return width * height * bpp; +} + +// ************************ +// * cellCamera functions * +// ************************ s32 cellCameraInit() { - cellCamera.warning("cellCameraInit()"); + cellCamera.todo("cellCameraInit()"); if (g_cfg.io.camera == camera_handler::null) { return CELL_CAMERA_ERROR_DEVICE_NOT_FOUND; } - const auto camera = fxm::make(); + // Start camera thread + const auto g_camera = fxm::make(); - if (!camera) + if (!g_camera) { return CELL_CAMERA_ERROR_ALREADY_INIT; } + semaphore_lock lock(g_camera->mutex); + switch (g_cfg.io.camera_type) { case fake_camera_type::eyetoy: { - camera->attr[CELL_CAMERA_SATURATION] = { 164 }; - camera->attr[CELL_CAMERA_BRIGHTNESS] = { 96 }; - camera->attr[CELL_CAMERA_AEC] = { 1 }; - camera->attr[CELL_CAMERA_AGC] = { 1 }; - camera->attr[CELL_CAMERA_AWB] = { 1 }; - camera->attr[CELL_CAMERA_ABC] = { 0 }; - camera->attr[CELL_CAMERA_LED] = { 1 }; - camera->attr[CELL_CAMERA_QS] = { 0 }; - camera->attr[CELL_CAMERA_NONZEROCOEFFS] = { 32, 32 }; - camera->attr[CELL_CAMERA_YUVFLAG] = { 0 }; - camera->attr[CELL_CAMERA_BACKLIGHTCOMP] = { 0 }; - camera->attr[CELL_CAMERA_MIRRORFLAG] = { 1 }; - camera->attr[CELL_CAMERA_422FLAG] = { 1 }; - camera->attr[CELL_CAMERA_USBLOAD] = { 4 }; + g_camera->attr[CELL_CAMERA_SATURATION] = { 164 }; + g_camera->attr[CELL_CAMERA_BRIGHTNESS] = { 96 }; + g_camera->attr[CELL_CAMERA_AEC] = { 1 }; + g_camera->attr[CELL_CAMERA_AGC] = { 1 }; + g_camera->attr[CELL_CAMERA_AWB] = { 1 }; + g_camera->attr[CELL_CAMERA_ABC] = { 0 }; + g_camera->attr[CELL_CAMERA_LED] = { 1 }; + g_camera->attr[CELL_CAMERA_QS] = { 0 }; + g_camera->attr[CELL_CAMERA_NONZEROCOEFFS] = { 32, 32 }; + g_camera->attr[CELL_CAMERA_YUVFLAG] = { 0 }; + g_camera->attr[CELL_CAMERA_BACKLIGHTCOMP] = { 0 }; + g_camera->attr[CELL_CAMERA_MIRRORFLAG] = { 1 }; + g_camera->attr[CELL_CAMERA_422FLAG] = { 1 }; + g_camera->attr[CELL_CAMERA_USBLOAD] = { 4 }; break; } case fake_camera_type::eyetoy2: { - camera->attr[CELL_CAMERA_SATURATION] = { 64 }; - camera->attr[CELL_CAMERA_BRIGHTNESS] = { 8 }; - camera->attr[CELL_CAMERA_AEC] = { 1 }; - camera->attr[CELL_CAMERA_AGC] = { 1 }; - camera->attr[CELL_CAMERA_AWB] = { 1 }; - camera->attr[CELL_CAMERA_LED] = { 1 }; - camera->attr[CELL_CAMERA_BACKLIGHTCOMP] = { 0 }; - camera->attr[CELL_CAMERA_MIRRORFLAG] = { 1 }; - camera->attr[CELL_CAMERA_GAMMA] = { 1 }; - camera->attr[CELL_CAMERA_AGCLIMIT] = { 4 }; - camera->attr[CELL_CAMERA_DENOISE] = { 0 }; - camera->attr[CELL_CAMERA_FRAMERATEADJUST] = { 0 }; - camera->attr[CELL_CAMERA_PIXELOUTLIERFILTER] = { 1 }; - camera->attr[CELL_CAMERA_AGCLOW] = { 48 }; - camera->attr[CELL_CAMERA_AGCHIGH] = { 64 }; + g_camera->attr[CELL_CAMERA_SATURATION] = { 64 }; + g_camera->attr[CELL_CAMERA_BRIGHTNESS] = { 8 }; + g_camera->attr[CELL_CAMERA_AEC] = { 1 }; + g_camera->attr[CELL_CAMERA_AGC] = { 1 }; + g_camera->attr[CELL_CAMERA_AWB] = { 1 }; + g_camera->attr[CELL_CAMERA_LED] = { 1 }; + g_camera->attr[CELL_CAMERA_BACKLIGHTCOMP] = { 0 }; + g_camera->attr[CELL_CAMERA_MIRRORFLAG] = { 1 }; + g_camera->attr[CELL_CAMERA_GAMMA] = { 1 }; + g_camera->attr[CELL_CAMERA_AGCLIMIT] = { 4 }; + g_camera->attr[CELL_CAMERA_DENOISE] = { 0 }; + g_camera->attr[CELL_CAMERA_FRAMERATEADJUST] = { 0 }; + g_camera->attr[CELL_CAMERA_PIXELOUTLIERFILTER] = { 1 }; + g_camera->attr[CELL_CAMERA_AGCLOW] = { 48 }; + g_camera->attr[CELL_CAMERA_AGCHIGH] = { 64 }; break; } default: @@ -175,9 +244,9 @@ s32 cellCameraInit() s32 cellCameraEnd() { - cellCamera.warning("cellCameraEnd()"); + cellCamera.todo("cellCameraEnd()"); - if (!fxm::remove()) + if (!fxm::remove()) { return CELL_CAMERA_ERROR_NOT_INIT; } @@ -185,7 +254,7 @@ s32 cellCameraEnd() return CELL_OK; } -s32 cellCameraOpen() +s32 cellCameraOpen() // seems unused { UNIMPLEMENTED_FUNC(cellCamera); return CELL_OK; @@ -193,13 +262,63 @@ s32 cellCameraOpen() s32 cellCameraOpenEx(s32 dev_num, vm::ptr info) { - UNIMPLEMENTED_FUNC(cellCamera); + cellCamera.todo("cellCameraOpenEx(dev_num=%d, type=*0x%x)", dev_num, info); + + const auto g_camera = fxm::get(); + + if (!g_camera) + { + return CELL_CAMERA_ERROR_NOT_INIT; + } + + s32 read_mode = info->read_mode; + + u32 status = set_and_send_read_mode(dev_num, read_mode); + if (status != CELL_OK) + { + return status; + } + + status = cellCameraSetAttribute(dev_num, CELL_CAMERA_GAMEPID, status, 0); // yup, that's what libGem does + if (status != CELL_OK) + { + return status; + } + + const auto vbuf_size = get_video_buffer_size(*info); + + if (info->read_mode == CELL_CAMERA_READ_FUNCCALL && !info->buffer) + { + info->buffer = vm::cast(vm::alloc(vbuf_size, vm::memory_location_t::main)); + info->bytesize = vbuf_size; + } + + std::tie(info->width, info->height) = get_video_resolution(*info); + + semaphore_lock lock(g_camera->mutex); + + g_camera->is_open = true; + g_camera->info = *info; + return CELL_OK; } s32 cellCameraClose(s32 dev_num) { - UNIMPLEMENTED_FUNC(cellCamera); + cellCamera.todo("cellCameraClose(dev_num=%d)", dev_num); + + const auto g_camera = fxm::get(); + + if (!g_camera) + { + return CELL_CAMERA_ERROR_NOT_INIT; + } + + semaphore_lock lock(g_camera->mutex); + + vm::dealloc(g_camera->info.buffer.addr(), vm::memory_location_t::main); + g_camera->is_open = false; + return CELL_OK; } @@ -211,15 +330,20 @@ s32 cellCameraGetDeviceGUID(s32 dev_num, vm::ptr guid) s32 cellCameraGetType(s32 dev_num, vm::ptr type) { - cellCamera.warning("cellCameraGetType(dev_num=%d, type=*0x%x)", dev_num, type); + cellCamera.todo("cellCameraGetType(dev_num=%d, type=*0x%x)", dev_num, type); - const auto camera = fxm::get(); + const auto g_camera = fxm::get(); - if (!camera) + if (!g_camera) { return CELL_CAMERA_ERROR_NOT_INIT; } + if (!check_dev_num(dev_num) || !type ) + { + return CELL_CAMERA_ERROR_PARAM; + } + switch (g_cfg.io.camera_type) { case fake_camera_type::unknown: *type = CELL_CAMERA_TYPE_UNKNOWN; break; @@ -234,42 +358,80 @@ s32 cellCameraGetType(s32 dev_num, vm::ptr type) s32 cellCameraIsAvailable(s32 dev_num) { cellCamera.todo("cellCameraIsAvailable(dev_num=%d)", dev_num); - return CELL_OK; + + const auto g_camera = fxm::get(); + + if (!g_camera || !check_dev_num(dev_num)) + { + return false; + } + + return true; } s32 cellCameraIsAttached(s32 dev_num) { - cellCamera.warning("cellCameraIsAttached(dev_num=%d)", dev_num); + cellCamera.todo("cellCameraIsAttached(dev_num=%d)", dev_num); - if (g_cfg.io.camera == camera_handler::fake) + if (g_cfg.io.camera == camera_handler::null) { - return 1; + return false; } - return 0; // It's not CELL_OK lol + const auto g_camera = fxm::get(); + + semaphore_lock lock(g_camera->mutex); + + bool is_attached = g_camera->is_attached; + + // "attach" camera here + if (!is_attached) + { + g_camera->send_attach_state(true); + is_attached = g_camera->is_attached; + } + + return is_attached; } s32 cellCameraIsOpen(s32 dev_num) { cellCamera.todo("cellCameraIsOpen(dev_num=%d)", dev_num); - return CELL_OK; + + if (g_cfg.io.camera == camera_handler::null) + { + return false; + } + + const auto g_camera = fxm::get(); + + bool is_open = g_camera->is_open; + return is_open; } s32 cellCameraIsStarted(s32 dev_num) { cellCamera.todo("cellCameraIsStarted(dev_num=%d)", dev_num); - return CELL_OK; + + if (g_cfg.io.camera == camera_handler::null) + { + return false; + } + + const auto g_camera = fxm::get(); + + bool is_streaming = g_camera->is_streaming; + return is_streaming; } s32 cellCameraGetAttribute(s32 dev_num, s32 attrib, vm::ptr arg1, vm::ptr arg2) { - cellCamera.warning("cellCameraGetAttribute(dev_num=%d, attrib=%d, arg1=*0x%x, arg2=*0x%x)", dev_num, attrib, arg1, arg2); - const auto attr_name = get_camera_attr_name(attrib); + cellCamera.todo("cellCameraGetAttribute: get attrib %s to: 0x%x - 0x%x)", attr_name ? attr_name : "(invalid)", arg1, arg2); - const auto camera = fxm::get(); + const auto g_camera = fxm::get(); - if (!camera) + if (!g_camera) { return CELL_CAMERA_ERROR_NOT_INIT; } @@ -279,21 +441,30 @@ s32 cellCameraGetAttribute(s32 dev_num, s32 attrib, vm::ptr arg1, vm::ptrattr[attrib].v1; - *arg2 = camera->attr[attrib].v2; + cellCamera.todo("cellCameraGetAttribute: get attrib %s arg1: %d arg2: %d", attr_name, arg1, arg2); + + semaphore_lock lock(g_camera->mutex); + + if (arg1) + { + *arg1 = g_camera->attr[attrib].v1; + } + if (arg2) + { + *arg2 = g_camera->attr[attrib].v2; + } return CELL_OK; } s32 cellCameraSetAttribute(s32 dev_num, s32 attrib, u32 arg1, u32 arg2) { - cellCamera.warning("cellCameraSetAttribute(dev_num=%d, attrib=%d, arg1=%d, arg2=%d)", dev_num, attrib, arg1, arg2); - const auto attr_name = get_camera_attr_name(attrib); + cellCamera.todo("cellCameraSetAttribute: set attrib %s to: %d - %d)", attr_name ? attr_name : "(invalid)", arg1, arg2); - const auto camera = fxm::get(); + const auto g_camera = fxm::get(); - if (!camera) + if (!g_camera) { return CELL_CAMERA_ERROR_NOT_INIT; } @@ -303,15 +474,45 @@ s32 cellCameraSetAttribute(s32 dev_num, s32 attrib, u32 arg1, u32 arg2) return CELL_CAMERA_ERROR_PARAM; } - camera->attr[attrib] = { arg1, arg2 }; + semaphore_lock lock(g_camera->mutex); + g_camera->attr[attrib] = { arg1, arg2 }; return CELL_OK; } s32 cellCameraGetBufferSize(s32 dev_num, vm::ptr info) { - UNIMPLEMENTED_FUNC(cellCamera); - return CELL_OK; + cellCamera.todo("cellCameraGetBufferSize(dev_num=%d, info=*0x%x)", dev_num, info); + + const auto g_camera = fxm::get(); + + if (!g_camera) + { + return CELL_CAMERA_ERROR_NOT_INIT; + } + + // TODO: a bunch of arg checks. here's one + if (!cellCameraIsAttached(dev_num)) + { + return CELL_CAMERA_ERROR_DEVICE_NOT_FOUND; + } + + const auto read_mode = info->read_mode; + + u32 status = set_and_send_read_mode(dev_num, read_mode); + if (status != CELL_OK) + { + return status; + } + + semaphore_lock lock(g_camera->mutex); + + info->bytesize = get_video_buffer_size(g_camera->info); + info->buffer = g_camera->info.buffer; + + g_camera->info = *info; + + return info->bytesize; } s32 cellCameraGetBufferInfo() @@ -322,7 +523,24 @@ s32 cellCameraGetBufferInfo() s32 cellCameraGetBufferInfoEx(s32 dev_num, vm::ptr info) { - UNIMPLEMENTED_FUNC(cellCamera); + cellCamera.todo("cellCameraReadEx(dev_num=%d, read=0x%x)", dev_num, info); + + const auto g_camera = fxm::get(); + + if (!g_camera) + { + return CELL_CAMERA_ERROR_NOT_INIT; + } + + if (!info) + { + return CELL_CAMERA_ERROR_PARAM; + } + + semaphore_lock lock(g_camera->mutex); + + *info = g_camera->info; + return CELL_OK; } @@ -353,12 +571,21 @@ s32 cellCameraSetExtensionUnit(s32 dev_num, u16 value, u16 length, vm::ptr d s32 cellCameraReset(s32 dev_num) { UNIMPLEMENTED_FUNC(cellCamera); + return CELL_OK; } s32 cellCameraStart(s32 dev_num) { - UNIMPLEMENTED_FUNC(cellCamera); + cellCamera.todo("cellCameraStart(dev_num=%d", dev_num); + + const auto g_camera = fxm::get(); + + semaphore_lock lock(g_camera->mutex); + + g_camera->timer.Start(); + g_camera->is_streaming = true; + return CELL_OK; } @@ -370,19 +597,46 @@ s32 cellCameraRead(s32 dev_num, vm::ptr frame_num, vm::ptr bytes_read) s32 cellCameraReadEx(s32 dev_num, vm::ptr read) { - UNIMPLEMENTED_FUNC(cellCamera); + cellCamera.todo("cellCameraReadEx(dev_num=%d, read=0x%x)", dev_num, read); + + const auto g_camera = fxm::get(); + + if (!g_camera) + { + return CELL_CAMERA_ERROR_NOT_INIT; + } + + semaphore_lock lock(g_camera->mutex); + + read->timestamp = g_camera->timer.GetElapsedTimeInMicroSec(); + read->frame = g_camera->frame_num; + read->bytesread = g_camera->is_streaming ? + get_video_buffer_size(g_camera->info) : 0; + + auto shared_data = fxm::get_always(); + + shared_data->frame_timestamp.exchange(read->timestamp); + return CELL_OK; } s32 cellCameraReadComplete(s32 dev_num, u32 bufnum, u32 arg2) { - UNIMPLEMENTED_FUNC(cellCamera); - return CELL_OK; + cellCamera.todo("cellCameraReadComplete(dev_num=%d, bufnum=%d, arg2=%d)", dev_num, bufnum, arg2); + return cellCameraSetAttribute(dev_num, CELL_CAMERA_READFINISH, bufnum, arg2); } s32 cellCameraStop(s32 dev_num) { - UNIMPLEMENTED_FUNC(cellCamera); + cellCamera.todo("cellCameraStop(dev_num=%d", dev_num); + + const auto g_camera = fxm::get(); + + semaphore_lock lock(g_camera->mutex); + + g_camera->is_streaming = false; + g_camera->timer.Stop(); + return CELL_OK; } @@ -400,13 +654,43 @@ s32 cellCameraRemoveNotifyEventQueue(u64 key) s32 cellCameraSetNotifyEventQueue2(u64 key, u64 source, u64 flag) { - UNIMPLEMENTED_FUNC(cellCamera); + cellCamera.todo("cellCameraSetNotifyEventQueue2(key=0x%x, source=%d, flag=%d)", key, source, flag); + + const auto g_camera = fxm::get(); + + if (!g_camera) + { + return CELL_CAMERA_ERROR_NOT_INIT; + } + { + semaphore_lock lock_data_map(g_camera->mutex_notify_data_map); + + g_camera->notify_data_map[key] = { source, flag }; + } + { + semaphore_lock lock(g_camera->mutex); + + // send ATTACH event if necessary - HACKY + g_camera->send_attach_state(true); + } return CELL_OK; } s32 cellCameraRemoveNotifyEventQueue2(u64 key) { - UNIMPLEMENTED_FUNC(cellCamera); + cellCamera.todo("cellCameraRemoveNotifyEventQueue2(key=0x%x", key); + + const auto g_camera = fxm::get(); + + if (!g_camera) + { + return CELL_CAMERA_ERROR_NOT_INIT; + } + + semaphore_lock lock(g_camera->mutex_notify_data_map); + + g_camera->notify_data_map.erase(key); + return CELL_OK; } @@ -447,3 +731,114 @@ DECLARE(ppu_module_manager::cellCamera)("cellCamera", []() REG_FUNC(cellCamera, cellCameraSetNotifyEventQueue2); REG_FUNC(cellCamera, cellCameraRemoveNotifyEventQueue2); }); + +void camera_thread::on_task() +{ + while (fxm::check() && !Emu.IsStopped()) + { + std::chrono::steady_clock::time_point frame_start = std::chrono::steady_clock::now(); + + if (Emu.IsPaused()) + { + std::this_thread::sleep_for(1ms); // hack + continue; + } + + semaphore_lock lock(mutex_notify_data_map); + + for (auto const& notify_data_entry : notify_data_map) + { + const auto& key = notify_data_entry.first; + const auto& evt_data = notify_data_entry.second; + + // handle FRAME_UPDATE + if (is_streaming && + evt_data.flag & CELL_CAMERA_EFLAG_FRAME_UPDATE && + info.framerate != 0) + { + if (auto queue = lv2_event_queue::find(key)) + { + u64 data2{ 0 }; + u64 data3{ 0 }; + + switch (read_mode.load()) + { + case CELL_CAMERA_READ_FUNCCALL: + { + data2 = 0; // device id (always 0) + data3 = 0; // unused + break; + } + case CELL_CAMERA_READ_DIRECT: + { + const u64 image_data_size = static_cast(info.bytesize); + const u64 buffer_number = 0; + const u64 camera_id = 0; + + data2 = image_data_size << 32 | buffer_number << 16 | camera_id; + data3 = timer.GetElapsedTimeInMicroSec(); // timestamp + break; + } + default: + { + cellCamera.error("Unknown read mode set: %d. This should never happen.", read_mode.load()); + return; + } + } + + const auto send_status = queue->send(evt_data.source, CELL_CAMERA_FRAME_UPDATE, data2, data3); + if (LIKELY(send_status)) + { + ++frame_num; + } + } + } + + } + + const std::chrono::microseconds frame_target_time{ static_cast(1000000.0 / info.framerate) }; + + std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now(); + + std::chrono::microseconds frame_processing_time = std::chrono::duration_cast(now - frame_start); + + if (frame_processing_time < frame_target_time) + { + std::chrono::microseconds frame_idle_time = frame_target_time - frame_processing_time; + std::this_thread::sleep_for(frame_idle_time); + } + } +} + +void camera_thread::on_init(const std::shared_ptr& _this) +{ + named_thread::on_init(_this); +} + +void camera_thread::send_attach_state(bool attached) +{ + semaphore_lock lock(mutex_notify_data_map); + + if (!notify_data_map.empty()) + { + for (auto const& notify_data_entry : notify_data_map) + { + const auto& key = notify_data_entry.first; + const auto& evt_data = notify_data_entry.second; + + if (auto queue = lv2_event_queue::find(key)) + { + const auto send_result = queue->send(evt_data.source, attached ? CELL_CAMERA_ATTACH : CELL_CAMERA_DETACH, 0, 0); + if (LIKELY(send_result)) + { + is_attached = attached; + } + } + } + } + else + { + // We're not expected to send any events for attaching/detaching + is_attached = attached; + } +} diff --git a/rpcs3/Emu/Cell/Modules/cellCamera.h b/rpcs3/Emu/Cell/Modules/cellCamera.h index 2aafef959f..4bed0f5efb 100644 --- a/rpcs3/Emu/Cell/Modules/cellCamera.h +++ b/rpcs3/Emu/Cell/Modules/cellCamera.h @@ -1,5 +1,11 @@ #pragma once +#include "Utilities/Timer.h" +#include "Emu/Cell/lv2/sys_memory.h" +#include "Utilities/sema.h" +#include "Utilities/Thread.h" + +#include // Error Codes @@ -20,7 +26,7 @@ enum CELL_CAMERA_ERROR_FATAL = 0x8014080f, }; -// Event types +// Event masks enum { CELL_CAMERA_EFLAG_FRAME_UPDATE = 0x00000001, @@ -31,6 +37,26 @@ enum CELL_CAMERA_EFLAG_RESET = 0x00000020, }; +// Event types +enum +{ + CELL_CAMERA_DETACH = 0, + CELL_CAMERA_ATTACH = 1, + CELL_CAMERA_FRAME_UPDATE = 2, + CELL_CAMERA_OPEN = 3, + CELL_CAMERA_CLOSE = 4, + CELL_CAMERA_START = 5, + CELL_CAMERA_STOP = 6, + CELL_CAMERA_RESET = 7 +}; + +// Read mode +enum +{ + CELL_CAMERA_READ_FUNCCALL = 0, + CELL_CAMERA_READ_DIRECT = 1, +}; + // Colormatching enum { @@ -286,14 +312,14 @@ enum CellCameraAttribute : s32 struct CellCameraInfoEx { - be_t format; // CellCameraFormat + be_t format; // CellCameraFormat be_t resolution; // CellCameraResolution be_t framerate; vm::bptr buffer; be_t bytesize; - be_t width; - be_t height; + be_t width; // only used if resolution == CELL_CAMERA_SPECIFIED_WIDTH_HEIGHT + be_t height; // likewise be_t dev_num; be_t guid; @@ -311,3 +337,52 @@ struct CellCameraReadEx be_t timestamp; vm::bptr pbuf; }; + +class camera_thread final : public named_thread +{ +private: + struct notify_event_data + { + u64 source; + u64 flag; + }; + + void on_task() override; + + std::string get_name() const override { return "Camera Thread"; } + +public: + void on_init(const std::shared_ptr&) override; + void send_attach_state(bool attached); + + std::map notify_data_map; + + semaphore<> mutex; + semaphore<> mutex_notify_data_map; + Timer timer; + + atomic_t read_mode; + atomic_t is_streaming; + atomic_t is_attached; + atomic_t is_open; + + CellCameraInfoEx info; + + struct attr_t + { + u32 v1, v2; + }; + attr_t attr[500]{}; + + lv2_memory_container container; + atomic_t frame_num; + + camera_thread() : read_mode(CELL_CAMERA_READ_FUNCCALL) {} + ~camera_thread() = default; +}; + +/// Shared data between cellGem and cellCamera +struct gem_camera_shared +{ + atomic_t frame_timestamp; // latest read timestamp from cellCamera (cellCameraRead(Ex)) +}; diff --git a/rpcs3/Emu/Cell/Modules/cellGem.cpp b/rpcs3/Emu/Cell/Modules/cellGem.cpp index 7e78357b6d..fab3c58fad 100644 --- a/rpcs3/Emu/Cell/Modules/cellGem.cpp +++ b/rpcs3/Emu/Cell/Modules/cellGem.cpp @@ -1,25 +1,326 @@ #include "stdafx.h" -#include "Emu/IdManager.h" -#include "Emu/Cell/PPUModule.h" - #include "cellGem.h" +#include "cellCamera.h" +#include "Emu/IdManager.h" +#include "Emu/System.h" +#include "Emu/Cell/PPUModule.h" +#include "pad_thread.h" +#include "Utilities/Timer.h" + logs::channel cellGem("cellGem"); +// ********************** +// * HLE helper structs * +// ********************** + struct gem_t { + struct gem_color + { + float r, g, b; + + gem_color() : r(0.0f), g(0.0f), b(0.0f) {} + gem_color(float r_, float g_, float b_) + { + r = clamp(r_); + g = clamp(g_); + b = clamp(b_); + } + + float clamp(float f) const + { + return std::max(0.0f, std::min(f, 1.0f)); + } + }; + + struct gem_controller + { + u32 status; // connection status (CELL_GEM_STATUS_DISCONNECTED or CELL_GEM_STATUS_READY) + u32 port; // assigned port + bool enabled_magnetometer; // whether the magnetometer is enabled (probably used for additional rotational precision) + bool calibrated_magnetometer; // whether the magnetometer is calibrated + bool enabled_filtering; // whether filtering is enabled + u8 rumble; // rumble intensity + gem_color sphere_rgb; // RGB color of the sphere LED + + gem_controller() : + status(CELL_GEM_STATUS_DISCONNECTED), + enabled_filtering(false), rumble(0), sphere_rgb() {} + }; + CellGemAttribute attribute; + CellGemVideoConvertAttribute vc_attribute; + u64 status_flags; + bool enable_pitch_correction; + u32 inertial_counter; + + std::array controllers; + u32 connected_controllers; + + Timer timer; + + // helper functions + bool is_controller_ready(u32 gem_num) const + { + return controllers[gem_num].status == CELL_GEM_STATUS_READY; + } + + void reset_controller(u32 gem_num) + { + switch (g_cfg.io.move) + { + default: + case move_handler::null: + { + connected_controllers = 0; + + controllers[gem_num].status = CELL_GEM_STATUS_DISCONNECTED; + controllers[gem_num].port = 0; + break; + } + + case move_handler::fake: + { + // fake one connected controller + connected_controllers = 1; + + if (gem_num < connected_controllers) + { + controllers[gem_num].status = CELL_GEM_STATUS_READY; + controllers[gem_num].port = 7u - gem_num; + } + else + { + controllers[gem_num].status = CELL_GEM_STATUS_DISCONNECTED; + controllers[gem_num].port = 0; + } + break; + } + } + } }; -s32 cellGemCalibrate() +// ************************ +// * HLE helper functions * +// ************************ + +template <> +void fmt_class_string::format(std::string& out, u64 arg) { - UNIMPLEMENTED_FUNC(cellGem); + format_enum(out, arg, [](auto value) + { + switch (value) + { + case move_handler::null: return "Null"; + case move_handler::fake: return "Fake"; + } + + return unknown; + }); +} + +/** + * \brief Verifies that a Move controller id is valid + * \param gem_num Move controler ID to verify + * \return True if the ID is valid, false otherwise + */ +static bool check_gem_num(const u32 gem_num) +{ + return gem_num >= 0 && gem_num < CELL_GEM_MAX_NUM; +} + + +/** + * \brief Maps Move controller data (digital buttons, and analog Trigger data) to DS3 pad input. + * Unavoidably buttons conflict with DS3 mappings, which is problematic for some games. + * \param port_no DS3 port number to use + * \param digital_buttons Bitmask filled with CELL_GEM_CTRL_* values + * \param analog_t Analog value of Move's Trigger. Currently mapped to R2. + * \return true on success, false if port_no controller is invalid + */ +static bool map_to_ds3_input(const u32 port_no, be_t& digital_buttons, be_t& analog_t) +{ + const auto handler = fxm::get(); + + if (!handler) + { + return false; + } + + const PadInfo& rinfo = handler->GetInfo(); + + if (port_no >= rinfo.max_connect || port_no >= rinfo.now_connect) + { + return false; + } + + auto& pads = handler->GetPads(); + auto pad = pads[port_no]; + + for (Button& button : pad->m_buttons) + { + //here we check btns, and set pad accordingly, + if (button.m_offset == CELL_PAD_BTN_OFFSET_DIGITAL2) + { + if (button.m_pressed) pad->m_digital_2 |= button.m_outKeyCode; + else pad->m_digital_2 &= ~button.m_outKeyCode; + + switch (button.m_outKeyCode) + { + case CELL_PAD_CTRL_SQUARE: + pad->m_press_square = button.m_value; + break; + case CELL_PAD_CTRL_CROSS: + pad->m_press_cross = button.m_value; + break; + case CELL_PAD_CTRL_CIRCLE: + pad->m_press_circle = button.m_value; + break; + case CELL_PAD_CTRL_TRIANGLE: + pad->m_press_triangle = button.m_value; + break; + case CELL_PAD_CTRL_R1: + pad->m_press_R1 = button.m_value; + break; + case CELL_PAD_CTRL_L1: + pad->m_press_L1 = button.m_value; + break; + case CELL_PAD_CTRL_R2: + pad->m_press_R2 = button.m_value; + break; + case CELL_PAD_CTRL_L2: + pad->m_press_L2 = button.m_value; + break; + default: break; + } + } + + if (button.m_flush) + { + button.m_pressed = false; + button.m_flush = false; + button.m_value = 0; + } + } + + memset(&digital_buttons, 0, sizeof(digital_buttons)); + + // map the Move key to R1 and the Trigger to R2 + + if (pad->m_press_R1) + digital_buttons |= CELL_GEM_CTRL_MOVE; + if (pad->m_press_R2) + digital_buttons |= CELL_GEM_CTRL_T; + + if (pad->m_press_cross) + digital_buttons |= CELL_GEM_CTRL_CROSS; + if (pad->m_press_circle) + digital_buttons |= CELL_GEM_CTRL_CIRCLE; + if (pad->m_press_square) + digital_buttons |= CELL_GEM_CTRL_SQUARE; + if (pad->m_press_triangle) + digital_buttons |= CELL_GEM_CTRL_TRIANGLE; + if (pad->m_digital_1) + digital_buttons |= CELL_GEM_CTRL_SELECT; + if (pad->m_digital_2) + digital_buttons |= CELL_GEM_CTRL_START; + + analog_t = pad->m_press_R2; + + return true; +} + +/** + * \brief Maps external Move controller data to DS3 input + * Implementation detail: CellGemExtPortData's digital/analog fields map the same way as + * libPad, so no translation is needed. + * \param port_no DS3 port number to use + * \param ext External data to modify + * \return true on success, false if port_no controller is invalid + */ +static bool map_ext_to_ds3_input(const u32 port_no, CellGemExtPortData& ext) +{ + const auto handler = fxm::get(); + + if (!handler) + { + return false; + } + + auto& pads = handler->GetPads(); + + const PadInfo& rinfo = handler->GetInfo(); + + if (port_no >= rinfo.max_connect) + { + return false; + } + + //We have a choice here of NO_DEVICE or READ_FAILED...lets try no device for now + if (port_no >= rinfo.now_connect) + { + return false; + } + + auto pad = pads[port_no]; + + ext.status = 0; // CELL_GEM_EXT_CONNECTED | CELL_GEM_EXT_EXT0 | CELL_GEM_EXT_EXT1 + ext.analog_left_x = pad->m_analog_left_x; + ext.analog_left_y = pad->m_analog_left_y; + ext.analog_right_x = pad->m_analog_right_x; + ext.analog_right_y = pad->m_analog_right_y; + ext.digital1 = pad->m_digital_1; + ext.digital2 = pad->m_digital_2; + + return true; +} + +// ********************* +// * cellGem functions * +// ********************* + +s32 cellGemCalibrate(u32 gem_num) +{ + cellGem.todo("cellGemCalibrate(gem_num=%d)", gem_num); + const auto gem = fxm::get(); + + if (!gem) + { + return CELL_GEM_ERROR_UNINITIALIZED; + } + + if (!check_gem_num(gem_num)) + { + return CELL_GEM_ERROR_INVALID_PARAMETER; + } + + if (g_cfg.io.move == move_handler::fake) + { + gem->controllers[gem_num].calibrated_magnetometer = true; + gem->status_flags = CELL_GEM_FLAG_CALIBRATION_OCCURRED | CELL_GEM_FLAG_CALIBRATION_SUCCEEDED; + } + return CELL_OK; } -s32 cellGemClearStatusFlags() +s32 cellGemClearStatusFlags(u32 gem_num, u64 mask) { - UNIMPLEMENTED_FUNC(cellGem); + cellGem.todo("cellGemClearStatusFlags(gem_num=%d, mask=0x%x)", gem_num, mask); + const auto gem = fxm::get(); + + if (!gem) + { + return CELL_GEM_ERROR_UNINITIALIZED; + } + + if (!check_gem_num(gem_num)) + { + return CELL_GEM_ERROR_INVALID_PARAMETER; + } + + gem->status_flags &= ~mask; + return CELL_OK; } @@ -35,15 +336,38 @@ s32 cellGemConvertVideoStart() return CELL_OK; } -s32 cellGemEnableCameraPitchAngleCorrection() +s32 cellGemEnableCameraPitchAngleCorrection(u32 enable_flag) { - UNIMPLEMENTED_FUNC(cellGem); + cellGem.todo("cellGemEnableCameraPitchAngleCorrection(enable_flag=%d", enable_flag); + const auto gem = fxm::get(); + + if (!gem) + { + return CELL_GEM_ERROR_UNINITIALIZED; + } + + gem->enable_pitch_correction = !!enable_flag; + return CELL_OK; } -s32 cellGemEnableMagnetometer() +s32 cellGemEnableMagnetometer(u32 gem_num, u32 enable) { - UNIMPLEMENTED_FUNC(cellGem); + cellGem.todo("cellGemEnableMagnetometer(gem_num=%d, enable=0x%x)", gem_num, enable); + const auto gem = fxm::get(); + + if (!gem) + { + return CELL_GEM_ERROR_UNINITIALIZED; + } + + if (!gem->is_controller_ready(gem_num)) + { + return CELL_GEM_NOT_CONNECTED; + } + + gem->controllers[gem_num].enabled_magnetometer = !!enable; + return CELL_OK; } @@ -59,15 +383,43 @@ s32 cellGemEnd() return CELL_OK; } -s32 cellGemFilterState() +s32 cellGemFilterState(u32 gem_num, u32 enable) { - UNIMPLEMENTED_FUNC(cellGem); + cellGem.warning("cellGemFilterState(gem_num=%d, enable=%d)", gem_num, enable); + const auto gem = fxm::get(); + + if (!gem) + { + return CELL_GEM_ERROR_UNINITIALIZED; + } + + if (!check_gem_num(gem_num)) + { + return CELL_GEM_ERROR_INVALID_PARAMETER; + } + + gem->controllers[gem_num].enabled_filtering = !!enable; + return CELL_OK; } -s32 cellGemForceRGB() +s32 cellGemForceRGB(u32 gem_num, float r, float g, float b) { - UNIMPLEMENTED_FUNC(cellGem); + cellGem.todo("cellGemForceRGB(gem_num=%d, r=%f, g=%f, b=%f)", gem_num, r, g, b); + const auto gem = fxm::get(); + + if (!gem) + { + return CELL_GEM_ERROR_UNINITIALIZED; + } + + if (!check_gem_num(gem_num)) + { + return CELL_GEM_ERROR_INVALID_PARAMETER; + } + + gem->controllers[gem_num].sphere_rgb = gem_t::gem_color(r, g, b); + return CELL_OK; } @@ -77,15 +429,30 @@ s32 cellGemGetAccelerometerPositionInDevice() return CELL_OK; } -s32 cellGemGetAllTrackableHues() +s32 cellGemGetAllTrackableHues(vm::ptr hues) { - UNIMPLEMENTED_FUNC(cellGem); + cellGem.todo("cellGemGetAllTrackableHues(hues=*0x%x)"); return CELL_OK; } -s32 cellGemGetCameraState() +s32 cellGemGetCameraState(vm::ptr camera_state) { - UNIMPLEMENTED_FUNC(cellGem); + cellGem.todo("cellGemGetCameraState(camera_state=0x%x)", camera_state); + const auto gem = fxm::get(); + + if (!gem) + { + return CELL_GEM_ERROR_UNINITIALIZED; + } + + if (!camera_state) + { + return CELL_GEM_ERROR_INVALID_PARAMETER; + } + + camera_state->exposure_time = 1.0f / 60.0f; // TODO: use correct framerate + camera_state->gain = 1.0; + return CELL_OK; } @@ -101,15 +468,68 @@ s32 cellGemGetHuePixels() return CELL_OK; } -s32 cellGemGetImageState() +s32 cellGemGetImageState(u32 gem_num, vm::ptr image_state) { - UNIMPLEMENTED_FUNC(cellGem); + cellGem.todo("cellGemGetImageState(gem_num=%d, image_state=&0x%x)", gem_num, image_state); + const auto gem = fxm::get(); + + if (!gem) + { + return CELL_GEM_ERROR_UNINITIALIZED; + } + + if (!check_gem_num(gem_num)) + { + return CELL_GEM_ERROR_INVALID_PARAMETER; + } + + if (g_cfg.io.move == move_handler::fake) + { + auto shared_data = fxm::get_always(); + + image_state->frame_timestamp = shared_data->frame_timestamp.load(); + image_state->timestamp = image_state->frame_timestamp + 10; // arbitrarily define 10 usecs of frame processing + image_state->visible = true; + image_state->u = 0; + image_state->v = 0; + image_state->r = 20; + image_state->r_valid = true; + image_state->distance = 2 * 1000; // 2 meters away from camera + // TODO + image_state->projectionx = 1; + image_state->projectiony = 1; + } + return CELL_OK; } -s32 cellGemGetInertialState() +s32 cellGemGetInertialState(u32 gem_num, u32 state_flag, u64 timestamp, vm::ptr inertial_state) { - UNIMPLEMENTED_FUNC(cellGem); + cellGem.todo("cellGemGetInertialState(gem_num=%d, state_flag=%d, timestamp=0x%x, inertial_state=0x%x)", gem_num, state_flag, timestamp, inertial_state); + const auto gem = fxm::get(); + + if (!gem) + { + return CELL_GEM_ERROR_UNINITIALIZED; + } + + if (!check_gem_num(gem_num) || !inertial_state || !gem->is_controller_ready(gem_num)) + { + return CELL_GEM_ERROR_INVALID_PARAMETER; + } + + if (g_cfg.io.move == move_handler::fake) + { + map_to_ds3_input(gem_num, inertial_state->pad.digitalbuttons, inertial_state->pad.analog_T); + map_ext_to_ds3_input(gem_num, inertial_state->ext); + + inertial_state->timestamp = gem->timer.GetElapsedTimeInMicroSec(); + + inertial_state->counter = gem->inertial_counter++; + + inertial_state->accelerometer[0] = 10; + } + return CELL_OK; } @@ -124,14 +544,19 @@ s32 cellGemGetInfo(vm::ptr info) return CELL_GEM_ERROR_UNINITIALIZED; } + if (!info) + { + return CELL_GEM_ERROR_INVALID_PARAMETER; + } + // TODO: Support connecting PlayStation Move controllers info->max_connect = gem->attribute.max_connect; - info->now_connect = 0; + info->now_connect = gem->connected_controllers; for (int i = 0; i < CELL_GEM_MAX_NUM; i++) { - info->status[i] = CELL_GEM_STATUS_DISCONNECTED; - info->port[i] = 0; + info->status[i] = gem->controllers[i].status; + info->port[i] = gem->controllers[i].port; } return CELL_OK; @@ -149,15 +574,46 @@ s32 cellGemGetMemorySize(s32 max_connect) return max_connect <= 2 ? 0x120000 : 0x140000; } -s32 cellGemGetRGB() +s32 cellGemGetRGB(u32 gem_num, vm::ptr r, vm::ptr g, vm::ptr b) { - UNIMPLEMENTED_FUNC(cellGem); + cellGem.todo("cellGemGetRGB(gem_num=%d, r=*0x%x, g=*0x%x, b=*0x%x)", gem_num, r, g, b); + const auto gem = fxm::get(); + + if (!gem) + { + return CELL_GEM_ERROR_UNINITIALIZED; + } + + if (!check_gem_num(gem_num) | !r || !g || !b ) + { + return CELL_GEM_ERROR_INVALID_PARAMETER; + } + + auto& sphere_color = gem->controllers[gem_num].sphere_rgb; + *r = sphere_color.r; + *g = sphere_color.g; + *b = sphere_color.b; + return CELL_OK; } -s32 cellGemGetRumble() +s32 cellGemGetRumble(u32 gem_num, vm::ptr rumble) { - UNIMPLEMENTED_FUNC(cellGem); + cellGem.todo("cellGemGetRumble(gem_num=%d, rumble=*0x%x)", gem_num, rumble); + auto gem = fxm::get(); + + if (!gem) + { + return CELL_GEM_ERROR_UNINITIALIZED; + } + + if (!check_gem_num(gem_num) || !rumble) + { + return CELL_GEM_ERROR_INVALID_PARAMETER; + } + + *rumble = gem->controllers[gem_num].rumble; + return CELL_OK; } @@ -167,17 +623,47 @@ s32 cellGemGetState(u32 gem_num, u32 flag, u64 time_parameter, vm::ptr(); if (!gem) + { return CELL_GEM_ERROR_UNINITIALIZED; + } - // clear out gem_state so no games get any funny ideas about them being connected... - std::memset(gem_state.get_ptr(), 0, sizeof(CellGemState)); + if (!check_gem_num(gem_num)) + { + return CELL_GEM_ERROR_INVALID_PARAMETER; + } + + if (g_cfg.io.move == move_handler::fake) + { + map_to_ds3_input(gem_num, gem_state->pad.digitalbuttons, gem_state->pad.analog_T); + map_ext_to_ds3_input(gem_num, gem_state->ext); + + gem_state->tracking_flags = CELL_GEM_TRACKING_FLAG_POSITION_TRACKED | + CELL_GEM_TRACKING_FLAG_VISIBLE; + gem_state->timestamp = gem->timer.GetElapsedTimeInMicroSec(); + + gem_state->quat[3] = 1.0; + } return CELL_GEM_NOT_CONNECTED; } -s32 cellGemGetStatusFlags() +s32 cellGemGetStatusFlags(u32 gem_num, vm::ptr flags) { - UNIMPLEMENTED_FUNC(cellGem); + cellGem.todo("cellGemGetStatusFlags(gem_num=%d, flags=*0x%x)", gem_num, flags); + const auto gem = fxm::get(); + + if (!gem) + { + return CELL_GEM_ERROR_UNINITIALIZED; + } + + if (!check_gem_num(gem_num) || !flags) + { + return CELL_GEM_ERROR_INVALID_PARAMETER; + } + + *flags = gem->status_flags; + return CELL_OK; } @@ -196,7 +682,6 @@ s32 cellGemHSVtoRGB() s32 cellGemInit(vm::cptr attribute) { cellGem.warning("cellGemInit(attribute=*0x%x)", attribute); - const auto gem = fxm::make(); if (!gem) @@ -204,8 +689,21 @@ s32 cellGemInit(vm::cptr attribute) return CELL_GEM_ERROR_ALREADY_INITIALIZED; } + if (!attribute || !attribute->spurs_addr || attribute->max_connect > CELL_GEM_MAX_NUM) + { + return CELL_GEM_ERROR_INVALID_PARAMETER; + } + gem->attribute = *attribute; + for (auto gem_num = 0; gem_num < CELL_GEM_MAX_NUM; gem_num++) + { + gem->reset_controller(gem_num); + } + + // TODO: is this correct? + gem->timer.Start(); + return CELL_OK; } @@ -215,10 +713,17 @@ s32 cellGemInvalidateCalibration() return CELL_OK; } -s32 cellGemIsTrackableHue() +s32 cellGemIsTrackableHue(u32 hue) { - UNIMPLEMENTED_FUNC(cellGem); - return CELL_OK; + cellGem.todo("cellGemIsTrackableHue(hue=%d)", hue); + const auto gem = fxm::get(); + + if (!gem || hue > 359) + { + return false; + } + + return true; } s32 cellGemPrepareCamera() @@ -227,9 +732,36 @@ s32 cellGemPrepareCamera() return CELL_OK; } -s32 cellGemPrepareVideoConvert() +s32 cellGemPrepareVideoConvert(vm::cptr vc_attribute) { - UNIMPLEMENTED_FUNC(cellGem); + cellGem.todo("cellGemPrepareVideoConvert(vc_attribute=*0x%x)", vc_attribute); + auto gem = fxm::get(); + + if (!gem) + { + return CELL_GEM_ERROR_UNINITIALIZED; + } + + if (!vc_attribute) + { + return CELL_GEM_ERROR_INVALID_PARAMETER; + } + + const auto vc = *vc_attribute; + + if (!vc_attribute || vc.version == 0 || vc.output_format == 0 || + vc.conversion_flags & CELL_GEM_VIDEO_CONVERT_UNK3 && !vc.buffer_memory) + { + return CELL_GEM_ERROR_INVALID_PARAMETER; + } + + if (vc.video_data_out & 0x1f || vc.buffer_memory & 0xff) + { + return CELL_GEM_ERROR_INVALID_ALIGNMENT; + } + + gem->vc_attribute = vc; + return CELL_OK; } @@ -239,15 +771,46 @@ s32 cellGemReadExternalPortDeviceInfo() return CELL_OK; } -s32 cellGemReset() +s32 cellGemReset(u32 gem_num) { - UNIMPLEMENTED_FUNC(cellGem); + cellGem.todo("cellGemReset(gem_num=%d)", gem_num); + auto gem = fxm::get(); + + if (!gem) + { + return CELL_GEM_ERROR_UNINITIALIZED; + } + + if (!check_gem_num(gem_num)) + { + return CELL_GEM_ERROR_INVALID_PARAMETER; + } + + gem->reset_controller(gem_num); + + // TODO: is this correct? + gem->timer.Start(); + return CELL_OK; } -s32 cellGemSetRumble() +s32 cellGemSetRumble(u32 gem_num, u8 rumble) { - UNIMPLEMENTED_FUNC(cellGem); + cellGem.todo("cellGemSetRumble(gem_num=%d, rumble=0x%x)", gem_num, rumble); + auto gem = fxm::get(); + + if (!gem) + { + return CELL_GEM_ERROR_UNINITIALIZED; + } + + if (!check_gem_num(gem_num)) + { + return CELL_GEM_ERROR_INVALID_PARAMETER; + } + + gem->controllers[gem_num].rumble = rumble; + return CELL_OK; } @@ -269,9 +832,16 @@ s32 cellGemUpdateFinish() return CELL_OK; } -s32 cellGemUpdateStart() +s32 cellGemUpdateStart(vm::cptr camera_frame, u64 timestamp) { - UNIMPLEMENTED_FUNC(cellGem); + cellGem.todo("cellGemUpdateStart(camera_frame=*0x%x, timestamp=%d)", camera_frame, timestamp); + auto gem = fxm::get(); + + if (!gem) + { + return CELL_GEM_ERROR_UNINITIALIZED; + } + return CELL_OK; } diff --git a/rpcs3/Emu/Cell/Modules/cellGem.h b/rpcs3/Emu/Cell/Modules/cellGem.h index 62c90d5883..0728c27f35 100644 --- a/rpcs3/Emu/Cell/Modules/cellGem.h +++ b/rpcs3/Emu/Cell/Modules/cellGem.h @@ -4,7 +4,7 @@ static const float CELL_GEM_SPHERE_RADIUS_MM = 22.5f; -// Error Codes +// Error codes enum { CELL_GEM_ERROR_RESOURCE_ALLOCATION_FAILED = 0x80121801, @@ -34,7 +34,7 @@ enum CELL_GEM_NO_EXTERNAL_PORT_DEVICE = 9, }; -// General constents +// General constants enum { CELL_GEM_CTRL_CIRCLE = 1 << 5, @@ -59,7 +59,7 @@ enum CELL_GEM_FLAG_CALIBRATION_OCCURRED = 1 << 0, CELL_GEM_FLAG_CALIBRATION_SUCCEEDED = 1 << 1, CELL_GEM_FLAG_CALIBRATION_WARNING_BRIGHT_LIGHTING = 1 << 6, - ELL_GEM_FLAG_CALIBRATION_WARNING_MOTION_DETECTED = 1 << 5, + CELL_GEM_FLAG_CALIBRATION_WARNING_MOTION_DETECTED = 1 << 5, CELL_GEM_FLAG_CAMERA_PITCH_ANGLE_CHANGED = 1 << 9, CELL_GEM_FLAG_CURRENT_HUE_CONFLICTS_WITH_ENVIRONMENT = 1 << 13, CELL_GEM_FLAG_LIGHTING_CHANGED = 1 << 7, @@ -84,6 +84,15 @@ enum CELL_GEM_VERSION = 2, }; +// Video conversion flags +enum +{ + CELL_GEM_VIDEO_CONVERT_UNK1 = 1 << 0, + CELL_GEM_VIDEO_CONVERT_UNK2 = 1 << 1, + CELL_GEM_VIDEO_CONVERT_UNK3 = 1 << 2, + CELL_GEM_VIDEO_CONVERT_UNK4 = 1 << 3, +}; + struct CellGemAttribute { be_t version; @@ -118,9 +127,9 @@ struct CellGemImageState { be_t frame_timestamp; be_t timestamp; - be_t u; - be_t v; - be_t r; + be_t u; // horizontal screen position in pixels + be_t v; // vertical screen position in pixels + be_t r; // size of sphere on screen in pixels be_t projectionx; be_t projectiony; be_t distance; diff --git a/rpcs3/Emu/Cell/Modules/cellSysutil.cpp b/rpcs3/Emu/Cell/Modules/cellSysutil.cpp index 933dc67b1d..6e28e8ce43 100644 --- a/rpcs3/Emu/Cell/Modules/cellSysutil.cpp +++ b/rpcs3/Emu/Cell/Modules/cellSysutil.cpp @@ -198,6 +198,10 @@ s32 cellSysutilGetSystemParamInt(CellSysutilParamId id, vm::ptr value) *value = 0; break; + case CELL_SYSUTIL_SYSTEMPARAM_ID_MAGNETOMETER: + *value = 0; + break; + default: return CELL_EINVAL; } @@ -301,7 +305,7 @@ s32 cellSysCacheMount(vm::ptr param) const std::string& cache_id = param->cacheId; verify(HERE), cache_id.size() < sizeof(param->cacheId); - + const std::string& cache_path = "/dev_hdd1/cache/" + cache_id + '/'; strcpy_trunc(param->getCachePath, cache_path); @@ -329,7 +333,7 @@ s32 cellSysutilEnableBgmPlaybackEx(vm::ptr par cellSysutil.warning("cellSysutilEnableBgmPlaybackEx(param=*0x%x)", param); // TODO - g_bgm_playback_enabled = true; + g_bgm_playback_enabled = true; return CELL_OK; } @@ -723,12 +727,12 @@ DECLARE(ppu_module_manager::cellSysutil)("cellSysutil", []() REG_FUNC(cellSysutil, _ZN8cxmlutil16CheckElementNameERKN4cxml7ElementEPKc); REG_FUNC(cellSysutil, _ZN8cxmlutil16FindChildElementERKN4cxml7ElementEPKcS5_S5_); REG_FUNC(cellSysutil, _ZN8cxmlutil7GetFileERKN4cxml7ElementEPKcPNS0_4FileE); - + REG_FUNC(cellSysutil, _ZN16sysutil_cxmlutil11FixedMemory3EndEi); REG_FUNC(cellSysutil, _ZN16sysutil_cxmlutil11FixedMemory5BeginEi); REG_FUNC(cellSysutil, _ZN16sysutil_cxmlutil11FixedMemory8AllocateEN4cxml14AllocationTypeEPvS3_jPS3_Pj); REG_FUNC(cellSysutil, _ZN16sysutil_cxmlutil12PacketWriter5WriteEPKvjPv); REG_FUNC(cellSysutil, _ZN16sysutil_cxmlutil12PacketWriterC1EiiRN4cxml8DocumentE); - + REG_FNID(cellSysutil, 0xE1EC7B6A, cellSysutil_E1EC7B6A); }); diff --git a/rpcs3/Emu/Cell/Modules/cellSysutil.h b/rpcs3/Emu/Cell/Modules/cellSysutil.h index 2470b0a18d..1430c51c0a 100644 --- a/rpcs3/Emu/Cell/Modules/cellSysutil.h +++ b/rpcs3/Emu/Cell/Modules/cellSysutil.h @@ -32,6 +32,7 @@ enum CellSysutilParamId: s32 CELL_SYSUTIL_SYSTEMPARAM_ID_JAPANESE_KEYBOARD_ENTRY_METHOD = 0x0154, CELL_SYSUTIL_SYSTEMPARAM_ID_CHINESE_KEYBOARD_ENTRY_METHOD = 0x0155, CELL_SYSUTIL_SYSTEMPARAM_ID_PAD_AUTOOFF = 0x0156, + CELL_SYSUTIL_SYSTEMPARAM_ID_MAGNETOMETER = 0x0157, // Strings CELL_SYSUTIL_SYSTEMPARAM_ID_NICKNAME = 0x0113, diff --git a/rpcs3/Emu/System.h b/rpcs3/Emu/System.h index a0f794ae47..070784d351 100644 --- a/rpcs3/Emu/System.h +++ b/rpcs3/Emu/System.h @@ -105,6 +105,12 @@ enum class fake_camera_type uvc1_1, }; +enum class move_handler +{ + null, + fake, +}; + enum class video_resolution { _1080, @@ -392,6 +398,7 @@ struct cfg_root : cfg::node cfg::_enum pad{this, "Pad", pad_handler::keyboard}; cfg::_enum camera{this, "Camera", camera_handler::null}; cfg::_enum camera_type{this, "Camera type", fake_camera_type::unknown}; + cfg::_enum move{this, "Move", move_handler::null}; } io{this}; diff --git a/rpcs3/Json/tooltips.json b/rpcs3/Json/tooltips.json index 700c3bfb7c..378932fe76 100644 --- a/rpcs3/Json/tooltips.json +++ b/rpcs3/Json/tooltips.json @@ -98,7 +98,8 @@ "keyboardHandlerBox": "Some games support native keyboard input.\nBasic will work in these cases.", "mouseHandlerBox": "Some games support native mouse input.\nBasic will work in these cases.", "cameraBox": "Camera support is not implemented, leave this on null.", - "cameraTypeBox": "Camera support is not implemented, leave this on unknown." + "cameraTypeBox": "Camera support is not implemented, leave this on unknown.", + "moveBox": "Playstation Move support.\nFake: Experimental, maps Move controls to DS4 controller mappings." }, "network": { "netStatusBox": "Leave as disconnected unless you're debugging.\nRPCS3 has no online support." diff --git a/rpcs3/rpcs3qt/emu_settings.h b/rpcs3/rpcs3qt/emu_settings.h index dad3ec4492..abd72e15ea 100644 --- a/rpcs3/rpcs3qt/emu_settings.h +++ b/rpcs3/rpcs3qt/emu_settings.h @@ -78,6 +78,7 @@ public: MouseHandler, Camera, CameraType, + Move, // Misc ExitRPCS3OnFinish, @@ -137,7 +138,7 @@ public: Render_Creator(); }; - /** Creates a settings object which reads in the config.yml file at rpcs3/bin/%path%/config.yml + /** Creates a settings object which reads in the config.yml file at rpcs3/bin/%path%/config.yml * Settings are only written when SaveSettings is called. */ emu_settings(); @@ -236,6 +237,7 @@ private: { MouseHandler, { "Input/Output", "Mouse"}}, { Camera, { "Input/Output", "Camera"}}, { CameraType, { "Input/Output", "Camera type"}}, + { Move, { "Input/Output", "Move" }}, // Misc {ExitRPCS3OnFinish, { "Miscellaneous", "Exit RPCS3 when process finishes" }}, diff --git a/rpcs3/rpcs3qt/settings_dialog.cpp b/rpcs3/rpcs3qt/settings_dialog.cpp index 386f7f7055..4e125068f8 100644 --- a/rpcs3/rpcs3qt/settings_dialog.cpp +++ b/rpcs3/rpcs3qt/settings_dialog.cpp @@ -652,6 +652,9 @@ settings_dialog::settings_dialog(std::shared_ptr guiSettings, std: xemu_settings->EnhanceComboBox(ui->cameraBox, emu_settings::Camera); SubscribeTooltip(ui->cameraBox, json_input["cameraBox"].toString()); + xemu_settings->EnhanceComboBox(ui->moveBox, emu_settings::Move); + SubscribeTooltip(ui->moveBox, json_input["moveBox"].toString()); + // _____ _ _______ _ // / ____| | | |__ __| | | // | (___ _ _ ___| |_ ___ _ __ ___ | | __ _| |__ diff --git a/rpcs3/rpcs3qt/settings_dialog.ui b/rpcs3/rpcs3qt/settings_dialog.ui index ba40328f98..48b081b932 100644 --- a/rpcs3/rpcs3qt/settings_dialog.ui +++ b/rpcs3/rpcs3qt/settings_dialog.ui @@ -890,20 +890,16 @@ - - - Qt::Horizontal + + + Move Handler - - QSizePolicy::MinimumExpanding - - - - 0 - 0 - - - + + + + + +