From ff57e778889b691154a0936b5445fd9896cc67cf Mon Sep 17 00:00:00 2001 From: Justin Marshall Date: Fri, 8 May 2026 12:56:20 -0700 Subject: [PATCH] Fixed an issue with alpha blended textures not rendering properly. --- neo/opengl/gl_d3d12raylight.cpp | 24 +++++----- neo/opengl/gl_d3d12shim.cpp | 81 ++++++++++++++++++++++++++------- neo/renderer/draw_common.cpp | 40 ++++++++++++++++ 3 files changed, 117 insertions(+), 28 deletions(-) diff --git a/neo/opengl/gl_d3d12raylight.cpp b/neo/opengl/gl_d3d12raylight.cpp index aa7ea04c..6bac4935 100644 --- a/neo/opengl/gl_d3d12raylight.cpp +++ b/neo/opengl/gl_d3d12raylight.cpp @@ -2019,8 +2019,9 @@ void glRaytracingSetMeshMaterialFlags(glRaytracingMeshHandle_t meshHandle, uint3 mesh->materialFlags = materialFlags; - // The glass bit changes whether the BLAS geometry is opaque, so force a full - // BLAS rebuild. The TLAS is also rebuilt so InstanceID carries the material bit. + // The glass bit also represents shim-auto-tagged alpha-blended surfaces. + // It changes whether BLAS geometry is opaque, so force a full BLAS rebuild. + // The TLAS is also rebuilt so InstanceID carries the material bit. mesh->blasBuilt = 0; mesh->dirty = 1; glRaytracingInvalidateInstancesForMesh(meshHandle, 0); @@ -2645,10 +2646,10 @@ void ShadowMiss(inout ShadowPayload payload) [shader("anyhit")] void ShadowAnyHit(inout ShadowPayload payload, in BuiltInTriangleIntersectionAttributes attr) { - // Glass should participate in the primary/raster image, but visibility rays - // must continue through it. Mark glass BLAS geometry non-opaque on the CPU - // side so this any-hit shader runs, then IgnoreHit() lets the ray keep going - // to whatever is behind the pane. + // Glass/alpha-blended surfaces should participate in the primary/raster image, + // but visibility rays must continue through them. Mark the BLAS geometry + // non-opaque on the CPU side so this any-hit shader runs, then IgnoreHit() + // lets the ray keep going to whatever is behind the pane/sprite. if (CurrentRayHitIsGlass()) { IgnoreHit(); @@ -2682,8 +2683,9 @@ void BounceMiss(inout BouncePayload payload) [shader("anyhit")] void BounceAnyHit(inout BouncePayload payload, in BuiltInTriangleIntersectionAttributes attr) { - // Secondary diffuse rays should see through the same glass that shadow rays - // see through. This keeps a glass pane from killing all bounced light behind it. + // Secondary diffuse rays should see through the same transparent surfaces that + // shadow rays see through. This keeps a pane/sprite from killing all bounced + // light behind it. if (CurrentRayHitIsGlass()) { IgnoreHit(); @@ -2694,8 +2696,8 @@ void BounceAnyHit(inout BouncePayload payload, in BuiltInTriangleIntersectionAtt [shader("closesthit")] void BounceClosestHit(inout BouncePayload payload, in BuiltInTriangleIntersectionAttributes attr) { - // Fallback for glass geometry that was accidentally built opaque. Correctly - // tagged glass is ignored in BounceAnyHit() above. + // Fallback for transparent geometry that was accidentally built opaque. + // Correctly tagged transparent surfaces are ignored in BounceAnyHit() above. if (CurrentRayHitIsGlass()) { payload.hit = 0; @@ -2716,7 +2718,7 @@ float TraceShadow(float3 origin, float3 dir, float maxT) RayDesc ray; ray.Origin = origin; ray.Direction = dir; - ray.TMin = 0.001; + ray.TMin = 5.0; ray.TMax = maxT; ShadowPayload payload; diff --git a/neo/opengl/gl_d3d12shim.cpp b/neo/opengl/gl_d3d12shim.cpp index cc3a523d..6fa9f46b 100644 --- a/neo/opengl/gl_d3d12shim.cpp +++ b/neo/opengl/gl_d3d12shim.cpp @@ -1691,17 +1691,49 @@ static inline uint32_t QD3D12_CurrentRayMaterialFlags() return QD3D12_ClampRayMaterialFlags(g_gl.currentRayMaterialFlags); } +static inline bool QD3D12_PipelineUsesAlphaBlend(PipelineMode mode) +{ + return mode == PIPE_BLEND_TEX || mode == PIPE_BLEND_UNTEX; +} + +static inline bool QD3D12_CurrentDrawUsesAlphaBlend() +{ + if (!g_gl.blend) + return false; + + // GL_BLEND with this pair is effectively a straight source overwrite. Treat + // it as opaque so old code that leaves GL_BLEND enabled by accident does not + // silently become ray-transparent. + if (g_gl.blendSrc == GL_ONE && g_gl.blendDst == GL_ZERO) + return false; + + return true; +} + +static inline uint32_t QD3D12_CurrentEffectiveRayMaterialFlags() +{ + uint32_t flags = QD3D12_CurrentRayMaterialFlags(); + + // Alpha-blended fixed-function surfaces are visually transparent. Mirror that + // into the DXR material bits automatically so callers do not also have to tag + // the same draw as glass just to make shadow/GI rays continue through it. + if (QD3D12_CurrentDrawUsesAlphaBlend()) + flags |= GL_RAYTRACING_MATERIAL_FLAG_GLASS; + + return QD3D12_ClampRayMaterialFlags(flags); +} + static inline float QD3D12_CurrentEffectiveGeometryFlag() { uint32_t bits = (uint32_t)max(0.0f, floorf(g_gl.currentGeometryFlag + 0.5f)); - if (QD3D12_RayMaterialFlagsHaveGlass(QD3D12_CurrentRayMaterialFlags())) + if (QD3D12_RayMaterialFlagsHaveGlass(QD3D12_CurrentEffectiveRayMaterialFlags())) bits |= QD3D12_GEOMETRY_FLAG_GLASS_BIT; return (float)bits; } static inline float QD3D12_CurrentEffectiveMaterialType() { - if (QD3D12_RayMaterialFlagsHaveGlass(QD3D12_CurrentRayMaterialFlags()) && + if (QD3D12_RayMaterialFlagsHaveGlass(QD3D12_CurrentEffectiveRayMaterialFlags()) && g_gl.currentMaterialType == 0.0f) { return QD3D12_MATERIAL_TYPE_GLASS; @@ -2248,6 +2280,7 @@ cbuffer DrawCB : register(b0) #define gGlowMapStrength gMotionPad.w #define gUseSpecularMap gMaterialMapPad.x #define gSpecularMapStrength gMaterialMapPad.y +#define gAlphaBlendPass gMaterialMapPad.z Texture2D gTex0 : register(t0); Texture2D gTex1 : register(t1); @@ -2713,6 +2746,8 @@ PSOut PSMain(VSOut i) { PSOut o; o.color = BuildTexturedColor(i); + if (gAlphaBlendPass > 0.5) + o.color.rgb += BuildGlowEmission(i).rgb; o.normal = float4(BuildGBufferNormal(i), i.attr.y); o.position = float4(i.worldPos, i.attr.x); o.velocity = BuildVelocity(i); @@ -2760,7 +2795,8 @@ float4 PSMainAlphaTestColorOnly(VSOut i) : SV_Target0 c.rgb += BuildGlowEmission(i).rgb; return c; } - +)HLSL" +R"HLSL( float4 PSMainUntexturedColorOnly(VSOut i) : SV_Target0 { return ApplyFog(i.col, i.fogCoord); @@ -5880,14 +5916,16 @@ static D3D12_GRAPHICS_PIPELINE_STATE_DESC BuildPSODesc( d.RasterizerState.DepthClipEnable = TRUE; d.RasterizerState.MultisampleEnable = (d.SampleDesc.Count > 1) ? TRUE : FALSE; + const bool alphaBlended = QD3D12_PipelineUsesAlphaBlend(mode); + d.BlendState.RenderTarget[0].RenderTargetWriteMask = key.colorWriteMask; if (!nativeColorOnly) { - d.BlendState.RenderTarget[1].RenderTargetWriteMask = key.colorWriteMask ? D3D12_COLOR_WRITE_ENABLE_ALL : 0; - d.BlendState.RenderTarget[2].RenderTargetWriteMask = key.colorWriteMask ? D3D12_COLOR_WRITE_ENABLE_ALL : 0; - d.BlendState.RenderTarget[3].RenderTargetWriteMask = key.colorWriteMask ? D3D12_COLOR_WRITE_ENABLE_ALL : 0; - d.BlendState.RenderTarget[4].RenderTargetWriteMask = key.colorWriteMask ? D3D12_COLOR_WRITE_ENABLE_ALL : 0; - d.BlendState.RenderTarget[5].RenderTargetWriteMask = key.colorWriteMask ? D3D12_COLOR_WRITE_ENABLE_ALL : 0; + d.BlendState.RenderTarget[1].RenderTargetWriteMask = (key.colorWriteMask && !alphaBlended) ? D3D12_COLOR_WRITE_ENABLE_ALL : 0; + d.BlendState.RenderTarget[2].RenderTargetWriteMask = (key.colorWriteMask && !alphaBlended) ? D3D12_COLOR_WRITE_ENABLE_ALL : 0; + d.BlendState.RenderTarget[3].RenderTargetWriteMask = (key.colorWriteMask && !alphaBlended) ? D3D12_COLOR_WRITE_ENABLE_ALL : 0; + d.BlendState.RenderTarget[4].RenderTargetWriteMask = (key.colorWriteMask && !alphaBlended) ? D3D12_COLOR_WRITE_ENABLE_ALL : 0; + d.BlendState.RenderTarget[5].RenderTargetWriteMask = (key.colorWriteMask && !alphaBlended) ? D3D12_COLOR_WRITE_ENABLE_ALL : 0; } d.BlendState.AlphaToCoverageEnable = FALSE; @@ -5907,7 +5945,7 @@ static D3D12_GRAPHICS_PIPELINE_STATE_DESC BuildPSODesc( rt.LogicOp = D3D12_LOGIC_OP_NOOP; } - if (mode == PIPE_BLEND_TEX || mode == PIPE_BLEND_UNTEX) + if (alphaBlended) { auto& rt = d.BlendState.RenderTarget[0]; rt.BlendEnable = TRUE; @@ -5929,7 +5967,10 @@ static D3D12_GRAPHICS_PIPELINE_STATE_DESC BuildPSODesc( //} } - ApplyRasterDepthStencilState(d, key); + BatchKey depthStencilKey = key; + if (alphaBlended) + depthStencilKey.depthWrite = false; + ApplyRasterDepthStencilState(d, depthStencilKey); return d; } @@ -6151,6 +6192,8 @@ static D3D12_GRAPHICS_PIPELINE_STATE_DESC BuildARBPSODesc( d.RasterizerState.DepthClipEnable = TRUE; d.RasterizerState.MultisampleEnable = (d.SampleDesc.Count > 1) ? TRUE : FALSE; + const bool alphaBlended = QD3D12_PipelineUsesAlphaBlend(key.pipeline); + d.BlendState.AlphaToCoverageEnable = FALSE; d.BlendState.IndependentBlendEnable = nativeColorOnly ? FALSE : TRUE; @@ -6169,7 +6212,7 @@ static D3D12_GRAPHICS_PIPELINE_STATE_DESC BuildARBPSODesc( rt.RenderTargetWriteMask = (i == 0) ? key.colorWriteMask : 0; } - if (key.pipeline == PIPE_BLEND_TEX || key.pipeline == PIPE_BLEND_UNTEX) + if (alphaBlended) { auto& rt = d.BlendState.RenderTarget[0]; rt.BlendEnable = TRUE; @@ -6181,7 +6224,10 @@ static D3D12_GRAPHICS_PIPELINE_STATE_DESC BuildARBPSODesc( rt.BlendOpAlpha = D3D12_BLEND_OP_ADD; } - ApplyRasterDepthStencilState(d, key); + BatchKey depthStencilKey = key; + if (alphaBlended) + depthStencilKey.depthWrite = false; + ApplyRasterDepthStencilState(d, depthStencilKey); return d; } @@ -7969,7 +8015,7 @@ static PipelineMode PickPipeline(bool useTex0, bool useTex1) { const bool textured = useTex0 || useTex1; - if (g_gl.blend) + if (QD3D12_CurrentDrawUsesAlphaBlend()) return textured ? PIPE_BLEND_TEX : PIPE_BLEND_UNTEX; if (g_gl.alphaTest) @@ -8514,7 +8560,7 @@ static void QD3D12_FlushQueuedBatches() dc->_motionPad[3] = (batch.key.useGlowMap > 0.5f) ? batch.key.glowMapStrength : 0.0f; dc->materialMapPad[0] = batch.key.useSpecularMap; dc->materialMapPad[1] = batch.key.specularMapStrength; - dc->materialMapPad[2] = 0.0f; + dc->materialMapPad[2] = QD3D12_PipelineUsesAlphaBlend(batch.key.pipeline) ? 1.0f : 0.0f; dc->materialMapPad[3] = 0.0f; if (batch.key.useARBPrograms) @@ -14276,11 +14322,12 @@ void glUpdateBottomAccelStructure(bool opaque, uint32_t& meshHandle) meshDesc.indexCount = (uint32_t)drawIndexes.size(); meshDesc.allowUpdate = 1; - const uint32_t materialFlags = QD3D12_CurrentRayMaterialFlags(); + const uint32_t materialFlags = QD3D12_CurrentEffectiveRayMaterialFlags(); const bool isGlass = QD3D12_RayMaterialFlagsHaveGlass(materialFlags); - // Glass must be non-opaque in the BLAS so the DXR any-hit shader can run - // IgnoreHit() and let visibility/path rays continue through the pane. + // Glass/alpha-blended surfaces must be non-opaque in the BLAS so the DXR + // any-hit shader can run IgnoreHit() and let visibility/path rays continue + // through the pane/sprite without requiring a separate caller-side tag. meshDesc.opaque = (opaque && !isGlass) ? 1 : 0; if (meshHandle) diff --git a/neo/renderer/draw_common.cpp b/neo/renderer/draw_common.cpp index 77b0ebb9..e119989c 100644 --- a/neo/renderer/draw_common.cpp +++ b/neo/renderer/draw_common.cpp @@ -502,6 +502,35 @@ void RB_T_FillDepthBuffer( const drawSurf_t *surf ) { // draw the entire surface solid if (drawSolid && !shader->IsSky()) { glColor4f(1.0f, 1.0f, 1.0f, 1.0f); + + if (shader->Coverage() == MC_PERFORATED) { + for (stage = 0; stage < shader->GetNumStages(); stage++) { + pStage = shader->GetStage(stage); + + if (regs[pStage->conditionRegister] == 0) { + continue; + } + + if (!pStage->hasAlphaTest) { + continue; + } + + // set the alpha modulate + color[3] = regs[pStage->color.registers[3]]; + color[0] = 1; + color[1] = 1; + color[2] = 1; + + glColor4fv(color); + glDisable(GL_BLEND); + glEnable(GL_ALPHA_TEST); + glAlphaFunc(GL_GREATER, regs[pStage->alphaTestRegister]); + + break; + } + } + + // bind the texture GL_SelectTexture(0); glEnable(GL_TEXTURE_2D); @@ -557,6 +586,17 @@ void RB_T_FillDepthBuffer( const drawSurf_t *surf ) { glBindNormalMapTexture(0); globalImages->BindNull(); GL_SelectTexture(0); + + if (shader->Coverage() == MC_PERFORATED) { + glDisable(GL_ALPHA_TEST); + glEnable(GL_BLEND); + color[0] = 1; + color[1] = 1; + color[2] = 1; + color[3] = 1; + + glColor4fv(color); + } }