feat(render): Campaign V slice V6k commit 2 - the viewports name their own target, and section 5.4 is discharged
V4g's first half, and the V7 blocker section 5.4 named. What moved. PrivateEntityViewportRenderer - the paperdoll and creature-appraisal viewports - stops hand-rolling an FBO, a colour texture and a depth renderbuffer and asks the device for an IGpuRenderTarget. The pass it opens DECLARES that target rather than binding one behind the RHI's back, and the colour attachment is registered into the global texture table through RegisterTexture like any other texture. Render() returns the UiTextureTableHandle the retained UI already speaks instead of a raw GL name. That deletes the V4a pre-approved transitional seam. GlGpuDevice's RegisterExternalColorTexture / TryResolveExternalColorTexture existed so the UI could blit a texture whose owner the RHI knew nothing about; plan section 7.1's final paragraph gave them exactly this slice as their end, and both are gone along with the GlGpuDevice casts in RetailPaperdollFrameView and RetailCreatureAppraisalFrameView. Those two views are now backend-neutral: they decode a handle instead of registering one. Section 5.4, stated precisely, because the answer is not what the section predicts. The divergence it describes - GL's BeginPass refusing to bind framebuffer 0 for a null target - is NOT on the tree and has not been since the V4c revert at543bc79f, which took that hunk with it. GL's BeginPass binds the declared target today, so the two backends already agree about what Target: null means. What the revert did not undo was the REASON the divergence existed: this renderer bound a framebuffer no pass had declared. It now names its target, and PortalTunnelPresentation - the other renderer section 5.4 names - draws into the active viewport rather than an offscreen buffer, which is the backbuffer, which is what a null target literally means. The obligation is therefore discharged on both halves and V7's second defect is closed. PortalDepthMaskRenderer is NOT ported and is not blocking. Its two-pass punch is built on glStencilFunc/glStencilOp/glStencilMask, and GpuPipelineDescription has no stencil dimension to express them with. That is a pinned-contract question, reported rather than worked around. The section 5.5.7 re-check, which was asked for explicitly and does not come back clean. That note recorded that "the render-target-view-in-table usage from V6c did not fire" and asked that it not be carried forward as accepted. It still does not fire, and now for a reason worth writing down: a Vulkan render-target image is viewed as VK_IMAGE_VIEW_TYPE_2D because that is what an attachment needs, while the texture table's descriptor array is declared sampler2DArray, so registering one is invalid usage rather than a mismatch that samples oddly. It has never fired because the only renderer with an offscreen target is composed on GL alone. VulkanGpuDevice.RegisterTexture now refuses it loudly and names the fix - a second, layered sampled view per render target - so the slice that gives the Vulkan arm a viewport finds a precondition instead of a driver-level fault. Gates. Release build green. App tests 4,109 passed / 3 skipped, unchanged from commit 1. Strict GL offline pixel gate against22aa2edc: 4.08e-05, 23 differing pixels of 563,200, inside the documented 9-31 band, maximumChannelDelta 48. GL connected repeat gate at 3 runs: 3/3 RENDERED on the desktop witness and 3/3 on the client capture. One offline Vulkan run with VK_LAYER_KHRONOS_validation proven inserted by the loader: zero validation errors, zero warnings. And the surface the automated gates cannot see was checked rather than banked. The offline scene never opens the inventory, so the pixel gate is a tripwire for this change and nothing more - plan section 5.1's debt table has said so since V6d. A connected run that presses ToggleInventoryPanel and captures the result is in artifacts/v6k-paperdoll: the doll renders through the new render target with the correct pose, orientation and alpha, which is the row that table has been carrying since V4c. No divergence-register row: no retail-facing behaviour changes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
22aa2edc65
commit
eb7e6b4e5c
7 changed files with 173 additions and 169 deletions
|
|
@ -399,55 +399,14 @@ internal sealed class GlGpuDevice : IGpuDevice
|
|||
_renderState.Reset();
|
||||
}
|
||||
|
||||
// ── V4a pre-approved transitional seam (campaign doc §7.1, final paragraph) ──
|
||||
//
|
||||
// The paperdoll and creature-appraisal viewport textures are produced by
|
||||
// PaperdollViewportRenderer / PrivateEntityViewportRenderer, both still raw
|
||||
// GL until V4g. UiViewport (ported this slice) needs a GpuTextureSlot for
|
||||
// whatever texture they hand it so it can draw through the same seam every
|
||||
// other ported UI texture uses, without those renderers themselves porting
|
||||
// early. This registers an EXTERNALLY-OWNED GL texture name into the
|
||||
// device's texture table without taking ownership of its GL lifetime: this
|
||||
// device never deletes it, and the owning renderer keeps recreating it on
|
||||
// resize exactly as before. Idempotent by GL name so calling this every
|
||||
// frame with the same still-live texture does not churn the table.
|
||||
//
|
||||
// Deleted at V4g, when PaperdollViewportRenderer / PrivateEntityViewportRenderer
|
||||
// port onto IGpuDevice and can call RegisterTexture directly instead.
|
||||
private readonly Dictionary<uint, GpuTextureSlot> _externalColorTextureSlotsByGlName = new();
|
||||
private readonly Dictionary<uint, uint> _externalColorTextureGlNamesBySlot = new();
|
||||
|
||||
internal GpuTextureSlot RegisterExternalColorTexture(uint glTextureName)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
if (glTextureName == 0)
|
||||
return GpuTextureSlot.Unassigned;
|
||||
if (_externalColorTextureSlotsByGlName.TryGetValue(glTextureName, out GpuTextureSlot existing))
|
||||
return existing;
|
||||
|
||||
ulong handle = _bindless.GetResidentHandle(glTextureName);
|
||||
uint slotIndex = _textureSlotAllocator.Allocate();
|
||||
WriteHandle(slotIndex, handle);
|
||||
var slot = new GpuTextureSlot(slotIndex);
|
||||
_externalColorTextureSlotsByGlName[glTextureName] = slot;
|
||||
_externalColorTextureGlNamesBySlot[slotIndex] = glTextureName;
|
||||
return slot;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves a slot produced by <see cref="RegisterExternalColorTexture"/>
|
||||
/// back to its raw GL texture name, for the still-classic texture-unit
|
||||
/// binding draw path (<c>TextRenderer.DrawSprite(uint texture, ...)</c>).
|
||||
/// </summary>
|
||||
internal bool TryResolveExternalColorTexture(GpuTextureSlot slot, out uint glTextureName)
|
||||
{
|
||||
if (!slot.IsAssigned)
|
||||
{
|
||||
glTextureName = 0;
|
||||
return false;
|
||||
}
|
||||
return _externalColorTextureGlNamesBySlot.TryGetValue(slot.Index, out glTextureName);
|
||||
}
|
||||
// Campaign V slice V6k deleted the V4a pre-approved transitional seam
|
||||
// (RegisterExternalColorTexture / TryResolveExternalColorTexture, campaign
|
||||
// doc §7.1's final paragraph). It existed so the retained UI could blit the
|
||||
// paperdoll and creature-appraisal viewport textures while their renderer
|
||||
// still owned a hand-rolled FBO the RHI knew nothing about. That renderer now
|
||||
// creates an IGpuRenderTarget and registers its colour attachment through
|
||||
// RegisterTexture like anything else, so the escape hatch has no caller —
|
||||
// exactly the end §7.1 wrote for it.
|
||||
|
||||
// ── V4t transitional seam: the world texture stack's entry to this table ──
|
||||
//
|
||||
|
|
|
|||
|
|
@ -258,6 +258,26 @@ internal sealed unsafe partial class VulkanGpuDevice
|
|||
if (sampler is not VulkanGpuSampler vulkanSampler)
|
||||
throw new ArgumentException("The Vulkan backend can only register a Vulkan sampler.", nameof(sampler));
|
||||
|
||||
// Campaign V slice V6k, the §5.5.7 re-check. A render-target image is
|
||||
// viewed as VK_IMAGE_VIEW_TYPE_2D because that is what an ATTACHMENT
|
||||
// needs; the texture table's descriptor array is declared
|
||||
// sampler2DArray, so writing that view into it is invalid usage rather
|
||||
// than a mismatch that samples oddly. §5.5.7 recorded that this "did not
|
||||
// fire" and asked that it be re-checked rather than accepted, and the
|
||||
// reason it still does not fire is that the one renderer with an
|
||||
// offscreen target — PrivateEntityViewportRenderer — is composed on GL
|
||||
// only. Making the precondition loud here is what stops that from being
|
||||
// discovered as a driver-level fault the first time it is composed;
|
||||
// serving it needs a SECOND, layered view per render target, which
|
||||
// belongs to the slice that gives the Vulkan arm a viewport.
|
||||
if (VulkanTextureFormatMapping.IsRenderTarget(vulkanTexture.Format))
|
||||
{
|
||||
throw new NotSupportedException(
|
||||
$"Render target '{vulkanTexture.Name}' cannot be registered into the texture table: "
|
||||
+ "its attachment view is 2-D and the table's descriptor array is sampler2DArray. "
|
||||
+ "A layered sampled view per render target is the fix (campaign plan §5.5.7).");
|
||||
}
|
||||
|
||||
return TextureTable.Register(vulkanTexture.View, vulkanSampler.Handle);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue