Skip to content

app,internal/d3d11: present the D3D11 swapchain with the flip model - #174

Open
dkrisman wants to merge 1 commit into
gioui:mainfrom
dkrisman:fix/d3d11-flip-model
Open

app,internal/d3d11: present the D3D11 swapchain with the flip model#174
dkrisman wants to merge 1 commit into
gioui:mainfrom
dkrisman:fix/d3d11-flip-model

Conversation

@dkrisman

@dkrisman dkrisman commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

The D3D11 backend still presents with the legacy blt model
(DXGI_SWAP_EFFECT_DISCARD, BufferCount 1). On recent drivers this can render
nothing at all: on 2025+ Intel Arc (Lunar Lake 140V) the window stays blank
while frames are drawn. The flip model has been Microsoft's recommended
presentation path since Windows 8.1, and DXGI_SWAP_EFFECT_FLIP_DISCARD
(Windows 10+) is the variant DXGI documentation steers new code toward.

Changes:

  • CreateSwapChain uses DXGI_SWAP_EFFECT_FLIP_DISCARD with BufferCount 2
    (the flip model requires at least two buffers).
  • The flip model rejects sRGB backbuffer formats, so the backbuffer becomes
    DXGI_FORMAT_R8G8B8A8_UNORM and the render target view is created over it
    with DXGI_FORMAT_R8G8B8A8_UNORM_SRGB via a new
    CreateRenderTargetViewDesc binding (flattened RENDER_TARGET_VIEW_DESC).
    Linear-to-sRGB conversion on write is preserved, so gamma behavior is
    unchanged.
  • The resize path already calls ResizeBuffers with DXGI_FORMAT_UNKNOWN,
    which preserves the non-sRGB buffer format.

Compatibility: DXGI_SWAP_EFFECT_FLIP_DISCARD requires Windows 10. If Gio
still targets Windows 8.x with D3D11, DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL
(Windows 8+) is a drop-in alternative; happy to switch or add a fallback if
that floor matters.

Verified on Windows 10 (LTSC 2021, Intel and NVIDIA adapters) and on the
Intel Arc / Lunar Lake 140V machine that motivated the change: the
previously blank window renders correctly, resize/minimize/restore behave,
and colors are unchanged against the blt-model build on hardware where both
work.

@eliasnaur eliasnaur left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, a few comments below.

Please note in the commit description that this change breaks compatibility with Windows versions < 10. I think that's ok given that recent Go versions also require Windows 10.

Comment thread internal/d3d11/d3d11_windows.go Outdated
Comment on lines +1656 to +1696
BufferCount: 1,
BufferCount: 2,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure changing BufferCount to 2 won't fix DXGI_SWAP_EFFECT_DISCARD? The documentation says

DXGI_SWAP_EFFECT_DISCARD ... This flag is valid for a swap chain with more than one back buffer

If so, please submit this change as a separate bugfix commit, with the switch to DXGI_SWAP_EFFECT_FLIP_DISCARD on top.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested this on the affected hardware (Arc 140V): DXGI_SWAP_EFFECT_DISCARD with BufferCount 2 still presents nothing - the window stays blank exactly as with BufferCount 1, while FLIP_DISCARD renders correctly in the same harness (minimal solid-fill window, automated pixel capture, repeated runs). Buffer count alone is not a fix on this hardware, so the change stays a single commit, with the negative result noted in the commit message.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright. Perhaps this is how you tested it, but: does rendering work on your setup if you keep everything else (the sRGB changes etc.) and only keep DXGI_SWAP_EFFECT_DISCARD?

Comment thread internal/d3d11/d3d11_windows.go Outdated
// CreateRenderTargetViewDesc is CreateRenderTargetView with an explicit
// view description, which is required to view a backbuffer through a
// different (for example sRGB) format than it was created with.
func (d *Device) CreateRenderTargetViewDesc(res *Resource, desc *RENDER_TARGET_VIEW_DESC) (*RenderTargetView, error) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't introduce a new function. Just change the existing CreateRenderTargetView to accept a descriptor. Pass the descriptor by value, not pointer.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CreateRenderTargetViewDesc is gone; CreateRenderTargetView takes RENDER_TARGET_VIEW_DESC by value. The texture render-target path passes its texture format explicitly, which resolves to the same view as the previous NULL descriptor (resource format, mip 0). gio's test suite passes, including gpu/headless and gpu/internal/rendertest on the D3D11 backend.

