feat(render): add integer vertex attributes and the terrain tiling binding
A scouting pass over V4d stopped before writing code and reported three gaps between terrain and the pinned contract. All three verified against source. The load-bearing one: terrain_modern.vert declares locations 2-5 as uvec4 and TerrainModernRenderer feeds them with glVertexAttribIPointer, but GpuVertexFormat had no integer format and the encoder only issued glVertexAttribPointer. GL leaves an integer shader input undefined if it arrives through the float path, and Vulkan needs the format named as R8G8B8A8_UINT rather than _UNORM, so UByte4Normalized cannot stand in for it. Those packed bytes carry terrain-type, road and split-direction codes that drive every blend decision, so normalising them would have produced garbage rather than an approximation. Adds GpuVertexFormat.UByte4UInt and an integer branch in the encoder. Also adds a uniform binding for terrain's 36-float per-layer tiling array, which at 144 bytes cannot ride in the 96-byte push-constant block or Vulkan's guaranteed 128-byte ceiling, and has no uniform-array verb to reach it otherwise. Corrects two V4d plan rows: TerrainAtlas belongs to V4t with the rest of the texture stack, and terrain has no GPU timer to port since its diagnostics use a CPU stopwatch. The uView/uProjection convergence gets its own pixel-gated sub-commit because it moves a matrix product from per-vertex GPU evaluation to a CPU multiply, and that rounding effect should be attributable on its own. Files #250: two zero-allocation tests fail about one run in three on an unchanged tree, independent of this campaign. That noise trains everyone to re-run until green, which is how a real regression gets waved through. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
cb0182a06c
commit
c7f5f251f8
7 changed files with 136 additions and 9 deletions
|
|
@ -3,7 +3,17 @@ using Silk.NET.OpenGL;
|
|||
namespace AcDream.App.Rendering.Gpu.Gl;
|
||||
|
||||
/// <summary>One vertex attribute's GL shape: component count, element type, and whether integer values normalize to [0,1]/[-1,1].</summary>
|
||||
internal readonly record struct GlVertexAttributeShape(int ComponentCount, VertexAttribPointerType Type, bool Normalized);
|
||||
/// <summary>
|
||||
/// How one vertex attribute reaches GL. <paramref name="Integer"/> selects
|
||||
/// <c>glVertexAttribIPointer</c> over <c>glVertexAttribPointer</c>: GL requires
|
||||
/// the integer entry point for an integer shader input (<c>uvec4</c> and friends)
|
||||
/// and leaves the value undefined otherwise.
|
||||
/// </summary>
|
||||
internal readonly record struct GlVertexAttributeShape(
|
||||
int ComponentCount,
|
||||
VertexAttribPointerType Type,
|
||||
bool Normalized,
|
||||
bool Integer = false);
|
||||
|
||||
/// <summary>
|
||||
/// Pure, GL-context-free mappings from the RHI's backend-neutral enums to
|
||||
|
|
@ -21,6 +31,9 @@ internal static class GlEnumMapping
|
|||
GpuVertexFormat.Float3 => new GlVertexAttributeShape(3, VertexAttribPointerType.Float, false),
|
||||
GpuVertexFormat.Float4 => new GlVertexAttributeShape(4, VertexAttribPointerType.Float, false),
|
||||
GpuVertexFormat.UByte4Normalized => new GlVertexAttributeShape(4, VertexAttribPointerType.UnsignedByte, true),
|
||||
// Integer attributes carry Integer = true; the encoder must route them
|
||||
// through glVertexAttribIPointer, not the normalized float path.
|
||||
GpuVertexFormat.UByte4UInt => new GlVertexAttributeShape(4, VertexAttribPointerType.UnsignedByte, false, Integer: true),
|
||||
_ => throw new NotSupportedException($"No GL vertex attribute shape for {format}."),
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -110,13 +110,27 @@ internal sealed class GlGpuPassEncoder : IGpuPassEncoder
|
|||
{
|
||||
GlVertexAttributeShape shape = GlEnumMapping.VertexShapeOf(attribute.Format);
|
||||
nint attributeOffset = (nint)(offsetBytes + attribute.OffsetBytes);
|
||||
_gl.VertexAttribPointer(
|
||||
attribute.Location,
|
||||
shape.ComponentCount,
|
||||
shape.Type,
|
||||
shape.Normalized,
|
||||
layout.StrideBytes,
|
||||
(void*)attributeOffset);
|
||||
if (shape.Integer)
|
||||
{
|
||||
// An integer shader input (uvec4) must come through the I-form.
|
||||
// Supplying it via glVertexAttribPointer leaves the value undefined.
|
||||
_gl.VertexAttribIPointer(
|
||||
attribute.Location,
|
||||
shape.ComponentCount,
|
||||
(VertexAttribIType)shape.Type,
|
||||
layout.StrideBytes,
|
||||
(void*)attributeOffset);
|
||||
}
|
||||
else
|
||||
{
|
||||
_gl.VertexAttribPointer(
|
||||
attribute.Location,
|
||||
shape.ComponentCount,
|
||||
shape.Type,
|
||||
shape.Normalized,
|
||||
layout.StrideBytes,
|
||||
(void*)attributeOffset);
|
||||
}
|
||||
}
|
||||
GLHelpers.ThrowOnResourceError(_gl, $"bind vertex buffer '{buffer.Name}'");
|
||||
_gl.BindBuffer(GLEnum.ArrayBuffer, 0);
|
||||
|
|
|
|||
|
|
@ -78,6 +78,17 @@ internal static class GpuBindingModel
|
|||
/// </summary>
|
||||
public const uint UniformSceneLighting = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Terrain per-layer texture tiling factors — 36 floats.
|
||||
///
|
||||
/// Added at slice V4d. These live in a <c>uniform float[36]</c> today, which
|
||||
/// is 144 bytes: too large for the 96-byte push-constant block (and for
|
||||
/// Vulkan's guaranteed 128-byte ceiling), and there is no RHI verb for setting
|
||||
/// a uniform array. A small uniform buffer is the Vulkan-legal home. Binding 2
|
||||
/// is taken by the terrain clip block, so this is 3.
|
||||
/// </summary>
|
||||
public const uint UniformTerrainTiling = 3;
|
||||
|
||||
/// <summary>Set index carrying every uniform buffer.</summary>
|
||||
public const uint UniformSet = 1;
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,25 @@ internal enum GpuVertexFormat
|
|||
Float2,
|
||||
Float3,
|
||||
Float4,
|
||||
|
||||
/// <summary>Four unsigned bytes scaled to [0,1] floats — a shader <c>vec4</c> input.</summary>
|
||||
UByte4Normalized,
|
||||
|
||||
/// <summary>
|
||||
/// Four unsigned bytes delivered as INTEGERS — a shader <c>uvec4</c> input.
|
||||
///
|
||||
/// Distinct from <see cref="UByte4Normalized"/> in kind, not just in scaling:
|
||||
/// GL requires <c>glVertexAttribIPointer</c> for an integer shader input and
|
||||
/// leaves the value undefined if it arrives through the float path, and Vulkan
|
||||
/// needs the format named as <c>R8G8B8A8_UINT</c> rather than <c>_UNORM</c>.
|
||||
///
|
||||
/// Added at slice V4d, which found `terrain_modern.vert` declares locations 2–5
|
||||
/// as <c>uvec4</c> and feeds them with <c>glVertexAttribIPointer</c>. Those
|
||||
/// packed bytes carry terrain-type, road and split-direction codes that drive
|
||||
/// every blend decision, so normalising them would not be an approximation —
|
||||
/// it would be garbage.
|
||||
/// </summary>
|
||||
UByte4UInt,
|
||||
}
|
||||
|
||||
/// <summary>One vertex attribute, matching a <c>layout(location = N) in</c> declaration.</summary>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue