Ports WorldBuilder's GL sampler-object pattern
(references/WorldBuilder/Chorizite.OpenGLSDLBackend/OpenGLGraphicsDevice.cs:115-132,
SkyboxRenderManager.cs:312). Two persistent samplers (Repeat +
ClampToEdge) are created once at GL init; the sky pass binds the
appropriate one to texture unit 0 per submesh instead of mutating
per-texture GL_TEXTURE_WRAP_S/T state.
Why this is better than the prior M1 track-and-restore hack:
1. Sampler state is decoupled from texture state. Two renderers can
share the same texture handle but sample it with different wrap
modes simultaneously and safely — sampler state at the bind point
overrides the texture's own wrap parameters.
2. No bookkeeping. Drops the HashSet<uint> clamped-textures tracking
and the end-of-pass restore loop. The only restore needed is
BindSampler(0, 0) to release unit 0 back to per-texture state.
3. Constant cost. Sampler objects are created once per GL context,
not per draw. Filter modes match TextureCache's upload defaults
(Linear/Linear, no mipmaps) so the binding is purely a wrap-mode
selection.
Field count: SkyRenderer.cs -28 lines, +14 lines. GameWindow.cs gets
the SamplerCache field + ctor + Dispose. SkyRenderer disposed before
SamplerCache so the sky teardown path doesn't reference a freed
sampler handle.
dotnet build green, dotnet test green: 695 / 393 / 243 = 1331 passed
(unchanged).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
63 lines
2.7 KiB
C#
63 lines
2.7 KiB
C#
using System;
|
|
using Silk.NET.OpenGL;
|
|
|
|
namespace AcDream.App.Rendering;
|
|
|
|
/// <summary>
|
|
/// Two persistent GL sampler objects (Repeat + ClampToEdge) created once
|
|
/// per GL context. Renderers <see cref="GL.BindSampler"/> the appropriate
|
|
/// one to a texture unit instead of mutating per-texture
|
|
/// <c>GL_TEXTURE_WRAP_S/T</c> state — sampler state overrides the
|
|
/// texture's own wrap parameters, so two renderers can share the same
|
|
/// texture handle but sample it with different wrap modes safely.
|
|
///
|
|
/// <para>
|
|
/// Ported from
|
|
/// <c>references/WorldBuilder/Chorizite.OpenGLSDLBackend/OpenGLGraphicsDevice.cs:115-132</c>.
|
|
/// Filter modes match <see cref="TextureCache"/>'s upload defaults
|
|
/// (Linear / Linear, no mipmaps) so binding either sampler doesn't
|
|
/// change the visual filtering behavior — only the wrap behavior at
|
|
/// UVs outside [0, 1].
|
|
/// </para>
|
|
///
|
|
/// <para>
|
|
/// Lifetime: created once at GL init, disposed with the GL context.
|
|
/// Anything that binds a sampler MUST unbind it (<c>BindSampler(unit, 0)</c>)
|
|
/// before yielding to a renderer that doesn't use samplers, otherwise
|
|
/// the bound sampler's wrap mode will silently override that renderer's
|
|
/// per-texture wrap state.
|
|
/// </para>
|
|
/// </summary>
|
|
public sealed class SamplerCache : IDisposable
|
|
{
|
|
private readonly GL _gl;
|
|
|
|
/// <summary>Sampler with WrapS = WrapT = Repeat. The default for textures uploaded by <see cref="TextureCache"/>.</summary>
|
|
public uint Wrap { get; }
|
|
|
|
/// <summary>Sampler with WrapS = WrapT = ClampToEdge. Used by sky meshes whose authored UVs are strictly in [0, 1] to avoid bilinear-filter bleed at seam edges.</summary>
|
|
public uint Clamp { get; }
|
|
|
|
public SamplerCache(GL gl)
|
|
{
|
|
_gl = gl ?? throw new ArgumentNullException(nameof(gl));
|
|
|
|
Wrap = _gl.GenSampler();
|
|
_gl.SamplerParameter(Wrap, SamplerParameterI.WrapS, (int)TextureWrapMode.Repeat);
|
|
_gl.SamplerParameter(Wrap, SamplerParameterI.WrapT, (int)TextureWrapMode.Repeat);
|
|
_gl.SamplerParameter(Wrap, SamplerParameterI.MinFilter, (int)TextureMinFilter.Linear);
|
|
_gl.SamplerParameter(Wrap, SamplerParameterI.MagFilter, (int)TextureMagFilter.Linear);
|
|
|
|
Clamp = _gl.GenSampler();
|
|
_gl.SamplerParameter(Clamp, SamplerParameterI.WrapS, (int)TextureWrapMode.ClampToEdge);
|
|
_gl.SamplerParameter(Clamp, SamplerParameterI.WrapT, (int)TextureWrapMode.ClampToEdge);
|
|
_gl.SamplerParameter(Clamp, SamplerParameterI.MinFilter, (int)TextureMinFilter.Linear);
|
|
_gl.SamplerParameter(Clamp, SamplerParameterI.MagFilter, (int)TextureMagFilter.Linear);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
if (Wrap != 0) _gl.DeleteSampler(Wrap);
|
|
if (Clamp != 0) _gl.DeleteSampler(Clamp);
|
|
}
|
|
}
|