OutputWindow: hwnd,
Windowed: 1,
SwapEffect: DXGI_SWAP_EFFECT_DISCARD,
SwapEffect: DXGI_SWAP_EFFECT_FLIP_DISCARD,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I vaguely remember DXGI_SWAP_EFFECT_FLIP_DISCARD not working great with window resizes. Do you see any artifacts during resizing, or is the content perfectly synchronized (that is, not stretching, padding etc.)?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No stretching in automated probes on the Arc 140V (60 programmatic size changes plus an interactive corner drag through the modal resize loop, about 100 rect-verified captures - content covered the client area in every one). Resizing by hand, though, a transient white band (a frame or two) can appear along the shrinking edge - bottom when shrinking vertically, right when shrinking horizontally - but only within the first ~5 seconds of resizing after application start; after that it never reproduces. IDXGIDevice1::SetMaximumFrameLatency(1) makes no difference, so it does not look like present-queue depth. I cannot compare against DISCARD on this hardware because the blt path presents nothing at all there. I can dig into the startup-window artifact here or as a follow-up, whichever you prefer.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Obviously, no rendering output is worse than flickering during resize. However, resize flicker is such a jarring artifact that I want to push back against switching to DXGI_SWAP_EFFECT_FLIP_DISCARD without adressing resizing.

I see two ways:

  • Switching to DXGI_SWAP_EFFECT_FLIP_DISCARD and ensuring synchronization with window resize repainting. This is the better way for performance, future compatibility, but probably also the most intrusive. Gemini suggests some combination of DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT, SetMaximumFrameLatency(1) and a ID3D11Query (inserted after Present and then waited on).
  • Fixing DXGI_SWAP_EFFECT_DISCARD on your setup. It surprises me that your graphics driver completely breaks DXGI_SWAP_EFFECT_DISCARD, because surely there must be a bunch of legacy programs that uses DXGI_SWAP_EFFECT_DISCARD that would also break. For example, does a minimal D3D11 example also break on your setup? If not, perhaps a fix can be bisected.

The swapchain was created with DXGI_SWAP_EFFECT_DISCARD, the legacy blt
presentation model. On an Intel Arc 140V (Lunar Lake) a blt-model
present puts nothing on screen: the window stays blank while the
renderer draws every frame. Raising BufferCount alone does not help -
DISCARD with BufferCount 2 still presents nothing on that hardware -
but switching to the flip model does.

Use DXGI_SWAP_EFFECT_FLIP_DISCARD, the presentation model Microsoft
recommends for new applications. Flip imposes two constraints: the
backbuffer format may not be sRGB, and BufferCount must be at least 2.
The format constraint would drop the automatic linear-to-sRGB
conversion on write, so the backbuffer is now created as
DXGI_FORMAT_R8G8B8A8_UNORM and viewed through an explicit
DXGI_FORMAT_R8G8B8A8_UNORM_SRGB render target view, preserving the
previous gamma behavior exactly.

Creating that view needs a D3D11_RENDER_TARGET_VIEW_DESC, so
CreateRenderTargetView now takes a RENDER_TARGET_VIEW_DESC by value and
the texture render target path supplies its texture format explicitly,
which resolves to the same view as before.

DXGI_SWAP_EFFECT_FLIP_DISCARD requires Windows 10, so this drops
support for Windows 8.x and earlier, consistent with recent Go
releases, which also require Windows 10.

Signed-off-by: Devon Krisman <gio@krisman.dev>
@dkrisman
dkrisman force-pushed the fix/d3d11-flip-model branch from bfb1530 to 98a6336 Compare August 5, 2026 05:02
@dkrisman

dkrisman commented Aug 5, 2026

Copy link
Copy Markdown
Contributor Author

The commit message now notes the Windows 10 requirement: FLIP_DISCARD is Windows 10+, so this drops Windows 8.x, in line with recent Go releases. The update also reworks the RTV binding and records the BufferCount test result, per the comments below.

@eliasnaur

Copy link
Copy Markdown
Contributor

https://raphlinus.github.io/rust/gui/2019/06/21/smooth-resize-test.html talks about the problem more. It seems the problem is thorny on Windows (we had a similar resizing issue on macOS until someone figured out the magic recipe for synchronization).

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