Skip to content

Align rasterizer state, readback paths, cull mode, triangle winding order across backends#1127

Open
EmilioLaiso wants to merge 4 commits intollvm:mainfrom
Traverse-Research:front-face-ccw
Open

Align rasterizer state, readback paths, cull mode, triangle winding order across backends#1127
EmilioLaiso wants to merge 4 commits intollvm:mainfrom
Traverse-Research:front-face-ccw

Conversation

@EmilioLaiso
Copy link
Copy Markdown
Contributor

@EmilioLaiso EmilioLaiso commented Apr 28, 2026

Summary

Make the three backends use the same image coordinate system, same culling mode and same SV_IsFrontFace polarity.

The framework had the following setup:

  1. Front-face polarity differed.
    DX/MTL Y-flip clip→framebuffer at the viewport; Vulkan with positive-height viewport doesn't.
    Same HLSL triangle ended up with opposite framebuffer winding on VK vs DX/MTL, so SV_IsFrontFace gave opposite answers for the same input. This surfaced while writing a graphics-stage system-values test that needed SV_IsFrontFace to agree across backends.

  2. Visual PNG output was aligned by two compensating flips.
    DX and MTL each had a row-reversal on readback documented as "render target origin is top-left, while our image writer expects bottom-left".
    The PNG writer reversed rows again on the way out.
    VK had neither flip, but VK's no-Y-flip viewport meant its framebuffer was already "upside down" relative to DX/MTL, so its single PNG flip happened to produce a visually identical image. Two-flips-cancel on DX/MTL, no-flip-needed on VK, by accident.
    Adding any new behavior on top of this (e.g., the negative-height-viewport change in the earlier stage of this PR that kicked off the investigation) immediately broke the cancellation and produced visually-flipped outputs.

PR

Pick HLSL/DX semantics as canonical:

  • Clip-space Y up
  • Framebuffer Y down
  • CCW = front

Untangled the asymmetry by removing the readback flips on DX/MTL and the PNG writer's row reversal together with the VK viewport flip, so all three places change as one atomic step. After that, every backend produces the same Y-down memory layout end-to-end.

Exposed shared copyFromTexture function that all backends can call.
If #1113 goes in main, which introduces Map/Unmap as functions on the Buffer type, the readback path can be generalized further.

Golden Images

All graphics golden images still work as expected. The resulting Y-order is maintained.
The Mandelbrot.test image required updating as it's a compute path. Here's the PR: llvm/offload-golden-images#6

@manon-traverse
Copy link
Copy Markdown
Contributor

I'm fine with either winding mode, but this feels like something we should add to the documentation.
In the future I can imagine we can specify this in the test, but we can then use what we pick now as a default.

@EmilioLaiso EmilioLaiso marked this pull request as draft April 28, 2026 14:08
@EmilioLaiso EmilioLaiso changed the title Align cull mode and triangle winding order between backends Align rasterizer state, readback paths, cull mode, triangle winding order across backends Apr 30, 2026
@EmilioLaiso EmilioLaiso marked this pull request as ready for review April 30, 2026 08:42
@manon-traverse
Copy link
Copy Markdown
Contributor

The CI is failing on a Mandelbrot test. @EmilioLaiso Could you check if it uses a golden image and if that is still correct? Perhaps there is still a bug in the texture readbacks?

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.

3 participants