Skip to content

Commit 72cb324

Browse files
committed
gfx/d3d10,d3d11,d3d12: Restore sprite-pipeline state before gfx_widgets_frame
When an onscreen overlay was active, gfx_widgets (the fast-forward indicator, FPS counter, achievement popups, load-progress bars, etc.) silently rendered nothing on d3d10/d3d11/d3d12. Same symptom across all three backends, same root cause: d3dN_render_overlay clobbers render state that gfx_widgets_frame relies on, and the state isn't restored before the widget draw. Specifically, the overlay render path: - Binds overlays.vbo (d3d10/d3d11) / overlays.vbo_view (d3d12) as the IA-stage vertex buffer. - Sets frame.viewport / frame.scissorRect when the overlay is not fullscreen (i.e. clamped to the game viewport, not the full screen). - On d3d12, also leaves sprites.pipe_blend bound — fine for SDR, but the OSD-msg block immediately below selects pipe_blend_hdr in HDR modes, and widgets need to follow the same selection. gfx_display_d3dN_draw (the entry point widgets use) writes its sprite vertices into sprites.vbo via Map(), then issues a Draw() that reads from whatever vbo is currently bound. After an overlay, that's overlays.vbo — so the GPU pulls vertices from the wrong buffer at the wrong stride and the result is invisible geometry, even though the widget state machine has run normally and the sprite buffer contents are correct. Widgets without an overlay work because nothing has clobbered the binding the menu path or the post-frame setup left in place. Restore the sprite-pipeline state at the top of each widget block, mirroring the OSD-msg block that follows the widget block in each of these drivers (which already does this and works for the same reason): - d3d10: viewport, blend state, sprites.vbo. - d3d11: viewport, blend state, sprites.vbo (the existing viewport-only restore was only a partial fix). - d3d12: PSO (HDR-aware), viewport, scissor rect, sprites.vbo_view. Vulkan and gl1 don't hit this because their overlay render paths either don't share input bindings with widgets (separate command buffers / fixed-function state) or they happen to set up widget state from scratch on every frame. Reported by users of the Steam Deck / Windows community on builds where overlay + widgets are both enabled. Companion to 7d6477b which fixed the analogous (but different-mechanism — widgets weren't being rendered at all) bug on d3d8.
1 parent c82db9f commit 72cb324

3 files changed

Lines changed: 71 additions & 0 deletions

File tree

gfx/drivers/d3d10.c

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3343,7 +3343,32 @@ static bool d3d10_gfx_frame(
33433343

33443344
#ifdef HAVE_GFX_WIDGETS
33453345
if (widgets_active)
3346+
{
3347+
/* d3d10_render_overlay binds d3d10->overlays.vbo as the
3348+
* input vertex buffer and may set a non-screen viewport
3349+
* (d3d10->frame.viewport when the overlay isn't
3350+
* fullscreen). gfx_display_d3d10_draw writes its sprite
3351+
* vertices into d3d10->sprites.vbo via Map() but reads
3352+
* back from whatever buffer is currently bound, so without
3353+
* restoring the binding here the widget draws fetch the
3354+
* wrong vertex stream and silently produce nothing on
3355+
* screen — the widget state machine still runs, the
3356+
* sprites still get written, but the Draw() reads from
3357+
* overlays.vbo instead. Mirror the OSD-msg block below:
3358+
* screen viewport, sprite vbo, sprite blend state. */
3359+
UINT offset = 0;
3360+
UINT stride = sizeof(d3d10_sprite_t);
3361+
d3d10->device->lpVtbl->RSSetViewports(d3d10->device,
3362+
1, &d3d10->viewport);
3363+
d3d10->device->lpVtbl->OMSetBlendState(d3d10->device,
3364+
d3d10->blend_enable, NULL,
3365+
D3D10_DEFAULT_SAMPLE_MASK);
3366+
d3d10->device->lpVtbl->IASetVertexBuffers(
3367+
d3d10->device, 0, 1,
3368+
(D3D10Buffer* const)&d3d10->sprites.vbo,
3369+
&stride, &offset);
33463370
gfx_widgets_frame(video_info);
3371+
}
33473372
#endif
33483373

33493374
if (msg && *msg)

gfx/drivers/d3d11.c

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4746,7 +4746,26 @@ static bool d3d11_gfx_frame(
47464746
#ifdef HAVE_GFX_WIDGETS
47474747
if (widgets_active)
47484748
{
4749+
/* d3d11_render_overlay binds d3d11->overlays.vbo as the
4750+
* input vertex buffer and may set a non-screen viewport
4751+
* (d3d11->frame.viewport when the overlay isn't
4752+
* fullscreen). gfx_display_d3d11_draw writes its sprite
4753+
* vertices into d3d11->sprites.vbo via Map() but reads
4754+
* back from whatever buffer is currently bound, so without
4755+
* restoring the binding here the widget draws fetch the
4756+
* wrong vertex stream and silently produce nothing on
4757+
* screen — the widget state machine still runs, the
4758+
* sprites still get written, but the Draw() reads from
4759+
* overlays.vbo instead. Mirror the OSD-msg block below:
4760+
* screen viewport, sprite vbo, sprite blend state. */
4761+
UINT stride = sizeof(d3d11_sprite_t);
4762+
UINT offset = 0;
47494763
context->lpVtbl->RSSetViewports(context, 1, &d3d11->viewport);
4764+
d3d11->context->lpVtbl->OMSetBlendState(d3d11->context,
4765+
d3d11->blend_enable,
4766+
NULL, D3D11_DEFAULT_SAMPLE_MASK);
4767+
context->lpVtbl->IASetVertexBuffers(
4768+
context, 0, 1, &d3d11->sprites.vbo, &stride, &offset);
47504769
gfx_widgets_frame(video_info);
47514770
}
47524771
#endif

gfx/drivers/d3d12.c

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5796,7 +5796,34 @@ static bool d3d12_gfx_frame(
57965796

57975797
#ifdef HAVE_GFX_WIDGETS
57985798
if (widgets_active)
5799+
{
5800+
/* d3d12_render_overlay binds d3d12->overlays.vbo_view as
5801+
* the input vertex buffer and may set frame.viewport /
5802+
* frame.scissorRect (when the overlay isn't fullscreen).
5803+
* Restore the same sprite-pipeline state the OSD-msg
5804+
* block below uses, so widget draws land against the
5805+
* sprite vbo with a screen-space viewport. Without this
5806+
* the widget state machine still runs and writes to
5807+
* sprites.vbo, but the GPU draws read from
5808+
* overlays.vbo_view → silent no-op on screen, which is
5809+
* the symptom users see when an overlay is active.
5810+
*
5811+
* The PSO has to be reset too even though
5812+
* d3d12_render_overlay also uses sprites.pipe_blend,
5813+
* because in HDR mode the OSD block selects
5814+
* pipe_blend_hdr and we want to match. */
5815+
#ifdef HAVE_DXGI_HDR
5816+
if ( (d3d12->chain.current_rt_format == DXGI_FORMAT_R10G10B10A2_UNORM)
5817+
|| (d3d12->chain.current_rt_format == DXGI_FORMAT_R16G16B16A16_FLOAT))
5818+
cmd->lpVtbl->SetPipelineState(cmd, d3d12->sprites.pipe_blend_hdr);
5819+
else
5820+
#endif
5821+
cmd->lpVtbl->SetPipelineState(cmd, d3d12->sprites.pipe_blend);
5822+
cmd->lpVtbl->RSSetViewports(cmd, 1, &d3d12->chain.viewport);
5823+
cmd->lpVtbl->RSSetScissorRects(cmd, 1, &d3d12->chain.scissorRect);
5824+
cmd->lpVtbl->IASetVertexBuffers(cmd, 0, 1, &d3d12->sprites.vbo_view);
57995825
gfx_widgets_frame(video_info);
5826+
}
58005827
#endif
58015828

58025829
if (msg && *msg)

0 commit comments

Comments
 (0)