feat(render): put the retained UI and debug lines on both backends
Campaign V slice V6d, commit 2 of 3. TextRenderer and DebugLineRenderer were the only two renderers speaking the RHI, and both refused any device that was not a GlGpuDevice. They now refuse nothing: this is the first production rendering acdream can do on Vulkan.
Three things had to go.
The loose uniforms. debug_line declared uView and uProjection separately and DebugLineRenderer set them straight against the compiled GL program, because the pinned push-constant block carries one combined matrix and IGpuPassEncoder has no verb for arbitrary named uniforms. That was never portable — Vulkan has no default uniform block at all — so the shader converged on uViewProjection and Flush multiplies on the CPU. System.Numerics is row-vector convention while GLSL reads the floats column-major, which transposes, so the CPU equivalent of the old per-vertex uProjection * uView is view * projection. The product now rounds once per frame rather than once per vertex; these lines only draw when collision wireframes are switched on, so the offline gate sees nothing of it. ui_text's uScreenSize became the block's two spare scalars, uParamA and uParamB, with the same two divisions and the same NDC mapping around them.
The sampling mode. uUseTexture selected between font coverage, RGBA modulate and flat colour, and no field of the 96-byte block means that. It did not need one: which of the two texture-table slots is assigned IS the mode. uTextureIndexB assigned means a single-channel coverage source, uTextureIndexA assigned means an RGBA colour source, neither assigned means the vertex colour alone. GpuTextureSlot.Unassigned is already a loud sentinel for exactly this kind of question, and both branches guard so it never reaches a sampler. That also retired the 1x1 white fill texture: DrawFill routed solid quads through the sprite bucket relying on white times colour, and the untextured branch produces the same value with no texture at all. Multiplying by 1.0 changes no bits, and the gate agrees.
The texture binding. The classic glActiveTexture/glBindTexture path survived V4a because DrawSprite takes an arbitrary texture from sixty-odd widget call sites. But TextureCache had already registered every one of those into the device's table — the classic path was consuming the raw GL name that registration also produced. The UI's currency is now UiTextureTableHandle, a one-based table index whose zero is the same "no texture" every widget already guards on; a raw slot index would have turned all of those guards into silent false negatives, since slot 0 is perfectly valid. One-based rather than the slot itself because GpuTextureSlot is internal to the pinned contract while UiRenderContext.DrawSprite, TextureCache.GetOrUploadRenderSurface and a dozen widget properties are public, and neither publishing a contract type nor converting the retained UI to internal belongs in this slice.
Two consequences worth stating. The two backends disagree about what a 2-D table entry is — GL reconstructs a sampler2D from the bindless handle, Vulkan reads layer 0 of its sampler2DArray descriptor array — and ACDREAM_SAMPLE_2D is the one place that lives. Keeping GL on sampler2D is what leaves the UI's textures exactly as they are, including the paperdoll/appraisal FBO colour texture, which is an externally-owned GL_TEXTURE_2D from the §7.1 transitional seam and cannot become an array before V4g. On the Vulkan side, sampled views are now always layered, which also removes a latent invalid usage V6c shipped: it registered a Type2D offscreen view into a descriptor array whose element type is sampler2DArray.
And one real fix. Sampling through the table means a bound sampler object overrides the texture's own parameters. Nearest-requested UI art used to get its point filtering from a glTexParameter applied before the bindless handle went resident, so registering it with the stock WorldRepeat sampler would have made every retail icon and dat-font glyph silently bilinear. Those now register with a nearest-and-repeat sampler.
Supporting moves: GlGpuDevice.CreatePipeline splices common.glsl the same way Shader does, since an RHI shader that reads the table needs the table declared; GlGpuPassEncoder binds the device's table with the pipeline, which is the GL analogue of Vulkan binding descriptor set 2 per draw, and has to be per-bind because every raw-GL world renderer puts its own privately-numbered table at that binding; and the encoder derives GL_MULTISAMPLE from the pass's SampleCount, which is where the retained UI's hand-rolled glDisable belonged all along. TextRenderGlStateScope is deleted — the encoder's ambient capture restored a strict superset of it — and its failure-safety test follows the guarantee to GlAmbientCapabilityState, which gains a fakeable seam and, with it, the multisample-dimension coverage #249 recorded as missing.
App tests 4,057 passed / 3 skipped, unchanged from commit 1. Offline pixel gate against 871c406b: differing fraction 2.31e-05, 13 pixels of 563,200 compared — below the documented 15-23 pixel same-commit noise band, on a change that redraws every pixel of the retained UI through a different sampling path. The capture was inspected: vitals, spell bar, radar, toolbar icons and slot digits, chat window and Send button all present and correctly placed. Both new .spv pairs compile; the manifest records ui_text and debug_line as Vulkan-ready, leaving six pairs blocked on the world-renderer slices.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
871c406b99
commit
f6f58a12db
26 changed files with 763 additions and 615 deletions
|
|
@ -3,8 +3,6 @@ using System.Collections.Generic;
|
|||
using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
using AcDream.App.Rendering.Gpu;
|
||||
using AcDream.App.Rendering.Gpu.Gl;
|
||||
using Silk.NET.OpenGL;
|
||||
|
||||
namespace AcDream.App.Rendering;
|
||||
|
||||
|
|
@ -16,32 +14,38 @@ namespace AcDream.App.Rendering;
|
|||
///
|
||||
/// Campaign V slice V4a: the <c>ui_text</c> shader compiles through
|
||||
/// <see cref="IGpuDevice.CreatePipeline"/> (one <see cref="IGpuPipeline"/>,
|
||||
/// replacing the old hand-rolled <c>Shader</c> class), its three
|
||||
/// replacing the old hand-rolled <c>Shader</c> class) and its three
|
||||
/// fence-buffered per-flight VBOs are gone in favour of a per-<see cref="IGpuFrame"/>
|
||||
/// ring allocation per draw bucket, and the 1×1 white fill texture is created
|
||||
/// and uploaded through <see cref="IGpuDevice.CreateTexture"/> and registered
|
||||
/// into the device's texture table.
|
||||
/// ring allocation per draw bucket.
|
||||
///
|
||||
/// <see cref="IGpuPassEncoder"/> has no verb for classic texture-unit binding
|
||||
/// (every acdream RHI pass samples through the bindless texture table) but
|
||||
/// <see cref="DrawSprite"/> receives an ARBITRARY externally-owned raw GL
|
||||
/// texture name from dozens of UI call sites that are not part of this slice
|
||||
/// (icons, dat chrome, composited item art) — converting that whole surface
|
||||
/// to slot-based sampling is out of scope here. So sprite/font texture
|
||||
/// binding stays classic (<c>glActiveTexture</c>/<c>glBindTexture</c>,
|
||||
/// <c>ui_text.frag</c>'s <c>uTex</c> sampler unchanged) issued directly against
|
||||
/// the GL handle this class keeps for that reason, while the shader program
|
||||
/// itself, its blend/depth/cull description, and every per-frame vertex
|
||||
/// upload now go through the RHI. This mirrors the same GL-only escape hatch
|
||||
/// the campaign's V2 note already documents for the interim bindless handle
|
||||
/// table, and is retired only when a later slice moves ALL of TextRenderer's
|
||||
/// texture consumers onto registered slots.
|
||||
/// <para>Campaign V slice V6d finished the job: this class no longer touches
|
||||
/// GL at all, and is the first production renderer that draws on either
|
||||
/// backend. Three things had to change for that.</para>
|
||||
///
|
||||
/// <para><b>Textures.</b> V4a kept a classic <c>glActiveTexture</c>/<c>glBindTexture</c>
|
||||
/// path because <see cref="DrawSprite"/> receives an arbitrary texture from
|
||||
/// dozens of widget call sites. Those textures are all registered into the
|
||||
/// device's global table by <c>TextureCache</c> already — the classic path was
|
||||
/// only ever consuming the raw GL name that registration also produced. They
|
||||
/// now travel as <see cref="UiTextureTableHandle"/> values instead, and the
|
||||
/// shader samples the table.</para>
|
||||
///
|
||||
/// <para><b>Loose uniforms.</b> <c>uScreenSize</c> and <c>uUseTexture</c> moved
|
||||
/// into the pinned <see cref="GpuPushConstants"/> block. Screen size is the
|
||||
/// block's two spare scalars; the sampling mode is derived from which of the
|
||||
/// two texture-table slots is assigned, so no new field was needed. See
|
||||
/// <c>ui_text.frag</c> for the three cases.</para>
|
||||
///
|
||||
/// <para><b>GL capability state.</b> The pass no longer disables multisampling
|
||||
/// by hand — <c>GlGpuPassEncoder</c> derives that from the pass's SampleCount
|
||||
/// and restores it on close, which is where pass state belongs and which the
|
||||
/// Vulkan backend gets from the pass description for free.</para>
|
||||
///
|
||||
/// Uses per-bucket ring allocations flushed in up to three draw calls per
|
||||
/// layer, to avoid a per-vertex "use texture" flag. Rects are drawn first so
|
||||
/// text sits on top of background panels.
|
||||
/// </summary>
|
||||
public sealed unsafe class TextRenderer : IDisposable
|
||||
public sealed class TextRenderer : IDisposable
|
||||
{
|
||||
private const int FloatsPerVertex = 8; // pos(2) + uv(2) + color(4)
|
||||
private const int VertexStrideBytes = FloatsPerVertex * sizeof(float);
|
||||
|
|
@ -54,16 +58,8 @@ public sealed unsafe class TextRenderer : IDisposable
|
|||
new GpuVertexAttribute(2, GpuVertexFormat.Float4, 16),
|
||||
]);
|
||||
|
||||
private readonly IGpuDevice _device;
|
||||
private readonly GlGpuDevice _glDevice;
|
||||
private readonly ICurrentGpuFrameSource _frameSource;
|
||||
private readonly GL _gl;
|
||||
private readonly ITextRenderGlStateApi _glState;
|
||||
private readonly IGpuPipeline _pipeline;
|
||||
private readonly IGpuTexture _whiteTexture;
|
||||
private readonly uint _whiteTex; // 1×1 white, for solid fills routed through the sprite bucket
|
||||
private readonly int _uScreenSizeLocation;
|
||||
private readonly int _uUseTextureLocation;
|
||||
|
||||
private sealed class SpriteSeg { public uint Texture; public readonly List<float> Verts = new(256); }
|
||||
|
||||
|
|
@ -112,82 +108,28 @@ public sealed unsafe class TextRenderer : IDisposable
|
|||
// change of their own.
|
||||
internal TextRenderer(IGpuDevice device, ICurrentGpuFrameSource frameSource, string shaderDir)
|
||||
{
|
||||
_device = device ?? throw new ArgumentNullException(nameof(device));
|
||||
ArgumentNullException.ThrowIfNull(device);
|
||||
_frameSource = frameSource ?? throw new ArgumentNullException(nameof(frameSource));
|
||||
if (device is not GlGpuDevice glDevice)
|
||||
{
|
||||
throw new NotSupportedException(
|
||||
"TextRenderer's classic sprite/font texture-unit binding path (see the class " +
|
||||
"remarks) is GL-only; it needs the raw GL handle GlGpuDevice exposes. Other " +
|
||||
"backends are out of scope until a later slice removes that classic path.");
|
||||
}
|
||||
_glDevice = glDevice;
|
||||
_gl = glDevice.Gl;
|
||||
_glState = new SilkTextRenderGlStateApi(_gl);
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(shaderDir);
|
||||
|
||||
IGpuPipeline? pipeline = null;
|
||||
IGpuTexture? whiteTexture = null;
|
||||
try
|
||||
_pipeline = device.CreatePipeline(new GpuPipelineDescription
|
||||
{
|
||||
pipeline = device.CreatePipeline(new GpuPipelineDescription
|
||||
{
|
||||
Name = "ui-text",
|
||||
Shaders = new GpuShaderSet("ui_text"),
|
||||
VertexLayout = SpriteVertexLayout,
|
||||
Topology = GpuPrimitiveTopology.TriangleList,
|
||||
Blend = GpuBlendMode.StraightAlpha,
|
||||
// The retained UI is a self-contained 2-D pass — depth is
|
||||
// irrelevant and the world pass's alpha-to-coverage state must
|
||||
// not leak in (feedback_render_self_contained_gl_state). Flush
|
||||
// below still asserts this by hand via raw GL calls (and
|
||||
// GL_MULTISAMPLE, which has no representation here), matching
|
||||
// what TextRenderGlStateScope has always restored on exit.
|
||||
Depth = GpuDepthState.Disabled,
|
||||
Cull = GpuCullMode.None,
|
||||
AlphaToCoverage = false,
|
||||
ColorWrite = true,
|
||||
SampleCount = 1,
|
||||
});
|
||||
|
||||
// 1×1 white texture so DrawFill can route solid-colour quads through the SPRITE
|
||||
// bucket (the shader multiplies texel×color → white×color = color). Lets a panel
|
||||
// background draw UNDER its text in painter order, which DrawRect's separate
|
||||
// bucket cannot (it always composites after all sprites).
|
||||
whiteTexture = device.CreateTexture(new GpuTextureDescription(
|
||||
"ui-text-white",
|
||||
GpuTextureKind.Texture2D,
|
||||
GpuTextureFormat.Rgba8Unorm,
|
||||
Width: 1,
|
||||
Height: 1,
|
||||
LayerCount: 1,
|
||||
MipLevelCount: 1));
|
||||
whiteTexture.Upload(0, 0, [255, 255, 255, 255]);
|
||||
IGpuSampler whiteSampler = device.CreateSampler(GpuSamplerDescription.UiNearest);
|
||||
device.RegisterTexture(whiteTexture, whiteSampler);
|
||||
}
|
||||
catch
|
||||
{
|
||||
whiteTexture?.Dispose();
|
||||
pipeline?.Dispose();
|
||||
throw;
|
||||
}
|
||||
|
||||
_pipeline = pipeline;
|
||||
_whiteTexture = whiteTexture;
|
||||
_whiteTex = ((GlGpuTexture)whiteTexture).GlName;
|
||||
|
||||
uint program = ((GlGpuPipeline)_pipeline).GlProgram;
|
||||
_uScreenSizeLocation = _gl.GetUniformLocation(program, "uScreenSize");
|
||||
_uUseTextureLocation = _gl.GetUniformLocation(program, "uUseTexture");
|
||||
// uTex (the sampler unit) never changes — bind it once rather than on every Flush.
|
||||
int texLocation = _gl.GetUniformLocation(program, "uTex");
|
||||
if (texLocation >= 0)
|
||||
{
|
||||
_gl.UseProgram(program);
|
||||
_gl.Uniform1(texLocation, 0);
|
||||
_gl.UseProgram(0);
|
||||
}
|
||||
Name = "ui-text",
|
||||
Shaders = new GpuShaderSet("ui_text"),
|
||||
VertexLayout = SpriteVertexLayout,
|
||||
Topology = GpuPrimitiveTopology.TriangleList,
|
||||
Blend = GpuBlendMode.StraightAlpha,
|
||||
// The retained UI is a self-contained 2-D pass — depth is
|
||||
// irrelevant and the world pass's alpha-to-coverage state must not
|
||||
// leak in (feedback_render_self_contained_gl_state). Multisampling
|
||||
// is the pass's business rather than the pipeline's and comes from
|
||||
// the SampleCount below; see GlGpuPassEncoder's constructor.
|
||||
Depth = GpuDepthState.Disabled,
|
||||
Cull = GpuCullMode.None,
|
||||
AlphaToCoverage = false,
|
||||
ColorWrite = true,
|
||||
SampleCount = 1,
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>Begin a HUD pass. Call once per frame before any Draw* calls.</summary>
|
||||
|
|
@ -218,9 +160,15 @@ public sealed unsafe class TextRenderer : IDisposable
|
|||
/// when active), so it composites in painter order with sprites + dat-font text. Use
|
||||
/// this — not <see cref="DrawRect"/> — for a panel BACKGROUND that text draws on top of:
|
||||
/// DrawRect's bucket always flushes after all sprites, so a rect background would cover
|
||||
/// the text instead.</summary>
|
||||
/// the text instead.
|
||||
///
|
||||
/// <para>Slice V6d: this used to route through a 1×1 white texture, relying on
|
||||
/// white × colour = colour. The shader now has an untextured branch that produces
|
||||
/// the same value directly (multiplying by exactly 1.0 changes no bits), so the
|
||||
/// white texture is gone and the fill is a sprite segment with no texture.</para>
|
||||
/// </summary>
|
||||
public void DrawFill(float x, float y, float w, float h, Vector4 color)
|
||||
=> DrawSprite(_whiteTex, x, y, w, h, 0f, 0f, 1f, 1f, color);
|
||||
=> DrawSprite(UiTextureTableHandle.None, x, y, w, h, 0f, 0f, 1f, 1f, color);
|
||||
|
||||
/// <summary>Draw a 1-pixel-thick outline rect.</summary>
|
||||
public void DrawRectOutline(float x, float y, float w, float h, Vector4 color, float thickness = 1f)
|
||||
|
|
@ -318,8 +266,15 @@ public sealed unsafe class TextRenderer : IDisposable
|
|||
|
||||
/// <summary>
|
||||
/// Draw a textured sprite quad in screen pixel space with an explicit
|
||||
/// source-UV rectangle (for 9-slice / atlas sub-regions). Batched per
|
||||
/// GL texture handle; flushed with uUseTexture=2 (RGBA modulate).
|
||||
/// source-UV rectangle (for 9-slice / atlas sub-regions).
|
||||
///
|
||||
/// <paramref name="texture"/> is a <see cref="UiTextureTableHandle"/> — a
|
||||
/// one-based index into the device's global texture table, which is what
|
||||
/// <c>TextureCache</c> now hands out in place of the raw GL name it used to.
|
||||
/// Segments batch per handle and draw in submission order.
|
||||
/// <see cref="UiTextureTableHandle.None"/> draws the tint alone; every
|
||||
/// widget guards against passing it, and <see cref="DrawFill"/> uses it
|
||||
/// deliberately.
|
||||
/// </summary>
|
||||
public void DrawSprite(uint texture, float x, float y, float w, float h,
|
||||
float u0, float v0, float u1, float v1, Vector4 tint)
|
||||
|
|
@ -331,14 +286,18 @@ public sealed unsafe class TextRenderer : IDisposable
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves a <see cref="GpuTextureSlot"/> produced by the paperdoll/appraisal
|
||||
/// Encodes a <see cref="GpuTextureSlot"/> produced by the paperdoll/appraisal
|
||||
/// viewport transitional seam (<c>GlGpuDevice.RegisterExternalColorTexture</c>,
|
||||
/// campaign doc §7.1) back to the raw GL texture name <see cref="DrawSprite"/>
|
||||
/// needs. Returns 0 (no texture) for an unassigned slot. GL-only; removed
|
||||
/// at V4g alongside the seam it resolves.
|
||||
/// campaign doc §7.1) as the handle <see cref="DrawSprite"/> takes. Returns
|
||||
/// <see cref="UiTextureTableHandle.None"/> for an unassigned slot.
|
||||
///
|
||||
/// <para>Slice V6d: this used to resolve the slot back to a raw GL texture
|
||||
/// name for the classic binding path. Now that the UI samples the table, the
|
||||
/// externally-owned texture needs no special treatment at draw time at all —
|
||||
/// registration already put it in the table, and this is a plain encode.</para>
|
||||
/// </summary>
|
||||
internal uint ResolveExternalTextureSlot(GpuTextureSlot slot) =>
|
||||
_glDevice.TryResolveExternalColorTexture(slot, out uint name) ? name : 0;
|
||||
internal static uint ResolveExternalTextureSlot(GpuTextureSlot slot) =>
|
||||
UiTextureTableHandle.FromSlot(slot);
|
||||
|
||||
/// <summary>Pick the sprite segment for <paramref name="texture"/>: extend the current
|
||||
/// same-texture run, else reuse a pooled segment, else allocate. Submission order is
|
||||
|
|
@ -401,10 +360,11 @@ public sealed unsafe class TextRenderer : IDisposable
|
|||
|
||||
// Retained UI is a private render pass: an upload or draw failure must
|
||||
// not leak its depth/cull/blend/MSAA state into a later recoverable
|
||||
// frame. The focused scope restores from Flush's generated finally,
|
||||
// including when either DrawLayer call throws.
|
||||
using var stateScope = new TextRenderGlStateScope(_glState);
|
||||
|
||||
// frame. Slice V6d: the encoder's own `using` is that guarantee — the
|
||||
// GL backend captures every ambient capability a pipeline bind can
|
||||
// change when the pass opens and restores it on close, including when
|
||||
// either DrawLayer call throws. That replaced this renderer's private
|
||||
// GL state scope, which restored a strict subset of the same values.
|
||||
using IGpuPassEncoder encoder = frame.BeginPass(new GpuPassDescription
|
||||
{
|
||||
Name = "ui-text",
|
||||
|
|
@ -422,21 +382,6 @@ public sealed unsafe class TextRenderer : IDisposable
|
|||
SampleCount = 1,
|
||||
});
|
||||
encoder.BindPipeline(_pipeline);
|
||||
_gl.Uniform2(_uScreenSizeLocation, _screenSize.X, _screenSize.Y);
|
||||
|
||||
// Establish the self-contained UI pass state.
|
||||
// The world pass leaves alpha-to-coverage + multisample enabled (WbDrawDispatcher,
|
||||
// QualitySettings MSAA). If they bleed into the UI pass, each glyph's soft alpha
|
||||
// EDGE is converted to dithered MSAA coverage instead of a clean alpha blend —
|
||||
// the "text not sharp / fuzzy" artifact. The UI composites with straight alpha
|
||||
// blending and must own this state (feedback_render_self_contained_gl_state).
|
||||
_gl.Disable(EnableCap.SampleAlphaToCoverage);
|
||||
_gl.Disable(EnableCap.Multisample);
|
||||
_gl.Disable(EnableCap.DepthTest);
|
||||
_gl.Disable(EnableCap.CullFace);
|
||||
_gl.DepthMask(false);
|
||||
_gl.Enable(EnableCap.Blend);
|
||||
_gl.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha);
|
||||
|
||||
// LAYERED compositing for the UI (background → fill → text):
|
||||
// 1. RGBA dat sprites — window chrome / panel backgrounds (behind)
|
||||
|
|
@ -459,16 +404,14 @@ public sealed unsafe class TextRenderer : IDisposable
|
|||
List<float> textBuf, int textVerts, BitmapFont? font,
|
||||
IGpuFrame frame, IGpuPassEncoder encoder)
|
||||
{
|
||||
// 1. RGBA dat sprites — one draw call per distinct GL texture.
|
||||
// 1. RGBA dat sprites — one draw call per distinct texture-table slot.
|
||||
if (segUsed > 0)
|
||||
{
|
||||
SetUseTexture(2);
|
||||
_gl.ActiveTexture(TextureUnit.Texture0);
|
||||
for (int i = 0; i < segUsed; i++)
|
||||
{
|
||||
var seg = spriteSegs[i];
|
||||
if (seg.Verts.Count == 0) continue;
|
||||
_gl.BindTexture(TextureTarget.Texture2D, seg.Texture);
|
||||
SetTextures(encoder, colorHandle: seg.Texture, coverageHandle: UiTextureTableHandle.None);
|
||||
DrawRing(frame, encoder, seg.Verts);
|
||||
}
|
||||
}
|
||||
|
|
@ -476,24 +419,33 @@ public sealed unsafe class TextRenderer : IDisposable
|
|||
// 2. Untextured rects — widget fills on top of the chrome.
|
||||
if (rectVerts > 0)
|
||||
{
|
||||
SetUseTexture(0);
|
||||
SetTextures(encoder, UiTextureTableHandle.None, UiTextureTableHandle.None);
|
||||
DrawRing(frame, encoder, rectBuf);
|
||||
}
|
||||
|
||||
// 3. Textured debug-font text glyphs on top.
|
||||
// 3. Textured debug-font text glyphs on top. The atlas is single-channel
|
||||
// coverage, which is the coverage slot rather than the colour one.
|
||||
if (textVerts > 0 && font is not null)
|
||||
{
|
||||
SetUseTexture(1);
|
||||
_gl.ActiveTexture(TextureUnit.Texture0);
|
||||
_gl.BindTexture(TextureTarget.Texture2D, font.TextureId);
|
||||
SetTextures(encoder, UiTextureTableHandle.None, coverageHandle: font.TextureId);
|
||||
DrawRing(frame, encoder, textBuf);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetUseTexture(int mode)
|
||||
/// <summary>
|
||||
/// Writes the shared push-constant block for one draw bucket: the screen
|
||||
/// size the vertex stage maps pixels to NDC with, and the two texture-table
|
||||
/// slots whose assignment selects the fragment stage's sampling mode.
|
||||
/// At most one of the two handles is ever a real texture.
|
||||
/// </summary>
|
||||
private void SetTextures(IGpuPassEncoder encoder, uint colorHandle, uint coverageHandle)
|
||||
{
|
||||
if (_uUseTextureLocation >= 0)
|
||||
_gl.Uniform1(_uUseTextureLocation, mode);
|
||||
GpuPushConstants constants = GpuPushConstants.Default;
|
||||
constants.ParamA = _screenSize.X;
|
||||
constants.ParamB = _screenSize.Y;
|
||||
constants.TextureIndexA = UiTextureTableHandle.ToSlot(colorHandle).Index;
|
||||
constants.TextureIndexB = UiTextureTableHandle.ToSlot(coverageHandle).Index;
|
||||
encoder.SetPushConstants(constants);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -513,9 +465,5 @@ public sealed unsafe class TextRenderer : IDisposable
|
|||
encoder.Draw((uint)(buf.Count / FloatsPerVertex), 1, 0, 0);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_whiteTexture.Dispose();
|
||||
_pipeline.Dispose();
|
||||
}
|
||||
public void Dispose() => _pipeline.Dispose();
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue