Skip to content

Fix invisible cursor for games using D3D9 software cursors#132

Open
Night1099 wants to merge 4 commits intoNVIDIAGameWorks:mainfrom
Night1099:fix/software-cursor-visibility
Open

Fix invisible cursor for games using D3D9 software cursors#132
Night1099 wants to merge 4 commits intoNVIDIAGameWorks:mainfrom
Night1099:fix/software-cursor-visibility

Conversation

@Night1099
Copy link
Copy Markdown

@Night1099 Night1099 commented Mar 13, 2026

Summary:
Games that draw their cursor as a D3D9 sprite and call SetCursor(NULL) to hide the OS cursor
end up with no visible cursor under Remix — the path tracer drops the sprite, and the OS cursor
is actively hidden.

Changes:

  • Server (d3d9_cursor.cpp): ShowCursor(TRUE) no longer calls ::SetCursor(nullptr) when
    m_hCursor is NULL (game never called SetCursorProperties), allowing the OS cursor to remain visible
  • Bridge client (di_hook.cpp): Hooks SetCursor alongside existing SetCursorPos/GetCursorPos
    hooks. When SetCursor(NULL) is called while the game's D3D9 ShowCursor state is TRUE (menu/UI),
    substitutes a fallback cursor. During gameplay (ShowCursor FALSE), NULL passes through for mouse-look
  • Bridge client (d3d9_device.cpp): When the game calls SetCursorProperties with a cursor bitmap,
    creates an HCURSOR on the client side so the hook uses the game's actual cursor icon instead of a
    generic arrow

Configuration:
client.forceSoftwareCursorVisibility (default: true) — set to false to disable

Tested:

  • Mount & Blade: Warband — cursor visible in menus, hidden during gameplay
  • Stronghold 2 — cursor visible with dynamic icon updates (build, attack, select cursors)
  • Games using only SetCursorProperties (hardware cursor) unaffected
  • Toggling client.forceSoftwareCursorVisibility = false disables the behavior
  • Fullscreen vs windowed mode

Night1099 and others added 4 commits March 13, 2026 13:45
Games that use software cursors (D3D9 sprites) never call
SetCursorProperties, leaving m_hCursor as nullptr.  When the game
calls IDirect3DDevice9::ShowCursor(TRUE), the previous code passed
nullptr to ::SetCursor, actively hiding the OS cursor.

Only call ::SetCursor(m_hCursor) when a hardware cursor has been
configured.  This allows the OS cursor to remain visible for games
whose software cursor sprite is not rendered by the path tracer.
Games that draw their cursor as a D3D9 sprite call SetCursor(NULL)
to hide the OS cursor.  Under Remix the path tracer drops the sprite,
leaving no visible cursor at all.

Add a Detours hook on SetCursor in the bridge client (alongside the
existing SetCursorPos/GetCursorPos hooks).  When the game's D3D9
ShowCursor state is TRUE (menu/UI mode) and SetCursor is called with
NULL, substitute the default arrow cursor so the OS cursor stays
visible.  During gameplay (ShowCursor FALSE) the NULL is passed
through so mouse-look works normally.

Controlled by client.forceSoftwareCursorVisibility (default: true).
When the game provides a cursor bitmap via SetCursorProperties, build
an HCURSOR on the bridge client side from the surface pixel data.
The SetCursor hook uses this cursor instead of the generic arrow,
preserving the game's intended cursor appearance under Remix.
@Night1099 Night1099 marked this pull request as ready for review March 13, 2026 19:08
@AlexDunn
Copy link
Copy Markdown
Collaborator

While this WAR may be useful, I wonder why it's necessary.

Doesnt DXVK already handle this for us? If not, it should, and would potentially be more robust than adding in another hook (generally we want to support the minimal number of hooks as possible).

@Night1099
Copy link
Copy Markdown
Author

Well upstream dxvk does handle it, the bug is introduced by the bridge

when DXVK's D3D9Cursor::ShowCursor calls ::SetCursor(), it happens in the same process as the game, so it "wins" over whatever the game does.
In dxvk-remix's bridge architecture, the server-side ::SetCursor() call happens in a different process from the game. The game's own ::SetCursor(NULL) call on the client side just overwrites it.

The server-side fix in d3d9_cursor.cpp is the DXVK-level fix — it prevents ShowCursor(TRUE) from nulling the cursor when no hardware cursor exists. However, games using software cursors also call ::SetCursor(NULL) directly (bypassing D3D9), and because of the bridge architecture those calls happen in the client process where DXVK's server-side code can't intercept them. The SetCursor hook in di_hook.cpp covers that path. The existing di_hook infrastructure already hooks other cursor functions (GetCursorPos, SetCursorPos), so the SetCursor hook follows the same pattern.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants