From f63a0c3cc827f4473c8a96c44a6fc6ae688cb722 Mon Sep 17 00:00:00 2001 From: Nick Date: Wed, 9 Sep 2026 08:22:18 -0400 Subject: [PATCH 01/49] test 2d texture array --- .../Renderers/Legacy/Hud/LegacyHudShader.cs | 4 +- .../Legacy/World/Entities/EntityProgram.cs | 6 +-- .../Portals/FloodFill/FloodFillProgram.cs | 4 +- .../Legacy/World/InterpolationShader.cs | 4 +- .../Legacy/World/Shader/FragFunction.cs | 2 +- .../Legacy/World/Shader/PlaneClip.cs | 6 +-- .../Legacy/World/Shader/VertexFunction.cs | 2 +- .../Sky/Sphere/SkySphereForegroundShader.cs | 4 +- .../World/Sky/Sphere/SkySphereShader.cs | 4 +- .../Renderers/Legacy/World/StaticShader.cs | 4 +- .../OpenGL/Texture/Legacy/GLLegacyTexture.cs | 2 +- .../Texture/Legacy/LegacyGLTextureManager.cs | 43 +++++++++++++++---- 12 files changed, 55 insertions(+), 30 deletions(-) diff --git a/Core/Render/OpenGL/Renderers/Legacy/Hud/LegacyHudShader.cs b/Core/Render/OpenGL/Renderers/Legacy/Hud/LegacyHudShader.cs index 1140b08cd..1197a1a98 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/Hud/LegacyHudShader.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/Hud/LegacyHudShader.cs @@ -129,7 +129,7 @@ private static string GetBrightmapColorBlend() out vec4 fragColor; - uniform sampler2D boundTexture; + uniform sampler2DArray boundTexture; uniform sampler2D opaqueTexture; uniform sampler2D brightmapTexture; uniform samplerBuffer colormapTexture; @@ -148,7 +148,7 @@ private static string GetBrightmapColorBlend() ${FuzzFunction} void main() { - fragColor = texture(boundTexture, uvFrag.st); + fragColor = texture(boundTexture, vec3(uvFrag.st, 0)); ${ColorMapFetch} ${AlphaFlag} fragColor.w *= alphaFrag; diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityProgram.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityProgram.cs index 84cdc0da8..755c3c6f5 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityProgram.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityProgram.cs @@ -96,7 +96,7 @@ protected override string VertexShader() => @" uniform float timeFrac; uniform int useSectorColor; uniform int useSectorFog; - uniform sampler2D boundTexture; + uniform sampler2DArray boundTexture; uniform samplerBuffer sectorColormapTexture; uniform samplerBuffer sectorFogTexture; @@ -126,7 +126,7 @@ void main() float offsetZSign = float(((intOptions >> 30) & 1) > 0); offsetXYOption = mix(offsetXYOption, -offsetXYOption, offsetXYSign); offsetZ = mix(offsetZ, -offsetZ, offsetZSign); - ivec2 textureDim = textureSize(boundTexture, 0); + ivec2 textureDim = textureSize(boundTexture, 0).xy; textureWidthFrag = textureDim.x; ${SectorColorMapVertexFunction} @@ -232,7 +232,7 @@ private static string AdjustSpriteVertexClip() uniform int hasInvulnerability; uniform float fuzzFrac; - uniform sampler2D boundTexture; + uniform sampler2DArray boundTexture; uniform sampler2D brightmapTexture; uniform samplerBuffer colormapTexture; uniform float lightLevelMix; diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Portals/FloodFill/FloodFillProgram.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Portals/FloodFill/FloodFillProgram.cs index 146b9a661..2da748d9c 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Portals/FloodFill/FloodFillProgram.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Portals/FloodFill/FloodFillProgram.cs @@ -106,7 +106,7 @@ protected override string FragmentShader() out vec4 fragColor; - uniform sampler2D boundTexture; + uniform sampler2DArray boundTexture; uniform sampler2D brightmapTexture; uniform vec3 camera; uniform mat4 mvpNoPitch; @@ -128,7 +128,7 @@ void main() vec3 lookDir = normalize(vertexPosFrag - camera); float planeDot = dot(pointOnPlane - camera, planeNormal) / dot(lookDir, planeNormal); vec3 planePos = camera + (lookDir * planeDot); - vec2 texDim = textureSize(boundTexture, 0); + vec2 texDim = textureSize(boundTexture, 0).xy; vec2 uvFrag = vec2(planePos.x / texDim.x, planePos.y / texDim.y); uvFrag.y = -uvFrag.y; // Vanilla textures are drawn top-down. diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/InterpolationShader.cs b/Core/Render/OpenGL/Renderers/Legacy/World/InterpolationShader.cs index 138d87fcb..9935d7f82 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/InterpolationShader.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/InterpolationShader.cs @@ -50,7 +50,7 @@ protected static string VertexShaderInternal(bool planeClip) => @" uniform mat4 mvp; uniform float timeFrac; uniform int vertexGapClampUV; - uniform sampler2D boundTexture; + uniform sampler2DArray boundTexture; uniform int useSectorColor; uniform int useSectorFog; @@ -125,7 +125,7 @@ protected override string FragmentShader() ${OutTargets} uniform int hasInvulnerability; - uniform sampler2D boundTexture; + uniform sampler2DArray boundTexture; uniform sampler2D brightmapTexture; uniform vec3 colorMix; uniform int paletteIndex; diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Shader/FragFunction.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Shader/FragFunction.cs index 8c52bdf80..7cdbc6d63 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Shader/FragFunction.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Shader/FragFunction.cs @@ -186,7 +186,7 @@ public static string FragColorFunction(FragColorFunctionOptions options, ColorMa var fragColor = @$" {textureMappingClamp} - {declareFragColor} = texture(boundTexture, texUV);"; + {declareFragColor} = texture(boundTexture, vec3(texUV, 0));"; return fragColor + diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Shader/PlaneClip.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Shader/PlaneClip.cs index 8d6c78eef..70253e835 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Shader/PlaneClip.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Shader/PlaneClip.cs @@ -30,7 +30,7 @@ public static string WritePlaneFragFunction(PlaneClipFragOptions options) in float depthFrag; ${{InVars}} - uniform sampler2D boundTexture; + uniform sampler2DArray boundTexture; layout (location = 0) out vec3 outPlane; @@ -56,7 +56,7 @@ public static string WriteWallFragFunction(WallClipFragOptions options) in float depthFrag; ${InVars} - uniform sampler2D boundTexture; + uniform sampler2DArray boundTexture; layout (location = 0) out vec4 outPlane; @@ -109,7 +109,7 @@ private static string GetAlphaSample(bool alphaSample) if (!alphaSample) return ""; - return @"float alpha = texture(boundTexture, uvFrag.xy).a; + return @"float alpha = texture(boundTexture, vec3(uvFrag.xy, 0)).a; if (alpha <= 0) discard;"; } diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Shader/VertexFunction.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Shader/VertexFunction.cs index 0983b268e..790e51dad 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Shader/VertexFunction.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Shader/VertexFunction.cs @@ -18,7 +18,7 @@ public static class VertexFunction uvClampMaxFrag = vec2(MaxValue, MaxValue); if (vertexGapClampUV == 1) { - ivec2 texSize = textureSize(boundTexture, 0); + ivec2 texSize = textureSize(boundTexture, 0).xy; const float VertexGapX = 0.1; float pixelSize = uvFrag.y * texSize.y; // Push y further since it's more likely to show t-junction issue with subsector flat splits diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Sky/Sphere/SkySphereForegroundShader.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Sky/Sphere/SkySphereForegroundShader.cs index 97955510c..369c82b08 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Sky/Sphere/SkySphereForegroundShader.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Sky/Sphere/SkySphereForegroundShader.cs @@ -36,7 +36,7 @@ protected override string FragmentShader() => @" out vec4 fragColor; uniform vec2 scale; - uniform sampler2D boundTexture; + uniform sampler2DArray boundTexture; uniform samplerBuffer colormapTexture; uniform int hasInvulnerability; uniform int paletteIndex; @@ -78,7 +78,7 @@ void main() { else { vec2 textureUV = uvFrag - skyMin; vec2 offset = mix(prevScrollOffset, scrollOffset, timeFrac); - fragColor = texture(boundTexture, textureUV / scale + offset); + fragColor = texture(boundTexture, vec3(textureUV / scale + offset, 0)); } if (fragColor.a == 0) diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Sky/Sphere/SkySphereShader.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Sky/Sphere/SkySphereShader.cs index c88cce514..13addb6ce 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Sky/Sphere/SkySphereShader.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Sky/Sphere/SkySphereShader.cs @@ -108,7 +108,7 @@ protected override string FragmentShader() => @" out vec4 fragColor; uniform vec2 scale; - uniform sampler2D boundTexture; + uniform sampler2DArray boundTexture; uniform samplerBuffer colormapTexture; uniform int hasInvulnerability; uniform int paletteIndex; @@ -144,7 +144,7 @@ void main() { else { vec2 textureUV = uvFrag - skyMin; vec2 offset = mix(prevScrollOffset, scrollOffset, timeFrac); - fragColor = texture(boundTexture, textureUV / scale + offset); + fragColor = texture(boundTexture, vec3(textureUV / scale + offset, 0)); } ${ColorMapFetch} diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/StaticShader.cs b/Core/Render/OpenGL/Renderers/Legacy/World/StaticShader.cs index f2af69166..00c66e94f 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/StaticShader.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/StaticShader.cs @@ -43,7 +43,7 @@ private static string VertexShaderInternal(bool planeClip) => @" uniform mat4 mvp; uniform float timeFrac; uniform int vertexGapClampUV; - uniform sampler2D boundTexture; + uniform sampler2DArray boundTexture; uniform int useSectorColor; uniform int useSectorFog; @@ -116,7 +116,7 @@ protected override string FragmentShader() ${OutTargets} uniform int hasInvulnerability; - uniform sampler2D boundTexture; + uniform sampler2DArray boundTexture; uniform sampler2D brightmapTexture; uniform vec3 colorMix; uniform int paletteIndex; diff --git a/Core/Render/OpenGL/Texture/Legacy/GLLegacyTexture.cs b/Core/Render/OpenGL/Texture/Legacy/GLLegacyTexture.cs index 7e8299f46..4c0ea711e 100644 --- a/Core/Render/OpenGL/Texture/Legacy/GLLegacyTexture.cs +++ b/Core/Render/OpenGL/Texture/Legacy/GLLegacyTexture.cs @@ -11,7 +11,7 @@ public class GLLegacyTexture : GLTexture public GLLegacyTexture(int textureId, string name, Dimension dimension, Vec2I offset, ResourceNamespace ns, TextureTarget target, int transparentPixelCount, int blankRowsFromTop = 0, int blankRowsFromBottom = 0) - : base(textureId, name, dimension, offset, ns, target, transparentPixelCount, blankRowsFromTop, blankRowsFromBottom) + : base(textureId, name, dimension, offset, ns, TextureTarget.Texture2DArray, transparentPixelCount, blankRowsFromTop, blankRowsFromBottom) { } diff --git a/Core/Render/OpenGL/Texture/Legacy/LegacyGLTextureManager.cs b/Core/Render/OpenGL/Texture/Legacy/LegacyGLTextureManager.cs index a61a53bae..88a85c045 100644 --- a/Core/Render/OpenGL/Texture/Legacy/LegacyGLTextureManager.cs +++ b/Core/Render/OpenGL/Texture/Legacy/LegacyGLTextureManager.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using Helion.Geometry; using Helion.Graphics; using Helion.Graphics.Fonts; using Helion.Render.Common.Textures; @@ -11,7 +10,10 @@ using Helion.Resources.Archives.Collection; using Helion.Util.Configs; using Helion.Util.Extensions; +using Helion.World.Entities.Players; using OpenTK.Graphics.OpenGL; +using System; +using System.Collections.Generic; using static Helion.Util.Assertion.Assert; namespace Helion.Render.OpenGL.Texture.Legacy; @@ -108,18 +110,41 @@ public unsafe void UploadAndSetParameters(GLLegacyTexture texture, Image image, if (GLInfo.DebugLabel) GLHelper.ObjectLabel(ObjectLabelIdentifier.Texture, texture.TextureId, $"Texture: {name} ({flags})"); - fixed (uint* pixelPtr = image.GetGlTexturePixels(ShaderVars.PaletteColorMode)) { IntPtr ptr = new(pixelPtr); - // Because the C# image format is 'ARGB', we can get it into the - // RGBA format by doing a BGRA format and then reversing it. - GL.TexImage2D(texture.Target, 0, PixelInternalFormat.Rgba8, image.Width, image.Height, 0, - PixelFormat.Bgra, PixelType.UnsignedInt8888Reversed, ptr); + if (texture.Target == TextureTarget.Texture2DArray) + { + GL.TexImage3D(TextureTarget.Texture2DArray, 0, PixelInternalFormat.Rgba8, image.Width, image.Height, 1, 0, + PixelFormat.Bgra, PixelType.UnsignedInt8888Reversed, IntPtr.Zero); + + // TODO this is forcing everything to be a 2DArray with one layer. + int layer = 0; + GL.TexSubImage3D( + texture.Target, + level: 0, + xoffset: 0, + yoffset: 0, + zoffset: 0, + image.Width, + image.Height, + depth: 1, + PixelFormat.Bgra, + PixelType.UnsignedInt8888Reversed, + ptr // pointer or byte[] for this layer + ); + } + else + { + // Because the C# image format is 'ARGB', we can get it into the + // RGBA format by doing a BGRA format and then reversing it. + GL.TexImage2D(texture.Target, 0, PixelInternalFormat.Rgba8, image.Width, image.Height, 0, + PixelFormat.Bgra, PixelType.UnsignedInt8888Reversed, ptr); + } } - GL.GenerateMipmap(GenerateMipmapTarget.Texture2D); - SetTextureParameters(TextureTarget.Texture2D, resourceNamespace, flags); + GL.GenerateMipmap(texture.Target == TextureTarget.Texture2DArray ? GenerateMipmapTarget.Texture2DArray : GenerateMipmapTarget.Texture2D); + SetTextureParameters(texture.Target, resourceNamespace, flags); GL.BindTexture(texture.Target, 0); texture.Flags = flags; From 8a432e89b7c3545e0624fdb9e2c1244471e6fa2d Mon Sep 17 00:00:00 2001 From: Nick Date: Wed, 9 Sep 2026 10:09:27 -0400 Subject: [PATCH 02/49] working test with flats --- .../Legacy/World/Entities/EntityProgram.cs | 2 + .../Legacy/World/Geometry/GeometryRenderer.cs | 4 +- .../Portals/FloodFill/FloodFillProgram.cs | 2 + .../World/Geometry/Static/RenderGeometry.cs | 22 ++++++- .../Static/StaticCacheGeometryRenderer.cs | 45 +++++++++---- .../Legacy/World/IRenderTextureArray.cs | 6 ++ .../Legacy/World/InterpolationShader.cs | 2 + .../Legacy/World/LegacyWorldRenderer.cs | 6 +- .../Legacy/World/RenderProgramBase.cs | 8 ++- .../Legacy/World/Shader/FragFunction.cs | 2 +- .../Legacy/World/Shader/PlaneClip.cs | 4 +- .../Renderers/Legacy/World/StaticShader.cs | 2 + .../Render/OpenGL/Texture/GLTextureManager.cs | 32 +++++++++ .../OpenGL/Texture/Legacy/GLLegacyTexture.cs | 3 + .../Texture/Legacy/LegacyGLTextureManager.cs | 66 ++++++++++++++++++- 15 files changed, 182 insertions(+), 24 deletions(-) create mode 100644 Core/Render/OpenGL/Renderers/Legacy/World/IRenderTextureArray.cs diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityProgram.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityProgram.cs index 755c3c6f5..479e24584 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityProgram.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityProgram.cs @@ -96,6 +96,7 @@ protected override string VertexShader() => @" uniform float timeFrac; uniform int useSectorColor; uniform int useSectorFog; + uniform int boundTextureIndex; uniform sampler2DArray boundTexture; uniform samplerBuffer sectorColormapTexture; uniform samplerBuffer sectorFogTexture; @@ -258,6 +259,7 @@ private static string AdjustSpriteVertexClip() uniform vec2 downScaleSampleFactor; uniform float colorClamp; uniform int useSectorFog; + uniform int boundTextureIndex; uniform sampler2D planeClipTexture; uniform sampler2D wallClipTexture; diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/GeometryRenderer.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/GeometryRenderer.cs index e4d767f5b..403bada14 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/GeometryRenderer.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/GeometryRenderer.cs @@ -370,8 +370,8 @@ public void ClearBsp() public void RenderStaticGeometryWalls() => m_staticCacheGeometryRenderer.RenderWalls(); - public void RenderStaticGeometryFlats() => - m_staticCacheGeometryRenderer.RenderFlats(); + public void RenderStaticGeometryFlats(IRenderTextureArray renderTextureArray) => + m_staticCacheGeometryRenderer.RenderFlats(renderTextureArray); public void RenderStaticCoverWalls() => m_staticCacheGeometryRenderer.RenderCoverWalls(); diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Portals/FloodFill/FloodFillProgram.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Portals/FloodFill/FloodFillProgram.cs index 2da748d9c..b99c391dc 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Portals/FloodFill/FloodFillProgram.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Portals/FloodFill/FloodFillProgram.cs @@ -54,6 +54,7 @@ protected override string VertexShader() => @" uniform float timeFrac; uniform int useSectorColor; uniform int useSectorFog; + uniform int boundTextureIndex; void main() { @@ -117,6 +118,7 @@ protected override string FragmentShader() uniform int colormapIndex; uniform int useBrightmaps; uniform int useSectorFog; + uniform int boundTextureIndex; ${LightLevelFragVariables} ${SectorColorMapFragVariables} diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/RenderGeometry.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/RenderGeometry.cs index df81389d6..8cb5182f8 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/RenderGeometry.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/RenderGeometry.cs @@ -1,5 +1,5 @@ using Helion.Util.Container; -using Helion.World.Geometry; +using System; using System.Collections.Generic; namespace Helion.Render.OpenGL.Renderers.Legacy.World.Geometry.Static; @@ -7,10 +7,28 @@ namespace Helion.Render.OpenGL.Renderers.Legacy.World.Geometry.Static; public class RenderGeometry { private readonly GeometryTypeLookup> m_lookup = new(() => new List()); + private readonly Comparison m_sortByTextureArray; + + public RenderGeometry() + { + m_sortByTextureArray = SortByTextureArray; + } public void AddGeometry(GeometryType type, GeometryData data) { - m_lookup.Get(type).Add(data); + var geometry = m_lookup.Get(type); + geometry.Add(data); + geometry.Sort(m_sortByTextureArray); + } + + private static int SortByTextureArray(GeometryData x, GeometryData y) + { + if (x.Texture.IsArray && !y.Texture.IsArray) + return 0; + if (!x.Texture.IsArray && y.Texture.IsArray) + return 1; + + return x.Texture.Dimension.Height.CompareTo(y.Texture.Dimension.Height); } public List GetGeometry(GeometryType type) diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/StaticCacheGeometryRenderer.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/StaticCacheGeometryRenderer.cs index 82ae4bde2..a353ec1f1 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/StaticCacheGeometryRenderer.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/StaticCacheGeometryRenderer.cs @@ -130,6 +130,8 @@ public void UpdateTo(IWorld world) AddSectorPlane(sector, SectorPlaneFace.Ceiling, false); } + BuildTextureArray(world); + if (WorldStatic.Sector3D) { for (int i = 0; i < world.Sectors.Count; i++) @@ -166,6 +168,19 @@ public void UpdateTo(IWorld world) m_worldReload = false; } + private void BuildTextureArray(IWorld world) + { + // This is currently a test that assumes 64x64 flats + var textures = new HashSet(); + foreach (var sector in world.Sectors) + { + textures.Add(sector.Floor.TextureHandle); + textures.Add(sector.Ceiling.TextureHandle); + } + + m_textureManager.CreateTextureArray(textures.ToArray(), true); + } + private void World_SectorFogColorChanged(object? sender, SectorFogEvent e) { if ((e.PreviousColor.Uint == 0 && e.Sector.FogColor.Uint == 0) || (e.PreviousColor.Uint != 0 && e.Sector.FogColor.Uint != 0)) @@ -738,12 +753,6 @@ private static void ClearVbo(VertexBufferObject? vbo) where T : struct vbo.Data.Clear(); } - private static void ClearBufferData(DynamicArray?> bufferData) - { - for (int i = 0; i < bufferData.Capacity; i++) - bufferData.Data[i]?.FlushStruct(); - } - private void AddSectorPlane(Sector sectorForSubsectors, SectorPlaneFace face, bool floor, bool update = false, Sector? renderSector = null, Sector? lightLevelSector = null, SectorPlane? geometryPlane = null, bool allowAlpha = false, Sector3D? sector3D = null) { @@ -809,8 +818,8 @@ public void RenderTwoSidedMiddleWalls() => public void RenderMiddle3D() => RenderGeometry(m_geometry.GetGeometry(GeometryType.Middle3D)); - public void RenderFlats() => - RenderGeometry(m_geometry.GetGeometry(GeometryType.Flat)); + public void RenderFlats(IRenderTextureArray renderTextureArray) => + RenderGeometry(m_geometry.GetGeometry(GeometryType.Flat), renderTextureArray); public void Render(GeometryType type) => RenderGeometry(m_geometry.GetGeometry(type)); @@ -845,25 +854,39 @@ private static void RenderCoverInternal(GeometryData? data) data.Pipeline.DrawArrays(); } - private void RenderGeometry(List geometry) + private void RenderGeometry(List geometry, IRenderTextureArray? renderTextureArray = null) { if (geometry.Count == 0) return; + GLLegacyTexture? lastTexture = null; + for (int i = 0; i < geometry.Count; i++) { var data = geometry[i]; if (data.Pipeline.Vbo.Count == 0) continue; - GL.ActiveTexture(BindTextures.BoundTexture); bool isNullCompatTex = data.TextureHandle <= Constants.NullCompatibilityTextureIndex; bool repeatY = (data.Texture.Flags & TextureFlags.ClampY) == 0; // Special case for one-sided walls with no texture. Uses black texture to block rendering so use directly. var texture = isNullCompatTex ? data.Texture : m_textureManager.GetTexture(data.TextureHandle, repeatY); - texture.Bind(); + + if (renderTextureArray != null && texture.IsArray && texture.ParentArrayTexture != null) + { + renderTextureArray.SetRenderTextureArray(texture.ArrayIndex); + texture = texture.ParentArrayTexture; + } + + if (lastTexture != texture) + { + GL.ActiveTexture(BindTextures.BoundTexture); + texture.Bind(); + } + + lastTexture = texture; var brightmapTexture = isNullCompatTex ? null diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/IRenderTextureArray.cs b/Core/Render/OpenGL/Renderers/Legacy/World/IRenderTextureArray.cs new file mode 100644 index 000000000..b32da9c2a --- /dev/null +++ b/Core/Render/OpenGL/Renderers/Legacy/World/IRenderTextureArray.cs @@ -0,0 +1,6 @@ +namespace Helion.Render.OpenGL.Renderers.Legacy.World; + +public interface IRenderTextureArray +{ + public void SetRenderTextureArray(int index); +} diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/InterpolationShader.cs b/Core/Render/OpenGL/Renderers/Legacy/World/InterpolationShader.cs index 9935d7f82..2d321bc3e 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/InterpolationShader.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/InterpolationShader.cs @@ -53,6 +53,7 @@ protected static string VertexShaderInternal(bool planeClip) => @" uniform sampler2DArray boundTexture; uniform int useSectorColor; uniform int useSectorFog; + uniform int boundTextureIndex; void main() { ${VertexOptionsSet} @@ -137,6 +138,7 @@ protected override string FragmentShader() uniform float downScaleAmount; uniform ivec2 screenBounds; uniform int useSectorFog; + uniform int boundTextureIndex; ${LightLevelFragVariables} ${SectorColorMapFragVariables} diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/LegacyWorldRenderer.cs b/Core/Render/OpenGL/Renderers/Legacy/World/LegacyWorldRenderer.cs index c41f8dded..37711a446 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/LegacyWorldRenderer.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/LegacyWorldRenderer.cs @@ -359,7 +359,7 @@ protected override void PerformRender(IWorld world, RenderInfo renderInfo, GLFra m_staticProgram.VertexGapClampUV(m_pixelGapCorrection); m_geometryRenderer.RenderStaticGeometryWalls(); m_staticProgram.VertexGapClampUV(false); - m_geometryRenderer.RenderStaticGeometryFlats(); + m_geometryRenderer.RenderStaticGeometryFlats(m_staticProgram); } RenderTwoSidedMiddleWalls(renderInfo); @@ -392,7 +392,7 @@ protected override void PerformRender(IWorld world, RenderInfo renderInfo, GLFra if (m_downscaleVanillaBuffer) { m_staticProgram.VertexGapClampUV(false); - m_geometryRenderer.RenderStaticGeometryFlats(); + m_geometryRenderer.RenderStaticGeometryFlats(m_staticProgram); } } @@ -561,7 +561,7 @@ private void WritePlaneClipData(PlaneClipFrameBuffer planeClipFrameBuffer, Rende GL.ActiveTexture(BindTextures.BoundTexture); program.VertexGapClampUV(false); SetStaticUniforms(program, renderInfo); - m_geometryRenderer.RenderStaticGeometryFlats(); + m_geometryRenderer.RenderStaticGeometryFlats(program); } } diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/RenderProgramBase.cs b/Core/Render/OpenGL/Renderers/Legacy/World/RenderProgramBase.cs index 527c39b1e..f37647299 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/RenderProgramBase.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/RenderProgramBase.cs @@ -6,7 +6,7 @@ namespace Helion.Render.OpenGL.Renderers.Legacy.World; -public abstract class RenderProgramBase : RenderProgram +public abstract class RenderProgramBase : RenderProgram, IRenderTextureArray { private readonly int m_boundTextureLocation; private readonly int m_sectorLightTextureLocation; @@ -38,6 +38,7 @@ public abstract class RenderProgramBase : RenderProgram private readonly int m_fogBarrierLocation; private readonly int m_useSectorColorLocation; private readonly int m_useSectorFogLocation; + private readonly int m_boundTextureIndexLocation; public RenderProgramBase(string label) : base(label) { @@ -71,6 +72,9 @@ public RenderProgramBase(string label) : base(label) m_fogBarrierLocation = Uniforms.GetLocation("fogBarrier"); m_useSectorColorLocation = Uniforms.GetLocation("useSectorColor"); m_useSectorFogLocation = Uniforms.GetLocation("useSectorFog"); + m_boundTextureIndexLocation = Uniforms.GetLocation("boundTextureIndex"); + + SetRenderTextureArray(0); } public void BoundTexture(TextureUnit unit) => ProgramUniforms.Set(unit, m_boundTextureLocation); @@ -104,4 +108,6 @@ public RenderProgramBase(string label) : base(label) public void FogBarrier(bool value) => ProgramUniforms.Set(value, m_fogBarrierLocation); public void UseSectorColor(bool value) => ProgramUniforms.Set(value, m_useSectorColorLocation); public void UseSectorFog(int value) => ProgramUniforms.Set(value, m_useSectorFogLocation); + + public void SetRenderTextureArray(int index) => ProgramUniforms.Set(index, m_boundTextureIndexLocation); } diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Shader/FragFunction.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Shader/FragFunction.cs index 7cdbc6d63..8fa50e19e 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Shader/FragFunction.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Shader/FragFunction.cs @@ -186,7 +186,7 @@ public static string FragColorFunction(FragColorFunctionOptions options, ColorMa var fragColor = @$" {textureMappingClamp} - {declareFragColor} = texture(boundTexture, vec3(texUV, 0));"; + {declareFragColor} = texture(boundTexture, vec3(texUV, boundTextureIndex));"; return fragColor + diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Shader/PlaneClip.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Shader/PlaneClip.cs index 70253e835..a80a08a69 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Shader/PlaneClip.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Shader/PlaneClip.cs @@ -31,6 +31,7 @@ public static string WritePlaneFragFunction(PlaneClipFragOptions options) ${{InVars}} uniform sampler2DArray boundTexture; + uniform int boundTextureIndex; layout (location = 0) out vec3 outPlane; @@ -57,6 +58,7 @@ public static string WriteWallFragFunction(WallClipFragOptions options) ${InVars} uniform sampler2DArray boundTexture; + uniform int boundTextureIndex; layout (location = 0) out vec4 outPlane; @@ -109,7 +111,7 @@ private static string GetAlphaSample(bool alphaSample) if (!alphaSample) return ""; - return @"float alpha = texture(boundTexture, vec3(uvFrag.xy, 0)).a; + return @"float alpha = texture(boundTexture, vec3(uvFrag.xy, boundTextureIndex)).a; if (alpha <= 0) discard;"; } diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/StaticShader.cs b/Core/Render/OpenGL/Renderers/Legacy/World/StaticShader.cs index 00c66e94f..a5e4c0796 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/StaticShader.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/StaticShader.cs @@ -46,6 +46,7 @@ private static string VertexShaderInternal(bool planeClip) => @" uniform sampler2DArray boundTexture; uniform int useSectorColor; uniform int useSectorFog; + uniform int boundTextureIndex; void main() { uvFrag = uv; @@ -128,6 +129,7 @@ protected override string FragmentShader() uniform float downScaleAmount; uniform ivec2 screenBounds; uniform int useSectorFog; + uniform int boundTextureIndex; ${LightLevelFragVariables} ${SectorColorMapFragVariables} diff --git a/Core/Render/OpenGL/Texture/GLTextureManager.cs b/Core/Render/OpenGL/Texture/GLTextureManager.cs index c52980d7d..34bbf8918 100644 --- a/Core/Render/OpenGL/Texture/GLTextureManager.cs +++ b/Core/Render/OpenGL/Texture/GLTextureManager.cs @@ -4,6 +4,7 @@ using Helion.Graphics.Fonts; using Helion.Render.Common.Textures; using Helion.Render.OpenGL.Shared; +using Helion.Render.OpenGL.Texture.Legacy; using Helion.Resources; using Helion.Resources.Archives.Collection; using Helion.Resources.Definitions.Zdoom; @@ -208,6 +209,30 @@ public GLTextureType GetTexture(int index, bool repeatY = true) return (GLTextureType)renderTexture; } + public void CreateTextureArray(int[] indices, bool repeatY) + { + if (indices.Length == 0) + return; + + var images = new Image[indices.Length]; + var textures = new Resources.Texture[indices.Length]; + + for (int i = 0; i < indices.Length; i++) + { + var index = indices[i]; + TextureManager.EnsureTextureImageLoaded(index); + var texture = TextureManager.GetTexture(index); + // TODO this can't be null + images[i] = texture.Image; + textures[i] = texture; + } + + var flags = repeatY ? TextureFlags.Default : TextureFlags.ClampY; + var glTextures = GenerateTextureArray(images, images[0].Dimension, ResourceNamespace.Textures, flags); + for (int i = 0; i < images.Length; i++) + textures[i].SetGLTexture(glTextures[i], repeatY); + } + public GLTextureType? GetBrightmapTexture(int index, bool repeatY = true) { var texture = TextureManager.GetTexture(index); @@ -403,6 +428,12 @@ protected GLTextureType CreateTexture(Image? image, string? name, ResourceNamesp return texture; } + //protected GLTextureType[] CreateTextureArray(Image[] images, Dimension dimension, ResourceNamespace resourceNamespace, bool repeatY) + //{ + // TextureFlags flags = repeatY ? TextureFlags.Default : TextureFlags.ClampY; + // return GenerateTextureArray(images, dimension, resourceNamespace, flags); + //} + private ResourceTracker GetTextureTracker(bool repeatY) => repeatY ? TextureTracker : TextureTrackerClamp; @@ -432,6 +463,7 @@ private void ReleaseUnmanagedResources() } protected abstract GLTextureType GenerateTexture(Image image, string name, ResourceNamespace resourceNamespace, TextureFlags flags = TextureFlags.Default); + protected abstract GLTextureType[] GenerateTextureArray(Image[] images, Dimension dimension, ResourceNamespace resourceNamespace, TextureFlags flags); public abstract void ReUpload(GLTextureType texture, Image image, uint[] imagePixels); diff --git a/Core/Render/OpenGL/Texture/Legacy/GLLegacyTexture.cs b/Core/Render/OpenGL/Texture/Legacy/GLLegacyTexture.cs index 4c0ea711e..5e158bf38 100644 --- a/Core/Render/OpenGL/Texture/Legacy/GLLegacyTexture.cs +++ b/Core/Render/OpenGL/Texture/Legacy/GLLegacyTexture.cs @@ -8,6 +8,9 @@ namespace Helion.Render.OpenGL.Texture.Legacy; public class GLLegacyTexture : GLTexture { public TextureFlags Flags; + public int ArrayIndex; + public bool IsArray => ParentArrayTexture != null; + public GLLegacyTexture? ParentArrayTexture; public GLLegacyTexture(int textureId, string name, Dimension dimension, Vec2I offset, ResourceNamespace ns, TextureTarget target, int transparentPixelCount, int blankRowsFromTop = 0, int blankRowsFromBottom = 0) diff --git a/Core/Render/OpenGL/Texture/Legacy/LegacyGLTextureManager.cs b/Core/Render/OpenGL/Texture/Legacy/LegacyGLTextureManager.cs index 88a85c045..c4d54df21 100644 --- a/Core/Render/OpenGL/Texture/Legacy/LegacyGLTextureManager.cs +++ b/Core/Render/OpenGL/Texture/Legacy/LegacyGLTextureManager.cs @@ -10,7 +10,6 @@ using Helion.Resources.Archives.Collection; using Helion.Util.Configs; using Helion.Util.Extensions; -using Helion.World.Entities.Players; using OpenTK.Graphics.OpenGL; using System; using System.Collections.Generic; @@ -101,6 +100,45 @@ private void HandleAnisotropyChange(object? sender, int e) Dispose(); } + public unsafe void UploadAndSetParameters(GLLegacyTexture texture, Image[] images, string name, ResourceNamespace resourceNamespace, TextureFlags flags) + { + GL.BindTexture(texture.Target, texture.TextureId); + + if (resourceNamespace == ResourceNamespace.Sprites || resourceNamespace == ResourceNamespace.Undefined) + flags = TextureFlags.ClampX | TextureFlags.ClampY; + + GL.TexImage3D(TextureTarget.Texture2DArray, 0, PixelInternalFormat.Rgba8, images[0].Width, images[0].Height, images.Length, 0, + PixelFormat.Bgra, PixelType.UnsignedInt8888Reversed, IntPtr.Zero); + + for (int i = 0; i < images.Length; i++) + { + var image = images[i]; + fixed (uint* pixelPtr = image.GetGlTexturePixels(ShaderVars.PaletteColorMode)) + { + IntPtr ptr = new(pixelPtr); + GL.TexSubImage3D( + texture.Target, + level: 0, + xoffset: 0, + yoffset: 0, + zoffset: i, + image.Width, + image.Height, + depth: 1, + PixelFormat.Bgra, + PixelType.UnsignedInt8888Reversed, + ptr + ); + } + } + + GL.GenerateMipmap(GenerateMipmapTarget.Texture2DArray); + SetTextureParameters(texture.Target, resourceNamespace, flags); + + GL.BindTexture(texture.Target, 0); + texture.Flags = flags; + } + public unsafe void UploadAndSetParameters(GLLegacyTexture texture, Image image, string name, ResourceNamespace resourceNamespace, TextureFlags flags) { GL.BindTexture(texture.Target, texture.TextureId); @@ -119,7 +157,6 @@ public unsafe void UploadAndSetParameters(GLLegacyTexture texture, Image image, PixelFormat.Bgra, PixelType.UnsignedInt8888Reversed, IntPtr.Zero); // TODO this is forcing everything to be a 2DArray with one layer. - int layer = 0; GL.TexSubImage3D( texture.Target, level: 0, @@ -131,7 +168,7 @@ public unsafe void UploadAndSetParameters(GLLegacyTexture texture, Image image, depth: 1, PixelFormat.Bgra, PixelType.UnsignedInt8888Reversed, - ptr // pointer or byte[] for this layer + ptr ); } else @@ -183,6 +220,29 @@ protected override GLLegacyTexture GenerateTexture(Image image, string name, return texture; } + protected override GLLegacyTexture[] GenerateTextureArray(Image[] images, Dimension dimension, ResourceNamespace resourceNamespace, TextureFlags flags) + { + int textureId = GL.GenTexture(); + var arrayTexture = new GLLegacyTexture(textureId, $"Texture Array {dimension}", dimension, default, resourceNamespace, TextureTarget.Texture2DArray, 0, 0, 0); + UploadAndSetParameters(arrayTexture, images, "", resourceNamespace, flags); + + var textures = new GLLegacyTexture[images.Length]; + for (int i = 0; i < images.Length; i++) + { + var image = images[i]; + var texture = new GLLegacyTexture(textureId, $"Sub Image {i}", dimension, image.Offset, resourceNamespace, TextureTarget.Texture2DArray, + image.TransparentPixelCount(), image.BlankRowsFromTop, image.BlankRowsFromBottom) + { + ArrayIndex = i, + ParentArrayTexture = arrayTexture + }; + + textures[i] = texture; + } + + return textures; + } + /// /// Creates a new font. The caller is responsible for disposing it. /// From f34a2be595c3f109fc785b108588fa91e8be87a5 Mon Sep 17 00:00:00 2001 From: Nick Date: Wed, 9 Sep 2026 10:59:48 -0400 Subject: [PATCH 03/49] testing with walls --- .../Legacy/World/Geometry/GeometryRenderer.cs | 39 ++++++++----- .../Static/StaticCacheGeometryRenderer.cs | 57 +++++++++++++++---- .../Legacy/World/LegacyWorldRenderer.cs | 4 +- Core/Render/OpenGL/Texture/GLTexture.cs | 6 +- .../Render/OpenGL/Texture/GLTextureManager.cs | 9 +++ .../OpenGL/Texture/Legacy/GLLegacyTexture.cs | 2 +- .../Texture/Legacy/LegacyGLTextureManager.cs | 2 +- 7 files changed, 88 insertions(+), 31 deletions(-) diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/GeometryRenderer.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/GeometryRenderer.cs index 403bada14..2a5b09bed 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/GeometryRenderer.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/GeometryRenderer.cs @@ -37,6 +37,10 @@ public partial class GeometryRenderer : IDisposable { public delegate void RenderCoverWallAction(Side side, Span vertices, WallLocation location, bool oneSided); + public HashSet FlatTextures = []; + public HashSet WallTexturesRepeat = []; + public HashSet WallTexturesClamp = []; + private const double MaxSky = 16384; private static readonly Sector DefaultSector = CreateDefault(); private static readonly GLLegacyTexture TestTexture = new(0, "TEST", default, default, default, default, default); @@ -367,8 +371,8 @@ public void ClearBsp() m_hitLines.SetAll(false); } - public void RenderStaticGeometryWalls() => - m_staticCacheGeometryRenderer.RenderWalls(); + public void RenderStaticGeometryWalls(IRenderTextureArray renderTextureArray) => + m_staticCacheGeometryRenderer.RenderWalls(renderTextureArray); public void RenderStaticGeometryFlats(IRenderTextureArray renderTextureArray) => m_staticCacheGeometryRenderer.RenderFlats(renderTextureArray); @@ -609,44 +613,49 @@ private void PreloadAllTextures(IWorld world) if (world.SameAsPreviousMap) return; - HashSet textures = []; for (int i = 0; i < world.Lines.Count; i++) { var line = world.Lines[i]; - AddSideTextures(textures, line.Front); + AddSideTextures(WallTexturesRepeat, WallTexturesClamp, line.Front); if (line.Back == null) continue; - AddSideTextures(textures, line.Back); + AddSideTextures(WallTexturesRepeat, WallTexturesClamp, line.Back); } for (int i = 0; i < world.Sectors.Count; i++) { var sector = world.Sectors[i]; - textures.Add(sector.Floor.TextureHandle); - textures.Add(sector.Ceiling.TextureHandle); + FlatTextures.Add(sector.Floor.TextureHandle); + FlatTextures.Add(sector.Ceiling.TextureHandle); if (sector.FloorSkyTextureHandle.HasValue) - textures.Add(sector.FloorSkyTextureHandle.Value); + FlatTextures.Add(sector.FloorSkyTextureHandle.Value); if (sector.CeilingSkyTextureHandle.HasValue) - textures.Add(sector.CeilingSkyTextureHandle.Value); + FlatTextures.Add(sector.CeilingSkyTextureHandle.Value); } foreach (var textureName in world.GetPreCacheTextureNames()) { var texture = TextureManager.GetTexture(textureName, ResourceNamespace.Global, ResourceNamespace.Textures); if (texture.Index > 0) - textures.Add(texture.Index); + { + WallTexturesRepeat.Add(texture.Index); + WallTexturesClamp.Add(texture.Index); + } } - TextureManager.LoadTextureImages(textures); + TextureManager.LoadTextureImages(FlatTextures); + TextureManager.LoadTextureImages(WallTexturesClamp); + TextureManager.LoadTextureImages(WallTexturesRepeat); } - private static void AddSideTextures(HashSet textures, Side side) + private static void AddSideTextures(HashSet wallTexturesRepeat, HashSet wallTexturesClamp, Side side) { - textures.Add(side.Lower.TextureHandle); - textures.Add(side.Middle.TextureHandle); - textures.Add(side.Upper.TextureHandle); + wallTexturesRepeat.Add(side.Lower.TextureHandle); + wallTexturesRepeat.Add(side.Middle.TextureHandle); + wallTexturesClamp.Add(side.Middle.TextureHandle); + wallTexturesRepeat.Add(side.Upper.TextureHandle); } private void RenderSectorWalls(Sector sector, Vec2D pos2D, Vec2D prevPos2D) diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/StaticCacheGeometryRenderer.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/StaticCacheGeometryRenderer.cs index a353ec1f1..739198753 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/StaticCacheGeometryRenderer.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/StaticCacheGeometryRenderer.cs @@ -1,4 +1,5 @@ -using Helion.Geometry.Vectors; +using Helion.Geometry; +using Helion.Geometry.Vectors; using Helion.Render.OpenGL.Buffer.Array.Vertex; using Helion.Render.OpenGL.Context; using Helion.Render.OpenGL.Renderers.Legacy.World.Data; @@ -130,7 +131,7 @@ public void UpdateTo(IWorld world) AddSectorPlane(sector, SectorPlaneFace.Ceiling, false); } - BuildTextureArray(world); + BuildTextureArrays(world); if (WorldStatic.Sector3D) { @@ -168,17 +169,53 @@ public void UpdateTo(IWorld world) m_worldReload = false; } - private void BuildTextureArray(IWorld world) + private void BuildTextureArrays(IWorld world) { // This is currently a test that assumes 64x64 flats - var textures = new HashSet(); - foreach (var sector in world.Sectors) + BuildTextureArray(m_geometryRenderer.FlatTextures.ToArray()); + + var textures = new List(); + foreach (var index in m_geometryRenderer.WallTexturesRepeat) + textures.Add(m_archiveCollection.TextureManager.GetTexture(index)); + + textures.Sort(SortTexturesByDimensions); + + var arrayTextures = new List(); + var dimension = new Dimension(0, 0); + foreach (var texture in textures) { - textures.Add(sector.Floor.TextureHandle); - textures.Add(sector.Ceiling.TextureHandle); + if (texture.Image == null) + continue; + + if (dimension != texture.Image.Dimension) + { + if (arrayTextures.Count > 0) + BuildTextureArray(arrayTextures.Select(x => x.Index).ToArray()); + arrayTextures.Clear(); + dimension = texture.Image.Dimension; + } + + arrayTextures.Add(texture); } - m_textureManager.CreateTextureArray(textures.ToArray(), true); + if (arrayTextures.Count > 0) + BuildTextureArray(arrayTextures.Select(x => x.Index).ToArray()); + } + + private static int SortTexturesByDimensions(Resources.Texture x, Resources.Texture y) + { + if (x.Image == null || y.Image == null) + return -1; + + if (x.Image.Height == y.Image.Height) + return x.Image.Width.CompareTo(y.Image.Width); + + return x.Image.Height.CompareTo(y.Image.Height); + } + + private void BuildTextureArray(int[] textures) + { + m_textureManager.CreateTextureArray(textures, true); } private void World_SectorFogColorChanged(object? sender, SectorFogEvent e) @@ -809,8 +846,8 @@ private void AddSectorPlane(Sector sectorForSubsectors, SectorPlaneFace face, bo AddVertices(vertices, renderedVertices); } - public void RenderWalls() => - RenderGeometry(m_geometry.GetGeometry(GeometryType.Wall)); + public void RenderWalls(IRenderTextureArray renderTextureArray) => + RenderGeometry(m_geometry.GetGeometry(GeometryType.Wall), renderTextureArray); public void RenderTwoSidedMiddleWalls() => RenderGeometry(m_geometry.GetGeometry(GeometryType.TwoSidedMiddleWall)); diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/LegacyWorldRenderer.cs b/Core/Render/OpenGL/Renderers/Legacy/World/LegacyWorldRenderer.cs index 37711a446..15d230fdc 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/LegacyWorldRenderer.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/LegacyWorldRenderer.cs @@ -357,7 +357,7 @@ protected override void PerformRender(IWorld world, RenderInfo renderInfo, GLFra GL.ActiveTexture(BindTextures.BoundTexture); SetStaticUniforms(m_staticProgram, renderInfo); m_staticProgram.VertexGapClampUV(m_pixelGapCorrection); - m_geometryRenderer.RenderStaticGeometryWalls(); + m_geometryRenderer.RenderStaticGeometryWalls(m_staticProgram); m_staticProgram.VertexGapClampUV(false); m_geometryRenderer.RenderStaticGeometryFlats(m_staticProgram); } @@ -387,7 +387,7 @@ protected override void PerformRender(IWorld world, RenderInfo renderInfo, GLFra GL.ActiveTexture(BindTextures.BoundTexture); SetStaticUniforms(m_staticProgram, renderInfo); m_staticProgram.VertexGapClampUV(m_pixelGapCorrection); - m_geometryRenderer.RenderStaticGeometryWalls(); + m_geometryRenderer.RenderStaticGeometryWalls(m_staticProgram); if (m_downscaleVanillaBuffer) { diff --git a/Core/Render/OpenGL/Texture/GLTexture.cs b/Core/Render/OpenGL/Texture/GLTexture.cs index b2e8e168d..da88ba6e2 100644 --- a/Core/Render/OpenGL/Texture/GLTexture.cs +++ b/Core/Render/OpenGL/Texture/GLTexture.cs @@ -22,6 +22,7 @@ public abstract class GLTexture : IRenderableTextureHandle, IDisposable public readonly TextureTarget Target; public readonly int TransparentPixelCount; private bool m_disposed; + private bool m_ownsTexture; public int Width => Dimension.Width; public int Height => Dimension.Height; @@ -30,7 +31,7 @@ public abstract class GLTexture : IRenderableTextureHandle, IDisposable public int BlankRowsFromBottom; protected GLTexture(int textureId, string name, Dimension dimension, Vec2I offset, ResourceNamespace ns, TextureTarget target, - int transparentPixelCount, int blankRowsFromTop, int blankRowsFromBottom) + int transparentPixelCount, int blankRowsFromTop, int blankRowsFromBottom, bool ownsTexture = true) { TextureId = textureId; Name = name; @@ -42,6 +43,7 @@ protected GLTexture(int textureId, string name, Dimension dimension, Vec2I offse TransparentPixelCount = transparentPixelCount; BlankRowsFromTop = blankRowsFromTop; BlankRowsFromBottom = blankRowsFromBottom; + m_ownsTexture = ownsTexture; } ~GLTexture() @@ -57,7 +59,7 @@ public void Dispose() protected virtual void ReleaseUnmanagedResources() { - if (m_disposed) + if (m_disposed || !m_ownsTexture) return; GL.DeleteTexture(TextureId); diff --git a/Core/Render/OpenGL/Texture/GLTextureManager.cs b/Core/Render/OpenGL/Texture/GLTextureManager.cs index 34bbf8918..ce187e849 100644 --- a/Core/Render/OpenGL/Texture/GLTextureManager.cs +++ b/Core/Render/OpenGL/Texture/GLTextureManager.cs @@ -209,6 +209,8 @@ public GLTextureType GetTexture(int index, bool repeatY = true) return (GLTextureType)renderTexture; } + private List m_arrayTextures = []; + public void CreateTextureArray(int[] indices, bool repeatY) { if (indices.Length == 0) @@ -223,6 +225,11 @@ public void CreateTextureArray(int[] indices, bool repeatY) TextureManager.EnsureTextureImageLoaded(index); var texture = TextureManager.GetTexture(index); // TODO this can't be null + if (texture.Image == null) + { + int x = 1; + } + images[i] = texture.Image; textures[i] = texture; } @@ -231,6 +238,8 @@ public void CreateTextureArray(int[] indices, bool repeatY) var glTextures = GenerateTextureArray(images, images[0].Dimension, ResourceNamespace.Textures, flags); for (int i = 0; i < images.Length; i++) textures[i].SetGLTexture(glTextures[i], repeatY); + + m_arrayTextures.Add(glTextures); } public GLTextureType? GetBrightmapTexture(int index, bool repeatY = true) diff --git a/Core/Render/OpenGL/Texture/Legacy/GLLegacyTexture.cs b/Core/Render/OpenGL/Texture/Legacy/GLLegacyTexture.cs index 5e158bf38..7eafa7eb6 100644 --- a/Core/Render/OpenGL/Texture/Legacy/GLLegacyTexture.cs +++ b/Core/Render/OpenGL/Texture/Legacy/GLLegacyTexture.cs @@ -13,7 +13,7 @@ public class GLLegacyTexture : GLTexture public GLLegacyTexture? ParentArrayTexture; public GLLegacyTexture(int textureId, string name, Dimension dimension, Vec2I offset, ResourceNamespace ns, TextureTarget target, - int transparentPixelCount, int blankRowsFromTop = 0, int blankRowsFromBottom = 0) + int transparentPixelCount, int blankRowsFromTop = 0, int blankRowsFromBottom = 0, bool ownsTexture = true) : base(textureId, name, dimension, offset, ns, TextureTarget.Texture2DArray, transparentPixelCount, blankRowsFromTop, blankRowsFromBottom) { } diff --git a/Core/Render/OpenGL/Texture/Legacy/LegacyGLTextureManager.cs b/Core/Render/OpenGL/Texture/Legacy/LegacyGLTextureManager.cs index c4d54df21..18617799d 100644 --- a/Core/Render/OpenGL/Texture/Legacy/LegacyGLTextureManager.cs +++ b/Core/Render/OpenGL/Texture/Legacy/LegacyGLTextureManager.cs @@ -231,7 +231,7 @@ protected override GLLegacyTexture[] GenerateTextureArray(Image[] images, Dimens { var image = images[i]; var texture = new GLLegacyTexture(textureId, $"Sub Image {i}", dimension, image.Offset, resourceNamespace, TextureTarget.Texture2DArray, - image.TransparentPixelCount(), image.BlankRowsFromTop, image.BlankRowsFromBottom) + image.TransparentPixelCount(), image.BlankRowsFromTop, image.BlankRowsFromBottom, ownsTexture: false) { ArrayIndex = i, ParentArrayTexture = arrayTexture From 35ce80b1d980d8c816be8c16fa3f4279232894c7 Mon Sep 17 00:00:00 2001 From: Nick Date: Thu, 10 Sep 2026 08:14:32 -0400 Subject: [PATCH 04/49] start using texture index in shader to merge texture array vbos --- .../Renderers/Legacy/World/DynamicVertex.cs | 3 + .../Legacy/World/Geometry/CoverWallUtil.cs | 12 +- .../Legacy/World/Geometry/GeometryRenderer.cs | 62 ++++++---- .../Portals/FloodFill/FloodFillInfo.cs | 3 +- .../Portals/FloodFill/FloodFillProgram.cs | 6 +- .../Portals/FloodFill/FloodFillVertex.cs | 6 +- .../World/Geometry/Static/GeometryData.cs | 5 + .../Geometry/Static/GeometryTextureLookup.cs | 13 ++- .../Static/StaticCacheGeometryRenderer.cs | 106 +++++++++++------- .../Legacy/World/IRenderTextureArray.cs | 6 - .../Legacy/World/InterpolationShader.cs | 1 + .../Legacy/World/LegacyWorldRenderer.cs | 10 +- .../Legacy/World/RenderProgramBase.cs | 8 +- .../Renderers/Legacy/World/StaticShader.cs | 6 +- .../Renderers/Legacy/World/StaticVertex.cs | 5 +- Core/Render/OpenGL/Texture/GLTexture.cs | 2 +- .../Render/OpenGL/Texture/GLTextureManager.cs | 23 ++-- .../OpenGL/Texture/Legacy/GLLegacyTexture.cs | 4 +- .../Texture/Legacy/LegacyGLTextureManager.cs | 4 +- Core/Render/OpenGL/Vertex/Attributes.cs | 4 +- 20 files changed, 176 insertions(+), 113 deletions(-) delete mode 100644 Core/Render/OpenGL/Renderers/Legacy/World/IRenderTextureArray.cs diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/DynamicVertex.cs b/Core/Render/OpenGL/Renderers/Legacy/World/DynamicVertex.cs index 7c82ef8b0..8aa89e8d2 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/DynamicVertex.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/DynamicVertex.cs @@ -32,4 +32,7 @@ public struct DynamicVertex [VertexAttribute] public float RenderOptions; + + [VertexAttribute] + public float TextureIndex; } diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/CoverWallUtil.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/CoverWallUtil.cs index d9ab6bacd..84aeee80f 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/CoverWallUtil.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/CoverWallUtil.cs @@ -48,22 +48,22 @@ public static unsafe void CopyCoverWallVertices(Side side, StaticVertex[] static { DynamicVertex* v = startVertex; staticVertices[index++] = new StaticVertex(v->X, v->Y, v->Z + heights.AddTop, v->U, v->V, - v->SurfaceOptions, v->LightLevelAdd, 0); + v->SurfaceOptions, v->LightLevelAdd, 0, 0); v++; staticVertices[index++] = new StaticVertex(v->X, v->Y, v->Z - heights.SubBottom, v->U, v->V, - v->SurfaceOptions, v->LightLevelAdd, 0); + v->SurfaceOptions, v->LightLevelAdd, 0, 0); v++; staticVertices[index++] = new StaticVertex(v->X, v->Y, v->Z + heights.AddTop, v->U, v->V, - v->SurfaceOptions, v->LightLevelAdd, 0); + v->SurfaceOptions, v->LightLevelAdd, 0, 0); v++; staticVertices[index++] = new StaticVertex(v->X, v->Y, v->Z - heights.SubBottom, v->U, v->V, - v->SurfaceOptions, v->LightLevelAdd, 0); + v->SurfaceOptions, v->LightLevelAdd, 0, 0); v++; staticVertices[index++] = new StaticVertex(v->X, v->Y, v->Z + heights.AddTop, v->U, v->V, - v->SurfaceOptions, v->LightLevelAdd, 0); + v->SurfaceOptions, v->LightLevelAdd, 0, 0); v++; staticVertices[index++] = new StaticVertex(v->X, v->Y, v->Z - heights.SubBottom, v->U, v->V, - v->SurfaceOptions, v->LightLevelAdd, 0); + v->SurfaceOptions, v->LightLevelAdd, 0, 0); } } diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/GeometryRenderer.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/GeometryRenderer.cs index 2a5b09bed..e42c86e03 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/GeometryRenderer.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/GeometryRenderer.cs @@ -371,11 +371,11 @@ public void ClearBsp() m_hitLines.SetAll(false); } - public void RenderStaticGeometryWalls(IRenderTextureArray renderTextureArray) => - m_staticCacheGeometryRenderer.RenderWalls(renderTextureArray); + public void RenderStaticGeometryWalls() => + m_staticCacheGeometryRenderer.RenderWalls(); - public void RenderStaticGeometryFlats(IRenderTextureArray renderTextureArray) => - m_staticCacheGeometryRenderer.RenderFlats(renderTextureArray); + public void RenderStaticGeometryFlats() => + m_staticCacheGeometryRenderer.RenderFlats(); public void RenderStaticCoverWalls() => m_staticCacheGeometryRenderer.RenderCoverWalls(); @@ -616,12 +616,12 @@ private void PreloadAllTextures(IWorld world) for (int i = 0; i < world.Lines.Count; i++) { var line = world.Lines[i]; - AddSideTextures(WallTexturesRepeat, WallTexturesClamp, line.Front); + AddSideTextures(WallTexturesRepeat, WallTexturesClamp, line.Front, line.Flags.TwoSided); if (line.Back == null) continue; - AddSideTextures(WallTexturesRepeat, WallTexturesClamp, line.Back); + AddSideTextures(WallTexturesRepeat, WallTexturesClamp, line.Back, line.Flags.TwoSided); } for (int i = 0; i < world.Sectors.Count; i++) @@ -650,12 +650,15 @@ private void PreloadAllTextures(IWorld world) TextureManager.LoadTextureImages(WallTexturesRepeat); } - private static void AddSideTextures(HashSet wallTexturesRepeat, HashSet wallTexturesClamp, Side side) + private static void AddSideTextures(HashSet wallTexturesRepeat, HashSet wallTexturesClamp, Side side, bool twoSided) { wallTexturesRepeat.Add(side.Lower.TextureHandle); - wallTexturesRepeat.Add(side.Middle.TextureHandle); - wallTexturesClamp.Add(side.Middle.TextureHandle); wallTexturesRepeat.Add(side.Upper.TextureHandle); + + if (twoSided) + wallTexturesClamp.Add(side.Middle.TextureHandle); + else + wallTexturesRepeat.Add(side.Middle.TextureHandle); } private void RenderSectorWalls(Sector sector, Vec2D pos2D, Vec2D prevPos2D) @@ -858,9 +861,9 @@ public void RenderOneSided(Side side, bool isFront, out DynamicVertex[]? vertice int addAlpha = allowAlpha ? 0 : 1; WorldTriangulator.HandleOneSided(side, offsetSide ?? side, floor, ceiling, texture.UVInverse, ref wall, isFront: isFront); if (data == null) - data = GetWallVertices(wall, GetLightLevelAdd(side), lightIndex, overrideLightIndex, GetWallLightLevel(side, side.Middle), side.Line.Id, WallLocation.Middle, addAlpha: addAlpha, alpha: side.Alpha); + data = GetWallVertices(wall, GetLightLevelAdd(side), lightIndex, overrideLightIndex, GetWallLightLevel(side, side.Middle), side.Line.Id, WallLocation.Middle, texture.ArrayIndex, addAlpha: addAlpha, alpha: side.Alpha); else - SetWallVertices(data, wall, GetLightLevelAdd(side), lightIndex, overrideLightIndex, GetWallLightLevel(side, side.Middle), side.Line.Id, WallLocation.Middle, addAlpha: addAlpha, alpha: side.Alpha); + SetWallVertices(data, wall, GetLightLevelAdd(side), lightIndex, overrideLightIndex, GetWallLightLevel(side, side.Middle), side.Line.Id, WallLocation.Middle, texture.ArrayIndex, addAlpha: addAlpha, alpha: side.Alpha); SetCachedSide(m_vertexLookup, side, data); } @@ -1216,9 +1219,9 @@ public void RenderTwoSidedLower(Side facingSide, Side otherSide, Sector facingSe WorldTriangulator.HandleTwoSidedLower(facingSide, top, bottom, texture.UVInverse, isFrontSide, ref wall); if (data == null) - data = GetWallVertices(wall, GetLightLevelAdd(facingSide), lightIndex, overrideLightIndex, GetWallLightLevel(facingSide, facingSide.Lower), facingSide.Line.Id, WallLocation.Lower); + data = GetWallVertices(wall, GetLightLevelAdd(facingSide), lightIndex, overrideLightIndex, GetWallLightLevel(facingSide, facingSide.Lower), facingSide.Line.Id, WallLocation.Lower, texture.ArrayIndex); else - SetWallVertices(data, wall, GetLightLevelAdd(facingSide), lightIndex, overrideLightIndex, GetWallLightLevel(facingSide, facingSide.Lower), facingSide.Line.Id, WallLocation.Lower); + SetWallVertices(data, wall, GetLightLevelAdd(facingSide), lightIndex, overrideLightIndex, GetWallLightLevel(facingSide, facingSide.Lower), facingSide.Line.Id, WallLocation.Lower, texture.ArrayIndex); SetCachedSide(m_vertexLowerLookup, facingSide, data); } @@ -1337,9 +1340,9 @@ public void RenderTwoSidedUpper(Side facingSide, Side otherSide, Sector facingSe int lightIndex = Renderer.GetLightBufferIndex(facingSide, facingSide.Upper, lightLevelSector, out var overrideLightIndex); WorldTriangulator.HandleTwoSidedUpper(facingSide, top, bottom, texture.UVInverse, isFrontSide, ref wall); if (data == null) - data = GetWallVertices(wall, GetLightLevelAdd(facingSide), lightIndex, overrideLightIndex, GetWallLightLevel(facingSide, facingSide.Upper), facingSide.Line.Id, WallLocation.Upper); + data = GetWallVertices(wall, GetLightLevelAdd(facingSide), lightIndex, overrideLightIndex, GetWallLightLevel(facingSide, facingSide.Upper), facingSide.Line.Id, WallLocation.Upper, texture.ArrayIndex); else - SetWallVertices(data, wall, GetLightLevelAdd(facingSide), lightIndex, overrideLightIndex, GetWallLightLevel(facingSide, facingSide.Upper), facingSide.Line.Id, WallLocation.Upper); + SetWallVertices(data, wall, GetLightLevelAdd(facingSide), lightIndex, overrideLightIndex, GetWallLightLevel(facingSide, facingSide.Upper), facingSide.Line.Id, WallLocation.Upper, texture.ArrayIndex); SetCachedSide(m_vertexUpperLookup, facingSide, data); } @@ -1392,7 +1395,7 @@ public DynamicVertex[] RenderTwoSidedUpperOrLowerRaw(WallLocation location, Side WorldTriangulator.HandleTwoSidedUpper(facingSide, facingSector.Ceiling, otherSector.Ceiling, texture.UVInverse, isFrontSide, ref wall); else WorldTriangulator.HandleTwoSidedLower(facingSide, otherSector.Floor, facingSector.Floor, texture.UVInverse, isFrontSide, ref wall); - SetWallVertices(m_wallVertices, wall, GetLightLevelAdd(facingSide), lightIndex, lightIndex, 0, facingSide.Line.Id, location); + SetWallVertices(m_wallVertices, wall, GetLightLevelAdd(facingSide), lightIndex, lightIndex, 0, facingSide.Line.Id, location, 0); return m_wallVertices; } @@ -1520,9 +1523,9 @@ public void RenderTwoSidedMiddle(Side facingSide, Side otherSide, Sector facingS clipPlanes: GetTwoSidedMiddleClipPlanes(facingSide, otherSide, facingSector, otherSector), restrictSpan: restrictSpan); if (data == null) - data = GetWallVertices(wall, GetLightLevelAdd(facingSide), lightIndex, overrideLightIndex, GetWallLightLevel(facingSide, facingSide.Middle), line.Id, WallLocation.None, alpha, addAlpha: 0); + data = GetWallVertices(wall, GetLightLevelAdd(facingSide), lightIndex, overrideLightIndex, GetWallLightLevel(facingSide, facingSide.Middle), line.Id, WallLocation.None, texture.ArrayIndex, alpha, addAlpha: 0); else - SetWallVertices(data, wall, GetLightLevelAdd(facingSide), lightIndex, overrideLightIndex, GetWallLightLevel(facingSide, facingSide.Middle), line.Id, WallLocation.None, alpha, addAlpha: 0); + SetWallVertices(data, wall, GetLightLevelAdd(facingSide), lightIndex, overrideLightIndex, GetWallLightLevel(facingSide, facingSide.Middle), line.Id, WallLocation.None, texture.ArrayIndex, alpha, addAlpha: 0); SetCachedSide(m_vertexLookup, facingSide, data); line.RenderSegStart = saveStart; @@ -1814,7 +1817,7 @@ private bool RenderFlat(DynamicArray subsectors, SectorPlane renderPl { ref var second = ref m_subsectorVertices.Data[i]; ref var third = ref m_subsectorVertices.Data[i + 1]; - GetFlatVertices(lookupData, indexStart, ref root, ref second, ref third, lightIndex, overrideLightIndex, flatLightLevel, upper, lower, addAlpha, alpha); + GetFlatVertices(lookupData, indexStart, ref root, ref second, ref third, lightIndex, overrideLightIndex, flatLightLevel, upper, lower, addAlpha, alpha, texture.ArrayIndex); indexStart += 3; } } @@ -2024,7 +2027,7 @@ private static unsafe void CreateSkyFlatVertices(SkyGeometryVertex[] vertices, i } private static unsafe void SetWallVertices(DynamicVertex[] data, in WallVertices wv, int lightLevelAdd, int lightIndex, int overrideLightIndex, byte wallLightLevel, - int mapId, WallLocation location, float alpha = 1.0f, int addAlpha = 1) + int mapId, WallLocation location, int textureIndex, float alpha = 1.0f, int addAlpha = 1) { var uvFlags = UvFlags.Normal; if (wv.TopLeft.U > wv.BottomRight.U) @@ -2051,6 +2054,7 @@ private static unsafe void SetWallVertices(DynamicVertex[] data, in WallVertices vertex->SurfaceOptions = VertexOptions.PackSurface(1, alpha, addAlpha, upper, lower, overrideLightIndex); vertex->LightLevelAdd = lightLevelAddAndMapId; vertex->RenderOptions = renderOptions; + vertex->TextureIndex = textureIndex; vertex++; vertex->X = wv.TopLeft.X; @@ -2066,6 +2070,7 @@ private static unsafe void SetWallVertices(DynamicVertex[] data, in WallVertices vertex->SurfaceOptions = VertexOptions.PackSurface(1, alpha, addAlpha, upper, lower, overrideLightIndex); vertex->LightLevelAdd = lightLevelAddAndMapId; vertex->RenderOptions = renderOptions; + vertex->TextureIndex = textureIndex; vertex++; vertex->X = wv.BottomRight.X; @@ -2081,6 +2086,7 @@ private static unsafe void SetWallVertices(DynamicVertex[] data, in WallVertices vertex->SurfaceOptions = VertexOptions.PackSurface(1, alpha, addAlpha, upper, lower, overrideLightIndex); vertex->LightLevelAdd = lightLevelAddAndMapId; vertex->RenderOptions = renderOptions; + vertex->TextureIndex = textureIndex; vertex++; vertex->X = wv.BottomRight.X; @@ -2096,6 +2102,7 @@ private static unsafe void SetWallVertices(DynamicVertex[] data, in WallVertices vertex->SurfaceOptions = VertexOptions.PackSurface(0, alpha, addAlpha, upper, lower, overrideLightIndex); vertex->LightLevelAdd = lightLevelAddAndMapId; vertex->RenderOptions = renderOptions; + vertex->TextureIndex = textureIndex; vertex++; vertex->X = wv.BottomRight.X; @@ -2111,6 +2118,7 @@ private static unsafe void SetWallVertices(DynamicVertex[] data, in WallVertices vertex->SurfaceOptions = VertexOptions.PackSurface(0, alpha, addAlpha, upper, lower, overrideLightIndex); vertex->LightLevelAdd = lightLevelAddAndMapId; vertex->RenderOptions = renderOptions; + vertex->TextureIndex = textureIndex; vertex++; vertex->X = wv.TopLeft.X; @@ -2126,11 +2134,12 @@ private static unsafe void SetWallVertices(DynamicVertex[] data, in WallVertices vertex->SurfaceOptions = VertexOptions.PackSurface(0, alpha, addAlpha, upper, lower, overrideLightIndex); vertex->LightLevelAdd = lightLevelAddAndMapId; vertex->RenderOptions = renderOptions; + vertex->TextureIndex = textureIndex; } } private static unsafe DynamicVertex[] GetWallVertices(in WallVertices wv, int lightLevelAdd, int lightIndex, int overrideLightIndex, byte wallLightLevel, - int mapId, WallLocation location, float alpha = 1.0f, int addAlpha = 1) + int mapId, WallLocation location, int textureIndex, float alpha = 1.0f, int addAlpha = 1) { var uvFlags = UvFlags.Normal; if (wv.TopLeft.U > wv.BottomRight.U) @@ -2167,6 +2176,7 @@ private static unsafe DynamicVertex[] GetWallVertices(in WallVertices wv, int li vertex->SurfaceOptions = VertexOptions.PackSurface(1, alpha, addAlpha, upper, lower, overrideLightIndex); vertex->LightLevelAdd = lightLevelAddAndMapId; vertex->RenderOptions = renderOptions; + vertex->TextureIndex = textureIndex; // 1 vertex++; @@ -2183,6 +2193,7 @@ private static unsafe DynamicVertex[] GetWallVertices(in WallVertices wv, int li vertex->SurfaceOptions = VertexOptions.PackSurface(1, alpha, addAlpha, upper, lower, overrideLightIndex); vertex->LightLevelAdd = lightLevelAddAndMapId; vertex->RenderOptions = renderOptions; + vertex->TextureIndex = textureIndex; // 2 vertex++; @@ -2199,6 +2210,7 @@ private static unsafe DynamicVertex[] GetWallVertices(in WallVertices wv, int li vertex->SurfaceOptions = VertexOptions.PackSurface(1, alpha, addAlpha, upper, lower, overrideLightIndex); vertex->LightLevelAdd = lightLevelAddAndMapId; vertex->RenderOptions = renderOptions; + vertex->TextureIndex = textureIndex; // 3 vertex++; @@ -2215,6 +2227,7 @@ private static unsafe DynamicVertex[] GetWallVertices(in WallVertices wv, int li vertex->SurfaceOptions = VertexOptions.PackSurface(0, alpha, addAlpha, upper, lower, overrideLightIndex); vertex->LightLevelAdd = lightLevelAddAndMapId; vertex->RenderOptions = renderOptions; + vertex->TextureIndex = textureIndex; // 4 vertex++; @@ -2231,6 +2244,7 @@ private static unsafe DynamicVertex[] GetWallVertices(in WallVertices wv, int li vertex->SurfaceOptions = VertexOptions.PackSurface(0, alpha, addAlpha, upper, lower, overrideLightIndex); vertex->LightLevelAdd = lightLevelAddAndMapId; vertex->RenderOptions = renderOptions; + vertex->TextureIndex = textureIndex; // 5 vertex++; @@ -2247,13 +2261,14 @@ private static unsafe DynamicVertex[] GetWallVertices(in WallVertices wv, int li vertex->SurfaceOptions = VertexOptions.PackSurface(0, alpha, addAlpha, upper, lower, overrideLightIndex); vertex->LightLevelAdd = lightLevelAddAndMapId; vertex->RenderOptions = renderOptions; + vertex->TextureIndex = textureIndex; } return data; } private static unsafe void GetFlatVertices(DynamicVertex[] vertices, int startIndex, ref TriangulatedWorldVertex root, ref TriangulatedWorldVertex second, ref TriangulatedWorldVertex third, - int lightIndex, int overrideLightIndex, int flatLightLevel, int upper, int lower, int addAlpha, float alpha) + int lightIndex, int overrideLightIndex, int flatLightLevel, int upper, int lower, int addAlpha, float alpha, int textureIndex) { var surfaceOptions = VertexOptions.PackSurface(0, alpha, addAlpha, upper, lower, overrideLightIndex); var renderOptions = VertexOptions.PackRender(lightIndex, flatLightLevel); @@ -2272,6 +2287,7 @@ private static unsafe void GetFlatVertices(DynamicVertex[] vertices, int startIn vertex->PrevV = root.PrevV; vertex->SurfaceOptions = surfaceOptions; vertex->RenderOptions = renderOptions; + vertex->TextureIndex = textureIndex; vertex++; vertex->X = second.X; @@ -2286,6 +2302,7 @@ private static unsafe void GetFlatVertices(DynamicVertex[] vertices, int startIn vertex->PrevV = second.PrevV; vertex->SurfaceOptions = surfaceOptions; vertex->RenderOptions = renderOptions; + vertex->TextureIndex = textureIndex; vertex++; vertex->X = third.X; @@ -2300,6 +2317,7 @@ private static unsafe void GetFlatVertices(DynamicVertex[] vertices, int startIn vertex->PrevV = third.PrevV; vertex->SurfaceOptions = surfaceOptions; vertex->RenderOptions = renderOptions; + vertex->TextureIndex = textureIndex; } } diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Portals/FloodFill/FloodFillInfo.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Portals/FloodFill/FloodFillInfo.cs index df3bc7965..957ef3d14 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Portals/FloodFill/FloodFillInfo.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Portals/FloodFill/FloodFillInfo.cs @@ -1,4 +1,5 @@ -using System; +using Helion.Render.OpenGL.Texture.Legacy; +using System; namespace Helion.Render.OpenGL.Renderers.Legacy.World.Geometry.Portals.FloodFill; diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Portals/FloodFill/FloodFillProgram.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Portals/FloodFill/FloodFillProgram.cs index b99c391dc..3acb0e683 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Portals/FloodFill/FloodFillProgram.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Portals/FloodFill/FloodFillProgram.cs @@ -30,6 +30,7 @@ protected override string VertexShader() => @" layout(location = 6) in float surfaceOptions; layout(location = 7) in float renderOptions; layout(location = 8) in float mapId; + layout(location = 9) in float textureIndex; flat out float planeZFrag; out vec3 vertexPosFrag; @@ -41,6 +42,7 @@ protected override string VertexShader() => @" flat out float mapIdFrag; flat out float upperFrag; flat out float lowerFrag; + flat out float boundTextureIndex; out float depthFrag; ${SectorColorMapVertexFragVariables} @@ -54,7 +56,6 @@ protected override string VertexShader() => @" uniform float timeFrac; uniform int useSectorColor; uniform int useSectorFog; - uniform int boundTextureIndex; void main() { @@ -78,6 +79,7 @@ void main() gl_Position = mvp * vec4(worldPos, 1.0); depthFrag = gl_Position.${Depth}; + boundTextureIndex = textureIndex; } " .Replace("${LightLevelVertexVariables}", LightLevel.VertexVariables(LightLevelOptions.NoDist)) @@ -102,6 +104,7 @@ protected override string FragmentShader() flat in float mapIdFrag; flat in float upperFrag; flat in float lowerFrag; + flat in float boundTextureIndex; in vec3 vertexPosFrag; in float dist2D; @@ -118,7 +121,6 @@ protected override string FragmentShader() uniform int colormapIndex; uniform int useBrightmaps; uniform int useSectorFog; - uniform int boundTextureIndex; ${LightLevelFragVariables} ${SectorColorMapFragVariables} diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Portals/FloodFill/FloodFillVertex.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Portals/FloodFill/FloodFillVertex.cs index 8c88f24a0..92a97aed4 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Portals/FloodFill/FloodFillVertex.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Portals/FloodFill/FloodFillVertex.cs @@ -4,8 +4,9 @@ namespace Helion.Render.OpenGL.Renderers.Legacy.World.Geometry.Portals.FloodFill; +// TODO textureIndex [StructLayout(LayoutKind.Sequential, Pack = 1)] -public struct FloodFillVertex(Vec3F pos, float prevZ, float planeZ, float prevPlaneZ, float minPlaneZ, float maxPlaneZ, float surfaceOptions, float renderOptions, int mapId) +public struct FloodFillVertex(Vec3F pos, float prevZ, float planeZ, float prevPlaneZ, float minPlaneZ, float maxPlaneZ, float surfaceOptions, float renderOptions, int mapId, int textureIndex = 0) { [VertexAttribute("pos", size: 3)] public Vec3F Pos = pos; @@ -33,4 +34,7 @@ public struct FloodFillVertex(Vec3F pos, float prevZ, float planeZ, float prevPl [VertexAttribute("mapId", size: 1, required: false)] public float MapId = mapId; + + [VertexAttribute("textureId", size: 1)] + public float TextureIndex = textureIndex; } \ No newline at end of file diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/GeometryData.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/GeometryData.cs index 43d1ce8d0..813a9092e 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/GeometryData.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/GeometryData.cs @@ -23,4 +23,9 @@ public void Dispose() { Pipeline.Dispose(); } + + public override string ToString() + { + return Texture.ToString(); + } } diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/GeometryTextureLookup.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/GeometryTextureLookup.cs index 51c7ff289..14bdc0233 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/GeometryTextureLookup.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/GeometryTextureLookup.cs @@ -1,10 +1,17 @@ -using System.Diagnostics.CodeAnalysis; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; namespace Helion.Render.OpenGL.Renderers.Legacy.World.Geometry.Static; public class GeometryTextureLookup { private readonly GeometryTypeLookup m_lookup = new(() => new TextureGeometryLookup()); + private readonly Dictionary m_arrayTextureLookup = []; + + public void AddTextureArrayMap(int arrayTextureIndex, int textureIndex) + { + m_arrayTextureLookup[textureIndex] = arrayTextureIndex; + } public void Clear() { @@ -15,11 +22,15 @@ public void Clear() public bool TryGetValue(GeometryType type, int textureHandle, bool repeatY, [NotNullWhen(true)] out GeometryData? value) { + if (m_arrayTextureLookup.TryGetValue(textureHandle, out var arrayTextureHandle)) + textureHandle = arrayTextureHandle; return m_lookup.Get(type).TryGetValue(textureHandle, repeatY, out value); } public void Add(GeometryType type, int textureHandle, bool repeatY, GeometryData data) { + if (m_arrayTextureLookup.TryGetValue(textureHandle, out var arrayTextureHandle)) + textureHandle = arrayTextureHandle; m_lookup.Get(type).Add(textureHandle, repeatY, data); } } diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/StaticCacheGeometryRenderer.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/StaticCacheGeometryRenderer.cs index 739198753..a762d62a9 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/StaticCacheGeometryRenderer.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/StaticCacheGeometryRenderer.cs @@ -14,6 +14,7 @@ using Helion.Util; using Helion.Util.Assertion; using Helion.Util.Container; +using Helion.Util.Loggers; using Helion.World; using Helion.World.Geometry.Lines; using Helion.World.Geometry.Sectors; @@ -116,7 +117,11 @@ public void UpdateTo(IWorld world) m_geometryRenderer.SetInitRender(); if (!world.SameAsPreviousMap) + { m_skyRenderer.Reset(); + // TODO probably needs to be smarter + BuildTextureArrays(); + } SetupCoverGeometry(world); @@ -131,8 +136,6 @@ public void UpdateTo(IWorld world) AddSectorPlane(sector, SectorPlaneFace.Ceiling, false); } - BuildTextureArrays(world); - if (WorldStatic.Sector3D) { for (int i = 0; i < world.Sectors.Count; i++) @@ -169,18 +172,36 @@ public void UpdateTo(IWorld world) m_worldReload = false; } - private void BuildTextureArrays(IWorld world) + private void BuildTextureArrays() { - // This is currently a test that assumes 64x64 flats - BuildTextureArray(m_geometryRenderer.FlatTextures.ToArray()); + int totalTextures = 0; + int arrayTextures = 0; + var textures = new DynamicArray(1024); + foreach (var index in m_geometryRenderer.FlatTextures) + AddTexture(textures, index); - var textures = new List(); foreach (var index in m_geometryRenderer.WallTexturesRepeat) - textures.Add(m_archiveCollection.TextureManager.GetTexture(index)); + AddTexture(textures, index); + + totalTextures += textures.Count; + textures.Sort(SortTexturesByDimensions); + arrayTextures += BuildTextureArrayFromTextures(textures, true); + + textures.Clear(); + foreach (var index in m_geometryRenderer.WallTexturesClamp) + AddTexture(textures, index); + totalTextures += textures.Count; textures.Sort(SortTexturesByDimensions); + arrayTextures += BuildTextureArrayFromTextures(textures, false); - var arrayTextures = new List(); + HelionLog.Info($"Compressed textures {totalTextures} -> {arrayTextures}"); + } + + private int BuildTextureArrayFromTextures(DynamicArray textures, bool repeatY) + { + int textureCount = 0; + var arrayTextures = new DynamicArray(); var dimension = new Dimension(0, 0); foreach (var texture in textures) { @@ -189,23 +210,32 @@ private void BuildTextureArrays(IWorld world) if (dimension != texture.Image.Dimension) { - if (arrayTextures.Count > 0) - BuildTextureArray(arrayTextures.Select(x => x.Index).ToArray()); + if (arrayTextures.Count > 0) + textureCount += BuildTextureArray(arrayTextures.Data.AsSpan(0, arrayTextures.Length), repeatY); arrayTextures.Clear(); dimension = texture.Image.Dimension; } - arrayTextures.Add(texture); + arrayTextures.Add(texture.Index); } if (arrayTextures.Count > 0) - BuildTextureArray(arrayTextures.Select(x => x.Index).ToArray()); + textureCount += BuildTextureArray(arrayTextures.Data.AsSpan(0, arrayTextures.Length), repeatY); + + return textureCount; + } + + private void AddTexture(DynamicArray textures, int index) + { + var texture = m_archiveCollection.TextureManager.GetTexture(index); + if (texture.Image != null) + textures.Add(texture); } private static int SortTexturesByDimensions(Resources.Texture x, Resources.Texture y) { if (x.Image == null || y.Image == null) - return -1; + throw new NullReferenceException("Texture image must not be null"); if (x.Image.Height == y.Image.Height) return x.Image.Width.CompareTo(y.Image.Width); @@ -213,9 +243,16 @@ private static int SortTexturesByDimensions(Resources.Texture x, Resources.Textu return x.Image.Height.CompareTo(y.Image.Height); } - private void BuildTextureArray(int[] textures) + private int BuildTextureArray(Span textures, bool repeatY) { - m_textureManager.CreateTextureArray(textures, true); + var arrayTexture = m_textureManager.CreateTextureArray(textures, repeatY); + if (arrayTexture == null) + return 0; + + foreach (var index in textures) + m_textureToGeometryLookup.AddTextureArrayMap(arrayTexture.Index, index); + + return 1; } private void World_SectorFogColorChanged(object? sender, SectorFogEvent e) @@ -660,10 +697,10 @@ private static void AddVertices(DynamicArray staticVertices, Span< { ref var v = ref vertices[i]; staticVertices.Data[staticStartIndex + i] = new StaticVertex(v.X, v.Y, v.Z, v.U, v.V, - v.SurfaceOptions, v.LightLevelAdd, v.RenderOptions); + v.SurfaceOptions, v.LightLevelAdd, v.RenderOptions, v.TextureIndex); } - staticVertices.SetLength(staticVertices.Length + vertices.Length); + staticVertices.SetLength(staticVertices.Length + vertices.Length); } private static void CopyVertices(StaticVertex[] staticVertices, Span vertices, int index) @@ -672,7 +709,7 @@ private static void CopyVertices(StaticVertex[] staticVertices, Span(label, vboSize); var pipeline = new VertexPipeline(m_program, vbo, label); @@ -846,8 +885,8 @@ private void AddSectorPlane(Sector sectorForSubsectors, SectorPlaneFace face, bo AddVertices(vertices, renderedVertices); } - public void RenderWalls(IRenderTextureArray renderTextureArray) => - RenderGeometry(m_geometry.GetGeometry(GeometryType.Wall), renderTextureArray); + public void RenderWalls() => + RenderGeometry(m_geometry.GetGeometry(GeometryType.Wall)); public void RenderTwoSidedMiddleWalls() => RenderGeometry(m_geometry.GetGeometry(GeometryType.TwoSidedMiddleWall)); @@ -855,8 +894,8 @@ public void RenderTwoSidedMiddleWalls() => public void RenderMiddle3D() => RenderGeometry(m_geometry.GetGeometry(GeometryType.Middle3D)); - public void RenderFlats(IRenderTextureArray renderTextureArray) => - RenderGeometry(m_geometry.GetGeometry(GeometryType.Flat), renderTextureArray); + public void RenderFlats() => + RenderGeometry(m_geometry.GetGeometry(GeometryType.Flat)); public void Render(GeometryType type) => RenderGeometry(m_geometry.GetGeometry(type)); @@ -891,13 +930,11 @@ private static void RenderCoverInternal(GeometryData? data) data.Pipeline.DrawArrays(); } - private void RenderGeometry(List geometry, IRenderTextureArray? renderTextureArray = null) + private void RenderGeometry(List geometry) { if (geometry.Count == 0) return; - GLLegacyTexture? lastTexture = null; - for (int i = 0; i < geometry.Count; i++) { var data = geometry[i]; @@ -909,21 +946,12 @@ private void RenderGeometry(List geometry, IRenderTextureArray? re // Special case for one-sided walls with no texture. Uses black texture to block rendering so use directly. var texture = isNullCompatTex ? data.Texture - : m_textureManager.GetTexture(data.TextureHandle, repeatY); - - if (renderTextureArray != null && texture.IsArray && texture.ParentArrayTexture != null) - { - renderTextureArray.SetRenderTextureArray(texture.ArrayIndex); - texture = texture.ParentArrayTexture; - } - - if (lastTexture != texture) - { - GL.ActiveTexture(BindTextures.BoundTexture); - texture.Bind(); - } + : data.Texture; + // TODO animation + //m_textureManager.GetTexture(data.TextureHandle, repeatY); - lastTexture = texture; + GL.ActiveTexture(BindTextures.BoundTexture); + texture.Bind(); var brightmapTexture = isNullCompatTex ? null diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/IRenderTextureArray.cs b/Core/Render/OpenGL/Renderers/Legacy/World/IRenderTextureArray.cs deleted file mode 100644 index b32da9c2a..000000000 --- a/Core/Render/OpenGL/Renderers/Legacy/World/IRenderTextureArray.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Helion.Render.OpenGL.Renderers.Legacy.World; - -public interface IRenderTextureArray -{ - public void SetRenderTextureArray(int index); -} diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/InterpolationShader.cs b/Core/Render/OpenGL/Renderers/Legacy/World/InterpolationShader.cs index 2d321bc3e..5a6cceba3 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/InterpolationShader.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/InterpolationShader.cs @@ -27,6 +27,7 @@ protected static string VertexShaderInternal(bool planeClip) => @" layout(location = 4) in vec3 prevPos; layout(location = 5) in vec2 prevUV; layout(location = 6) in float renderOptions; + layout(location = 7) in float textureIndex; out vec2 uvFrag; flat out float alphaFrag; diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/LegacyWorldRenderer.cs b/Core/Render/OpenGL/Renderers/Legacy/World/LegacyWorldRenderer.cs index 15d230fdc..c41f8dded 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/LegacyWorldRenderer.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/LegacyWorldRenderer.cs @@ -357,9 +357,9 @@ protected override void PerformRender(IWorld world, RenderInfo renderInfo, GLFra GL.ActiveTexture(BindTextures.BoundTexture); SetStaticUniforms(m_staticProgram, renderInfo); m_staticProgram.VertexGapClampUV(m_pixelGapCorrection); - m_geometryRenderer.RenderStaticGeometryWalls(m_staticProgram); + m_geometryRenderer.RenderStaticGeometryWalls(); m_staticProgram.VertexGapClampUV(false); - m_geometryRenderer.RenderStaticGeometryFlats(m_staticProgram); + m_geometryRenderer.RenderStaticGeometryFlats(); } RenderTwoSidedMiddleWalls(renderInfo); @@ -387,12 +387,12 @@ protected override void PerformRender(IWorld world, RenderInfo renderInfo, GLFra GL.ActiveTexture(BindTextures.BoundTexture); SetStaticUniforms(m_staticProgram, renderInfo); m_staticProgram.VertexGapClampUV(m_pixelGapCorrection); - m_geometryRenderer.RenderStaticGeometryWalls(m_staticProgram); + m_geometryRenderer.RenderStaticGeometryWalls(); if (m_downscaleVanillaBuffer) { m_staticProgram.VertexGapClampUV(false); - m_geometryRenderer.RenderStaticGeometryFlats(m_staticProgram); + m_geometryRenderer.RenderStaticGeometryFlats(); } } @@ -561,7 +561,7 @@ private void WritePlaneClipData(PlaneClipFrameBuffer planeClipFrameBuffer, Rende GL.ActiveTexture(BindTextures.BoundTexture); program.VertexGapClampUV(false); SetStaticUniforms(program, renderInfo); - m_geometryRenderer.RenderStaticGeometryFlats(program); + m_geometryRenderer.RenderStaticGeometryFlats(); } } diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/RenderProgramBase.cs b/Core/Render/OpenGL/Renderers/Legacy/World/RenderProgramBase.cs index f37647299..527c39b1e 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/RenderProgramBase.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/RenderProgramBase.cs @@ -6,7 +6,7 @@ namespace Helion.Render.OpenGL.Renderers.Legacy.World; -public abstract class RenderProgramBase : RenderProgram, IRenderTextureArray +public abstract class RenderProgramBase : RenderProgram { private readonly int m_boundTextureLocation; private readonly int m_sectorLightTextureLocation; @@ -38,7 +38,6 @@ public abstract class RenderProgramBase : RenderProgram, IRenderTextureArray private readonly int m_fogBarrierLocation; private readonly int m_useSectorColorLocation; private readonly int m_useSectorFogLocation; - private readonly int m_boundTextureIndexLocation; public RenderProgramBase(string label) : base(label) { @@ -72,9 +71,6 @@ public RenderProgramBase(string label) : base(label) m_fogBarrierLocation = Uniforms.GetLocation("fogBarrier"); m_useSectorColorLocation = Uniforms.GetLocation("useSectorColor"); m_useSectorFogLocation = Uniforms.GetLocation("useSectorFog"); - m_boundTextureIndexLocation = Uniforms.GetLocation("boundTextureIndex"); - - SetRenderTextureArray(0); } public void BoundTexture(TextureUnit unit) => ProgramUniforms.Set(unit, m_boundTextureLocation); @@ -108,6 +104,4 @@ public RenderProgramBase(string label) : base(label) public void FogBarrier(bool value) => ProgramUniforms.Set(value, m_fogBarrierLocation); public void UseSectorColor(bool value) => ProgramUniforms.Set(value, m_useSectorColorLocation); public void UseSectorFog(int value) => ProgramUniforms.Set(value, m_useSectorFogLocation); - - public void SetRenderTextureArray(int index) => ProgramUniforms.Set(index, m_boundTextureIndexLocation); } diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/StaticShader.cs b/Core/Render/OpenGL/Renderers/Legacy/World/StaticShader.cs index a5e4c0796..5e2dda3d7 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/StaticShader.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/StaticShader.cs @@ -20,6 +20,7 @@ private static string VertexShaderInternal(bool planeClip) => @" layout(location = 2) in float lightLevelAdd; layout(location = 3) in float surfaceOptions; layout(location = 4) in float renderOptions; + layout(location = 5) in float textureIndex; out vec2 uvFrag; flat out float alphaFrag; @@ -31,6 +32,7 @@ private static string VertexShaderInternal(bool planeClip) => @" flat out float mapIdFrag; flat out float upperFrag; flat out float lowerFrag; + flat out float boundTextureIndex; out float depthFrag; ${VertexGapVariables} ${VertexDistVar3D} @@ -46,7 +48,6 @@ private static string VertexShaderInternal(bool planeClip) => @" uniform sampler2DArray boundTexture; uniform int useSectorColor; uniform int useSectorFog; - uniform int boundTextureIndex; void main() { uvFrag = uv; @@ -65,6 +66,7 @@ void main() { gl_Position = mvp * mixPos; zPos = pos.z; depthFrag = gl_Position.${Depth}; + boundTextureIndex = textureIndex; } " .Replace("${LightLevelVertexVariables}", planeClip ? "" : LightLevel.VertexVariables(LightLevelOptions.Default)) @@ -110,6 +112,7 @@ protected override string FragmentShader() flat in float mapIdFrag; flat in float upperFrag; flat in float lowerFrag; + flat in float boundTextureIndex; in float depthFrag; ${VertexGapVariables} ${VertexDistVar3D} @@ -129,7 +132,6 @@ protected override string FragmentShader() uniform float downScaleAmount; uniform ivec2 screenBounds; uniform int useSectorFog; - uniform int boundTextureIndex; ${LightLevelFragVariables} ${SectorColorMapFragVariables} diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/StaticVertex.cs b/Core/Render/OpenGL/Renderers/Legacy/World/StaticVertex.cs index 4bd5a67cb..d82f0fb3c 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/StaticVertex.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/StaticVertex.cs @@ -4,7 +4,7 @@ namespace Helion.Render.OpenGL.Renderers.Legacy.World; [StructLayout(LayoutKind.Sequential, Pack = 1)] -public struct StaticVertex(float x, float y, float z, float u, float v, float surfaceOptions, float lightLevelAdd, float renderOptions) +public struct StaticVertex(float x, float y, float z, float u, float v, float surfaceOptions, float lightLevelAdd, float renderOptions, float textureIndex) { [VertexAttribute("pos", size: 3)] public float X = x; @@ -23,4 +23,7 @@ public struct StaticVertex(float x, float y, float z, float u, float v, float su [VertexAttribute] public float RenderOptions = renderOptions; + + [VertexAttribute] + public float TextureIndex = textureIndex; } diff --git a/Core/Render/OpenGL/Texture/GLTexture.cs b/Core/Render/OpenGL/Texture/GLTexture.cs index da88ba6e2..eb494cc45 100644 --- a/Core/Render/OpenGL/Texture/GLTexture.cs +++ b/Core/Render/OpenGL/Texture/GLTexture.cs @@ -21,8 +21,8 @@ public abstract class GLTexture : IRenderableTextureHandle, IDisposable public readonly ResourceNamespace Namespace; public readonly TextureTarget Target; public readonly int TransparentPixelCount; + private readonly bool m_ownsTexture; private bool m_disposed; - private bool m_ownsTexture; public int Width => Dimension.Width; public int Height => Dimension.Height; diff --git a/Core/Render/OpenGL/Texture/GLTextureManager.cs b/Core/Render/OpenGL/Texture/GLTextureManager.cs index ce187e849..ea2cfaddf 100644 --- a/Core/Render/OpenGL/Texture/GLTextureManager.cs +++ b/Core/Render/OpenGL/Texture/GLTextureManager.cs @@ -4,13 +4,14 @@ using Helion.Graphics.Fonts; using Helion.Render.Common.Textures; using Helion.Render.OpenGL.Shared; -using Helion.Render.OpenGL.Texture.Legacy; using Helion.Resources; using Helion.Resources.Archives.Collection; using Helion.Resources.Definitions.Zdoom; using Helion.Resources.Definitions.ZDoom; using Helion.Util; +using Helion.Util.Assertion; using Helion.Util.Configs; +using Helion.Util.Container; using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; @@ -35,6 +36,7 @@ public abstract class GLTextureManager : IRendererTextureManager protected readonly ResourceTracker TextureTracker = new(); protected readonly ResourceTracker TextureTrackerClamp = new(); private readonly Dictionary> m_fonts = new(StringComparer.OrdinalIgnoreCase); + private readonly DynamicArray m_arrayTextures = new(256); private bool m_disposed; private TextureManager TextureManager => ArchiveCollection.TextureManager; @@ -209,12 +211,10 @@ public GLTextureType GetTexture(int index, bool repeatY = true) return (GLTextureType)renderTexture; } - private List m_arrayTextures = []; - - public void CreateTextureArray(int[] indices, bool repeatY) + public GLTextureType? CreateTextureArray(Span indices, bool repeatY) { if (indices.Length == 0) - return; + return null; var images = new Image[indices.Length]; var textures = new Resources.Texture[indices.Length]; @@ -224,22 +224,19 @@ public void CreateTextureArray(int[] indices, bool repeatY) var index = indices[i]; TextureManager.EnsureTextureImageLoaded(index); var texture = TextureManager.GetTexture(index); - // TODO this can't be null - if (texture.Image == null) - { - int x = 1; - } + Assert.Precondition(texture.Image != null, "Texture must have an image"); - images[i] = texture.Image; + images[i] = texture.Image ?? new Image(default, ImageType.Argb); textures[i] = texture; } var flags = repeatY ? TextureFlags.Default : TextureFlags.ClampY; - var glTextures = GenerateTextureArray(images, images[0].Dimension, ResourceNamespace.Textures, flags); + var glTextures = GenerateTextureArray(images, images[0].Dimension, ResourceNamespace.Textures, flags, out var arrayTexture); for (int i = 0; i < images.Length; i++) textures[i].SetGLTexture(glTextures[i], repeatY); m_arrayTextures.Add(glTextures); + return arrayTexture; } public GLTextureType? GetBrightmapTexture(int index, bool repeatY = true) @@ -472,7 +469,7 @@ private void ReleaseUnmanagedResources() } protected abstract GLTextureType GenerateTexture(Image image, string name, ResourceNamespace resourceNamespace, TextureFlags flags = TextureFlags.Default); - protected abstract GLTextureType[] GenerateTextureArray(Image[] images, Dimension dimension, ResourceNamespace resourceNamespace, TextureFlags flags); + protected abstract GLTextureType[] GenerateTextureArray(Image[] images, Dimension dimension, ResourceNamespace resourceNamespace, TextureFlags flags, out GLTextureType? arrayTexture); public abstract void ReUpload(GLTextureType texture, Image image, uint[] imagePixels); diff --git a/Core/Render/OpenGL/Texture/Legacy/GLLegacyTexture.cs b/Core/Render/OpenGL/Texture/Legacy/GLLegacyTexture.cs index 7eafa7eb6..277cee4d2 100644 --- a/Core/Render/OpenGL/Texture/Legacy/GLLegacyTexture.cs +++ b/Core/Render/OpenGL/Texture/Legacy/GLLegacyTexture.cs @@ -14,7 +14,7 @@ public class GLLegacyTexture : GLTexture public GLLegacyTexture(int textureId, string name, Dimension dimension, Vec2I offset, ResourceNamespace ns, TextureTarget target, int transparentPixelCount, int blankRowsFromTop = 0, int blankRowsFromBottom = 0, bool ownsTexture = true) - : base(textureId, name, dimension, offset, ns, TextureTarget.Texture2DArray, transparentPixelCount, blankRowsFromTop, blankRowsFromBottom) + : base(textureId, name, dimension, offset, ns, TextureTarget.Texture2DArray, transparentPixelCount, blankRowsFromTop, blankRowsFromBottom, ownsTexture) { } @@ -28,5 +28,5 @@ public void Unbind() GL.BindTexture(Target, 0); } - public override string ToString() => $"{TextureId}:{Name}"; + public override string ToString() => $"{TextureId}:{Name} [{Dimension}]"; } diff --git a/Core/Render/OpenGL/Texture/Legacy/LegacyGLTextureManager.cs b/Core/Render/OpenGL/Texture/Legacy/LegacyGLTextureManager.cs index 18617799d..1b23899ae 100644 --- a/Core/Render/OpenGL/Texture/Legacy/LegacyGLTextureManager.cs +++ b/Core/Render/OpenGL/Texture/Legacy/LegacyGLTextureManager.cs @@ -220,10 +220,10 @@ protected override GLLegacyTexture GenerateTexture(Image image, string name, return texture; } - protected override GLLegacyTexture[] GenerateTextureArray(Image[] images, Dimension dimension, ResourceNamespace resourceNamespace, TextureFlags flags) + protected override GLLegacyTexture[] GenerateTextureArray(Image[] images, Dimension dimension, ResourceNamespace resourceNamespace, TextureFlags flags, out GLLegacyTexture? arrayTexture) { int textureId = GL.GenTexture(); - var arrayTexture = new GLLegacyTexture(textureId, $"Texture Array {dimension}", dimension, default, resourceNamespace, TextureTarget.Texture2DArray, 0, 0, 0); + arrayTexture = new GLLegacyTexture(textureId, $"Texture Array Length={images.Length}", dimension, default, resourceNamespace, TextureTarget.Texture2DArray, 0, 0, 0); UploadAndSetParameters(arrayTexture, images, "", resourceNamespace, flags); var textures = new GLLegacyTexture[images.Length]; diff --git a/Core/Render/OpenGL/Vertex/Attributes.cs b/Core/Render/OpenGL/Vertex/Attributes.cs index 5962bfd99..00205ce36 100644 --- a/Core/Render/OpenGL/Vertex/Attributes.cs +++ b/Core/Render/OpenGL/Vertex/Attributes.cs @@ -162,8 +162,8 @@ private static int GetNextIndex(VertexAttributeAttribute codeAttribute, HashSet< foreach (VaoAttribute attr in ReadStructAttributes()) { ProgramAttribute? progAttr = FindShaderAttribute(shaderAttribs, attr.Name); - if (progAttr == null && attr.Required) - throw new($"Cannot find shader attribute named {attr.Name}, was it optimized out?"); + //if (progAttr == null && attr.Required) + // throw new($"Cannot find shader attribute named {attr.Name}, was it optimized out?"); // TODO: This doesn't work because Size = 1 for Vec2, but we supply Size = 2 for Float. // This means we need to be able to find out if different types are the same size, since From 50951da8eabfb2b5e63b9a8e33d1eb6774276fe2 Mon Sep 17 00:00:00 2001 From: Nick Date: Thu, 10 Sep 2026 08:16:28 -0400 Subject: [PATCH 05/49] remove test code --- .../World/Geometry/Static/RenderGeometry.cs | 21 +------------------ 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/RenderGeometry.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/RenderGeometry.cs index 8cb5182f8..1f9f5e2c5 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/RenderGeometry.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/RenderGeometry.cs @@ -1,5 +1,4 @@ using Helion.Util.Container; -using System; using System.Collections.Generic; namespace Helion.Render.OpenGL.Renderers.Legacy.World.Geometry.Static; @@ -7,28 +6,10 @@ namespace Helion.Render.OpenGL.Renderers.Legacy.World.Geometry.Static; public class RenderGeometry { private readonly GeometryTypeLookup> m_lookup = new(() => new List()); - private readonly Comparison m_sortByTextureArray; - - public RenderGeometry() - { - m_sortByTextureArray = SortByTextureArray; - } public void AddGeometry(GeometryType type, GeometryData data) { - var geometry = m_lookup.Get(type); - geometry.Add(data); - geometry.Sort(m_sortByTextureArray); - } - - private static int SortByTextureArray(GeometryData x, GeometryData y) - { - if (x.Texture.IsArray && !y.Texture.IsArray) - return 0; - if (!x.Texture.IsArray && y.Texture.IsArray) - return 1; - - return x.Texture.Dimension.Height.CompareTo(y.Texture.Dimension.Height); + m_lookup.Get(type).Add(data); } public List GetGeometry(GeometryType type) From 2f05f474d0d1799664ea812bc16387032df72f8c Mon Sep 17 00:00:00 2001 From: Nick Date: Thu, 10 Sep 2026 12:13:52 -0400 Subject: [PATCH 06/49] move array texture lookup to gl texture manager --- .../Geometry/Static/GeometryTextureLookup.cs | 18 ++++------- .../Static/StaticCacheGeometryRenderer.cs | 7 ++-- .../Render/OpenGL/Texture/GLTextureManager.cs | 32 +++++++++++++------ .../Texture/Legacy/LegacyGLTextureManager.cs | 2 +- 4 files changed, 32 insertions(+), 27 deletions(-) diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/GeometryTextureLookup.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/GeometryTextureLookup.cs index 14bdc0233..d45340fb0 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/GeometryTextureLookup.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/GeometryTextureLookup.cs @@ -1,17 +1,13 @@ -using System.Collections.Generic; +using Helion.Render.OpenGL.Texture; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; namespace Helion.Render.OpenGL.Renderers.Legacy.World.Geometry.Static; -public class GeometryTextureLookup +public class GeometryTextureLookup(IArrayTextureLookup arrayTextureLookup) { private readonly GeometryTypeLookup m_lookup = new(() => new TextureGeometryLookup()); - private readonly Dictionary m_arrayTextureLookup = []; - - public void AddTextureArrayMap(int arrayTextureIndex, int textureIndex) - { - m_arrayTextureLookup[textureIndex] = arrayTextureIndex; - } + private readonly IArrayTextureLookup m_arrayTextureLookup = arrayTextureLookup; public void Clear() { @@ -22,15 +18,13 @@ public void Clear() public bool TryGetValue(GeometryType type, int textureHandle, bool repeatY, [NotNullWhen(true)] out GeometryData? value) { - if (m_arrayTextureLookup.TryGetValue(textureHandle, out var arrayTextureHandle)) - textureHandle = arrayTextureHandle; + textureHandle = m_arrayTextureLookup.GetArrayTextureHandle(textureHandle); return m_lookup.Get(type).TryGetValue(textureHandle, repeatY, out value); } public void Add(GeometryType type, int textureHandle, bool repeatY, GeometryData data) { - if (m_arrayTextureLookup.TryGetValue(textureHandle, out var arrayTextureHandle)) - textureHandle = arrayTextureHandle; + textureHandle = m_arrayTextureLookup.GetArrayTextureHandle(textureHandle); m_lookup.Get(type).Add(textureHandle, repeatY, data); } } diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/StaticCacheGeometryRenderer.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/StaticCacheGeometryRenderer.cs index a762d62a9..66c0494bb 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/StaticCacheGeometryRenderer.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/StaticCacheGeometryRenderer.cs @@ -43,7 +43,7 @@ public partial class StaticCacheGeometryRenderer : StyleRendererBase, IDisposabl private readonly RenderProgram m_program; private readonly RenderGeometry m_geometry = new(); - private readonly GeometryTextureLookup m_textureToGeometryLookup = new(); + private readonly GeometryTextureLookup m_textureToGeometryLookup; private readonly FreeGeometryManager m_freeManager = new(); private readonly LegacySkyRenderer m_skyRenderer; @@ -74,6 +74,7 @@ public StaticCacheGeometryRenderer(ArchiveCollection archiveCollection, LegacyGL m_floodFillRenderer = geometryRenderer.Portals.GetStaticFloodFillRenderer(); m_program = program; m_skyRenderer = new(archiveCollection, textureManager); + m_textureToGeometryLookup = new(textureManager); m_renderCoverWallAction = AddOrUpdateCoverWall; m_renderOneSidedSliceFunc = m_geometryRenderer.RenderOneSidedSlice; @@ -248,10 +249,6 @@ private int BuildTextureArray(Span textures, bool repeatY) var arrayTexture = m_textureManager.CreateTextureArray(textures, repeatY); if (arrayTexture == null) return 0; - - foreach (var index in textures) - m_textureToGeometryLookup.AddTextureArrayMap(arrayTexture.Index, index); - return 1; } diff --git a/Core/Render/OpenGL/Texture/GLTextureManager.cs b/Core/Render/OpenGL/Texture/GLTextureManager.cs index ea2cfaddf..e4d2be85a 100644 --- a/Core/Render/OpenGL/Texture/GLTextureManager.cs +++ b/Core/Render/OpenGL/Texture/GLTextureManager.cs @@ -28,7 +28,13 @@ public enum TextureFlags ClampY = 2, } -public abstract class GLTextureManager : IRendererTextureManager +public interface IArrayTextureLookup +{ + // Returns unique base array texture handle given a texture handle from the TextureManager + int GetArrayTextureHandle(int textureHandle); +} + +public abstract class GLTextureManager : IRendererTextureManager, IArrayTextureLookup where GLTextureType : GLTexture { protected readonly IConfig Config; @@ -37,6 +43,7 @@ public abstract class GLTextureManager : IRendererTextureManager protected readonly ResourceTracker TextureTrackerClamp = new(); private readonly Dictionary> m_fonts = new(StringComparer.OrdinalIgnoreCase); private readonly DynamicArray m_arrayTextures = new(256); + private readonly Dictionary m_arrayTextureLookup = []; private bool m_disposed; private TextureManager TextureManager => ArchiveCollection.TextureManager; @@ -233,12 +240,25 @@ public GLTextureType GetTexture(int index, bool repeatY = true) var flags = repeatY ? TextureFlags.Default : TextureFlags.ClampY; var glTextures = GenerateTextureArray(images, images[0].Dimension, ResourceNamespace.Textures, flags, out var arrayTexture); for (int i = 0; i < images.Length; i++) - textures[i].SetGLTexture(glTextures[i], repeatY); + { + var glTexture = glTextures[i]; + var texture = textures[i]; + // Flip high bit to ensure no collisions + m_arrayTextureLookup[texture.Index] = arrayTexture.TextureId | (1 << 30); + textures[i].SetGLTexture(glTexture, repeatY); + } m_arrayTextures.Add(glTextures); return arrayTexture; } + public int GetArrayTextureHandle(int textureHandle) + { + if (m_arrayTextureLookup.TryGetValue(textureHandle, out var handle)) + return handle; + return textureHandle; + } + public GLTextureType? GetBrightmapTexture(int index, bool repeatY = true) { var texture = TextureManager.GetTexture(index); @@ -434,12 +454,6 @@ protected GLTextureType CreateTexture(Image? image, string? name, ResourceNamesp return texture; } - //protected GLTextureType[] CreateTextureArray(Image[] images, Dimension dimension, ResourceNamespace resourceNamespace, bool repeatY) - //{ - // TextureFlags flags = repeatY ? TextureFlags.Default : TextureFlags.ClampY; - // return GenerateTextureArray(images, dimension, resourceNamespace, flags); - //} - private ResourceTracker GetTextureTracker(bool repeatY) => repeatY ? TextureTracker : TextureTrackerClamp; @@ -469,7 +483,7 @@ private void ReleaseUnmanagedResources() } protected abstract GLTextureType GenerateTexture(Image image, string name, ResourceNamespace resourceNamespace, TextureFlags flags = TextureFlags.Default); - protected abstract GLTextureType[] GenerateTextureArray(Image[] images, Dimension dimension, ResourceNamespace resourceNamespace, TextureFlags flags, out GLTextureType? arrayTexture); + protected abstract GLTextureType[] GenerateTextureArray(Image[] images, Dimension dimension, ResourceNamespace resourceNamespace, TextureFlags flags, out GLTextureType arrayTexture); public abstract void ReUpload(GLTextureType texture, Image image, uint[] imagePixels); diff --git a/Core/Render/OpenGL/Texture/Legacy/LegacyGLTextureManager.cs b/Core/Render/OpenGL/Texture/Legacy/LegacyGLTextureManager.cs index 1b23899ae..66a0cb587 100644 --- a/Core/Render/OpenGL/Texture/Legacy/LegacyGLTextureManager.cs +++ b/Core/Render/OpenGL/Texture/Legacy/LegacyGLTextureManager.cs @@ -220,7 +220,7 @@ protected override GLLegacyTexture GenerateTexture(Image image, string name, return texture; } - protected override GLLegacyTexture[] GenerateTextureArray(Image[] images, Dimension dimension, ResourceNamespace resourceNamespace, TextureFlags flags, out GLLegacyTexture? arrayTexture) + protected override GLLegacyTexture[] GenerateTextureArray(Image[] images, Dimension dimension, ResourceNamespace resourceNamespace, TextureFlags flags, out GLLegacyTexture arrayTexture) { int textureId = GL.GenTexture(); arrayTexture = new GLLegacyTexture(textureId, $"Texture Array Length={images.Length}", dimension, default, resourceNamespace, TextureTarget.Texture2DArray, 0, 0, 0); From 4df742cf6965ac2b459b8ed13d5b82c30335b20f Mon Sep 17 00:00:00 2001 From: Nick Date: Thu, 10 Sep 2026 12:50:26 -0400 Subject: [PATCH 07/49] support in flood fill --- .../Portals/FloodFill/FloodFillInfo.cs | 2 +- .../Portals/FloodFill/FloodFillProgram.cs | 4 +- .../Portals/FloodFill/FloodFillRenderer.cs | 47 ++++++++++--------- .../Portals/FloodFill/FloodFillVertex.cs | 9 ++-- .../World/Geometry/Static/GeometryData.cs | 4 +- .../Static/StaticCacheGeometryRenderer.cs | 4 +- .../Texture/Legacy/LegacyGLTextureManager.cs | 6 +++ 7 files changed, 41 insertions(+), 35 deletions(-) diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Portals/FloodFill/FloodFillInfo.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Portals/FloodFill/FloodFillInfo.cs index 957ef3d14..c5a1ce850 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Portals/FloodFill/FloodFillInfo.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Portals/FloodFill/FloodFillInfo.cs @@ -3,7 +3,7 @@ namespace Helion.Render.OpenGL.Renderers.Legacy.World.Geometry.Portals.FloodFill; -public record FloodFillInfo(int TextureHandle, double Z, VertexPipeline Pipeline) : IDisposable +public record FloodFillInfo(int TextureHandle, GLLegacyTexture Texture, double Z, VertexPipeline Pipeline) : IDisposable { private bool m_disposed; diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Portals/FloodFill/FloodFillProgram.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Portals/FloodFill/FloodFillProgram.cs index 3acb0e683..b3ee0528a 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Portals/FloodFill/FloodFillProgram.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Portals/FloodFill/FloodFillProgram.cs @@ -29,8 +29,8 @@ protected override string VertexShader() => @" layout(location = 5) in float prevPlaneZ; layout(location = 6) in float surfaceOptions; layout(location = 7) in float renderOptions; - layout(location = 8) in float mapId; - layout(location = 9) in float textureIndex; + layout(location = 8) in float textureIndex; + layout(location = 9) in float mapId; flat out float planeZFrag; out vec3 vertexPosFrag; diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Portals/FloodFill/FloodFillRenderer.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Portals/FloodFill/FloodFillRenderer.cs index 649fe43dd..1aa637dc7 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Portals/FloodFill/FloodFillRenderer.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Portals/FloodFill/FloodFillRenderer.cs @@ -52,16 +52,19 @@ private FloodFillInfo CreateFloodFillInfo(SectorPlane plane) { var label = GetLabel(plane); var pipeline = new VertexPipeline(m_program, new StaticVertexBuffer(label), label); - return new(plane.TextureHandle, plane.Z, pipeline); + + var texture = m_glTextureManager.GetArrayTextureOrDefault(plane.TextureHandle); + return new(plane.TextureHandle, texture, plane.Z, pipeline); } - private unsafe FloodFillInfo GetOrCreateFloodFillInfo(SectorPlane plane) + private FloodFillInfo GetOrCreateFloodFillInfo(SectorPlane plane) { - if (m_textureHandleToFloodFillInfoIndex.TryGetValue(plane.TextureHandle, out int index)) + var textureHandle = m_glTextureManager.GetArrayTextureHandle(plane.TextureHandle); + if (m_textureHandleToFloodFillInfoIndex.TryGetValue(textureHandle, out int index)) return m_floodFillInfos[index]; - FloodFillInfo floodInfo = CreateFloodFillInfo(plane); - m_textureHandleToFloodFillInfoIndex[plane.TextureHandle] = m_floodFillInfos.Count; + var floodInfo = CreateFloodFillInfo(plane); + m_textureHandleToFloodFillInfoIndex[textureHandle] = m_floodFillInfos.Count; m_floodFillInfos.Add(floodInfo); return floodInfo; } @@ -112,15 +115,16 @@ public void UpdateStaticWall(int floodKey, SectorPlane floodPlane, WallVertices var upper = sideTexture == SideTexture.Upper ? 1 : 0; var lower = sideTexture == SideTexture.Lower ? 1 : 0; var options = VertexOptions.PackSurface(0, 0, 0, upper, lower, data.OverrideLightIndex); + var textureIndex = m_glTextureManager.GetTexture(floodPlane.TextureHandle).ArrayIndex; FloodFillVertex topLeft = new((vertices.TopLeft.X, vertices.TopLeft.Y, topZ), - prevTopZ, planeZ, prevPlaneZ, minZ, maxZ, options, data.RenderOptions, mapId); + prevTopZ, planeZ, prevPlaneZ, minZ, maxZ, options, data.RenderOptions, mapId, textureIndex); FloodFillVertex topRight = new((vertices.BottomRight.X, vertices.BottomRight.Y, topZ), - prevTopZ, planeZ, prevPlaneZ, minZ, maxZ, options, data.RenderOptions, mapId); + prevTopZ, planeZ, prevPlaneZ, minZ, maxZ, options, data.RenderOptions, mapId, textureIndex); FloodFillVertex bottomLeft = new((vertices.TopLeft.X, vertices.TopLeft.Y, bottomZ), - prevBottomZ, planeZ, prevPlaneZ, minZ, maxZ, options, data.RenderOptions, mapId); + prevBottomZ, planeZ, prevPlaneZ, minZ, maxZ, options, data.RenderOptions, mapId, textureIndex); FloodFillVertex bottomRight = new((vertices.BottomRight.X, vertices.BottomRight.Y, bottomZ), - prevBottomZ, planeZ, prevPlaneZ, minZ, maxZ, options, data.RenderOptions, mapId); + prevBottomZ, planeZ, prevPlaneZ, minZ, maxZ, options, data.RenderOptions, mapId, textureIndex); var vbo = floodInfo.Pipeline.Vbo; vbo.Data[data.VboOffset] = topLeft; @@ -132,7 +136,7 @@ public void UpdateStaticWall(int floodKey, SectorPlane floodPlane, WallVertices if (isFloodFillPlane) ProjectFloodPlane(vbo, data.VboOffset + VerticesPerWall, vertices, minZ, maxZ, planeZ, prevPlaneZ, data.OverrideLightIndex, - maxPlaneZ > Constants.MaxTextureHeight ? -Constants.MaxTextureHeight : Constants.MaxTextureHeight, false, data.RenderOptions, mapId); + maxPlaneZ > Constants.MaxTextureHeight ? -Constants.MaxTextureHeight : Constants.MaxTextureHeight, false, data.RenderOptions, mapId, textureIndex); vbo.Bind(); vbo.UploadSubData(data.VboOffset, data.Vertices); @@ -184,15 +188,16 @@ public int AddStaticWall(SectorPlane sectorPlane, in WallVertices vertices, doub var upper = sideTexture == SideTexture.Upper ? 1 : 0; var lower = sideTexture == SideTexture.Lower ? 1 : 0; var options = VertexOptions.PackSurface(0, 0, 0, upper, lower, overrideLightIndex); + var textureIndex = m_glTextureManager.GetTexture(sectorPlane.TextureHandle).ArrayIndex; FloodFillVertex topLeft = new((vertices.TopLeft.X, vertices.TopLeft.Y, vertices.TopLeft.Z), - vertices.TopLeft.Z, planeZ, prevPlaneZ, minZ, maxZ, options, lightBufferIndex, mapId); + vertices.TopLeft.Z, planeZ, prevPlaneZ, minZ, maxZ, options, lightBufferIndex, mapId, textureIndex); FloodFillVertex topRight = new((vertices.BottomRight.X, vertices.BottomRight.Y, vertices.TopLeft.Z), - vertices.TopLeft.Z, planeZ, prevPlaneZ, minZ, maxZ, options, lightBufferIndex, mapId); + vertices.TopLeft.Z, planeZ, prevPlaneZ, minZ, maxZ, options, lightBufferIndex, mapId, textureIndex); FloodFillVertex bottomLeft = new((vertices.TopLeft.X, vertices.TopLeft.Y, vertices.BottomRight.Z), - vertices.BottomRight.Z, planeZ, prevPlaneZ, minZ, maxZ, options, lightBufferIndex, mapId); + vertices.BottomRight.Z, planeZ, prevPlaneZ, minZ, maxZ, options, lightBufferIndex, mapId, textureIndex); FloodFillVertex bottomRight = new((vertices.BottomRight.X, vertices.BottomRight.Y, vertices.BottomRight.Z), - vertices.BottomRight.Z, planeZ, prevPlaneZ, minZ, maxZ, options, lightBufferIndex, mapId); + vertices.BottomRight.Z, planeZ, prevPlaneZ, minZ, maxZ, options, lightBufferIndex, mapId, textureIndex); int offset = vbo.Data.Length; int newLength = vbo.Data.Length + VerticesPerWall; @@ -208,7 +213,7 @@ public int AddStaticWall(SectorPlane sectorPlane, in WallVertices vertices, doub if (isFloodFillPlane) ProjectFloodPlane(vbo, vbo.Data.Length, vertices, minZ, maxZ, planeZ, prevPlaneZ, lightIndex, - maxPlaneZ > Constants.MaxTextureHeight ? -Constants.MaxTextureHeight : Constants.MaxTextureHeight, true, lightIndex, mapId); + maxPlaneZ > Constants.MaxTextureHeight ? -Constants.MaxTextureHeight : Constants.MaxTextureHeight, true, lightIndex, mapId, textureIndex); if (m_renderMode == FloodFillRenderMode.Dynamic) return 0; @@ -217,7 +222,7 @@ public int AddStaticWall(SectorPlane sectorPlane, in WallVertices vertices, doub } private static void ProjectFloodPlane(VertexBufferObject vbo, int startIndex, - WallVertices vertices, float minZ, float maxZ, float planeZ, float prevPlaneZ, int overrideLightIndex, int addHeight, bool add, float renderOptions, int mapId) + WallVertices vertices, float minZ, float maxZ, float planeZ, float prevPlaneZ, int overrideLightIndex, int addHeight, bool add, float renderOptions, int mapId, int textureIndex) { int newLength = startIndex + FloodPlaneAddCount * VerticesPerWall; vbo.Data.EnsureCapacity(newLength); @@ -233,13 +238,13 @@ private static void ProjectFloodPlane(VertexBufferObject vbo, i var surfaceOptions = VertexOptions.PackSurface(0, 0, 0, 0, 0, overrideLightIndex); FloodFillVertex topLeft = new((vertices.TopLeft.X, vertices.TopLeft.Y, topLeftZ), - prevTopZ, planeZ, prevPlaneZ, minZ, maxZ, surfaceOptions, renderOptions, mapId); + prevTopZ, planeZ, prevPlaneZ, minZ, maxZ, surfaceOptions, renderOptions, mapId, textureIndex); FloodFillVertex topRight = new((vertices.BottomRight.X, vertices.BottomRight.Y, topLeftZ), - prevTopZ, planeZ, prevPlaneZ, minZ, maxZ, surfaceOptions, renderOptions, mapId); + prevTopZ, planeZ, prevPlaneZ, minZ, maxZ, surfaceOptions, renderOptions, mapId, textureIndex); FloodFillVertex bottomLeft = new((vertices.TopLeft.X, vertices.TopLeft.Y, bottomRightZ), - prevBottomZ, planeZ, prevPlaneZ, minZ, maxZ, surfaceOptions, renderOptions, mapId); + prevBottomZ, planeZ, prevPlaneZ, minZ, maxZ, surfaceOptions, renderOptions, mapId, textureIndex); FloodFillVertex bottomRight = new((vertices.BottomRight.X, vertices.BottomRight.Y, bottomRightZ), - prevBottomZ, planeZ, prevPlaneZ, minZ, maxZ, surfaceOptions, renderOptions, mapId); + prevBottomZ, planeZ, prevPlaneZ, minZ, maxZ, surfaceOptions, renderOptions, mapId, textureIndex); buffer[startIndex++] = topLeft; buffer[startIndex++] = bottomLeft; @@ -288,7 +293,7 @@ private static void OverwriteAndSubUploadVboWithZero(VertexBufferObject(label, vboSize); var pipeline = new VertexPipeline(m_program, vbo, label); diff --git a/Core/Render/OpenGL/Texture/Legacy/LegacyGLTextureManager.cs b/Core/Render/OpenGL/Texture/Legacy/LegacyGLTextureManager.cs index 66a0cb587..6f0c08c86 100644 --- a/Core/Render/OpenGL/Texture/Legacy/LegacyGLTextureManager.cs +++ b/Core/Render/OpenGL/Texture/Legacy/LegacyGLTextureManager.cs @@ -44,6 +44,12 @@ public void UnRegisterTexture(GLLegacyTexture texture) m_registeredTextures.Remove(texture); } + public GLLegacyTexture GetArrayTextureOrDefault(int index, bool repeatY = true) + { + var texture = GetTexture(index, repeatY); + return texture.ParentArrayTexture ?? texture; + } + private void HandleFilterChange(object? sender, FilterType e) { UpdateTextureTrackerFilter(TextureTracker); From a2fe7c3ca6fa2391bd2660084eaca9f126030ba2 Mon Sep 17 00:00:00 2001 From: Nick Date: Thu, 10 Sep 2026 13:10:48 -0400 Subject: [PATCH 08/49] support texture arrays for dynamic rendering --- .../Legacy/World/Geometry/GeometryRenderer.cs | 15 ++++++++++----- .../Portals/FloodFill/FloodFillRenderer.cs | 2 +- .../Static/StaticCacheGeometryRenderer.cs | 2 +- .../Renderers/Legacy/World/InterpolationShader.cs | 5 +++-- .../Texture/Legacy/LegacyGLTextureManager.cs | 2 +- 5 files changed, 16 insertions(+), 10 deletions(-) diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/GeometryRenderer.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/GeometryRenderer.cs index e42c86e03..16feca4f2 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/GeometryRenderer.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/GeometryRenderer.cs @@ -871,7 +871,8 @@ public void RenderOneSided(Side side, bool isFront, out DynamicVertex[]? vertice if (m_buffer) { var geometryType = GetGeometryType(style, baseType); - var renderData = m_worldDataManager.GetRenderData(texture, geometryType, brightmapTexture); + var arrayTexture = m_glTextureManager?.GetParentArrayTexture(side.Middle.TextureHandle) ?? TestTexture; + var renderData = m_worldDataManager.GetRenderData(arrayTexture, geometryType, brightmapTexture); renderData.Pipeline.Vbo.AddMemoryCopy(data); if (m_vanillaRender && baseType == GeometryType.Wall && style == RenderDataStyle.Normal) m_worldDataManager.AddCoverWallVertices(side, data, side.Middle.Location, true); @@ -1228,7 +1229,8 @@ public void RenderTwoSidedLower(Side facingSide, Side otherSide, Sector facingSe if (m_buffer) { - var renderData = m_worldDataManager.GetRenderData(texture, GeometryType.Wall, brightmapTexture); + var arrayTexture = m_glTextureManager.GetParentArrayTexture(lowerWall.TextureHandle); + var renderData = m_worldDataManager.GetRenderData(arrayTexture, GeometryType.Wall, brightmapTexture); renderData.Pipeline.Vbo.AddMemoryCopy(data); } vertices = data; @@ -1349,7 +1351,8 @@ public void RenderTwoSidedUpper(Side facingSide, Side otherSide, Sector facingSe if (m_buffer) { - var renderData = m_worldDataManager.GetRenderData(texture, GeometryType.Wall, brightmapTexture); + var arrayTexture = m_glTextureManager.GetParentArrayTexture(upperWall.TextureHandle); + var renderData = m_worldDataManager.GetRenderData(arrayTexture, GeometryType.Wall, brightmapTexture); renderData.Pipeline.Vbo.AddMemoryCopy(data); } vertices = data; @@ -1535,7 +1538,8 @@ public void RenderTwoSidedMiddle(Side facingSide, Side otherSide, Sector facingS if (m_buffer) { var geometryType = facingSide == m_fogSide ? GeometryType.FogBarrier : GetWallType(facingSide, facingSide.Middle, null); - var renderData = m_worldDataManager.GetRenderData(texture, geometryType, brightmapTexture); + var arrayTexture = m_glTextureManager.GetParentArrayTexture(middleWall.TextureHandle, repeatY: facingSide.Flags.WrapMidTex); + var renderData = m_worldDataManager.GetRenderData(arrayTexture, geometryType, brightmapTexture); renderData.Pipeline.Vbo.AddMemoryCopy(data); } vertices = data; @@ -1827,7 +1831,8 @@ private bool RenderFlat(DynamicArray subsectors, SectorPlane renderPl vertices = lookupData; if (m_buffer) { - var renderData = m_worldDataManager.GetRenderData(texture, geometryType, brightmapTexture); + var arrayTexture = m_glTextureManager.GetParentArrayTexture(textureHandle); + var renderData = m_worldDataManager.GetRenderData(arrayTexture, geometryType, brightmapTexture); renderData.Pipeline.Vbo.AddMemoryCopy(lookupData); // Don't need to clip floor on lower view and ceiling on upper view if (sector.TransferHeights != null diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Portals/FloodFill/FloodFillRenderer.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Portals/FloodFill/FloodFillRenderer.cs index 1aa637dc7..3809a3b86 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Portals/FloodFill/FloodFillRenderer.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Portals/FloodFill/FloodFillRenderer.cs @@ -53,7 +53,7 @@ private FloodFillInfo CreateFloodFillInfo(SectorPlane plane) var label = GetLabel(plane); var pipeline = new VertexPipeline(m_program, new StaticVertexBuffer(label), label); - var texture = m_glTextureManager.GetArrayTextureOrDefault(plane.TextureHandle); + var texture = m_glTextureManager.GetParentArrayTexture(plane.TextureHandle); return new(plane.TextureHandle, texture, plane.Z, pipeline); } diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/StaticCacheGeometryRenderer.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/StaticCacheGeometryRenderer.cs index f1a3a89a2..2aff78b0e 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/StaticCacheGeometryRenderer.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/StaticCacheGeometryRenderer.cs @@ -757,7 +757,7 @@ private GeometryData AllocateGeometryData(GeometryType type, int textureHandle, { vboSize = Math.Max(vboSize, 32); label ??= GetGeometryLabel(type, textureHandle, repeat); - var texture = overrideTexture ?? m_textureManager.GetArrayTextureOrDefault(textureHandle, repeat); + var texture = overrideTexture ?? m_textureManager.GetParentArrayTexture(textureHandle, repeat); var brightmapTexture = m_textureManager.GetBrightmapTexture(textureHandle, repeat); var vbo = new StaticVertexBuffer(label, vboSize); var pipeline = new VertexPipeline(m_program, vbo, label); diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/InterpolationShader.cs b/Core/Render/OpenGL/Renderers/Legacy/World/InterpolationShader.cs index 5a6cceba3..23cecb202 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/InterpolationShader.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/InterpolationShader.cs @@ -39,6 +39,7 @@ protected static string VertexShaderInternal(bool planeClip) => @" flat out float mapIdFrag; flat out float upperFrag; flat out float lowerFrag; + flat out float boundTextureIndex; out float depthFrag; ${VertexGapVariables} ${VertexDistVar3D} @@ -54,7 +55,6 @@ protected static string VertexShaderInternal(bool planeClip) => @" uniform sampler2DArray boundTexture; uniform int useSectorColor; uniform int useSectorFog; - uniform int boundTextureIndex; void main() { ${VertexOptionsSet} @@ -75,6 +75,7 @@ void main() { gl_Position = mvp * mixPos; zPos = mixPos.z; depthFrag = gl_Position.${Depth}; + boundTextureIndex = textureIndex; } " .Replace("${LightLevelVertexVariables}", planeClip ? "" : LightLevel.VertexVariables(LightLevelOptions.Default)) @@ -120,6 +121,7 @@ protected override string FragmentShader() flat in float distFrag; flat in float upperFrag; flat in float lowerFrag; + flat in float boundTextureIndex; in float depthFrag; ${VertexGapVariables} ${VertexDistVar3D} @@ -139,7 +141,6 @@ protected override string FragmentShader() uniform float downScaleAmount; uniform ivec2 screenBounds; uniform int useSectorFog; - uniform int boundTextureIndex; ${LightLevelFragVariables} ${SectorColorMapFragVariables} diff --git a/Core/Render/OpenGL/Texture/Legacy/LegacyGLTextureManager.cs b/Core/Render/OpenGL/Texture/Legacy/LegacyGLTextureManager.cs index 6f0c08c86..91c9f7c7d 100644 --- a/Core/Render/OpenGL/Texture/Legacy/LegacyGLTextureManager.cs +++ b/Core/Render/OpenGL/Texture/Legacy/LegacyGLTextureManager.cs @@ -44,7 +44,7 @@ public void UnRegisterTexture(GLLegacyTexture texture) m_registeredTextures.Remove(texture); } - public GLLegacyTexture GetArrayTextureOrDefault(int index, bool repeatY = true) + public GLLegacyTexture GetParentArrayTexture(int index, bool repeatY = true) { var texture = GetTexture(index, repeatY); return texture.ParentArrayTexture ?? texture; From 4e554f4a7384229517891a110290f974ee726848 Mon Sep 17 00:00:00 2001 From: Nick Date: Thu, 10 Sep 2026 13:23:14 -0400 Subject: [PATCH 09/49] fix lookup --- .../Portals/FloodFill/FloodFillRenderer.cs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Portals/FloodFill/FloodFillRenderer.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Portals/FloodFill/FloodFillRenderer.cs index 3809a3b86..9b42857b5 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Portals/FloodFill/FloodFillRenderer.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Portals/FloodFill/FloodFillRenderer.cs @@ -10,6 +10,7 @@ using Helion.Render.OpenGL.Texture.Legacy; using Helion.Resources; using Helion.Util; +using Helion.Util.Assertion; using Helion.Util.Container; using Helion.World; using Helion.World.Geometry.Sectors; @@ -82,6 +83,14 @@ private ref FloodGeometry TryGetFloodGeometry(int floodKey, out bool success) return ref m_floodGeometry.Data[floodKey]; } + private bool TryGetFloodFillInfoIndex(int textureHandle, out int index) + { + textureHandle = m_glTextureManager.GetArrayTextureHandle(textureHandle); + var success = m_textureHandleToFloodFillInfoIndex.TryGetValue(textureHandle, out index); + Assert.Precondition(success, $"Failed to find flood fill info for {textureHandle}"); + return success; + } + public void UpdateStaticWall(int floodKey, SectorPlane floodPlane, WallVertices vertices, double minPlaneZ, double maxPlaneZ, SideTexture sideTexture, int mapId, bool isFloodFillPlane = false) { @@ -98,7 +107,7 @@ public void UpdateStaticWall(int floodKey, SectorPlane floodPlane, WallVertices return; } - if (!m_textureHandleToFloodFillInfoIndex.TryGetValue(data.TextureHandle, out int index)) + if (!TryGetFloodFillInfoIndex(data.TextureHandle, out int index)) return; FloodFillInfo floodInfo = m_floodFillInfos[index]; @@ -267,7 +276,9 @@ public void ClearStaticWall(int floodKey) ref var data = ref TryGetFloodGeometry(floodKey, out var success); if (success) { - int listIndex = m_textureHandleToFloodFillInfoIndex[data.TextureHandle]; + if (!TryGetFloodFillInfoIndex(data.TextureHandle, out var listIndex)) + return; + FloodFillInfo info = m_floodFillInfos[listIndex]; OverwriteAndSubUploadVboWithZero(info.Pipeline.Vbo, data.VboOffset, data.Vertices); From 0e7266e285ff189ed4b1bae46abe8b3b8e7b05dd Mon Sep 17 00:00:00 2001 From: Nick Date: Thu, 10 Sep 2026 14:25:44 -0400 Subject: [PATCH 10/49] move to GLTextureArrayBuilder and clear texture arrays on map change --- .../Legacy/World/Geometry/GeometryRenderer.cs | 3 + .../Static/StaticCacheGeometryRenderer.cs | 89 +---------------- .../Render/OpenGL/Texture/GLTextureManager.cs | 26 ++++- .../Texture/Legacy/GLTextureArrayBuilder.cs | 97 +++++++++++++++++++ 4 files changed, 126 insertions(+), 89 deletions(-) create mode 100644 Core/Render/OpenGL/Texture/Legacy/GLTextureArrayBuilder.cs diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/GeometryRenderer.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/GeometryRenderer.cs index 16feca4f2..f42c382f1 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/GeometryRenderer.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/GeometryRenderer.cs @@ -231,6 +231,9 @@ public void UpdateTo(IWorld world, bool unitTest = false) if (!unitTest) { + var arrayBuilder = new GLTextureArrayBuilder(m_archiveCollection.TextureManager, m_glTextureManager); + arrayBuilder.BuildLevel(FlatTextures, WallTexturesRepeat, WallTexturesClamp); + Portals.UpdateTo(world); m_staticCacheGeometryRenderer.UpdateTo(world); m_worldDataManager?.InitCoverWallRenderData(m_glTextureManager.WhiteTexture, m_program); diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/StaticCacheGeometryRenderer.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/StaticCacheGeometryRenderer.cs index 2aff78b0e..f4d0b82a6 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/StaticCacheGeometryRenderer.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/StaticCacheGeometryRenderer.cs @@ -1,5 +1,4 @@ -using Helion.Geometry; -using Helion.Geometry.Vectors; +using Helion.Geometry.Vectors; using Helion.Render.OpenGL.Buffer.Array.Vertex; using Helion.Render.OpenGL.Context; using Helion.Render.OpenGL.Renderers.Legacy.World.Data; @@ -14,7 +13,6 @@ using Helion.Util; using Helion.Util.Assertion; using Helion.Util.Container; -using Helion.Util.Loggers; using Helion.World; using Helion.World.Geometry.Lines; using Helion.World.Geometry.Sectors; @@ -118,11 +116,7 @@ public void UpdateTo(IWorld world) m_geometryRenderer.SetInitRender(); if (!world.SameAsPreviousMap) - { m_skyRenderer.Reset(); - // TODO probably needs to be smarter - BuildTextureArrays(); - } SetupCoverGeometry(world); @@ -173,84 +167,7 @@ public void UpdateTo(IWorld world) m_worldReload = false; } - private void BuildTextureArrays() - { - int totalTextures = 0; - int arrayTextures = 0; - var textures = new DynamicArray(1024); - foreach (var index in m_geometryRenderer.FlatTextures) - AddTexture(textures, index); - - foreach (var index in m_geometryRenderer.WallTexturesRepeat) - AddTexture(textures, index); - - totalTextures += textures.Count; - textures.Sort(SortTexturesByDimensions); - arrayTextures += BuildTextureArrayFromTextures(textures, true); - - textures.Clear(); - foreach (var index in m_geometryRenderer.WallTexturesClamp) - AddTexture(textures, index); - - totalTextures += textures.Count; - textures.Sort(SortTexturesByDimensions); - arrayTextures += BuildTextureArrayFromTextures(textures, false); - - HelionLog.Info($"Compressed textures {totalTextures} -> {arrayTextures}"); - } - - private int BuildTextureArrayFromTextures(DynamicArray textures, bool repeatY) - { - int textureCount = 0; - var arrayTextures = new DynamicArray(); - var dimension = new Dimension(0, 0); - foreach (var texture in textures) - { - if (texture.Image == null) - continue; - - if (dimension != texture.Image.Dimension) - { - if (arrayTextures.Count > 0) - textureCount += BuildTextureArray(arrayTextures.Data.AsSpan(0, arrayTextures.Length), repeatY); - arrayTextures.Clear(); - dimension = texture.Image.Dimension; - } - arrayTextures.Add(texture.Index); - } - - if (arrayTextures.Count > 0) - textureCount += BuildTextureArray(arrayTextures.Data.AsSpan(0, arrayTextures.Length), repeatY); - - return textureCount; - } - - private void AddTexture(DynamicArray textures, int index) - { - var texture = m_archiveCollection.TextureManager.GetTexture(index); - if (texture.Image != null) - textures.Add(texture); - } - - private static int SortTexturesByDimensions(Resources.Texture x, Resources.Texture y) - { - if (x.Image == null || y.Image == null) - throw new NullReferenceException("Texture image must not be null"); - - if (x.Image.Height == y.Image.Height) - return x.Image.Width.CompareTo(y.Image.Width); - - return x.Image.Height.CompareTo(y.Image.Height); - } - - private int BuildTextureArray(Span textures, bool repeatY) - { - var arrayTexture = m_textureManager.CreateTextureArray(textures, repeatY); - if (arrayTexture == null) - return 0; - return 1; - } private void World_SectorFogColorChanged(object? sender, SectorFogEvent e) { @@ -941,9 +858,7 @@ private void RenderGeometry(List geometry) // Special case for one-sided walls with no texture. Uses black texture to block rendering so use directly. var texture = isNullCompatTex ? data.Texture - : data.Texture; - // TODO animation - //m_textureManager.GetTexture(data.TextureHandle, repeatY); + : m_textureManager.GetTexture(data.TextureHandle, repeatY); GL.ActiveTexture(BindTextures.BoundTexture); texture.Bind(); diff --git a/Core/Render/OpenGL/Texture/GLTextureManager.cs b/Core/Render/OpenGL/Texture/GLTextureManager.cs index e4d2be85a..c6b54fddd 100644 --- a/Core/Render/OpenGL/Texture/GLTextureManager.cs +++ b/Core/Render/OpenGL/Texture/GLTextureManager.cs @@ -42,7 +42,9 @@ public abstract class GLTextureManager : IRendererTextureManager, protected readonly ResourceTracker TextureTracker = new(); protected readonly ResourceTracker TextureTrackerClamp = new(); private readonly Dictionary> m_fonts = new(StringComparer.OrdinalIgnoreCase); - private readonly DynamicArray m_arrayTextures = new(256); + private readonly DynamicArray m_arraySubTextures = new(256); + private readonly DynamicArray m_arrayTextures = new(256); + private readonly DynamicArray m_texturesForArrays = new(256); private readonly Dictionary m_arrayTextureLookup = []; private bool m_disposed; @@ -99,6 +101,24 @@ private SpriteRotation CreateNullSpriteRotation() Dispose(); } + public void DestroyTextureArrays() + { + for (int i = 0; i < m_arrayTextures.Length; i++) + m_arrayTextures.Data[i].Dispose(); + + for (int i = 0; i < m_texturesForArrays.Length; i++) + { + var texture = m_texturesForArrays.Data[i]; + texture.RenderStore = null; + texture.RenderStoreClamp = null; + } + + m_arrayTextures.Clear(); + m_arraySubTextures.Clear(); + m_arrayTextureLookup.Clear(); + m_texturesForArrays.Clear(); + } + public bool TryGet(string name, [NotNullWhen(true)] out IRenderableTextureHandle? handle, ResourceNamespace? specificNamespace = null, int upscalingFactor = 1, BrightmapDefinition? brightmap = null) { if (TryGet(name, specificNamespace ?? ResourceNamespace.Undefined, out GLTextureType texture, upscalingFactor, brightmap)) @@ -235,6 +255,7 @@ public GLTextureType GetTexture(int index, bool repeatY = true) images[i] = texture.Image ?? new Image(default, ImageType.Argb); textures[i] = texture; + m_texturesForArrays.Add(texture); } var flags = repeatY ? TextureFlags.Default : TextureFlags.ClampY; @@ -248,7 +269,8 @@ public GLTextureType GetTexture(int index, bool repeatY = true) textures[i].SetGLTexture(glTexture, repeatY); } - m_arrayTextures.Add(glTextures); + m_arrayTextures.Add(arrayTexture); + m_arraySubTextures.Add(glTextures); return arrayTexture; } diff --git a/Core/Render/OpenGL/Texture/Legacy/GLTextureArrayBuilder.cs b/Core/Render/OpenGL/Texture/Legacy/GLTextureArrayBuilder.cs new file mode 100644 index 000000000..9edea1442 --- /dev/null +++ b/Core/Render/OpenGL/Texture/Legacy/GLTextureArrayBuilder.cs @@ -0,0 +1,97 @@ +using Helion.Geometry; +using Helion.Resources; +using Helion.Util.Container; +using Helion.Util.Loggers; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Helion.Render.OpenGL.Texture.Legacy; + +public class GLTextureArrayBuilder(TextureManager textureManager, LegacyGLTextureManager glTextureManager) +{ + private readonly TextureManager m_textureManager = textureManager; + private readonly LegacyGLTextureManager m_glTextureManager = glTextureManager; + + public void BuildLevel(IEnumerable flatTextures, IEnumerable wallTexturesRepeat, IEnumerable wallTexturesClamp) + { + m_glTextureManager.DestroyTextureArrays(); + + // Animations are excluded. This leaves them reliant on their previous behavior of swapping textures. + int buildTextures = 0; + int arrayTextures = 0; + var textures = new DynamicArray(1024); + foreach (var index in flatTextures.Where(x => !m_textureManager.IsTextureAnimated(x))) + AddTexture(textures, index); + + foreach (var index in wallTexturesRepeat.Where(x => !m_textureManager.IsTextureAnimated(x))) + AddTexture(textures, index); + + buildTextures += textures.Count; + textures.Sort(SortTexturesByDimensions); + arrayTextures += BuildTextureArrayFromTextures(textures, true); + + textures.Clear(); + foreach (var index in wallTexturesClamp.Where(x => !m_textureManager.IsTextureAnimated(x))) + AddTexture(textures, index); + + buildTextures += textures.Count; + textures.Sort(SortTexturesByDimensions); + arrayTextures += BuildTextureArrayFromTextures(textures, false); + + var totalTextures = flatTextures.Count() + wallTexturesRepeat.Count() + wallTexturesClamp.Count(); + var animated = totalTextures - buildTextures; + HelionLog.Info($"Compressed textures {totalTextures} -> {arrayTextures + animated}"); + } + + private int BuildTextureArrayFromTextures(DynamicArray textures, bool repeatY) + { + int textureCount = 0; + var arrayTextures = new DynamicArray(); + var dimension = new Dimension(0, 0); + foreach (var texture in textures) + { + if (texture.Image == null) + continue; + + if (dimension != texture.Image.Dimension) + { + if (arrayTextures.Count > 0) + textureCount += BuildTextureArray(arrayTextures.Data.AsSpan(0, arrayTextures.Length), repeatY); + arrayTextures.Clear(); + dimension = texture.Image.Dimension; + } + + arrayTextures.Add(texture.Index); + } + + if (arrayTextures.Count > 0) + textureCount += BuildTextureArray(arrayTextures.Data.AsSpan(0, arrayTextures.Length), repeatY); + + return textureCount; + } + + private void AddTexture(DynamicArray textures, int index) + { + var texture = m_textureManager.GetTexture(index); + if (texture.Image != null) + textures.Add(texture); + } + + private static int SortTexturesByDimensions(Resources.Texture x, Resources.Texture y) + { + if (x.Image == null || y.Image == null) + throw new NullReferenceException("Texture image must not be null"); + + if (x.Image.Height == y.Image.Height) + return x.Image.Width.CompareTo(y.Image.Width); + + return x.Image.Height.CompareTo(y.Image.Height); + } + + private int BuildTextureArray(Span textures, bool repeatY) + { + var arrayTexture = m_glTextureManager.CreateTextureArray(textures, repeatY); + return arrayTexture == null ? 0 : 1; + } +} From 3bdcf33f412a6c71bd2e5dd60c6bb0aadf586456 Mon Sep 17 00:00:00 2001 From: Nick Date: Thu, 10 Sep 2026 15:28:02 -0400 Subject: [PATCH 11/49] removing using --- .../Legacy/World/Geometry/Static/GeometryTextureLookup.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/GeometryTextureLookup.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/GeometryTextureLookup.cs index d45340fb0..19971aae5 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/GeometryTextureLookup.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/GeometryTextureLookup.cs @@ -1,5 +1,4 @@ using Helion.Render.OpenGL.Texture; -using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; namespace Helion.Render.OpenGL.Renderers.Legacy.World.Geometry.Static; From 6b188dec59166be755fb587194fc63befd66ddb5 Mon Sep 17 00:00:00 2001 From: Nick Date: Fri, 11 Sep 2026 05:54:48 -0400 Subject: [PATCH 12/49] move texture index to pack in surface options --- .../Renderers/Legacy/World/DynamicVertex.cs | 3 - .../Legacy/World/Geometry/CoverWallUtil.cs | 12 +-- .../Legacy/World/Geometry/GeometryRenderer.cs | 91 ++++++++----------- .../Portals/FloodFill/FloodFillProgram.cs | 4 +- .../Portals/FloodFill/FloodFillRenderer.cs | 32 +++---- .../Portals/FloodFill/FloodFillVertex.cs | 5 +- .../Static/StaticCacheGeometryRenderer.cs | 4 +- .../Legacy/World/InterpolationShader.cs | 7 -- .../Legacy/World/Shader/VertexFunction.cs | 3 +- .../Renderers/Legacy/World/StaticShader.cs | 2 - .../Renderers/Legacy/World/StaticVertex.cs | 5 +- .../Renderers/Legacy/World/VertexOptions.cs | 4 +- 12 files changed, 71 insertions(+), 101 deletions(-) diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/DynamicVertex.cs b/Core/Render/OpenGL/Renderers/Legacy/World/DynamicVertex.cs index 8aa89e8d2..7c82ef8b0 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/DynamicVertex.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/DynamicVertex.cs @@ -32,7 +32,4 @@ public struct DynamicVertex [VertexAttribute] public float RenderOptions; - - [VertexAttribute] - public float TextureIndex; } diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/CoverWallUtil.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/CoverWallUtil.cs index 84aeee80f..d9ab6bacd 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/CoverWallUtil.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/CoverWallUtil.cs @@ -48,22 +48,22 @@ public static unsafe void CopyCoverWallVertices(Side side, StaticVertex[] static { DynamicVertex* v = startVertex; staticVertices[index++] = new StaticVertex(v->X, v->Y, v->Z + heights.AddTop, v->U, v->V, - v->SurfaceOptions, v->LightLevelAdd, 0, 0); + v->SurfaceOptions, v->LightLevelAdd, 0); v++; staticVertices[index++] = new StaticVertex(v->X, v->Y, v->Z - heights.SubBottom, v->U, v->V, - v->SurfaceOptions, v->LightLevelAdd, 0, 0); + v->SurfaceOptions, v->LightLevelAdd, 0); v++; staticVertices[index++] = new StaticVertex(v->X, v->Y, v->Z + heights.AddTop, v->U, v->V, - v->SurfaceOptions, v->LightLevelAdd, 0, 0); + v->SurfaceOptions, v->LightLevelAdd, 0); v++; staticVertices[index++] = new StaticVertex(v->X, v->Y, v->Z - heights.SubBottom, v->U, v->V, - v->SurfaceOptions, v->LightLevelAdd, 0, 0); + v->SurfaceOptions, v->LightLevelAdd, 0); v++; staticVertices[index++] = new StaticVertex(v->X, v->Y, v->Z + heights.AddTop, v->U, v->V, - v->SurfaceOptions, v->LightLevelAdd, 0, 0); + v->SurfaceOptions, v->LightLevelAdd, 0); v++; staticVertices[index++] = new StaticVertex(v->X, v->Y, v->Z - heights.SubBottom, v->U, v->V, - v->SurfaceOptions, v->LightLevelAdd, 0, 0); + v->SurfaceOptions, v->LightLevelAdd, 0); } } diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/GeometryRenderer.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/GeometryRenderer.cs index f42c382f1..65a0885db 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/GeometryRenderer.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/GeometryRenderer.cs @@ -35,11 +35,9 @@ namespace Helion.Render.OpenGL.Renderers.Legacy.World.Geometry; public partial class GeometryRenderer : IDisposable { - public delegate void RenderCoverWallAction(Side side, Span vertices, WallLocation location, bool oneSided); + sealed record class LoadTextureCounts(HashSet FlatTextures, HashSet WallTexturesRepeat, HashSet WallTexturesClamp); - public HashSet FlatTextures = []; - public HashSet WallTexturesRepeat = []; - public HashSet WallTexturesClamp = []; + public delegate void RenderCoverWallAction(Side side, Span vertices, WallLocation location, bool oneSided); private const double MaxSky = 16384; private static readonly Sector DefaultSector = CreateDefault(); @@ -150,7 +148,7 @@ public GeometryRenderer(IConfig config, ArchiveCollection archiveCollection, Leg m_renderTwoSidedMiddleSliceFunc = RenderTwoSidedMiddleSlice; m_renderSectorSliceFunc3D = RenderSectorSlice3D; - var options = VertexOptions.PackSurface(1, 1, 0, 0, 0, 0); + var options = VertexOptions.PackSurface(1, 1, 0, 0, 0, 0, 0); for (int i = 0; i < m_wallVertices.Length; i++) m_wallVertices[i].SurfaceOptions = options; @@ -178,8 +176,6 @@ public void UpdateTo(IWorld world, bool unitTest = false) m_vanillaRender = world.Config.Render.VanillaRender; m_pixelGapCorrection = world.Config.Render.PixelGapCorrection; - PreloadAllTextures(world); - int sideCount = world.Geometry.GetSideCount(); int sectorCount = world.Geometry.GetSectorCount(); bool freeData = !world.SameAsPreviousMap; @@ -197,8 +193,11 @@ public void UpdateTo(IWorld world, bool unitTest = false) m_invalidatedCounter = 1; + LoadTextureCounts? loadTextureCounts = null; if (!world.SameAsPreviousMap) { + loadTextureCounts = PreloadAllTextures(world); + for (int i = 0; i < m_subsectors.Length; i++) { m_subsectors[i].FlushReferences(); @@ -231,8 +230,11 @@ public void UpdateTo(IWorld world, bool unitTest = false) if (!unitTest) { - var arrayBuilder = new GLTextureArrayBuilder(m_archiveCollection.TextureManager, m_glTextureManager); - arrayBuilder.BuildLevel(FlatTextures, WallTexturesRepeat, WallTexturesClamp); + if (!world.SameAsPreviousMap && loadTextureCounts != null) + { + var arrayBuilder = new GLTextureArrayBuilder(m_archiveCollection.TextureManager, m_glTextureManager); + arrayBuilder.BuildLevel(loadTextureCounts.FlatTextures, loadTextureCounts.WallTexturesRepeat, loadTextureCounts.WallTexturesClamp); + } Portals.UpdateTo(world); m_staticCacheGeometryRenderer.UpdateTo(world); @@ -611,31 +613,32 @@ public void Dispose() GC.SuppressFinalize(this); } - private void PreloadAllTextures(IWorld world) + private LoadTextureCounts? PreloadAllTextures(IWorld world) { - if (world.SameAsPreviousMap) - return; + var wallTexturesRepeat = new HashSet(128); + var wallTexturesClamp = new HashSet(128); + var flatTextures = new HashSet(128); for (int i = 0; i < world.Lines.Count; i++) { var line = world.Lines[i]; - AddSideTextures(WallTexturesRepeat, WallTexturesClamp, line.Front, line.Flags.TwoSided); + AddSideTextures(wallTexturesRepeat, wallTexturesClamp, line.Front, line.Flags.TwoSided); if (line.Back == null) continue; - AddSideTextures(WallTexturesRepeat, WallTexturesClamp, line.Back, line.Flags.TwoSided); + AddSideTextures(wallTexturesRepeat, wallTexturesClamp, line.Back, line.Flags.TwoSided); } for (int i = 0; i < world.Sectors.Count; i++) { var sector = world.Sectors[i]; - FlatTextures.Add(sector.Floor.TextureHandle); - FlatTextures.Add(sector.Ceiling.TextureHandle); + flatTextures.Add(sector.Floor.TextureHandle); + flatTextures.Add(sector.Ceiling.TextureHandle); if (sector.FloorSkyTextureHandle.HasValue) - FlatTextures.Add(sector.FloorSkyTextureHandle.Value); + flatTextures.Add(sector.FloorSkyTextureHandle.Value); if (sector.CeilingSkyTextureHandle.HasValue) - FlatTextures.Add(sector.CeilingSkyTextureHandle.Value); + flatTextures.Add(sector.CeilingSkyTextureHandle.Value); } foreach (var textureName in world.GetPreCacheTextureNames()) @@ -643,14 +646,15 @@ private void PreloadAllTextures(IWorld world) var texture = TextureManager.GetTexture(textureName, ResourceNamespace.Global, ResourceNamespace.Textures); if (texture.Index > 0) { - WallTexturesRepeat.Add(texture.Index); - WallTexturesClamp.Add(texture.Index); + wallTexturesClamp.Add(texture.Index); + wallTexturesRepeat.Add(texture.Index); } } - TextureManager.LoadTextureImages(FlatTextures); - TextureManager.LoadTextureImages(WallTexturesClamp); - TextureManager.LoadTextureImages(WallTexturesRepeat); + TextureManager.LoadTextureImages(flatTextures); + TextureManager.LoadTextureImages(wallTexturesClamp); + TextureManager.LoadTextureImages(wallTexturesRepeat); + return new(flatTextures, wallTexturesRepeat, wallTexturesClamp); } private static void AddSideTextures(HashSet wallTexturesRepeat, HashSet wallTexturesClamp, Side side, bool twoSided) @@ -2059,10 +2063,9 @@ private static unsafe void SetWallVertices(DynamicVertex[] data, in WallVertices vertex->V = wv.TopLeft.V; vertex->PrevU = wv.TopLeft.PrevU; vertex->PrevV = wv.TopLeft.PrevV; - vertex->SurfaceOptions = VertexOptions.PackSurface(1, alpha, addAlpha, upper, lower, overrideLightIndex); + vertex->SurfaceOptions = VertexOptions.PackSurface(1, alpha, addAlpha, upper, lower, overrideLightIndex, textureIndex); vertex->LightLevelAdd = lightLevelAddAndMapId; vertex->RenderOptions = renderOptions; - vertex->TextureIndex = textureIndex; vertex++; vertex->X = wv.TopLeft.X; @@ -2075,10 +2078,9 @@ private static unsafe void SetWallVertices(DynamicVertex[] data, in WallVertices vertex->V = wv.BottomRight.V; vertex->PrevU = wv.TopLeft.PrevU; vertex->PrevV = wv.BottomRight.PrevV; - vertex->SurfaceOptions = VertexOptions.PackSurface(1, alpha, addAlpha, upper, lower, overrideLightIndex); + vertex->SurfaceOptions = VertexOptions.PackSurface(1, alpha, addAlpha, upper, lower, overrideLightIndex, textureIndex); vertex->LightLevelAdd = lightLevelAddAndMapId; vertex->RenderOptions = renderOptions; - vertex->TextureIndex = textureIndex; vertex++; vertex->X = wv.BottomRight.X; @@ -2091,10 +2093,9 @@ private static unsafe void SetWallVertices(DynamicVertex[] data, in WallVertices vertex->V = wv.TopLeft.V; vertex->PrevU = wv.BottomRight.PrevU; vertex->PrevV = wv.TopLeft.PrevV; - vertex->SurfaceOptions = VertexOptions.PackSurface(1, alpha, addAlpha, upper, lower, overrideLightIndex); + vertex->SurfaceOptions = VertexOptions.PackSurface(1, alpha, addAlpha, upper, lower, overrideLightIndex, textureIndex); vertex->LightLevelAdd = lightLevelAddAndMapId; vertex->RenderOptions = renderOptions; - vertex->TextureIndex = textureIndex; vertex++; vertex->X = wv.BottomRight.X; @@ -2107,10 +2108,9 @@ private static unsafe void SetWallVertices(DynamicVertex[] data, in WallVertices vertex->V = wv.BottomRight.V; vertex->PrevU = wv.BottomRight.PrevU; vertex->PrevV = wv.BottomRight.PrevV; - vertex->SurfaceOptions = VertexOptions.PackSurface(0, alpha, addAlpha, upper, lower, overrideLightIndex); + vertex->SurfaceOptions = VertexOptions.PackSurface(0, alpha, addAlpha, upper, lower, overrideLightIndex, textureIndex); vertex->LightLevelAdd = lightLevelAddAndMapId; vertex->RenderOptions = renderOptions; - vertex->TextureIndex = textureIndex; vertex++; vertex->X = wv.BottomRight.X; @@ -2123,10 +2123,9 @@ private static unsafe void SetWallVertices(DynamicVertex[] data, in WallVertices vertex->V = wv.TopLeft.V; vertex->PrevU = wv.BottomRight.PrevU; vertex->PrevV = wv.TopLeft.PrevV; - vertex->SurfaceOptions = VertexOptions.PackSurface(0, alpha, addAlpha, upper, lower, overrideLightIndex); + vertex->SurfaceOptions = VertexOptions.PackSurface(0, alpha, addAlpha, upper, lower, overrideLightIndex, textureIndex); vertex->LightLevelAdd = lightLevelAddAndMapId; vertex->RenderOptions = renderOptions; - vertex->TextureIndex = textureIndex; vertex++; vertex->X = wv.TopLeft.X; @@ -2139,10 +2138,9 @@ private static unsafe void SetWallVertices(DynamicVertex[] data, in WallVertices vertex->V = wv.BottomRight.V; vertex->PrevU = wv.TopLeft.PrevU; vertex->PrevV = wv.BottomRight.PrevV; - vertex->SurfaceOptions = VertexOptions.PackSurface(0, alpha, addAlpha, upper, lower, overrideLightIndex); + vertex->SurfaceOptions = VertexOptions.PackSurface(0, alpha, addAlpha, upper, lower, overrideLightIndex, textureIndex); vertex->LightLevelAdd = lightLevelAddAndMapId; vertex->RenderOptions = renderOptions; - vertex->TextureIndex = textureIndex; } } @@ -2181,10 +2179,9 @@ private static unsafe DynamicVertex[] GetWallVertices(in WallVertices wv, int li vertex->V = wv.TopLeft.V; vertex->PrevU = wv.TopLeft.PrevU; vertex->PrevV = wv.TopLeft.PrevV; - vertex->SurfaceOptions = VertexOptions.PackSurface(1, alpha, addAlpha, upper, lower, overrideLightIndex); + vertex->SurfaceOptions = VertexOptions.PackSurface(1, alpha, addAlpha, upper, lower, overrideLightIndex, textureIndex); vertex->LightLevelAdd = lightLevelAddAndMapId; vertex->RenderOptions = renderOptions; - vertex->TextureIndex = textureIndex; // 1 vertex++; @@ -2198,10 +2195,9 @@ private static unsafe DynamicVertex[] GetWallVertices(in WallVertices wv, int li vertex->V = wv.BottomRight.V; vertex->PrevU = wv.TopLeft.PrevU; vertex->PrevV = wv.BottomRight.PrevV; - vertex->SurfaceOptions = VertexOptions.PackSurface(1, alpha, addAlpha, upper, lower, overrideLightIndex); + vertex->SurfaceOptions = VertexOptions.PackSurface(1, alpha, addAlpha, upper, lower, overrideLightIndex, textureIndex); vertex->LightLevelAdd = lightLevelAddAndMapId; vertex->RenderOptions = renderOptions; - vertex->TextureIndex = textureIndex; // 2 vertex++; @@ -2215,10 +2211,9 @@ private static unsafe DynamicVertex[] GetWallVertices(in WallVertices wv, int li vertex->V = wv.TopLeft.V; vertex->PrevU = wv.BottomRight.PrevU; vertex->PrevV = wv.TopLeft.PrevV; - vertex->SurfaceOptions = VertexOptions.PackSurface(1, alpha, addAlpha, upper, lower, overrideLightIndex); + vertex->SurfaceOptions = VertexOptions.PackSurface(1, alpha, addAlpha, upper, lower, overrideLightIndex, textureIndex); vertex->LightLevelAdd = lightLevelAddAndMapId; vertex->RenderOptions = renderOptions; - vertex->TextureIndex = textureIndex; // 3 vertex++; @@ -2232,10 +2227,9 @@ private static unsafe DynamicVertex[] GetWallVertices(in WallVertices wv, int li vertex->V = wv.BottomRight.V; vertex->PrevU = wv.BottomRight.PrevU; vertex->PrevV = wv.BottomRight.PrevV; - vertex->SurfaceOptions = VertexOptions.PackSurface(0, alpha, addAlpha, upper, lower, overrideLightIndex); + vertex->SurfaceOptions = VertexOptions.PackSurface(0, alpha, addAlpha, upper, lower, overrideLightIndex, textureIndex); vertex->LightLevelAdd = lightLevelAddAndMapId; vertex->RenderOptions = renderOptions; - vertex->TextureIndex = textureIndex; // 4 vertex++; @@ -2249,10 +2243,9 @@ private static unsafe DynamicVertex[] GetWallVertices(in WallVertices wv, int li vertex->V = wv.TopLeft.V; vertex->PrevU = wv.BottomRight.PrevU; vertex->PrevV = wv.TopLeft.PrevV; - vertex->SurfaceOptions = VertexOptions.PackSurface(0, alpha, addAlpha, upper, lower, overrideLightIndex); + vertex->SurfaceOptions = VertexOptions.PackSurface(0, alpha, addAlpha, upper, lower, overrideLightIndex, textureIndex); vertex->LightLevelAdd = lightLevelAddAndMapId; vertex->RenderOptions = renderOptions; - vertex->TextureIndex = textureIndex; // 5 vertex++; @@ -2266,10 +2259,9 @@ private static unsafe DynamicVertex[] GetWallVertices(in WallVertices wv, int li vertex->V = wv.BottomRight.V; vertex->PrevU = wv.TopLeft.PrevU; vertex->PrevV = wv.BottomRight.PrevV; - vertex->SurfaceOptions = VertexOptions.PackSurface(0, alpha, addAlpha, upper, lower, overrideLightIndex); + vertex->SurfaceOptions = VertexOptions.PackSurface(0, alpha, addAlpha, upper, lower, overrideLightIndex, textureIndex); vertex->LightLevelAdd = lightLevelAddAndMapId; vertex->RenderOptions = renderOptions; - vertex->TextureIndex = textureIndex; } return data; @@ -2278,7 +2270,7 @@ private static unsafe DynamicVertex[] GetWallVertices(in WallVertices wv, int li private static unsafe void GetFlatVertices(DynamicVertex[] vertices, int startIndex, ref TriangulatedWorldVertex root, ref TriangulatedWorldVertex second, ref TriangulatedWorldVertex third, int lightIndex, int overrideLightIndex, int flatLightLevel, int upper, int lower, int addAlpha, float alpha, int textureIndex) { - var surfaceOptions = VertexOptions.PackSurface(0, alpha, addAlpha, upper, lower, overrideLightIndex); + var surfaceOptions = VertexOptions.PackSurface(0, alpha, addAlpha, upper, lower, overrideLightIndex, textureIndex); var renderOptions = VertexOptions.PackRender(lightIndex, flatLightLevel); fixed (DynamicVertex* startVertex = &vertices[startIndex]) { @@ -2295,7 +2287,6 @@ private static unsafe void GetFlatVertices(DynamicVertex[] vertices, int startIn vertex->PrevV = root.PrevV; vertex->SurfaceOptions = surfaceOptions; vertex->RenderOptions = renderOptions; - vertex->TextureIndex = textureIndex; vertex++; vertex->X = second.X; @@ -2310,7 +2301,6 @@ private static unsafe void GetFlatVertices(DynamicVertex[] vertices, int startIn vertex->PrevV = second.PrevV; vertex->SurfaceOptions = surfaceOptions; vertex->RenderOptions = renderOptions; - vertex->TextureIndex = textureIndex; vertex++; vertex->X = third.X; @@ -2325,7 +2315,6 @@ private static unsafe void GetFlatVertices(DynamicVertex[] vertices, int startIn vertex->PrevV = third.PrevV; vertex->SurfaceOptions = surfaceOptions; vertex->RenderOptions = renderOptions; - vertex->TextureIndex = textureIndex; } } diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Portals/FloodFill/FloodFillProgram.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Portals/FloodFill/FloodFillProgram.cs index b3ee0528a..27b48efec 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Portals/FloodFill/FloodFillProgram.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Portals/FloodFill/FloodFillProgram.cs @@ -29,8 +29,7 @@ protected override string VertexShader() => @" layout(location = 5) in float prevPlaneZ; layout(location = 6) in float surfaceOptions; layout(location = 7) in float renderOptions; - layout(location = 8) in float textureIndex; - layout(location = 9) in float mapId; + layout(location = 8) in float mapId; flat out float planeZFrag; out vec3 vertexPosFrag; @@ -79,7 +78,6 @@ void main() gl_Position = mvp * vec4(worldPos, 1.0); depthFrag = gl_Position.${Depth}; - boundTextureIndex = textureIndex; } " .Replace("${LightLevelVertexVariables}", LightLevel.VertexVariables(LightLevelOptions.NoDist)) diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Portals/FloodFill/FloodFillRenderer.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Portals/FloodFill/FloodFillRenderer.cs index 9b42857b5..410ca8056 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Portals/FloodFill/FloodFillRenderer.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Portals/FloodFill/FloodFillRenderer.cs @@ -123,17 +123,17 @@ public void UpdateStaticWall(int floodKey, SectorPlane floodPlane, WallVertices var upper = sideTexture == SideTexture.Upper ? 1 : 0; var lower = sideTexture == SideTexture.Lower ? 1 : 0; - var options = VertexOptions.PackSurface(0, 0, 0, upper, lower, data.OverrideLightIndex); var textureIndex = m_glTextureManager.GetTexture(floodPlane.TextureHandle).ArrayIndex; + var options = VertexOptions.PackSurface(0, 0, 0, upper, lower, data.OverrideLightIndex, textureIndex); FloodFillVertex topLeft = new((vertices.TopLeft.X, vertices.TopLeft.Y, topZ), - prevTopZ, planeZ, prevPlaneZ, minZ, maxZ, options, data.RenderOptions, mapId, textureIndex); + prevTopZ, planeZ, prevPlaneZ, minZ, maxZ, options, data.RenderOptions, mapId); FloodFillVertex topRight = new((vertices.BottomRight.X, vertices.BottomRight.Y, topZ), - prevTopZ, planeZ, prevPlaneZ, minZ, maxZ, options, data.RenderOptions, mapId, textureIndex); + prevTopZ, planeZ, prevPlaneZ, minZ, maxZ, options, data.RenderOptions, mapId); FloodFillVertex bottomLeft = new((vertices.TopLeft.X, vertices.TopLeft.Y, bottomZ), - prevBottomZ, planeZ, prevPlaneZ, minZ, maxZ, options, data.RenderOptions, mapId, textureIndex); + prevBottomZ, planeZ, prevPlaneZ, minZ, maxZ, options, data.RenderOptions, mapId); FloodFillVertex bottomRight = new((vertices.BottomRight.X, vertices.BottomRight.Y, bottomZ), - prevBottomZ, planeZ, prevPlaneZ, minZ, maxZ, options, data.RenderOptions, mapId, textureIndex); + prevBottomZ, planeZ, prevPlaneZ, minZ, maxZ, options, data.RenderOptions, mapId); var vbo = floodInfo.Pipeline.Vbo; vbo.Data[data.VboOffset] = topLeft; @@ -196,17 +196,17 @@ public int AddStaticWall(SectorPlane sectorPlane, in WallVertices vertices, doub var upper = sideTexture == SideTexture.Upper ? 1 : 0; var lower = sideTexture == SideTexture.Lower ? 1 : 0; - var options = VertexOptions.PackSurface(0, 0, 0, upper, lower, overrideLightIndex); var textureIndex = m_glTextureManager.GetTexture(sectorPlane.TextureHandle).ArrayIndex; + var options = VertexOptions.PackSurface(0, 0, 0, upper, lower, overrideLightIndex, textureIndex); FloodFillVertex topLeft = new((vertices.TopLeft.X, vertices.TopLeft.Y, vertices.TopLeft.Z), - vertices.TopLeft.Z, planeZ, prevPlaneZ, minZ, maxZ, options, lightBufferIndex, mapId, textureIndex); + vertices.TopLeft.Z, planeZ, prevPlaneZ, minZ, maxZ, options, lightBufferIndex, mapId); FloodFillVertex topRight = new((vertices.BottomRight.X, vertices.BottomRight.Y, vertices.TopLeft.Z), - vertices.TopLeft.Z, planeZ, prevPlaneZ, minZ, maxZ, options, lightBufferIndex, mapId, textureIndex); + vertices.TopLeft.Z, planeZ, prevPlaneZ, minZ, maxZ, options, lightBufferIndex, mapId); FloodFillVertex bottomLeft = new((vertices.TopLeft.X, vertices.TopLeft.Y, vertices.BottomRight.Z), - vertices.BottomRight.Z, planeZ, prevPlaneZ, minZ, maxZ, options, lightBufferIndex, mapId, textureIndex); + vertices.BottomRight.Z, planeZ, prevPlaneZ, minZ, maxZ, options, lightBufferIndex, mapId); FloodFillVertex bottomRight = new((vertices.BottomRight.X, vertices.BottomRight.Y, vertices.BottomRight.Z), - vertices.BottomRight.Z, planeZ, prevPlaneZ, minZ, maxZ, options, lightBufferIndex, mapId, textureIndex); + vertices.BottomRight.Z, planeZ, prevPlaneZ, minZ, maxZ, options, lightBufferIndex, mapId); int offset = vbo.Data.Length; int newLength = vbo.Data.Length + VerticesPerWall; @@ -245,15 +245,15 @@ private static void ProjectFloodPlane(VertexBufferObject vbo, i float prevTopZ = vertices.PrevTopZ + currentAddHeight; float prevBottomZ = vertices.PrevBottomZ + currentAddHeight; - var surfaceOptions = VertexOptions.PackSurface(0, 0, 0, 0, 0, overrideLightIndex); + var surfaceOptions = VertexOptions.PackSurface(0, 0, 0, 0, 0, overrideLightIndex, textureIndex); FloodFillVertex topLeft = new((vertices.TopLeft.X, vertices.TopLeft.Y, topLeftZ), - prevTopZ, planeZ, prevPlaneZ, minZ, maxZ, surfaceOptions, renderOptions, mapId, textureIndex); + prevTopZ, planeZ, prevPlaneZ, minZ, maxZ, surfaceOptions, renderOptions, mapId); FloodFillVertex topRight = new((vertices.BottomRight.X, vertices.BottomRight.Y, topLeftZ), - prevTopZ, planeZ, prevPlaneZ, minZ, maxZ, surfaceOptions, renderOptions, mapId, textureIndex); + prevTopZ, planeZ, prevPlaneZ, minZ, maxZ, surfaceOptions, renderOptions, mapId); FloodFillVertex bottomLeft = new((vertices.TopLeft.X, vertices.TopLeft.Y, bottomRightZ), - prevBottomZ, planeZ, prevPlaneZ, minZ, maxZ, surfaceOptions, renderOptions, mapId, textureIndex); + prevBottomZ, planeZ, prevPlaneZ, minZ, maxZ, surfaceOptions, renderOptions, mapId); FloodFillVertex bottomRight = new((vertices.BottomRight.X, vertices.BottomRight.Y, bottomRightZ), - prevBottomZ, planeZ, prevPlaneZ, minZ, maxZ, surfaceOptions, renderOptions, mapId, textureIndex); + prevBottomZ, planeZ, prevPlaneZ, minZ, maxZ, surfaceOptions, renderOptions, mapId); buffer[startIndex++] = topLeft; buffer[startIndex++] = bottomLeft; @@ -304,7 +304,7 @@ private static void OverwriteAndSubUploadVboWithZero(VertexBufferObject staticVertices, Span< { ref var v = ref vertices[i]; staticVertices.Data[staticStartIndex + i] = new StaticVertex(v.X, v.Y, v.Z, v.U, v.V, - v.SurfaceOptions, v.LightLevelAdd, v.RenderOptions, v.TextureIndex); + v.SurfaceOptions, v.LightLevelAdd, v.RenderOptions); } staticVertices.SetLength(staticVertices.Length + vertices.Length); @@ -623,7 +623,7 @@ private static void CopyVertices(StaticVertex[] staticVertices, Span @" layout(location = 4) in vec3 prevPos; layout(location = 5) in vec2 prevUV; layout(location = 6) in float renderOptions; - layout(location = 7) in float textureIndex; out vec2 uvFrag; flat out float alphaFrag; @@ -75,7 +69,6 @@ void main() { gl_Position = mvp * mixPos; zPos = mixPos.z; depthFrag = gl_Position.${Depth}; - boundTextureIndex = textureIndex; } " .Replace("${LightLevelVertexVariables}", planeClip ? "" : LightLevel.VertexVariables(LightLevelOptions.Default)) diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Shader/VertexFunction.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Shader/VertexFunction.cs index 790e51dad..ac4bf4a32 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Shader/VertexFunction.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Shader/VertexFunction.cs @@ -45,7 +45,8 @@ public static class VertexFunction addAlphaFrag = float((intOptions >> 9) & 1); upperFrag = float((intOptions >> 10) & 1); lowerFrag = float((intOptions >> 11) & 1); - float overrideLightIndex = float(float(intOptions >> 12));"; + float overrideLightIndex = float((intOptions >> 12) & 1); + boundTextureIndex = float((intOptions >> 13) & 0xFFFFF);"; public static string ColorMapAndLightLevelSet => @" diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/StaticShader.cs b/Core/Render/OpenGL/Renderers/Legacy/World/StaticShader.cs index 5e2dda3d7..7206c5964 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/StaticShader.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/StaticShader.cs @@ -20,7 +20,6 @@ private static string VertexShaderInternal(bool planeClip) => @" layout(location = 2) in float lightLevelAdd; layout(location = 3) in float surfaceOptions; layout(location = 4) in float renderOptions; - layout(location = 5) in float textureIndex; out vec2 uvFrag; flat out float alphaFrag; @@ -66,7 +65,6 @@ void main() { gl_Position = mvp * mixPos; zPos = pos.z; depthFrag = gl_Position.${Depth}; - boundTextureIndex = textureIndex; } " .Replace("${LightLevelVertexVariables}", planeClip ? "" : LightLevel.VertexVariables(LightLevelOptions.Default)) diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/StaticVertex.cs b/Core/Render/OpenGL/Renderers/Legacy/World/StaticVertex.cs index d82f0fb3c..4bd5a67cb 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/StaticVertex.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/StaticVertex.cs @@ -4,7 +4,7 @@ namespace Helion.Render.OpenGL.Renderers.Legacy.World; [StructLayout(LayoutKind.Sequential, Pack = 1)] -public struct StaticVertex(float x, float y, float z, float u, float v, float surfaceOptions, float lightLevelAdd, float renderOptions, float textureIndex) +public struct StaticVertex(float x, float y, float z, float u, float v, float surfaceOptions, float lightLevelAdd, float renderOptions) { [VertexAttribute("pos", size: 3)] public float X = x; @@ -23,7 +23,4 @@ public struct StaticVertex(float x, float y, float z, float u, float v, float su [VertexAttribute] public float RenderOptions = renderOptions; - - [VertexAttribute] - public float TextureIndex = textureIndex; } diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/VertexOptions.cs b/Core/Render/OpenGL/Renderers/Legacy/World/VertexOptions.cs index 3f83bfef1..510b8595e 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/VertexOptions.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/VertexOptions.cs @@ -7,10 +7,10 @@ public static class VertexOptions { // When overrideLightIndex is non-zero then lighting uses index overrideLightIndex - 1 [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe float PackSurface(int topLeft, float alpha, int addAlpha, int upper, int lower, int overrideLightIndex) + public static unsafe float PackSurface(int topLeft, float alpha, int addAlpha, int upper, int lower, int overrideLightIndex, int textureIndex) { int alphaByte = (int)(alpha * 255.0f); - int packed = (alphaByte & 0xFF) | (topLeft << 8) | (addAlpha << 9) | (upper << 10) | (lower << 11) | (overrideLightIndex << 12); + int packed = (alphaByte & 0xFF) | (topLeft << 8) | (addAlpha << 9) | (upper << 10) | (lower << 11) | (overrideLightIndex << 12) | ((textureIndex & 0xFFFFF) << 13); return *(float*)&packed; } From 814e5f49dc34926decaccad5725e69dd89c79f2b Mon Sep 17 00:00:00 2001 From: Nick Date: Fri, 11 Sep 2026 08:40:37 -0400 Subject: [PATCH 13/49] correct separation for clamped array textures --- .../Portals/FloodFill/FloodFillRenderer.cs | 4 +-- .../Geometry/Static/GeometryTextureLookup.cs | 4 +-- .../Static/StaticCacheGeometryRenderer.cs | 12 ++++++++- Core/Render/OpenGL/Texture/GLTexture.cs | 2 +- .../Render/OpenGL/Texture/GLTextureManager.cs | 26 ++++++++++++++----- .../Texture/Legacy/LegacyGLTextureManager.cs | 4 +-- Core/Resources/Texture.cs | 5 ++++ 7 files changed, 43 insertions(+), 14 deletions(-) diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Portals/FloodFill/FloodFillRenderer.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Portals/FloodFill/FloodFillRenderer.cs index 410ca8056..8bd4dad1b 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Portals/FloodFill/FloodFillRenderer.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Portals/FloodFill/FloodFillRenderer.cs @@ -60,7 +60,7 @@ private FloodFillInfo CreateFloodFillInfo(SectorPlane plane) private FloodFillInfo GetOrCreateFloodFillInfo(SectorPlane plane) { - var textureHandle = m_glTextureManager.GetArrayTextureHandle(plane.TextureHandle); + var textureHandle = m_glTextureManager.GetArrayTextureHandle(plane.TextureHandle, true); if (m_textureHandleToFloodFillInfoIndex.TryGetValue(textureHandle, out int index)) return m_floodFillInfos[index]; @@ -85,7 +85,7 @@ private ref FloodGeometry TryGetFloodGeometry(int floodKey, out bool success) private bool TryGetFloodFillInfoIndex(int textureHandle, out int index) { - textureHandle = m_glTextureManager.GetArrayTextureHandle(textureHandle); + textureHandle = m_glTextureManager.GetArrayTextureHandle(textureHandle, true); var success = m_textureHandleToFloodFillInfoIndex.TryGetValue(textureHandle, out index); Assert.Precondition(success, $"Failed to find flood fill info for {textureHandle}"); return success; diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/GeometryTextureLookup.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/GeometryTextureLookup.cs index 19971aae5..a6f329bba 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/GeometryTextureLookup.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/GeometryTextureLookup.cs @@ -17,13 +17,13 @@ public void Clear() public bool TryGetValue(GeometryType type, int textureHandle, bool repeatY, [NotNullWhen(true)] out GeometryData? value) { - textureHandle = m_arrayTextureLookup.GetArrayTextureHandle(textureHandle); + textureHandle = m_arrayTextureLookup.GetArrayTextureHandle(textureHandle, repeatY); return m_lookup.Get(type).TryGetValue(textureHandle, repeatY, out value); } public void Add(GeometryType type, int textureHandle, bool repeatY, GeometryData data) { - textureHandle = m_arrayTextureLookup.GetArrayTextureHandle(textureHandle); + textureHandle = m_arrayTextureLookup.GetArrayTextureHandle(textureHandle, repeatY); m_lookup.Get(type).Add(textureHandle, repeatY, data); } } diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/StaticCacheGeometryRenderer.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/StaticCacheGeometryRenderer.cs index 6cfc8eace..bdd2191b2 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/StaticCacheGeometryRenderer.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/StaticCacheGeometryRenderer.cs @@ -674,7 +674,13 @@ private GeometryData AllocateGeometryData(GeometryType type, int textureHandle, { vboSize = Math.Max(vboSize, 32); label ??= GetGeometryLabel(type, textureHandle, repeat); - var texture = overrideTexture ?? m_textureManager.GetParentArrayTexture(textureHandle, repeat); + + GLLegacyTexture? texture; + if (overrideTexture != null) + texture = overrideTexture.ParentArrayTexture ?? overrideTexture; + else + texture = m_textureManager.GetParentArrayTexture(textureHandle, repeat); + var brightmapTexture = m_textureManager.GetBrightmapTexture(textureHandle, repeat); var vbo = new StaticVertexBuffer(label, vboSize); var pipeline = new VertexPipeline(m_program, vbo, label); @@ -1311,6 +1317,10 @@ private void UpdateVertices(ref StaticGeometryData staticGeometry, int textureHa if (textureHandle <= Constants.NullCompatibilityTextureIndex && !isOneSidedLine) return; + // This doesn't practically matter but makes debugging nicer + if (texture != null) + texture = texture.ParentArrayTexture ?? texture; + // If this surface generated more vertices than previously cached, release so a new one can be requested. (happens with 3D sectors) if (staticGeometry.GeometryData != null && staticGeometry.Length < vertices.Length) { diff --git a/Core/Render/OpenGL/Texture/GLTexture.cs b/Core/Render/OpenGL/Texture/GLTexture.cs index eb494cc45..1b4530e27 100644 --- a/Core/Render/OpenGL/Texture/GLTexture.cs +++ b/Core/Render/OpenGL/Texture/GLTexture.cs @@ -16,7 +16,7 @@ public abstract class GLTexture : IRenderableTextureHandle, IDisposable public Dimension Dimension { get; } public Vec2I Offset { get; } public readonly int TextureId; - public readonly string Name; + public string Name; public readonly Vec2F UVInverse; public readonly ResourceNamespace Namespace; public readonly TextureTarget Target; diff --git a/Core/Render/OpenGL/Texture/GLTextureManager.cs b/Core/Render/OpenGL/Texture/GLTextureManager.cs index c6b54fddd..33eff676e 100644 --- a/Core/Render/OpenGL/Texture/GLTextureManager.cs +++ b/Core/Render/OpenGL/Texture/GLTextureManager.cs @@ -14,7 +14,10 @@ using Helion.Util.Container; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using static System.Net.Mime.MediaTypeNames; using Font = Helion.Graphics.Fonts.Font; using Image = Helion.Graphics.Image; @@ -31,7 +34,7 @@ public enum TextureFlags public interface IArrayTextureLookup { // Returns unique base array texture handle given a texture handle from the TextureManager - int GetArrayTextureHandle(int textureHandle); + int GetArrayTextureHandle(int textureHandle, bool repeatY); } public abstract class GLTextureManager : IRendererTextureManager, IArrayTextureLookup @@ -46,6 +49,7 @@ public abstract class GLTextureManager : IRendererTextureManager, private readonly DynamicArray m_arrayTextures = new(256); private readonly DynamicArray m_texturesForArrays = new(256); private readonly Dictionary m_arrayTextureLookup = []; + private readonly Dictionary m_arrayTextureLookupClamp = []; private bool m_disposed; private TextureManager TextureManager => ArchiveCollection.TextureManager; @@ -258,15 +262,18 @@ public GLTextureType GetTexture(int index, bool repeatY = true) m_texturesForArrays.Add(texture); } + var lookup = repeatY ? m_arrayTextureLookup : m_arrayTextureLookupClamp; var flags = repeatY ? TextureFlags.Default : TextureFlags.ClampY; var glTextures = GenerateTextureArray(images, images[0].Dimension, ResourceNamespace.Textures, flags, out var arrayTexture); + // Flip high bit to ensure no collisions + var arrayTextureId = arrayTexture.TextureId | (1 << 30); for (int i = 0; i < images.Length; i++) { var glTexture = glTextures[i]; var texture = textures[i]; - // Flip high bit to ensure no collisions - m_arrayTextureLookup[texture.Index] = arrayTexture.TextureId | (1 << 30); - textures[i].SetGLTexture(glTexture, repeatY); + SetDebugName(glTexture, texture); + lookup[texture.Index] = arrayTextureId; + texture.SetGLTexture(glTexture, repeatY); } m_arrayTextures.Add(arrayTexture); @@ -274,9 +281,16 @@ public GLTextureType GetTexture(int index, bool repeatY = true) return arrayTexture; } - public int GetArrayTextureHandle(int textureHandle) + [Conditional("DEBUG")] + private static void SetDebugName(GLTextureType glTexture, Resources.Texture texture) { - if (m_arrayTextureLookup.TryGetValue(textureHandle, out var handle)) + glTexture.Name = $"{glTexture.Name} {texture.Index}:{texture.Name}"; + } + + public int GetArrayTextureHandle(int textureHandle, bool repeatY) + { + var lookup = repeatY ? m_arrayTextureLookup : m_arrayTextureLookupClamp; + if (lookup.TryGetValue(textureHandle, out var handle)) return handle; return textureHandle; } diff --git a/Core/Render/OpenGL/Texture/Legacy/LegacyGLTextureManager.cs b/Core/Render/OpenGL/Texture/Legacy/LegacyGLTextureManager.cs index 91c9f7c7d..21a87d97a 100644 --- a/Core/Render/OpenGL/Texture/Legacy/LegacyGLTextureManager.cs +++ b/Core/Render/OpenGL/Texture/Legacy/LegacyGLTextureManager.cs @@ -22,7 +22,7 @@ public class LegacyGLTextureManager : GLTextureManager public override IImageDrawInfoProvider ImageDrawInfoProvider { get; } private bool m_disposed; - private List m_registeredTextures = new(); + private readonly List m_registeredTextures = []; public LegacyGLTextureManager(IConfig config, ArchiveCollection archiveCollection) : base(config, archiveCollection) @@ -229,7 +229,7 @@ protected override GLLegacyTexture GenerateTexture(Image image, string name, protected override GLLegacyTexture[] GenerateTextureArray(Image[] images, Dimension dimension, ResourceNamespace resourceNamespace, TextureFlags flags, out GLLegacyTexture arrayTexture) { int textureId = GL.GenTexture(); - arrayTexture = new GLLegacyTexture(textureId, $"Texture Array Length={images.Length}", dimension, default, resourceNamespace, TextureTarget.Texture2DArray, 0, 0, 0); + arrayTexture = new GLLegacyTexture(textureId, $"Texture Array Length={images.Length} {flags}", dimension, default, resourceNamespace, TextureTarget.Texture2DArray, 0, 0, 0); UploadAndSetParameters(arrayTexture, images, "", resourceNamespace, flags); var textures = new GLLegacyTexture[images.Length]; diff --git a/Core/Resources/Texture.cs b/Core/Resources/Texture.cs index 139f692df..725344cd8 100644 --- a/Core/Resources/Texture.cs +++ b/Core/Resources/Texture.cs @@ -62,4 +62,9 @@ public void SetBrightmapGLTexture(object glTexture, bool repeatY) /// public object? BrightmapRenderStore; public object? BrightmapRenderStoreClamp; + + public override string ToString() + { + return $"{Name} {Image?.Dimension}"; + } } From f5227479dda017893fb07a2686a9bfbf212fe452 Mon Sep 17 00:00:00 2001 From: Nick Date: Fri, 11 Sep 2026 10:04:39 -0400 Subject: [PATCH 14/49] make free geometry manager aware of array textures --- .../Legacy/World/Geometry/Static/FreeGeometryManager.cs | 9 ++++++--- .../World/Geometry/Static/StaticCacheGeometryRenderer.cs | 7 +++---- Core/Render/OpenGL/Texture/GLTextureManager.cs | 8 -------- Core/Render/OpenGL/Texture/IArrayTextureLookup.cs | 8 ++++++++ 4 files changed, 17 insertions(+), 15 deletions(-) create mode 100644 Core/Render/OpenGL/Texture/IArrayTextureLookup.cs diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/FreeGeometryManager.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/FreeGeometryManager.cs index a174e119a..43981590e 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/FreeGeometryManager.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/FreeGeometryManager.cs @@ -1,4 +1,5 @@ -using Helion.Util.Assertion; +using Helion.Render.OpenGL.Texture; +using Helion.Util.Assertion; using Helion.World.Static; using System; using System.Collections.Generic; @@ -29,8 +30,9 @@ public override bool Equals(object? obj) } } -public class FreeGeometryManager +public class FreeGeometryManager(IArrayTextureLookup arrayTextureLookup) { + private readonly IArrayTextureLookup m_arrayTextureLookup = arrayTextureLookup; private readonly Dictionary m_data = new(128); public void Add(in StaticGeometryData geometryData, GeometryType type, bool repeatY) @@ -38,7 +40,7 @@ public void Add(in StaticGeometryData geometryData, GeometryType type, bool repe if (geometryData.GeometryData == null) return; - var textureHandle = geometryData.GeometryData.TextureHandle; + var textureHandle = m_arrayTextureLookup.GetArrayTextureHandle(geometryData.GeometryData.TextureHandle, repeatY); var key = new GeometryKey(textureHandle, type, repeatY); if (!m_data.TryGetValue(key, out var list)) @@ -86,6 +88,7 @@ public bool GetAndRemove(int textureHandle, GeometryType type, bool repeatY, int { int minLength = int.MaxValue; int minIndex = -1; + textureHandle = m_arrayTextureLookup.GetArrayTextureHandle(textureHandle, repeatY); var key = new GeometryKey(textureHandle, type, repeatY); if (!m_data.TryGetValue(key, out var list)) diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/StaticCacheGeometryRenderer.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/StaticCacheGeometryRenderer.cs index bdd2191b2..7630cc736 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/StaticCacheGeometryRenderer.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/StaticCacheGeometryRenderer.cs @@ -43,7 +43,7 @@ public partial class StaticCacheGeometryRenderer : StyleRendererBase, IDisposabl private readonly GeometryTextureLookup m_textureToGeometryLookup; - private readonly FreeGeometryManager m_freeManager = new(); + private readonly FreeGeometryManager m_freeManager; private readonly LegacySkyRenderer m_skyRenderer; private readonly LookupArray?> m_transferHeightsLookup = new(); @@ -73,6 +73,7 @@ public StaticCacheGeometryRenderer(ArchiveCollection archiveCollection, LegacyGL m_program = program; m_skyRenderer = new(archiveCollection, textureManager); m_textureToGeometryLookup = new(textureManager); + m_freeManager = new(textureManager); m_renderCoverWallAction = AddOrUpdateCoverWall; m_renderOneSidedSliceFunc = m_geometryRenderer.RenderOneSidedSlice; @@ -167,8 +168,6 @@ public void UpdateTo(IWorld world) m_worldReload = false; } - - private void World_SectorFogColorChanged(object? sender, SectorFogEvent e) { if ((e.PreviousColor.Uint == 0 && e.Sector.FogColor.Uint == 0) || (e.PreviousColor.Uint != 0 && e.Sector.FogColor.Uint != 0)) @@ -1534,7 +1533,7 @@ private void SetRuntimeGeometryData(SectorPlane? plane, Side? side, Wall? wall, } } - private static unsafe void ClearGeometryVertices(GeometryData geometryData, int startIndex, int length) + private static void ClearGeometryVertices(GeometryData geometryData, int startIndex, int length) { ref var reference = ref geometryData.Pipeline.Vbo.Data.Data[startIndex]; Unsafe.InitBlockUnaligned(ref Unsafe.As(ref reference), 0, (uint)(Marshal.SizeOf() * length)); diff --git a/Core/Render/OpenGL/Texture/GLTextureManager.cs b/Core/Render/OpenGL/Texture/GLTextureManager.cs index 33eff676e..98aa63020 100644 --- a/Core/Render/OpenGL/Texture/GLTextureManager.cs +++ b/Core/Render/OpenGL/Texture/GLTextureManager.cs @@ -16,8 +16,6 @@ using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; -using static System.Net.Mime.MediaTypeNames; using Font = Helion.Graphics.Fonts.Font; using Image = Helion.Graphics.Image; @@ -31,12 +29,6 @@ public enum TextureFlags ClampY = 2, } -public interface IArrayTextureLookup -{ - // Returns unique base array texture handle given a texture handle from the TextureManager - int GetArrayTextureHandle(int textureHandle, bool repeatY); -} - public abstract class GLTextureManager : IRendererTextureManager, IArrayTextureLookup where GLTextureType : GLTexture { diff --git a/Core/Render/OpenGL/Texture/IArrayTextureLookup.cs b/Core/Render/OpenGL/Texture/IArrayTextureLookup.cs new file mode 100644 index 000000000..f9df6de38 --- /dev/null +++ b/Core/Render/OpenGL/Texture/IArrayTextureLookup.cs @@ -0,0 +1,8 @@ +namespace Helion.Render.OpenGL.Texture; + +public interface IArrayTextureLookup +{ + // Returns unique base array texture handle given a texture handle from the TextureManager + // If the texture does not have a parent array texture then the textureHandle parameter is returned + int GetArrayTextureHandle(int textureHandle, bool repeatY); +} From 5d7b02fe9a4a58a588b978f299193dfc84fdb20a Mon Sep 17 00:00:00 2001 From: Nick Date: Fri, 11 Sep 2026 10:06:48 -0400 Subject: [PATCH 15/49] add missing calls to disable dynamic buffering --- .../Legacy/World/Geometry/Static/StaticCacheGeometryRenderer.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/StaticCacheGeometryRenderer.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/StaticCacheGeometryRenderer.cs index 7630cc736..60096d26d 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/StaticCacheGeometryRenderer.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/StaticCacheGeometryRenderer.cs @@ -1230,6 +1230,7 @@ private void World_SideTextureChanged(object? sender, SideTextureEvent e) CheckForFloodFill(e.Side, e.Side.PartnerSide, e.Side.Sector, e.Side.PartnerSide.Sector, e.Side.IsFront); } + m_geometryRenderer.SetBuffer(false); m_geometryRenderer.SetRenderMode(GeometryRenderMode.Dynamic, TransferHeightView.Middle); AddLine(e.Side.Line, update: true); @@ -1271,6 +1272,7 @@ private void World_PlaneTextureChanged(object? sender, PlaneTextureEvent e) e.Plane.Static.GeometryData = null; + m_geometryRenderer.SetBuffer(false); m_geometryRenderer.SetRenderMode(GeometryRenderMode.Dynamic, TransferHeightView.Middle); if (WorldStatic.Sector3D && e.Plane.Sector.TaggedSectors3D.Length > 0) From c3cce7286b9f1f3f5c4653c7dc304655ba47ce82 Mon Sep 17 00:00:00 2001 From: Nick Date: Fri, 11 Sep 2026 10:17:29 -0400 Subject: [PATCH 16/49] remove todo --- .../Texture/Legacy/LegacyGLTextureManager.cs | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/Core/Render/OpenGL/Texture/Legacy/LegacyGLTextureManager.cs b/Core/Render/OpenGL/Texture/Legacy/LegacyGLTextureManager.cs index 21a87d97a..5b3f1f97b 100644 --- a/Core/Render/OpenGL/Texture/Legacy/LegacyGLTextureManager.cs +++ b/Core/Render/OpenGL/Texture/Legacy/LegacyGLTextureManager.cs @@ -162,20 +162,8 @@ public unsafe void UploadAndSetParameters(GLLegacyTexture texture, Image image, GL.TexImage3D(TextureTarget.Texture2DArray, 0, PixelInternalFormat.Rgba8, image.Width, image.Height, 1, 0, PixelFormat.Bgra, PixelType.UnsignedInt8888Reversed, IntPtr.Zero); - // TODO this is forcing everything to be a 2DArray with one layer. - GL.TexSubImage3D( - texture.Target, - level: 0, - xoffset: 0, - yoffset: 0, - zoffset: 0, - image.Width, - image.Height, - depth: 1, - PixelFormat.Bgra, - PixelType.UnsignedInt8888Reversed, - ptr - ); + GL.TexSubImage3D(texture.Target, 0, 0, 0, 0, image.Width, image.Height, depth: 1, + PixelFormat.Bgra, PixelType.UnsignedInt8888Reversed, ptr); } else { From bdc550176a0f015218d53de7e37631a015a42bef Mon Sep 17 00:00:00 2001 From: Nick Date: Fri, 11 Sep 2026 10:17:41 -0400 Subject: [PATCH 17/49] also load switch textures --- .../Legacy/World/Geometry/GeometryRenderer.cs | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/GeometryRenderer.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/GeometryRenderer.cs index 65a0885db..a849cc8ab 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/GeometryRenderer.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/GeometryRenderer.cs @@ -13,6 +13,8 @@ using Helion.Render.OpenGL.Texture.Legacy; using Helion.Resources; using Helion.Resources.Archives.Collection; +using Helion.Resources.Definitions.Animdefs.Textures; +using Helion.Resources.IWad; using Helion.Util; using Helion.Util.Configs; using Helion.Util.Configs.Components; @@ -24,6 +26,7 @@ using Helion.World.Geometry.Subsectors; using Helion.World.Geometry.Walls; using Helion.World.Physics; +using Helion.World.Special.Switches; using Helion.World.Static; using System; using System.Collections; @@ -651,12 +654,48 @@ public void Dispose() } } + AddLineSwitchTextures(wallTexturesRepeat, wallTexturesClamp); + TextureManager.LoadTextureImages(flatTextures); TextureManager.LoadTextureImages(wallTexturesClamp); TextureManager.LoadTextureImages(wallTexturesRepeat); return new(flatTextures, wallTexturesRepeat, wallTexturesClamp); } + private void AddLineSwitchTextures(HashSet wallTexturesRepeat, HashSet wallTexturesClamp) + { + var switches = new List(m_archiveCollection.Definitions.Animdefs.AnimatedSwitches.Count); + for (int i = 0; i < m_archiveCollection.Definitions.Animdefs.AnimatedSwitches.Count; i++) + { + var animSwitch = m_archiveCollection.Definitions.Animdefs.AnimatedSwitches[i]; + if (animSwitch.IWad != IWadBaseType.None && animSwitch.IWad != m_archiveCollection.IWadType) + continue; + switches.Add(animSwitch); + } + + AddLineSwitchTextures(wallTexturesRepeat, switches); + AddLineSwitchTextures(wallTexturesClamp, switches); + } + + private static void AddLineSwitchTextures(HashSet wallTextures, List switches) + { + var addTextures = new HashSet(); + foreach (var textureIndex in wallTextures) + { + foreach (var animSwitch in switches) + { + if (animSwitch.IsMatch(textureIndex)) + { + addTextures.Add(animSwitch.GetOpposingTexture(textureIndex)); + break; + } + } + } + + foreach (var textureIndex in addTextures) + wallTextures.Add(textureIndex); + } + private static void AddSideTextures(HashSet wallTexturesRepeat, HashSet wallTexturesClamp, Side side, bool twoSided) { wallTexturesRepeat.Add(side.Lower.TextureHandle); From 737f3fe0e0044951ef3aa10d98595d94ba283f56 Mon Sep 17 00:00:00 2001 From: Nick Date: Fri, 11 Sep 2026 10:36:02 -0400 Subject: [PATCH 18/49] fix reupload, cleanup --- .../Texture/Legacy/LegacyGLTextureManager.cs | 58 ++++++++++--------- 1 file changed, 31 insertions(+), 27 deletions(-) diff --git a/Core/Render/OpenGL/Texture/Legacy/LegacyGLTextureManager.cs b/Core/Render/OpenGL/Texture/Legacy/LegacyGLTextureManager.cs index 5b3f1f97b..cc0c8a161 100644 --- a/Core/Render/OpenGL/Texture/Legacy/LegacyGLTextureManager.cs +++ b/Core/Render/OpenGL/Texture/Legacy/LegacyGLTextureManager.cs @@ -106,60 +106,56 @@ private void HandleAnisotropyChange(object? sender, int e) Dispose(); } - public unsafe void UploadAndSetParameters(GLLegacyTexture texture, Image[] images, string name, ResourceNamespace resourceNamespace, TextureFlags flags) + private unsafe void UploadAndSetParameters3D(GLLegacyTexture texture, Image[] images, string name, ResourceNamespace resourceNamespace, TextureFlags flags) { GL.BindTexture(texture.Target, texture.TextureId); - - if (resourceNamespace == ResourceNamespace.Sprites || resourceNamespace == ResourceNamespace.Undefined) - flags = TextureFlags.ClampX | TextureFlags.ClampY; + flags = GetFlagsForNamespace(resourceNamespace, flags); GL.TexImage3D(TextureTarget.Texture2DArray, 0, PixelInternalFormat.Rgba8, images[0].Width, images[0].Height, images.Length, 0, PixelFormat.Bgra, PixelType.UnsignedInt8888Reversed, IntPtr.Zero); + if (GLInfo.DebugLabel) + GLHelper.ObjectLabel(ObjectLabelIdentifier.Texture, texture.TextureId, $"Texture: {name} ({flags})"); + for (int i = 0; i < images.Length; i++) { var image = images[i]; fixed (uint* pixelPtr = image.GetGlTexturePixels(ShaderVars.PaletteColorMode)) { IntPtr ptr = new(pixelPtr); - GL.TexSubImage3D( - texture.Target, - level: 0, - xoffset: 0, - yoffset: 0, - zoffset: i, - image.Width, - image.Height, - depth: 1, - PixelFormat.Bgra, - PixelType.UnsignedInt8888Reversed, - ptr - ); + GL.TexSubImage3D(texture.Target, 0, 0, 0, zoffset: i, image.Width, image.Height, + depth: 1, PixelFormat.Bgra, PixelType.UnsignedInt8888Reversed, ptr); } } GL.GenerateMipmap(GenerateMipmapTarget.Texture2DArray); - SetTextureParameters(texture.Target, resourceNamespace, flags); + SetTextureParameters(texture.Target, flags); GL.BindTexture(texture.Target, 0); texture.Flags = flags; } - public unsafe void UploadAndSetParameters(GLLegacyTexture texture, Image image, string name, ResourceNamespace resourceNamespace, TextureFlags flags) + private static TextureFlags GetFlagsForNamespace(ResourceNamespace resourceNamespace, TextureFlags flags) { - GL.BindTexture(texture.Target, texture.TextureId); - if (resourceNamespace == ResourceNamespace.Sprites || resourceNamespace == ResourceNamespace.Undefined) flags = TextureFlags.ClampX | TextureFlags.ClampY; + return flags; + } + + public unsafe void UploadAndSetParameters(GLLegacyTexture texture, Image image, string name, ResourceNamespace resourceNamespace, TextureFlags flags) + { + GL.BindTexture(texture.Target, texture.TextureId); + flags = GetFlagsForNamespace(resourceNamespace, flags); if (GLInfo.DebugLabel) GLHelper.ObjectLabel(ObjectLabelIdentifier.Texture, texture.TextureId, $"Texture: {name} ({flags})"); + fixed (uint* pixelPtr = image.GetGlTexturePixels(ShaderVars.PaletteColorMode)) { IntPtr ptr = new(pixelPtr); if (texture.Target == TextureTarget.Texture2DArray) { - GL.TexImage3D(TextureTarget.Texture2DArray, 0, PixelInternalFormat.Rgba8, image.Width, image.Height, 1, 0, + GL.TexImage3D(texture.Target, 0, PixelInternalFormat.Rgba8, image.Width, image.Height, 1, 0, PixelFormat.Bgra, PixelType.UnsignedInt8888Reversed, IntPtr.Zero); GL.TexSubImage3D(texture.Target, 0, 0, 0, 0, image.Width, image.Height, depth: 1, @@ -175,7 +171,7 @@ public unsafe void UploadAndSetParameters(GLLegacyTexture texture, Image image, } GL.GenerateMipmap(texture.Target == TextureTarget.Texture2DArray ? GenerateMipmapTarget.Texture2DArray : GenerateMipmapTarget.Texture2D); - SetTextureParameters(texture.Target, resourceNamespace, flags); + SetTextureParameters(texture.Target, flags); GL.BindTexture(texture.Target, 0); texture.Flags = flags; @@ -188,8 +184,16 @@ public unsafe override void ReUpload(GLLegacyTexture texture, Image image, uint[ fixed (uint* pixelPtr = imagePixels) { IntPtr ptr = new(pixelPtr); - GL.TexSubImage2D(texture.Target, 0, 0, 0, image.Width, image.Height, - PixelFormat.Bgra, PixelType.UnsignedInt8888Reversed, ptr); + if (texture.Target == TextureTarget.Texture2DArray) + { + GL.TexSubImage3D(texture.Target, 0, 0, 0, 0, image.Width, image.Height, depth: 1, + PixelFormat.Bgra, PixelType.UnsignedInt8888Reversed, ptr); + } + else + { + GL.TexSubImage2D(texture.Target, 0, 0, 0, image.Width, image.Height, + PixelFormat.Bgra, PixelType.UnsignedInt8888Reversed, ptr); + } } GL.BindTexture(texture.Target, 0); @@ -218,7 +222,7 @@ protected override GLLegacyTexture[] GenerateTextureArray(Image[] images, Dimens { int textureId = GL.GenTexture(); arrayTexture = new GLLegacyTexture(textureId, $"Texture Array Length={images.Length} {flags}", dimension, default, resourceNamespace, TextureTarget.Texture2DArray, 0, 0, 0); - UploadAndSetParameters(arrayTexture, images, "", resourceNamespace, flags); + UploadAndSetParameters3D(arrayTexture, images, arrayTexture.Name, resourceNamespace, flags); var textures = new GLLegacyTexture[images.Length]; for (int i = 0; i < images.Length; i++) @@ -251,7 +255,7 @@ protected override GLFontTexture GenerateFont(Font font, string return fontTexture; } - private void SetTextureParameters(TextureTarget targetType, ResourceNamespace resourceNamespace, TextureFlags flags) + private void SetTextureParameters(TextureTarget targetType, TextureFlags flags) { TextureWrapMode textureWrapS = (flags & TextureFlags.ClampX) != 0 ? TextureWrapMode.ClampToEdge : TextureWrapMode.Repeat; TextureWrapMode textureWrapT = (flags & TextureFlags.ClampY) != 0 ? TextureWrapMode.ClampToEdge : TextureWrapMode.Repeat; From 7476cbe6501f67ea7d44a00dcb8d4b75f7e3a5e4 Mon Sep 17 00:00:00 2001 From: Nick Date: Fri, 11 Sep 2026 14:06:13 -0400 Subject: [PATCH 19/49] add draw counters to profiler --- .../Renderers/Legacy/World/Data/RenderData.cs | 5 +++-- .../Legacy/World/Data/RenderDataCollection.cs | 9 +++++++-- .../Legacy/World/Data/RenderDataManager.cs | 6 ++++-- .../Legacy/World/Data/RenderWorldData.cs | 5 +++-- .../Legacy/World/Data/RenderWorldDataList.cs | 11 +++++++---- .../Legacy/World/Data/RenderWorldDataManager.cs | 6 ++++-- .../Legacy/World/Entities/EntityRenderer.cs | 12 ++++++++++-- .../Legacy/World/Geometry/GeometryRenderer.cs | 10 ++++++---- .../Portals/FloodFill/FloodFillRenderer.cs | 9 ++++++++- .../World/Geometry/Portals/PortalRenderer.cs | 7 ++++--- .../Static/StaticCacheGeometryRenderer.cs | 9 +++++++-- .../Renderers/Legacy/World/LegacyWorldRenderer.cs | 11 ++++++++--- .../Legacy/World/Sky/LegacySkyRenderer.cs | 5 ++++- Core/Util/Profiling/Timers/RenderProfiler.cs | 15 ++++++++++++++- Tests/Unit/GameAction/3DSector/Sector3D_Walls.cs | 2 +- 15 files changed, 90 insertions(+), 32 deletions(-) diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Data/RenderData.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Data/RenderData.cs index b5cc49afb..d852f7ee0 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Data/RenderData.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Data/RenderData.cs @@ -45,10 +45,10 @@ public void Clear() Pipeline.Clear(); } - public void Draw() + public bool Draw() { if (Pipeline.Empty) - return; + return false; GL.ActiveTexture(BindTextures.BoundTexture); Texture.Bind(); @@ -63,6 +63,7 @@ public void Draw() Pipeline.Vbo.DrawArraysInstanced(PrimitiveType.TriangleStrip, 0, 4); Texture.Unbind(); + return true; } protected virtual void Dispose(bool disposing) diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Data/RenderDataCollection.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Data/RenderDataCollection.cs index 7ec2a7159..5e7f5ebe3 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Data/RenderDataCollection.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Data/RenderDataCollection.cs @@ -18,13 +18,15 @@ public class RenderDataCollection<[DynamicallyAccessedMembers(DynamicallyAccesse private readonly DynamicArray> m_dataToRender = new(2048); private readonly RenderProgram m_program; private readonly RenderDataPool m_renderDataPool; + private readonly Action? m_onDraw; private int m_renderCount; private bool m_disposed; - public RenderDataCollection(RenderProgram program, RenderDataPool renderDataPool) + public RenderDataCollection(RenderProgram program, RenderDataPool renderDataPool, Action? onDraw = null) { m_program = program; m_renderDataPool = renderDataPool; + m_onDraw = onDraw; } ~RenderDataCollection() @@ -72,7 +74,10 @@ public void Render() return; for (int i = 0; i < m_dataToRender.Length; i++) - m_dataToRender[i].Draw(); + { + if (m_dataToRender[i].Draw()) + m_onDraw?.Invoke(); + } } protected virtual void Dispose(bool disposing) diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Data/RenderDataManager.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Data/RenderDataManager.cs index 9ed807eaa..c34b5f337 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Data/RenderDataManager.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Data/RenderDataManager.cs @@ -23,16 +23,18 @@ public class RenderDataManager<[DynamicallyAccessedMembers(DynamicallyAccessedMe private readonly RenderDataCollection[] m_renderDataStyles; private readonly RenderData m_healthBarData; + private readonly Action? m_onDraw; private bool m_disposed; - public RenderDataManager(RenderProgram program, GLLegacyTexture healthBarTexture, RenderDataPool renderDataPool) + public RenderDataManager(RenderProgram program, GLLegacyTexture healthBarTexture, RenderDataPool renderDataPool, Action? onDraw = null) { Assert.Precondition(RenderStyleLookup.Length == (int)RenderStyle.Count, "Render style lookup size mismatch"); m_renderDataStyles = new RenderDataCollection[(int)RenderDataStyle.Count]; for (int i = 0; i < m_renderDataStyles.Length; i++) - m_renderDataStyles[i] = new(program, renderDataPool); + m_renderDataStyles[i] = new(program, renderDataPool, onDraw); m_healthBarData = new(program, healthBarTexture); + m_onDraw = onDraw; } ~RenderDataManager() diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Data/RenderWorldData.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Data/RenderWorldData.cs index 705d3953a..12c53c567 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Data/RenderWorldData.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Data/RenderWorldData.cs @@ -42,10 +42,10 @@ public void Clear() Pipeline.Clear(); } - public void Draw() + public bool Draw() { if (Pipeline.Empty) - return; + return false; // We are doing binding manually since apparently these are all // coming up in the memory profiler as a bunch of new 'actions'. @@ -64,6 +64,7 @@ public void Draw() Pipeline.DrawArrays(); Pipeline.Unbind(); Texture.Unbind(); + return true; } public void Dispose() diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Data/RenderWorldDataList.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Data/RenderWorldDataList.cs index 47db2f961..4656a3beb 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Data/RenderWorldDataList.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Data/RenderWorldDataList.cs @@ -1,17 +1,17 @@ -using Helion.Render.OpenGL.Shader; -using Helion.Render.OpenGL.Texture.Legacy; +using Helion.Render.OpenGL.Texture.Legacy; using Helion.Util.Container; using System; using System.Collections.Generic; namespace Helion.Render.OpenGL.Renderers.Legacy.World.Data; -public class RenderWorldDataList(RenderWorldDataPool pool) +public class RenderWorldDataList(RenderWorldDataPool pool, Action? onDraw) { public List RenderData = []; private RenderWorldData?[] m_allRenderData = new RenderWorldData?[1024]; private readonly DynamicArray m_dataToRender = new(1024); private readonly RenderWorldDataPool m_pool = pool; + private readonly Action? m_onDraw = onDraw; private int m_renderCount; public void Reset() @@ -60,7 +60,10 @@ public RenderWorldData Add(GLLegacyTexture texture, GLLegacyTexture? brightmapTe public void Draw() { for (int i = 0; i < m_dataToRender.Length; i++) - m_dataToRender[i].Draw(); + { + if (m_dataToRender[i].Draw()) + m_onDraw?.Invoke(); + } } public void Clear() diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Data/RenderWorldDataManager.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Data/RenderWorldDataManager.cs index b41f2034d..2976b0262 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Data/RenderWorldDataManager.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Data/RenderWorldDataManager.cs @@ -12,19 +12,21 @@ public sealed class RenderWorldDataManager : StyleRendererBase, IDisposable { private readonly RenderWorldDataPool m_pool; private readonly GeometryTypeLookup m_lookup; + private readonly Action? m_onDraw; private RenderWorldData? m_coverWalls; public bool BufferCoverWalls = true; - public RenderWorldDataManager(RenderProgram program) + public RenderWorldDataManager(RenderProgram program, Action? onDraw) { m_pool = new(program, 1024); + m_onDraw = onDraw; m_lookup = new(AllocateDataList); } private RenderWorldDataList AllocateDataList() { - return new RenderWorldDataList(m_pool); + return new RenderWorldDataList(m_pool, m_onDraw); } public void Reset() diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityRenderer.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityRenderer.cs index d24dbd979..9c9f13f1c 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityRenderer.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityRenderer.cs @@ -10,6 +10,7 @@ using Helion.Util; using Helion.Util.Configs; using Helion.Util.Container; +using Helion.Util.Profiling.Timers; using Helion.World; using Helion.World.Entities; using Helion.World.Entities.Definition; @@ -40,6 +41,7 @@ public sealed class EntityRenderer : StyleRendererBase, IDisposable private readonly SpriteRotation m_nullSpriteRotation; private readonly ArchiveCollection m_archiveCollection; private readonly RenderDataPool m_renderDataPool; + private readonly RenderProfiler m_renderProfiler; private readonly bool m_vanillaRender; private Vec2F m_viewRightNormal; private Vec2F m_prevViewRightNormal; @@ -54,19 +56,25 @@ public sealed class EntityRenderer : StyleRendererBase, IDisposable private bool m_disposed; private int m_lastViewerEntityId; - public EntityRenderer(IConfig config, LegacyGLTextureManager textureManager, ArchiveCollection archiveCollection) + public EntityRenderer(IConfig config, LegacyGLTextureManager textureManager, ArchiveCollection archiveCollection, RenderProfiler renderProfiler) { m_config = config; m_textureManager = textureManager; m_archiveCollection = archiveCollection; m_nullSpriteRotation = m_textureManager.NullSpriteRotation; m_renderDataPool = new(m_program, RenderPoolSize); - m_dataManager = new(m_program, textureManager.BlackTexture, m_renderDataPool); + m_dataManager = new(m_program, textureManager.BlackTexture, m_renderDataPool, OnDraw); m_spriteAlpha = m_config.Render.SpriteTransparency; m_spriteClip = m_config.Render.SpriteClip; m_spriteClipMin = m_config.Render.SpriteClipMin; m_vanillaRender = m_config.Render.VanillaRender; m_spriteClipFactorMax = (float)m_config.Render.SpriteClipFactorMax.Value; + m_renderProfiler = renderProfiler; + } + + private void OnDraw() + { + m_renderProfiler.DrawCounts.Sprites++; } ~EntityRenderer() diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/GeometryRenderer.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/GeometryRenderer.cs index b2cf8ddc0..54587a5f8 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/GeometryRenderer.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/GeometryRenderer.cs @@ -19,6 +19,7 @@ using Helion.Util.Configs; using Helion.Util.Configs.Components; using Helion.Util.Container; +using Helion.Util.Profiling.Timers; using Helion.World; using Helion.World.Geometry.Lines; using Helion.World.Geometry.Sectors; @@ -108,13 +109,14 @@ sealed record class LoadTextureCounts(HashSet FlatTextures, HashSet Wa private BitArray m_hitLines = new(0); public GeometryRenderer(IConfig config, ArchiveCollection archiveCollection, LegacyGLTextureManager glTextureManager, - RenderProgram program, RenderProgram staticProgram, RenderWorldDataManager worldDataManager, ViewClipper viewClipper, ViewClipper viewClipperPrev, bool unitTest = false) + RenderProgram program, RenderProgram staticProgram, RenderWorldDataManager worldDataManager, + ViewClipper viewClipper, ViewClipper viewClipperPrev, RenderProfiler renderProfiler, bool unitTest = false) { m_config = config; m_program = program; m_glTextureManager = glTextureManager; m_worldDataManager = worldDataManager; - m_skyRenderer = new LegacySkyRenderer(archiveCollection, glTextureManager); + m_skyRenderer = new LegacySkyRenderer(archiveCollection, glTextureManager, renderProfiler); m_archiveCollection = archiveCollection; m_fakeSideScrollData = new(); m_fakeSide = new(0, default, m_fakeWall, m_fakeWall, m_fakeWall, m_sliceSector) @@ -140,8 +142,8 @@ public GeometryRenderer(IConfig config, ArchiveCollection archiveCollection, Leg } else { - Portals = new(archiveCollection, glTextureManager); - m_staticCacheGeometryRenderer = new(archiveCollection, glTextureManager, staticProgram, this); + Portals = new(archiveCollection, glTextureManager, renderProfiler); + m_staticCacheGeometryRenderer = new(archiveCollection, glTextureManager, staticProgram, renderProfiler, this); m_renderCoverWallAction = m_worldDataManager.AddCoverWallVertices; } diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Portals/FloodFill/FloodFillRenderer.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Portals/FloodFill/FloodFillRenderer.cs index 8bd4dad1b..26db9df8d 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Portals/FloodFill/FloodFillRenderer.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Portals/FloodFill/FloodFillRenderer.cs @@ -12,6 +12,7 @@ using Helion.Util; using Helion.Util.Assertion; using Helion.Util.Container; +using Helion.Util.Profiling.Timers; using Helion.World; using Helion.World.Geometry.Sectors; using Helion.World.Static; @@ -19,7 +20,7 @@ namespace Helion.Render.OpenGL.Renderers.Legacy.World.Geometry.Portals.FloodFill; -public class FloodFillRenderer(LegacyGLTextureManager glTextureManager, FloodFillRenderMode renderMode) : IDisposable +public class FloodFillRenderer(LegacyGLTextureManager glTextureManager, FloodFillRenderMode renderMode, RenderProfiler renderProfiler) : IDisposable { const int FloodPlaneAddCount = 2; const int VerticesPerWall = 6; @@ -33,6 +34,7 @@ public class FloodFillRenderer(LegacyGLTextureManager glTextureManager, FloodFil private readonly DynamicArray m_floodGeometry = []; private readonly LinkedList m_freeData = []; private readonly DynamicArray> m_freeNodes = []; + private readonly RenderProfiler m_renderProfiler = renderProfiler; private TextureManager? m_textureManager; private bool m_disposed; @@ -342,6 +344,11 @@ private void Render(FloodFillProgram program, RenderInfo renderInfo, bool wallCl info.Pipeline.Vbo.UploadIfNeeded(); info.Pipeline.Bind(); info.Pipeline.DrawArrays(); + + if (m_renderMode == FloodFillRenderMode.Dynamic) + m_renderProfiler.DrawCounts.FloodFillDynamic++; + else + m_renderProfiler.DrawCounts.FloodFillStatic++; } } diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Portals/PortalRenderer.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Portals/PortalRenderer.cs index 6675924d3..5fd1e8233 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Portals/PortalRenderer.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Portals/PortalRenderer.cs @@ -14,6 +14,7 @@ using Helion.World.Geometry.Lines; using Helion.Resources; using Helion.World.Geometry.Walls; +using Helion.Util.Profiling.Timers; namespace Helion.Render.OpenGL.Renderers.Legacy.World.Geometry.Portals; @@ -39,11 +40,11 @@ public class PortalRenderer : IDisposable private bool m_disposed; - public PortalRenderer(ArchiveCollection archiveCollection, LegacyGLTextureManager glTextureManager) + public PortalRenderer(ArchiveCollection archiveCollection, LegacyGLTextureManager glTextureManager, RenderProfiler renderProfiler) { m_archiveCollection = archiveCollection; - m_floodFillStatic = new(glTextureManager, FloodFillRenderMode.Static); - m_floodFillDynamic = new(glTextureManager, FloodFillRenderMode.Dynamic); + m_floodFillStatic = new(glTextureManager, FloodFillRenderMode.Static, renderProfiler); + m_floodFillDynamic = new(glTextureManager, FloodFillRenderMode.Dynamic, renderProfiler); m_floodFillRenderer = m_floodFillStatic; m_transferHeightView = TransferHeightView.Middle; } diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/StaticCacheGeometryRenderer.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/StaticCacheGeometryRenderer.cs index 60096d26d..efda3bc05 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/StaticCacheGeometryRenderer.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/StaticCacheGeometryRenderer.cs @@ -13,6 +13,7 @@ using Helion.Util; using Helion.Util.Assertion; using Helion.Util.Container; +using Helion.Util.Profiling.Timers; using Helion.World; using Helion.World.Geometry.Lines; using Helion.World.Geometry.Sectors; @@ -40,6 +41,7 @@ public partial class StaticCacheGeometryRenderer : StyleRendererBase, IDisposabl private readonly FloodFillRenderer m_floodFillRenderer; private readonly RenderProgram m_program; private readonly RenderGeometry m_geometry = new(); + private readonly RenderProfiler m_renderProfiler; private readonly GeometryTextureLookup m_textureToGeometryLookup; @@ -64,16 +66,17 @@ public partial class StaticCacheGeometryRenderer : StyleRendererBase, IDisposabl private readonly bool m_vanillaRender; public StaticCacheGeometryRenderer(ArchiveCollection archiveCollection, LegacyGLTextureManager textureManager, - RenderProgram program, GeometryRenderer geometryRenderer) + RenderProgram program, RenderProfiler renderProfiler, GeometryRenderer geometryRenderer) { m_archiveCollection = archiveCollection; m_textureManager = textureManager; m_geometryRenderer = geometryRenderer; m_floodFillRenderer = geometryRenderer.Portals.GetStaticFloodFillRenderer(); m_program = program; - m_skyRenderer = new(archiveCollection, textureManager); + m_skyRenderer = new(archiveCollection, textureManager, renderProfiler); m_textureToGeometryLookup = new(textureManager); m_freeManager = new(textureManager); + m_renderProfiler = renderProfiler; m_renderCoverWallAction = AddOrUpdateCoverWall; m_renderOneSidedSliceFunc = m_geometryRenderer.RenderOneSidedSlice; @@ -881,6 +884,8 @@ private void RenderGeometry(List geometry) data.Pipeline.Bind(); data.Pipeline.DrawArrays(); + + m_renderProfiler.DrawCounts.GeometryStatic++; } } diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/LegacyWorldRenderer.cs b/Core/Render/OpenGL/Renderers/Legacy/World/LegacyWorldRenderer.cs index 234ce3955..f4e9db04e 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/LegacyWorldRenderer.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/LegacyWorldRenderer.cs @@ -79,15 +79,20 @@ public LegacyWorldRenderer(IConfig config, ArchiveCollection archiveCollection, { m_config = config; m_renderProfiler = renderProfiler; - m_entityRenderer = new(config, textureManager, archiveCollection); + m_entityRenderer = new(config, textureManager, archiveCollection, renderProfiler); m_primitiveRenderer = new(); - m_worldDataManager = new(m_interpolationProgram); - m_geometryRenderer = new(config, archiveCollection, textureManager, m_interpolationProgram, m_staticProgram, m_worldDataManager, m_viewClipper, m_viewClipperPrev); + m_worldDataManager = new(m_interpolationProgram, OnDynamicDraw); + m_geometryRenderer = new(config, archiveCollection, textureManager, m_interpolationProgram, m_staticProgram, m_worldDataManager, m_viewClipper, m_viewClipperPrev, renderProfiler); m_archiveCollection = archiveCollection; m_textureManager = textureManager; m_vanillaRender = config.Render.VanillaRender; } + private void OnDynamicDraw() + { + m_renderProfiler.DrawCounts.GeometryDynamic++; + } + ~LegacyWorldRenderer() { ReleaseUnmanagedResources(); diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Sky/LegacySkyRenderer.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Sky/LegacySkyRenderer.cs index b1e990f0a..ac907dd38 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Sky/LegacySkyRenderer.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Sky/LegacySkyRenderer.cs @@ -8,12 +8,13 @@ using Helion.Render.OpenGL.Texture.Legacy; using Helion.Resources.Archives.Collection; using Helion.Resources.Definitions; +using Helion.Util.Profiling.Timers; using OpenTK.Graphics.OpenGL; using static Helion.Util.Assertion.Assert; namespace Helion.Render.OpenGL.Renderers.Legacy.World.Sky; -public class LegacySkyRenderer(ArchiveCollection archiveCollection, LegacyGLTextureManager glTextureManager) : IDisposable +public class LegacySkyRenderer(ArchiveCollection archiveCollection, LegacyGLTextureManager glTextureManager, RenderProfiler renderProfiler) : IDisposable { private const int MaxSkyTextures = 255; @@ -23,6 +24,7 @@ public class LegacySkyRenderer(ArchiveCollection archiveCollection, LegacyGLText private readonly LegacyGLTextureManager m_glTextureManager = glTextureManager; private readonly Dictionary m_skyComponents = []; private readonly List m_skyComponentsList = []; + private readonly RenderProfiler m_renderProfiler = renderProfiler; ~LegacySkyRenderer() { @@ -108,6 +110,7 @@ public void Render(RenderInfo renderInfo) sky.RenderSky(renderInfo); GL.Enable(EnableCap.DepthTest); + m_renderProfiler.DrawCounts.Skies++; } GL.Disable(EnableCap.StencilTest); diff --git a/Core/Util/Profiling/Timers/RenderProfiler.cs b/Core/Util/Profiling/Timers/RenderProfiler.cs index 07228a753..bf2a4cd4e 100644 --- a/Core/Util/Profiling/Timers/RenderProfiler.cs +++ b/Core/Util/Profiling/Timers/RenderProfiler.cs @@ -2,7 +2,17 @@ namespace Helion.Util.Profiling.Timers; -public class RenderProfiler: ProfileComponent +public struct DrawCounts +{ + public int Sprites; + public int GeometryStatic; + public int GeometryDynamic; + public int FloodFillStatic; + public int FloodFillDynamic; + public int Skies; +} + +public class RenderProfiler : ProfileComponent { public readonly ProfilerStopwatch FlushPipeline = new(); public readonly ProfilerStopwatch Hud = new(); @@ -17,6 +27,8 @@ public class RenderProfiler: ProfileComponent public readonly ProfilerStopwatch WorldFloodFill = new(); public readonly ProfilerStopwatch Automap = new(); + public DrawCounts DrawCounts; + public override List Profilers { get; } = []; public RenderProfiler() @@ -47,5 +59,6 @@ internal void ResetAll() WorldTransparent.Reset(); WorldFloodFill.Reset(); Automap.Reset(); + DrawCounts = default; } } diff --git a/Tests/Unit/GameAction/3DSector/Sector3D_Walls.cs b/Tests/Unit/GameAction/3DSector/Sector3D_Walls.cs index ff6f7b0e5..7428cd1c9 100644 --- a/Tests/Unit/GameAction/3DSector/Sector3D_Walls.cs +++ b/Tests/Unit/GameAction/3DSector/Sector3D_Walls.cs @@ -30,7 +30,7 @@ record struct WallSlice(double TopZ, double BottomZ, short LightLevel, Vec2D Off public Sector3D_Walls() { World = WorldAllocator.LoadMap("Resources/sector3d-map.zip", "sector3d-map.wad", "MAP01", GetType().Name, (world) => { }, IWadType.Doom2); - GeometryRenderer = new GeometryRenderer(World.Config, World.ArchiveCollection, null!, null!, null!, null!, null!, null!, unitTest: true); + GeometryRenderer = new GeometryRenderer(World.Config, World.ArchiveCollection, null!, null!, null!, null!, null!, null!, null!, unitTest: true); GeometryRenderer.UpdateTo(World, unitTest: true); GeometryRenderer.SetBuffer(false); } From 4955255015be2c3ec000e3801709066618dbfc6c Mon Sep 17 00:00:00 2001 From: Nick Date: Fri, 11 Sep 2026 14:08:48 -0400 Subject: [PATCH 20/49] move log to debug --- .../OpenGL/Texture/Legacy/GLTextureArrayBuilder.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Core/Render/OpenGL/Texture/Legacy/GLTextureArrayBuilder.cs b/Core/Render/OpenGL/Texture/Legacy/GLTextureArrayBuilder.cs index 9edea1442..6c73ee08e 100644 --- a/Core/Render/OpenGL/Texture/Legacy/GLTextureArrayBuilder.cs +++ b/Core/Render/OpenGL/Texture/Legacy/GLTextureArrayBuilder.cs @@ -4,6 +4,7 @@ using Helion.Util.Loggers; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; namespace Helion.Render.OpenGL.Texture.Legacy; @@ -41,7 +42,13 @@ public void BuildLevel(IEnumerable flatTextures, IEnumerable wallTextu var totalTextures = flatTextures.Count() + wallTexturesRepeat.Count() + wallTexturesClamp.Count(); var animated = totalTextures - buildTextures; - HelionLog.Info($"Compressed textures {totalTextures} -> {arrayTextures + animated}"); + DebugLog(totalTextures, arrayTextures + animated); + } + + [Conditional("DEBUG")] + private static void DebugLog(int totalTextures, int compressed) + { + HelionLog.Info($"Compressed textures {totalTextures} -> {compressed}"); } private int BuildTextureArrayFromTextures(DynamicArray textures, bool repeatY) From 321fe86ae1498251507c44e589fe65bbf83df0c0 Mon Sep 17 00:00:00 2001 From: Nick Date: Fri, 11 Sep 2026 19:06:21 -0400 Subject: [PATCH 21/49] ensure store is set in rotation --- Core/Render/OpenGL/Texture/GLTextureManager.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Core/Render/OpenGL/Texture/GLTextureManager.cs b/Core/Render/OpenGL/Texture/GLTextureManager.cs index 98aa63020..cc18f2190 100644 --- a/Core/Render/OpenGL/Texture/GLTextureManager.cs +++ b/Core/Render/OpenGL/Texture/GLTextureManager.cs @@ -365,10 +365,14 @@ public void CacheSpriteRotations(SpriteDefinition spriteDef) continue; if (rotation.Texture.RenderStore != null) + { + rotation.RenderStore ??= rotation.Texture.RenderStore; + rotation.BrightmapRenderStore ??= rotation.Texture.RenderStore; continue; + } - rotation.Texture.RenderStore = CreateTexture(rotation.Texture.Image, rotation.Texture.Name, ResourceNamespace.Sprites); - rotation.Texture.BrightmapRenderStore = CreateBrightMapTexture(rotation.Texture.BrightmapImage, rotation.Texture.Name, ResourceNamespace.Brightmaps); + rotation.RenderStore = rotation.Texture.RenderStore = CreateTexture(rotation.Texture.Image, rotation.Texture.Name, ResourceNamespace.Sprites); + rotation.BrightmapRenderStore = rotation.Texture.BrightmapRenderStore = CreateBrightMapTexture(rotation.Texture.BrightmapImage, rotation.Texture.Name, ResourceNamespace.Brightmaps); } } } From 5b0e6295d3d8f68b5fc9727a1f252f0abf69b395 Mon Sep 17 00:00:00 2001 From: Nick Date: Fri, 11 Sep 2026 19:51:31 -0400 Subject: [PATCH 22/49] fix remaining boundTextureIndex variables --- .../Renderers/Legacy/World/Entities/EntityProgram.cs | 3 +-- .../OpenGL/Renderers/Legacy/World/Shader/PlaneClip.cs | 8 +++----- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityProgram.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityProgram.cs index 479e24584..b66e6163c 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityProgram.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityProgram.cs @@ -96,7 +96,6 @@ protected override string VertexShader() => @" uniform float timeFrac; uniform int useSectorColor; uniform int useSectorFog; - uniform int boundTextureIndex; uniform sampler2DArray boundTexture; uniform samplerBuffer sectorColormapTexture; uniform samplerBuffer sectorFogTexture; @@ -259,7 +258,6 @@ private static string AdjustSpriteVertexClip() uniform vec2 downScaleSampleFactor; uniform float colorClamp; uniform int useSectorFog; - uniform int boundTextureIndex; uniform sampler2D planeClipTexture; uniform sampler2D wallClipTexture; @@ -272,6 +270,7 @@ private static string AdjustSpriteVertexClip() void main() { + int boundTextureIndex = 0; ${CheckPlaneClip} ${LightLevelFragFunction} ${SectorColorMapFragFunction} diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Shader/PlaneClip.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Shader/PlaneClip.cs index a80a08a69..c0540e577 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Shader/PlaneClip.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Shader/PlaneClip.cs @@ -1,6 +1,4 @@ -using System; - -namespace Helion.Render.OpenGL.Renderers.Legacy.World.Shader; +namespace Helion.Render.OpenGL.Renderers.Legacy.World.Shader; public enum WallClipFragOptions { @@ -27,11 +25,11 @@ public static string WritePlaneFragFunction(PlaneClipFragOptions options) flat in float zPos; flat in float upperFrag; flat in float lowerFrag; + flat in float boundTextureIndex; in float depthFrag; ${{InVars}} uniform sampler2DArray boundTexture; - uniform int boundTextureIndex; layout (location = 0) out vec3 outPlane; @@ -54,11 +52,11 @@ public static string WriteWallFragFunction(WallClipFragOptions options) flat in float mapIdFrag; flat in float upperFrag; flat in float lowerFrag; + flat in float boundTextureIndex; in float depthFrag; ${InVars} uniform sampler2DArray boundTexture; - uniform int boundTextureIndex; layout (location = 0) out vec4 outPlane; From f928660163475287c2ec8ab01f14c269aac3ba2f Mon Sep 17 00:00:00 2001 From: Nick Date: Sat, 12 Sep 2026 06:53:40 -0400 Subject: [PATCH 23/49] corect samplers for brightmap --- Core/Render/OpenGL/Renderers/Legacy/Hud/LegacyHudShader.cs | 4 ++-- .../OpenGL/Renderers/Legacy/World/Entities/EntityProgram.cs | 2 +- .../World/Geometry/Portals/FloodFill/FloodFillProgram.cs | 2 +- .../OpenGL/Renderers/Legacy/World/InterpolationShader.cs | 2 +- .../OpenGL/Renderers/Legacy/World/Shader/FragFunction.cs | 4 ++-- Core/Render/OpenGL/Renderers/Legacy/World/StaticShader.cs | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Core/Render/OpenGL/Renderers/Legacy/Hud/LegacyHudShader.cs b/Core/Render/OpenGL/Renderers/Legacy/Hud/LegacyHudShader.cs index 1197a1a98..223afe76b 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/Hud/LegacyHudShader.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/Hud/LegacyHudShader.cs @@ -112,7 +112,7 @@ private static string GetBrightmapColorBlend() return @" if (useBrightmaps == 1) - fragColor.rgb *= mix(vec3(1.0), min(vec3(1.0), texture(brightmapTexture, uvFrag.st).rgb + rgbMultiplierFrag.rgb), rgbMultiplierFrag.w); + fragColor.rgb *= mix(vec3(1.0), min(vec3(1.0), texture(brightmapTexture, vec3(uvFrag.st, 0)).rgb + rgbMultiplierFrag.rgb), rgbMultiplierFrag.w); else fragColor.xyz *= mix(vec3(1.0), rgbMultiplierFrag.xyz, rgbMultiplierFrag.w);"; } @@ -131,7 +131,7 @@ private static string GetBrightmapColorBlend() uniform sampler2DArray boundTexture; uniform sampler2D opaqueTexture; - uniform sampler2D brightmapTexture; + uniform sampler2DArray brightmapTexture; uniform samplerBuffer colormapTexture; uniform float fuzzFrac; uniform float fuzzDiv; diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityProgram.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityProgram.cs index b66e6163c..4fe7b93fd 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityProgram.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityProgram.cs @@ -233,7 +233,7 @@ private static string AdjustSpriteVertexClip() uniform int hasInvulnerability; uniform float fuzzFrac; uniform sampler2DArray boundTexture; - uniform sampler2D brightmapTexture; + uniform sampler2DArray brightmapTexture; uniform samplerBuffer colormapTexture; uniform float lightLevelMix; uniform int extraLight; diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Portals/FloodFill/FloodFillProgram.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Portals/FloodFill/FloodFillProgram.cs index 27b48efec..7fdad609c 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Portals/FloodFill/FloodFillProgram.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Portals/FloodFill/FloodFillProgram.cs @@ -109,7 +109,7 @@ protected override string FragmentShader() out vec4 fragColor; uniform sampler2DArray boundTexture; - uniform sampler2D brightmapTexture; + uniform sampler2DArray brightmapTexture; uniform vec3 camera; uniform mat4 mvpNoPitch; uniform mat4 mvp; diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/InterpolationShader.cs b/Core/Render/OpenGL/Renderers/Legacy/World/InterpolationShader.cs index 967aa8134..cd5309f7f 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/InterpolationShader.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/InterpolationShader.cs @@ -123,7 +123,7 @@ protected override string FragmentShader() uniform int hasInvulnerability; uniform sampler2DArray boundTexture; - uniform sampler2D brightmapTexture; + uniform sampler2DArray brightmapTexture; uniform vec3 colorMix; uniform int paletteIndex; uniform int colormapIndex; diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Shader/FragFunction.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Shader/FragFunction.cs index 8fa50e19e..9942a295b 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Shader/FragFunction.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Shader/FragFunction.cs @@ -165,7 +165,7 @@ public static string ColorMapFetch(bool lightLevel, ColorMapFetchContext ctx) static string BrightMapLightColorIndexFetch(string uvVar) => @$" - vec3 brightColor = texture(brightmapTexture, {uvVar}).rgb; + vec3 brightColor = texture(brightmapTexture, vec3({uvVar}, boundTextureIndex)).rgb; float hasBrightColor = float(brightColor.r != 0 && brightColor.r == brightColor.g && brightColor.g == brightColor.b); lightColorIndex = int(mix(lightColorIndex, min(int((1 - brightColor.r) * 31), lightColorIndex), hasBrightColor)); "; @@ -232,7 +232,7 @@ private static string GetBrightMapBlend(FragColorFunctionOptions options) return @" if (useBrightmaps == 1) - fragColor.rgb *= min(vec3(1.0), texture(brightmapTexture, texUV).rgb + vec3(lightLevel)); + fragColor.rgb *= min(vec3(1.0), texture(brightmapTexture, vec3(texUV, boundTextureIndex)).rgb + vec3(lightLevel)); else fragColor.rgb *= lightLevel;"; } diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/StaticShader.cs b/Core/Render/OpenGL/Renderers/Legacy/World/StaticShader.cs index 7206c5964..deef0b54a 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/StaticShader.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/StaticShader.cs @@ -119,7 +119,7 @@ protected override string FragmentShader() uniform int hasInvulnerability; uniform sampler2DArray boundTexture; - uniform sampler2D brightmapTexture; + uniform sampler2DArray brightmapTexture; uniform vec3 colorMix; uniform int paletteIndex; uniform int colormapIndex; From ea7f5fe6f3c837708b73392eed5c350047ccead9 Mon Sep 17 00:00:00 2001 From: Nick Date: Sat, 12 Sep 2026 06:56:32 -0400 Subject: [PATCH 24/49] exclude brightmaps from array textures --- Core/Render/OpenGL/Texture/Legacy/GLTextureArrayBuilder.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Core/Render/OpenGL/Texture/Legacy/GLTextureArrayBuilder.cs b/Core/Render/OpenGL/Texture/Legacy/GLTextureArrayBuilder.cs index 6c73ee08e..a0f095578 100644 --- a/Core/Render/OpenGL/Texture/Legacy/GLTextureArrayBuilder.cs +++ b/Core/Render/OpenGL/Texture/Legacy/GLTextureArrayBuilder.cs @@ -80,8 +80,9 @@ private int BuildTextureArrayFromTextures(DynamicArray textur private void AddTexture(DynamicArray textures, int index) { + // Exclude brightmaps for now. They probably could be made into arrays but like animations the complication may not be worth it. var texture = m_textureManager.GetTexture(index); - if (texture.Image != null) + if (texture.Image != null && texture.BrightmapImage == null) textures.Add(texture); } From 38739bd1147a9dda62ebf9e487cd6000784eb946 Mon Sep 17 00:00:00 2001 From: Nick Date: Sat, 12 Sep 2026 08:41:19 -0400 Subject: [PATCH 25/49] add boundTextureIndex as constants since they aren't used in entity/hud shaders --- Core/Render/OpenGL/Renderers/Legacy/Hud/LegacyHudShader.cs | 2 ++ .../OpenGL/Renderers/Legacy/World/Entities/EntityProgram.cs | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Core/Render/OpenGL/Renderers/Legacy/Hud/LegacyHudShader.cs b/Core/Render/OpenGL/Renderers/Legacy/Hud/LegacyHudShader.cs index 223afe76b..ce2294f5c 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/Hud/LegacyHudShader.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/Hud/LegacyHudShader.cs @@ -145,6 +145,8 @@ private static string GetBrightmapColorBlend() uniform vec2 fuzzSampleOffset; uniform int fuzzRefraction; + const int boundTextureIndex = 0; + ${FuzzFunction} void main() { diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityProgram.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityProgram.cs index 4fe7b93fd..72513005d 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityProgram.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityProgram.cs @@ -268,9 +268,10 @@ private static string AdjustSpriteVertexClip() ${FuzzFunction} ${SoftwareSpriteEmulationFunctions} + const int boundTextureIndex = 0; + void main() { - int boundTextureIndex = 0; ${CheckPlaneClip} ${LightLevelFragFunction} ${SectorColorMapFragFunction} From 5c10805c4d21705e9579babcd13c4c9abe61a9f7 Mon Sep 17 00:00:00 2001 From: Nick Date: Sat, 12 Sep 2026 09:16:54 -0400 Subject: [PATCH 26/49] add hud elements draw tracking and drawcounts command --- Client/Client.Commands.cs | 15 +++++++++++++++ .../Renderers/Legacy/Hud/LegacyHudRenderer.cs | 6 +++++- Core/Render/Renderer.cs | 7 ++++++- Core/Util/Profiling/Timers/RenderProfiler.cs | 1 + 4 files changed, 27 insertions(+), 2 deletions(-) diff --git a/Client/Client.Commands.cs b/Client/Client.Commands.cs index ddf6da33d..55f730f43 100644 --- a/Client/Client.Commands.cs +++ b/Client/Client.Commands.cs @@ -657,6 +657,21 @@ private void Use(ConsoleCommandEventArgs args) => private static void GarbageCollect(ConsoleCommandEventArgs args) => GCUtil.ForceGarbageCollection(); + [ConsoleCommand("drawcounts", "Logs the draw counts of the renderer.")] + private void DrawCounts(ConsoleCommandEventArgs args) + { + ref var counts = ref m_window.Renderer.LastDrawCounts; + HelionLog.Info($"GeometryStatic: {counts.GeometryStatic}"); + HelionLog.Info($"GeometryDynamic: {counts.GeometryDynamic}"); + HelionLog.Info($"FloodFillStatic: {counts.FloodFillStatic}"); + HelionLog.Info($"FloodFillDynamic: {counts.FloodFillDynamic}"); + HelionLog.Info($"Skies: {counts.Skies}"); + HelionLog.Info($"Sprites: {counts.Sprites}"); + HelionLog.Info($"HudElements: {counts.HudElements}"); + HelionLog.Info($"Level Total: {counts.GeometryStatic + counts.GeometryDynamic + counts.FloodFillStatic + counts.FloodFillDynamic + counts.Skies + counts.Sprites}"); + HelionLog.Info($"Total: {counts.GeometryStatic + counts.GeometryDynamic + counts.FloodFillStatic + counts.FloodFillDynamic + counts.Skies + counts.Sprites + counts.HudElements}"); + } + private void DoUseCommand(ConsoleCommandEventArgs args) { if (m_layerManager.WorldLayer == null || args.Args.Count == 0) diff --git a/Core/Render/OpenGL/Renderers/Legacy/Hud/LegacyHudRenderer.cs b/Core/Render/OpenGL/Renderers/Legacy/Hud/LegacyHudRenderer.cs index 39023bf9e..24cf46de8 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/Hud/LegacyHudRenderer.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/Hud/LegacyHudRenderer.cs @@ -13,6 +13,7 @@ using Helion.Resources.Definitions.Zdoom; using Helion.Util; using Helion.Util.Configs; +using Helion.Util.Profiling.Timers; using OpenTK.Graphics.OpenGL; using static Helion.Util.Assertion.Assert; @@ -25,12 +26,14 @@ public class LegacyHudRenderer : HudRenderer private readonly LegacyHudShader m_program; private readonly HudDrawBuffer m_drawBuffer; private readonly IConfig m_config; + private readonly RenderProfiler m_renderProfiler; private float DrawDepth = 1.0f; - public LegacyHudRenderer(IConfig config, LegacyGLTextureManager textureManager, DataCache dataCache) + public LegacyHudRenderer(IConfig config, LegacyGLTextureManager textureManager, DataCache dataCache, RenderProfiler renderProfiler) { m_config = config; m_textureManager = textureManager; + m_renderProfiler = renderProfiler; m_program = new(); m_pipeline = new(m_program, new StreamVertexBuffer("Hud"), "Hud"); m_drawBuffer = new(dataCache); @@ -178,6 +181,7 @@ public override void Render(Rectangle viewport, Dimension windowDimension, Dimen GL.BindTexture(TextureTarget.Texture2D, 0); m_pipeline.DrawArrays(); data.Texture.Unbind(); + m_renderProfiler.DrawCounts.HudElements++; } m_pipeline.Unbind(); diff --git a/Core/Render/Renderer.cs b/Core/Render/Renderer.cs index 5dad134cf..570e28bbe 100644 --- a/Core/Render/Renderer.cs +++ b/Core/Render/Renderer.cs @@ -51,6 +51,8 @@ public partial class Renderer : IDisposable public readonly IWindow Window; public readonly GLSurface Default; + public readonly RenderProfiler RenderProfiler; + public DrawCounts LastDrawCounts; private GLFramebuffer m_mainFramebuffer; private GLFramebuffer? m_virtualFramebuffer; private GLFramebuffer m_worldFramebuffer; @@ -86,6 +88,7 @@ public partial class Renderer : IDisposable public Renderer(IWindow window, IConfig config, ArchiveCollection archiveCollection, FpsTracker fpsTracker, RenderProfiler renderProfiler) { Window = window; + RenderProfiler = renderProfiler; m_config = config; m_archiveCollection = archiveCollection; m_fpsTracker = fpsTracker; @@ -95,7 +98,7 @@ public Renderer(IWindow window, IConfig config, ArchiveCollection archiveCollect Textures = new LegacyGLTextureManager(config, archiveCollection); m_worldRenderer = new LegacyWorldRenderer(config, archiveCollection, Textures, renderProfiler); - m_hudRenderer = new LegacyHudRenderer(config, Textures, archiveCollection.DataCache); + m_hudRenderer = new LegacyHudRenderer(config, Textures, archiveCollection.DataCache, renderProfiler); m_automapRenderer = new LegacyAutomapRenderer(archiveCollection); m_transitionRenderer = new TransitionRenderer(window); Default = new(window, this); @@ -608,6 +611,8 @@ public void Render(RenderCommands renderCommands) if (!virtualFrameBufferDraw) DrawVirtualFramebufferToMain(); + + LastDrawCounts = RenderProfiler.DrawCounts; } private static void HandleScissorCommand(ScissorCommand cmd) diff --git a/Core/Util/Profiling/Timers/RenderProfiler.cs b/Core/Util/Profiling/Timers/RenderProfiler.cs index bf2a4cd4e..c3bb5698c 100644 --- a/Core/Util/Profiling/Timers/RenderProfiler.cs +++ b/Core/Util/Profiling/Timers/RenderProfiler.cs @@ -10,6 +10,7 @@ public struct DrawCounts public int FloodFillStatic; public int FloodFillDynamic; public int Skies; + public int HudElements; } public class RenderProfiler : ProfileComponent From 08cf14f682e4587f208c86ccbb0be4e8eb87f5af Mon Sep 17 00:00:00 2001 From: Nick Date: Sat, 12 Sep 2026 15:22:35 -0400 Subject: [PATCH 27/49] add count functions --- Client/Client.Commands.cs | 4 ++-- Core/Util/Profiling/Timers/RenderProfiler.cs | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Client/Client.Commands.cs b/Client/Client.Commands.cs index 55f730f43..a0dba9809 100644 --- a/Client/Client.Commands.cs +++ b/Client/Client.Commands.cs @@ -668,8 +668,8 @@ private void DrawCounts(ConsoleCommandEventArgs args) HelionLog.Info($"Skies: {counts.Skies}"); HelionLog.Info($"Sprites: {counts.Sprites}"); HelionLog.Info($"HudElements: {counts.HudElements}"); - HelionLog.Info($"Level Total: {counts.GeometryStatic + counts.GeometryDynamic + counts.FloodFillStatic + counts.FloodFillDynamic + counts.Skies + counts.Sprites}"); - HelionLog.Info($"Total: {counts.GeometryStatic + counts.GeometryDynamic + counts.FloodFillStatic + counts.FloodFillDynamic + counts.Skies + counts.Sprites + counts.HudElements}"); + HelionLog.Info($"Level Total: {counts.LevelTotal()}"); + HelionLog.Info($"Total: {counts.Total()}"); } private void DoUseCommand(ConsoleCommandEventArgs args) diff --git a/Core/Util/Profiling/Timers/RenderProfiler.cs b/Core/Util/Profiling/Timers/RenderProfiler.cs index c3bb5698c..61be99907 100644 --- a/Core/Util/Profiling/Timers/RenderProfiler.cs +++ b/Core/Util/Profiling/Timers/RenderProfiler.cs @@ -11,6 +11,9 @@ public struct DrawCounts public int FloodFillDynamic; public int Skies; public int HudElements; + + public readonly int Total() => GeometryStatic + GeometryDynamic + FloodFillStatic + FloodFillDynamic + Skies + Sprites + HudElements; + public readonly int LevelTotal() => Total() - HudElements; } public class RenderProfiler : ProfileComponent From 9153913458407483dcd818c6954a4d76a4cd843c Mon Sep 17 00:00:00 2001 From: Nick Date: Sat, 12 Sep 2026 15:38:57 -0400 Subject: [PATCH 28/49] correct brightmap clear texture binds --- Core/Render/OpenGL/Renderers/Legacy/Hud/LegacyHudRenderer.cs | 2 +- Core/Render/OpenGL/Renderers/Legacy/World/Data/RenderData.cs | 2 +- .../OpenGL/Renderers/Legacy/World/Data/RenderWorldData.cs | 2 +- .../World/Geometry/Portals/FloodFill/FloodFillRenderer.cs | 2 +- .../World/Geometry/Static/StaticCacheGeometryRenderer.cs | 4 ++-- Core/Render/OpenGL/Renderers/Legacy/World/StaticShader.cs | 5 +++++ 6 files changed, 11 insertions(+), 6 deletions(-) diff --git a/Core/Render/OpenGL/Renderers/Legacy/Hud/LegacyHudRenderer.cs b/Core/Render/OpenGL/Renderers/Legacy/Hud/LegacyHudRenderer.cs index 24cf46de8..355f36565 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/Hud/LegacyHudRenderer.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/Hud/LegacyHudRenderer.cs @@ -178,7 +178,7 @@ public override void Render(Rectangle viewport, Dimension windowDimension, Dimen if (data.BrightmapTexture != null) data.BrightmapTexture.Bind(); else - GL.BindTexture(TextureTarget.Texture2D, 0); + GL.BindTexture(data.Texture.Target, 0); m_pipeline.DrawArrays(); data.Texture.Unbind(); m_renderProfiler.DrawCounts.HudElements++; diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Data/RenderData.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Data/RenderData.cs index d852f7ee0..86516d6cb 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Data/RenderData.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Data/RenderData.cs @@ -56,7 +56,7 @@ public bool Draw() if (BrightMapTexture != null) BrightMapTexture.Bind(); else - GL.BindTexture(TextureTarget.Texture2D, 0); + GL.BindTexture(Texture.Target, 0); Pipeline.Bind(true); Pipeline.Vbo.Upload(); diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Data/RenderWorldData.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Data/RenderWorldData.cs index 12c53c567..8b5d11a4b 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Data/RenderWorldData.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Data/RenderWorldData.cs @@ -57,7 +57,7 @@ public bool Draw() if (BrightmapTexture != null) BrightmapTexture.Bind(); else - GL.BindTexture(TextureTarget.Texture2D, 0); + GL.BindTexture(Texture.Target, 0); Pipeline.Bind(true); Pipeline.Vbo.Upload(); diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Portals/FloodFill/FloodFillRenderer.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Portals/FloodFill/FloodFillRenderer.cs index 26db9df8d..f92eec1b4 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Portals/FloodFill/FloodFillRenderer.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Portals/FloodFill/FloodFillRenderer.cs @@ -338,7 +338,7 @@ private void Render(FloodFillProgram program, RenderInfo renderInfo, bool wallCl if (brightmapTexture != null) brightmapTexture.Bind(); else - GL.BindTexture(TextureTarget.Texture2D, 0); + GL.BindTexture(texture.Target, 0); } info.Pipeline.Vbo.UploadIfNeeded(); diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/StaticCacheGeometryRenderer.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/StaticCacheGeometryRenderer.cs index efda3bc05..e3699da72 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/StaticCacheGeometryRenderer.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/StaticCacheGeometryRenderer.cs @@ -843,7 +843,7 @@ private static void RenderCoverInternal(GeometryData? data) GL.ActiveTexture(BindTextures.BoundTexture); texture.Bind(); GL.ActiveTexture(BindTextures.BrightmapTexture); - GL.BindTexture(TextureTarget.Texture2D, 0); + GL.BindTexture(data.Texture.Target, 0); data.Pipeline.Vbo.UploadCapacity(); data.Pipeline.Bind(); @@ -878,7 +878,7 @@ private void RenderGeometry(List geometry) if (brightmapTexture != null) brightmapTexture.Bind(); else - GL.BindTexture(TextureTarget.Texture2D, 0); + GL.BindTexture(texture.Target, 0); data.Pipeline.Vbo.UploadIfNeeded(); diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/StaticShader.cs b/Core/Render/OpenGL/Renderers/Legacy/World/StaticShader.cs index deef0b54a..edc2814f4 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/StaticShader.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/StaticShader.cs @@ -99,6 +99,11 @@ protected override string FragmentShader() bool planeClip = this is StaticPlaneClipShaderMrt; + if (!planeClip) + { + int lol = 1; + } + return @" #version 330 From 33833e3635fe8f3d98ef3429cddaff3ad6ed0233 Mon Sep 17 00:00:00 2001 From: Nick Date: Sat, 12 Sep 2026 18:52:34 -0400 Subject: [PATCH 29/49] refactor so array textures have context for future use and use ArrayTextureData --- .../Portals/FloodFill/FloodFillRenderer.cs | 4 +- .../Geometry/Static/FreeGeometryManager.cs | 4 +- .../Geometry/Static/GeometryTextureLookup.cs | 4 +- .../Renderers/Legacy/World/StaticShader.cs | 5 -- .../Render/OpenGL/Texture/ArrayTextureData.cs | 52 ++++++++++++++ Core/Render/OpenGL/Texture/GLTexture.cs | 10 ++- .../Render/OpenGL/Texture/GLTextureManager.cs | 68 ++++++++++--------- .../OpenGL/Texture/IArrayTextureLookup.cs | 2 +- .../OpenGL/Texture/Legacy/GLLegacyTexture.cs | 4 +- .../Texture/Legacy/GLTextureArrayBuilder.cs | 4 +- .../Texture/Legacy/LegacyGLTextureManager.cs | 6 +- Core/Resources/Texture.cs | 6 ++ 12 files changed, 117 insertions(+), 52 deletions(-) create mode 100644 Core/Render/OpenGL/Texture/ArrayTextureData.cs diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Portals/FloodFill/FloodFillRenderer.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Portals/FloodFill/FloodFillRenderer.cs index f92eec1b4..983e49c30 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Portals/FloodFill/FloodFillRenderer.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Portals/FloodFill/FloodFillRenderer.cs @@ -62,7 +62,7 @@ private FloodFillInfo CreateFloodFillInfo(SectorPlane plane) private FloodFillInfo GetOrCreateFloodFillInfo(SectorPlane plane) { - var textureHandle = m_glTextureManager.GetArrayTextureHandle(plane.TextureHandle, true); + var textureHandle = m_glTextureManager.GetWorldArrayTextureHandle(plane.TextureHandle, true); if (m_textureHandleToFloodFillInfoIndex.TryGetValue(textureHandle, out int index)) return m_floodFillInfos[index]; @@ -87,7 +87,7 @@ private ref FloodGeometry TryGetFloodGeometry(int floodKey, out bool success) private bool TryGetFloodFillInfoIndex(int textureHandle, out int index) { - textureHandle = m_glTextureManager.GetArrayTextureHandle(textureHandle, true); + textureHandle = m_glTextureManager.GetWorldArrayTextureHandle(textureHandle, true); var success = m_textureHandleToFloodFillInfoIndex.TryGetValue(textureHandle, out index); Assert.Precondition(success, $"Failed to find flood fill info for {textureHandle}"); return success; diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/FreeGeometryManager.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/FreeGeometryManager.cs index 43981590e..ba637955d 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/FreeGeometryManager.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/FreeGeometryManager.cs @@ -40,7 +40,7 @@ public void Add(in StaticGeometryData geometryData, GeometryType type, bool repe if (geometryData.GeometryData == null) return; - var textureHandle = m_arrayTextureLookup.GetArrayTextureHandle(geometryData.GeometryData.TextureHandle, repeatY); + var textureHandle = m_arrayTextureLookup.GetWorldArrayTextureHandle(geometryData.GeometryData.TextureHandle, repeatY); var key = new GeometryKey(textureHandle, type, repeatY); if (!m_data.TryGetValue(key, out var list)) @@ -88,7 +88,7 @@ public bool GetAndRemove(int textureHandle, GeometryType type, bool repeatY, int { int minLength = int.MaxValue; int minIndex = -1; - textureHandle = m_arrayTextureLookup.GetArrayTextureHandle(textureHandle, repeatY); + textureHandle = m_arrayTextureLookup.GetWorldArrayTextureHandle(textureHandle, repeatY); var key = new GeometryKey(textureHandle, type, repeatY); if (!m_data.TryGetValue(key, out var list)) diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/GeometryTextureLookup.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/GeometryTextureLookup.cs index a6f329bba..74ef0a5d1 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/GeometryTextureLookup.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/Static/GeometryTextureLookup.cs @@ -17,13 +17,13 @@ public void Clear() public bool TryGetValue(GeometryType type, int textureHandle, bool repeatY, [NotNullWhen(true)] out GeometryData? value) { - textureHandle = m_arrayTextureLookup.GetArrayTextureHandle(textureHandle, repeatY); + textureHandle = m_arrayTextureLookup.GetWorldArrayTextureHandle(textureHandle, repeatY); return m_lookup.Get(type).TryGetValue(textureHandle, repeatY, out value); } public void Add(GeometryType type, int textureHandle, bool repeatY, GeometryData data) { - textureHandle = m_arrayTextureLookup.GetArrayTextureHandle(textureHandle, repeatY); + textureHandle = m_arrayTextureLookup.GetWorldArrayTextureHandle(textureHandle, repeatY); m_lookup.Get(type).Add(textureHandle, repeatY, data); } } diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/StaticShader.cs b/Core/Render/OpenGL/Renderers/Legacy/World/StaticShader.cs index edc2814f4..deef0b54a 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/StaticShader.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/StaticShader.cs @@ -99,11 +99,6 @@ protected override string FragmentShader() bool planeClip = this is StaticPlaneClipShaderMrt; - if (!planeClip) - { - int lol = 1; - } - return @" #version 330 diff --git a/Core/Render/OpenGL/Texture/ArrayTextureData.cs b/Core/Render/OpenGL/Texture/ArrayTextureData.cs new file mode 100644 index 000000000..30db1e50d --- /dev/null +++ b/Core/Render/OpenGL/Texture/ArrayTextureData.cs @@ -0,0 +1,52 @@ +using Helion.Util.Assertion; +using Helion.Util.Container; +using System.Collections.Generic; + +namespace Helion.Render.OpenGL.Texture; + +internal sealed class ArrayTextureData(TextureContext context) where GLTextureType : GLTexture +{ + public readonly TextureContext Context = context; + + private readonly DynamicArray m_arraySubTextures = new(256); + private readonly DynamicArray m_arrayTextures = new(256); + private readonly DynamicArray m_texturesForArrays = new(256); + private readonly Dictionary m_arrayTextureLookup = []; + private readonly Dictionary m_arrayTextureLookupClamp = []; + + public void Add(GLTextureType arrayTexture, GLTextureType[] arraySubTextures, Resources.Texture[] textures, bool clamp) + { + Assert.Precondition(arraySubTextures.Length == textures.Length, "arraySubTextures != textures length"); + // Flip high bit to ensure no collisions + var arrayTextureId = arrayTexture.TextureId | (1 << 30); + m_arrayTextures.Add(arrayTexture); + m_arraySubTextures.Add(arraySubTextures); + m_texturesForArrays.Add(textures); + var lookup = clamp ? m_arrayTextureLookup : m_arrayTextureLookupClamp; + for (int i = 0; i < textures.Length; i++) + lookup[textures[i].Index] = arrayTextureId; + } + + public int GetArrayTextureHandle(int textureHandle, bool clamp) + { + var lookup = clamp ? m_arrayTextureLookup : m_arrayTextureLookupClamp; + if (lookup.TryGetValue(textureHandle, out var handle)) + return handle; + return textureHandle; + } + + public void Destroy() + { + for (int i = 0; i < m_arrayTextures.Length; i++) + m_arrayTextures[i].Dispose(); + + for (int i = 0; i < m_texturesForArrays.Length; i++) + m_texturesForArrays.Data[i].ClearGLTexture(); + + m_arrayTextures.Clear(); + m_arraySubTextures.Clear(); + m_arrayTextureLookup.Clear(); + m_arrayTextureLookupClamp.Clear(); + m_texturesForArrays.Clear(); + } +} diff --git a/Core/Render/OpenGL/Texture/GLTexture.cs b/Core/Render/OpenGL/Texture/GLTexture.cs index 1b4530e27..6319580f9 100644 --- a/Core/Render/OpenGL/Texture/GLTexture.cs +++ b/Core/Render/OpenGL/Texture/GLTexture.cs @@ -8,6 +8,12 @@ namespace Helion.Render.OpenGL.Texture; +public enum TextureContext +{ + Default, + WorldArray +} + public abstract class GLTexture : IRenderableTextureHandle, IDisposable { public int Index => TextureId; @@ -21,6 +27,7 @@ public abstract class GLTexture : IRenderableTextureHandle, IDisposable public readonly ResourceNamespace Namespace; public readonly TextureTarget Target; public readonly int TransparentPixelCount; + public readonly TextureContext Context; private readonly bool m_ownsTexture; private bool m_disposed; @@ -31,7 +38,7 @@ public abstract class GLTexture : IRenderableTextureHandle, IDisposable public int BlankRowsFromBottom; protected GLTexture(int textureId, string name, Dimension dimension, Vec2I offset, ResourceNamespace ns, TextureTarget target, - int transparentPixelCount, int blankRowsFromTop, int blankRowsFromBottom, bool ownsTexture = true) + int transparentPixelCount, int blankRowsFromTop, int blankRowsFromBottom, bool ownsTexture = true, TextureContext textureContext = TextureContext.Default) { TextureId = textureId; Name = name; @@ -44,6 +51,7 @@ protected GLTexture(int textureId, string name, Dimension dimension, Vec2I offse BlankRowsFromTop = blankRowsFromTop; BlankRowsFromBottom = blankRowsFromBottom; m_ownsTexture = ownsTexture; + Context = textureContext; } ~GLTexture() diff --git a/Core/Render/OpenGL/Texture/GLTextureManager.cs b/Core/Render/OpenGL/Texture/GLTextureManager.cs index cc18f2190..9f0c1e4dd 100644 --- a/Core/Render/OpenGL/Texture/GLTextureManager.cs +++ b/Core/Render/OpenGL/Texture/GLTextureManager.cs @@ -37,11 +37,7 @@ public abstract class GLTextureManager : IRendererTextureManager, protected readonly ResourceTracker TextureTracker = new(); protected readonly ResourceTracker TextureTrackerClamp = new(); private readonly Dictionary> m_fonts = new(StringComparer.OrdinalIgnoreCase); - private readonly DynamicArray m_arraySubTextures = new(256); - private readonly DynamicArray m_arrayTextures = new(256); - private readonly DynamicArray m_texturesForArrays = new(256); - private readonly Dictionary m_arrayTextureLookup = []; - private readonly Dictionary m_arrayTextureLookupClamp = []; + private readonly LookupArray?> m_arrayTextures = new(); private bool m_disposed; private TextureManager TextureManager => ArchiveCollection.TextureManager; @@ -97,22 +93,28 @@ private SpriteRotation CreateNullSpriteRotation() Dispose(); } - public void DestroyTextureArrays() + public void DestroyTextureArrays(TextureContext textureContext) { - for (int i = 0; i < m_arrayTextures.Length; i++) - m_arrayTextures.Data[i].Dispose(); + if (!m_arrayTextures.TryGetValue((int)textureContext, out var arrayTextureData)) + return; - for (int i = 0; i < m_texturesForArrays.Length; i++) - { - var texture = m_texturesForArrays.Data[i]; - texture.RenderStore = null; - texture.RenderStoreClamp = null; - } + arrayTextureData.Destroy(); + m_arrayTextures.Set((int)textureContext, null); + //for (int i = 0; i < m_arrayTextures.Length; i++) + // m_arrayTextures[i].Dispose(); + + //for (int i = 0; i < m_texturesForArrays.Length; i++) + //{ + // var texture = m_texturesForArrays.Data[i]; + // texture.RenderStore = null; + // texture.RenderStoreClamp = null; + //} - m_arrayTextures.Clear(); - m_arraySubTextures.Clear(); - m_arrayTextureLookup.Clear(); - m_texturesForArrays.Clear(); + //m_arrayTextures.Clear(); + //m_arraySubTextures.Clear(); + //m_arrayTextureLookup.Clear(); + //m_arrayTextureLookupClamp.Clear(); + //m_texturesForArrays.Clear(); } public bool TryGet(string name, [NotNullWhen(true)] out IRenderableTextureHandle? handle, ResourceNamespace? specificNamespace = null, int upscalingFactor = 1, BrightmapDefinition? brightmap = null) @@ -234,7 +236,7 @@ public GLTextureType GetTexture(int index, bool repeatY = true) return (GLTextureType)renderTexture; } - public GLTextureType? CreateTextureArray(Span indices, bool repeatY) + public GLTextureType? CreateTextureArray(Span indices, TextureContext textureContext, bool repeatY) { if (indices.Length == 0) return null; @@ -251,25 +253,25 @@ public GLTextureType GetTexture(int index, bool repeatY = true) images[i] = texture.Image ?? new Image(default, ImageType.Argb); textures[i] = texture; - m_texturesForArrays.Add(texture); } - var lookup = repeatY ? m_arrayTextureLookup : m_arrayTextureLookupClamp; var flags = repeatY ? TextureFlags.Default : TextureFlags.ClampY; - var glTextures = GenerateTextureArray(images, images[0].Dimension, ResourceNamespace.Textures, flags, out var arrayTexture); - // Flip high bit to ensure no collisions - var arrayTextureId = arrayTexture.TextureId | (1 << 30); + var arraySubTextures = GenerateTextureArray(images, images[0].Dimension, ResourceNamespace.Textures, flags, textureContext, out var arrayTexture); for (int i = 0; i < images.Length; i++) { - var glTexture = glTextures[i]; + var glTexture = arraySubTextures[i]; var texture = textures[i]; SetDebugName(glTexture, texture); - lookup[texture.Index] = arrayTextureId; texture.SetGLTexture(glTexture, repeatY); } - m_arrayTextures.Add(arrayTexture); - m_arraySubTextures.Add(glTextures); + if (!m_arrayTextures.TryGetValue((int)textureContext, out var arrayTextureData)) + { + arrayTextureData = new(textureContext); + m_arrayTextures.Set((int)textureContext, arrayTextureData); + } + + arrayTextureData.Add(arrayTexture, arraySubTextures, textures, repeatY); return arrayTexture; } @@ -279,11 +281,11 @@ private static void SetDebugName(GLTextureType glTexture, Resources.Texture text glTexture.Name = $"{glTexture.Name} {texture.Index}:{texture.Name}"; } - public int GetArrayTextureHandle(int textureHandle, bool repeatY) + public int GetWorldArrayTextureHandle(int textureHandle, bool repeatY) { - var lookup = repeatY ? m_arrayTextureLookup : m_arrayTextureLookupClamp; - if (lookup.TryGetValue(textureHandle, out var handle)) - return handle; + if (m_arrayTextures.TryGetValue((int)TextureContext.WorldArray, out var arrayTextureData)) + return arrayTextureData.GetArrayTextureHandle(textureHandle, repeatY); + return textureHandle; } @@ -515,7 +517,7 @@ private void ReleaseUnmanagedResources() } protected abstract GLTextureType GenerateTexture(Image image, string name, ResourceNamespace resourceNamespace, TextureFlags flags = TextureFlags.Default); - protected abstract GLTextureType[] GenerateTextureArray(Image[] images, Dimension dimension, ResourceNamespace resourceNamespace, TextureFlags flags, out GLTextureType arrayTexture); + protected abstract GLTextureType[] GenerateTextureArray(Image[] images, Dimension dimension, ResourceNamespace resourceNamespace, TextureFlags flags, TextureContext textureContext, out GLTextureType arrayTexture); public abstract void ReUpload(GLTextureType texture, Image image, uint[] imagePixels); diff --git a/Core/Render/OpenGL/Texture/IArrayTextureLookup.cs b/Core/Render/OpenGL/Texture/IArrayTextureLookup.cs index f9df6de38..ead917743 100644 --- a/Core/Render/OpenGL/Texture/IArrayTextureLookup.cs +++ b/Core/Render/OpenGL/Texture/IArrayTextureLookup.cs @@ -4,5 +4,5 @@ public interface IArrayTextureLookup { // Returns unique base array texture handle given a texture handle from the TextureManager // If the texture does not have a parent array texture then the textureHandle parameter is returned - int GetArrayTextureHandle(int textureHandle, bool repeatY); + int GetWorldArrayTextureHandle(int textureHandle, bool repeatY); } diff --git a/Core/Render/OpenGL/Texture/Legacy/GLLegacyTexture.cs b/Core/Render/OpenGL/Texture/Legacy/GLLegacyTexture.cs index 277cee4d2..b19edd3cc 100644 --- a/Core/Render/OpenGL/Texture/Legacy/GLLegacyTexture.cs +++ b/Core/Render/OpenGL/Texture/Legacy/GLLegacyTexture.cs @@ -13,8 +13,8 @@ public class GLLegacyTexture : GLTexture public GLLegacyTexture? ParentArrayTexture; public GLLegacyTexture(int textureId, string name, Dimension dimension, Vec2I offset, ResourceNamespace ns, TextureTarget target, - int transparentPixelCount, int blankRowsFromTop = 0, int blankRowsFromBottom = 0, bool ownsTexture = true) - : base(textureId, name, dimension, offset, ns, TextureTarget.Texture2DArray, transparentPixelCount, blankRowsFromTop, blankRowsFromBottom, ownsTexture) + int transparentPixelCount, int blankRowsFromTop = 0, int blankRowsFromBottom = 0, bool ownsTexture = true, TextureContext textureContext = TextureContext.Default) + : base(textureId, name, dimension, offset, ns, TextureTarget.Texture2DArray, transparentPixelCount, blankRowsFromTop, blankRowsFromBottom, ownsTexture, textureContext) { } diff --git a/Core/Render/OpenGL/Texture/Legacy/GLTextureArrayBuilder.cs b/Core/Render/OpenGL/Texture/Legacy/GLTextureArrayBuilder.cs index a0f095578..22f4a0f78 100644 --- a/Core/Render/OpenGL/Texture/Legacy/GLTextureArrayBuilder.cs +++ b/Core/Render/OpenGL/Texture/Legacy/GLTextureArrayBuilder.cs @@ -16,7 +16,7 @@ public class GLTextureArrayBuilder(TextureManager textureManager, LegacyGLTextur public void BuildLevel(IEnumerable flatTextures, IEnumerable wallTexturesRepeat, IEnumerable wallTexturesClamp) { - m_glTextureManager.DestroyTextureArrays(); + m_glTextureManager.DestroyTextureArrays(TextureContext.WorldArray); // Animations are excluded. This leaves them reliant on their previous behavior of swapping textures. int buildTextures = 0; @@ -99,7 +99,7 @@ private static int SortTexturesByDimensions(Resources.Texture x, Resources.Textu private int BuildTextureArray(Span textures, bool repeatY) { - var arrayTexture = m_glTextureManager.CreateTextureArray(textures, repeatY); + var arrayTexture = m_glTextureManager.CreateTextureArray(textures, TextureContext.WorldArray, repeatY); return arrayTexture == null ? 0 : 1; } } diff --git a/Core/Render/OpenGL/Texture/Legacy/LegacyGLTextureManager.cs b/Core/Render/OpenGL/Texture/Legacy/LegacyGLTextureManager.cs index cc0c8a161..5ee93fd6c 100644 --- a/Core/Render/OpenGL/Texture/Legacy/LegacyGLTextureManager.cs +++ b/Core/Render/OpenGL/Texture/Legacy/LegacyGLTextureManager.cs @@ -218,10 +218,12 @@ protected override GLLegacyTexture GenerateTexture(Image image, string name, return texture; } - protected override GLLegacyTexture[] GenerateTextureArray(Image[] images, Dimension dimension, ResourceNamespace resourceNamespace, TextureFlags flags, out GLLegacyTexture arrayTexture) + protected override GLLegacyTexture[] GenerateTextureArray(Image[] images, Dimension dimension, ResourceNamespace resourceNamespace, TextureFlags flags, + TextureContext textureContext, out GLLegacyTexture arrayTexture) { int textureId = GL.GenTexture(); - arrayTexture = new GLLegacyTexture(textureId, $"Texture Array Length={images.Length} {flags}", dimension, default, resourceNamespace, TextureTarget.Texture2DArray, 0, 0, 0); + arrayTexture = new GLLegacyTexture(textureId, $"Texture Array Length={images.Length} {flags}", dimension, default, resourceNamespace, TextureTarget.Texture2DArray, 0, 0, 0, + textureContext: textureContext); UploadAndSetParameters3D(arrayTexture, images, arrayTexture.Name, resourceNamespace, flags); var textures = new GLLegacyTexture[images.Length]; diff --git a/Core/Resources/Texture.cs b/Core/Resources/Texture.cs index 725344cd8..279a6c1f8 100644 --- a/Core/Resources/Texture.cs +++ b/Core/Resources/Texture.cs @@ -27,6 +27,12 @@ public void SetBrightmapGLTexture(object glTexture, bool repeatY) BrightmapRenderStoreClamp = glTexture; } + public void ClearGLTexture() + { + RenderStore = null; + RenderStoreClamp = null; + } + /// /// Name of the texture. /// From 35d470084bac38b012507632aaee6913f1cc2a1c Mon Sep 17 00:00:00 2001 From: Nick Date: Sat, 12 Sep 2026 19:41:00 -0400 Subject: [PATCH 30/49] flag if cached --- .../OpenGL/Renderers/Legacy/World/LegacyWorldRenderer.cs | 3 ++- Core/Resources/SpriteDefinition.cs | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/LegacyWorldRenderer.cs b/Core/Render/OpenGL/Renderers/Legacy/World/LegacyWorldRenderer.cs index f4e9db04e..c8db11c86 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/LegacyWorldRenderer.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/LegacyWorldRenderer.cs @@ -126,10 +126,11 @@ public override void UpdateToNewWorld(IWorld world) for (int i = 0; i < spriteDefinitions.Length; i++) { var spriteDefinition = spriteDefinitions[i]; - if (spriteDefinition == null) + if (spriteDefinition == null || spriteDefinition.Cached) continue; m_textureManager.CacheSpriteRotations(spriteDefinition); + spriteDefinition.Cached = true; } m_geometryRenderer.UpdateTo(world); diff --git a/Core/Resources/SpriteDefinition.cs b/Core/Resources/SpriteDefinition.cs index 6dd9ef214..4a9c02b1c 100644 --- a/Core/Resources/SpriteDefinition.cs +++ b/Core/Resources/SpriteDefinition.cs @@ -14,6 +14,7 @@ public class SpriteDefinition public const int MaxRotationIndex = MaxRotations - 1; public SpriteRotation?[,] Rotations = new SpriteRotation[MaxFrames, MaxRotations]; public bool HasRotations; + public bool Cached; private static readonly Dictionary SpriteTextureLookup = []; From 2ead51963f4fde13c9c35bcc7a49711b571f192e Mon Sep 17 00:00:00 2001 From: Nick Date: Sun, 13 Sep 2026 05:10:23 -0400 Subject: [PATCH 31/49] pass through context --- .../OpenGL/Texture/Legacy/GLTextureArrayBuilder.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Core/Render/OpenGL/Texture/Legacy/GLTextureArrayBuilder.cs b/Core/Render/OpenGL/Texture/Legacy/GLTextureArrayBuilder.cs index 22f4a0f78..408c527e1 100644 --- a/Core/Render/OpenGL/Texture/Legacy/GLTextureArrayBuilder.cs +++ b/Core/Render/OpenGL/Texture/Legacy/GLTextureArrayBuilder.cs @@ -30,7 +30,7 @@ public void BuildLevel(IEnumerable flatTextures, IEnumerable wallTextu buildTextures += textures.Count; textures.Sort(SortTexturesByDimensions); - arrayTextures += BuildTextureArrayFromTextures(textures, true); + arrayTextures += BuildTextureArrayFromTextures(textures, TextureContext.WorldArray, true); textures.Clear(); foreach (var index in wallTexturesClamp.Where(x => !m_textureManager.IsTextureAnimated(x))) @@ -38,7 +38,7 @@ public void BuildLevel(IEnumerable flatTextures, IEnumerable wallTextu buildTextures += textures.Count; textures.Sort(SortTexturesByDimensions); - arrayTextures += BuildTextureArrayFromTextures(textures, false); + arrayTextures += BuildTextureArrayFromTextures(textures, TextureContext.WorldArray, false); var totalTextures = flatTextures.Count() + wallTexturesRepeat.Count() + wallTexturesClamp.Count(); var animated = totalTextures - buildTextures; @@ -51,7 +51,7 @@ private static void DebugLog(int totalTextures, int compressed) HelionLog.Info($"Compressed textures {totalTextures} -> {compressed}"); } - private int BuildTextureArrayFromTextures(DynamicArray textures, bool repeatY) + private int BuildTextureArrayFromTextures(DynamicArray textures, TextureContext textureContext, bool repeatY) { int textureCount = 0; var arrayTextures = new DynamicArray(); @@ -64,7 +64,7 @@ private int BuildTextureArrayFromTextures(DynamicArray textur if (dimension != texture.Image.Dimension) { if (arrayTextures.Count > 0) - textureCount += BuildTextureArray(arrayTextures.Data.AsSpan(0, arrayTextures.Length), repeatY); + textureCount += BuildTextureArray(arrayTextures.Data.AsSpan(0, arrayTextures.Length), textureContext, repeatY); arrayTextures.Clear(); dimension = texture.Image.Dimension; } @@ -73,7 +73,7 @@ private int BuildTextureArrayFromTextures(DynamicArray textur } if (arrayTextures.Count > 0) - textureCount += BuildTextureArray(arrayTextures.Data.AsSpan(0, arrayTextures.Length), repeatY); + textureCount += BuildTextureArray(arrayTextures.Data.AsSpan(0, arrayTextures.Length), textureContext, repeatY); return textureCount; } @@ -97,9 +97,9 @@ private static int SortTexturesByDimensions(Resources.Texture x, Resources.Textu return x.Image.Height.CompareTo(y.Image.Height); } - private int BuildTextureArray(Span textures, bool repeatY) + private int BuildTextureArray(Span textures, TextureContext textureContext, bool repeatY) { - var arrayTexture = m_glTextureManager.CreateTextureArray(textures, TextureContext.WorldArray, repeatY); + var arrayTexture = m_glTextureManager.CreateTextureArray(textures, textureContext, repeatY); return arrayTexture == null ? 0 : 1; } } From c2e0ded211f716f9488119fff58dac78122b3904 Mon Sep 17 00:00:00 2001 From: Nick Date: Sun, 13 Sep 2026 08:06:29 -0400 Subject: [PATCH 32/49] refactor to use texture directly --- Core/Render/OpenGL/Texture/ArrayTextureData.cs | 3 ++- Core/Render/OpenGL/Texture/GLTextureManager.cs | 15 ++++++--------- .../Texture/Legacy/GLTextureArrayBuilder.cs | 6 +++--- 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/Core/Render/OpenGL/Texture/ArrayTextureData.cs b/Core/Render/OpenGL/Texture/ArrayTextureData.cs index 30db1e50d..97badd5de 100644 --- a/Core/Render/OpenGL/Texture/ArrayTextureData.cs +++ b/Core/Render/OpenGL/Texture/ArrayTextureData.cs @@ -1,5 +1,6 @@ using Helion.Util.Assertion; using Helion.Util.Container; +using System; using System.Collections.Generic; namespace Helion.Render.OpenGL.Texture; @@ -14,7 +15,7 @@ internal sealed class ArrayTextureData(TextureContext context) wh private readonly Dictionary m_arrayTextureLookup = []; private readonly Dictionary m_arrayTextureLookupClamp = []; - public void Add(GLTextureType arrayTexture, GLTextureType[] arraySubTextures, Resources.Texture[] textures, bool clamp) + public void Add(GLTextureType arrayTexture, GLTextureType[] arraySubTextures, Span textures, bool clamp) { Assert.Precondition(arraySubTextures.Length == textures.Length, "arraySubTextures != textures length"); // Flip high bit to ensure no collisions diff --git a/Core/Render/OpenGL/Texture/GLTextureManager.cs b/Core/Render/OpenGL/Texture/GLTextureManager.cs index 9f0c1e4dd..51577b5f1 100644 --- a/Core/Render/OpenGL/Texture/GLTextureManager.cs +++ b/Core/Render/OpenGL/Texture/GLTextureManager.cs @@ -236,19 +236,16 @@ public GLTextureType GetTexture(int index, bool repeatY = true) return (GLTextureType)renderTexture; } - public GLTextureType? CreateTextureArray(Span indices, TextureContext textureContext, bool repeatY) + public GLTextureType? CreateTextureArray(Span textures, TextureContext textureContext, bool repeatY) { - if (indices.Length == 0) + if (textures.Length == 0) return null; - var images = new Image[indices.Length]; - var textures = new Resources.Texture[indices.Length]; - - for (int i = 0; i < indices.Length; i++) + var images = new Image[textures.Length]; + for (int i = 0; i < textures.Length; i++) { - var index = indices[i]; - TextureManager.EnsureTextureImageLoaded(index); - var texture = TextureManager.GetTexture(index); + var texture = textures[i]; + TextureManager.EnsureTextureImageLoaded(texture.Index); Assert.Precondition(texture.Image != null, "Texture must have an image"); images[i] = texture.Image ?? new Image(default, ImageType.Argb); diff --git a/Core/Render/OpenGL/Texture/Legacy/GLTextureArrayBuilder.cs b/Core/Render/OpenGL/Texture/Legacy/GLTextureArrayBuilder.cs index 408c527e1..90c9ee994 100644 --- a/Core/Render/OpenGL/Texture/Legacy/GLTextureArrayBuilder.cs +++ b/Core/Render/OpenGL/Texture/Legacy/GLTextureArrayBuilder.cs @@ -54,7 +54,7 @@ private static void DebugLog(int totalTextures, int compressed) private int BuildTextureArrayFromTextures(DynamicArray textures, TextureContext textureContext, bool repeatY) { int textureCount = 0; - var arrayTextures = new DynamicArray(); + var arrayTextures = new DynamicArray(); var dimension = new Dimension(0, 0); foreach (var texture in textures) { @@ -69,7 +69,7 @@ private int BuildTextureArrayFromTextures(DynamicArray textur dimension = texture.Image.Dimension; } - arrayTextures.Add(texture.Index); + arrayTextures.Add(texture); } if (arrayTextures.Count > 0) @@ -97,7 +97,7 @@ private static int SortTexturesByDimensions(Resources.Texture x, Resources.Textu return x.Image.Height.CompareTo(y.Image.Height); } - private int BuildTextureArray(Span textures, TextureContext textureContext, bool repeatY) + private int BuildTextureArray(Span textures, TextureContext textureContext, bool repeatY) { var arrayTexture = m_glTextureManager.CreateTextureArray(textures, textureContext, repeatY); return arrayTexture == null ? 0 : 1; From 7967fcfd1ee292a94ce8b4dcf075acdde0a4fc1e Mon Sep 17 00:00:00 2001 From: Nick Date: Sun, 13 Sep 2026 12:47:55 -0400 Subject: [PATCH 33/49] start work for sprites --- .../Legacy/World/Entities/EntityProgram.cs | 6 +- .../Legacy/World/Entities/EntityRenderer.cs | 11 +-- .../Legacy/World/Entities/EntityVertex.cs | 3 + .../Legacy/World/LegacyWorldRenderer.cs | 13 ++-- .../Render/OpenGL/Texture/ArrayTextureData.cs | 20 ++++- Core/Render/OpenGL/Texture/GLTexture.cs | 3 +- .../Render/OpenGL/Texture/GLTextureManager.cs | 60 ++++---------- .../Texture/Legacy/GLTextureArrayBuilder.cs | 78 ++++++++++++++++--- Core/Resources/SpriteDefinition.cs | 3 +- 9 files changed, 125 insertions(+), 72 deletions(-) diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityProgram.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityProgram.cs index 72513005d..6b98758ce 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityProgram.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityProgram.cs @@ -69,6 +69,7 @@ protected override string VertexShader() => @" layout(location = 2) in vec3 prevPos; layout(location = 3) in float offsetXYZ; layout(location = 4) in float renderOptions; + layout(location = 5) in float textureIndex; flat out float lightLevelFrag; flat out float alphaFrag; @@ -76,6 +77,7 @@ protected override string VertexShader() => @" flat out float colorMapTranslationFrag; flat out float zPosFrag; flat out float textureWidthFrag; + flat out float boundTextureIndex; flat out vec3 centerPosFrag; flat out vec3 minPosFrag; flat out vec3 maxPosFrag; @@ -170,6 +172,7 @@ void main() minPosFrag = minPos; maxPosFrag = maxPos; renderDistSquared = distSquared(viewPos.xy, interpolatedPos.xy); + boundTextureIndex = textureIndex; } " .Replace("${SectorColorMapVertexFunction}", SectorColorMap.VertexFunction("lightIndexInt", "sectorColorMapIndexFrag", "sectorFogColorFrag")) @@ -222,6 +225,7 @@ private static string AdjustSpriteVertexClip() flat in float colorMapTranslationFrag; flat in float zPosFrag; flat in float textureWidthFrag; + flat in float boundTextureIndex; flat in vec3 centerPosFrag; flat in vec3 minPosFrag; flat in vec3 maxPosFrag; @@ -268,8 +272,6 @@ private static string AdjustSpriteVertexClip() ${FuzzFunction} ${SoftwareSpriteEmulationFunctions} - const int boundTextureIndex = 0; - void main() { ${CheckPlaneClip} diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityRenderer.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityRenderer.cs index 9c9f13f1c..bc91f1240 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityRenderer.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityRenderer.cs @@ -176,7 +176,7 @@ private SpriteRotation GetSpriteRotation(SpriteDefinition spriteDefinition, int } public void RenderEntity(Entity entity, in Vec2D position, int renderIndex) - { + { Vec3D centerBottom = entity.Position; Vec2D nudgeAmount = default; @@ -246,7 +246,6 @@ public void RenderEntity(Entity entity, in Vec2D position, int renderIndex) var disableFullbright = spriteRotation.BrightmapNoFullbright; var isFullBright = (entity.Flags.Bright() || entity.FrameState.Frame.Properties.Bright) && !disableFullbright; var offsetZ = GetOffsetZ(entity, texture); - int fuzz; RenderStyle renderStyle; @@ -257,7 +256,7 @@ public void RenderEntity(Entity entity, in Vec2D position, int renderIndex) } else { - renderStyle = m_spriteAlpha ? entity.RenderStyle: RenderStyle.Normal; + renderStyle = m_spriteAlpha ? entity.RenderStyle : RenderStyle.Normal; fuzz = 0; } @@ -277,7 +276,8 @@ public void RenderEntity(Entity entity, in Vec2D position, int renderIndex) if (renderStyle == RenderStyle.ColorAdd) entityAlpha = 1.0f; - var renderData = m_dataManager.GetByRenderStyle(renderStyle, texture, brightmapTexture); + var arrayTexture = texture.ParentArrayTexture ?? texture; + var renderData = m_dataManager.GetByRenderStyle(renderStyle, arrayTexture, brightmapTexture); var alpha = m_spriteAlpha && renderStyle != RenderStyle.Normal ? entityAlpha : 1.0f; var arrayData = renderData.ArrayData; @@ -299,6 +299,7 @@ public void RenderEntity(Entity entity, in Vec2D position, int renderIndex) vertex.SurfaceOptions = VertexOptions.EntityPackSurface(alpha, fuzz, flipU, colorMapIndex, lightLevel); vertex.RenderOptions = VertexOptions.EntityPackRender( Renderer.GetLightBufferIndex(sector, WorldStatic.Sector3D && sector.Sectors3D.Length > 0 ? LightBufferType.Wall : LightBufferType.Floor), renderIndex); + vertex.TextureIndex = texture.ArrayIndex; if (entity.Definition.Flags.SpawnCeiling() && m_vanillaRender) { @@ -309,7 +310,7 @@ public void RenderEntity(Entity entity, in Vec2D position, int renderIndex) vertex.Pos.Z = ceilingZ + diff; vertex.PrevPos.Z = entity.PrevPosition.Z != entity.Position.Z ? (float)entity.Sector.Ceiling.PrevZ : ceilingZ; } - + vertex.OffsetXYZ = VertexOptions.EntityPackXYZ(offsetX, offsetZ); arrayData.Length = length + 1; diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityVertex.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityVertex.cs index 1dfdf3608..e34982c47 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityVertex.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityVertex.cs @@ -22,4 +22,7 @@ public struct EntityVertex [VertexAttribute(divisor: 1)] public float RenderOptions; + + [VertexAttribute(divisor: 1)] + public float TextureIndex; } diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/LegacyWorldRenderer.cs b/Core/Render/OpenGL/Renderers/Legacy/World/LegacyWorldRenderer.cs index c8db11c86..018ef400b 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/LegacyWorldRenderer.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/LegacyWorldRenderer.cs @@ -67,6 +67,7 @@ public partial class LegacyWorldRenderer : WorldRenderer private bool m_downscaleVanillaBuffer; private bool m_postProcessingEffects; private bool m_lastUseBsp; + private bool m_spritesCached; private int m_lastTicker = -1; private Entity? m_viewerEntity; private IWorld? m_previousWorld; @@ -122,15 +123,11 @@ public override void UpdateToNewWorld(IWorld world) m_previousWorld?.OnResetInterpolation -= World_OnResetInterpolation; - var spriteDefinitions = m_archiveCollection.TextureManager.SpriteDefinitions; - for (int i = 0; i < spriteDefinitions.Length; i++) + if (!m_spritesCached) { - var spriteDefinition = spriteDefinitions[i]; - if (spriteDefinition == null || spriteDefinition.Cached) - continue; - - m_textureManager.CacheSpriteRotations(spriteDefinition); - spriteDefinition.Cached = true; + var textureArrayBuilder = new GLTextureArrayBuilder(m_archiveCollection.TextureManager, m_textureManager); + textureArrayBuilder.BuildSprites(m_archiveCollection.TextureManager.SpriteDefinitions); + m_spritesCached = true; } m_geometryRenderer.UpdateTo(world); diff --git a/Core/Render/OpenGL/Texture/ArrayTextureData.cs b/Core/Render/OpenGL/Texture/ArrayTextureData.cs index 97badd5de..55a967bff 100644 --- a/Core/Render/OpenGL/Texture/ArrayTextureData.cs +++ b/Core/Render/OpenGL/Texture/ArrayTextureData.cs @@ -1,7 +1,9 @@ using Helion.Util.Assertion; using Helion.Util.Container; +using OpenTK.Platform.Windows; using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; namespace Helion.Render.OpenGL.Texture; @@ -19,7 +21,7 @@ public void Add(GLTextureType arrayTexture, GLTextureType[] arraySubTextures, Sp { Assert.Precondition(arraySubTextures.Length == textures.Length, "arraySubTextures != textures length"); // Flip high bit to ensure no collisions - var arrayTextureId = arrayTexture.TextureId | (1 << 30); + var arrayTextureId = arrayTexture.TextureId | (1 << 30 - (int)Context); m_arrayTextures.Add(arrayTexture); m_arraySubTextures.Add(arraySubTextures); m_texturesForArrays.Add(textures); @@ -36,6 +38,22 @@ public int GetArrayTextureHandle(int textureHandle, bool clamp) return textureHandle; } + public bool TryGetTexture(int textureIndex, [NotNullWhen(true)] out Resources.Texture? texture) + { + for (int i = 0; i < m_texturesForArrays.Length; i++) + { + var check = m_texturesForArrays.Data[i]; + if (check.Index == textureIndex) + { + texture = check; + return true; + } + } + + texture = null; + return false; + } + public void Destroy() { for (int i = 0; i < m_arrayTextures.Length; i++) diff --git a/Core/Render/OpenGL/Texture/GLTexture.cs b/Core/Render/OpenGL/Texture/GLTexture.cs index 6319580f9..f95ec9568 100644 --- a/Core/Render/OpenGL/Texture/GLTexture.cs +++ b/Core/Render/OpenGL/Texture/GLTexture.cs @@ -11,7 +11,8 @@ namespace Helion.Render.OpenGL.Texture; public enum TextureContext { Default, - WorldArray + WorldArray, + WorldSprites } public abstract class GLTexture : IRenderableTextureHandle, IDisposable diff --git a/Core/Render/OpenGL/Texture/GLTextureManager.cs b/Core/Render/OpenGL/Texture/GLTextureManager.cs index 51577b5f1..2464940ba 100644 --- a/Core/Render/OpenGL/Texture/GLTextureManager.cs +++ b/Core/Render/OpenGL/Texture/GLTextureManager.cs @@ -100,21 +100,6 @@ public void DestroyTextureArrays(TextureContext textureContext) arrayTextureData.Destroy(); m_arrayTextures.Set((int)textureContext, null); - //for (int i = 0; i < m_arrayTextures.Length; i++) - // m_arrayTextures[i].Dispose(); - - //for (int i = 0; i < m_texturesForArrays.Length; i++) - //{ - // var texture = m_texturesForArrays.Data[i]; - // texture.RenderStore = null; - // texture.RenderStoreClamp = null; - //} - - //m_arrayTextures.Clear(); - //m_arraySubTextures.Clear(); - //m_arrayTextureLookup.Clear(); - //m_arrayTextureLookupClamp.Clear(); - //m_texturesForArrays.Clear(); } public bool TryGet(string name, [NotNullWhen(true)] out IRenderableTextureHandle? handle, ResourceNamespace? specificNamespace = null, int upscalingFactor = 1, BrightmapDefinition? brightmap = null) @@ -236,7 +221,7 @@ public GLTextureType GetTexture(int index, bool repeatY = true) return (GLTextureType)renderTexture; } - public GLTextureType? CreateTextureArray(Span textures, TextureContext textureContext, bool repeatY) + public GLTextureType? CreateTextureArray(Span textures, TextureContext textureContext, TextureFlags textureFlags) { if (textures.Length == 0) return null; @@ -252,14 +237,13 @@ public GLTextureType GetTexture(int index, bool repeatY = true) textures[i] = texture; } - var flags = repeatY ? TextureFlags.Default : TextureFlags.ClampY; - var arraySubTextures = GenerateTextureArray(images, images[0].Dimension, ResourceNamespace.Textures, flags, textureContext, out var arrayTexture); + var arraySubTextures = GenerateTextureArray(images, images[0].Dimension, ResourceNamespace.Textures, textureFlags, textureContext, out var arrayTexture); for (int i = 0; i < images.Length; i++) { var glTexture = arraySubTextures[i]; var texture = textures[i]; SetDebugName(glTexture, texture); - texture.SetGLTexture(glTexture, repeatY); + texture.SetGLTexture(glTexture, (textureFlags & TextureFlags.ClampY) != 0); } if (!m_arrayTextures.TryGetValue((int)textureContext, out var arrayTextureData)) @@ -268,7 +252,7 @@ public GLTextureType GetTexture(int index, bool repeatY = true) m_arrayTextures.Set((int)textureContext, arrayTextureData); } - arrayTextureData.Add(arrayTexture, arraySubTextures, textures, repeatY); + arrayTextureData.Add(arrayTexture, arraySubTextures, textures, (textureFlags & TextureFlags.ClampY) != 0); return arrayTexture; } @@ -286,6 +270,17 @@ public int GetWorldArrayTextureHandle(int textureHandle, bool repeatY) return textureHandle; } + public bool TryGetTexture(int textureIndex, TextureContext textureContext, [NotNullWhen(true)] out Resources.Texture? texture) + { + if (!m_arrayTextures.TryGetValue((int)textureContext, out var arrayTextureData)) + { + texture = null; + return false; + } + + return arrayTextureData.TryGetTexture(textureIndex, out texture); + } + public GLTextureType? GetBrightmapTexture(int index, bool repeatY = true) { var texture = TextureManager.GetTexture(index); @@ -353,29 +348,6 @@ public SpriteRotation GetSpriteRotation(SpriteDefinition spriteDefinition, int f return spriteRotation; } - public void CacheSpriteRotations(SpriteDefinition spriteDef) - { - for (int i = 0; i < SpriteDefinition.MaxFrames; i++) - { - for (int j = 0; j < SpriteDefinition.MaxRotations; j++) - { - var rotation = spriteDef.Rotations[i, j]; - if (rotation == null) - continue; - - if (rotation.Texture.RenderStore != null) - { - rotation.RenderStore ??= rotation.Texture.RenderStore; - rotation.BrightmapRenderStore ??= rotation.Texture.RenderStore; - continue; - } - - rotation.RenderStore = rotation.Texture.RenderStore = CreateTexture(rotation.Texture.Image, rotation.Texture.Name, ResourceNamespace.Sprites); - rotation.BrightmapRenderStore = rotation.Texture.BrightmapRenderStore = CreateBrightMapTexture(rotation.Texture.BrightmapImage, rotation.Texture.Name, ResourceNamespace.Brightmaps); - } - } - } - public SpriteDefinition? GetSpriteDefinition(int spriteIndex) { SpriteDefinition? spriteDef = TextureManager.GetSpriteDefinition(spriteIndex); @@ -449,7 +421,7 @@ protected static int CalculateMipmapLevels(Dimension dimension) return (int)Math.Floor(Math.Log(smallerAxis, 2)); } - protected GLTextureType CreateBrightMapTexture(Image? image, string? name, ResourceNamespace resourceNamespace, bool repeatY = true) + public GLTextureType CreateBrightMapTexture(Image? image, string? name, ResourceNamespace resourceNamespace, bool repeatY = true) { var texture = CreateTexture(image, name, resourceNamespace, repeatY); // Ensure that the brightmap texture is the null transparent texture. The debug option creates red/black checker texture for NullTexture. diff --git a/Core/Render/OpenGL/Texture/Legacy/GLTextureArrayBuilder.cs b/Core/Render/OpenGL/Texture/Legacy/GLTextureArrayBuilder.cs index 90c9ee994..d04651756 100644 --- a/Core/Render/OpenGL/Texture/Legacy/GLTextureArrayBuilder.cs +++ b/Core/Render/OpenGL/Texture/Legacy/GLTextureArrayBuilder.cs @@ -1,5 +1,7 @@ -using Helion.Geometry; +using Helion.Dehacked; +using Helion.Geometry; using Helion.Resources; +using Helion.Util.Assertion; using Helion.Util.Container; using Helion.Util.Loggers; using System; @@ -29,30 +31,86 @@ public void BuildLevel(IEnumerable flatTextures, IEnumerable wallTextu AddTexture(textures, index); buildTextures += textures.Count; - textures.Sort(SortTexturesByDimensions); - arrayTextures += BuildTextureArrayFromTextures(textures, TextureContext.WorldArray, true); + arrayTextures += BuildTextureArrayFromTextures(textures, TextureContext.WorldArray, TextureFlags.Default); textures.Clear(); foreach (var index in wallTexturesClamp.Where(x => !m_textureManager.IsTextureAnimated(x))) AddTexture(textures, index); buildTextures += textures.Count; - textures.Sort(SortTexturesByDimensions); - arrayTextures += BuildTextureArrayFromTextures(textures, TextureContext.WorldArray, false); + arrayTextures += BuildTextureArrayFromTextures(textures, TextureContext.WorldArray, TextureFlags.ClampY); var totalTextures = flatTextures.Count() + wallTexturesRepeat.Count() + wallTexturesClamp.Count(); var animated = totalTextures - buildTextures; DebugLog(totalTextures, arrayTextures + animated); } + public void BuildSprites(DynamicArray spriteDefinitions) + { + var spriteTextures = new DynamicArray(); + var spriteTextureHandles = new HashSet(); + foreach (var spriteDefinition in spriteDefinitions) + { + if (spriteDefinition == null) + continue; + + for (int i = 0; i < SpriteDefinition.MaxFrames; i++) + { + for (int j = 0; j < SpriteDefinition.MaxRotations; j++) + { + var rotation = spriteDefinition.Rotations[i, j]; + if (rotation == null) + continue; + + if (rotation.Texture.RenderStore != null) + continue; + + if (spriteTextureHandles.Add(rotation.Texture.Index)) + spriteTextures.Add(rotation.Texture); + + } + } + } + + BuildTextureArrayFromTextures(spriteTextures, TextureContext.WorldSprites, TextureFlags.ClampX | TextureFlags.ClampY); + + foreach (var spriteDefinition in spriteDefinitions) + { + if (spriteDefinition == null) + continue; + + for (int i = 0; i < SpriteDefinition.MaxFrames; i++) + { + for (int j = 0; j < SpriteDefinition.MaxRotations; j++) + { + var rotation = spriteDefinition.Rotations[i, j]; + if (rotation == null) + continue; + + if (rotation.RenderStore != null) + continue; + + rotation.BrightmapRenderStore = m_glTextureManager.CreateBrightMapTexture(rotation.Texture.BrightmapImage, rotation.Texture.Name, ResourceNamespace.Brightmaps); + + if (m_glTextureManager.TryGetTexture(rotation.Texture.Index, TextureContext.WorldSprites, out var texture)) + rotation.RenderStore = texture.RenderStoreClamp ?? texture.RenderStore; + + Assert.Postcondition(rotation.RenderStore != null, $"Failed to find generated array image for sprite"); + } + } + } + } + [Conditional("DEBUG")] private static void DebugLog(int totalTextures, int compressed) { HelionLog.Info($"Compressed textures {totalTextures} -> {compressed}"); } - private int BuildTextureArrayFromTextures(DynamicArray textures, TextureContext textureContext, bool repeatY) + private int BuildTextureArrayFromTextures(DynamicArray textures, TextureContext textureContext, TextureFlags textureFlags) { + textures.Sort(SortTexturesByDimensions); + int textureCount = 0; var arrayTextures = new DynamicArray(); var dimension = new Dimension(0, 0); @@ -64,7 +122,7 @@ private int BuildTextureArrayFromTextures(DynamicArray textur if (dimension != texture.Image.Dimension) { if (arrayTextures.Count > 0) - textureCount += BuildTextureArray(arrayTextures.Data.AsSpan(0, arrayTextures.Length), textureContext, repeatY); + textureCount += BuildTextureArray(arrayTextures.Data.AsSpan(0, arrayTextures.Length), textureContext, textureFlags); arrayTextures.Clear(); dimension = texture.Image.Dimension; } @@ -73,7 +131,7 @@ private int BuildTextureArrayFromTextures(DynamicArray textur } if (arrayTextures.Count > 0) - textureCount += BuildTextureArray(arrayTextures.Data.AsSpan(0, arrayTextures.Length), textureContext, repeatY); + textureCount += BuildTextureArray(arrayTextures.Data.AsSpan(0, arrayTextures.Length), textureContext, textureFlags); return textureCount; } @@ -97,9 +155,9 @@ private static int SortTexturesByDimensions(Resources.Texture x, Resources.Textu return x.Image.Height.CompareTo(y.Image.Height); } - private int BuildTextureArray(Span textures, TextureContext textureContext, bool repeatY) + private int BuildTextureArray(Span textures, TextureContext textureContext, TextureFlags textureFlags) { - var arrayTexture = m_glTextureManager.CreateTextureArray(textures, textureContext, repeatY); + var arrayTexture = m_glTextureManager.CreateTextureArray(textures, textureContext, textureFlags); return arrayTexture == null ? 0 : 1; } } diff --git a/Core/Resources/SpriteDefinition.cs b/Core/Resources/SpriteDefinition.cs index 4a9c02b1c..33ed57f68 100644 --- a/Core/Resources/SpriteDefinition.cs +++ b/Core/Resources/SpriteDefinition.cs @@ -16,6 +16,7 @@ public class SpriteDefinition public bool HasRotations; public bool Cached; + private static int StaticId; private static readonly Dictionary SpriteTextureLookup = []; public SpriteDefinition(IList entries, IImageRetriever imageRetriever, ArchiveCollection archiveCollection) @@ -53,7 +54,7 @@ private void CreateRotations(Entry entry, IImageRetriever imageRetriever, Archiv if (!SpriteTextureLookup.TryGetValue(entry.Path.Name, out var texture)) { - texture = new(entry.Path.Name, ResourceNamespace.Sprites, 0); + texture = new(entry.Path.Name, ResourceNamespace.Sprites, ++StaticId); texture.Image = imageRetriever.GetOnly(entry.Path.Name, ResourceNamespace.Sprites); SpriteTextureLookup[entry.Path.Name] = texture; } From b7cb3cd4bcd2b23562745cd9f13bc898a6778bc8 Mon Sep 17 00:00:00 2001 From: Nick Date: Sun, 13 Sep 2026 13:40:28 -0400 Subject: [PATCH 34/49] fix lookup and refactor to use based off flags --- .../Render/OpenGL/Texture/ArrayTextureData.cs | 39 ++++++++++++++----- .../Render/OpenGL/Texture/GLTextureManager.cs | 6 +-- .../Texture/Legacy/GLTextureArrayBuilder.cs | 3 +- 3 files changed, 33 insertions(+), 15 deletions(-) diff --git a/Core/Render/OpenGL/Texture/ArrayTextureData.cs b/Core/Render/OpenGL/Texture/ArrayTextureData.cs index 55a967bff..e262d5055 100644 --- a/Core/Render/OpenGL/Texture/ArrayTextureData.cs +++ b/Core/Render/OpenGL/Texture/ArrayTextureData.cs @@ -1,12 +1,35 @@ using Helion.Util.Assertion; using Helion.Util.Container; -using OpenTK.Platform.Windows; using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; namespace Helion.Render.OpenGL.Texture; +internal readonly struct LookupKey(TextureFlags textureFlags, int textureHandle) : IEquatable +{ + public readonly int Key1 = (int)textureFlags; + public readonly int Key2 = textureHandle; + + public override int GetHashCode() + { + return Key1 + Key2 * 131072; + } + + public override bool Equals([NotNullWhen(true)] object? obj) + { + if (obj is LookupKey key) + return key.Key1 == Key1 && key.Key2 == Key2; + return false; + } + + public bool Equals(LookupKey other) + { + return other.Key1 == Key1 && other.Key2 == Key2; + } +} + internal sealed class ArrayTextureData(TextureContext context) where GLTextureType : GLTexture { public readonly TextureContext Context = context; @@ -14,10 +37,9 @@ internal sealed class ArrayTextureData(TextureContext context) wh private readonly DynamicArray m_arraySubTextures = new(256); private readonly DynamicArray m_arrayTextures = new(256); private readonly DynamicArray m_texturesForArrays = new(256); - private readonly Dictionary m_arrayTextureLookup = []; - private readonly Dictionary m_arrayTextureLookupClamp = []; + private readonly Dictionary m_arrayTextureLookup = []; - public void Add(GLTextureType arrayTexture, GLTextureType[] arraySubTextures, Span textures, bool clamp) + public void Add(GLTextureType arrayTexture, GLTextureType[] arraySubTextures, Span textures, TextureFlags textureFlags) { Assert.Precondition(arraySubTextures.Length == textures.Length, "arraySubTextures != textures length"); // Flip high bit to ensure no collisions @@ -25,15 +47,13 @@ public void Add(GLTextureType arrayTexture, GLTextureType[] arraySubTextures, Sp m_arrayTextures.Add(arrayTexture); m_arraySubTextures.Add(arraySubTextures); m_texturesForArrays.Add(textures); - var lookup = clamp ? m_arrayTextureLookup : m_arrayTextureLookupClamp; for (int i = 0; i < textures.Length; i++) - lookup[textures[i].Index] = arrayTextureId; + m_arrayTextureLookup[new(textureFlags, textures[i].Index)] = arrayTextureId; } - public int GetArrayTextureHandle(int textureHandle, bool clamp) + public int GetArrayTextureHandle(int textureHandle, TextureFlags textureFlags) { - var lookup = clamp ? m_arrayTextureLookup : m_arrayTextureLookupClamp; - if (lookup.TryGetValue(textureHandle, out var handle)) + if (m_arrayTextureLookup.TryGetValue(new(textureFlags, textureHandle), out var handle)) return handle; return textureHandle; } @@ -65,7 +85,6 @@ public void Destroy() m_arrayTextures.Clear(); m_arraySubTextures.Clear(); m_arrayTextureLookup.Clear(); - m_arrayTextureLookupClamp.Clear(); m_texturesForArrays.Clear(); } } diff --git a/Core/Render/OpenGL/Texture/GLTextureManager.cs b/Core/Render/OpenGL/Texture/GLTextureManager.cs index 2464940ba..9f1ca8103 100644 --- a/Core/Render/OpenGL/Texture/GLTextureManager.cs +++ b/Core/Render/OpenGL/Texture/GLTextureManager.cs @@ -243,7 +243,7 @@ public GLTextureType GetTexture(int index, bool repeatY = true) var glTexture = arraySubTextures[i]; var texture = textures[i]; SetDebugName(glTexture, texture); - texture.SetGLTexture(glTexture, (textureFlags & TextureFlags.ClampY) != 0); + texture.SetGLTexture(glTexture, (textureFlags & TextureFlags.ClampY) == 0); } if (!m_arrayTextures.TryGetValue((int)textureContext, out var arrayTextureData)) @@ -252,7 +252,7 @@ public GLTextureType GetTexture(int index, bool repeatY = true) m_arrayTextures.Set((int)textureContext, arrayTextureData); } - arrayTextureData.Add(arrayTexture, arraySubTextures, textures, (textureFlags & TextureFlags.ClampY) != 0); + arrayTextureData.Add(arrayTexture, arraySubTextures, textures, textureFlags); return arrayTexture; } @@ -265,7 +265,7 @@ private static void SetDebugName(GLTextureType glTexture, Resources.Texture text public int GetWorldArrayTextureHandle(int textureHandle, bool repeatY) { if (m_arrayTextures.TryGetValue((int)TextureContext.WorldArray, out var arrayTextureData)) - return arrayTextureData.GetArrayTextureHandle(textureHandle, repeatY); + return arrayTextureData.GetArrayTextureHandle(textureHandle, repeatY ? TextureFlags.Default : TextureFlags.ClampY); return textureHandle; } diff --git a/Core/Render/OpenGL/Texture/Legacy/GLTextureArrayBuilder.cs b/Core/Render/OpenGL/Texture/Legacy/GLTextureArrayBuilder.cs index d04651756..7e837d223 100644 --- a/Core/Render/OpenGL/Texture/Legacy/GLTextureArrayBuilder.cs +++ b/Core/Render/OpenGL/Texture/Legacy/GLTextureArrayBuilder.cs @@ -1,5 +1,4 @@ -using Helion.Dehacked; -using Helion.Geometry; +using Helion.Geometry; using Helion.Resources; using Helion.Util.Assertion; using Helion.Util.Container; From 030c27a3328abc90439d5793c4a4359211ed82c9 Mon Sep 17 00:00:00 2001 From: Nick Date: Sun, 13 Sep 2026 14:59:04 -0400 Subject: [PATCH 35/49] add bucket code --- .../Texture/Legacy/GLTextureArrayBuilder.cs | 57 ++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) diff --git a/Core/Render/OpenGL/Texture/Legacy/GLTextureArrayBuilder.cs b/Core/Render/OpenGL/Texture/Legacy/GLTextureArrayBuilder.cs index 7e837d223..51b932e6c 100644 --- a/Core/Render/OpenGL/Texture/Legacy/GLTextureArrayBuilder.cs +++ b/Core/Render/OpenGL/Texture/Legacy/GLTextureArrayBuilder.cs @@ -1,5 +1,6 @@ using Helion.Geometry; using Helion.Resources; +using Helion.Util; using Helion.Util.Assertion; using Helion.Util.Container; using Helion.Util.Loggers; @@ -10,8 +11,11 @@ namespace Helion.Render.OpenGL.Texture.Legacy; +record struct TextureBucket(Dimension Dimension, DynamicArray Textures); + public class GLTextureArrayBuilder(TextureManager textureManager, LegacyGLTextureManager glTextureManager) { + private readonly TextureManager m_textureManager = textureManager; private readonly LegacyGLTextureManager m_glTextureManager = glTextureManager; @@ -71,7 +75,9 @@ public void BuildSprites(DynamicArray spriteDefinitions) } } - BuildTextureArrayFromTextures(spriteTextures, TextureContext.WorldSprites, TextureFlags.ClampX | TextureFlags.ClampY); + var buckets = CreateTextureBuckets(spriteTextures, 32, 5); + foreach (var bucket in buckets) + BuildTextureArray(bucket.Textures.Data.AsSpan(0, bucket.Textures.Length), TextureContext.WorldSprites, TextureFlags.ClampX | TextureFlags.ClampY); foreach (var spriteDefinition in spriteDefinitions) { @@ -159,4 +165,53 @@ private int BuildTextureArray(Span textures, TextureContext t var arrayTexture = m_glTextureManager.CreateTextureArray(textures, textureContext, textureFlags); return arrayTexture == null ? 0 : 1; } + + private static TextureBucket[] CreateTextureBuckets(DynamicArray textures, int baseSize, int bucketCount) + { + if (baseSize <= 0) + throw new ArgumentException($"Invalid baseSize {baseSize}"); + if (bucketCount <= 0) + throw new ArgumentException($"Invalid bucketCount {bucketCount}"); + + var buckets = new TextureBucket[bucketCount]; + + for (int i = 0; i < bucketCount; i++) + { + var size = baseSize * (int)Math.Pow(2, i); + buckets[i] = new TextureBucket(new Dimension(size, size), new(128)); + } + + buckets[^1] = new TextureBucket(new Dimension(0, 0), []); + + foreach (var texture in textures) + { + // This shouldn't happen + if (texture.Image == null) + continue; + + var index = GetBucketIndex(texture.Image.Dimension, baseSize, bucketCount); + buckets[index].Textures.Add(texture); + } + + return buckets; + } + + private static int GetBucketIndex(Dimension dimension, int baseSize, int bucketCount) + { + int size = MathHelper.Max(dimension.Width, dimension.Height); + // Fits in the smallest bucket + if (size <= baseSize) + return 0; + + int index = 0; + int current = baseSize; + + while (index < bucketCount - 1 && size > current) + { + current *= 2; + index++; + } + + return index; + } } From ebb8729f9e249c8e963e43acf1475ad85f39c87d Mon Sep 17 00:00:00 2001 From: Nick Date: Sun, 13 Sep 2026 15:10:03 -0400 Subject: [PATCH 36/49] pad images --- .../Render/OpenGL/Texture/GLTextureManager.cs | 16 ++++++++++++++-- .../Texture/Legacy/GLTextureArrayBuilder.cs | 19 ++++++++++++------- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/Core/Render/OpenGL/Texture/GLTextureManager.cs b/Core/Render/OpenGL/Texture/GLTextureManager.cs index 9f1ca8103..cf3b29ae2 100644 --- a/Core/Render/OpenGL/Texture/GLTextureManager.cs +++ b/Core/Render/OpenGL/Texture/GLTextureManager.cs @@ -221,7 +221,7 @@ public GLTextureType GetTexture(int index, bool repeatY = true) return (GLTextureType)renderTexture; } - public GLTextureType? CreateTextureArray(Span textures, TextureContext textureContext, TextureFlags textureFlags) + public GLTextureType? CreateTextureArray(Span textures, TextureContext textureContext, TextureFlags textureFlags, Dimension dimension) { if (textures.Length == 0) return null; @@ -233,7 +233,19 @@ public GLTextureType GetTexture(int index, bool repeatY = true) TextureManager.EnsureTextureImageLoaded(texture.Index); Assert.Precondition(texture.Image != null, "Texture must have an image"); - images[i] = texture.Image ?? new Image(default, ImageType.Argb); + var image = texture.Image ?? new Image(default, ImageType.Argb); + + if (image.Dimension.Width < dimension.Width || image.Dimension.Height < dimension.Height) + { + var fitImage = new Image(dimension, image.ImageType); + for (int x = 0; x < image.Indices.Length; x++) + fitImage.Indices[x] = image.Indices[x]; + for (int x = 0; x < image.Pixels.Length; x++) + fitImage.Pixels[x] = image.Pixels[x]; + image = fitImage; + } + + images[i] = image; textures[i] = texture; } diff --git a/Core/Render/OpenGL/Texture/Legacy/GLTextureArrayBuilder.cs b/Core/Render/OpenGL/Texture/Legacy/GLTextureArrayBuilder.cs index 51b932e6c..50cea3196 100644 --- a/Core/Render/OpenGL/Texture/Legacy/GLTextureArrayBuilder.cs +++ b/Core/Render/OpenGL/Texture/Legacy/GLTextureArrayBuilder.cs @@ -76,8 +76,13 @@ public void BuildSprites(DynamicArray spriteDefinitions) } var buckets = CreateTextureBuckets(spriteTextures, 32, 5); - foreach (var bucket in buckets) - BuildTextureArray(bucket.Textures.Data.AsSpan(0, bucket.Textures.Length), TextureContext.WorldSprites, TextureFlags.ClampX | TextureFlags.ClampY); + for (int i = 0; i < buckets.Length - 1; i++) + { + var bucket = buckets[i]; + BuildTextureArray(bucket.Textures.Data.AsSpan(0, bucket.Textures.Length), TextureContext.WorldSprites, TextureFlags.ClampX | TextureFlags.ClampY, bucket.Dimension); + } + + // TODO overflow bucket ^1 foreach (var spriteDefinition in spriteDefinitions) { @@ -127,7 +132,7 @@ private int BuildTextureArrayFromTextures(DynamicArray textur if (dimension != texture.Image.Dimension) { if (arrayTextures.Count > 0) - textureCount += BuildTextureArray(arrayTextures.Data.AsSpan(0, arrayTextures.Length), textureContext, textureFlags); + textureCount += BuildTextureArray(arrayTextures.Data.AsSpan(0, arrayTextures.Length), textureContext, textureFlags, dimension); arrayTextures.Clear(); dimension = texture.Image.Dimension; } @@ -136,7 +141,7 @@ private int BuildTextureArrayFromTextures(DynamicArray textur } if (arrayTextures.Count > 0) - textureCount += BuildTextureArray(arrayTextures.Data.AsSpan(0, arrayTextures.Length), textureContext, textureFlags); + textureCount += BuildTextureArray(arrayTextures.Data.AsSpan(0, arrayTextures.Length), textureContext, textureFlags, dimension); return textureCount; } @@ -160,9 +165,9 @@ private static int SortTexturesByDimensions(Resources.Texture x, Resources.Textu return x.Image.Height.CompareTo(y.Image.Height); } - private int BuildTextureArray(Span textures, TextureContext textureContext, TextureFlags textureFlags) + private int BuildTextureArray(Span textures, TextureContext textureContext, TextureFlags textureFlags, Dimension dimension) { - var arrayTexture = m_glTextureManager.CreateTextureArray(textures, textureContext, textureFlags); + var arrayTexture = m_glTextureManager.CreateTextureArray(textures, textureContext, textureFlags, dimension); return arrayTexture == null ? 0 : 1; } @@ -173,7 +178,7 @@ private static TextureBucket[] CreateTextureBuckets(DynamicArray Date: Sun, 13 Sep 2026 20:30:43 -0400 Subject: [PATCH 37/49] it works --- Core/Render/BindTextures.cs | 1 + .../Legacy/World/Entities/EntityProgram.cs | 25 +++++++++---- .../Legacy/World/Entities/EntityRenderer.cs | 3 ++ .../Legacy/World/Entities/EntityVertex.cs | 3 ++ .../Legacy/World/LegacyWorldRenderer.cs | 5 +-- Core/Render/OpenGL/Renderers/WorldRenderer.cs | 5 ++- .../Render/OpenGL/Texture/GLTextureManager.cs | 16 ++++++--- .../Texture/Legacy/GLTextureArrayBuilder.cs | 17 ++++++--- Core/Render/Renderer.World.cs | 35 +++++++++++++++++++ Core/Render/Renderer.cs | 6 ++++ Core/Resources/SpriteRotation.cs | 1 + 11 files changed, 99 insertions(+), 18 deletions(-) diff --git a/Core/Render/BindTextures.cs b/Core/Render/BindTextures.cs index 8f56195e2..b84b206df 100644 --- a/Core/Render/BindTextures.cs +++ b/Core/Render/BindTextures.cs @@ -19,4 +19,5 @@ public static class BindTextures public const TextureUnit LineHeights = TextureUnit.Texture11; public const TextureUnit BrightmapTexture = TextureUnit.Texture12; public const TextureUnit SectorFog = TextureUnit.Texture13; + public const TextureUnit SpriteTextureDimensions = TextureUnit.Texture14; } diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityProgram.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityProgram.cs index 6b98758ce..ac7c9cdbe 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityProgram.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityProgram.cs @@ -21,6 +21,7 @@ public class EntityProgram : RenderProgramBase private readonly int m_mapDataTextureLocation; private readonly int m_lineHeightsTextureLocation; private readonly int m_colorClampLocation; + private readonly int m_spriteTextureDimensionsTextureLocation; public EntityProgram(string name) : base($"Entity - {name}") { @@ -38,12 +39,14 @@ public EntityProgram(string name) : base($"Entity - {name}") m_fuzzTextureLocation = Uniforms.GetLocation("fuzzTexture"); m_opaqueTextureLocation = Uniforms.GetLocation("opaqueTexture"); m_colorClampLocation = Uniforms.GetLocation("colorClamp"); + m_spriteTextureDimensionsTextureLocation = Uniforms.GetLocation("spriteTextureDimensionsTexture"); } public void FuzzTexture(TextureUnit unit) => ProgramUniforms.Set(unit, m_fuzzTextureLocation); public void OpaqueTexture(TextureUnit unit) => ProgramUniforms.Set(unit, m_opaqueTextureLocation); public void MapDataTexture(TextureUnit unit) => ProgramUniforms.Set(unit, m_mapDataTextureLocation); public void LineHeightsTexture(TextureUnit unit) => ProgramUniforms.Set(unit, m_lineHeightsTextureLocation); + public void SpriteTextureDimensionsTexture(TextureUnit unit) => ProgramUniforms.Set(unit, m_spriteTextureDimensionsTextureLocation); public void FuzzFrac(float frac) => ProgramUniforms.Set(frac, m_fuzzFracLocation); public void ViewRightNormal(Vec2F viewRightNormal) => ProgramUniforms.Set(viewRightNormal, m_viewRightNormalLocation); public void PrevViewRightNormal(Vec2F viewRightNormal) => ProgramUniforms.Set(viewRightNormal, m_prevViewRightNormalLocation); @@ -70,6 +73,7 @@ protected override string VertexShader() => @" layout(location = 3) in float offsetXYZ; layout(location = 4) in float renderOptions; layout(location = 5) in float textureIndex; + layout(location = 6) in float textureDimIndex; flat out float lightLevelFrag; flat out float alphaFrag; @@ -101,6 +105,7 @@ protected override string VertexShader() => @" uniform sampler2DArray boundTexture; uniform samplerBuffer sectorColormapTexture; uniform samplerBuffer sectorFogTexture; + uniform samplerBuffer spriteTextureDimensionsTexture; float distSquared(vec2 v1, vec2 v2) { vec2 length = v1.xy - v2.xy; @@ -128,8 +133,9 @@ void main() float offsetZSign = float(((intOptions >> 30) & 1) > 0); offsetXYOption = mix(offsetXYOption, -offsetXYOption, offsetXYSign); offsetZ = mix(offsetZ, -offsetZ, offsetZSign); - ivec2 textureDim = textureSize(boundTexture, 0).xy; - textureWidthFrag = textureDim.x; + vec3 textureDimFloat = texelFetch(spriteTextureDimensionsTexture, int(textureDimIndex)).rgb; + ivec2 textureDim = ivec2(textureDimFloat.x, textureDimFloat.y); + textureWidthFrag = textureDimFloat.x; ${SectorColorMapVertexFunction} @@ -137,6 +143,9 @@ void main() vec3 offsetXY = vec3(posMoveDir.xy * offsetXYOption, 0); vec3 interpolatedPos = mix(prevPos, pos, timeFrac); + float baseSize = float(textureSize(boundTexture, 0).x); + float calcU = textureDimFloat.x / baseSize; + float calcV = textureDimFloat.y / baseSize; ${MinMaxPos} centerPosFrag = interpolatedPos; @@ -152,11 +161,13 @@ void main() vec3 cornerPos = vec3(x, y, z); - float leftU = clamp(flipU, 0, 1); - float rightU = 1 - clamp(flipU, 0, 1); + //float leftU = clamp(flipU, 0, calcU); + //float rightU = 1 - clamp(flipU, 0, calcU); + float leftU = mix(0.0, calcU, float(flipU)); + float rightU = mix(calcU, 0.0, float(flipU)); float u = mix(leftU, rightU, xSelect); - float v = mix(1.0, 0.0, ySelect); + float v = mix(calcV, 0.0, ySelect); uvFrag = vec2(u, v); @@ -196,10 +207,12 @@ private string GetMinMaxPos() } return @" + float worldWidth = calcU * baseSize; + float worldHeight = calcV * baseSize; zPosFrag = interpolatedPos.z; interpolatedPos.z += offsetZ; vec3 minPos = interpolatedPos - offsetXY; - vec3 maxPos = interpolatedPos + (posMoveDir * textureDim.x) + (vec3(0, 0, 1) * textureDim.y) - offsetXY;"; + vec3 maxPos = interpolatedPos + (posMoveDir * worldWidth) + (vec3(0, 0, 1) * worldHeight) - offsetXY;"; } private static string AdjustSpriteVertexClip() diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityRenderer.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityRenderer.cs index bc91f1240..9eb59a8d0 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityRenderer.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityRenderer.cs @@ -300,6 +300,7 @@ public void RenderEntity(Entity entity, in Vec2D position, int renderIndex) vertex.RenderOptions = VertexOptions.EntityPackRender( Renderer.GetLightBufferIndex(sector, WorldStatic.Sector3D && sector.Sectors3D.Length > 0 ? LightBufferType.Wall : LightBufferType.Floor), renderIndex); vertex.TextureIndex = texture.ArrayIndex; + vertex.TextureDimIndex = spriteRotation.Texture.Index; if (entity.Definition.Flags.SpawnCeiling() && m_vanillaRender) { @@ -311,6 +312,7 @@ public void RenderEntity(Entity entity, in Vec2D position, int renderIndex) vertex.PrevPos.Z = entity.PrevPosition.Z != entity.Position.Z ? (float)entity.Sector.Ceiling.PrevZ : ceilingZ; } + offsetZ = 0; vertex.OffsetXYZ = VertexOptions.EntityPackXYZ(offsetX, offsetZ); arrayData.Length = length + 1; @@ -418,6 +420,7 @@ private void SetUniforms(EntityProgram program, RenderInfo renderInfo) program.PlaneClipTexture(BindTextures.PlaneClipTexture); program.MapDataTexture(BindTextures.MapLineData); program.LineHeightsTexture(BindTextures.LineHeights); + program.SpriteTextureDimensionsTexture(BindTextures.SpriteTextureDimensions); } public void RenderOpaque(RenderInfo renderInfo) diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityVertex.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityVertex.cs index e34982c47..69c32694b 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityVertex.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityVertex.cs @@ -25,4 +25,7 @@ public struct EntityVertex [VertexAttribute(divisor: 1)] public float TextureIndex; + + [VertexAttribute(divisor: 1)] + public float TextureDimIndex; } diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/LegacyWorldRenderer.cs b/Core/Render/OpenGL/Renderers/Legacy/World/LegacyWorldRenderer.cs index 018ef400b..67c513334 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/LegacyWorldRenderer.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/LegacyWorldRenderer.cs @@ -17,7 +17,6 @@ using Helion.Util.Configs; using Helion.Util.Configs.Components; using Helion.Util.Profiling.Timers; -using Helion.Util.Timing; using Helion.World; using Helion.World.Entities; using Helion.World.Geometry.Sectors; @@ -30,6 +29,8 @@ namespace Helion.Render.OpenGL.Renderers.Legacy.World; public partial class LegacyWorldRenderer : WorldRenderer { + public override TextureBucket[] SpriteTextureBuckets { get; protected set; } = []; + const int EntityRenderIndexMax = 100; private static readonly Logger Log = LogManager.GetCurrentClassLogger(); private readonly IConfig m_config; @@ -126,7 +127,7 @@ public override void UpdateToNewWorld(IWorld world) if (!m_spritesCached) { var textureArrayBuilder = new GLTextureArrayBuilder(m_archiveCollection.TextureManager, m_textureManager); - textureArrayBuilder.BuildSprites(m_archiveCollection.TextureManager.SpriteDefinitions); + SpriteTextureBuckets = textureArrayBuilder.BuildSprites(m_archiveCollection.TextureManager.SpriteDefinitions); m_spritesCached = true; } diff --git a/Core/Render/OpenGL/Renderers/WorldRenderer.cs b/Core/Render/OpenGL/Renderers/WorldRenderer.cs index 98667db2d..219681929 100644 --- a/Core/Render/OpenGL/Renderers/WorldRenderer.cs +++ b/Core/Render/OpenGL/Renderers/WorldRenderer.cs @@ -1,7 +1,8 @@ -using System; using Helion.Render.OpenGL.Framebuffer; using Helion.Render.OpenGL.Shared; +using Helion.Render.OpenGL.Texture.Legacy; using Helion.World; +using System; namespace Helion.Render.OpenGL.Renderers; @@ -10,6 +11,8 @@ namespace Helion.Render.OpenGL.Renderers; /// public abstract class WorldRenderer : IDisposable { + public abstract TextureBucket[] SpriteTextureBuckets { get; protected set; } + protected readonly WeakReference m_lastRenderedWorld = new WeakReference(null); /// diff --git a/Core/Render/OpenGL/Texture/GLTextureManager.cs b/Core/Render/OpenGL/Texture/GLTextureManager.cs index cf3b29ae2..f9deae233 100644 --- a/Core/Render/OpenGL/Texture/GLTextureManager.cs +++ b/Core/Render/OpenGL/Texture/GLTextureManager.cs @@ -238,10 +238,18 @@ public GLTextureType GetTexture(int index, bool repeatY = true) if (image.Dimension.Width < dimension.Width || image.Dimension.Height < dimension.Height) { var fitImage = new Image(dimension, image.ImageType); - for (int x = 0; x < image.Indices.Length; x++) - fitImage.Indices[x] = image.Indices[x]; - for (int x = 0; x < image.Pixels.Length; x++) - fitImage.Pixels[x] = image.Pixels[x]; + + // TODO probably make this faster + for (int x = 0; x < image.Dimension.Width; x++) + { + for (int y = 0; y < image.Dimension.Height; y++) + { + fitImage.SetPixel(x, y, image.GetPixel(x, y)); + if (image.ImageType == ImageType.PaletteWithArgb) + fitImage.SetIndex(x, y, image.GetIndex(x, y)); + } + } + image = fitImage; } diff --git a/Core/Render/OpenGL/Texture/Legacy/GLTextureArrayBuilder.cs b/Core/Render/OpenGL/Texture/Legacy/GLTextureArrayBuilder.cs index 50cea3196..05c66b890 100644 --- a/Core/Render/OpenGL/Texture/Legacy/GLTextureArrayBuilder.cs +++ b/Core/Render/OpenGL/Texture/Legacy/GLTextureArrayBuilder.cs @@ -11,7 +11,7 @@ namespace Helion.Render.OpenGL.Texture.Legacy; -record struct TextureBucket(Dimension Dimension, DynamicArray Textures); +public record struct TextureBucket(Dimension Dimension, DynamicArray Textures); public class GLTextureArrayBuilder(TextureManager textureManager, LegacyGLTextureManager glTextureManager) { @@ -48,7 +48,7 @@ public void BuildLevel(IEnumerable flatTextures, IEnumerable wallTextu DebugLog(totalTextures, arrayTextures + animated); } - public void BuildSprites(DynamicArray spriteDefinitions) + public TextureBucket[] BuildSprites(DynamicArray spriteDefinitions) { var spriteTextures = new DynamicArray(); var spriteTextureHandles = new HashSet(); @@ -75,7 +75,9 @@ public void BuildSprites(DynamicArray spriteDefinitions) } } - var buckets = CreateTextureBuckets(spriteTextures, 32, 5); + const int BaseSize = 32; + const int BucketCount = 5; + var buckets = CreateTextureBuckets(spriteTextures, BaseSize, BucketCount); for (int i = 0; i < buckets.Length - 1; i++) { var bucket = buckets[i]; @@ -99,7 +101,10 @@ public void BuildSprites(DynamicArray spriteDefinitions) if (rotation.RenderStore != null) continue; - + + if (rotation.Texture.Image != null) + rotation.TextureBucket = GetBucketIndex(rotation.Texture.Image.Dimension, BaseSize, BucketCount); + rotation.BrightmapRenderStore = m_glTextureManager.CreateBrightMapTexture(rotation.Texture.BrightmapImage, rotation.Texture.Name, ResourceNamespace.Brightmaps); if (m_glTextureManager.TryGetTexture(rotation.Texture.Index, TextureContext.WorldSprites, out var texture)) @@ -109,6 +114,8 @@ public void BuildSprites(DynamicArray spriteDefinitions) } } } + + return buckets; } [Conditional("DEBUG")] @@ -201,7 +208,7 @@ private static TextureBucket[] CreateTextureBuckets(DynamicArray? m_colorMapBuffer; private GLBufferTextureStorage? m_mapDataBuffer; private GLBufferTextureStorage? m_lineHeightsBuffer; + private GLBufferTextureStorage? m_spriteTextureDimensionsBuffer; private bool m_sectorFog; private bool m_sectorColor; @@ -108,6 +113,36 @@ public unsafe void UpdateToNewWorld(IWorld world) m_sectorColorMapsBuffer = InitLightBuffer(world, alloc, m_sectorColorMapsBuffer, InitSectorColorMap, "Sector colormaps", SizedInternalFormat.Rgba32f, GLBufferTextureStorage.FourComponentLength); m_sectorFogBuffer = InitLightBuffer(world, alloc, m_sectorFogBuffer, InitSectorFogBuffer, "Sector fog", SizedInternalFormat.Rgba32f, GLBufferTextureStorage.FourComponentLength); SetLineHeights(world, alloc); + + if (m_spriteTextureDimensionsBuffer == null) + { + var textures = new DynamicArray(); + foreach (var bucket in m_worldRenderer.SpriteTextureBuckets) + textures.AddRange(bucket.Textures); + + var maxId = 0; + foreach (var texture in textures) + maxId = MathHelper.Max(maxId, texture.Index); + + // TODO probably shouldn't be float + var bufferLength = (maxId + 1) * 2; + m_spriteTextureDimensionsBuffer = new("Sprite Texture Dimensions", new float[bufferLength], SizedInternalFormat.Rg32f, false); + m_spriteTextureDimensionsBuffer.Map(data => + { + float* buffer = (float*)data.ToPointer(); + for (int i = 0; i < textures.Length; i++) + { + var texture = textures[i]; + if (texture.Image == null) + continue; + + var index = texture.Index * 2; + Assert.Precondition(index + 1 < bufferLength, $"Invalid texture index {texture.Index}"); + buffer[index] = texture.Image.Dimension.Width; + buffer[index + 1] = texture.Image.Dimension.Height; + } + }); + } } private unsafe void SetLineHeights(IWorld world, bool alloc) diff --git a/Core/Render/Renderer.cs b/Core/Render/Renderer.cs index 570e28bbe..df813be74 100644 --- a/Core/Render/Renderer.cs +++ b/Core/Render/Renderer.cs @@ -552,6 +552,7 @@ public void Render(RenderCommands renderCommands) BindLightBuffer(); BindMapDataBuffer(); BindLineHeightsBuffer(); + BindSpriteTextureDimensionsBuffer(); // This has to be tracked beyond just the rendering command, and it // also prevents something from going terribly wrong if there is no @@ -658,6 +659,11 @@ private void BindLineHeightsBuffer() m_lineHeightsBuffer?.BindTexture(BindTextures.LineHeights); } + private void BindSpriteTextureDimensionsBuffer() + { + m_spriteTextureDimensionsBuffer?.BindTexture(BindTextures.SpriteTextureDimensions); + } + public void PerformThrowableErrorChecks() { if (m_config.Developer.Render.Debug) diff --git a/Core/Resources/SpriteRotation.cs b/Core/Resources/SpriteRotation.cs index 2cf479a30..eefc48878 100644 --- a/Core/Resources/SpriteRotation.cs +++ b/Core/Resources/SpriteRotation.cs @@ -11,6 +11,7 @@ public class SpriteRotation(Texture texture, bool mirror, bool brightmapNoFullbr public object? RenderStore; public object? BrightmapRenderStore; public bool BrightmapNoFullbright = brightmapNoFullbright; + public int TextureBucket; private LookupArray? m_translationRotations; public bool TryGetTranslationRotation(int index, [NotNullWhen(true)] out SpriteRotation? rotation) From e29f8a785643d99a4ff7a0cfa66dcf848744f246 Mon Sep 17 00:00:00 2001 From: Nick Date: Mon, 14 Sep 2026 05:25:11 -0400 Subject: [PATCH 38/49] fix z offset --- .../Renderers/Legacy/World/Entities/EntityRenderer.cs | 11 +++++------ Core/Render/OpenGL/Texture/GLTextureManager.cs | 2 +- .../OpenGL/Texture/Legacy/GLTextureArrayBuilder.cs | 8 ++------ Core/Resources/SpriteRotation.cs | 3 ++- 4 files changed, 10 insertions(+), 14 deletions(-) diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityRenderer.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityRenderer.cs index 9eb59a8d0..4951dee25 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityRenderer.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityRenderer.cs @@ -136,9 +136,9 @@ private static uint CalculateRotation(uint viewAngle, uint entityAngle) return unchecked((viewAngle - entityAngle + SpriteFrameRotationAngle) >> 29); } - private int GetOffsetZ(Entity entity, GLLegacyTexture texture) + private int GetOffsetZ(Entity entity, GLLegacyTexture texture, int textureHeight) { - int offsetAmount = texture.Offset.Y - texture.Height; + int offsetAmount = texture.Offset.Y - textureHeight; if (m_vanillaRender) return offsetAmount; @@ -148,13 +148,13 @@ private int GetOffsetZ(Entity entity, GLLegacyTexture texture) if (entity.Sector.Flood || entity.Sector.Floor.NoRender) return offsetAmount; - if (!m_spriteClip || texture.Height < m_spriteClipMin || entity.Definition.IsInventory) + if (!m_spriteClip || textureHeight < m_spriteClipMin || entity.Definition.IsInventory) return MathHelper.Max(offsetAmount, -texture.BlankRowsFromBottom); if (entity.Position.Z - entity.HighestFloorSector.Floor.Z < texture.Offset.Y) { // Truncate to integer pixel amount. This helps the jumpiness for the stock large torches. - int maxHeight = (int)((texture.Height - texture.BlankRowsFromBottom) * m_spriteClipFactorMax); + int maxHeight = (int)((textureHeight - texture.BlankRowsFromBottom) * m_spriteClipFactorMax); if (-offsetAmount > maxHeight) offsetAmount = -maxHeight - texture.BlankRowsFromBottom; return offsetAmount; @@ -245,7 +245,7 @@ public void RenderEntity(Entity entity, in Vec2D position, int renderIndex) var disableFullbright = spriteRotation.BrightmapNoFullbright; var isFullBright = (entity.Flags.Bright() || entity.FrameState.Frame.Properties.Bright) && !disableFullbright; - var offsetZ = GetOffsetZ(entity, texture); + var offsetZ = GetOffsetZ(entity, texture, spriteRotation.TextureDimension.Height); int fuzz; RenderStyle renderStyle; @@ -312,7 +312,6 @@ public void RenderEntity(Entity entity, in Vec2D position, int renderIndex) vertex.PrevPos.Z = entity.PrevPosition.Z != entity.Position.Z ? (float)entity.Sector.Ceiling.PrevZ : ceilingZ; } - offsetZ = 0; vertex.OffsetXYZ = VertexOptions.EntityPackXYZ(offsetX, offsetZ); arrayData.Length = length + 1; diff --git a/Core/Render/OpenGL/Texture/GLTextureManager.cs b/Core/Render/OpenGL/Texture/GLTextureManager.cs index f9deae233..638fa3768 100644 --- a/Core/Render/OpenGL/Texture/GLTextureManager.cs +++ b/Core/Render/OpenGL/Texture/GLTextureManager.cs @@ -237,7 +237,7 @@ public GLTextureType GetTexture(int index, bool repeatY = true) if (image.Dimension.Width < dimension.Width || image.Dimension.Height < dimension.Height) { - var fitImage = new Image(dimension, image.ImageType); + var fitImage = new Image(dimension, image.ImageType, offset: image.Offset); // TODO probably make this faster for (int x = 0; x < image.Dimension.Width; x++) diff --git a/Core/Render/OpenGL/Texture/Legacy/GLTextureArrayBuilder.cs b/Core/Render/OpenGL/Texture/Legacy/GLTextureArrayBuilder.cs index 05c66b890..787d5e0ec 100644 --- a/Core/Render/OpenGL/Texture/Legacy/GLTextureArrayBuilder.cs +++ b/Core/Render/OpenGL/Texture/Legacy/GLTextureArrayBuilder.cs @@ -70,13 +70,12 @@ public TextureBucket[] BuildSprites(DynamicArray spriteDefinit if (spriteTextureHandles.Add(rotation.Texture.Index)) spriteTextures.Add(rotation.Texture); - } } } - const int BaseSize = 32; - const int BucketCount = 5; + const int BaseSize = 16; + const int BucketCount = 6; var buckets = CreateTextureBuckets(spriteTextures, BaseSize, BucketCount); for (int i = 0; i < buckets.Length - 1; i++) { @@ -102,9 +101,6 @@ public TextureBucket[] BuildSprites(DynamicArray spriteDefinit if (rotation.RenderStore != null) continue; - if (rotation.Texture.Image != null) - rotation.TextureBucket = GetBucketIndex(rotation.Texture.Image.Dimension, BaseSize, BucketCount); - rotation.BrightmapRenderStore = m_glTextureManager.CreateBrightMapTexture(rotation.Texture.BrightmapImage, rotation.Texture.Name, ResourceNamespace.Brightmaps); if (m_glTextureManager.TryGetTexture(rotation.Texture.Index, TextureContext.WorldSprites, out var texture)) diff --git a/Core/Resources/SpriteRotation.cs b/Core/Resources/SpriteRotation.cs index eefc48878..3ad336b3d 100644 --- a/Core/Resources/SpriteRotation.cs +++ b/Core/Resources/SpriteRotation.cs @@ -1,3 +1,4 @@ +using Helion.Geometry; using Helion.Util.Container; using System.Diagnostics.CodeAnalysis; @@ -11,7 +12,7 @@ public class SpriteRotation(Texture texture, bool mirror, bool brightmapNoFullbr public object? RenderStore; public object? BrightmapRenderStore; public bool BrightmapNoFullbright = brightmapNoFullbright; - public int TextureBucket; + public Dimension TextureDimension = texture.Image?.Dimension ?? default; private LookupArray? m_translationRotations; public bool TryGetTranslationRotation(int index, [NotNullWhen(true)] out SpriteRotation? rotation) From 1ce507be8de5b43091086be6bb830339ebaccb15 Mon Sep 17 00:00:00 2001 From: Nick Date: Mon, 14 Sep 2026 05:44:08 -0400 Subject: [PATCH 39/49] start with 32 bucket --- Core/Render/OpenGL/Texture/Legacy/GLTextureArrayBuilder.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Core/Render/OpenGL/Texture/Legacy/GLTextureArrayBuilder.cs b/Core/Render/OpenGL/Texture/Legacy/GLTextureArrayBuilder.cs index 787d5e0ec..4e6e198cb 100644 --- a/Core/Render/OpenGL/Texture/Legacy/GLTextureArrayBuilder.cs +++ b/Core/Render/OpenGL/Texture/Legacy/GLTextureArrayBuilder.cs @@ -74,8 +74,8 @@ public TextureBucket[] BuildSprites(DynamicArray spriteDefinit } } - const int BaseSize = 16; - const int BucketCount = 6; + const int BaseSize = 32; + const int BucketCount = 5; var buckets = CreateTextureBuckets(spriteTextures, BaseSize, BucketCount); for (int i = 0; i < buckets.Length - 1; i++) { From 0c94efeb6c07d1d5cb8da51db951f1b4c72926a0 Mon Sep 17 00:00:00 2001 From: Nick Date: Mon, 14 Sep 2026 06:05:05 -0400 Subject: [PATCH 40/49] store max index in bucket, refactor to ui --- .../Legacy/World/Entities/EntityProgram.cs | 11 +++-- .../Legacy/World/LegacyWorldRenderer.cs | 2 +- Core/Render/OpenGL/Renderers/WorldRenderer.cs | 2 +- .../Texture/Legacy/GLTextureArrayBuilder.cs | 12 +++--- Core/Render/Renderer.World.cs | 40 ++++++++----------- 5 files changed, 30 insertions(+), 37 deletions(-) diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityProgram.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityProgram.cs index ac7c9cdbe..bdb48a411 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityProgram.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityProgram.cs @@ -105,7 +105,7 @@ protected override string VertexShader() => @" uniform sampler2DArray boundTexture; uniform samplerBuffer sectorColormapTexture; uniform samplerBuffer sectorFogTexture; - uniform samplerBuffer spriteTextureDimensionsTexture; + uniform usamplerBuffer spriteTextureDimensionsTexture; float distSquared(vec2 v1, vec2 v2) { vec2 length = v1.xy - v2.xy; @@ -133,9 +133,8 @@ void main() float offsetZSign = float(((intOptions >> 30) & 1) > 0); offsetXYOption = mix(offsetXYOption, -offsetXYOption, offsetXYSign); offsetZ = mix(offsetZ, -offsetZ, offsetZSign); - vec3 textureDimFloat = texelFetch(spriteTextureDimensionsTexture, int(textureDimIndex)).rgb; - ivec2 textureDim = ivec2(textureDimFloat.x, textureDimFloat.y); - textureWidthFrag = textureDimFloat.x; + uvec2 textureDim = texelFetch(spriteTextureDimensionsTexture, int(textureDimIndex)).rg; + textureWidthFrag = textureDim.x; ${SectorColorMapVertexFunction} @@ -144,8 +143,8 @@ void main() vec3 interpolatedPos = mix(prevPos, pos, timeFrac); float baseSize = float(textureSize(boundTexture, 0).x); - float calcU = textureDimFloat.x / baseSize; - float calcV = textureDimFloat.y / baseSize; + float calcU = textureDim.x / baseSize; + float calcV = textureDim.y / baseSize; ${MinMaxPos} centerPosFrag = interpolatedPos; diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/LegacyWorldRenderer.cs b/Core/Render/OpenGL/Renderers/Legacy/World/LegacyWorldRenderer.cs index 67c513334..ce42007bb 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/LegacyWorldRenderer.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/LegacyWorldRenderer.cs @@ -29,7 +29,7 @@ namespace Helion.Render.OpenGL.Renderers.Legacy.World; public partial class LegacyWorldRenderer : WorldRenderer { - public override TextureBucket[] SpriteTextureBuckets { get; protected set; } = []; + public override TextureBuckets SpriteTextureBuckets { get; protected set; } const int EntityRenderIndexMax = 100; private static readonly Logger Log = LogManager.GetCurrentClassLogger(); diff --git a/Core/Render/OpenGL/Renderers/WorldRenderer.cs b/Core/Render/OpenGL/Renderers/WorldRenderer.cs index 219681929..d9864c123 100644 --- a/Core/Render/OpenGL/Renderers/WorldRenderer.cs +++ b/Core/Render/OpenGL/Renderers/WorldRenderer.cs @@ -11,7 +11,7 @@ namespace Helion.Render.OpenGL.Renderers; /// public abstract class WorldRenderer : IDisposable { - public abstract TextureBucket[] SpriteTextureBuckets { get; protected set; } + public abstract TextureBuckets SpriteTextureBuckets { get; protected set; } protected readonly WeakReference m_lastRenderedWorld = new WeakReference(null); diff --git a/Core/Render/OpenGL/Texture/Legacy/GLTextureArrayBuilder.cs b/Core/Render/OpenGL/Texture/Legacy/GLTextureArrayBuilder.cs index 4e6e198cb..f8b651d75 100644 --- a/Core/Render/OpenGL/Texture/Legacy/GLTextureArrayBuilder.cs +++ b/Core/Render/OpenGL/Texture/Legacy/GLTextureArrayBuilder.cs @@ -11,6 +11,7 @@ namespace Helion.Render.OpenGL.Texture.Legacy; +public record struct TextureBuckets(int MaxTextureIndex, TextureBucket[] Buckets); public record struct TextureBucket(Dimension Dimension, DynamicArray Textures); public class GLTextureArrayBuilder(TextureManager textureManager, LegacyGLTextureManager glTextureManager) @@ -48,8 +49,9 @@ public void BuildLevel(IEnumerable flatTextures, IEnumerable wallTextu DebugLog(totalTextures, arrayTextures + animated); } - public TextureBucket[] BuildSprites(DynamicArray spriteDefinitions) + public TextureBuckets BuildSprites(DynamicArray spriteDefinitions) { + var maxIndex = 0; var spriteTextures = new DynamicArray(); var spriteTextureHandles = new HashSet(); foreach (var spriteDefinition in spriteDefinitions) @@ -65,11 +67,11 @@ public TextureBucket[] BuildSprites(DynamicArray spriteDefinit if (rotation == null) continue; - if (rotation.Texture.RenderStore != null) - continue; - if (spriteTextureHandles.Add(rotation.Texture.Index)) + { spriteTextures.Add(rotation.Texture); + maxIndex = MathHelper.Max(rotation.Texture.Index, maxIndex); + } } } } @@ -111,7 +113,7 @@ public TextureBucket[] BuildSprites(DynamicArray spriteDefinit } } - return buckets; + return new(maxIndex, buckets); } [Conditional("DEBUG")] diff --git a/Core/Render/Renderer.World.cs b/Core/Render/Renderer.World.cs index c3fc16b1c..8f11f00f5 100644 --- a/Core/Render/Renderer.World.cs +++ b/Core/Render/Renderer.World.cs @@ -4,11 +4,9 @@ using Helion.Render.OpenGL.Context; using Helion.Render.OpenGL.Renderers.Legacy.World.Geometry.Static; using Helion.Render.OpenGL.Renderers.Legacy.World.Shader; -using Helion.Render.OpenGL.Texture.Legacy; using Helion.Render.OpenGL.Textures; using Helion.Util; using Helion.Util.Assertion; -using Helion.Util.Container; using Helion.World; using Helion.World.Geometry.Lines; using Helion.World.Geometry.Sectors; @@ -44,7 +42,7 @@ enum BlockSide private GLBufferTextureStorage? m_colorMapBuffer; private GLBufferTextureStorage? m_mapDataBuffer; private GLBufferTextureStorage? m_lineHeightsBuffer; - private GLBufferTextureStorage? m_spriteTextureDimensionsBuffer; + private GLBufferTextureStorage? m_spriteTextureDimensionsBuffer; private bool m_sectorFog; private bool m_sectorColor; @@ -116,30 +114,24 @@ public unsafe void UpdateToNewWorld(IWorld world) if (m_spriteTextureDimensionsBuffer == null) { - var textures = new DynamicArray(); - foreach (var bucket in m_worldRenderer.SpriteTextureBuckets) - textures.AddRange(bucket.Textures); - - var maxId = 0; - foreach (var texture in textures) - maxId = MathHelper.Max(maxId, texture.Index); - - // TODO probably shouldn't be float - var bufferLength = (maxId + 1) * 2; - m_spriteTextureDimensionsBuffer = new("Sprite Texture Dimensions", new float[bufferLength], SizedInternalFormat.Rg32f, false); + var bufferLength = (m_worldRenderer.SpriteTextureBuckets.MaxTextureIndex + 1) * 2; + m_spriteTextureDimensionsBuffer = new("Sprite Texture Dimensions", new uint[bufferLength], SizedInternalFormat.Rg32ui, false); m_spriteTextureDimensionsBuffer.Map(data => { - float* buffer = (float*)data.ToPointer(); - for (int i = 0; i < textures.Length; i++) + uint* buffer = (uint*)data.ToPointer(); + foreach (var bucket in m_worldRenderer.SpriteTextureBuckets.Buckets) { - var texture = textures[i]; - if (texture.Image == null) - continue; - - var index = texture.Index * 2; - Assert.Precondition(index + 1 < bufferLength, $"Invalid texture index {texture.Index}"); - buffer[index] = texture.Image.Dimension.Width; - buffer[index + 1] = texture.Image.Dimension.Height; + for (int i = 0; i < bucket.Textures.Length; i++) + { + var texture = bucket.Textures[i]; + if (texture.Image == null) + continue; + + var index = texture.Index * 2; + Assert.Precondition(index + 1 < bufferLength, $"Invalid texture index {texture.Index}"); + buffer[index] = (uint)texture.Image.Dimension.Width; + buffer[index + 1] = (uint)texture.Image.Dimension.Height; + } } }); } From 2e9cef0465b3b4b03dac431521f965a2eb15edad Mon Sep 17 00:00:00 2001 From: Nick Date: Mon, 14 Sep 2026 08:08:39 -0400 Subject: [PATCH 41/49] use single fit image, move brightmap sprites to overflow bucket --- Core/Graphics/Image.cs | 21 +++++++++- .../Legacy/World/Entities/EntityProgram.cs | 3 -- .../Render/OpenGL/Texture/GLTextureManager.cs | 39 ++++++++----------- .../Texture/Legacy/GLTextureArrayBuilder.cs | 25 ++++++++---- .../Texture/Legacy/LegacyGLTextureManager.cs | 32 +++++++-------- 5 files changed, 69 insertions(+), 51 deletions(-) diff --git a/Core/Graphics/Image.cs b/Core/Graphics/Image.cs index 90908ed01..c837cb674 100644 --- a/Core/Graphics/Image.cs +++ b/Core/Graphics/Image.cs @@ -2,6 +2,7 @@ using Helion.Geometry.Vectors; using Helion.Graphics.Palettes; using Helion.Resources; +using Helion.Util.Assertion; using Helion.Util.Extensions; using SixLabors.ImageSharp.PixelFormats; using System; @@ -30,7 +31,7 @@ public class Image public Dimension Dimension; public ImageType ImageType; public ImageType UploadType; - public readonly Vec2I Offset; + public Vec2I Offset; public readonly ResourceNamespace Namespace; public uint[] m_pixels; // Stored as argb with a = high byte, b = low byte public readonly byte[] m_indices; @@ -407,6 +408,24 @@ public void SetPixel(int x, int y, Color color, Palette palette) } } + public void CopyPixelsFrom(Image image) + { + Assert.Precondition(image.ImageType == ImageType, "Image type mismatch"); + + var width = Math.Min(image.Dimension.Width, Dimension.Width); + var height = Math.Min(image.Dimension.Height, Dimension.Height); + + for (int x = 0; x < width; x++) + { + for (int y = 0; y < height; y++) + { + SetPixel(x, y, image.GetPixel(x, y)); + if (image.ImageType == ImageType.PaletteWithArgb) + SetIndex(x, y, image.GetIndex(x, y)); + } + } + } + public void ConvertToGrayscale(bool normalize) { if (ImageType == ImageType.Palette) diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityProgram.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityProgram.cs index bdb48a411..7ad8f40ac 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityProgram.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityProgram.cs @@ -159,9 +159,6 @@ void main() float z = mix(minPos.z, maxPos.z, ySelect); vec3 cornerPos = vec3(x, y, z); - - //float leftU = clamp(flipU, 0, calcU); - //float rightU = 1 - clamp(flipU, 0, calcU); float leftU = mix(0.0, calcU, float(flipU)); float rightU = mix(calcU, 0.0, float(flipU)); diff --git a/Core/Render/OpenGL/Texture/GLTextureManager.cs b/Core/Render/OpenGL/Texture/GLTextureManager.cs index 638fa3768..3dd20a1a3 100644 --- a/Core/Render/OpenGL/Texture/GLTextureManager.cs +++ b/Core/Render/OpenGL/Texture/GLTextureManager.cs @@ -234,30 +234,23 @@ public GLTextureType GetTexture(int index, bool repeatY = true) Assert.Precondition(texture.Image != null, "Texture must have an image"); var image = texture.Image ?? new Image(default, ImageType.Argb); - - if (image.Dimension.Width < dimension.Width || image.Dimension.Height < dimension.Height) - { - var fitImage = new Image(dimension, image.ImageType, offset: image.Offset); - - // TODO probably make this faster - for (int x = 0; x < image.Dimension.Width; x++) - { - for (int y = 0; y < image.Dimension.Height; y++) - { - fitImage.SetPixel(x, y, image.GetPixel(x, y)); - if (image.ImageType == ImageType.PaletteWithArgb) - fitImage.SetIndex(x, y, image.GetIndex(x, y)); - } - } - - image = fitImage; - } - images[i] = image; textures[i] = texture; } - var arraySubTextures = GenerateTextureArray(images, images[0].Dimension, ResourceNamespace.Textures, textureFlags, textureContext, out var arrayTexture); + var fitImage = new Image(dimension, images[0].ImageType); + + var arraySubTextures = GenerateTextureArray(imageIndex => + { + var image = images[imageIndex]; + if (image.Width == dimension.Width && image.Height == dimension.Height) + return image; + + fitImage.CopyPixelsFrom(image); + fitImage.Offset = image.Offset; + return fitImage; + }, images.Length, dimension, ResourceNamespace.Textures, textureFlags, textureContext, out var arrayTexture); + for (int i = 0; i < images.Length; i++) { var glTexture = arraySubTextures[i]; @@ -450,9 +443,9 @@ public GLTextureType CreateBrightMapTexture(Image? image, string? name, Resource return texture; } - protected GLTextureType CreateTexture(Image? image, bool repeatY) => CreateTexture(image, null, ResourceNamespace.Global, repeatY); + public GLTextureType CreateTexture(Image? image, bool repeatY) => CreateTexture(image, null, ResourceNamespace.Global, repeatY); - protected GLTextureType CreateTexture(Image? image, string? name, ResourceNamespace resourceNamespace, bool repeatY = true) + public GLTextureType CreateTexture(Image? image, string? name, ResourceNamespace resourceNamespace, bool repeatY = true) { var textureTracker = GetTextureTracker(repeatY); GLTextureType? texture; @@ -506,7 +499,7 @@ private void ReleaseUnmanagedResources() } protected abstract GLTextureType GenerateTexture(Image image, string name, ResourceNamespace resourceNamespace, TextureFlags flags = TextureFlags.Default); - protected abstract GLTextureType[] GenerateTextureArray(Image[] images, Dimension dimension, ResourceNamespace resourceNamespace, TextureFlags flags, TextureContext textureContext, out GLTextureType arrayTexture); + protected abstract GLTextureType[] GenerateTextureArray(Func getImage, int imageLength, Dimension dimension, ResourceNamespace resourceNamespace, TextureFlags flags, TextureContext textureContext, out GLTextureType arrayTexture); public abstract void ReUpload(GLTextureType texture, Image image, uint[] imagePixels); diff --git a/Core/Render/OpenGL/Texture/Legacy/GLTextureArrayBuilder.cs b/Core/Render/OpenGL/Texture/Legacy/GLTextureArrayBuilder.cs index f8b651d75..1db197e87 100644 --- a/Core/Render/OpenGL/Texture/Legacy/GLTextureArrayBuilder.cs +++ b/Core/Render/OpenGL/Texture/Legacy/GLTextureArrayBuilder.cs @@ -1,5 +1,6 @@ using Helion.Geometry; using Helion.Resources; +using Helion.Resources.Textures; using Helion.Util; using Helion.Util.Assertion; using Helion.Util.Container; @@ -105,6 +106,12 @@ public TextureBuckets BuildSprites(DynamicArray spriteDefiniti rotation.BrightmapRenderStore = m_glTextureManager.CreateBrightMapTexture(rotation.Texture.BrightmapImage, rotation.Texture.Name, ResourceNamespace.Brightmaps); + if (!CanTextureArray(rotation.Texture)) + { + rotation.RenderStore = m_glTextureManager.CreateTexture(rotation.Texture.Image, rotation.Texture.Name, ResourceNamespace.Sprites); + continue; + } + if (m_glTextureManager.TryGetTexture(rotation.Texture.Index, TextureContext.WorldSprites, out var texture)) rotation.RenderStore = texture.RenderStoreClamp ?? texture.RenderStore; @@ -153,12 +160,17 @@ private int BuildTextureArrayFromTextures(DynamicArray textur private void AddTexture(DynamicArray textures, int index) { - // Exclude brightmaps for now. They probably could be made into arrays but like animations the complication may not be worth it. var texture = m_textureManager.GetTexture(index); - if (texture.Image != null && texture.BrightmapImage == null) + if (CanTextureArray(texture)) textures.Add(texture); } + private static bool CanTextureArray(Resources.Texture texture) + { + // Exclude brightmaps for now. They probably could be made into arrays but like animations the complication may not be worth it. + return texture.Image != null && texture.BrightmapImage == null; + } + private static int SortTexturesByDimensions(Resources.Texture x, Resources.Texture y) { if (x.Image == null || y.Image == null) @@ -191,15 +203,12 @@ private static TextureBucket[] CreateTextureBuckets(DynamicArray getImage, int imageLength, string name, ResourceNamespace resourceNamespace, TextureFlags flags, + Dimension dimensions, Action onUploadImage) { GL.BindTexture(texture.Target, texture.TextureId); flags = GetFlagsForNamespace(resourceNamespace, flags); - GL.TexImage3D(TextureTarget.Texture2DArray, 0, PixelInternalFormat.Rgba8, images[0].Width, images[0].Height, images.Length, 0, + GL.TexImage3D(TextureTarget.Texture2DArray, 0, PixelInternalFormat.Rgba8, dimensions.Width, dimensions.Height, imageLength, 0, PixelFormat.Bgra, PixelType.UnsignedInt8888Reversed, IntPtr.Zero); if (GLInfo.DebugLabel) GLHelper.ObjectLabel(ObjectLabelIdentifier.Texture, texture.TextureId, $"Texture: {name} ({flags})"); - for (int i = 0; i < images.Length; i++) + for (int i = 0; i < imageLength; i++) { - var image = images[i]; + var image = getImage(i); fixed (uint* pixelPtr = image.GetGlTexturePixels(ShaderVars.PaletteColorMode)) { IntPtr ptr = new(pixelPtr); GL.TexSubImage3D(texture.Target, 0, 0, 0, zoffset: i, image.Width, image.Height, depth: 1, PixelFormat.Bgra, PixelType.UnsignedInt8888Reversed, ptr); } + onUploadImage(i, image); } GL.GenerateMipmap(GenerateMipmapTarget.Texture2DArray); @@ -218,27 +220,25 @@ protected override GLLegacyTexture GenerateTexture(Image image, string name, return texture; } - protected override GLLegacyTexture[] GenerateTextureArray(Image[] images, Dimension dimension, ResourceNamespace resourceNamespace, TextureFlags flags, + protected override GLLegacyTexture[] GenerateTextureArray(Func getImage, int imageLength, Dimension dimension, ResourceNamespace resourceNamespace, TextureFlags flags, TextureContext textureContext, out GLLegacyTexture arrayTexture) { + var textures = new GLLegacyTexture[imageLength]; int textureId = GL.GenTexture(); - arrayTexture = new GLLegacyTexture(textureId, $"Texture Array Length={images.Length} {flags}", dimension, default, resourceNamespace, TextureTarget.Texture2DArray, 0, 0, 0, + arrayTexture = new GLLegacyTexture(textureId, $"Texture Array Length={imageLength} {flags}", dimension, default, resourceNamespace, TextureTarget.Texture2DArray, 0, 0, 0, textureContext: textureContext); - UploadAndSetParameters3D(arrayTexture, images, arrayTexture.Name, resourceNamespace, flags); - - var textures = new GLLegacyTexture[images.Length]; - for (int i = 0; i < images.Length; i++) + var parentArrayTexture = arrayTexture; + UploadAndSetParameters3D(arrayTexture, getImage, imageLength, arrayTexture.Name, resourceNamespace, flags, dimension, (int imageIndex, Image image) => { - var image = images[i]; - var texture = new GLLegacyTexture(textureId, $"Sub Image {i}", dimension, image.Offset, resourceNamespace, TextureTarget.Texture2DArray, + var texture = new GLLegacyTexture(textureId, $"Sub Image {imageIndex}", dimension, image.Offset, resourceNamespace, TextureTarget.Texture2DArray, image.TransparentPixelCount(), image.BlankRowsFromTop, image.BlankRowsFromBottom, ownsTexture: false) { - ArrayIndex = i, - ParentArrayTexture = arrayTexture + ArrayIndex = imageIndex, + ParentArrayTexture = parentArrayTexture }; - textures[i] = texture; - } + textures[imageIndex] = texture; + }); return textures; } From 38ccd34f611846f11a2e7666b1c8df52602402a4 Mon Sep 17 00:00:00 2001 From: Nick Date: Mon, 14 Sep 2026 08:37:33 -0400 Subject: [PATCH 42/49] fix flag --- Core/Render/OpenGL/Texture/GLTextureManager.cs | 1 + Core/Render/OpenGL/Texture/Legacy/LegacyGLTextureManager.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Core/Render/OpenGL/Texture/GLTextureManager.cs b/Core/Render/OpenGL/Texture/GLTextureManager.cs index 3dd20a1a3..0066ad4bb 100644 --- a/Core/Render/OpenGL/Texture/GLTextureManager.cs +++ b/Core/Render/OpenGL/Texture/GLTextureManager.cs @@ -447,6 +447,7 @@ public GLTextureType CreateBrightMapTexture(Image? image, string? name, Resource public GLTextureType CreateTexture(Image? image, string? name, ResourceNamespace resourceNamespace, bool repeatY = true) { + repeatY = resourceNamespace != ResourceNamespace.Sprites && resourceNamespace != ResourceNamespace.Brightmaps && resourceNamespace != ResourceNamespace.Brightmaps && repeatY; var textureTracker = GetTextureTracker(repeatY); GLTextureType? texture; if (name != null) diff --git a/Core/Render/OpenGL/Texture/Legacy/LegacyGLTextureManager.cs b/Core/Render/OpenGL/Texture/Legacy/LegacyGLTextureManager.cs index d7e3597c2..49e380a21 100644 --- a/Core/Render/OpenGL/Texture/Legacy/LegacyGLTextureManager.cs +++ b/Core/Render/OpenGL/Texture/Legacy/LegacyGLTextureManager.cs @@ -139,7 +139,7 @@ private unsafe void UploadAndSetParameters3D(GLLegacyTexture texture, Func Date: Mon, 14 Sep 2026 08:42:33 -0400 Subject: [PATCH 43/49] fix for non bucketed textures --- .../Renderers/Legacy/World/Entities/EntityProgram.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityProgram.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityProgram.cs index 7ad8f40ac..7845e0411 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityProgram.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityProgram.cs @@ -142,9 +142,9 @@ void main() vec3 offsetXY = vec3(posMoveDir.xy * offsetXYOption, 0); vec3 interpolatedPos = mix(prevPos, pos, timeFrac); - float baseSize = float(textureSize(boundTexture, 0).x); - float calcU = textureDim.x / baseSize; - float calcV = textureDim.y / baseSize; + ivec2 boundTextureSize = textureSize(boundTexture, 0).xy; + float calcU = textureDim.x / float(boundTextureSize.x); + float calcV = textureDim.y / float(boundTextureSize.y); ${MinMaxPos} centerPosFrag = interpolatedPos; @@ -203,8 +203,8 @@ private string GetMinMaxPos() } return @" - float worldWidth = calcU * baseSize; - float worldHeight = calcV * baseSize; + float worldWidth = calcU * boundTextureSize.x; + float worldHeight = calcV * boundTextureSize.y; zPosFrag = interpolatedPos.z; interpolatedPos.z += offsetZ; vec3 minPos = interpolatedPos - offsetXY; From 3a5819e3282d0c64153c43b6a845c04b39b597b2 Mon Sep 17 00:00:00 2001 From: Nick Date: Mon, 14 Sep 2026 09:44:21 -0400 Subject: [PATCH 44/49] pack array index / texture dim index to single float --- .../Renderers/Legacy/World/Entities/EntityProgram.cs | 9 +++++---- .../Renderers/Legacy/World/Entities/EntityRenderer.cs | 3 +-- .../Renderers/Legacy/World/Entities/EntityVertex.cs | 3 --- .../OpenGL/Renderers/Legacy/World/VertexOptions.cs | 7 +++++++ Core/Resources/SpriteRotation.cs | 1 + 5 files changed, 14 insertions(+), 9 deletions(-) diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityProgram.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityProgram.cs index 7845e0411..e4a2b79d1 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityProgram.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityProgram.cs @@ -73,7 +73,6 @@ protected override string VertexShader() => @" layout(location = 3) in float offsetXYZ; layout(location = 4) in float renderOptions; layout(location = 5) in float textureIndex; - layout(location = 6) in float textureDimIndex; flat out float lightLevelFrag; flat out float alphaFrag; @@ -127,13 +126,16 @@ void main() intOptions = floatBitsToInt(offsetXYZ); float offsetXYOption = float((intOptions >> 16) & 0x3FFF); - float offsetZ = float(intOptions & 0x3FFF); float offsetXYSign = float(((intOptions >> 31) & 1) > 0); float offsetZSign = float(((intOptions >> 30) & 1) > 0); offsetXYOption = mix(offsetXYOption, -offsetXYOption, offsetXYSign); offsetZ = mix(offsetZ, -offsetZ, offsetZSign); - uvec2 textureDim = texelFetch(spriteTextureDimensionsTexture, int(textureDimIndex)).rg; + + intOptions = floatBitsToInt(textureIndex); + boundTextureIndex = (intOptions >> 16); + int textureDimIndex = intOptions & 0xFFFF; + uvec2 textureDim = texelFetch(spriteTextureDimensionsTexture, textureDimIndex).rg; textureWidthFrag = textureDim.x; ${SectorColorMapVertexFunction} @@ -179,7 +181,6 @@ void main() minPosFrag = minPos; maxPosFrag = maxPos; renderDistSquared = distSquared(viewPos.xy, interpolatedPos.xy); - boundTextureIndex = textureIndex; } " .Replace("${SectorColorMapVertexFunction}", SectorColorMap.VertexFunction("lightIndexInt", "sectorColorMapIndexFrag", "sectorFogColorFrag")) diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityRenderer.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityRenderer.cs index 4951dee25..53c8cb5e0 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityRenderer.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityRenderer.cs @@ -299,8 +299,7 @@ public void RenderEntity(Entity entity, in Vec2D position, int renderIndex) vertex.SurfaceOptions = VertexOptions.EntityPackSurface(alpha, fuzz, flipU, colorMapIndex, lightLevel); vertex.RenderOptions = VertexOptions.EntityPackRender( Renderer.GetLightBufferIndex(sector, WorldStatic.Sector3D && sector.Sectors3D.Length > 0 ? LightBufferType.Wall : LightBufferType.Floor), renderIndex); - vertex.TextureIndex = texture.ArrayIndex; - vertex.TextureDimIndex = spriteRotation.Texture.Index; + vertex.TextureIndex = VertexOptions.EntityPackTextureIndex(texture.ArrayIndex, spriteRotation.TextureIndex); if (entity.Definition.Flags.SpawnCeiling() && m_vanillaRender) { diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityVertex.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityVertex.cs index 69c32694b..e34982c47 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityVertex.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityVertex.cs @@ -25,7 +25,4 @@ public struct EntityVertex [VertexAttribute(divisor: 1)] public float TextureIndex; - - [VertexAttribute(divisor: 1)] - public float TextureDimIndex; } diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/VertexOptions.cs b/Core/Render/OpenGL/Renderers/Legacy/World/VertexOptions.cs index 510b8595e..32e6ab675 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/VertexOptions.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/VertexOptions.cs @@ -63,4 +63,11 @@ public static unsafe float EntityPackXYZ(int offsetXY, int offsetZ) int packed = (offsetXYSign << 31) | (offsetZSign << 30) | (offsetXY << 16) | offsetZ; return *(float*)&packed; } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe float EntityPackTextureIndex(int arrayIndex, int textureDimensionIndex) + { + int packed = (arrayIndex << 16) | textureDimensionIndex; + return *(float*)&packed; + } } diff --git a/Core/Resources/SpriteRotation.cs b/Core/Resources/SpriteRotation.cs index 3ad336b3d..7b42a2f72 100644 --- a/Core/Resources/SpriteRotation.cs +++ b/Core/Resources/SpriteRotation.cs @@ -9,6 +9,7 @@ public class SpriteRotation(Texture texture, bool mirror, bool brightmapNoFullbr public Texture Texture = texture; public bool Mirror = mirror; public int FlipU = mirror ? 1 : 0; + public int TextureIndex = texture.Index; public object? RenderStore; public object? BrightmapRenderStore; public bool BrightmapNoFullbright = brightmapNoFullbright; From dbce4bf27c1a99671d0fb7fbd392bafa0b64e633 Mon Sep 17 00:00:00 2001 From: Nick Date: Mon, 14 Sep 2026 14:14:12 -0400 Subject: [PATCH 45/49] remove using --- Core/Render/Renderer.World.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/Core/Render/Renderer.World.cs b/Core/Render/Renderer.World.cs index 8f11f00f5..d0d7425c0 100644 --- a/Core/Render/Renderer.World.cs +++ b/Core/Render/Renderer.World.cs @@ -5,7 +5,6 @@ using Helion.Render.OpenGL.Renderers.Legacy.World.Geometry.Static; using Helion.Render.OpenGL.Renderers.Legacy.World.Shader; using Helion.Render.OpenGL.Textures; -using Helion.Util; using Helion.Util.Assertion; using Helion.World; using Helion.World.Geometry.Lines; @@ -14,7 +13,6 @@ using Helion.World.Geometry.Walls; using OpenTK.Graphics.OpenGL; using System; -using System.Linq; using static Helion.Util.Constants; namespace Helion.Render; From bd1af95df8fe08ed3fa5d2fdfac83b56478ed5fb Mon Sep 17 00:00:00 2001 From: Nick Date: Mon, 14 Sep 2026 14:25:04 -0400 Subject: [PATCH 46/49] correct healthbars --- .../Renderers/Legacy/World/Entities/EntityProgram.cs | 9 ++++++--- .../Renderers/Legacy/World/Entities/EntityRenderer.cs | 1 + 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityProgram.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityProgram.cs index e4a2b79d1..19248f81a 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityProgram.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityProgram.cs @@ -144,9 +144,6 @@ void main() vec3 offsetXY = vec3(posMoveDir.xy * offsetXYOption, 0); vec3 interpolatedPos = mix(prevPos, pos, timeFrac); - ivec2 boundTextureSize = textureSize(boundTexture, 0).xy; - float calcU = textureDim.x / float(boundTextureSize.x); - float calcV = textureDim.y / float(boundTextureSize.y); ${MinMaxPos} centerPosFrag = interpolatedPos; @@ -195,6 +192,8 @@ private string GetMinMaxPos() if (this is EntityHealthBarProgram) { return @" + float calcU = 1; + float calcV = 1; zPosFrag = interpolatedPos.z; interpolatedPos.z += offsetZ; vec3 minPos = interpolatedPos; @@ -204,6 +203,10 @@ private string GetMinMaxPos() } return @" + ivec2 boundTextureSize = textureSize(boundTexture, 0).xy; + float calcU = textureDim.x / float(boundTextureSize.x); + float calcV = textureDim.y / float(boundTextureSize.y); + float worldWidth = calcU * boundTextureSize.x; float worldHeight = calcV * boundTextureSize.y; zPosFrag = interpolatedPos.z; diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityRenderer.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityRenderer.cs index 53c8cb5e0..1c709dffe 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityRenderer.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityRenderer.cs @@ -343,6 +343,7 @@ private void RenderHealthBar(Entity entity, GLLegacyTexture texture, float offse vertex.Pos = entityVertex.Pos; vertex.PrevPos = entityVertex.PrevPos; vertex.OffsetXYZ = VertexOptions.EntityPackXYZ(0, offset); + vertex.TextureIndex = 0; array.SetLength(array.Length + 1); } From 2b18d5ecdc849b2cf002b930894bd546669c41c2 Mon Sep 17 00:00:00 2001 From: Nick Date: Mon, 14 Sep 2026 15:04:21 -0400 Subject: [PATCH 47/49] refactor to pack dimensions with array index into single float property instead of looking up in texture --- Core/Render/BindTextures.cs | 1 - .../Legacy/World/Entities/EntityProgram.cs | 13 +++------- .../Legacy/World/Entities/EntityRenderer.cs | 5 ++-- .../Legacy/World/Entities/EntityVertex.cs | 2 +- .../Renderers/Legacy/World/VertexOptions.cs | 5 ++-- Core/Render/Renderer.World.cs | 25 ------------------- Core/Render/Renderer.cs | 6 ----- 7 files changed, 10 insertions(+), 47 deletions(-) diff --git a/Core/Render/BindTextures.cs b/Core/Render/BindTextures.cs index b84b206df..8f56195e2 100644 --- a/Core/Render/BindTextures.cs +++ b/Core/Render/BindTextures.cs @@ -19,5 +19,4 @@ public static class BindTextures public const TextureUnit LineHeights = TextureUnit.Texture11; public const TextureUnit BrightmapTexture = TextureUnit.Texture12; public const TextureUnit SectorFog = TextureUnit.Texture13; - public const TextureUnit SpriteTextureDimensions = TextureUnit.Texture14; } diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityProgram.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityProgram.cs index 19248f81a..1e4badc56 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityProgram.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityProgram.cs @@ -21,7 +21,6 @@ public class EntityProgram : RenderProgramBase private readonly int m_mapDataTextureLocation; private readonly int m_lineHeightsTextureLocation; private readonly int m_colorClampLocation; - private readonly int m_spriteTextureDimensionsTextureLocation; public EntityProgram(string name) : base($"Entity - {name}") { @@ -39,14 +38,12 @@ public EntityProgram(string name) : base($"Entity - {name}") m_fuzzTextureLocation = Uniforms.GetLocation("fuzzTexture"); m_opaqueTextureLocation = Uniforms.GetLocation("opaqueTexture"); m_colorClampLocation = Uniforms.GetLocation("colorClamp"); - m_spriteTextureDimensionsTextureLocation = Uniforms.GetLocation("spriteTextureDimensionsTexture"); } public void FuzzTexture(TextureUnit unit) => ProgramUniforms.Set(unit, m_fuzzTextureLocation); public void OpaqueTexture(TextureUnit unit) => ProgramUniforms.Set(unit, m_opaqueTextureLocation); public void MapDataTexture(TextureUnit unit) => ProgramUniforms.Set(unit, m_mapDataTextureLocation); public void LineHeightsTexture(TextureUnit unit) => ProgramUniforms.Set(unit, m_lineHeightsTextureLocation); - public void SpriteTextureDimensionsTexture(TextureUnit unit) => ProgramUniforms.Set(unit, m_spriteTextureDimensionsTextureLocation); public void FuzzFrac(float frac) => ProgramUniforms.Set(frac, m_fuzzFracLocation); public void ViewRightNormal(Vec2F viewRightNormal) => ProgramUniforms.Set(viewRightNormal, m_viewRightNormalLocation); public void PrevViewRightNormal(Vec2F viewRightNormal) => ProgramUniforms.Set(viewRightNormal, m_prevViewRightNormalLocation); @@ -72,7 +69,7 @@ protected override string VertexShader() => @" layout(location = 2) in vec3 prevPos; layout(location = 3) in float offsetXYZ; layout(location = 4) in float renderOptions; - layout(location = 5) in float textureIndex; + layout(location = 5) in float textureInfo; flat out float lightLevelFrag; flat out float alphaFrag; @@ -104,7 +101,6 @@ protected override string VertexShader() => @" uniform sampler2DArray boundTexture; uniform samplerBuffer sectorColormapTexture; uniform samplerBuffer sectorFogTexture; - uniform usamplerBuffer spriteTextureDimensionsTexture; float distSquared(vec2 v1, vec2 v2) { vec2 length = v1.xy - v2.xy; @@ -132,10 +128,9 @@ void main() offsetXYOption = mix(offsetXYOption, -offsetXYOption, offsetXYSign); offsetZ = mix(offsetZ, -offsetZ, offsetZSign); - intOptions = floatBitsToInt(textureIndex); - boundTextureIndex = (intOptions >> 16); - int textureDimIndex = intOptions & 0xFFFF; - uvec2 textureDim = texelFetch(spriteTextureDimensionsTexture, textureDimIndex).rg; + intOptions = floatBitsToInt(textureInfo); + boundTextureIndex = (intOptions >> 20) & 0xFFF; + ivec2 textureDim = ivec2((intOptions >> 10) & 0x3FF, intOptions & 0x3FF); textureWidthFrag = textureDim.x; ${SectorColorMapVertexFunction} diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityRenderer.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityRenderer.cs index 1c709dffe..7a30141dc 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityRenderer.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityRenderer.cs @@ -299,7 +299,7 @@ public void RenderEntity(Entity entity, in Vec2D position, int renderIndex) vertex.SurfaceOptions = VertexOptions.EntityPackSurface(alpha, fuzz, flipU, colorMapIndex, lightLevel); vertex.RenderOptions = VertexOptions.EntityPackRender( Renderer.GetLightBufferIndex(sector, WorldStatic.Sector3D && sector.Sectors3D.Length > 0 ? LightBufferType.Wall : LightBufferType.Floor), renderIndex); - vertex.TextureIndex = VertexOptions.EntityPackTextureIndex(texture.ArrayIndex, spriteRotation.TextureIndex); + vertex.TextureInfo = VertexOptions.EntityPackTextureInfo(texture.ArrayIndex, spriteRotation.TextureDimension.Width, spriteRotation.TextureDimension.Height); if (entity.Definition.Flags.SpawnCeiling() && m_vanillaRender) { @@ -343,7 +343,7 @@ private void RenderHealthBar(Entity entity, GLLegacyTexture texture, float offse vertex.Pos = entityVertex.Pos; vertex.PrevPos = entityVertex.PrevPos; vertex.OffsetXYZ = VertexOptions.EntityPackXYZ(0, offset); - vertex.TextureIndex = 0; + vertex.TextureInfo = 0; array.SetLength(array.Length + 1); } @@ -419,7 +419,6 @@ private void SetUniforms(EntityProgram program, RenderInfo renderInfo) program.PlaneClipTexture(BindTextures.PlaneClipTexture); program.MapDataTexture(BindTextures.MapLineData); program.LineHeightsTexture(BindTextures.LineHeights); - program.SpriteTextureDimensionsTexture(BindTextures.SpriteTextureDimensions); } public void RenderOpaque(RenderInfo renderInfo) diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityVertex.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityVertex.cs index e34982c47..d97ae2682 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityVertex.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Entities/EntityVertex.cs @@ -24,5 +24,5 @@ public struct EntityVertex public float RenderOptions; [VertexAttribute(divisor: 1)] - public float TextureIndex; + public float TextureInfo; } diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/VertexOptions.cs b/Core/Render/OpenGL/Renderers/Legacy/World/VertexOptions.cs index 32e6ab675..59a3f18ab 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/VertexOptions.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/VertexOptions.cs @@ -65,9 +65,10 @@ public static unsafe float EntityPackXYZ(int offsetXY, int offsetZ) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe float EntityPackTextureIndex(int arrayIndex, int textureDimensionIndex) + public static unsafe float EntityPackTextureInfo(int arrayIndex, int paddedWidth, int paddedHeight) { - int packed = (arrayIndex << 16) | textureDimensionIndex; + // 12, 10, and 10 bits + int packed = ((arrayIndex & 0xFFF) << 20) | ((paddedWidth & 0x3FF) << 10) | ((paddedHeight & 0x3FF)); return *(float*)&packed; } } diff --git a/Core/Render/Renderer.World.cs b/Core/Render/Renderer.World.cs index d0d7425c0..aeee833a0 100644 --- a/Core/Render/Renderer.World.cs +++ b/Core/Render/Renderer.World.cs @@ -40,7 +40,6 @@ enum BlockSide private GLBufferTextureStorage? m_colorMapBuffer; private GLBufferTextureStorage? m_mapDataBuffer; private GLBufferTextureStorage? m_lineHeightsBuffer; - private GLBufferTextureStorage? m_spriteTextureDimensionsBuffer; private bool m_sectorFog; private bool m_sectorColor; @@ -109,30 +108,6 @@ public unsafe void UpdateToNewWorld(IWorld world) m_sectorColorMapsBuffer = InitLightBuffer(world, alloc, m_sectorColorMapsBuffer, InitSectorColorMap, "Sector colormaps", SizedInternalFormat.Rgba32f, GLBufferTextureStorage.FourComponentLength); m_sectorFogBuffer = InitLightBuffer(world, alloc, m_sectorFogBuffer, InitSectorFogBuffer, "Sector fog", SizedInternalFormat.Rgba32f, GLBufferTextureStorage.FourComponentLength); SetLineHeights(world, alloc); - - if (m_spriteTextureDimensionsBuffer == null) - { - var bufferLength = (m_worldRenderer.SpriteTextureBuckets.MaxTextureIndex + 1) * 2; - m_spriteTextureDimensionsBuffer = new("Sprite Texture Dimensions", new uint[bufferLength], SizedInternalFormat.Rg32ui, false); - m_spriteTextureDimensionsBuffer.Map(data => - { - uint* buffer = (uint*)data.ToPointer(); - foreach (var bucket in m_worldRenderer.SpriteTextureBuckets.Buckets) - { - for (int i = 0; i < bucket.Textures.Length; i++) - { - var texture = bucket.Textures[i]; - if (texture.Image == null) - continue; - - var index = texture.Index * 2; - Assert.Precondition(index + 1 < bufferLength, $"Invalid texture index {texture.Index}"); - buffer[index] = (uint)texture.Image.Dimension.Width; - buffer[index + 1] = (uint)texture.Image.Dimension.Height; - } - } - }); - } } private unsafe void SetLineHeights(IWorld world, bool alloc) diff --git a/Core/Render/Renderer.cs b/Core/Render/Renderer.cs index df813be74..570e28bbe 100644 --- a/Core/Render/Renderer.cs +++ b/Core/Render/Renderer.cs @@ -552,7 +552,6 @@ public void Render(RenderCommands renderCommands) BindLightBuffer(); BindMapDataBuffer(); BindLineHeightsBuffer(); - BindSpriteTextureDimensionsBuffer(); // This has to be tracked beyond just the rendering command, and it // also prevents something from going terribly wrong if there is no @@ -659,11 +658,6 @@ private void BindLineHeightsBuffer() m_lineHeightsBuffer?.BindTexture(BindTextures.LineHeights); } - private void BindSpriteTextureDimensionsBuffer() - { - m_spriteTextureDimensionsBuffer?.BindTexture(BindTextures.SpriteTextureDimensions); - } - public void PerformThrowableErrorChecks() { if (m_config.Developer.Render.Debug) From 682acc4cc54eb2a03ff71b5de6b4f15028156c69 Mon Sep 17 00:00:00 2001 From: Nick Date: Mon, 14 Sep 2026 19:33:51 -0400 Subject: [PATCH 48/49] limit bucket sizes to MaxArrayLayers --- Client/Client.cs | 1 - Core/Graphics/Image.cs | 2 +- Core/Render/OpenGL/Context/GLInfo.cs | 7 ++++ .../Texture/Legacy/GLTextureArrayBuilder.cs | 40 ++++++++++++++++--- Core/Render/Renderer.cs | 1 + 5 files changed, 44 insertions(+), 7 deletions(-) diff --git a/Client/Client.cs b/Client/Client.cs index 3c62599e4..7b9442fc7 100755 --- a/Client/Client.cs +++ b/Client/Client.cs @@ -38,7 +38,6 @@ using System.IO; using System.Reflection; using System.Runtime.InteropServices; -using System.Threading; using System.Threading.Tasks; using static Helion.Util.Assertion.Assert; diff --git a/Core/Graphics/Image.cs b/Core/Graphics/Image.cs index c837cb674..6df56a2ae 100644 --- a/Core/Graphics/Image.cs +++ b/Core/Graphics/Image.cs @@ -410,7 +410,7 @@ public void SetPixel(int x, int y, Color color, Palette palette) public void CopyPixelsFrom(Image image) { - Assert.Precondition(image.ImageType == ImageType, "Image type mismatch"); + Precondition(!(image.Indices.Length > 0 && Indices.Length == 0), "Image type mismatch"); var width = Math.Min(image.Dimension.Width, Dimension.Width); var height = Math.Min(image.Dimension.Height, Dimension.Height); diff --git a/Core/Render/OpenGL/Context/GLInfo.cs b/Core/Render/OpenGL/Context/GLInfo.cs index 351b47db7..35ae03557 100644 --- a/Core/Render/OpenGL/Context/GLInfo.cs +++ b/Core/Render/OpenGL/Context/GLInfo.cs @@ -32,11 +32,18 @@ public static class GLInfo public static bool MapPersistentBitSupported = true; public static bool MemoryBarrierSupported = true; public static bool DebugLabel; + public static int MaxArrayLayers; static GLInfo() { Renderer = GL.GetString(StringName.Renderer); ShadingVersion = GL.GetString(StringName.ShadingLanguageVersion); Vendor = GL.GetString(StringName.Vendor); + MaxArrayLayers = GetMaxArrayLayers(); + } + public static int GetMaxArrayLayers() + { + GL.GetInteger(GetPName.MaxArrayTextureLayers, out var maxLayers); + return maxLayers; } } diff --git a/Core/Render/OpenGL/Texture/Legacy/GLTextureArrayBuilder.cs b/Core/Render/OpenGL/Texture/Legacy/GLTextureArrayBuilder.cs index 1db197e87..e57b65b40 100644 --- a/Core/Render/OpenGL/Texture/Legacy/GLTextureArrayBuilder.cs +++ b/Core/Render/OpenGL/Texture/Legacy/GLTextureArrayBuilder.cs @@ -1,6 +1,6 @@ using Helion.Geometry; +using Helion.Render.OpenGL.Context; using Helion.Resources; -using Helion.Resources.Textures; using Helion.Util; using Helion.Util.Assertion; using Helion.Util.Container; @@ -54,7 +54,7 @@ public TextureBuckets BuildSprites(DynamicArray spriteDefiniti { var maxIndex = 0; var spriteTextures = new DynamicArray(); - var spriteTextureHandles = new HashSet(); + var spriteTextureHandles = new HashSet(spriteDefinitions.Length); foreach (var spriteDefinition in spriteDefinitions) { if (spriteDefinition == null) @@ -86,8 +86,6 @@ public TextureBuckets BuildSprites(DynamicArray spriteDefiniti BuildTextureArray(bucket.Textures.Data.AsSpan(0, bucket.Textures.Length), TextureContext.WorldSprites, TextureFlags.ClampX | TextureFlags.ClampY, bucket.Dimension); } - // TODO overflow bucket ^1 - foreach (var spriteDefinition in spriteDefinitions) { if (spriteDefinition == null) @@ -212,7 +210,39 @@ private static TextureBucket[] CreateTextureBuckets(DynamicArray(); + foreach (var bucket in buckets) + { + if (bucket.Textures.Length <= GLInfo.MaxArrayLayers) + { + finalizedBuckets.Add(bucket); + continue; + } + + var remaining = bucket.Textures.Length; + var offset = 0; + + while (remaining > 0) + { + var count = MathHelper.Min(remaining, GLInfo.MaxArrayLayers); + var newBucket = new TextureBucket(bucket.Dimension, new(count)); + for (int i = 0; i < count; i++) + newBucket.Textures.Add(bucket.Textures[offset + i]); + + finalizedBuckets.Add(newBucket); + + offset += count; + remaining -= count; + } + } + + return [.. finalizedBuckets]; } public static int GetBucketIndex(Dimension dimension, int baseSize, int bucketCount) diff --git a/Core/Render/Renderer.cs b/Core/Render/Renderer.cs index 570e28bbe..87e19efe5 100644 --- a/Core/Render/Renderer.cs +++ b/Core/Render/Renderer.cs @@ -682,6 +682,7 @@ private static void PrintGLInfo() Log.Info("GL_ARB_clip_control {0}", GLInfo.ClipControlSupported); Log.Info("GL_ARB_shader_image_load_store {0}", GLInfo.MemoryBarrierSupported); Log.Info("GL_ARB_buffer_storage {0}", GLInfo.MapPersistentBitSupported); + Log.Info("MaxArrayLayers {0}", GLInfo.MaxArrayLayers); InfoPrinted = true; } From 57eb79bed6fc09f1a32c6474628c453ebe3392fb Mon Sep 17 00:00:00 2001 From: Nick Date: Mon, 14 Sep 2026 20:18:26 -0400 Subject: [PATCH 49/49] update release notes --- RELEASENOTES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 19bcbb6c1..ed828aa42 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -6,6 +6,7 @@ - Automatic blood color and fuzz blood options. - Initial support for MacOS ARM64 builds. - Add weapon fire bob option. +- Pack textures into 2D arrays for faster rendering speeds, especially on older iGPUs. ## Bug Fixes: - Do not clear player velocity when slide movement fails. Matches vanilla doom behavior where players can move out of lines with enough momentum to pass clip checks. (Fixes Hellevator MAP06 start)