From eced511382306695afd6673995c25f080c051b84 Mon Sep 17 00:00:00 2001 From: zhupengfei Date: Sun, 1 Mar 2020 17:07:43 +0800 Subject: [PATCH 1/2] service/cam: Implement Vsync interrupt callbacks According to HW tests, this vsync event is signaled for activated cameras at about the same frequency as the frame rate. The last 5 vsync timings are recorded (in microseconds) and can be retrieved with the service function. Also, corrected the default frame_rate to 15, according to HW test. This should fix the missing camera images in certain games. --- src/core/hle/service/cam/cam.cpp | 57 ++++++++++++++++++++++++++++++ src/core/hle/service/cam/cam.h | 22 +++++++++++- src/core/hle/service/cam/cam_c.cpp | 2 +- src/core/hle/service/cam/cam_s.cpp | 2 +- src/core/hle/service/cam/cam_u.cpp | 2 +- 5 files changed, 81 insertions(+), 4 deletions(-) diff --git a/src/core/hle/service/cam/cam.cpp b/src/core/hle/service/cam/cam.cpp index bf1bab8c0c..47076f50c7 100644 --- a/src/core/hle/service/cam/cam.cpp +++ b/src/core/hle/service/cam/cam.cpp @@ -60,6 +60,7 @@ void Module::PortConfig::Clear() { completion_event->Clear(); buffer_error_interrupt_event->Clear(); vsync_interrupt_event->Clear(); + vsync_timings.clear(); is_receiving = false; is_active = false; is_pending_receiving = false; @@ -133,6 +134,27 @@ void Module::CompletionEventCallBack(u64 port_id, s64) { port.completion_event->Signal(); } +static constexpr std::size_t MaxVsyncTimings = 5; + +void Module::VsyncInterruptEventCallBack(u64 port_id, s64 cycles_late) { + PortConfig& port = ports[port_id]; + const CameraConfig& camera = cameras[port.camera_id]; + + if (!port.is_active) { + return; + } + + port.vsync_timings.emplace_front(system.CoreTiming().GetGlobalTimeUs().count()); + if (port.vsync_timings.size() > MaxVsyncTimings) { + port.vsync_timings.pop_back(); + } + port.vsync_interrupt_event->Signal(); + + system.CoreTiming().ScheduleEvent( + msToCycles(LATENCY_BY_FRAME_RATE[static_cast(camera.frame_rate)]) - cycles_late, + vsync_interrupt_event_callback, port_id); +} + void Module::StartReceiving(int port_id) { PortConfig& port = ports[port_id]; port.is_receiving = true; @@ -173,6 +195,9 @@ void Module::ActivatePort(int port_id, int camera_id) { } ports[port_id].is_active = true; ports[port_id].camera_id = camera_id; + system.CoreTiming().ScheduleEvent( + msToCycles(LATENCY_BY_FRAME_RATE[static_cast(cameras[camera_id].frame_rate)]), + vsync_interrupt_event_callback, port_id); } template @@ -631,6 +656,7 @@ void Module::Interface::Activate(Kernel::HLERequestContext& ctx) { cam->ports[i].is_busy = false; } cam->ports[i].is_active = false; + cam->system.CoreTiming().UnscheduleEvent(cam->vsync_interrupt_event_callback, i); } rb.Push(RESULT_SUCCESS); } else if (camera_select[0] && camera_select[1]) { @@ -860,6 +886,33 @@ void Module::Interface::SynchronizeVsyncTiming(Kernel::HLERequestContext& ctx) { camera_select1, camera_select2); } +void Module::Interface::GetLatestVsyncTiming(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 0x2A, 2, 0); + const PortSet port_select(rp.Pop()); + const u32 count = rp.Pop(); + + if (!port_select.IsSingle() || count > MaxVsyncTimings) { + IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); + rb.Push(ERROR_OUT_OF_RANGE); + rb.PushStaticBuffer({}, 0); + return; + } + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); + rb.Push(RESULT_SUCCESS); + + std::vector out(count * sizeof(s64_le)); + std::size_t offset = 0; + for (const s64_le timing : cam->ports[port_select.m_val].vsync_timings) { + std::memcpy(out.data() + offset * sizeof(timing), &timing, sizeof(timing)); + offset++; + if (offset >= count) { + break; + } + } + rb.PushStaticBuffer(out, 0); +} + void Module::Interface::GetStereoCameraCalibrationData(Kernel::HLERequestContext& ctx) { IPC::RequestBuilder rb = IPC::RequestParser(ctx, 0x2B, 0, 0).MakeBuilder(17, 0); @@ -1032,6 +1085,10 @@ Module::Module(Core::System& system) : system(system) { completion_event_callback = system.CoreTiming().RegisterEvent( "CAM::CompletionEventCallBack", [this](u64 userdata, s64 cycles_late) { CompletionEventCallBack(userdata, cycles_late); }); + vsync_interrupt_event_callback = system.CoreTiming().RegisterEvent( + "CAM::VsyncInterruptEventCallBack", [this](u64 userdata, s64 cycles_late) { + VsyncInterruptEventCallBack(userdata, cycles_late); + }); } Module::~Module() { diff --git a/src/core/hle/service/cam/cam.h b/src/core/hle/service/cam/cam.h index 9f3d819901..989a3a6a74 100644 --- a/src/core/hle/service/cam/cam.h +++ b/src/core/hle/service/cam/cam.h @@ -5,6 +5,7 @@ #pragma once #include +#include #include #include #include @@ -616,6 +617,21 @@ public: */ void SynchronizeVsyncTiming(Kernel::HLERequestContext& ctx); + /** + * Gets the vsync timing record of the specified camera for the specified number of signals. + * Inputs: + * 0: 0x002A0080 + * 1: Port + * 2: Number of timings to get + * 64: ((PastTimings * 8) << 14) | 2 + * 65: s64* TimingsOutput + * Outputs: + * 0: 0x002A0042 + * 1: ResultCode + * 2-3: Output static buffer + */ + void GetLatestVsyncTiming(Kernel::HLERequestContext& ctx); + /** * Returns calibration data relating the outside cameras to each other, for use in AR * applications. @@ -716,6 +732,7 @@ public: private: void CompletionEventCallBack(u64 port_id, s64); + void VsyncInterruptEventCallBack(u64 port_id, s64 cycles_late); // Starts a receiving process on the specified port. This can only be called when is_busy = true // and is_receiving = false. @@ -744,7 +761,7 @@ private: std::unique_ptr impl; std::array contexts; int current_context{0}; - FrameRate frame_rate{FrameRate::Rate_5}; + FrameRate frame_rate{FrameRate::Rate_15}; }; struct PortConfig { @@ -773,6 +790,8 @@ private: std::shared_ptr buffer_error_interrupt_event; std::shared_ptr vsync_interrupt_event; + std::deque vsync_timings; + std::future> capture_result; // will hold the received frame. Kernel::Process* dest_process{nullptr}; VAddr dest{0}; // the destination address of the receiving process @@ -787,6 +806,7 @@ private: std::array cameras; std::array ports; Core::TimingEventType* completion_event_callback; + Core::TimingEventType* vsync_interrupt_event_callback; std::atomic is_camera_reload_pending{false}; }; diff --git a/src/core/hle/service/cam/cam_c.cpp b/src/core/hle/service/cam/cam_c.cpp index 80816c9237..e5aca5361a 100644 --- a/src/core/hle/service/cam/cam_c.cpp +++ b/src/core/hle/service/cam/cam_c.cpp @@ -50,7 +50,7 @@ CAM_C::CAM_C(std::shared_ptr cam) : Module::Interface(std::move(cam), "c {0x00270140, nullptr, "SetAutoWhiteBalanceWindow"}, {0x00280080, nullptr, "SetNoiseFilter"}, {0x00290080, &CAM_C::SynchronizeVsyncTiming, "SynchronizeVsyncTiming"}, - {0x002A0080, nullptr, "GetLatestVsyncTiming"}, + {0x002A0080, &CAM_C::GetLatestVsyncTiming, "GetLatestVsyncTiming"}, {0x002B0000, &CAM_C::GetStereoCameraCalibrationData, "GetStereoCameraCalibrationData"}, {0x002C0400, nullptr, "SetStereoCameraCalibrationData"}, {0x002D00C0, nullptr, "WriteRegisterI2c"}, diff --git a/src/core/hle/service/cam/cam_s.cpp b/src/core/hle/service/cam/cam_s.cpp index c4936f9b54..606c8d3af7 100644 --- a/src/core/hle/service/cam/cam_s.cpp +++ b/src/core/hle/service/cam/cam_s.cpp @@ -50,7 +50,7 @@ CAM_S::CAM_S(std::shared_ptr cam) : Module::Interface(std::move(cam), "c {0x00270140, nullptr, "SetAutoWhiteBalanceWindow"}, {0x00280080, nullptr, "SetNoiseFilter"}, {0x00290080, &CAM_S::SynchronizeVsyncTiming, "SynchronizeVsyncTiming"}, - {0x002A0080, nullptr, "GetLatestVsyncTiming"}, + {0x002A0080, &CAM_S::GetLatestVsyncTiming, "GetLatestVsyncTiming"}, {0x002B0000, &CAM_S::GetStereoCameraCalibrationData, "GetStereoCameraCalibrationData"}, {0x002C0400, nullptr, "SetStereoCameraCalibrationData"}, {0x002D00C0, nullptr, "WriteRegisterI2c"}, diff --git a/src/core/hle/service/cam/cam_u.cpp b/src/core/hle/service/cam/cam_u.cpp index 3c3c4b17df..83cace5a70 100644 --- a/src/core/hle/service/cam/cam_u.cpp +++ b/src/core/hle/service/cam/cam_u.cpp @@ -50,7 +50,7 @@ CAM_U::CAM_U(std::shared_ptr cam) : Module::Interface(std::move(cam), "c {0x00270140, nullptr, "SetAutoWhiteBalanceWindow"}, {0x00280080, nullptr, "SetNoiseFilter"}, {0x00290080, &CAM_U::SynchronizeVsyncTiming, "SynchronizeVsyncTiming"}, - {0x002A0080, nullptr, "GetLatestVsyncTiming"}, + {0x002A0080, &CAM_U::GetLatestVsyncTiming, "GetLatestVsyncTiming"}, {0x002B0000, &CAM_U::GetStereoCameraCalibrationData, "GetStereoCameraCalibrationData"}, {0x002C0400, nullptr, "SetStereoCameraCalibrationData"}, {0x002D00C0, nullptr, "WriteRegisterI2c"}, From 28014496c67eb7547f7df3821d07d7a2c32de661 Mon Sep 17 00:00:00 2001 From: zhupengfei Date: Tue, 3 Mar 2020 12:13:45 +0800 Subject: [PATCH 2/2] Fix incorrect port_id use --- src/core/hle/service/cam/cam.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/core/hle/service/cam/cam.cpp b/src/core/hle/service/cam/cam.cpp index 47076f50c7..c5d24c3144 100644 --- a/src/core/hle/service/cam/cam.cpp +++ b/src/core/hle/service/cam/cam.cpp @@ -901,9 +901,10 @@ void Module::Interface::GetLatestVsyncTiming(Kernel::HLERequestContext& ctx) { IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); rb.Push(RESULT_SUCCESS); + const std::size_t port_id = port_select.m_val == 1 ? 0 : 1; std::vector out(count * sizeof(s64_le)); std::size_t offset = 0; - for (const s64_le timing : cam->ports[port_select.m_val].vsync_timings) { + for (const s64_le timing : cam->ports[port_id].vsync_timings) { std::memcpy(out.data() + offset * sizeof(timing), &timing, sizeof(timing)); offset++; if (offset >= count) {