graphics_renderer_frame.cpp 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. #include <Windows.h>
  2. #include <array>
  3. #include <imgui.h>
  4. #include <imgui_impl_dx11.h>
  5. #include <imgui_impl_win32.h>
  6. #include "../../../../core/ui/busy/busy.h"
  7. #include "../../../../core/ui/fonts/runtime/ui_runtime_font_lifecycle.h"
  8. #include "../../../../core/ui/layout/layout.h"
  9. #include "../../../../core/ui/notice/ui_notice_overlay.h"
  10. #include "../../../../core/ui/runtime/ui_visibility_runtime.h"
  11. #include "../../../../core/ui/scaling/dpi/ui_dpi_scaling.h"
  12. #include "../../../../core/ui/theme/sunrise_ui_theme.h"
  13. #include "../input/input.h"
  14. #include "graphics_renderer_report.h"
  15. #include "state.h"
  16. extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND window,
  17. UINT message,
  18. WPARAM word,
  19. LPARAM value);
  20. namespace sunrise::client::hooks::graphics::renderer {
  21. namespace {
  22. /** The overlay uses one render target, set only while Dear ImGui draws. */
  23. constexpr UINT kOverlayRenderTargetCount = 1;
  24. // A visible UI eats this whole range. A range that stopped short of the wheel or the extra
  25. // buttons would leak them to the game with no other sign.
  26. static_assert(WM_MOUSEWHEEL <= WM_MOUSELAST);
  27. static_assert(WM_MOUSEHWHEEL <= WM_MOUSELAST);
  28. static_assert(WM_XBUTTONDBLCLK <= WM_MOUSELAST);
  29. /** @param message Win32 message ID. @return True for ordinary client-area mouse input. */
  30. [[nodiscard]] bool is_mouse_input(UINT message) noexcept {
  31. return message >= WM_MOUSEFIRST && message <= WM_MOUSELAST;
  32. }
  33. /** @param message Win32 message ID. @return True for the raw mouse and keyboard delivery. */
  34. [[nodiscard]] bool is_raw_input(UINT message) noexcept {
  35. return message == WM_INPUT;
  36. }
  37. /**
  38. * Says which keyboard and text messages the UI bridge takes.
  39. * @param message Win32 message ID.
  40. * @return True for ordinary keyboard or text input.
  41. */
  42. [[nodiscard]] bool is_keyboard_input(UINT message) noexcept {
  43. switch (message) {
  44. case WM_KEYDOWN:
  45. case WM_KEYUP:
  46. case WM_SYSKEYDOWN:
  47. case WM_SYSKEYUP:
  48. case WM_CHAR:
  49. case WM_SYSCHAR:
  50. case WM_DEADCHAR:
  51. case WM_SYSDEADCHAR:
  52. return true;
  53. default:
  54. return false;
  55. }
  56. }
  57. /**
  58. * Applies a visibility change to Dear ImGui input and drops events queued while hidden.
  59. * @param visible Current Core visibility state.
  60. */
  61. void transition_input_visibility_locked(bool visible) noexcept {
  62. ImGuiIO& io = ImGui::GetIO();
  63. // The game renders a virtual cursor, so visible Sunrise frames need ImGui's software cursor.
  64. io.MouseDrawCursor = visible;
  65. if (visible) {
  66. g_resources.inputVisible = true;
  67. return;
  68. }
  69. if (!g_resources.inputVisible) {
  70. return;
  71. }
  72. io.ClearEventsQueue();
  73. io.ClearInputKeys();
  74. io.ClearInputMouse();
  75. g_resources.inputVisible = false;
  76. // The capture release is deferred because ReleaseCapture can re-enter WndProc at once.
  77. g_captureReleaseWindow = g_resources.window;
  78. }
  79. /**
  80. * Draws Dear ImGui data, then puts back every output-merger target that was set before.
  81. * @param drawData Completed frame draw data.
  82. */
  83. void draw_data(ImDrawData* drawData) noexcept {
  84. if (drawData == nullptr) {
  85. return;
  86. }
  87. std::array<ID3D11RenderTargetView*, D3D11_SIMULTANEOUS_RENDER_TARGET_COUNT> priorTargets{};
  88. ID3D11DepthStencilView* priorDepth = nullptr;
  89. g_resources.context->OMGetRenderTargets(
  90. static_cast<UINT>(priorTargets.size()), priorTargets.data(), &priorDepth);
  91. ID3D11RenderTargetView* overlayTarget = g_resources.renderTarget;
  92. g_resources.context->OMSetRenderTargets(kOverlayRenderTargetCount, &overlayTarget, nullptr);
  93. ImGui_ImplDX11_RenderDrawData(drawData);
  94. g_resources.context->OMSetRenderTargets(
  95. static_cast<UINT>(priorTargets.size()), priorTargets.data(), priorDepth);
  96. for (ID3D11RenderTargetView* target : priorTargets) {
  97. if (target != nullptr) {
  98. target->Release();
  99. }
  100. }
  101. if (priorDepth != nullptr) {
  102. priorDepth->Release();
  103. }
  104. }
  105. } // namespace
  106. /**
  107. * Runs one Dear ImGui frame. Draw data is sent only while the Core UI is visible.
  108. */
  109. void render_frame_locked() noexcept {
  110. if (!fully_active_locked()) {
  111. return;
  112. }
  113. if (core::ui::scaling::dpi::update(g_resources.window)) {
  114. // Style and text scale change together, before the backend sets up the frame.
  115. core::ui::theme::apply();
  116. if (!core::ui::fonts::runtime::apply_scale(core::ui::scaling::dpi::current())) {
  117. report::note(report::Stage::frame, report::Reason::fontScale);
  118. (void)shutdown_locked();
  119. return;
  120. }
  121. }
  122. const core::ui::runtime::VisibilitySnapshot visibility = core::ui::runtime::snapshot();
  123. transition_input_visibility_locked(visibility.visible);
  124. ImGui_ImplDX11_NewFrame();
  125. ImGui_ImplWin32_NewFrame();
  126. ImGui::NewFrame();
  127. // A hidden surface still draws until its close animation ends, so the layout decides. The
  128. // running-work overlay draws whether the surface is open or not.
  129. const bool surfaceDrawn = core::ui::layout::render(visibility.visible);
  130. const bool busyDrawn = core::ui::busy::draw();
  131. const bool noticeDrawn = core::ui::notice::draw();
  132. if (!surfaceDrawn && !busyDrawn && !noticeDrawn) {
  133. // A frame nobody claimed still drains backend state, and sends no draw data.
  134. ImGui::EndFrame();
  135. return;
  136. }
  137. ImGui::Render();
  138. draw_data(ImGui::GetDrawData());
  139. }
  140. /** Feeds one ordinary window message into the active Dear ImGui context. */
  141. bool handle_window_message(HWND window, UINT message, WPARAM word, LPARAM value) noexcept {
  142. AcquireSRWLockExclusive(&g_rendererLock);
  143. if (!fully_active_locked() || g_resources.window != window) {
  144. ReleaseSRWLockExclusive(&g_rendererLock);
  145. return false;
  146. }
  147. const core::ui::runtime::VisibilitySnapshot visibility = core::ui::runtime::snapshot();
  148. transition_input_visibility_locked(visibility.visible);
  149. if (!visibility.visible) {
  150. // Hidden input stays with the game and never enters Dear ImGui's event queue.
  151. ReleaseSRWLockExclusive(&g_rendererLock);
  152. return false;
  153. }
  154. (void)ImGui_ImplWin32_WndProcHandler(window, message, word, value);
  155. // A visible UI is modal: no mouse, keyboard or raw input reaches the game, hit test aside.
  156. const bool capture =
  157. is_mouse_input(message) || is_keyboard_input(message) || is_raw_input(message);
  158. ReleaseSRWLockExclusive(&g_rendererLock);
  159. return capture;
  160. }
  161. /** Releases mouse capture, but only after the renderer and window locks are gone. */
  162. void dispatch_pending_input_release(HWND window) noexcept {
  163. AcquireSRWLockExclusive(&g_rendererLock);
  164. const DWORD windowThread = GetWindowThreadProcessId(window, nullptr);
  165. const bool releaseCapture = g_captureReleaseWindow == window && windowThread != 0
  166. && windowThread == GetCurrentThreadId();
  167. if (releaseCapture) {
  168. g_captureReleaseWindow = nullptr;
  169. }
  170. ReleaseSRWLockExclusive(&g_rendererLock);
  171. if (releaseCapture && GetCapture() == window) {
  172. // The renderer lock is gone first, because Windows sends WM_CAPTURECHANGED at once.
  173. (void)ReleaseCapture();
  174. }
  175. }
  176. /** Runs any deferred capture release once the hook and renderer locks are gone. */
  177. void dispatch_pending_input_release() noexcept {
  178. AcquireSRWLockShared(&g_rendererLock);
  179. const HWND window = g_captureReleaseWindow;
  180. ReleaseSRWLockShared(&g_rendererLock);
  181. if (window == nullptr) {
  182. return;
  183. }
  184. const DWORD windowThread = GetWindowThreadProcessId(window, nullptr);
  185. if (windowThread != 0 && windowThread != GetCurrentThreadId()) {
  186. // The game's presentation message still reaches hooked Present, even if we were unhooked.
  187. (void)PostMessageW(window, input::kRequiredForwardMessage, 0, 0);
  188. return;
  189. }
  190. dispatch_pending_input_release(window);
  191. }
  192. } // namespace sunrise::client::hooks::graphics::renderer