feat(render): draw the retained UI on Vulkan, and fix the pass it exposed
Campaign V slice V6d, commit 3 of 3 — the evidence commit, which turned out to also be a bug-fix commit.
VulkanBringUpHost now builds a real UiHost and DebugLineRenderer on the Vulkan device and draws them after the V6c verification scene, in their own single-sampled load/store passes against the backbuffer — the same shape the GL client's HUD phase has. Nothing in the retained stack is backend-aware: UiRoot walks a real widget tree, each widget draws through UiRenderContext, and UiHost.Draw brackets it with TextRenderer.Begin/Flush. What it cannot be is the game's own UI, because the retail tree is built from LayoutDesc and DAT chrome by TextureCache, which stays a GL type until V4t; the sprites here are generated instead. The widget rectangles are authored at known pixel offsets from the top-left and nothing is mirror-symmetric, so a wrong Y flip would put the title bar at the bottom.
The frame this produced was wrong, and usefully so. Whole runs of the debug-line figure were missing. Vulkan's rasterization-order guarantees are scoped to one render-pass instance; between two instances writing the same attachment there is no implicit ordering, and that includes a multisample RESOLVE, which is part of the render pass and therefore equally unordered against what follows. TransitionBackbufferForRendering emitted its acquire barrier once per frame and returned for every pass after the first, so the second and third passes raced the first one's resolve. V6c's frame had exactly one backbuffer pass and could not see this; V6d's has three. A later backbuffer pass now gets a colour-attachment dependency instead of nothing, and keeps ColorAttachmentOptimal as its old layout rather than Undefined, which would have licensed discarding everything drawn so far. Every line renders continuously afterwards.
Inspection of artifacts/vk-ui/vulkan-bringup.png against the authored layout, by pixel probe:
The header panel is authored at (24,18), 420x96. Its tiled chrome fills exactly x 24..443 and y 18..113 — one pixel outside on any edge is the clear colour. The tile's lit edge appears at the top and left of every cell, so texture row 0 lands at the top and the V axis is not flipped. Both labels read left to right, right side up, through the font-coverage branch. The nested panel's border samples exactly (153,191,255) against an authored (0.6,0.75,1.0), unblended — the untextured branch is bit-exact. The badge sprite is authored at (460,58), 64x64, and its gradient starts at x=460 with the clear colour at 455 — the RGBA-modulate branch, sampling a table slot. The two debug-line segments land on their computed screen coordinates. All three fragment branches, the pixel-to-NDC mapping, the top-left origin, straight-alpha blending and table sampling are therefore all confirmed on Vulkan, which is everything the offline GL gate confirms about the same code on GL.
The plan's V6 milestone is amended rather than claimed: "full game frame on Vulkan" is not reachable while V4c/V4d are parked and the world renderers and TextureCache are still raw GL, so V6 delivers the backend plus the two renderers that can use it today. The accumulated user-gate table gains a V6d row for the paperdoll/appraisal viewport sprite — the one retained-UI texture the offline scene never draws, on a slice that changed how every UI texture is sampled.
App tests 4,057 passed / 3 skipped, unchanged. Offline pixel gate against f6f58a12: differing fraction 3.20e-05, 18 pixels of 563,200, inside the documented 15-23 pixel noise band — as expected, since this commit touches only Vulkan files and the campaign doc.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
f6f58a12db
commit
95f8c25f31
4 changed files with 323 additions and 12 deletions
|
|
@ -518,20 +518,49 @@ internal sealed unsafe partial class VulkanGpuDevice
|
|||
_openPass = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prepares the acquired swapchain image for a backbuffer pass.
|
||||
///
|
||||
/// <para>The FIRST pass of a frame acquires it: undefined contents, no prior
|
||||
/// access to wait on, layout moved to colour-attachment.</para>
|
||||
///
|
||||
/// <para>Every pass AFTER that needs a dependency instead, and slice V6d is
|
||||
/// where that started to matter. Vulkan's rasterization-order guarantees are
|
||||
/// scoped to one render-pass instance; between two instances writing the same
|
||||
/// attachment there is no implicit ordering at all, so the second one's draws
|
||||
/// can land before or interleaved with the first one's colour writes — and
|
||||
/// with the first one's multisample RESOLVE, which is part of the render pass
|
||||
/// and therefore also unordered against what follows. V6c's frame had exactly
|
||||
/// one backbuffer pass and could not see this. V6d's has three (world scene,
|
||||
/// debug lines, retained UI), and the symptom was unmistakable once looked
|
||||
/// at: whole runs of the debug-line figure missing where the earlier pass's
|
||||
/// resolve had overwritten them, while the last pass's output survived
|
||||
/// intact.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
private void TransitionBackbufferForRendering(CommandBuffer commands, Image image)
|
||||
{
|
||||
if (_backbufferRenderingReady)
|
||||
return;
|
||||
bool first = !_backbufferRenderingReady;
|
||||
_backbufferRenderingReady = true;
|
||||
|
||||
var barrier = new ImageMemoryBarrier2
|
||||
{
|
||||
SType = StructureType.ImageMemoryBarrier2,
|
||||
SrcStageMask = PipelineStageFlags2.TopOfPipeBit,
|
||||
SrcAccessMask = AccessFlags2.None,
|
||||
SrcStageMask = first
|
||||
? PipelineStageFlags2.TopOfPipeBit
|
||||
: PipelineStageFlags2.ColorAttachmentOutputBit,
|
||||
SrcAccessMask = first
|
||||
? AccessFlags2.None
|
||||
: AccessFlags2.ColorAttachmentWriteBit,
|
||||
DstStageMask = PipelineStageFlags2.ColorAttachmentOutputBit,
|
||||
DstAccessMask = AccessFlags2.ColorAttachmentWriteBit,
|
||||
OldLayout = ImageLayout.Undefined,
|
||||
DstAccessMask = first
|
||||
? AccessFlags2.ColorAttachmentWriteBit
|
||||
: AccessFlags2.ColorAttachmentWriteBit | AccessFlags2.ColorAttachmentReadBit,
|
||||
// Undefined for the acquire — the contents are genuinely undefined
|
||||
// and saying so lets the driver skip a decompress. A later pass in
|
||||
// the same frame must NOT say Undefined: that would license
|
||||
// discarding everything drawn so far.
|
||||
OldLayout = first ? ImageLayout.Undefined : ImageLayout.ColorAttachmentOptimal,
|
||||
NewLayout = ImageLayout.ColorAttachmentOptimal,
|
||||
SrcQueueFamilyIndex = Silk.NET.Vulkan.Vk.QueueFamilyIgnored,
|
||||
DstQueueFamilyIndex = Silk.NET.Vulkan.Vk.QueueFamilyIgnored,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue