diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/GeometryRenderer.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/GeometryRenderer.cs index bda131282..ae0889a24 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/GeometryRenderer.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Geometry/GeometryRenderer.cs @@ -112,6 +112,7 @@ public GeometryRenderer(IConfig config, ArchiveCollection archiveCollection, Leg m_fakeSideScrollData = new(); m_fakeSide = new(0, default, m_fakeWall, m_fakeWall, m_fakeWall, m_sliceSector) { + NoCache = true, ScrollData = m_fakeSideScrollData }; m_emptyTraverseSide = new(0, default, m_fakeWall, m_fakeWall, m_fakeWall, m_emptyTraverseSector); @@ -119,6 +120,7 @@ public GeometryRenderer(IConfig config, ArchiveCollection archiveCollection, Leg m_fogWall.TextureHandle = TextureManager.BlackTextureIndex; m_fogSide = new(0, default, m_fogWall, m_fogWall, m_fogWall, null!) { + NoCache = true, RenderDataStyle = RenderDataStyle.FogBarrier }; m_fogSide.Flags.WrapMidTex = true; @@ -744,7 +746,7 @@ public void RenderOneSided(Side side, bool isFront, out DynamicVertex[]? vertice WallVertices wall = default; texture = m_glTextureManager?.GetTexture(side.Middle.TextureHandle) ?? TestTexture; var brightmapTexture = m_glTextureManager?.GetBrightmapTexture(side.Middle.TextureHandle); - var data = m_vertexLookup[side.Id]; + var data = GetCachedSide(m_vertexLookup, side); renderSector ??= side.Sector.GetRenderSector(m_transferHeightsView); lightLevelSector ??= renderSector; @@ -778,7 +780,7 @@ public void RenderOneSided(Side side, bool isFront, out DynamicVertex[]? vertice else SetWallVertices(data, wall, GetLightLevelAdd(side), lightIndex, colorMapIndex, GetWallLightLevel(side, side.Middle), side.Line.Id, WallLocation.Middle, addAlpha: addAlpha, alpha: side.Alpha); - m_vertexLookup[side.Id] = data; + SetCachedSide(m_vertexLookup, side, data); } if (m_buffer) @@ -792,6 +794,19 @@ public void RenderOneSided(Side side, bool isFront, out DynamicVertex[]? vertice vertices = data; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static DynamicVertex[]? GetCachedSide(DynamicVertex[]?[] lookup, Side side) + { + return side.NoCache ? null : lookup[side.Id]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SetCachedSide(DynamicVertex[]?[] lookup, Side side, DynamicVertex[] data) + { + if (!side.NoCache) + lookup[side.Id] = data; + } + private static GeometryType GetGeometryType(RenderDataStyle style, GeometryType baseType) { if (style != RenderDataStyle.Normal) @@ -1113,7 +1128,7 @@ public void RenderTwoSidedLower(Side facingSide, Side otherSide, Sector facingSe } else { - DynamicVertex[]? data = m_vertexLowerLookup[facingSide.Id]; + var data = GetCachedSide(m_vertexLowerLookup, facingSide); if (facingSide.OffsetChanged || m_sectorChangedLine || data == null) { @@ -1129,7 +1144,7 @@ public void RenderTwoSidedLower(Side facingSide, Side otherSide, Sector facingSe else SetWallVertices(data, wall, GetLightLevelAdd(facingSide), lightIndex, colorMapIndex, GetWallLightLevel(facingSide, facingSide.Lower), facingSide.Line.Id, WallLocation.Lower); - m_vertexLowerLookup[facingSide.Id] = data; + SetCachedSide(m_vertexLookup, facingSide, data); } if (m_buffer) @@ -1236,7 +1251,7 @@ public void RenderTwoSidedUpper(Side facingSide, Side otherSide, Sector facingSe return; } - DynamicVertex[]? data = m_vertexUpperLookup[facingSide.Id]; + var data = GetCachedSide(m_vertexUpperLookup, facingSide); if (facingSide.OffsetChanged || m_sectorChangedLine || data == null) { @@ -1248,7 +1263,7 @@ public void RenderTwoSidedUpper(Side facingSide, Side otherSide, Sector facingSe else SetWallVertices(data, wall, GetLightLevelAdd(facingSide), lightIndex, colorMapIndex, GetWallLightLevel(facingSide, facingSide.Upper), facingSide.Line.Id, WallLocation.Upper); - m_vertexUpperLookup[facingSide.Id] = data; + SetCachedSide(m_vertexUpperLookup, facingSide, data); } if (m_buffer) @@ -1392,14 +1407,11 @@ public void RenderTwoSidedMiddle(Side facingSide, Side otherSide, Sector facingS GLLegacyTexture? brightmapTexture = m_glTextureManager.GetBrightmapTexture(middleWall.TextureHandle, repeatY: facingSide.Flags.WrapMidTex); var line = facingSide.Line; - float alpha = m_config.Render.TextureTransparency ? Math.Clamp(line.Alpha, 0, 1) : 1.0f; - DynamicVertex[]? data = m_vertexLookup[facingSide.Id]; + var alpha = m_config.Render.TextureTransparency ? Math.Clamp(line.Alpha, 0, 1) : 1.0f; + var data = GetCachedSide(m_vertexLookup, facingSide); var geometryType = alpha < 1 ? GeometryType.Translucent : GeometryType.TwoSidedMiddleWall; if (facingSide == m_fogSide) - { - data = null; geometryType = GeometryType.FogBarrier; - } if (facingSide.OffsetChanged || m_sectorChangedLine || data == null) { @@ -1437,8 +1449,7 @@ public void RenderTwoSidedMiddle(Side facingSide, Side otherSide, Sector facingS else SetWallVertices(data, wall, GetLightLevelAdd(facingSide), lightIndex, colorMapIndex, GetWallLightLevel(facingSide, facingSide.Middle), line.Id, WallLocation.None, alpha, addAlpha: 0); - if (facingSide != m_fogSide) - m_vertexLookup[facingSide.Id] = data; + SetCachedSide(m_vertexLookup, facingSide, data); line.RenderSegStart = saveStart; line.RenderSegEnd = saveEnd; } diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/Shader/VertexFunction.cs b/Core/Render/OpenGL/Renderers/Legacy/World/Shader/VertexFunction.cs index 76a457963..e87a2e02f 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/Shader/VertexFunction.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/Shader/VertexFunction.cs @@ -47,7 +47,7 @@ public static class VertexFunction @" int colorMapAndLightLevel = floatBitsToInt(colorMapIndex); vertexLightLevelFrag = float(colorMapAndLightLevel & 0xFF); - colorMapIndexFrag = float((colorMapAndLightLevel >> 10) & 0xFFFF); + colorMapIndexFrag = float((colorMapAndLightLevel >> 10) & 0x3FFFFF); uvFlags = float((colorMapAndLightLevel >> 8) & 0x3);"; public static string LightLevelAddAndMapIdSet => diff --git a/Core/Render/OpenGL/Renderers/Legacy/World/VertexOptions.cs b/Core/Render/OpenGL/Renderers/Legacy/World/VertexOptions.cs index 441c2ae9c..8e99a495c 100644 --- a/Core/Render/OpenGL/Renderers/Legacy/World/VertexOptions.cs +++ b/Core/Render/OpenGL/Renderers/Legacy/World/VertexOptions.cs @@ -16,8 +16,8 @@ public static unsafe float World(int topLeft, float alpha, int addAlpha, int upp // UvFlags only required for walls public static unsafe float ColorMapIndex(int colorMapIndex, int vertexLightLevel, UvFlags uvFlags = UvFlags.Normal) { - // colorMapIndex 16 bits, uvFlags 2 bits, vertexLightLevel 8 bits (6 bits free) - int packed = ((colorMapIndex & 0xFFFF) << 10) | (((int)uvFlags & 0x3) << 8) | ((vertexLightLevel) & 0xFF); + // colorMapIndex 22 bits, uvFlags 2 bits, vertexLightLevel 8 bits + int packed = ((colorMapIndex & 0x3FFFFF) << 10) | (((int)uvFlags & 0x3) << 8) | (vertexLightLevel & 0xFF); return *(float*)&packed; } diff --git a/Core/World/Geometry/Sectors/Sector3D.cs b/Core/World/Geometry/Sectors/Sector3D.cs index 3def3d0d2..13a256a37 100644 --- a/Core/World/Geometry/Sectors/Sector3D.cs +++ b/Core/World/Geometry/Sectors/Sector3D.cs @@ -262,7 +262,7 @@ public static void SetHeights3D(Sector sector) var overlapLight = false; if (i > 0 && lastPlane3D.Sector3D != null && lastPlane3D.ControlPlane.Z == plane3D.Plane.Z) { - if (lastPlane3D.Face == plane3D.Face) + if (lastPlane3D.Face == plane3D.Face && !sector3D.ShouldRenderInsideWalls) { // Flag previous plane not to render since this one takes precedence var keepPlane = (SectorPlanes)(lastPlane3D.Face + 1); @@ -656,7 +656,12 @@ private bool ShouldClipSector3D(Sector3D other, bool clipSolid = false, bool cli return false; if (RenderDataStyle != RenderDataStyle.Normal && other.RenderDataStyle != RenderDataStyle.Normal) + { + if ((Alpha == 0 && other.Alpha != 0) || (Alpha != 0 && other.Alpha == 0)) + return false; + return true; + } if (ClipStyle != other.ClipStyle && (IsLightTransfer || other.IsLightTransfer)) return false; diff --git a/Core/World/Geometry/Sides/Side.cs b/Core/World/Geometry/Sides/Side.cs index d5bc78081..f95a5fcbe 100644 --- a/Core/World/Geometry/Sides/Side.cs +++ b/Core/World/Geometry/Sides/Side.cs @@ -48,6 +48,7 @@ public sealed class Side public bool IsFront; public bool FogCleared; + public bool NoCache; public Side? PartnerSide; public SideScrollData? ScrollData; diff --git a/Tests/Resources/sector3d-map.zip b/Tests/Resources/sector3d-map.zip index 3d70ccf8c..1d6c3bd36 100644 Binary files a/Tests/Resources/sector3d-map.zip and b/Tests/Resources/sector3d-map.zip differ diff --git a/Tests/Unit/GameAction/3DSector/Sector3D_Map.cs b/Tests/Unit/GameAction/3DSector/Sector3D_Map.cs index eff9e7dce..81fd540de 100644 --- a/Tests/Unit/GameAction/3DSector/Sector3D_Map.cs +++ b/Tests/Unit/GameAction/3DSector/Sector3D_Map.cs @@ -191,6 +191,26 @@ public void OverlappingAlphaWalls() wallHeights = left3D.WallHeights; left3D.CalculateWallHeights(GameActions.GetLine(World, 307).Front, out newWallHeights).Should().BeFalse(); } + + [Fact(DisplayName = "Overlapping alpha walls render when other is zero")] + public void OverlappingAlphaWallsZeroAlpha() + { + var innerSector = GameActions.GetSector(World, 225); + var outerSector = GameActions.GetSector(World, 224); + var inner3D = innerSector.Sectors3D[0]; + var outer3D = outerSector.Sectors3D[0]; + inner3D.RenderDataStyle.Should().Be(RenderDataStyle.Translucent); + outer3D.RenderDataStyle.Should().Be(RenderDataStyle.Translucent); + inner3D.Alpha.Should().BeApproximately(0.5f, 1); + outer3D.Alpha.Should().Be(0f); + + inner3D.CalculateWallHeights(GameActions.GetLine(World, 837).Front, out var newWallHeights).Should().BeTrue(); + newWallHeights.TopZ.Should().Be(32); + newWallHeights.BottomZ.Should().Be(0); + + outer3D.Alpha = 1f; + inner3D.CalculateWallHeights(GameActions.GetLine(World, 837).Front, out _).Should().BeFalse(); + } [Fact(DisplayName = "Partially overlapping non-solid walls")] public void PartiallyOverlappingNonSolidWalls() diff --git a/Tests/Unit/GameAction/3DSector/Sector3D_PlaneSort.cs b/Tests/Unit/GameAction/3DSector/Sector3D_PlaneSort.cs index 910e83318..fc2ab4f50 100644 --- a/Tests/Unit/GameAction/3DSector/Sector3D_PlaneSort.cs +++ b/Tests/Unit/GameAction/3DSector/Sector3D_PlaneSort.cs @@ -3,6 +3,7 @@ using Helion.Resources.IWad; using Helion.World.Geometry.Sectors; using Helion.World.Impl.SinglePlayer; +using System.Linq; using Xunit; namespace Helion.Tests.Unit.GameAction._3DSector; @@ -115,7 +116,7 @@ public void ComplexSort() */ [Fact(DisplayName = "3D sector light transfer pool with floating solid sector")] - private void LightTransferLavaPool() + public void LightTransferLavaPool() { var lavaSector = GameActions.GetSector(World, 167); AssertPlanes3D(lavaSector, @@ -153,7 +154,7 @@ private void LightTransferLavaPool() } [Fact(DisplayName = "3D sector light water pool with floating solid sector")] - private void LightTransferWaterPool() + public void LightTransferWaterPool() { var waterSector = GameActions.GetSector(World, 173); AssertPlanes3D(waterSector, @@ -190,6 +191,17 @@ public void WaterSector() ); } + [Fact(DisplayName = "3D sector with overlapping planes should render inside walls")] + public void OverlapRenderInside() + { + var sector = GameActions.GetSector(World, 229); + sector.Sectors3D.Length.Should().Be(2); + sector.Sectors3D[1].ShouldRenderInsideWalls.Should().BeTrue(); + + foreach (var plane in sector.SectorPlanes3D) + plane.NoRenderWall.Should().BeFalse(); + } + private static void AssertPlanes3D(Sector sector, params PlaneData[] planes) { for (int i = 0; i < planes.Length; i++)