fix(render): Campaign V slice V6j commit 1 - the Vulkan winding needs no inversion
VulkanViewportMapping has inverted the front face since V6c, on the standard argument that rendering with a negative viewport height mirrors framebuffer space and therefore reverses triangle orientation. The world arm is the first consumer that culls anything, and it falsified the inversion twice over on one frame. Nothing exercised it before now. Every Vulkan consumer through V6i - TextRenderer, DebugLineRenderer and the bring-up scene - declares Cull = GpuCullMode.None, so the mapping had never decided a single fragment. That is why a wrong answer survived four slices and a validation-clean run: an unexercised path. What the world arm measured, on the same offline scene the GL pixel gate captures. Terrain is the one single-sided surface acdream draws - FrontFace(Ccw) plus Cull(Back), matching ACRender::landPolysDraw's per-triangle eye-side predicate - and under the inversion it vanished completely, 190 multi-draw commands issuing against 625 loaded landblocks with nothing on screen. Every closed building shell rendered inside-out in the same frame: the front wall culled and the interior beams visible through the gap, which is what a back-face-front cull looks like on geometry that is only nearly convex. Declaring the GL winding verbatim restores both at once - terrain draws single-sided from above, and the shells close. Two independent surfaces, one change, and the correction is the identity mapping. Recorded here rather than worked around in the renderers, because a renderer that compensates for its backend is exactly the shape this file exists to prevent: the contract says renderers speak GL and the backend translates, and the backend was translating wrongly. The viewport flip itself is untouched and still correct - it is what puts GL-authored geometry the right way up with no shader or matrix change. What goes is the claim that a winding inversion has to travel with it. The scissor's explicit flip is a separate correction with a separate justification and is likewise untouched. The test suite says so now rather than describing the old behaviour: the pass-through is asserted directly, and the exact-inverses test becomes a travels-alone test, so a later change that reintroduces the inversion fails here first and on any single-sided surface second. Gates. Release build green. App tests 4,112 passed / 3 skipped, the unchanged baseline. GL offline pixel gate unaffected by construction - this file has no GL arm - and measured with the world arm in commit 2. No divergence-register row: this corrects a backend translation error rather than introducing a deviation from retail. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
847f14aeef
commit
81fe5e1b63
2 changed files with 52 additions and 19 deletions
|
|
@ -88,25 +88,39 @@ public sealed class VulkanViewportMappingTests
|
|||
Assert.Equal(20u, scissor.Extent.Height);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Campaign V slice V6j: the winding a renderer declares is the winding
|
||||
/// Vulkan gets. V6c inverted it on the standard negative-viewport argument;
|
||||
/// the world arm — the mapping's first culling consumer — measured that the
|
||||
/// inversion culls terrain outright and turns every closed building shell
|
||||
/// inside-out. See <c>VulkanViewportMapping</c>'s remarks for the captures.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void FrontFaceIsInvertedBecauseTheViewportMirrorsFramebufferSpace()
|
||||
public void FrontFacePassesThroughSoRenderersDeclareTheGlWinding()
|
||||
{
|
||||
Assert.Equal(FrontFace.Clockwise, VulkanViewportMapping.ToVulkan(GpuFrontFace.CounterClockwise));
|
||||
Assert.Equal(FrontFace.CounterClockwise, VulkanViewportMapping.ToVulkan(GpuFrontFace.Clockwise));
|
||||
Assert.Equal(
|
||||
FrontFace.CounterClockwise,
|
||||
VulkanViewportMapping.ToVulkan(GpuFrontFace.CounterClockwise));
|
||||
Assert.Equal(
|
||||
FrontFace.Clockwise,
|
||||
VulkanViewportMapping.ToVulkan(GpuFrontFace.Clockwise));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TheFlipAndTheWindingInversionAreExactInverses()
|
||||
public void TheViewportFlipTravelsAloneAndTheWindingIsUntouched()
|
||||
{
|
||||
// Mirroring twice is identity, and inverting the winding twice is too.
|
||||
// If a later change ever flipped one without the other, this is the
|
||||
// shape of the assertion that catches it.
|
||||
// The viewport still mirrors — that is what puts GL-authored geometry
|
||||
// the right way up with no matrix change. What it does NOT do is drag a
|
||||
// winding inversion along with it. A later change that reintroduces one
|
||||
// fails here and, more usefully, fails visibly on any single-sided
|
||||
// surface.
|
||||
Viewport once = VulkanViewportMapping.ToVulkan(0, 0, 640, 480, attachmentHeight: 480);
|
||||
Assert.Equal(-480f, once.Height);
|
||||
Assert.Equal(480f, once.Y);
|
||||
|
||||
FrontFace inverted = VulkanViewportMapping.ToVulkan(GpuFrontFace.CounterClockwise);
|
||||
Assert.NotEqual(FrontFace.CounterClockwise, inverted);
|
||||
Assert.Equal(
|
||||
FrontFace.CounterClockwise,
|
||||
VulkanViewportMapping.ToVulkan(GpuFrontFace.CounterClockwise));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue