From 877b549e0ab9b93de47b85a8fead7a99560e2eaa Mon Sep 17 00:00:00 2001 From: Asval Date: Mon, 7 Sep 2026 18:25:33 +0200 Subject: [PATCH 1/7] use uvec2 for bindless textures and cast to sampler2D --- Snooper/Shaders/Landscape/landscape.frag | 9 +++++---- Snooper/Shaders/Landscape/landscape.tese | 11 ++++++----- Snooper/Shaders/billboard.frag | 4 ++-- Snooper/Shaders/material_sampling.glsl | 12 ++++++------ 4 files changed, 19 insertions(+), 17 deletions(-) diff --git a/Snooper/Shaders/Landscape/landscape.frag b/Snooper/Shaders/Landscape/landscape.frag index c0b8a41..f19393d 100644 --- a/Snooper/Shaders/Landscape/landscape.frag +++ b/Snooper/Shaders/Landscape/landscape.frag @@ -11,8 +11,8 @@ struct PerMaterialData bool IsReady; uint WeightmapCount; - sampler2D Heightmap; - sampler2D Weightmaps[4]; + uvec2 Heightmap; + uvec2 Weightmaps[4]; uint EnabledChannels[4]; vec2 HeightmapScaleBias; @@ -77,7 +77,8 @@ vec3 getColorFromWeightmap(PerMaterialData materialData, WeightHighlightMapping for (int i = 0; i < weightmapCount; i++) { - vec2 weightmapSize = textureSize(materialData.Weightmaps[i], 0); + sampler2D weightmap = sampler2D(materialData.Weightmaps[i]); + vec2 weightmapSize = textureSize(weightmap, 0); vec2 texelSize = 1.0 / weightmapSize; vec2 weightmapUvSize = vec2(uSizeQuads) / weightmapSize; @@ -85,7 +86,7 @@ vec3 getColorFromWeightmap(PerMaterialData materialData, WeightHighlightMapping uv2 = uv2 * (1.0 - texelSize) + 0.5 * texelSize; uint mask = materialData.EnabledChannels[i]; - vec4 weightmapColor = texture(materialData.Weightmaps[i], uv2); + vec4 weightmapColor = texture(weightmap, uv2); for (int c = 0; c < 4; c++) { if (!channelEnabled(mask, c)) diff --git a/Snooper/Shaders/Landscape/landscape.tese b/Snooper/Shaders/Landscape/landscape.tese index 3e93c95..d6b2725 100644 --- a/Snooper/Shaders/Landscape/landscape.tese +++ b/Snooper/Shaders/Landscape/landscape.tese @@ -10,8 +10,8 @@ struct PerMaterialData bool IsReady; uint WeightmapCount; - sampler2D Heightmap; - sampler2D Weightmaps[4]; + uvec2 Heightmap; + uvec2 Weightmaps[4]; uint EnabledChannels[4]; vec2 HeightmapScaleBias; @@ -88,7 +88,7 @@ void main() // pushing the vertex out of clip space when the channel value > 0.5. if (materialData.VisibilityTextureIndex != 0xFFFFFFFFu) { - sampler2D weightmap = materialData.Weightmaps[materialData.VisibilityTextureIndex]; + sampler2D weightmap = sampler2D(materialData.Weightmaps[materialData.VisibilityTextureIndex]); vec2 weightmapSize = textureSize(weightmap, 0); vec2 weightmapTexelSize = 1.0 / weightmapSize; @@ -107,14 +107,15 @@ void main() } } - vec2 heightmapSize = textureSize(materialData.Heightmap, 0); + sampler2D heightmap = sampler2D(materialData.Heightmap); + vec2 heightmapSize = textureSize(heightmap, 0); vec2 heightmapTexelSize = 1.0 / heightmapSize; vec2 heightmapUvSize = vec2(uSizeQuads) / heightmapSize; vec2 uv = materialData.HeightmapScaleBias + subPatchOffset * heightmapUvSize + vec2(u, v) * (heightmapUvSize * quadFraction); uv = uv * (1.0 - heightmapTexelSize) + 0.5 * heightmapTexelSize; - vec4 color = texture(materialData.Heightmap, uv); + vec4 color = texture(heightmap, uv); float R = color.r * 255.0; float G = color.g * 255.0; te_out.vHeight = ((R * 256.0) + G - 32768.0) / 128.0 * uGlobalScale; diff --git a/Snooper/Shaders/billboard.frag b/Snooper/Shaders/billboard.frag index 6e42a11..ab5c47b 100644 --- a/Snooper/Shaders/billboard.frag +++ b/Snooper/Shaders/billboard.frag @@ -6,7 +6,7 @@ struct PerMaterialData { bool IsReady; float OpacityMask; - sampler2D Sprite; + uvec2 Sprite; }; layout(std430, binding = BINDING_MATERIAL_DATA) restrict readonly buffer PerMaterialDataBuffer @@ -30,7 +30,7 @@ void main() vec4 color = vec4(1.0); if (materialData.IsReady) { - color = texture(materialData.Sprite, vTexCoords); + color = texture(sampler2D(materialData.Sprite), vTexCoords); if (color.a < materialData.OpacityMask) { discard; diff --git a/Snooper/Shaders/material_sampling.glsl b/Snooper/Shaders/material_sampling.glsl index a0ae86e..ec4a7a7 100644 --- a/Snooper/Shaders/material_sampling.glsl +++ b/Snooper/Shaders/material_sampling.glsl @@ -9,9 +9,9 @@ struct PerMaterialData uint LayerTextureFlags; // Fixed arrays for up to 4 layers - sampler2D Diffuse[4]; - sampler2D Normal[4]; - sampler2D Specular[4]; + uvec2 Diffuse[4]; + uvec2 Normal[4]; + uvec2 Specular[4]; // Per-layer material properties // Roughness: 2 floats per layer (min, max) * 4 layers = 8 floats @@ -54,7 +54,7 @@ vec4 SampleLayerDiffuse(PerMaterialData materialData, uint layer, vec2 uv) if (HasLayerTexture(materialData, layer, 0u)) { - return texture(materialData.Diffuse[layer], uv); + return texture(sampler2D(materialData.Diffuse[layer]), uv); } return vec4(1.0); @@ -68,7 +68,7 @@ vec3 SampleLayerNormal(PerMaterialData materialData, uint layer, vec2 uv) if (HasLayerTexture(materialData, layer, 1u)) { - vec2 xy = texture(materialData.Normal[layer], uv).rg * 2.0 - 1.0; + vec2 xy = texture(sampler2D(materialData.Normal[layer]), uv).rg * 2.0 - 1.0; float z = sqrt(max(0.0, 1.0 - dot(xy, xy))); return normalize(vec3(xy, z)); } @@ -84,7 +84,7 @@ vec3 SampleLayerSpecular(PerMaterialData materialData, uint layer, vec2 uv) if (HasLayerTexture(materialData, layer, 2u)) { - vec3 spec = texture(materialData.Specular[layer], uv).rgb; + vec3 spec = texture(sampler2D(materialData.Specular[layer]), uv).rgb; vec2 roughness = GetLayerRoughness(materialData, layer); spec.b = mix(roughness.x, roughness.y, spec.b); return spec; From dea71c1c06bb95e4a84603da18d8e6fd2e02f96a Mon Sep 17 00:00:00 2001 From: Asval Date: Mon, 7 Sep 2026 18:25:33 +0200 Subject: [PATCH 2/7] fix ssbo crash --- Snooper/Core/Containers/Buffers/Buffer.cs | 28 +++++++++---------- .../Containers/Buffers/ShaderStorageBuffer.cs | 1 + 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/Snooper/Core/Containers/Buffers/Buffer.cs b/Snooper/Core/Containers/Buffers/Buffer.cs index 7f165ca..0856e59 100644 --- a/Snooper/Core/Containers/Buffers/Buffer.cs +++ b/Snooper/Core/Containers/Buffers/Buffer.cs @@ -38,7 +38,7 @@ public abstract class Buffer(BufferTarget target, BufferUsageHint usageHint, public int Count { get; private set; } public int Capacity { get; private set; } - private bool _bInitialized; + protected bool IsAllocated; private readonly Dictionary _allocations = new(); private readonly SortedSet _freeBlocks = new(Comparer.Create((a, b) => { @@ -52,12 +52,12 @@ public abstract class Buffer(BufferTarget target, BufferUsageHint usageHint, public override void Generate() { - if (_bInitialized) + if (IsAllocated) throw new InvalidOperationException("Buffer is already initialized."); GL.CreateBuffers(1, out uint handle); Handle = handle; - _bInitialized = false; + IsAllocated = false; } public void Bind() @@ -78,11 +78,11 @@ private void ResizeIfNeeded(int newSize, double factor = 1.5, bool copy = false) var oldCapacity = Capacity; Capacity = (int) Math.Max(Capacity * factor, newSize); - if (_bInitialized) + if (IsAllocated) { Log.Warning("Resizing buffer {0} ({1}) from {2} to {3} (asked: {4}) (initialized!!!!!!)", Handle, PName, oldCapacity, Capacity, newSize); - _bInitialized = false; + IsAllocated = false; if (copy) { var oldBuffer = Handle; @@ -109,7 +109,7 @@ private void ResizeIfNeeded(int newSize, double factor = 1.5, bool copy = false) public void Reallocate(int size) { - _bInitialized = false; + IsAllocated = false; Allocate(size); } @@ -117,7 +117,7 @@ public void Reallocate(int size) public void Allocate(int size) { ArgumentOutOfRangeException.ThrowIfNegativeOrZero(size); - if (_bInitialized) + if (IsAllocated) throw new InvalidOperationException("Buffer is already initialized. Use Update method to modify data."); if (size > Capacity) @@ -133,7 +133,7 @@ public void Allocate(int size) // _allocationIdCounter = 0; // _allocations.Clear(); // _freeBlocks.Clear(); - _bInitialized = true; + IsAllocated = true; } public BufferAllocation Add(T data) => AddInternal([data]); @@ -143,7 +143,7 @@ private BufferAllocation AddInternal(T[] data) var length = data.Length; ArgumentOutOfRangeException.ThrowIfNegativeOrZero(length); - if (!_bInitialized) + if (!IsAllocated) { Allocate(length); } @@ -176,7 +176,7 @@ private void UpsertInternal(int index, T[] data) ArgumentOutOfRangeException.ThrowIfNegativeOrZero(length); ArgumentOutOfRangeException.ThrowIfNegative(index); - if (!_bInitialized) + if (!IsAllocated) { Allocate(index + length); } @@ -196,7 +196,7 @@ private void UpsertInternal(int index, T[] data) public void Update(int allocationId, T[] data) => UpdateInternal(allocationId, data); private void UpdateInternal(int allocationId, T[] data, bool batched = false) { - if (!_bInitialized) + if (!IsAllocated) throw new InvalidOperationException("Buffer is not initialized. Use Add method to initialize it."); if (!_allocations.TryGetValue(allocationId, out var metadata)) @@ -215,7 +215,7 @@ private void UpdateInternal(int allocationId, T[] data, bool batched = false) public void UpdateCustom(BufferAllocation allocation, TCustom data, int offset) where TCustom : unmanaged => UpdateCustomInternal(allocation.AllocationId, data, offset); private void UpdateCustomInternal(int allocationId, TCustom data, int offset) where TCustom : unmanaged { - if (!_bInitialized) + if (!IsAllocated) throw new InvalidOperationException("Buffer is not initialized. Use Add method to initialize it."); if (!_allocations.TryGetValue(allocationId, out var metadata)) @@ -304,7 +304,7 @@ public BufferAllocation CopyFrom(Buffer sourceBuffer, BufferAllocation source var length = sourceAllocation.Length; ArgumentOutOfRangeException.ThrowIfNegativeOrZero(length); - if (!_bInitialized) + if (!IsAllocated) { Allocate(length); } @@ -326,7 +326,7 @@ public BufferAllocation CopyFrom(Buffer sourceBuffer, BufferAllocation source public void Clear() { - if (!_bInitialized) + if (!IsAllocated) throw new InvalidOperationException("Cannot clear a buffer that is not initialized."); ClearStorage(0, TotalElements * Stride); diff --git a/Snooper/Core/Containers/Buffers/ShaderStorageBuffer.cs b/Snooper/Core/Containers/Buffers/ShaderStorageBuffer.cs index 9c601c2..a8c9a42 100644 --- a/Snooper/Core/Containers/Buffers/ShaderStorageBuffer.cs +++ b/Snooper/Core/Containers/Buffers/ShaderStorageBuffer.cs @@ -11,6 +11,7 @@ public sealed class ShaderStorageBuffer(BufferUsageHint usageHint = BufferUsa public void Bind(uint index) { + if (!IsAllocated) return; // iGPUs don't like binding unallocated buffers GL.BindBufferBase(BufferRangeTarget.ShaderStorageBuffer, index, Handle); } From 1214a533152da8e2926493dbfe3cd04013cfc394 Mon Sep 17 00:00:00 2001 From: Asval Date: Mon, 7 Sep 2026 18:25:34 +0200 Subject: [PATCH 3/7] manual vertex layout relative offset + hardware-decoded normal + fix bitangent sign --- .../Core/Containers/Resources/GeometryPool.cs | 31 ++++++++++++++++--- .../Containers/Resources/IndirectResources.cs | 2 +- .../Components/Mesh/MeshComponent.cs | 9 +++--- Snooper/Rendering/Systems/BillboardSystem.cs | 7 +---- .../Rendering/Systems/IndirectRenderSystem.cs | 2 +- Snooper/Rendering/Systems/LandscapeSystem.cs | 7 +---- Snooper/Rendering/Systems/MeshRenderSystem.cs | 21 ++++--------- Snooper/Rendering/Systems/PrimitiveSystem.cs | 7 +---- Snooper/Rendering/Systems/TextRenderSystem.cs | 12 ++----- Snooper/Shaders/Buffers/CommonMesh.vert | 11 +++---- Snooper/Shaders/Hooks/skinning.glsl | 8 ++--- 11 files changed, 53 insertions(+), 64 deletions(-) diff --git a/Snooper/Core/Containers/Resources/GeometryPool.cs b/Snooper/Core/Containers/Resources/GeometryPool.cs index ca6a354..ec3e5b4 100644 --- a/Snooper/Core/Containers/Resources/GeometryPool.cs +++ b/Snooper/Core/Containers/Resources/GeometryPool.cs @@ -18,6 +18,29 @@ public class GeometryHandle(uint firstIndex, uint baseVertex, BufferAllocation m public int OverrideLod { get; internal set; } = overrideLod; } +public readonly struct VertexArrayLayout(uint vao, uint vbo, int stride) +{ + public VertexArrayLayout Float(uint location, int size, VertexAttribType type = VertexAttribType.Float, bool normalized = false, int offset = 0) + { + GL.VertexArrayAttribFormat(vao, location, size, type, normalized, 0); + return Enable(location, offset); + } + + public VertexArrayLayout Integer(uint location, int size, VertexAttribIType type = VertexAttribIType.UnsignedInt, int offset = 0) + { + GL.VertexArrayAttribIFormat(vao, location, size, type, 0); + return Enable(location, offset); + } + + private VertexArrayLayout Enable(uint location, int offset) + { + GL.VertexArrayVertexBuffer(vao, location, vbo, offset, stride); + GL.VertexArrayAttribBinding(vao, location, location); + GL.EnableVertexArrayAttrib(vao, location); + return this; + } +} + public class GeometryPool : IMemoryDetailsProvider, IDisposable where TVertex : unmanaged { private readonly VertexArray _vao = new(); @@ -27,7 +50,7 @@ public class GeometryPool : IMemoryDetailsProvider, IDisposable where T private readonly CullingResources _culling = new(); private readonly Dictionary _cache = new(); - private Action? _vertexLayoutSetter; + private Action? _vertexLayoutSetter; public void Generate() { @@ -41,7 +64,7 @@ public void Generate() _vbo.OnHandleChanged += (_, _) => BindBuffersToVao(); } - public void SetVertexLayout(Action setter) + public void SetVertexLayout(Action setter) { _vertexLayoutSetter = setter; BindBuffersToVao(); @@ -49,10 +72,8 @@ public void SetVertexLayout(Action setter) private void BindBuffersToVao() { - GL.VertexArrayVertexBuffer(_vao, 0, _vbo, 0, _vbo.Stride); GL.VertexArrayElementBuffer(_vao, _ebo); - - _vertexLayoutSetter?.Invoke(_vao); + _vertexLayoutSetter?.Invoke(new VertexArrayLayout(_vao, _vbo, _vbo.Stride)); } public void Allocate(AllocationCounts counts) diff --git a/Snooper/Core/Containers/Resources/IndirectResources.cs b/Snooper/Core/Containers/Resources/IndirectResources.cs index 6aa0383..7318502 100644 --- a/Snooper/Core/Containers/Resources/IndirectResources.cs +++ b/Snooper/Core/Containers/Resources/IndirectResources.cs @@ -41,7 +41,7 @@ public void Generate() _materialData.Generate(); } - public void SetVertexLayout(Action setter) => _geometry.SetVertexLayout(setter); + public void SetVertexLayout(Action setter) => _geometry.SetVertexLayout(setter); public void Allocate(AllocationCounts counts) { diff --git a/Snooper/Rendering/Components/Mesh/MeshComponent.cs b/Snooper/Rendering/Components/Mesh/MeshComponent.cs index 05e7357..1f612fc 100644 --- a/Snooper/Rendering/Components/Mesh/MeshComponent.cs +++ b/Snooper/Rendering/Components/Mesh/MeshComponent.cs @@ -23,7 +23,7 @@ namespace Snooper.Rendering.Components.Mesh; /// /// Packed vertex layout — 20 bytes total
/// loc 0: uvec2 — pos.x|pos.y (half2), pos.z|0 (half2) [offset 0, 8 bytes]
-/// loc 1: uint — normal xyzw RGB10A2 SNorm [offset 8, 4 bytes]
+/// loc 1: uint — normal xyz RGB10A2 SNorm, w = basis sign [offset 8, 4 bytes]
/// loc 2: uint — tangent xyz RGB10A2 SNorm, w = texLayer(0-3) [offset 12, 4 bytes]
/// loc 3: uint — uv.x|uv.y (half2) [offset 16, 4 bytes]
///
@@ -43,10 +43,11 @@ private static uint PackHalf2(float x, float y) return hx | (hy << 16); } - private static uint PackRgb10A2Snorm(Vector4 v) => PackRgb10A2Snorm(v.X, v.Y, v.Z, Snorm10(v.W)); + private static uint PackRgb10A2Snorm(Vector4 v) => PackRgb10A2Snorm(v.X, v.Y, v.Z, Snorm2(v.W)); private static uint PackRgb10A2Snorm(Vector3 v, uint texLayer) => PackRgb10A2Snorm(v.X, v.Y, v.Z, texLayer & 0x3u); private static uint PackRgb10A2Snorm(float x, float y, float z, uint w) => Snorm10(x) | (Snorm10(y) << 10) | (Snorm10(z) << 20) | (w << 30); private static uint Snorm10(float f) => (uint)(int)MathF.Round(Math.Clamp(f, -1f, 1f) * 511f) & 0x3FFu; + private static uint Snorm2(float f) => f < 0f ? 3u : 1u; } public unsafe struct PerMaterialMeshData : IPerMaterialData @@ -189,7 +190,7 @@ public Geometry(MeshVertex[] vertices, uint[] indices, FColor[]? colors, FMeshUV { var vertex = vertices[i]; var position = new Vector3(vertex.Position.X, vertex.Position.Z, vertex.Position.Y) * Settings.GlobalScale; - var normal = new Vector4(vertex.Normal.X, vertex.Normal.Z, vertex.Normal.Y, vertex.Normal.W); + var normal = new Vector4(vertex.Normal.X, vertex.Normal.Z, vertex.Normal.Y, -vertex.Normal.W); var tangent = new Vector3(vertex.Tangent.X, vertex.Tangent.Z, vertex.Tangent.Y); var texCoord = new Vector2(vertex.Uv.U, vertex.Uv.V); var texLayer = extraUvs != null ? (uint)Math.Floor(extraUvs[i].U) : 0u; @@ -219,7 +220,7 @@ public Geometry(SkinnedMeshVertex[] vertices, uint[] indices, FColor[]? colors, { var vertex = vertices[i]; var position = new Vector3(vertex.Position.X, vertex.Position.Z, vertex.Position.Y) * Settings.GlobalScale; - var normal = new Vector4(vertex.Normal.X, vertex.Normal.Z, vertex.Normal.Y, vertex.Normal.W); + var normal = new Vector4(vertex.Normal.X, vertex.Normal.Z, vertex.Normal.Y, -vertex.Normal.W); var tangent = new Vector3(vertex.Tangent.X, vertex.Tangent.Z, vertex.Tangent.Y); var texCoord = new Vector2(vertex.Uv.U, vertex.Uv.V); var texLayer = extraUvs != null ? (uint)Math.Floor(extraUvs[i].U) : 0u; diff --git a/Snooper/Rendering/Systems/BillboardSystem.cs b/Snooper/Rendering/Systems/BillboardSystem.cs index b2d6d5b..3eb11db 100644 --- a/Snooper/Rendering/Systems/BillboardSystem.cs +++ b/Snooper/Rendering/Systems/BillboardSystem.cs @@ -16,10 +16,5 @@ public class BillboardSystem : PrimitiveSystem VertexLayout { get; } = vao => - { - GL.VertexArrayAttribFormat(vao, 0, 2, VertexAttribType.Float, false, 0); - GL.EnableVertexArrayAttrib(vao, 0); - GL.VertexArrayAttribBinding(vao, 0, 0); - }; + protected override Action VertexLayout { get; } = layout => layout.Float(0, 2); } diff --git a/Snooper/Rendering/Systems/IndirectRenderSystem.cs b/Snooper/Rendering/Systems/IndirectRenderSystem.cs index 1c6330b..57f223e 100644 --- a/Snooper/Rendering/Systems/IndirectRenderSystem.cs +++ b/Snooper/Rendering/Systems/IndirectRenderSystem.cs @@ -23,7 +23,7 @@ public abstract class IndirectRenderSystem ActorSystemType.Rendering; protected override bool AllowDerivation => false; - protected abstract Action VertexLayout { get; } + protected abstract Action VertexLayout { get; } protected IndirectResources Resources { get; } = new(type, viewCount); protected virtual IEnumerable<(uint Binding, IIndexedBind Buffer)> SystemBuffers => []; diff --git a/Snooper/Rendering/Systems/LandscapeSystem.cs b/Snooper/Rendering/Systems/LandscapeSystem.cs index aeda8fe..07409fa 100644 --- a/Snooper/Rendering/Systems/LandscapeSystem.cs +++ b/Snooper/Rendering/Systems/LandscapeSystem.cs @@ -38,12 +38,7 @@ private abstract class LandscapeBindings : Bindings Defines = LandscapeBindings.OwnDefines } }; - protected override Action VertexLayout { get; } = vao => - { - GL.VertexArrayAttribFormat(vao, 0, 2, VertexAttribType.Float, false, 0); - GL.EnableVertexArrayAttrib(vao, 0); - GL.VertexArrayAttribBinding(vao, 0, 0); - }; + protected override Action VertexLayout { get; } = layout => layout.Float(0, 2); private readonly ShaderStorageBuffer _scales = new(); private readonly ShaderStorageBuffer _mapping = new(); diff --git a/Snooper/Rendering/Systems/MeshRenderSystem.cs b/Snooper/Rendering/Systems/MeshRenderSystem.cs index bd578e0..76afb29 100644 --- a/Snooper/Rendering/Systems/MeshRenderSystem.cs +++ b/Snooper/Rendering/Systems/MeshRenderSystem.cs @@ -25,21 +25,12 @@ public abstract class MeshRenderSystem(string[]? defines = null, int Defines = defines }; - protected override Action VertexLayout { get; } = vao => - { - GL.VertexArrayAttribIFormat(vao, 0, 2, VertexAttribIType.UnsignedInt, 0); - GL.VertexArrayAttribIFormat(vao, 1, 1, VertexAttribIType.UnsignedInt, 8); - GL.VertexArrayAttribIFormat(vao, 2, 1, VertexAttribIType.UnsignedInt, 12); - GL.VertexArrayAttribIFormat(vao, 3, 1, VertexAttribIType.UnsignedInt, 16); - GL.EnableVertexArrayAttrib(vao, 0); - GL.EnableVertexArrayAttrib(vao, 1); - GL.EnableVertexArrayAttrib(vao, 2); - GL.EnableVertexArrayAttrib(vao, 3); - GL.VertexArrayAttribBinding(vao, 0, 0); - GL.VertexArrayAttribBinding(vao, 1, 0); - GL.VertexArrayAttribBinding(vao, 2, 0); - GL.VertexArrayAttribBinding(vao, 3, 0); - }; + // see MeshComponent packed vertex layout + protected override Action VertexLayout { get; } = layout => layout + .Integer(0, 2) + .Float(1, 4, VertexAttribType.Int2101010Rev, normalized: true, offset: 8) + .Integer(2, 1, offset: 12) + .Integer(3, 1, offset: 16); protected override void OnLoad() { diff --git a/Snooper/Rendering/Systems/PrimitiveSystem.cs b/Snooper/Rendering/Systems/PrimitiveSystem.cs index b17e513..5700726 100644 --- a/Snooper/Rendering/Systems/PrimitiveSystem.cs +++ b/Snooper/Rendering/Systems/PrimitiveSystem.cs @@ -141,12 +141,7 @@ public class PrimitiveSystem(Primit { public override uint Order => 20; - protected override Action VertexLayout { get; } = vao => - { - GL.VertexArrayAttribFormat(vao, 0, 3, VertexAttribType.Float, false, 0); - GL.EnableVertexArrayAttrib(vao, 0); - GL.VertexArrayAttribBinding(vao, 0, 0); - }; + protected override Action VertexLayout { get; } = layout => layout.Float(0, 3); } public class PrimitiveSystem(PrimitiveType type = PrimitiveType.Triangles) diff --git a/Snooper/Rendering/Systems/TextRenderSystem.cs b/Snooper/Rendering/Systems/TextRenderSystem.cs index 0ba6eb5..938738c 100644 --- a/Snooper/Rendering/Systems/TextRenderSystem.cs +++ b/Snooper/Rendering/Systems/TextRenderSystem.cs @@ -20,15 +20,9 @@ public class TextRenderSystem : PrimitiveSystem VertexLayout { get; } = vao => - { - GL.VertexArrayAttribFormat(vao, 0, 2, VertexAttribType.Float, false, 0); - GL.VertexArrayAttribFormat(vao, 1, 2, VertexAttribType.Float, false, 8); - GL.EnableVertexArrayAttrib(vao, 0); - GL.EnableVertexArrayAttrib(vao, 1); - GL.VertexArrayAttribBinding(vao, 0, 0); - GL.VertexArrayAttribBinding(vao, 1, 0); - }; + protected override Action VertexLayout { get; } = layout => layout + .Float(0, 2) + .Float(1, 2, offset: 8); protected override void OnLoad() { diff --git a/Snooper/Shaders/Buffers/CommonMesh.vert b/Snooper/Shaders/Buffers/CommonMesh.vert index f759db7..166ea44 100644 --- a/Snooper/Shaders/Buffers/CommonMesh.vert +++ b/Snooper/Shaders/Buffers/CommonMesh.vert @@ -5,7 +5,7 @@ #define MESH_VERTEX_STAGE layout (location = 0) in uvec2 aPosHalf; // half2(pos.xy) | half2(pos.zw) -layout (location = 1) in uint aNormalPacked; // RGB10A2: bits 0-9=nx, 10-19=ny, 20-29=nz, 30-31=nw +layout (location = 1) in vec4 aNormal; // RGB10A2 snorm expanded by the vertex fetch, w = tangent basis sign (-1 or +1) layout (location = 2) in uint aTangentPacked; // RGB10A2: bits 0-9=tx, 10-19=ty, 20-29=tz, 30-31=texLayer layout (location = 3) in uint aTexCoordsHalf; // half2(uv.xy) packed @@ -68,11 +68,7 @@ void CommonMeshMain() MeshVertex vertex; vertex.Position = vec4(posXY, posZW); - vertex.Normal = normalize(vec4( - Unpack10Snorm(aNormalPacked), - Unpack10Snorm(aNormalPacked >> 10u), - Unpack10Snorm(aNormalPacked >> 20u), - Unpack10Snorm(aNormalPacked >> 30u))); + vertex.Normal = vec4(normalize(aNormal.xyz), aNormal.w); vertex.Tangent = normalize(vec3( Unpack10Snorm(aTangentPacked), Unpack10Snorm(aTangentPacked >> 10u), @@ -91,6 +87,7 @@ void CommonMeshMain() vec4 viewPos = uViewMatrix * matrix * vertex.Position; gl_Position = uProjectionMatrix * viewPos; + float handedness = vertex.Normal.w * sign(determinant(mat3(matrix))); mat3 nMatrix = transpose(inverse(mat3(matrix))); vec3 T = normalize(nMatrix * vertex.Tangent); vec3 N = normalize(nMatrix * vertex.Normal.xyz); @@ -99,7 +96,7 @@ void CommonMeshMain() vTexLayer = texLayer; vs_out.vViewPos = viewPos.xyz; vs_out.vTexCoords = aTexCoords; - vs_out.TBN = mat3(T, cross(N, T) * vertex.Normal.w, N); + vs_out.TBN = mat3(T, cross(N, T) * handedness, N); vec3 color = vec3(0.5); vColorMode = uFragmentColorMode != 0 ? uFragmentColorMode : uMeshDataBuffer[draw.MeshIndex].ColorMode; diff --git a/Snooper/Shaders/Hooks/skinning.glsl b/Snooper/Shaders/Hooks/skinning.glsl index 0fa6c32..dcaf386 100644 --- a/Snooper/Shaders/Hooks/skinning.glsl +++ b/Snooper/Shaders/Hooks/skinning.glsl @@ -19,9 +19,9 @@ void SkinDeformVertex(PerDrawStatic draw, PerDrawCulled culled, int instance, in vec4 pos = v.Position; vec4 uePos = vec4(0.0); #if !defined(MESH_DEPTH_ONLY) - vec4 normal = v.Normal; + vec3 normal = v.Normal.xyz; vec3 tangent = v.Tangent; - vec4 ueNormal = vec4(0.0); + vec3 ueNormal = vec3(0.0); vec3 ueTangent = vec3(0.0); #endif @@ -34,14 +34,14 @@ void SkinDeformVertex(PerDrawStatic draw, PerDrawCulled culled, int instance, in mat4 skinningMatrix = uPoseBuffer[basePose + boneIndex] * uInverseBindBuffer[baseBone + boneIndex]; uePos += skinningMatrix * pos * weight; #if !defined(MESH_DEPTH_ONLY) - ueNormal += skinningMatrix * normal * weight; + ueNormal += mat3(skinningMatrix) * normal * weight; ueTangent += mat3(skinningMatrix) * tangent * weight; #endif } v.Position = uePos; #if !defined(MESH_DEPTH_ONLY) - v.Normal = ueNormal; + v.Normal = vec4(ueNormal, v.Normal.w); v.Tangent = ueTangent; #endif } From 5ecaf1f49ffcca36bdfea5f7989c6ca00cd7f8d4 Mon Sep 17 00:00:00 2001 From: Asval Date: Mon, 7 Sep 2026 19:25:58 +0200 Subject: [PATCH 4/7] test, it killed performance --- .../Containers/Programs/EmbeddedShader.cs | 3 +- .../Core/Containers/Resources/GeometryPool.cs | 35 +++++++++++-------- Snooper/Core/Hardware/DeviceInfo.cs | 4 +++ Snooper/Shaders/Buffers/bindless.glsl | 14 ++++++++ Snooper/Shaders/Landscape/landscape.frag | 8 +++-- Snooper/Shaders/Landscape/landscape.tese | 9 ++--- Snooper/Shaders/billboard.frag | 6 ++-- Snooper/Shaders/material_sampling.glsl | 14 ++++---- 8 files changed, 63 insertions(+), 30 deletions(-) create mode 100644 Snooper/Shaders/Buffers/bindless.glsl diff --git a/Snooper/Core/Containers/Programs/EmbeddedShader.cs b/Snooper/Core/Containers/Programs/EmbeddedShader.cs index c7edf54..7a5133f 100644 --- a/Snooper/Core/Containers/Programs/EmbeddedShader.cs +++ b/Snooper/Core/Containers/Programs/EmbeddedShader.cs @@ -1,6 +1,7 @@ using System.Reflection; using OpenTK.Graphics.OpenGL4; using Snooper.Core.Containers.Buffers; +using Snooper.Core.Hardware; namespace Snooper.Core.Containers.Programs; @@ -25,7 +26,7 @@ protected override uint CompileShader(ShaderType type, string file) content = string.Join("\n", Defines.Select(d => $"#define {d}")) + "\n" + content; } - content = string.Join('\n', "#version 460 core", "", Bindings.GlslDefines, "", content); + content = string.Join('\n', "#version 460 core", "", Bindings.GlslDefines, DeviceInfo.GlslDefines, content); return base.CompileShader(type, content); } diff --git a/Snooper/Core/Containers/Resources/GeometryPool.cs b/Snooper/Core/Containers/Resources/GeometryPool.cs index ec3e5b4..78eb431 100644 --- a/Snooper/Core/Containers/Resources/GeometryPool.cs +++ b/Snooper/Core/Containers/Resources/GeometryPool.cs @@ -1,8 +1,6 @@ -using System.Numerics; -using CUE4Parse.UE4.Objects.Core.Misc; +using CUE4Parse.UE4.Objects.Core.Misc; using OpenTK.Graphics.OpenGL4; using Snooper.Core.Containers.Buffers; -using Snooper.Rendering.Components.Camera; using Snooper.Rendering.Components.Descriptors; namespace Snooper.Core.Containers.Resources; @@ -18,25 +16,34 @@ public class GeometryHandle(uint firstIndex, uint baseVertex, BufferAllocation m public int OverrideLod { get; internal set; } = overrideLod; } -public readonly struct VertexArrayLayout(uint vao, uint vbo, int stride) +public readonly struct VertexArrayLayout { - public VertexArrayLayout Float(uint location, int size, VertexAttribType type = VertexAttribType.Float, bool normalized = false, int offset = 0) + private const uint BindingIndex = 0; + + private readonly uint _vao; + + public VertexArrayLayout(uint vao, uint vbo, int stride) + { + _vao = vao; + GL.VertexArrayVertexBuffer(vao, BindingIndex, vbo, 0, stride); + } + + public VertexArrayLayout Float(uint location, int size, VertexAttribType type = VertexAttribType.Float, bool normalized = false, uint offset = 0) { - GL.VertexArrayAttribFormat(vao, location, size, type, normalized, 0); - return Enable(location, offset); + GL.VertexArrayAttribFormat(_vao, location, size, type, normalized, offset); + return Enable(location); } - public VertexArrayLayout Integer(uint location, int size, VertexAttribIType type = VertexAttribIType.UnsignedInt, int offset = 0) + public VertexArrayLayout Integer(uint location, int size, VertexAttribIType type = VertexAttribIType.UnsignedInt, uint offset = 0) { - GL.VertexArrayAttribIFormat(vao, location, size, type, 0); - return Enable(location, offset); + GL.VertexArrayAttribIFormat(_vao, location, size, type, offset); + return Enable(location); } - private VertexArrayLayout Enable(uint location, int offset) + private VertexArrayLayout Enable(uint location) { - GL.VertexArrayVertexBuffer(vao, location, vbo, offset, stride); - GL.VertexArrayAttribBinding(vao, location, location); - GL.EnableVertexArrayAttrib(vao, location); + GL.VertexArrayAttribBinding(_vao, location, BindingIndex); + GL.EnableVertexArrayAttrib(_vao, location); return this; } } diff --git a/Snooper/Core/Hardware/DeviceInfo.cs b/Snooper/Core/Hardware/DeviceInfo.cs index 0a1d71a..7346a58 100644 --- a/Snooper/Core/Hardware/DeviceInfo.cs +++ b/Snooper/Core/Hardware/DeviceInfo.cs @@ -10,6 +10,8 @@ public class DeviceInfo public ExtensionSupport ExtensionSupport { get; } = new(); public GpuMemoryInfo Memory { get; } = new(); + public static string GlslDefines { get; private set; } = string.Empty; + public void Load() { Name = GL.GetString(StringName.Renderer); @@ -17,5 +19,7 @@ public void Load() MaxShaderStorageBufferBindings = GL.GetInteger(GetPName.MaxShaderStorageBufferBindings); ExtensionSupport.Load(); Memory.Load(ExtensionSupport); + + GlslDefines = Vendor.Contains("Intel", StringComparison.OrdinalIgnoreCase) ? "#define BINDLESS_RAW_HANDLES\n" : string.Empty; } } diff --git a/Snooper/Shaders/Buffers/bindless.glsl b/Snooper/Shaders/Buffers/bindless.glsl new file mode 100644 index 0000000..9e68969 --- /dev/null +++ b/Snooper/Shaders/Buffers/bindless.glsl @@ -0,0 +1,14 @@ +// Bindless texture handle storage inside SSBO structs. +// +// TEXTURE_HANDLE is the member type, TO_SAMPLER(h) turns it into a sampler2D at the sampling site. +// Declaring the member as sampler2D lets the compiler treat the handle as dynamically uniform and keep +// it in a scalar register. Declaring it as uvec2 (BINDLESS_RAW_HANDLES, injected by DeviceInfo for +// drivers that reject opaque types in buffer blocks) forces the compiler to assume divergence and +// waterfall every texture fetch, so only enable it where the sampler2D form does not compile. +#ifdef BINDLESS_RAW_HANDLES +#define TEXTURE_HANDLE uvec2 +#define TO_SAMPLER(h) sampler2D(h) +#else +#define TEXTURE_HANDLE sampler2D +#define TO_SAMPLER(h) (h) +#endif diff --git a/Snooper/Shaders/Landscape/landscape.frag b/Snooper/Shaders/Landscape/landscape.frag index f19393d..0d2c864 100644 --- a/Snooper/Shaders/Landscape/landscape.frag +++ b/Snooper/Shaders/Landscape/landscape.frag @@ -6,13 +6,15 @@ layout (location = 2) out vec4 gColor; layout (location = 3) out vec4 gSpecular; layout (location = 4) out uint gPicking; +#include "Buffers/bindless.glsl" + struct PerMaterialData { bool IsReady; uint WeightmapCount; - uvec2 Heightmap; - uvec2 Weightmaps[4]; + TEXTURE_HANDLE Heightmap; + TEXTURE_HANDLE Weightmaps[4]; uint EnabledChannels[4]; vec2 HeightmapScaleBias; @@ -77,7 +79,7 @@ vec3 getColorFromWeightmap(PerMaterialData materialData, WeightHighlightMapping for (int i = 0; i < weightmapCount; i++) { - sampler2D weightmap = sampler2D(materialData.Weightmaps[i]); + sampler2D weightmap = TO_SAMPLER(materialData.Weightmaps[i]); vec2 weightmapSize = textureSize(weightmap, 0); vec2 texelSize = 1.0 / weightmapSize; vec2 weightmapUvSize = vec2(uSizeQuads) / weightmapSize; diff --git a/Snooper/Shaders/Landscape/landscape.tese b/Snooper/Shaders/Landscape/landscape.tese index d6b2725..8d22ada 100644 --- a/Snooper/Shaders/Landscape/landscape.tese +++ b/Snooper/Shaders/Landscape/landscape.tese @@ -4,14 +4,15 @@ layout (quads, fractional_odd_spacing, ccw) in; #include "Buffers/PerDrawData.glsl" #include "Buffers/PerInstanceData.glsl" +#include "Buffers/bindless.glsl" struct PerMaterialData { bool IsReady; uint WeightmapCount; - uvec2 Heightmap; - uvec2 Weightmaps[4]; + TEXTURE_HANDLE Heightmap; + TEXTURE_HANDLE Weightmaps[4]; uint EnabledChannels[4]; vec2 HeightmapScaleBias; @@ -88,7 +89,7 @@ void main() // pushing the vertex out of clip space when the channel value > 0.5. if (materialData.VisibilityTextureIndex != 0xFFFFFFFFu) { - sampler2D weightmap = sampler2D(materialData.Weightmaps[materialData.VisibilityTextureIndex]); + sampler2D weightmap = TO_SAMPLER(materialData.Weightmaps[materialData.VisibilityTextureIndex]); vec2 weightmapSize = textureSize(weightmap, 0); vec2 weightmapTexelSize = 1.0 / weightmapSize; @@ -107,7 +108,7 @@ void main() } } - sampler2D heightmap = sampler2D(materialData.Heightmap); + sampler2D heightmap = TO_SAMPLER(materialData.Heightmap); vec2 heightmapSize = textureSize(heightmap, 0); vec2 heightmapTexelSize = 1.0 / heightmapSize; vec2 heightmapUvSize = vec2(uSizeQuads) / heightmapSize; diff --git a/Snooper/Shaders/billboard.frag b/Snooper/Shaders/billboard.frag index ab5c47b..ade5808 100644 --- a/Snooper/Shaders/billboard.frag +++ b/Snooper/Shaders/billboard.frag @@ -2,11 +2,13 @@ layout (location = 1) out uint gPicking; +#include "Buffers/bindless.glsl" + struct PerMaterialData { bool IsReady; float OpacityMask; - uvec2 Sprite; + TEXTURE_HANDLE Sprite; }; layout(std430, binding = BINDING_MATERIAL_DATA) restrict readonly buffer PerMaterialDataBuffer @@ -30,7 +32,7 @@ void main() vec4 color = vec4(1.0); if (materialData.IsReady) { - color = texture(sampler2D(materialData.Sprite), vTexCoords); + color = texture(TO_SAMPLER(materialData.Sprite), vTexCoords); if (color.a < materialData.OpacityMask) { discard; diff --git a/Snooper/Shaders/material_sampling.glsl b/Snooper/Shaders/material_sampling.glsl index ec4a7a7..4e32d62 100644 --- a/Snooper/Shaders/material_sampling.glsl +++ b/Snooper/Shaders/material_sampling.glsl @@ -1,6 +1,8 @@ // Material sampling utilities for multi-layer materials // Shared between geometry.frag and mesh.frag +#include "Buffers/bindless.glsl" + struct PerMaterialData { bool IsReady; @@ -9,9 +11,9 @@ struct PerMaterialData uint LayerTextureFlags; // Fixed arrays for up to 4 layers - uvec2 Diffuse[4]; - uvec2 Normal[4]; - uvec2 Specular[4]; + TEXTURE_HANDLE Diffuse[4]; + TEXTURE_HANDLE Normal[4]; + TEXTURE_HANDLE Specular[4]; // Per-layer material properties // Roughness: 2 floats per layer (min, max) * 4 layers = 8 floats @@ -54,7 +56,7 @@ vec4 SampleLayerDiffuse(PerMaterialData materialData, uint layer, vec2 uv) if (HasLayerTexture(materialData, layer, 0u)) { - return texture(sampler2D(materialData.Diffuse[layer]), uv); + return texture(TO_SAMPLER(materialData.Diffuse[layer]), uv); } return vec4(1.0); @@ -68,7 +70,7 @@ vec3 SampleLayerNormal(PerMaterialData materialData, uint layer, vec2 uv) if (HasLayerTexture(materialData, layer, 1u)) { - vec2 xy = texture(sampler2D(materialData.Normal[layer]), uv).rg * 2.0 - 1.0; + vec2 xy = texture(TO_SAMPLER(materialData.Normal[layer]), uv).rg * 2.0 - 1.0; float z = sqrt(max(0.0, 1.0 - dot(xy, xy))); return normalize(vec3(xy, z)); } @@ -84,7 +86,7 @@ vec3 SampleLayerSpecular(PerMaterialData materialData, uint layer, vec2 uv) if (HasLayerTexture(materialData, layer, 2u)) { - vec3 spec = texture(sampler2D(materialData.Specular[layer]), uv).rgb; + vec3 spec = texture(TO_SAMPLER(materialData.Specular[layer]), uv).rgb; vec2 roughness = GetLayerRoughness(materialData, layer); spec.b = mix(roughness.x, roughness.y, spec.b); return spec; From 7ad6efc88b67a031041379a220f152a10f62cc33 Mon Sep 17 00:00:00 2001 From: Asval Date: Mon, 7 Sep 2026 19:27:48 +0200 Subject: [PATCH 5/7] and try this too --- .../Core/Containers/Resources/GeometryPool.cs | 32 +++++++++++++------ Snooper/Core/Hardware/DeviceInfo.cs | 4 ++- 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/Snooper/Core/Containers/Resources/GeometryPool.cs b/Snooper/Core/Containers/Resources/GeometryPool.cs index 78eb431..2757b8a 100644 --- a/Snooper/Core/Containers/Resources/GeometryPool.cs +++ b/Snooper/Core/Containers/Resources/GeometryPool.cs @@ -1,6 +1,7 @@ using CUE4Parse.UE4.Objects.Core.Misc; using OpenTK.Graphics.OpenGL4; using Snooper.Core.Containers.Buffers; +using Snooper.Core.Hardware; using Snooper.Rendering.Components.Descriptors; namespace Snooper.Core.Containers.Resources; @@ -18,31 +19,44 @@ public class GeometryHandle(uint firstIndex, uint baseVertex, BufferAllocation m public readonly struct VertexArrayLayout { - private const uint BindingIndex = 0; - private readonly uint _vao; + private readonly uint _vbo; + private readonly int _stride; public VertexArrayLayout(uint vao, uint vbo, int stride) { _vao = vao; - GL.VertexArrayVertexBuffer(vao, BindingIndex, vbo, 0, stride); + _vbo = vbo; + _stride = stride; + + if (!DeviceInfo.IsIntel) + { + GL.VertexArrayVertexBuffer(vao, 0, vbo, 0, stride); + } } public VertexArrayLayout Float(uint location, int size, VertexAttribType type = VertexAttribType.Float, bool normalized = false, uint offset = 0) { - GL.VertexArrayAttribFormat(_vao, location, size, type, normalized, offset); - return Enable(location); + GL.VertexArrayAttribFormat(_vao, location, size, type, normalized, DeviceInfo.IsIntel ? 0 : offset); + return Enable(location, offset); } public VertexArrayLayout Integer(uint location, int size, VertexAttribIType type = VertexAttribIType.UnsignedInt, uint offset = 0) { - GL.VertexArrayAttribIFormat(_vao, location, size, type, offset); - return Enable(location); + GL.VertexArrayAttribIFormat(_vao, location, size, type, DeviceInfo.IsIntel ? 0 : offset); + return Enable(location, offset); } - private VertexArrayLayout Enable(uint location) + private VertexArrayLayout Enable(uint location, uint offset) { - GL.VertexArrayAttribBinding(_vao, location, BindingIndex); + var binding = 0u; + if (DeviceInfo.IsIntel) + { + binding = location; + GL.VertexArrayVertexBuffer(_vao, binding, _vbo, (nint)offset, _stride); + } + + GL.VertexArrayAttribBinding(_vao, location, binding); GL.EnableVertexArrayAttrib(_vao, location); return this; } diff --git a/Snooper/Core/Hardware/DeviceInfo.cs b/Snooper/Core/Hardware/DeviceInfo.cs index 7346a58..52ed2a8 100644 --- a/Snooper/Core/Hardware/DeviceInfo.cs +++ b/Snooper/Core/Hardware/DeviceInfo.cs @@ -10,6 +10,7 @@ public class DeviceInfo public ExtensionSupport ExtensionSupport { get; } = new(); public GpuMemoryInfo Memory { get; } = new(); + public static bool IsIntel { get; private set; } public static string GlslDefines { get; private set; } = string.Empty; public void Load() @@ -20,6 +21,7 @@ public void Load() ExtensionSupport.Load(); Memory.Load(ExtensionSupport); - GlslDefines = Vendor.Contains("Intel", StringComparison.OrdinalIgnoreCase) ? "#define BINDLESS_RAW_HANDLES\n" : string.Empty; + IsIntel = Vendor.Contains("Intel", StringComparison.OrdinalIgnoreCase); + GlslDefines = IsIntel ? "#define BINDLESS_RAW_HANDLES\n" : string.Empty; } } From 2c981b5d5d3a4d94b52f73fead78c8e5aaf80c08 Mon Sep 17 00:00:00 2001 From: Asval Date: Mon, 7 Sep 2026 20:02:37 +0200 Subject: [PATCH 6/7] fix light crash --- Snooper/Core/Containers/Buffers/ShaderStorageBuffer.cs | 7 +++++-- Snooper/Core/Managers/ActorManager.cs | 9 +++++++++ Snooper/Rendering/Managers/PostProcessor.cs | 4 ++-- Snooper/Rendering/Systems/ClusteredLightSystem.cs | 5 +++-- 4 files changed, 19 insertions(+), 6 deletions(-) diff --git a/Snooper/Core/Containers/Buffers/ShaderStorageBuffer.cs b/Snooper/Core/Containers/Buffers/ShaderStorageBuffer.cs index a8c9a42..fb9d028 100644 --- a/Snooper/Core/Containers/Buffers/ShaderStorageBuffer.cs +++ b/Snooper/Core/Containers/Buffers/ShaderStorageBuffer.cs @@ -11,8 +11,11 @@ public sealed class ShaderStorageBuffer(BufferUsageHint usageHint = BufferUsa public void Bind(uint index) { - if (!IsAllocated) return; // iGPUs don't like binding unallocated buffers - GL.BindBufferBase(BufferRangeTarget.ShaderStorageBuffer, index, Handle); + // iGPUs don't like binding unallocated buffers, and unlike some other buffers, we never unbind SSBOs + // silently skipping the bind leaves whatever another system bound at this index in place + // that's too risky, so we unbind the slot instead so an unallocated buffer reads as out of range + // TODO: we should properly unbind SSBOs, and skip if unallocated + GL.BindBufferBase(BufferRangeTarget.ShaderStorageBuffer, index, IsAllocated ? Handle : 0); } public void QueueUpdate(BufferAllocation allocation, T data) => _batcher.Add(allocation, data); diff --git a/Snooper/Core/Managers/ActorManager.cs b/Snooper/Core/Managers/ActorManager.cs index b0ee4d2..a1c2405 100644 --- a/Snooper/Core/Managers/ActorManager.cs +++ b/Snooper/Core/Managers/ActorManager.cs @@ -191,6 +191,10 @@ private void DequeueSystems(int limit = 0) Systems.Add(system.Order, system); system.Load(); + + if (system is IResizable resizable) + resizable.Resize(_width, _height); // resize right away for the screen-sized resources to get allocated (ClusteredLightSystem) + count++; } } @@ -232,8 +236,13 @@ private void TrackBackgroundWork() } } + private int _width; + private int _height; public virtual void Resize(int newWidth, int newHeight) { + _width = newWidth; + _height = newHeight; + foreach (var system in Systems.Values.OfType()) system.Resize(newWidth, newHeight); } diff --git a/Snooper/Rendering/Managers/PostProcessor.cs b/Snooper/Rendering/Managers/PostProcessor.cs index 4c0b14a..9b8f158 100644 --- a/Snooper/Rendering/Managers/PostProcessor.cs +++ b/Snooper/Rendering/Managers/PostProcessor.cs @@ -95,7 +95,7 @@ public override void Generate() shader.SetUniform("ssao", 4); } - if (ctx.LightSystem is { IsEnabled: true } system) + if (ctx.LightSystem is { IsEnabled: true, HasClusters: true } system) { system.BindForRendering(); shader.SetUniform("useLighting", true); @@ -191,7 +191,7 @@ public override void Generate() shader.SetUniform("uShowGrid", ctx.ShowGrid); shader.SetUniform("uMaxLightsPerCluster", ClusteredLightSystem.MaxLightsPerClusterLimit); - if (ctx.LightSystem is { IsEnabled: true } system) + if (ctx.LightSystem is { IsEnabled: true, HasClusters: true } system) { system.BindForRendering(); shader.SetUniform("uHasLights", true); diff --git a/Snooper/Rendering/Systems/ClusteredLightSystem.cs b/Snooper/Rendering/Systems/ClusteredLightSystem.cs index 95be613..a5f5f71 100644 --- a/Snooper/Rendering/Systems/ClusteredLightSystem.cs +++ b/Snooper/Rendering/Systems/ClusteredLightSystem.cs @@ -91,6 +91,7 @@ internal abstract class LightBindings : Bindings public int GridDimensionX { get; private set; } public int GridDimensionY { get; private set; } public int GridDimensionZ => 16; + public bool HasClusters => _numClusters > 0; private int _numClusters; private int _numWorkGroups; @@ -161,7 +162,7 @@ private bool ConsumeClustersDirty(CameraComponent camera) private void BuildClusters(CameraComponent camera) { - if (_numClusters == 0) return; + if (!HasClusters) return; _clusterBuildProgram.Use(); _clusterBuildProgram.SetUniform("uScreenWidth", _screenWidth); @@ -183,7 +184,7 @@ private void BuildClusters(CameraComponent camera) private void CullLights(CameraComponent camera) { - if (_numClusters == 0 || _lightDataBuffer.Count == 0) + if (!HasClusters || _lightDataBuffer.Count == 0) { return; } From 8f0ff3b0ee517bd5ac8adfc16fe7b5da99b80808 Mon Sep 17 00:00:00 2001 From: Asval Date: Thu, 10 Sep 2026 19:09:58 +0200 Subject: [PATCH 7/7] I don't like that but there's no other way --- Snooper/Rendering/Systems/ClusteredLightSystem.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Snooper/Rendering/Systems/ClusteredLightSystem.cs b/Snooper/Rendering/Systems/ClusteredLightSystem.cs index a5f5f71..7b255d7 100644 --- a/Snooper/Rendering/Systems/ClusteredLightSystem.cs +++ b/Snooper/Rendering/Systems/ClusteredLightSystem.cs @@ -256,6 +256,7 @@ public void Resize(int newWidth, int newHeight) GridDimensionY = (_screenHeight + TileSize - 1) / TileSize; _numClusters = GridDimensionX * GridDimensionY * GridDimensionZ; _numWorkGroups = (_numClusters + WorkGroupSize - 1) / WorkGroupSize; + if (!HasClusters) return; _clusterAABBBuffer.Reallocate(_numClusters); _clusterDataBuffer.Reallocate(_numClusters);