diff options
| -rw-r--r-- | src/core/hle/service/nx2_gsv.h | 128 |
1 files changed, 113 insertions, 15 deletions
diff --git a/src/core/hle/service/nx2_gsv.h b/src/core/hle/service/nx2_gsv.h index 628bdeda5b..08f321f133 100644 --- a/src/core/hle/service/nx2_gsv.h +++ b/src/core/hle/service/nx2_gsv.h @@ -1405,8 +1405,20 @@ public: {3, &INx2Vi2Session::ReturnOne, "GetDisplayCount (guessed)"}, {5, &INx2Vi2Session::GetTimestampRecorderCount, "GetTimestampRecorderCount (guessed)"}, {11, &INx2Vi2Session::ReturnEvent, "Unknown11"}, - {31, &INx2Vi2Session::GetVsyncEvent, "GetVsyncEvent (guessed)"}, - {32, &INx2Vi2Session::GetVsyncEvent, "GetDisplayVsyncEvent (guessed)"}, + {31, &INx2Vi2Session::AcquireNativeEvent, "AcquireNativeEvent"}, + {32, &INx2Vi2Session::DuplicateNativeEvent, "DuplicateNativeEvent"}, + {261, &INx2Vi2Session::BindVsync, "BindDisplayVsyncEvent"}, + {262, &INx2Vi2Session::UnbindNativeEvent, "UnbindDisplayVsyncEvent"}, + {263, &INx2Vi2Session::BindChange, "BindDisplayRefreshRateEvent"}, + {264, &INx2Vi2Session::UnbindNativeEvent, "UnbindDisplayRefreshRateEvent"}, + {265, &INx2Vi2Session::BindChange, "BindDisplayHdrStatusEvent"}, + {266, &INx2Vi2Session::UnbindNativeEvent, "UnbindDisplayHdrStatusEvent"}, + {267, &INx2Vi2Session::BindChange, "BindDisplayHdrPaperWhiteEvent"}, + {268, &INx2Vi2Session::UnbindNativeEvent, "UnbindDisplayHdrPaperWhiteEvent"}, + {269, &INx2Vi2Session::BindChange, "BindDisplayBufferMaxResolutionEvent"}, + {270, &INx2Vi2Session::UnbindNativeEvent, "UnbindDisplayBufferMaxResolutionEvent"}, + {271, &INx2Vi2Session::BindChange, "BindDisplayModeCapabilityEvent"}, + {272, &INx2Vi2Session::UnbindNativeEvent, "UnbindDisplayModeCapabilityEvent"}, // Title 1 calls these ~75 times a second with the display name "default"; the // auto stub's empty success reply works, but logged two lines per call (a 50 MB log // in an 18 minute session). @@ -1774,17 +1786,105 @@ private: rb.PushCopyObjects(ctx, event->GetReadableEvent()); } - // 31(u32 kind) and 32(u32 display): events signalled on every vblank. Title 1's main thread - // waits on the 32 event until it has fired twice before it submits any GPU work, and hung - // there while it never fired. - void GetVsyncEvent(HLERequestContext& ctx) { - LOG_INFO(Service, "nx2:vi2 {} vsync event, [2]={:#x}", ctx.GetCommand(), ctx.CommandBuffer()[2]); - auto* event = m_context.CreateEvent(fmt::format("nx2:vi2:Vsync{}", m_events.size())); - m_events.push_back(event); - { - std::scoped_lock lk{m_vsync->mutex}; - m_vsync->events.push_back(event); + // 31(u32 index < 8): the session's event for that slot (the sdk picks a free index). The + // 261..272 commands bind a slot to a display event source and unbind it again. Until a slot + // is bound it fires on every vblank, as all of them used to. + void AcquireNativeEvent(HLERequestContext& ctx) { + const u32 index = ctx.CommandBuffer()[2]; + if (index >= m_native_events.size()) { + LOG_ERROR(Service, "nx2:vi2 31 index {} out of range", index); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultUnknown); + return; + } + LOG_INFO(Service, "nx2:vi2 31 native event {}", index); + ReplyNativeEvent(ctx, index); + } + + // 32(u32 index, copy handle): the client hands back its handle for an acquired slot and gets + // the slot's event again; the handle descriptor and handle come before the index. Games call + // it for slot 0 right after binding it to vsync. Title 1's main thread waits on this event + // until it has fired twice before it submits any GPU work, and hung there while it never + // fired. + void DuplicateNativeEvent(HLERequestContext& ctx) { + const u32* words = ctx.CommandBuffer(); + u32 data = 2; + if (words[1] >> 31) { + const u32 descriptor = words[2]; + data = 3 + ((descriptor >> 1) & 0xf) + ((descriptor >> 5) & 0xf) + (descriptor & 1 ? 2 : 0); + } + const u32 index = words[data]; + if (index >= m_native_events.size()) { + LOG_ERROR(Service, "nx2:vi2 32 index {} out of range", index); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultUnknown); + return; + } + LOG_INFO(Service, "nx2:vi2 32 native event {}", index); + ReplyNativeEvent(ctx, index); + } + + // 261(u64 display name, u32 index): fire the slot on every vblank. + void BindVsync(HLERequestContext& ctx) { + const u32 index = ctx.CommandBuffer()[4]; + LOG_INFO(Service, "nx2:vi2 261 native event {} -> vsync", index); + if (index < m_native_events.size() && m_native_events[index]) { + SetVsyncSlot(index, true); + } + TipcReplyWords(ctx, {}); + } + + // 263/265/267/269/271(u64 display name, u32 index): fire the slot when the display's refresh + // rate, HDR status, HDR paper white, buffer max resolution or mode capability changes, so the + // game re-queries it. None of them change here: fire once so the game reads the current + // state, then leave the slot quiet. Kirby Air Riders re-queried its HDR status on every + // present while this slot fired with vsync. + void BindChange(HLERequestContext& ctx) { + const u32 index = ctx.CommandBuffer()[4]; + LOG_INFO(Service, "nx2:vi2 {} native event {} -> change notification", ctx.GetCommand(), index); + if (index < m_native_events.size() && m_native_events[index]) { + SetVsyncSlot(index, false); + m_native_events[index]->Signal(system.Kernel()); + } + TipcReplyWords(ctx, {}); + } + + // 262..272 even (u64 display name, u32 index): detach the slot from its source. + void UnbindNativeEvent(HLERequestContext& ctx) { + const u32 index = ctx.CommandBuffer()[4]; + LOG_INFO(Service, "nx2:vi2 {} native event {} unbound", ctx.GetCommand(), index); + if (index < m_native_events.size() && m_native_events[index]) { + SetVsyncSlot(index, false); } + TipcReplyWords(ctx, {}); + } + + void ReplyNativeEvent(HLERequestContext& ctx, u32 index) { + auto*& event = m_native_events[index]; + if (!event) { + event = m_context.CreateEvent(fmt::format("nx2:vi2:Native{}", index)); + m_events.push_back(event); + SetVsyncSlot(index, true); + StartVsyncTimer(); + } + IPC::ResponseBuilder rb{ctx, 2, 1, 0}; + rb.Push(ResultSuccess); + rb.PushCopyObjects(ctx, event->GetReadableEvent()); + } + + void SetVsyncSlot(u32 index, bool on) { + auto* event = m_native_events[index]; + std::scoped_lock lk{m_vsync->mutex}; + auto& list = m_vsync->events; + const auto it = std::find(list.begin(), list.end(), event); + if (on && it == list.end()) { + list.push_back(event); + } else if (!on && it != list.end()) { + list.erase(it); + } + } + + void StartVsyncTimer() { if (!m_vsync_timer) { m_vsync_timer = Core::Timing::CreateEvent( "nx2:vi2 vsync", @@ -1805,14 +1905,12 @@ private: }()}; system.CoreTiming().ScheduleLoopingEvent(VblankInterval, VblankInterval, m_vsync_timer); } - IPC::ResponseBuilder rb{ctx, 2, 1, 0}; - rb.Push(ResultSuccess); - rb.PushCopyObjects(ctx, event->GetReadableEvent()); } KernelHelpers::ServiceContext m_context; std::shared_ptr<Nx2NativeObjects> m_objects; std::vector<Kernel::KEvent*> m_events; + std::array<Kernel::KEvent*, 8> m_native_events{}; ///< 31's slots, also listed in m_events // Shared with the vsync timer callback, which can outlive the session by one call. struct VsyncState { std::mutex mutex; |
