diff --git a/3rdparty/qt5.cmake b/3rdparty/qt5.cmake index 914ba435e1..6f465458da 100644 --- a/3rdparty/qt5.cmake +++ b/3rdparty/qt5.cmake @@ -2,17 +2,17 @@ add_library(3rdparty_qt5 INTERFACE) set(QT_MIN_VER 5.15.2) -find_package(Qt5 ${QT_MIN_VER} CONFIG COMPONENTS Widgets Concurrent) +find_package(Qt5 ${QT_MIN_VER} CONFIG COMPONENTS Widgets Concurrent Multimedia) if(WIN32) find_package(Qt5 ${QT_MIN_VER} COMPONENTS WinExtras REQUIRED) - target_link_libraries(3rdparty_qt5 INTERFACE Qt5::Widgets Qt5::WinExtras Qt5::Concurrent) + target_link_libraries(3rdparty_qt5 INTERFACE Qt5::Widgets Qt5::WinExtras Qt5::Concurrent Qt5::Multimedia) else() find_package(Qt5 ${QT_MIN_VER} COMPONENTS DBus Gui) if(Qt5DBus_FOUND) - target_link_libraries(3rdparty_qt5 INTERFACE Qt5::Widgets Qt5::DBus Qt5::Concurrent) + target_link_libraries(3rdparty_qt5 INTERFACE Qt5::Widgets Qt5::DBus Qt5::Concurrent Qt5::Multimedia) target_compile_definitions(3rdparty_qt5 INTERFACE -DHAVE_QTDBUS) else() - target_link_libraries(3rdparty_qt5 INTERFACE Qt5::Widgets Qt5::Concurrent) + target_link_libraries(3rdparty_qt5 INTERFACE Qt5::Widgets Qt5::Concurrent Qt5::Multimedia) endif() target_include_directories(3rdparty_qt5 INTERFACE ${Qt5Gui_PRIVATE_INCLUDE_DIRS}) endif() diff --git a/rpcs3/Emu/Cell/Modules/cellCamera.cpp b/rpcs3/Emu/Cell/Modules/cellCamera.cpp index 16d6ec9194..1e7098920e 100644 --- a/rpcs3/Emu/Cell/Modules/cellCamera.cpp +++ b/rpcs3/Emu/Cell/Modules/cellCamera.cpp @@ -115,7 +115,8 @@ static bool check_dev_num(s32 dev_num) return dev_num == 0; } -static error_code check_camera_info(const CellCameraInfoEx& info) +template +static error_code check_camera_info(const VariantOfCellCameraInfo& info) { // TODO: I managed to get 0x80990004 once. :thonkang: @@ -263,14 +264,88 @@ 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; + u32 bytes_per_pixel; + + switch (info.format) + { + case CELL_CAMERA_RAW8: + bytes_per_pixel = 1; + break; + case CELL_CAMERA_YUV422: + case CELL_CAMERA_YUV420: + case CELL_CAMERA_V_Y1_U_Y0: + case CELL_CAMERA_RAW10: + bytes_per_pixel = 2; + break; + case CELL_CAMERA_JPG: + case CELL_CAMERA_RGBA: + case CELL_CAMERA_FORMAT_UNKNOWN: + default: + bytes_per_pixel = 4; + break; + } + + return width * height * bytes_per_pixel; } // ************************ // * cellCamera functions * // ************************ +// This represents 4 almost identical subfunctions used by the Start/Stop/Reset/Close functions +error_code check_init_and_open(s32 dev_num) +{ + if (!check_dev_num(dev_num)) + { + return CELL_CAMERA_ERROR_PARAM; + } + // TODO: Yet another CELL_CAMERA_ERROR_BUSY + + auto& g_camera = g_fxo->get(); + + if (!g_camera.init) + { + return CELL_CAMERA_ERROR_NOT_INIT; + } + if (!g_camera.is_open) + { + return CELL_CAMERA_ERROR_NOT_OPEN; + } + return CELL_OK; +} + +// This represents a recurring subfunction throughout libCamera +error_code check_resolution(s32 dev_num) +{ + // TODO: Some sort of connection check maybe? + error_code error = CELL_OK; + + if (error == CELL_CAMERA_ERROR_RESOLUTION_UNKNOWN) + { + return CELL_CAMERA_ERROR_TIMEOUT; + } + // TODO: Yet another CELL_CAMERA_ERROR_FATAL + return CELL_OK; +} + +// This represents a oftenly used sequence in libCamera (usually the beginning of a subfunction). +// There also exist common sequences for mutex lock/unlock by the way. +error_code check_resolution_ex(s32 dev_num) +{ + // TODO: Yet another CELL_CAMERA_ERROR_BUSY + + if (!check_dev_num(dev_num)) + { + return CELL_CAMERA_ERROR_PARAM; + } + if (error_code error = check_resolution(dev_num)) + { + return error; + } + return CELL_OK; +} + + error_code cellCameraInit() { cellCamera.todo("cellCameraInit()"); @@ -337,11 +412,7 @@ error_code cellCameraInit() // TODO: Some other default attributes? Need to check the actual behaviour on a real PS3. - if (g_cfg.io.camera == camera_handler::fake) - { - g_camera.is_attached = true; - } - + g_camera.is_attached = true; g_camera.init = 1; return CELL_OK; } @@ -359,11 +430,7 @@ error_code cellCameraEnd() return CELL_CAMERA_ERROR_NOT_INIT; } - // TODO: My tests hinted to this behavior, but I'm not sure, so I'll leave this commented - //if (auto res = cellCameraClose(0)) - //{ - // return res; - //} + // TODO: call cellCameraClose(0), ignore errors // TODO g_camera.init = 0; @@ -371,9 +438,42 @@ error_code cellCameraEnd() return CELL_OK; } -error_code cellCameraOpen() // seems unused +error_code cellCameraOpen(s32 dev_num, vm::ptr info) { - UNIMPLEMENTED_FUNC(cellCamera); + cellCamera.todo("cellCameraOpen(dev_num=%d, info=*0x%x)", dev_num, info); + + if (!info) + { + return CELL_CAMERA_ERROR_PARAM; + } + + auto& g_camera = g_fxo->get(); + + if (!g_camera.init) + { + return CELL_CAMERA_ERROR_NOT_INIT; + } + + if (!check_dev_num(dev_num)) + { + return CELL_CAMERA_ERROR_PARAM; + } + + if (g_camera.is_open) + { + return CELL_CAMERA_ERROR_ALREADY_OPEN; + } + + if (auto res = check_camera_info(*info)) + { + return res; + } + + if (!g_camera.is_attached) + { + return CELL_CAMERA_ERROR_DEVICE_NOT_FOUND; + } + return CELL_OK; } @@ -385,7 +485,7 @@ error_code cellCameraOpenAsync() error_code cellCameraOpenEx(s32 dev_num, vm::ptr info) { - cellCamera.todo("cellCameraOpenEx(dev_num=%d, type=*0x%x)", dev_num, info); + cellCamera.todo("cellCameraOpenEx(dev_num=%d, info=*0x%x)", dev_num, info); // This function has a very weird order of checking for errors @@ -445,9 +545,29 @@ error_code cellCameraOpenEx(s32 dev_num, vm::ptr info) std::tie(info->width, info->height) = get_video_resolution(*info); + g_camera.handler.reset(); + g_camera.handler = Emu.GetCallbacks().get_camera_handler(); + + atomic_t wake_up = false; + + Emu.CallAfter([&wake_up, handler = g_camera.handler]() + { + handler->open_camera(); + wake_up = true; + wake_up.notify_one(); + }); + + while (!wake_up && !Emu.IsStopped()) + { + thread_ctrl::wait_on(wake_up, false); + } + 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", + 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; @@ -465,31 +585,45 @@ error_code cellCameraClose(s32 dev_num) { cellCamera.todo("cellCameraClose(dev_num=%d)", dev_num); - if (!check_dev_num(dev_num)) + if (error_code error = check_init_and_open(dev_num)) + { + return error; + } + + // TODO: Yet another CELL_CAMERA_ERROR_BUSY + + if (dev_num != 0) { return CELL_CAMERA_ERROR_PARAM; } + if (error_code error = check_resolution(dev_num)) + { + return error; + } + auto& g_camera = g_fxo->get(); - - if (!g_camera.init) - { - return CELL_CAMERA_ERROR_NOT_INIT; - } - - if (g_cfg.io.camera == camera_handler::null) - { - return not_an_error(CELL_CAMERA_ERROR_NOT_OPEN); - } - std::lock_guard lock(g_camera.mutex); - if (!g_camera.is_open) + vm::dealloc(g_camera.info.buffer.addr(), vm::main); + + if (g_camera.handler) { - return CELL_CAMERA_ERROR_NOT_OPEN; + atomic_t wake_up = false; + + Emu.CallAfter([&wake_up, handler = g_camera.handler]() + { + handler->close_camera(); + wake_up = true; + wake_up.notify_one(); + }); + + while (!wake_up && !Emu.IsStopped()) + { + thread_ctrl::wait_on(wake_up, false); + } } - vm::dealloc(g_camera.info.buffer.addr(), vm::main); g_camera.is_open = false; return CELL_OK; @@ -509,7 +643,7 @@ error_code cellCameraClosePost() error_code cellCameraGetDeviceGUID(s32 dev_num, vm::ptr guid) { - cellCamera.todo("cellCameraGetDeviceGUID(dev_num=%d, guid=*0x%x)", dev_num, guid); + cellCamera.notice("cellCameraGetDeviceGUID(dev_num=%d, guid=*0x%x)", dev_num, guid); auto& g_camera = g_fxo->get(); @@ -518,9 +652,10 @@ error_code cellCameraGetDeviceGUID(s32 dev_num, vm::ptr guid) return CELL_CAMERA_ERROR_NOT_INIT; } - // Does not check params or is_open (maybe attached?) - - *guid = 0; // apparently always 0 + if (guid) + { + *guid = 0; // apparently always 0 + } return CELL_OK; } @@ -541,11 +676,16 @@ error_code cellCameraGetType(s32 dev_num, vm::ptr type) return not_an_error(CELL_CAMERA_ERROR_DEVICE_NOT_FOUND); } - if (!check_dev_num(dev_num) || !type ) + if (!check_dev_num(dev_num) || !type) { return CELL_CAMERA_ERROR_PARAM; } + if (error_code error = check_resolution(dev_num)) + { + return error; + } + if (!g_camera.is_attached) { return CELL_CAMERA_ERROR_DEVICE_NOT_FOUND; @@ -571,16 +711,16 @@ s32 cellCameraIsAvailable(s32 dev_num) return false; } - auto& g_camera = g_fxo->get(); + vm::var type; - if (!g_camera.init) + if (cellCameraGetType(dev_num, type) != CELL_OK || *type == CELL_CAMERA_TYPE_UNKNOWN) { return false; } - if (!check_dev_num(dev_num)) + if (*type > CELL_CAMERA_TYPE_UNKNOWN || *type <= CELL_CAMERA_USBVIDEOCLASS) { - return false; + // TODO: checks CELL_CAMERA_DEVICESPEED attribute } return true; @@ -607,11 +747,18 @@ s32 cellCameraIsAttached(s32 dev_num) return false; } + vm::var type; + + if (cellCameraGetType(dev_num, type) != CELL_OK) + { + return false; + } + std::lock_guard lock(g_camera.mutex); bool is_attached = g_camera.is_attached; - if (g_cfg.io.camera == camera_handler::fake) + if (g_cfg.io.camera != camera_handler::null) { // "attach" camera here // normally should be attached immediately after event queue is registered, but just to be sure @@ -627,7 +774,7 @@ s32 cellCameraIsAttached(s32 dev_num) s32 cellCameraIsOpen(s32 dev_num) { - cellCamera.warning("cellCameraIsOpen(dev_num=%d)", dev_num); + cellCamera.notice("cellCameraIsOpen(dev_num=%d)", dev_num); if (g_cfg.io.camera == camera_handler::null) { @@ -653,7 +800,7 @@ s32 cellCameraIsOpen(s32 dev_num) s32 cellCameraIsStarted(s32 dev_num) { - cellCamera.warning("cellCameraIsStarted(dev_num=%d)", dev_num); + cellCamera.notice("cellCameraIsStarted(dev_num=%d)", dev_num); if (g_cfg.io.camera == camera_handler::null) { @@ -689,22 +836,32 @@ error_code cellCameraGetAttribute(s32 dev_num, s32 attrib, vm::ptr arg1, vm return CELL_CAMERA_ERROR_NOT_INIT; } - if (g_cfg.io.camera == camera_handler::null) - { - return not_an_error(CELL_CAMERA_ERROR_DEVICE_NOT_FOUND); - } - - if (!check_dev_num(dev_num) || !attr_name || !arg1) // invalid attributes don't have a name and at least arg1 should not be NULL + if (!check_dev_num(dev_num)) { return CELL_CAMERA_ERROR_PARAM; } + if (g_cfg.io.camera == camera_handler::null) + { + return not_an_error(CELL_CAMERA_ERROR_NOT_OPEN); + } + // actually compares <= 0x63 which is equivalent if (attrib < CELL_CAMERA_FORMATCAP && !g_camera.is_open) { return CELL_CAMERA_ERROR_NOT_OPEN; } + if (!arg1) + { + return CELL_CAMERA_ERROR_PARAM; + } + + if (error_code error = check_resolution(dev_num)) + { + return error; + } + std::lock_guard lock(g_camera.mutex); if (!g_camera.is_attached) @@ -712,15 +869,22 @@ error_code cellCameraGetAttribute(s32 dev_num, s32 attrib, vm::ptr arg1, vm return CELL_CAMERA_ERROR_DEVICE_NOT_FOUND; } + if (!attr_name) // invalid attributes don't have a name + { + return CELL_CAMERA_ERROR_PARAM; + } + if (arg1) { *arg1 = g_camera.attr[attrib].v1; } + if (arg2) { *arg2 = g_camera.attr[attrib].v2; } + cellCamera.todo("cellCameraGetAttribute(attr_name=%s, v1=%d, v2=%d)", attr_name, g_camera.attr[attrib].v1, g_camera.attr[attrib].v2); return CELL_OK; } @@ -736,22 +900,32 @@ error_code cellCameraSetAttribute(s32 dev_num, s32 attrib, u32 arg1, u32 arg2) return CELL_CAMERA_ERROR_NOT_INIT; } + if (!check_dev_num(dev_num)) + { + return CELL_CAMERA_ERROR_PARAM; + } + if (g_cfg.io.camera == camera_handler::null) { return not_an_error(CELL_CAMERA_ERROR_NOT_OPEN); } - if (!check_dev_num(dev_num) || !attr_name) // invalid attributes don't have a name - { - return CELL_CAMERA_ERROR_PARAM; - } - // actually compares <= 0x63 which is equivalent if (attrib < CELL_CAMERA_FORMATCAP && !g_camera.is_open) { return CELL_CAMERA_ERROR_NOT_OPEN; } + if (error_code error = check_resolution(dev_num)) + { + return error; + } + + if (!attr_name) // invalid attributes don't have a name + { + return CELL_CAMERA_ERROR_PARAM; + } + g_camera.set_attr(attrib, arg1, arg2); return CELL_OK; @@ -779,8 +953,6 @@ error_code cellCameraGetBufferSize(s32 dev_num, vm::ptr info) return not_an_error(CELL_CAMERA_ERROR_DEVICE_NOT_FOUND); } - // the next few checks have a strange order, if I can trust the tests - if (!check_dev_num(dev_num)) { return CELL_CAMERA_ERROR_PARAM; @@ -811,29 +983,24 @@ error_code cellCameraGetBufferSize(s32 dev_num, vm::ptr info) return status; } + if (error_code error = check_resolution(dev_num)) + { + return error; + } + std::lock_guard lock(g_camera.mutex); - info->bytesize = get_video_buffer_size(g_camera.info); g_camera.info = *info; + info->bytesize = get_video_buffer_size(g_camera.info); - return info->bytesize; + cellCamera.notice("cellCameraGetBufferSize info: format=%d, 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); + + return not_an_error(info->bytesize); } -error_code cellCameraGetBufferInfo() +error_code check_get_camera_info(s32 dev_num, bool is_valid_info_struct) { - UNIMPLEMENTED_FUNC(cellCamera); - - // called by cellCameraGetBufferInfoEx - - return CELL_OK; -} - -error_code cellCameraGetBufferInfoEx(s32 dev_num, vm::ptr info) -{ - cellCamera.todo("cellCameraGetBufferInfoEx(dev_num=%d, read=0x%x)", dev_num, info); - - // the following should be moved to cellCameraGetBufferInfo - auto& g_camera = g_fxo->get(); if (!g_camera.init) @@ -841,27 +1008,70 @@ error_code cellCameraGetBufferInfoEx(s32 dev_num, vm::ptr info return CELL_CAMERA_ERROR_NOT_INIT; } - if (g_cfg.io.camera == camera_handler::null) - { - return not_an_error(CELL_CAMERA_ERROR_NOT_OPEN); - } - if (!check_dev_num(dev_num)) { return CELL_CAMERA_ERROR_PARAM; } + if (g_cfg.io.camera == camera_handler::null) + { + return not_an_error(CELL_CAMERA_ERROR_NOT_OPEN); + } + if (!g_camera.is_open) { return CELL_CAMERA_ERROR_NOT_OPEN; } - if (!info) + if (!is_valid_info_struct) { return CELL_CAMERA_ERROR_PARAM; } + return CELL_OK; +} + +error_code cellCameraGetBufferInfo(s32 dev_num, vm::ptr info) +{ + cellCamera.todo("cellCameraGetBufferInfo(dev_num=%d, info=0x%x)", dev_num, info); + + // called by cellCameraGetBufferInfoEx + + if (error_code error = check_get_camera_info(dev_num, !!info)) + { + return error; + } + + auto& g_camera = g_fxo->get(); std::lock_guard lock(g_camera.mutex); + + info->format = g_camera.info.format; + info->resolution = g_camera.info.resolution; + info->framerate = g_camera.info.framerate; + info->buffer = g_camera.info.buffer; + info->bytesize = g_camera.info.bytesize; + info->width = g_camera.info.width; + info->height = g_camera.info.height; + info->dev_num = g_camera.info.dev_num; + info->guid = g_camera.info.guid; + + return CELL_OK; +} + +error_code cellCameraGetBufferInfoEx(s32 dev_num, vm::ptr info) +{ + cellCamera.todo("cellCameraGetBufferInfoEx(dev_num=%d, info=0x%x)", dev_num, info); + + // calls cellCameraGetBufferInfo + + if (error_code error = check_get_camera_info(dev_num, !!info)) + { + return error; + } + + auto& g_camera = g_fxo->get(); + std::lock_guard lock(g_camera.mutex); + *info = g_camera.info; return CELL_OK; @@ -869,13 +1079,56 @@ error_code cellCameraGetBufferInfoEx(s32 dev_num, vm::ptr info error_code cellCameraPrepExtensionUnit(s32 dev_num, vm::ptr guidExtensionCode) { - UNIMPLEMENTED_FUNC(cellCamera); + cellCamera.todo("cellCameraPrepExtensionUnit(dev_num=%d, guidExtensionCode=0x%x)", dev_num, guidExtensionCode); + + if (!check_dev_num(dev_num) || !guidExtensionCode) + { + return CELL_CAMERA_ERROR_PARAM; + } + + if (error_code error = check_resolution(dev_num)) + { + return error; + } + + auto& g_camera = g_fxo->get(); + + if (!g_camera.is_attached) + { + return CELL_CAMERA_ERROR_DEVICE_NOT_FOUND; + } + return CELL_OK; } error_code cellCameraCtrlExtensionUnit(s32 dev_num, u8 request, u16 value, u16 length, vm::ptr data) { - UNIMPLEMENTED_FUNC(cellCamera); + cellCamera.todo("cellCameraCtrlExtensionUnit(dev_num=%d, request=%d, value=%d, length=%d, data=*0x%x)", dev_num, request, value, length, data); + + if (!check_dev_num(dev_num)) + { + return CELL_CAMERA_ERROR_PARAM; + } + + auto& g_camera = g_fxo->get(); + + if (!g_camera.is_open) + { + return CELL_CAMERA_ERROR_NOT_OPEN; + } + + if (!data) + { + return CELL_CAMERA_ERROR_PARAM; + } + + if (!g_camera.is_attached) + { + return CELL_CAMERA_ERROR_DEVICE_NOT_FOUND; + } + + // TODO: Yet another CELL_CAMERA_ERROR_PARAM + return CELL_OK; } @@ -903,28 +1156,18 @@ error_code cellCameraReset(s32 dev_num) { cellCamera.todo("cellCameraReset(dev_num=%d)", dev_num); - if (!check_dev_num(dev_num)) + if (error_code error = check_init_and_open(dev_num)) { - return CELL_CAMERA_ERROR_PARAM; + return error; + } + + if (error_code error = check_resolution_ex(dev_num)) + { + return error; } auto& g_camera = g_fxo->get(); - if (!g_camera.init) - { - return CELL_CAMERA_ERROR_NOT_INIT; - } - - if (g_cfg.io.camera == camera_handler::null) - { - return not_an_error(CELL_CAMERA_ERROR_NOT_OPEN); - } - - if (!g_camera.is_open) - { - return CELL_CAMERA_ERROR_NOT_OPEN; - } - if (!g_camera.is_attached) { return CELL_CAMERA_ERROR_DEVICE_NOT_FOUND; @@ -951,35 +1194,45 @@ error_code cellCameraStart(s32 dev_num) { cellCamera.todo("cellCameraStart(dev_num=%d)", dev_num); - if (!check_dev_num(dev_num)) + if (error_code error = check_init_and_open(dev_num)) { - return CELL_CAMERA_ERROR_PARAM; + return error; + } + + if (error_code error = check_resolution_ex(dev_num)) + { + return error; } auto& g_camera = g_fxo->get(); - if (!g_camera.init) - { - return CELL_CAMERA_ERROR_NOT_INIT; - } - - if (g_cfg.io.camera == camera_handler::null) - { - return not_an_error(CELL_CAMERA_ERROR_NOT_OPEN); - } - - std::lock_guard lock(g_camera.mutex); - - if (!g_camera.is_open) - { - return CELL_CAMERA_ERROR_NOT_OPEN; - } - if (!g_camera.is_attached) { return CELL_CAMERA_ERROR_DEVICE_NOT_FOUND; } + // TODO: Yet another CELL_CAMERA_ERROR_TIMEOUT + + if (g_camera.handler) + { + g_camera.handler->set_mirrored(!!g_camera.attr[CELL_CAMERA_MIRRORFLAG].v1); + g_camera.handler->set_frame_rate(g_camera.info.framerate); + + atomic_t wake_up = false; + + Emu.CallAfter([&wake_up, handler = g_camera.handler]() + { + handler->start_camera(); + wake_up = true; + wake_up.notify_one(); + }); + + while (!wake_up && !Emu.IsStopped()) + { + thread_ctrl::wait_on(wake_up, false); + } + } + g_camera.start_timestamp = get_guest_system_time(); g_camera.is_streaming = true; @@ -1000,7 +1253,7 @@ error_code cellCameraStartPost() error_code cellCameraRead(s32 dev_num, vm::ptr frame_num, vm::ptr bytes_read) { - cellCamera.todo("cellCameraRead(dev_num=%d, frame_num=*0x%x, bytes_read=*0x%x)", dev_num, frame_num, bytes_read); + cellCamera.notice("cellCameraRead(dev_num=%d, frame_num=*0x%x, bytes_read=*0x%x)", dev_num, frame_num, bytes_read); vm::ptr read_ex = vm::make_var({}); @@ -1030,7 +1283,7 @@ error_code cellCameraRead2() error_code cellCameraReadEx(s32 dev_num, vm::ptr read) { - cellCamera.todo("cellCameraReadEx(dev_num=%d, read=0x%x)", dev_num, read); + cellCamera.notice("cellCameraReadEx(dev_num=%d, read=0x%x)", dev_num, read); auto& g_camera = g_fxo->get(); @@ -1079,6 +1332,46 @@ error_code cellCameraReadEx(s32 dev_num, vm::ptr read) shared_data.frame_timestamp.exchange(read->timestamp); } + if (g_camera.handler) + { + u32 width{}; + u32 height{}; + u64 frame_number{}; + u64 bytes_read{}; + + atomic_t wake_up = false; + bool result = false; + + Emu.CallAfter([&]() + { + result = g_camera.handler->get_image(g_camera.info.buffer.get_ptr(), g_camera.info.bytesize, width, height, frame_number, bytes_read); + wake_up = true; + wake_up.notify_one(); + }); + + while (!wake_up && !Emu.IsStopped()) + { + thread_ctrl::wait_on(wake_up, false); + } + + if (!result) + { + g_camera.is_streaming = false; + g_camera.is_attached = false; + g_camera.is_open = false; + return CELL_CAMERA_ERROR_DEVICE_NOT_FOUND; + } + + if (read) + { + read->frame = frame_number; + read->bytesread = 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); + } + return CELL_OK; } @@ -1093,28 +1386,18 @@ error_code cellCameraStop(s32 dev_num) { cellCamera.todo("cellCameraStop(dev_num=%d)", dev_num); - if (!check_dev_num(dev_num)) + if (error_code error = check_init_and_open(dev_num)) { - return CELL_CAMERA_ERROR_PARAM; + return error; + } + + if (error_code error = check_resolution_ex(dev_num)) + { + return error; } auto& g_camera = g_fxo->get(); - if (!g_camera.init) - { - return CELL_CAMERA_ERROR_NOT_INIT; - } - - if (g_cfg.io.camera == camera_handler::null) - { - return not_an_error(CELL_CAMERA_ERROR_NOT_OPEN); - } - - if (!g_camera.is_open) - { - return CELL_CAMERA_ERROR_NOT_OPEN; - } - if (!g_camera.is_attached) { return CELL_CAMERA_ERROR_DEVICE_NOT_FOUND; @@ -1125,6 +1408,23 @@ error_code cellCameraStop(s32 dev_num) return CELL_CAMERA_ERROR_NOT_STARTED; } + if (g_camera.handler) + { + atomic_t wake_up = false; + + Emu.CallAfter([&wake_up, handler = g_camera.handler]() + { + handler->stop_camera(); + wake_up = true; + wake_up.notify_one(); + }); + + while (!wake_up && !Emu.IsStopped()) + { + thread_ctrl::wait_on(wake_up, false); + } + } + g_camera.is_streaming = false; return CELL_OK; @@ -1158,6 +1458,11 @@ error_code cellCameraSetNotifyEventQueue(u64 key) return CELL_OK; } + if (error_code error = check_resolution(0)) + { + return error; + } + g_camera.add_queue(key, 0, 0); return CELL_OK; @@ -1179,6 +1484,11 @@ error_code cellCameraRemoveNotifyEventQueue(u64 key) return CELL_OK; } + if (error_code error = check_resolution(0)) + { + return error; + } + g_camera.remove_queue(key); return CELL_OK; @@ -1188,11 +1498,6 @@ error_code cellCameraSetNotifyEventQueue2(u64 key, u64 source, u64 flag) { cellCamera.todo("cellCameraSetNotifyEventQueue2(key=0x%x, source=%d, flag=%d)", key, source, flag); - if (g_cfg.io.camera == camera_handler::null) - { - return CELL_OK; - } - auto& g_camera = g_fxo->get(); if (!g_camera.init) @@ -1200,6 +1505,16 @@ error_code cellCameraSetNotifyEventQueue2(u64 key, u64 source, u64 flag) return CELL_CAMERA_ERROR_NOT_INIT; } + if (g_cfg.io.camera == camera_handler::null) + { + return CELL_OK; + } + + if (error_code error = check_resolution(0)) + { + return error; + } + g_camera.add_queue(key, source, flag); return CELL_OK; @@ -1341,6 +1656,7 @@ void camera_context::reset_state() std::scoped_lock lock(mutex_notify_data_map); notify_data_map.clear(); + handler.reset(); } void camera_context::send_attach_state(bool attached) @@ -1372,7 +1688,9 @@ void camera_context::send_attach_state(bool attached) void camera_context::set_attr(s32 attrib, u32 arg1, u32 arg2) { - if (attrib == CELL_CAMERA_READMODE) + switch (attrib) + { + case CELL_CAMERA_READMODE: { if (arg1 != CELL_CAMERA_READ_FUNCCALL && arg1 != CELL_CAMERA_READ_DIRECT) { @@ -1380,6 +1698,18 @@ void camera_context::set_attr(s32 attrib, u32 arg1, u32 arg2) arg1 = CELL_CAMERA_READ_FUNCCALL; } read_mode.exchange(arg1); + break; + } + case CELL_CAMERA_MIRRORFLAG: + { + if (handler) + { + handler->set_mirrored(!!arg1); + } + break; + } + default: + break; } std::lock_guard lock(mutex); diff --git a/rpcs3/Emu/Cell/Modules/cellCamera.h b/rpcs3/Emu/Cell/Modules/cellCamera.h index c99ad956c9..10fc540a66 100644 --- a/rpcs3/Emu/Cell/Modules/cellCamera.h +++ b/rpcs3/Emu/Cell/Modules/cellCamera.h @@ -3,6 +3,7 @@ #include "Utilities/Timer.h" #include "Emu/Cell/lv2/sys_memory.h" #include "Utilities/Thread.h" +#include "Emu/Io/camera_handler_base.h" #include @@ -436,6 +437,8 @@ public: atomic_t init = 0; static constexpr auto thread_name = "Camera Thread"sv; + + std::shared_ptr handler; }; using camera_thread = named_thread; diff --git a/rpcs3/Emu/Io/Null/null_camera_handler.h b/rpcs3/Emu/Io/Null/null_camera_handler.h new file mode 100644 index 0000000000..c0bd2d0bc2 --- /dev/null +++ b/rpcs3/Emu/Io/Null/null_camera_handler.h @@ -0,0 +1,50 @@ +#pragma once + +#include "Emu/Io/camera_handler_base.h" + +class null_camera_handler final : public camera_handler_base +{ +public: + null_camera_handler() : camera_handler_base() {} + + void open_camera() override {}; + void close_camera() override {}; + void start_camera() override {}; + void stop_camera() override {}; + + void set_format(s32 format, u32 bytes_per_pixel) override + { + m_format = format; + m_bytes_per_pixel = bytes_per_pixel; + } + + void set_frame_rate(u32 frame_rate) override + { + m_frame_rate = frame_rate; + } + + void set_resolution(u32 width, u32 height) override + { + m_width = width; + m_height = height; + }; + + void set_mirrored(bool mirrored) override + { + m_mirrored = mirrored; + } + + u64 frame_number() const override + { + return 0; + } + + bool get_image(u8* /*buf*/, u64 /*size*/, u32& width, u32& height, u64& frame_number, u64& bytes_read) override + { + width = 0; + height = 0; + frame_number = 0; + bytes_read = 0; + return true; + } +}; diff --git a/rpcs3/Emu/Io/camera_handler_base.h b/rpcs3/Emu/Io/camera_handler_base.h new file mode 100644 index 0000000000..c421c9e58d --- /dev/null +++ b/rpcs3/Emu/Io/camera_handler_base.h @@ -0,0 +1,42 @@ +#pragma once + +#include +#include + +class camera_handler_base +{ +public: + enum class camera_handler_state + { + closed, + open, + running + }; + + virtual ~camera_handler_base() = default; + + virtual void open_camera() = 0; + virtual void close_camera() = 0; + virtual void start_camera() = 0; + virtual void stop_camera() = 0; + + virtual void set_format(s32 format, u32 bytes_per_pixel) = 0; + virtual void set_frame_rate(u32 frame_rate) = 0; + virtual void set_resolution(u32 width, u32 height) = 0; + virtual void set_mirrored(bool mirrored) = 0; + + virtual u64 frame_number() const = 0; // Convenience function to check if there's a new frame. + virtual bool get_image(u8* buf, u64 size, u32& width, u32& height, u64& frame_number, u64& bytes_read) = 0; + + camera_handler_state get_state() const { return m_state.load(); }; + +protected: + std::mutex m_mutex; + atomic_t m_state = camera_handler_state::closed; + bool m_mirrored = false; + s32 m_format = 2; // CELL_CAMERA_RAW8 + u32 m_bytes_per_pixel = 1; + u32 m_width = 640; + u32 m_height = 480; + u32 m_frame_rate = 30; +}; diff --git a/rpcs3/Emu/System.cpp b/rpcs3/Emu/System.cpp index 0eaad0b453..f7dd943b69 100644 --- a/rpcs3/Emu/System.cpp +++ b/rpcs3/Emu/System.cpp @@ -635,7 +635,7 @@ game_boot_result Emulator::Load(const std::string& title_id, bool add_only, bool if (!add_only) { - if (m_config_mode == cfg_mode::custom_selection || m_config_mode == cfg_mode::continuous) + if (m_config_mode == cfg_mode::custom_selection || (m_config_mode == cfg_mode::continuous && !m_config_path.empty())) { if (fs::file cfg_file{ m_config_path }) { diff --git a/rpcs3/Emu/System.h b/rpcs3/Emu/System.h index 783e68e111..2ebfff999a 100644 --- a/rpcs3/Emu/System.h +++ b/rpcs3/Emu/System.h @@ -70,6 +70,7 @@ struct EmuCallbacks std::function init_pad_handler; std::function()> get_gs_frame; std::function init_gs_render; + std::function()> get_camera_handler; std::function()> get_audio; std::function()> get_msg_dialog; std::function()> get_osk_dialog; diff --git a/rpcs3/Emu/system_config.h b/rpcs3/Emu/system_config.h index 079f6713e3..e08b863625 100644 --- a/rpcs3/Emu/system_config.h +++ b/rpcs3/Emu/system_config.h @@ -243,6 +243,7 @@ struct cfg_root : cfg::node cfg::_enum mouse{ this, "Mouse", mouse_handler::basic }; cfg::_enum camera{ this, "Camera", camera_handler::null }; cfg::_enum camera_type{ this, "Camera type", fake_camera_type::unknown }; + cfg::_enum camera_flip{ this, "Camera flip", camera_flip::none, true }; cfg::_enum move{ this, "Move", move_handler::null }; cfg::_enum buzz{ this, "Buzz emulated controller", buzz_handler::null }; cfg::_enum turntable{this, "Turntable emulated controller", turntable_handler::null}; diff --git a/rpcs3/Emu/system_config_types.cpp b/rpcs3/Emu/system_config_types.cpp index 8c96f9a618..3c6b744934 100644 --- a/rpcs3/Emu/system_config_types.cpp +++ b/rpcs3/Emu/system_config_types.cpp @@ -334,6 +334,7 @@ void fmt_class_string::format(std::string& out, u64 arg) { case camera_handler::null: return "Null"; case camera_handler::fake: return "Fake"; + case camera_handler::qt: return "Qt"; } return unknown; @@ -357,6 +358,23 @@ 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 value) + { + switch (value) + { + case camera_flip::none: return "None"; + case camera_flip::horizontal: return "Horizontal"; + case camera_flip::vertical: return "Vertical"; + case camera_flip::both: return "Both"; + } + + return unknown; + }); +} + template <> void fmt_class_string::format(std::string& out, u64 arg) { diff --git a/rpcs3/Emu/system_config_types.h b/rpcs3/Emu/system_config_types.h index fa876424d0..c68ba33d2c 100644 --- a/rpcs3/Emu/system_config_types.h +++ b/rpcs3/Emu/system_config_types.h @@ -78,6 +78,15 @@ enum class camera_handler { null, fake, + qt +}; + +enum class camera_flip +{ + none, + horizontal, + vertical, + both }; enum class fake_camera_type diff --git a/rpcs3/emucore.vcxproj b/rpcs3/emucore.vcxproj index 85d789f9d4..0fc877d913 100644 --- a/rpcs3/emucore.vcxproj +++ b/rpcs3/emucore.vcxproj @@ -445,6 +445,8 @@ + + @@ -458,15 +460,15 @@ - - - - - - - - - + + + + + + + + + diff --git a/rpcs3/emucore.vcxproj.filters b/rpcs3/emucore.vcxproj.filters index c0394c45b3..2f59ff84dc 100644 --- a/rpcs3/emucore.vcxproj.filters +++ b/rpcs3/emucore.vcxproj.filters @@ -1999,6 +1999,12 @@ Emu\GPU\RSX\Common + + Emu\Io + + + Emu\Io\Null + diff --git a/rpcs3/rpcs3.vcxproj b/rpcs3/rpcs3.vcxproj index ff1375b8d9..f4832f178c 100644 --- a/rpcs3/rpcs3.vcxproj +++ b/rpcs3/rpcs3.vcxproj @@ -71,7 +71,7 @@ - ..\3rdparty\flatbuffers\include;..\3rdparty\wolfssl\wolfssl;..\3rdparty\curl\curl\include;..\3rdparty\libusb\libusb\libusb;$(VULKAN_SDK)\Include;..\3rdparty\XAudio2Redist\include;$(QTDIR)\include;$(QTDIR)\include\QtWidgets;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtANGLE;$(QTDIR)\include\QtCore;.\release;$(QTDIR)\mkspecs\win32-msvc2015;.\QTGeneratedFiles\$(ConfigurationName);.\QTGeneratedFiles;$(QTDIR)\include\QtWinExtras;$(QTDIR)\include\QtConcurrent;%(AdditionalIncludeDirectories) + ..\3rdparty\flatbuffers\include;..\3rdparty\wolfssl\wolfssl;..\3rdparty\curl\curl\include;..\3rdparty\libusb\libusb\libusb;$(VULKAN_SDK)\Include;..\3rdparty\XAudio2Redist\include;$(QTDIR)\include;$(QTDIR)\include\QtWidgets;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtANGLE;$(QTDIR)\include\QtCore;.\release;$(QTDIR)\mkspecs\win32-msvc2015;.\QTGeneratedFiles\$(ConfigurationName);.\QTGeneratedFiles;$(QTDIR)\include\QtWinExtras;$(QTDIR)\include\QtConcurrent;$(QTDIR)\include\QtMultimedia;%(AdditionalIncludeDirectories) -Zc:strictStrings -Zc:throwingNew- -w34100 -w34189 -w44996 -w44456 -w44457 -w44458 %(AdditionalOptions) release\ false @@ -79,7 +79,7 @@ 4577;4467;%(DisableSpecificWarnings) $(IntDir) MaxSpeed - _WINDOWS;UNICODE;WIN32;WIN64;WITH_DISCORD_RPC;QT_NO_DEBUG;QT_WIDGETS_LIB;QT_GUI_LIB;QT_CORE_LIB;NDEBUG;QT_WINEXTRAS_LIB;QT_CONCURRENT_LIB;%(PreprocessorDefinitions) + _WINDOWS;UNICODE;WIN32;WIN64;WITH_DISCORD_RPC;QT_NO_DEBUG;QT_WIDGETS_LIB;QT_GUI_LIB;QT_CORE_LIB;NDEBUG;QT_WINEXTRAS_LIB;QT_CONCURRENT_LIB;QT_MULTIMEDIA_LIB;%(PreprocessorDefinitions) false $(IntDir)vc$(PlatformToolsetVersion).pdb true @@ -88,7 +88,7 @@ Level3 - Ole32.lib;gdi32.lib;..\hidapi.lib;..\libusb-1.0.lib;winmm.lib;OpenAL.lib;XAudio.lib;GLGSRender.lib;shlwapi.lib;VKGSRender.lib;vulkan-1.lib;wolfssl.lib;libcurl.lib;Wldap32.lib;glslang.lib;OSDependent.lib;OGLCompiler.lib;SPIRV.lib;MachineIndependent.lib;GenericCodeGen.lib;Advapi32.lib;user32.lib;zlib.lib;..\libpng16.lib;asmjit.lib;yaml-cpp.lib;discord-rpc.lib;emucore.lib;dxgi.lib;$(QTDIR)\lib\qtmain.lib;shell32.lib;$(QTDIR)\lib\Qt5Widgets.lib;$(QTDIR)\lib\Qt5Gui.lib;$(QTDIR)\lib\Qt5Core.lib;Qt5Core.lib;Qt5Gui.lib;Qt5Widgets.lib;Qt5WinExtras.lib;Qt5Concurrent.lib;7zlib.lib;SPIRV-Tools.lib;SPIRV-Tools-opt.lib;%(AdditionalDependencies) + Ole32.lib;gdi32.lib;..\hidapi.lib;..\libusb-1.0.lib;winmm.lib;OpenAL.lib;XAudio.lib;GLGSRender.lib;shlwapi.lib;VKGSRender.lib;vulkan-1.lib;wolfssl.lib;libcurl.lib;Wldap32.lib;glslang.lib;OSDependent.lib;OGLCompiler.lib;SPIRV.lib;MachineIndependent.lib;GenericCodeGen.lib;Advapi32.lib;user32.lib;zlib.lib;..\libpng16.lib;asmjit.lib;yaml-cpp.lib;discord-rpc.lib;emucore.lib;dxgi.lib;$(QTDIR)\lib\qtmain.lib;shell32.lib;$(QTDIR)\lib\Qt5Widgets.lib;$(QTDIR)\lib\Qt5Gui.lib;$(QTDIR)\lib\Qt5Core.lib;Qt5Core.lib;Qt5Gui.lib;Qt5Widgets.lib;Qt5WinExtras.lib;Qt5Concurrent.lib;7zlib.lib;SPIRV-Tools.lib;SPIRV-Tools-opt.lib;Qt5Multimedia.lib;%(AdditionalDependencies) ..\3rdparty\OpenAL\libs\Win64;..\3rdparty\glslang\build\hlsl\Release;..\3rdparty\glslang\build\SPIRV\Release;..\3rdparty\glslang\build\OGLCompilersDLL\Release;..\3rdparty\glslang\build\glslang\OSDependent\Windows\Release;..\3rdparty\glslang\build\glslang\Release;..\3rdparty\SPIRV\build\source\Release;..\3rdparty\SPIRV\build\source\opt\Release;..\lib\$(CONFIGURATION)-$(PLATFORM);..\3rdparty\XAudio2Redist\libs;..\3rdparty\discord-rpc\lib;$(QTDIR)\lib;%(AdditionalLibraryDirectories);$(VULKAN_SDK)\Lib "/MANIFESTDEPENDENCY:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' publicKeyToken='6595b64144ccf1df' language='*' processorArchitecture='*'" %(AdditionalOptions) true @@ -122,7 +122,7 @@ - ..\3rdparty\flatbuffers\include;..\3rdparty\wolfssl\wolfssl;..\3rdparty\curl\curl\include;..\3rdparty\libusb\libusb\libusb;$(VULKAN_SDK)\Include;..\3rdparty\XAudio2Redist\include;$(QTDIR)\include;$(QTDIR)\include\QtWidgets;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtANGLE;$(QTDIR)\include\QtCore;.\debug;$(QTDIR)\mkspecs\win32-msvc2015;.\QTGeneratedFiles\$(ConfigurationName);.\QTGeneratedFiles;$(QTDIR)\include\QtWinExtras;$(QTDIR)\include\QtConcurrent;%(AdditionalIncludeDirectories) + ..\3rdparty\flatbuffers\include;..\3rdparty\wolfssl\wolfssl;..\3rdparty\curl\curl\include;..\3rdparty\libusb\libusb\libusb;$(VULKAN_SDK)\Include;..\3rdparty\XAudio2Redist\include;$(QTDIR)\include;$(QTDIR)\include\QtWidgets;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtANGLE;$(QTDIR)\include\QtCore;.\debug;$(QTDIR)\mkspecs\win32-msvc2015;.\QTGeneratedFiles\$(ConfigurationName);.\QTGeneratedFiles;$(QTDIR)\include\QtWinExtras;$(QTDIR)\include\QtConcurrent;$(QTDIR)\include\QtMultimedia;%(AdditionalIncludeDirectories) -Zc:strictStrings -Zc:throwingNew- -w34100 -w34189 -w44996 -w44456 -w44457 -w44458 %(AdditionalOptions) debug\ false @@ -130,7 +130,7 @@ 4577;4467;%(DisableSpecificWarnings) $(IntDir) Disabled - _WINDOWS;UNICODE;WIN32;WIN64;QT_WIDGETS_LIB;QT_GUI_LIB;QT_CORE_LIB;QT_WINEXTRAS_LIB;QT_CONCURRENT_LIB;%(PreprocessorDefinitions) + _WINDOWS;UNICODE;WIN32;WIN64;QT_WIDGETS_LIB;QT_GUI_LIB;QT_CORE_LIB;QT_WINEXTRAS_LIB;QT_CONCURRENT_LIB;QT_MULTIMEDIA_LIB;%(PreprocessorDefinitions) false true true @@ -139,7 +139,7 @@ $(IntDir)vc$(PlatformToolsetVersion).pdb - Ole32.lib;gdi32.lib;..\hidapi.lib;..\libusb-1.0.lib;winmm.lib;OpenAL.lib;XAudio.lib;GLGSRender.lib;shlwapi.lib;VKGSRender.lib;vulkan-1.lib;wolfssl.lib;libcurl.lib;Wldap32.lib;glslangd.lib;OSDependentd.lib;OGLCompilerd.lib;SPIRVd.lib;MachineIndependentd.lib;GenericCodeGend.lib;Advapi32.lib;user32.lib;zlib.lib;..\libpng16.lib;asmjit.lib;yaml-cpp.lib;discord-rpc.lib;emucore.lib;dxgi.lib;$(QTDIR)\lib\qtmaind.lib;shell32.lib;$(QTDIR)\lib\Qt5Widgetsd.lib;$(QTDIR)\lib\Qt5Guid.lib;$(QTDIR)\lib\Qt5Cored.lib;Qt5Cored.lib;Qt5Guid.lib;Qt5Widgetsd.lib;Qt5WinExtrasd.lib;Qt5Concurrentd.lib;7zlib.lib;SPIRV-Tools.lib;SPIRV-Tools-opt.lib;%(AdditionalDependencies) + Ole32.lib;gdi32.lib;..\hidapi.lib;..\libusb-1.0.lib;winmm.lib;OpenAL.lib;XAudio.lib;GLGSRender.lib;shlwapi.lib;VKGSRender.lib;vulkan-1.lib;wolfssl.lib;libcurl.lib;Wldap32.lib;glslangd.lib;OSDependentd.lib;OGLCompilerd.lib;SPIRVd.lib;MachineIndependentd.lib;GenericCodeGend.lib;Advapi32.lib;user32.lib;zlib.lib;..\libpng16.lib;asmjit.lib;yaml-cpp.lib;discord-rpc.lib;emucore.lib;dxgi.lib;$(QTDIR)\lib\qtmaind.lib;shell32.lib;$(QTDIR)\lib\Qt5Widgetsd.lib;$(QTDIR)\lib\Qt5Guid.lib;$(QTDIR)\lib\Qt5Cored.lib;Qt5Cored.lib;Qt5Guid.lib;Qt5Widgetsd.lib;Qt5WinExtrasd.lib;Qt5Concurrentd.lib;7zlib.lib;SPIRV-Tools.lib;SPIRV-Tools-opt.lib;Qt5Multimediad.lib;%(AdditionalDependencies) ..\3rdparty\OpenAL\libs\Win64;..\3rdparty\glslang\build\hlsl\Debug;..\3rdparty\glslang\build\SPIRV\Debug;..\3rdparty\glslang\build\OGLCompilersDLL\Debug;..\3rdparty\glslang\build\glslang\OSDependent\Windows\Debug;..\3rdparty\glslang\build\glslang\Debug;..\3rdparty\SPIRV\build\source\opt\Debug;..\3rdparty\XAudio2Redist\libs;..\3rdparty\discord-rpc\lib;..\lib\$(CONFIGURATION)-$(PLATFORM);$(QTDIR)\lib;%(AdditionalLibraryDirectories);$(VULKAN_SDK)\Lib "/MANIFESTDEPENDENCY:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' publicKeyToken='6595b64144ccf1df' language='*' processorArchitecture='*'" /VERBOSE %(AdditionalOptions) true @@ -594,6 +594,7 @@ + @@ -604,6 +605,7 @@ + @@ -1088,6 +1090,7 @@ .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWITH_DISCORD_RPC -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DNDEBUG -DQT_WINEXTRAS_LIB -DQT_CONCURRENT_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\flatbuffers\include" "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I.\..\3rdparty\XAudio2Redist\include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" "-I$(QTDIR)\include\QtConcurrent" + $(QTDIR)\bin\moc.exe;%(FullPath) @@ -1130,6 +1133,7 @@ .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWITH_DISCORD_RPC -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DNDEBUG -DQT_WINEXTRAS_LIB -DQT_CONCURRENT_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I.\..\3rdparty\XAudio2Redist\include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" "-I$(QTDIR)\include\QtConcurrent" + $(QTDIR)\bin\moc.exe;%(FullPath) diff --git a/rpcs3/rpcs3.vcxproj.filters b/rpcs3/rpcs3.vcxproj.filters index 3fe6e350f6..143c58fbfe 100644 --- a/rpcs3/rpcs3.vcxproj.filters +++ b/rpcs3/rpcs3.vcxproj.filters @@ -133,6 +133,9 @@ {dfd401ec-dd22-4f8a-9dea-565a03efab6a} + + {f1996891-48b7-441a-b3fe-52794cbebe80} + @@ -777,6 +780,12 @@ Generated Files\Release + + Io\camera + + + Io\camera + @@ -911,6 +920,12 @@ Generated Files + + Io\camera + + + Io\camera + diff --git a/rpcs3/rpcs3qt/CMakeLists.txt b/rpcs3/rpcs3qt/CMakeLists.txt index 9bba87cde6..78a011755c 100644 --- a/rpcs3/rpcs3qt/CMakeLists.txt +++ b/rpcs3/rpcs3qt/CMakeLists.txt @@ -49,6 +49,8 @@ set(SRC_FILES persistent_settings.cpp pkg_install_dialog.cpp progress_dialog.cpp + qt_camera_handler.cpp + qt_camera_video_surface.cpp qt_utils.cpp register_editor_dialog.cpp recvmessage_dialog_frame.cpp diff --git a/rpcs3/rpcs3qt/emu_settings.cpp b/rpcs3/rpcs3qt/emu_settings.cpp index cd29a1d690..9fa30cb5c4 100644 --- a/rpcs3/rpcs3qt/emu_settings.cpp +++ b/rpcs3/rpcs3qt/emu_settings.cpp @@ -1007,11 +1007,21 @@ QString emu_settings::GetLocalizedSetting(const QString& original, emu_settings_ case fake_camera_type::uvc1_1: return tr("UVC 1.1", "Camera type"); } break; + case emu_settings_type::CameraFlip: + switch (static_cast(index)) + { + case camera_flip::none: return tr("No", "Camera flip"); + case camera_flip::horizontal: return tr("Flip horizontally", "Camera flip"); + case camera_flip::vertical: return tr("Flip vertically", "Camera flip"); + case camera_flip::both: return tr("Flip both axis", "Camera flip"); + } + break; case emu_settings_type::Camera: switch (static_cast(index)) { case camera_handler::null: return tr("Null", "Camera handler"); case camera_handler::fake: return tr("Fake", "Camera handler"); + case camera_handler::qt: return tr("Qt", "Camera handler"); } break; case emu_settings_type::Move: diff --git a/rpcs3/rpcs3qt/emu_settings_type.h b/rpcs3/rpcs3qt/emu_settings_type.h index 0dda65d73e..158ced6633 100644 --- a/rpcs3/rpcs3qt/emu_settings_type.h +++ b/rpcs3/rpcs3qt/emu_settings_type.h @@ -125,6 +125,7 @@ enum class emu_settings_type MouseHandler, Camera, CameraType, + CameraFlip, Move, Buzz, Turntable, @@ -279,6 +280,7 @@ inline static const QMap settings_location = { emu_settings_type::MouseHandler, { "Input/Output", "Mouse"}}, { emu_settings_type::Camera, { "Input/Output", "Camera"}}, { emu_settings_type::CameraType, { "Input/Output", "Camera type"}}, + { emu_settings_type::CameraFlip, { "Input/Output", "Camera flip"}}, { emu_settings_type::Move, { "Input/Output", "Move" }}, { emu_settings_type::Buzz, { "Input/Output", "Buzz emulated controller" }}, { emu_settings_type::Turntable, { "Input/Output", "Turntable emulated controller" }}, diff --git a/rpcs3/rpcs3qt/gui_application.cpp b/rpcs3/rpcs3qt/gui_application.cpp index a6c3d455fb..640bba95b5 100644 --- a/rpcs3/rpcs3qt/gui_application.cpp +++ b/rpcs3/rpcs3qt/gui_application.cpp @@ -15,11 +15,13 @@ #include "gl_gs_frame.h" #include "display_sleep_control.h" #include "localized_emu.h" +#include "qt_camera_handler.h" #ifdef WITH_DISCORD_RPC #include "_discord_utils.h" #endif +#include "Emu/Io/Null/null_camera_handler.h" #include "Emu/Cell/Modules/cellAudio.h" #include "Emu/RSX/Overlays/overlay_perf_metrics.h" #include "Emu/system_utils.hpp" @@ -349,6 +351,21 @@ void gui_application::InitializeCallbacks() } }; + callbacks.get_camera_handler = []() -> std::shared_ptr + { + switch (g_cfg.io.camera.get()) + { + case camera_handler::null: + case camera_handler::fake: + { + return std::make_shared(); + } + case camera_handler::qt: + { + return std::make_shared(); + } + } + }; callbacks.get_gs_frame = [this]() -> std::unique_ptr { return get_gs_frame(); }; callbacks.get_msg_dialog = [this]() -> std::shared_ptr { return m_show_gui ? std::make_shared() : nullptr; }; callbacks.get_osk_dialog = [this]() -> std::shared_ptr { return m_show_gui ? std::make_shared() : nullptr; }; diff --git a/rpcs3/rpcs3qt/qt_camera_handler.cpp b/rpcs3/rpcs3qt/qt_camera_handler.cpp new file mode 100644 index 0000000000..f0677e1e22 --- /dev/null +++ b/rpcs3/rpcs3qt/qt_camera_handler.cpp @@ -0,0 +1,333 @@ +#include "stdafx.h" +#include "qt_camera_handler.h" +#include "Emu/System.h" + +#include +#include + +LOG_CHANNEL(camera_log, "Camera"); + +qt_camera_handler::qt_camera_handler() : camera_handler_base() +{ +} + +qt_camera_handler::~qt_camera_handler() +{ + close_camera(); +} + +void qt_camera_handler::set_camera(const QCameraInfo& cameraInfo) +{ + if (cameraInfo.isNull()) + { + camera_log.error("No camera present"); + return; + } + + // Determine if the camera is front facing, in which case we will need to flip the image horizontally. + const bool front_facing = cameraInfo.position() == QCamera::Position::FrontFace; + + camera_log.success("Using camera: name=\"%s\", description=\"%s\", front_facing=%d", cameraInfo.deviceName().toStdString(), cameraInfo.description().toStdString(), front_facing); + + // Create camera and video surface + m_surface.reset(new qt_camera_video_surface(front_facing, nullptr)); + m_camera.reset(new QCamera(cameraInfo)); + + // Create connects (may not work due to threading) + connect(m_camera.get(), &QCamera::stateChanged, this, [this](QCamera::State state){ handle_camera_state(state); }); + connect(m_camera.get(), &QCamera::statusChanged, this, [this](QCamera::Status status){ handle_camera_status(status); }); + connect(m_camera.get(), &QCamera::errorOccurred, this, [this](QCamera::Error error){ handle_camera_error(error); }); + connect(m_camera.get(), &QCamera::captureModeChanged, this, [this](QCamera::CaptureModes modes){ handle_capture_modes(modes); }); + connect(m_camera.get(), QOverload::of(&QCamera::lockStatusChanged), this, [this](QCamera::LockStatus status, QCamera::LockChangeReason reason){ handle_lock_status(status, reason); }); + + // Set view finder and update the settings + m_camera->setViewfinder(m_surface.get()); + update_camera_settings(); + + // Log some states + handle_camera_state(m_camera->state()); + handle_lock_status(m_camera->lockStatus(), QCamera::UserRequest); +} + +void qt_camera_handler::open_camera() +{ + // List available cameras + for (const QCameraInfo& cameraInfo : QCameraInfo::availableCameras()) + { + camera_log.success("Found camera: name=%s, description=%s", cameraInfo.deviceName().toStdString(), cameraInfo.description().toStdString()); + } + + // Let's use the default camera for now + set_camera(QCameraInfo::defaultCamera()); + + camera_log.notice("Loading camera"); + + if (!m_camera) + { + camera_log.error("No camera found"); + m_state = camera_handler_state::closed; + return; + } + + if (m_camera->state() != QCamera::State::UnloadedState) + { + camera_log.notice("Camera already loaded"); + } + + // Load/open camera + m_camera->load(); + + // List all supported formats for debugging + for (const QCamera::FrameRateRange& frame_rate : m_camera->supportedViewfinderFrameRateRanges()) + { + camera_log.notice("Supported frame rate range: %f-%f", frame_rate.minimumFrameRate, frame_rate.maximumFrameRate); + } + for (const QVideoFrame::PixelFormat& pixel_format : m_camera->supportedViewfinderPixelFormats()) + { + camera_log.notice("Supported pixel format: %d", static_cast(pixel_format)); + } + for (const QSize& resolution : m_camera->supportedViewfinderResolutions()) + { + camera_log.notice("Supported resolution: %dx%d", resolution.width(), resolution.height()); + } + + // Update camera and view finder settings + update_camera_settings(); + + m_state = camera_handler_state::open; +} + +void qt_camera_handler::close_camera() +{ + camera_log.notice("Unloading camera"); + + if (!m_camera) + { + camera_log.error("No camera found"); + m_state = camera_handler_state::closed; + return; + } + + if (m_camera->state() == QCamera::State::UnloadedState) + { + camera_log.notice("Camera already unloaded"); + } + + // Unload/close camera + m_camera->unload(); + m_state = camera_handler_state::closed; +} + +void qt_camera_handler::start_camera() +{ + camera_log.notice("Starting camera"); + + if (!m_camera) + { + camera_log.error("No camera found"); + m_state = camera_handler_state::closed; + return; + } + + if (m_camera->state() == QCamera::State::ActiveState) + { + camera_log.notice("Camera already started"); + } + else if (m_camera->state() == QCamera::State::UnloadedState) + { + camera_log.notice("Camera not open"); + open_camera(); + } + + // Start camera. We will start receiving frames now. + m_camera->start(); + m_state = camera_handler_state::running; +} + +void qt_camera_handler::stop_camera() +{ + camera_log.notice("Stopping camera"); + + if (!m_camera) + { + camera_log.error("No camera found"); + m_state = camera_handler_state::closed; + return; + } + + if (m_camera->state() == QCamera::State::LoadedState) + { + camera_log.notice("Camera already stopped"); + } + + // Stop camera. The camera will still be drawing power. + m_camera->stop(); + m_state = camera_handler_state::open; +} + +void qt_camera_handler::set_format(s32 format, u32 bytes_per_pixel) +{ + m_format = format; + m_bytes_per_pixel = bytes_per_pixel; + + update_camera_settings(); +} + +void qt_camera_handler::set_frame_rate(u32 frame_rate) +{ + m_frame_rate = frame_rate; + + update_camera_settings(); +} + +void qt_camera_handler::set_resolution(u32 width, u32 height) +{ + m_width = width; + m_height = height; + + update_camera_settings(); +} + +void qt_camera_handler::set_mirrored(bool mirrored) +{ + m_mirrored = mirrored; + + update_camera_settings(); +} + +u64 qt_camera_handler::frame_number() const +{ + return m_surface ? m_surface->frame_number() : 0; +} + +bool qt_camera_handler::get_image(u8* buf, u64 size, u32& width, u32& height, u64& frame_number, u64& bytes_read) +{ + width = 0; + height = 0; + frame_number = 0; + bytes_read = 0; + + // Check for errors + if (!m_camera || !m_surface || m_state != camera_handler_state::running) + { + camera_log.error("Error: camera invalid"); + m_state = camera_handler_state::closed; + return false; + } + + if (QCamera::Error error = m_camera->error(); error != QCamera::NoError) + { + camera_log.error("Error: \"%s\" (error=%d)", m_camera ? m_camera->errorString().toStdString() : "", static_cast(error)); + m_state = camera_handler_state::closed; + return false; + } + + switch (QCamera::Status status = m_camera->status()) + { + case QCamera::UnavailableStatus: + case QCamera::UnloadedStatus: + case QCamera::UnloadingStatus: + camera_log.error("Camera not open. State=%d", static_cast(status)); + m_state = camera_handler_state::closed; + return false; + case QCamera::LoadedStatus: + case QCamera::StandbyStatus: + case QCamera::StoppingStatus: + camera_log.error("Camera not active. State=%d", static_cast(status)); + m_state = camera_handler_state::open; + return false; + case QCamera::LoadingStatus: + case QCamera::StartingStatus: + case QCamera::ActiveStatus: + default: + break; + } + + // Copy latest image into out buffer. + m_surface->get_image(buf, size, width, height, frame_number, bytes_read); + + return true; +} + +void qt_camera_handler::update_camera_settings() +{ + // Update camera if possible. We can only do this if it is already loaded. + if (m_camera && m_camera->state() != QCamera::State::UnloadedState) + { + // List all available settings in a cascading fashion and choose the proper value if possible. + // After each step, the next one will only list the settings that are compatible with the prior ones. + QCameraViewfinderSettings settings; + + // Set resolution if possible. + for (const QSize& resolution : m_camera->supportedViewfinderResolutions(settings)) + { + if (m_width == resolution.width() && m_height == resolution.height()) + { + settings.setResolution(resolution.width(), resolution.height()); + break; + } + } + + // Set frame rate if possible. + for (const QCamera::FrameRateRange& frame_rate : m_camera->supportedViewfinderFrameRateRanges(settings)) + { + // Some cameras do not have an exact match, so let's approximate. + if (static_cast(m_frame_rate) >= (frame_rate.maximumFrameRate - 0.5) && static_cast(m_frame_rate) <= (frame_rate.maximumFrameRate + 0.5)) + { + // Lock the frame rate by setting the min and max to the same value. + settings.setMinimumFrameRate(m_frame_rate); + settings.setMaximumFrameRate(m_frame_rate); + break; + } + } + + // Set pixel format if possible. (Unused for now, because formats differ between Qt and cell) + //for (const QVideoFrame::PixelFormat& pixel_format : m_camera->supportedViewfinderPixelFormats(settings)) + //{ + // if (pixel_format matches m_format) + // { + // settings.setPixelFormat(pixel_format); + // break; + // } + //} + + camera_log.notice("Setting view finder settings: frame_rate=%f, width=%d, height=%d, pixel_format=%d", + settings.maximumFrameRate(), settings.resolution().width(), settings.resolution().height(), static_cast(settings.pixelFormat())); + + // Apply settings. + m_camera->setViewfinderSettings(settings); + } + + // Update video surface if possible + if (m_surface) + { + m_surface->set_resolution(m_width, m_height); + m_surface->set_format(m_format, m_bytes_per_pixel); + m_surface->set_mirrored(m_mirrored); + } +} + +void qt_camera_handler::handle_camera_state(QCamera::State state) +{ + camera_log.notice("Camera state changed to %d", static_cast(state)); +} + +void qt_camera_handler::handle_camera_status(QCamera::Status status) +{ + camera_log.notice("Camera status changed to %d", static_cast(status)); +} + +void qt_camera_handler::handle_lock_status(QCamera::LockStatus status, QCamera::LockChangeReason reason) +{ + camera_log.notice("Camera lock status changed to %d (reason=%d)", static_cast(status), static_cast(reason)); +} + +void qt_camera_handler::handle_capture_modes(QCamera::CaptureModes capture_modes) +{ + camera_log.notice("Camera capture modes changed to %d", static_cast(capture_modes)); +} + +void qt_camera_handler::handle_camera_error(QCamera::Error error) +{ + camera_log.error("Error: \"%s\" (error=%d)", m_camera ? m_camera->errorString().toStdString() : "", static_cast(error)); +} diff --git a/rpcs3/rpcs3qt/qt_camera_handler.h b/rpcs3/rpcs3qt/qt_camera_handler.h new file mode 100644 index 0000000000..729ae4dfed --- /dev/null +++ b/rpcs3/rpcs3qt/qt_camera_handler.h @@ -0,0 +1,41 @@ +#pragma once + +#include "Emu/Io/camera_handler_base.h" +#include "qt_camera_video_surface.h" + +#include +#include +#include + +class video_surface; + +class qt_camera_handler final : public camera_handler_base, public QObject +{ +public: + qt_camera_handler(); + virtual ~qt_camera_handler(); + + void set_camera(const QCameraInfo &cameraInfo); + + void open_camera() override; + void close_camera() override; + void start_camera() override; + void stop_camera() override; + void set_format(s32 format, u32 bytes_per_pixel) override; + void set_frame_rate(u32 frame_rate) override; + void set_resolution(u32 width, u32 height) override; + void set_mirrored(bool mirrored) override; + u64 frame_number() const override; + bool get_image(u8* buf, u64 size, u32& width, u32& height, u64& frame_number, u64& bytes_read) override; + +private: + void handle_lock_status(QCamera::LockStatus, QCamera::LockChangeReason); + void handle_capture_modes(QCamera::CaptureModes capture_modes); + void handle_camera_state(QCamera::State state); + void handle_camera_status(QCamera::Status status); + void handle_camera_error(QCamera::Error error); + void update_camera_settings(); + + std::unique_ptr m_camera; + std::unique_ptr m_surface; +}; diff --git a/rpcs3/rpcs3qt/qt_camera_video_surface.cpp b/rpcs3/rpcs3qt/qt_camera_video_surface.cpp new file mode 100644 index 0000000000..c1ac12e0f8 --- /dev/null +++ b/rpcs3/rpcs3qt/qt_camera_video_surface.cpp @@ -0,0 +1,262 @@ +#include "stdafx.h" +#include "qt_camera_video_surface.h" + +#include "Emu/Cell/Modules/cellCamera.h" +#include "Emu/system_config.h" + +LOG_CHANNEL(camera_log, "Camera"); + +qt_camera_video_surface::qt_camera_video_surface(bool front_facing, QObject *parent) + : QAbstractVideoSurface(parent), m_front_facing(front_facing) +{ +} + +qt_camera_video_surface::~qt_camera_video_surface() +{ + std::lock_guard lock(m_mutex); + + // Free memory + for (auto& image_buffer : m_image_buffer) + { + if (image_buffer.data) + { + delete[] image_buffer.data; + image_buffer.data = nullptr; + } + } +} + +QList qt_camera_video_surface::supportedPixelFormats(QAbstractVideoBuffer::HandleType type) const +{ + Q_UNUSED(type) + + // Let's only allow RGB formats for now + QList result; + result + << QVideoFrame::Format_RGB32 + << QVideoFrame::Format_RGB24; + return result; +} + +bool qt_camera_video_surface::present(const QVideoFrame& frame) +{ + if (!frame.isValid()) + { + return false; + } + + // Get video image + QImage image = frame.image(); + + if (!image.isNull()) + { + // Scale image if necessary + if (m_width > 0 && m_height > 0 && m_width != image.width() && m_height != image.height()) + { + image = image.scaled(m_width, m_height, Qt::AspectRatioMode::KeepAspectRatio, Qt::SmoothTransformation); + } + + // Determine image flip + const camera_flip flip_setting = g_cfg.io.camera_flip; + + bool flip_horizontally = m_front_facing; // Front facing cameras are flipped already + if (flip_setting == camera_flip::horizontal || flip_setting == camera_flip::both) + { + flip_horizontally = !flip_horizontally; + } + if (m_mirrored) // Set by the game + { + flip_horizontally = !flip_horizontally; + } + + bool flip_vertically = true; // It appears games expect this. Might be camera specific. + if (flip_setting == camera_flip::vertical || flip_setting == camera_flip::both) + { + flip_vertically = !flip_vertically; + } + + // Flip image if necessary + if (flip_horizontally || flip_vertically) + { + image = image.mirrored(flip_horizontally, flip_vertically); + } + } + + const u64 new_size = m_width * m_height * m_bytes_per_pixel; + image_buffer& image_buffer = m_image_buffer[m_write_index]; + + // Reset buffer if necessary + if (image_buffer.size != new_size) + { + image_buffer.size = 0; + if (image_buffer.data) + { + delete[] image_buffer.data; + image_buffer.data = nullptr; + } + } + + // Create buffer if necessary + if (!image_buffer.data && new_size > 0) + { + image_buffer.data = new u8[new_size]; + image_buffer.size = new_size; + image_buffer.width = m_width; + image_buffer.height = m_height; + } + + if (image_buffer.size > 0 && !image.isNull()) + { + // 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 + + switch (m_format) + { + case CELL_CAMERA_JPG: + break; + case CELL_CAMERA_RGBA: + break; + case CELL_CAMERA_RAW8: // The game seems to expect BGGR + { + // Let's use a very simple algorithm to convert the image to raw BGGR + for (int y = 0; y < std::min(image_buffer.height, image.height()); y++) + { + for (int x = 0; x < std::min(image_buffer.width, image.width()); x++) + { + u8& pixel = image_buffer.data[image_buffer.width * y + x]; + const bool is_left_pixel = (x % 2) == 0; + const bool is_top_pixel = (y % 2) == 0; + + if (is_left_pixel && is_top_pixel) + { + pixel = qBlue(image.pixel(x, y)); + } + else if (is_left_pixel || is_top_pixel) + { + pixel = qGreen(image.pixel(x, y)); + } + else + { + pixel = qRed(image.pixel(x, y)); + } + } + } + break; + } + //case CELL_CAMERA_Y0_U_Y1_V: + case CELL_CAMERA_YUV422: + { + // Simple conversion from stackoverflow. + const int rgb_bytes_per_pixel = 4; + const int yuv_bytes_per_pixel = 2; + const int yuv_pitch = image_buffer.width * yuv_bytes_per_pixel; + + for (int y = 0; y < image_buffer.height; y++) + { + const uint8_t* rgb_row_ptr = image.constScanLine(y); + uint8_t* yuv_row_ptr = &image_buffer.data[y * yuv_pitch]; + + for (int x = 0; x < image_buffer.width - 1; x += 2) + { + const int rgb_index = x * rgb_bytes_per_pixel; + const int yuv_index = x * yuv_bytes_per_pixel; + + const uint8_t R1 = rgb_row_ptr[rgb_index + 0]; + const uint8_t G1 = rgb_row_ptr[rgb_index + 1]; + const uint8_t B1 = rgb_row_ptr[rgb_index + 2]; + //const uint8_t A1 = rgb_row_ptr[rgb_index + 3]; + const uint8_t R2 = rgb_row_ptr[rgb_index + 4]; + const uint8_t G2 = rgb_row_ptr[rgb_index + 5]; + const uint8_t B2 = rgb_row_ptr[rgb_index + 6]; + //const uint8_t A2 = rgb_row_ptr[rgb_index + 7]; + + const int Y = (0.257f * R1) + (0.504f * G1) + (0.098f * B1) + 16.0f; + const int U = -(0.148f * R1) - (0.291f * G1) + (0.439f * B1) + 128.0f; + const int V = (0.439f * R1) - (0.368f * G1) - (0.071f * B1) + 128.0f; + const int Y2 = (0.257f * R2) + (0.504f * G2) + (0.098f * B2) + 16.0f; + + yuv_row_ptr[yuv_index + 0] = std::max(0, std::min(Y, 255)); + yuv_row_ptr[yuv_index + 1] = std::max(0, std::min(U, 255)); + yuv_row_ptr[yuv_index + 2] = std::max(0, std::min(Y2, 255)); + yuv_row_ptr[yuv_index + 3] = std::max(0, std::min(V, 255)); + } + } + break; + } + case CELL_CAMERA_RAW10: + case CELL_CAMERA_YUV420: + case CELL_CAMERA_V_Y1_U_Y0: + case CELL_CAMERA_FORMAT_UNKNOWN: + default: + std::memcpy(image_buffer.data, image.constBits(), std::min(image_buffer.size, image.height() * image.bytesPerLine())); + break; + } + } + + camera_log.trace("Wrote image to video surface. index=%d, m_frame_number=%d, width=%d, height=%d, bytesPerLine=%d", + m_write_index, m_frame_number.load(), image.width(), image.height(), image.bytesPerLine()); + + // 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 qt_camera_video_surface::set_format(s32 format, u32 bytes_per_pixel) +{ + camera_log.notice("Setting format: format=%d, bytes_per_pixel=%d", format, bytes_per_pixel); + + m_format = format; + m_bytes_per_pixel = bytes_per_pixel; +} + +void qt_camera_video_surface::set_resolution(u32 width, u32 height) +{ + camera_log.notice("Setting resolution: width=%d, height=%d", width, height); + + m_width = width; + m_height = height; +} + +void qt_camera_video_surface::set_mirrored(bool mirrored) +{ + camera_log.notice("Setting mirrored: mirrored=%d", mirrored); + + m_mirrored = mirrored; +} + +u64 qt_camera_video_surface::frame_number() const +{ + return m_frame_number.load(); +} + +void qt_camera_video_surface::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) + { + bytes_read = std::min(image_buffer.size, size); + std::memcpy(buf, image_buffer.data, bytes_read); + } + else + { + bytes_read = 0; + } +} + +u32 qt_camera_video_surface::read_index() const +{ + // The read buffer index cannot be the same as the write index + return (m_write_index + 1u) % ::narrow(m_image_buffer.size()); +} diff --git a/rpcs3/rpcs3qt/qt_camera_video_surface.h b/rpcs3/rpcs3qt/qt_camera_video_surface.h new file mode 100644 index 0000000000..070323e881 --- /dev/null +++ b/rpcs3/rpcs3qt/qt_camera_video_surface.h @@ -0,0 +1,48 @@ +#pragma once + +#include +#include + +#include + +class qt_camera_video_surface final : public QAbstractVideoSurface +{ +public: + qt_camera_video_surface(bool front_facing, QObject *parent = nullptr); + virtual ~qt_camera_video_surface(); + + QList supportedPixelFormats(QAbstractVideoBuffer::HandleType type = QAbstractVideoBuffer::NoHandle) const override; + bool present(const QVideoFrame& frame) override; + + void set_format(s32 format, u32 bytes_per_pixel); + void set_resolution(u32 width, u32 height); + void set_mirrored(bool mirrored); + + u64 frame_number() const; + + void get_image(u8* buf, u64 size, u32& width, u32& height, u64& frame_number, u64& bytes_read); + +private: + u32 read_index() const; + + bool m_front_facing = false; + bool m_mirrored = false; // Set by cellCamera + s32 m_format = 2; // CELL_CAMERA_RAW8, set by cellCamera + u32 m_bytes_per_pixel = 1; + u32 m_width = 640; + u32 m_height = 480; + + std::mutex m_mutex; + atomic_t m_frame_number{0}; + u32 m_write_index{0}; + + struct image_buffer + { + u64 frame_number = 0; + u32 width = 0; + u32 height = 0; + u64 size = 0; + u8* data = nullptr; + }; + std::array m_image_buffer; +}; diff --git a/rpcs3/rpcs3qt/screenshot_preview.cpp b/rpcs3/rpcs3qt/screenshot_preview.cpp index fa239efb19..1bdc307ef0 100644 --- a/rpcs3/rpcs3qt/screenshot_preview.cpp +++ b/rpcs3/rpcs3qt/screenshot_preview.cpp @@ -21,6 +21,7 @@ screenshot_preview::screenshot_preview(const QString& filepath, QWidget* parent) setWindowTitle(tr("Screenshot Viewer")); setObjectName("screenshot_preview"); setContextMenuPolicy(Qt::CustomContextMenu); + setAttribute(Qt::WA_DeleteOnClose); setAlignment(Qt::AlignHCenter | Qt::AlignVCenter); setPixmap(QPixmap::fromImage(m_image)); setMinimumSize(160, 90); diff --git a/rpcs3/rpcs3qt/settings_dialog.cpp b/rpcs3/rpcs3qt/settings_dialog.cpp index e342b5a8c4..bf4677238e 100644 --- a/rpcs3/rpcs3qt/settings_dialog.cpp +++ b/rpcs3/rpcs3qt/settings_dialog.cpp @@ -955,6 +955,9 @@ settings_dialog::settings_dialog(std::shared_ptr gui_settings, std m_emu_settings->EnhanceComboBox(ui->cameraBox, emu_settings_type::Camera); SubscribeTooltip(ui->gb_camera_setting, tooltips.settings.camera); + m_emu_settings->EnhanceComboBox(ui->cameraFlipBox, emu_settings_type::CameraFlip); + SubscribeTooltip(ui->gb_camera_flip, tooltips.settings.camera_flip); + m_emu_settings->EnhanceComboBox(ui->moveBox, emu_settings_type::Move); SubscribeTooltip(ui->gb_move_handler, tooltips.settings.move); diff --git a/rpcs3/rpcs3qt/settings_dialog.ui b/rpcs3/rpcs3qt/settings_dialog.ui index dcb3586e92..b46b6c96ca 100644 --- a/rpcs3/rpcs3qt/settings_dialog.ui +++ b/rpcs3/rpcs3qt/settings_dialog.ui @@ -1494,7 +1494,7 @@ - + @@ -1520,7 +1520,16 @@ - + + + Camera Flip + + + + + + + diff --git a/rpcs3/rpcs3qt/tooltips.h b/rpcs3/rpcs3qt/tooltips.h index 39c0dcd776..f09419919a 100644 --- a/rpcs3/rpcs3qt/tooltips.h +++ b/rpcs3/rpcs3qt/tooltips.h @@ -192,8 +192,9 @@ public: const QString pad_handler_linux = tr("If you want to use the keyboard to control, select the Keyboard option.\nIf you have a DualShock 4, select DualShock 4."); const QString keyboard_handler = tr("Some games support native keyboard input.\nBasic will work in these cases."); const QString mouse_handler = tr("Some games support native mouse input.\nBasic will work in these cases."); - const QString camera = tr("Camera support is not implemented, leave this on null."); - const QString camera_type = tr("Camera support is not implemented, leave this on unknown."); + const QString camera = tr("Select Qt Camera to use the default camera device of your operating system."); + const QString camera_type = tr("Depending on the game, you may need to select a specific camera type."); + const QString camera_flip = tr("Flips the camera image either horizontally, vertically, or on both axis."); const QString move = tr("PlayStation Move support.\nFake: Experimental! This maps Move controls to DS3 controller mappings.\nMouse: Emulate PSMove with Mouse handler."); const QString buzz = tr("Buzz! support.\nSelect 1 or 2 controllers if the game requires Buzz! controllers and you don't have real controllers.\nSelect Null if the game has support for DualShock or if you have real Buzz! controllers."); const QString turntable = tr("DJ Hero Turntable controller support.\nSelect 1 or 2 controllers if the game requires DJ Hero Turntable controllers and you don't have real turntable controllers.\nSelect Null if the game has support for DualShock or if you have real turntable controllers.\nA real turntable controller can be used at the same time as an emulated turntable controller.");