diff --git a/neo/doom3/game/gamesys/TypeInfoGen.h b/neo/doom3/game/gamesys/TypeInfoGen.h index e688623d..cc43887d 100644 --- a/neo/doom3/game/gamesys/TypeInfoGen.h +++ b/neo/doom3/game/gamesys/TypeInfoGen.h @@ -9,7 +9,7 @@ 1199 constants 135 enums - 619 classes/structs/unions + 620 classes/structs/unions 38 templates 7 max inheritance level for 'idAI_Vagary' @@ -4129,6 +4129,15 @@ static classVariableInfo_t glScreenSpaceDecal_t_typeInfo[] = { { NULL, 0 } }; +static classVariableInfo_t glScreenSpaceParticleVertex_t_typeInfo[] = { + { "float[4]", "clip", (intptr_t)(&((glScreenSpaceParticleVertex_t *)0)->clip), sizeof( ((glScreenSpaceParticleVertex_t *)0)->clip ) }, + { "float[2]", "st", (intptr_t)(&((glScreenSpaceParticleVertex_t *)0)->st), sizeof( ((glScreenSpaceParticleVertex_t *)0)->st ) }, + { "float[4]", "color", (intptr_t)(&((glScreenSpaceParticleVertex_t *)0)->color), sizeof( ((glScreenSpaceParticleVertex_t *)0)->color ) }, + { "float[3]", "world", (intptr_t)(&((glScreenSpaceParticleVertex_t *)0)->world), sizeof( ((glScreenSpaceParticleVertex_t *)0)->world ) }, + { "float", "pad0", (intptr_t)(&((glScreenSpaceParticleVertex_t *)0)->pad0), sizeof( ((glScreenSpaceParticleVertex_t *)0)->pad0 ) }, + { NULL, 0 } +}; + static classVariableInfo_t QD3D12NeuralPOMImageRGBA8_typeInfo[] = { { "uint32_t", "width", (intptr_t)(&((QD3D12NeuralPOMImageRGBA8 *)0)->width), sizeof( ((QD3D12NeuralPOMImageRGBA8 *)0)->width ) }, { "uint32_t", "height", (intptr_t)(&((QD3D12NeuralPOMImageRGBA8 *)0)->height), sizeof( ((QD3D12NeuralPOMImageRGBA8 *)0)->height ) }, @@ -5248,11 +5257,11 @@ static classVariableInfo_t frameLookup_t_typeInfo[] = { { NULL, 0 } }; -static classVariableInfo_t class_287_class_287_typeInfo[] = { -// { "const idSoundShader *", "soundShader", (intptr_t)(&((class_287::class_287 *)0)->soundShader), sizeof( ((class_287::class_287 *)0)->soundShader ) }, -// { "const function_t *", "function", (intptr_t)(&((class_287::class_287 *)0)->function), sizeof( ((class_287::class_287 *)0)->function ) }, -// { "const idDeclSkin *", "skin", (intptr_t)(&((class_287::class_287 *)0)->skin), sizeof( ((class_287::class_287 *)0)->skin ) }, -// { "int", "index", (intptr_t)(&((class_287::class_287 *)0)->index), sizeof( ((class_287::class_287 *)0)->index ) }, +static classVariableInfo_t class_288_class_288_typeInfo[] = { +// { "const idSoundShader *", "soundShader", (intptr_t)(&((class_288::class_288 *)0)->soundShader), sizeof( ((class_288::class_288 *)0)->soundShader ) }, +// { "const function_t *", "function", (intptr_t)(&((class_288::class_288 *)0)->function), sizeof( ((class_288::class_288 *)0)->function ) }, +// { "const idDeclSkin *", "skin", (intptr_t)(&((class_288::class_288 *)0)->skin), sizeof( ((class_288::class_288 *)0)->skin ) }, +// { "int", "index", (intptr_t)(&((class_288::class_288 *)0)->index), sizeof( ((class_288::class_288 *)0)->index ) }, { NULL, 0 } }; @@ -9174,6 +9183,7 @@ static classTypeInfo_t classTypeInfo[] = { { "glRaytracingLight_t", "", sizeof(glRaytracingLight_t), glRaytracingLight_t_typeInfo }, { "glRaytracingLightingPassDesc_t", "", sizeof(glRaytracingLightingPassDesc_t), glRaytracingLightingPassDesc_t_typeInfo }, { "glScreenSpaceDecal_t", "", sizeof(glScreenSpaceDecal_t), glScreenSpaceDecal_t_typeInfo }, + { "glScreenSpaceParticleVertex_t", "", sizeof(glScreenSpaceParticleVertex_t), glScreenSpaceParticleVertex_t_typeInfo }, { "QD3D12NeuralPOMImageRGBA8", "", sizeof(QD3D12NeuralPOMImageRGBA8), QD3D12NeuralPOMImageRGBA8_typeInfo }, { "QD3D12NeuralPOMTrainDesc", "", sizeof(QD3D12NeuralPOMTrainDesc), QD3D12NeuralPOMTrainDesc_typeInfo }, { "QD3D12NeuralPOMTrainStats", "", sizeof(QD3D12NeuralPOMTrainStats), QD3D12NeuralPOMTrainStats_typeInfo }, @@ -9284,7 +9294,7 @@ static classTypeInfo_t classTypeInfo[] = { { "jointAnimInfo_t", "", sizeof(jointAnimInfo_t), jointAnimInfo_t_typeInfo }, { "jointMod_t", "", sizeof(jointMod_t), jointMod_t_typeInfo }, { "frameLookup_t", "", sizeof(frameLookup_t), frameLookup_t_typeInfo }, -// { "class_287::class_287", "", sizeof(class_287::class_287), class_287_class_287_typeInfo }, +// { "class_288::class_288", "", sizeof(class_288::class_288), class_288_class_288_typeInfo }, { "frameCommand_t", "", sizeof(frameCommand_t), frameCommand_t_typeInfo }, { "animFlags_t", "", sizeof(animFlags_t), animFlags_t_typeInfo }, { "idModelExport", "", sizeof(idModelExport), idModelExport_typeInfo }, diff --git a/neo/engine/opengl/gl_d3d12shim.cpp b/neo/engine/opengl/gl_d3d12shim.cpp index 63dc62d5..ca35b96e 100644 --- a/neo/engine/opengl/gl_d3d12shim.cpp +++ b/neo/engine/opengl/gl_d3d12shim.cpp @@ -467,11 +467,12 @@ static const UINT QD3D12_UploadBufferSize = 128 * 1024 * 1024; // MSAA has been removed from the G-buffer path. Temporal AA now owns edge cleanup // after lighting, so all scene/deferred render targets remain single-sample. static const UINT QD3D12_RequestedGBufferSampleCount = 1; -static constexpr UINT QD3D12_PostSrvCount = 14; +static constexpr UINT QD3D12_PostSrvCount = 16; static constexpr UINT QD3D12_PostRootConstants = QD3D12_PostSrvCount; static constexpr UINT QD3D12_PostConstantDwords = 24; static constexpr UINT QD3D12_ToneMapTileDim = 64; static constexpr UINT QD3D12_MaxScreenSpaceDecals = 64; +static constexpr UINT QD3D12_MaxScreenSpaceParticleLights = GL_RAYTRACING_MAX_LIGHTS; enum QD3D12RTVSlotGroup { @@ -544,6 +545,14 @@ struct QD3D12CameraState bool valid = false; }; +struct QD3D12ScreenSpaceParticleBatch +{ + std::vector vertices; + uint32_t textureId = 0; + uint32_t blendMode = 0; + float softDepth = 16.0f; +}; + enum PipelineMode { PIPE_OPAQUE_TEX = 0, @@ -1504,17 +1513,29 @@ struct GLState bool enableRayAIDenoise = false; bool enableDLSSRayReconstruction = false; bool enableFSRRayRegeneration = false; + bool blockLightingUpscaleInputThisFrame = false; uint32_t frameGenerationMultiplier = 0; uint32_t pathTracingSamplesPerPixel = 1; uint32_t pathTracingFallbackSamplesPerPixel = 2; uint32_t pathTracingMaxBounces = 3; glScreenSpaceDecal_t screenSpaceDecals[QD3D12_MaxScreenSpaceDecals] = {}; uint32_t screenSpaceDecalCount = 0; + std::vector screenSpaceParticleBatches; + glRaytracingLight_t screenSpaceParticleLights[QD3D12_MaxScreenSpaceParticleLights] = {}; + uint32_t screenSpaceParticleLightCount = 0; ComPtr screenSpaceDecalBuffer; void* screenSpaceDecalBufferMapped = nullptr; UINT screenSpaceDecalSrvIndex = UINT_MAX; D3D12_CPU_DESCRIPTOR_HANDLE screenSpaceDecalSrvCpu{}; D3D12_GPU_DESCRIPTOR_HANDLE screenSpaceDecalSrvGpu{}; + ComPtr screenSpaceParticleLightBuffer; + void* screenSpaceParticleLightBufferMapped = nullptr; + UINT screenSpaceParticleLightSrvIndex = UINT_MAX; + D3D12_CPU_DESCRIPTOR_HANDLE screenSpaceParticleLightSrvCpu{}; + D3D12_GPU_DESCRIPTOR_HANDLE screenSpaceParticleLightSrvGpu{}; + UINT screenSpaceParticleTlasSrvIndex = UINT_MAX; + D3D12_CPU_DESCRIPTOR_HANDLE screenSpaceParticleTlasSrvCpu{}; + D3D12_GPU_DESCRIPTOR_HANDLE screenSpaceParticleTlasSrvGpu{}; float jitterX = 0.0f; float jitterY = 0.0f; @@ -1696,6 +1717,8 @@ struct GLState ComPtr postGBufferPointResolveMsaaPSO; ComPtr postScreenDecalPSO; ComPtr postScreenDecalStainPSO; + ComPtr postScreenParticlePSO; + ComPtr postScreenParticleAdditivePSO; ComPtr vsMainBlob; ComPtr hsMainBlob; @@ -1718,6 +1741,8 @@ struct GLState ComPtr postPsDepthResolveMsaaBlob; ComPtr postPsGBufferPointResolveMsaaBlob; ComPtr postPsScreenDecalBlob; + ComPtr postVsScreenParticleBlob; + ComPtr postPsScreenParticleBlob; TextureResource whiteTexture; @@ -4448,12 +4473,40 @@ struct ScreenSpaceDecal StructuredBuffer gScreenDecals : register(t13); +struct ScreenParticleLight +{ + float3 position; + float radius; + float3 color; + float intensity; + float3 normal; + uint type; + float3 axisU; + float halfWidth; + float3 axisV; + float halfHeight; + uint samples; + uint twoSided; + float persistant; + float volumetricScattering; + float3 pointRadius; + float pointRadiusPad; + uint iesTextureId; + float iesStrength; + float2 iesPad; +}; + +StructuredBuffer gScreenParticleLights : register(t14); +#if defined(QD3D12_SCREEN_PARTICLE_RAYQUERY) +RaytracingAccelerationStructure gScreenParticleSceneBVH : register(t15); +#endif + cbuffer PostCB : register(b0) { // gPostParams0: x = sharpen strength, y = history weight, z = TAA reset flag. // gPostParams1: x = tone-map exposure, y = white-point scale, z = display brightness. // gPostParams2: x = fog enabled, y = fog mode, z = fog density, w = fog start. - // gPostParams3: x = fog end, y = fog camera valid. + // gPostParams3: x = fog end, y = fog camera valid, z = particle soft depth, w = particle light count. float4 gPostParams0; float4 gPostParams1; float4 gPostParams2; @@ -4475,15 +4528,37 @@ cbuffer PostCB : register(b0) #define gPostFogStart gPostParams2.w #define gPostFogEnd gPostParams3.x #define gPostFogCameraValid gPostParams3.y +#define gScreenParticleSoftDepth gPostParams3.z +#define gScreenParticleLightCount gPostParams3.w #define gScreenDecalCount gPostFogCameraWorldPos.w #define QD3D12_TONEMAP_TILE_DIM 64u +static const uint GL_RAYTRACING_LIGHT_TYPE_POINT = 0u; +static const uint GL_RAYTRACING_LIGHT_TYPE_RECT = 1u; +static const uint GL_RAYTRACING_LIGHT_TYPE_SPOT = 2u; + struct VSOut { float4 pos : SV_Position; float2 uv : TEXCOORD0; }; +struct ParticleVSIn +{ + float4 clipPos : POSITION0; + float2 uv : TEXCOORD0; + float4 color : COLOR0; + float3 world : TEXCOORD1; +}; + +struct ParticleVSOut +{ + float4 pos : SV_Position; + float2 uv : TEXCOORD0; + float4 color : COLOR0; + float3 world : TEXCOORD1; +}; + VSOut VSMain(uint vid : SV_VertexID) { VSOut o; @@ -4497,6 +4572,16 @@ VSOut VSMain(uint vid : SV_VertexID) return o; } +ParticleVSOut VSParticle(ParticleVSIn i) +{ + ParticleVSOut o; + o.pos = i.clipPos; + o.uv = i.uv; + o.color = i.color; + o.world = i.world; + return o; +} + float QD3D12_PostFogFactor(float fogCoord) { if (gPostFogEnabled < 0.5) @@ -4590,6 +4675,175 @@ float4 PSScreenDecals(VSOut i) : SV_Target0 return float4(decalColor, decalAlpha); } +float3 ScreenParticleSafeNormalizeOr(float3 v, float3 fallbackDir) +{ + float lenSq = dot(v, v); + return (lenSq > 1e-8) ? (v * rsqrt(lenSq)) : fallbackDir; +} + +float3 ScreenParticlePointRadius(ScreenParticleLight Lgt) +{ + float scalarRadius = max(abs(Lgt.radius), 1e-4); + float3 r = abs(Lgt.pointRadius); + if (max(max(r.x, r.y), r.z) <= 1e-4) + r = float3(scalarRadius, scalarRadius, scalarRadius); + return max(r, float3(1e-4, 1e-4, 1e-4)); +} + +float ScreenParticlePointAttenuation(float3 worldPos, ScreenParticleLight Lgt) +{ + float3 radii = ScreenParticlePointRadius(Lgt); + float3 offset = worldPos - Lgt.position; + + float3 axisU = ScreenParticleSafeNormalizeOr(Lgt.axisU, float3(1.0, 0.0, 0.0)); + float3 axisV = ScreenParticleSafeNormalizeOr(Lgt.axisV, float3(0.0, 1.0, 0.0)); + float3 axisW = ScreenParticleSafeNormalizeOr(Lgt.normal, float3(0.0, 0.0, 1.0)); + + float3 perAxis; + perAxis.x = 1.0 - saturate(abs(dot(offset, axisU)) / radii.x); + perAxis.y = 1.0 - saturate(abs(dot(offset, axisV)) / radii.y); + perAxis.z = 1.0 - saturate(abs(dot(offset, axisW)) / radii.z); + return perAxis.x * perAxis.y * perAxis.z; +} + +float ScreenParticleBasicFalloff(float lightDist, float maxLightDist) +{ + return saturate(1.0 - lightDist / max(maxLightDist, 1e-4)); +} + +float ScreenParticleSpotAttenuation(float3 worldPos, ScreenParticleLight Lgt) +{ + float3 lightToSurface = worldPos - Lgt.position; + float nearClip = max(Lgt.pointRadius.x, 0.0); + float farClip = max(Lgt.radius, nearClip + 1e-4); + float3 spotDir = ScreenParticleSafeNormalizeOr(Lgt.normal, float3(0.0, 0.0, 1.0)); + + float depth = dot(lightToSurface, spotDir); + if (depth <= nearClip || depth >= farClip) + return 0.0; + + float3 axisU = ScreenParticleSafeNormalizeOr(Lgt.axisU, float3(1.0, 0.0, 0.0)); + float3 axisV = ScreenParticleSafeNormalizeOr(Lgt.axisV, float3(0.0, 1.0, 0.0)); + float invDepth = 1.0 / max(depth, 1e-4); + float halfU = max(abs(Lgt.halfWidth), 1e-4); + float halfV = max(abs(Lgt.halfHeight), 1e-4); + float signedU = (dot(lightToSurface, axisU) * invDepth) / halfU; + float signedV = (dot(lightToSurface, axisV) * invDepth) / halfV; + if (abs(signedU) >= 1.0 || abs(signedV) >= 1.0) + return 0.0; + + float3 lightVector = normalize(lightToSurface); + float maxSlope = max(halfU, halfV); + float outerAngle = rsqrt(1.0 + maxSlope * maxSlope); + float innerSlope = maxSlope * 0.65; + float innerAngle = rsqrt(1.0 + innerSlope * innerSlope); + float spotCookie = smoothstep(outerAngle, innerAngle, saturate(dot(lightVector, spotDir))); + return spotCookie * ScreenParticleBasicFalloff(depth - nearClip, farClip - nearClip); +} + +float ScreenParticleTraceShadow(float3 worldPos, float3 viewDir, float3 L, float dist, ScreenParticleLight Lgt) +{ + if (Lgt.samples == 0u) + return 1.0; + +#if defined(QD3D12_SCREEN_PARTICLE_RAYQUERY) + RayDesc ray; + ray.Origin = worldPos + L * 5.0 + viewDir * 1.0; + ray.Direction = L; + ray.TMin = 0.001; + ray.TMax = max(dist - 8.0, 0.001); + + RayQuery q; + q.TraceRayInline( + gScreenParticleSceneBVH, + RAY_FLAG_ACCEPT_FIRST_HIT_AND_END_SEARCH | RAY_FLAG_FORCE_OPAQUE | RAY_FLAG_SKIP_PROCEDURAL_PRIMITIVES, + 0xFF, + ray); + while (q.Proceed()) {} + + return (q.CommittedStatus() == COMMITTED_TRIANGLE_HIT) ? 0.0 : 1.0; +#else + return 1.0; +#endif +} + +float3 ScreenParticleDirectLighting(float3 worldPos) +{ + uint lightCount = min((uint)gScreenParticleLightCount, 256u); + if (lightCount == 0u || gPostFogCameraValid <= 0.5) + return 1.0; + + float3 viewDir = ScreenParticleSafeNormalizeOr(gPostFogCameraWorldPos.xyz - worldPos, float3(0.0, 0.0, 1.0)); + float3 lighting = float3(0.16, 0.16, 0.16); + + [loop] + for (uint lightIndex = 0u; lightIndex < lightCount; ++lightIndex) + { + ScreenParticleLight Lgt = gScreenParticleLights[lightIndex]; + if (Lgt.type != GL_RAYTRACING_LIGHT_TYPE_POINT && Lgt.type != GL_RAYTRACING_LIGHT_TYPE_SPOT) + continue; + + float3 toLight = Lgt.position - worldPos; + float dist = length(toLight); + if (dist <= 0.01) + continue; + + float atten = (Lgt.type == GL_RAYTRACING_LIGHT_TYPE_POINT) + ? ScreenParticlePointAttenuation(worldPos, Lgt) + : ScreenParticleSpotAttenuation(worldPos, Lgt); + if (atten <= 0.0) + continue; + + float3 L = toLight / dist; + float shadow = ScreenParticleTraceShadow(worldPos, viewDir, L, dist, Lgt); + if (shadow <= 0.0) + continue; + + float forwardScatter = saturate(dot(viewDir, -L) * 0.5 + 0.5); + float phase = lerp(0.65, 1.0, forwardScatter); + lighting += Lgt.color * (Lgt.intensity * atten * shadow * phase * 0.25); + } + + return clamp(lighting, 0.08, 8.0); +} + +float4 PSScreenParticles(ParticleVSOut i) : SV_Target0 +{ + float4 texel = gTex0.Sample(gSamp0, i.uv) * i.color; + float alpha = saturate(texel.a); + if (alpha <= 0.0001) + discard; + + if (gPostFogCameraValid > 0.5) + { + uint2 pixel = uint2(i.pos.xy); + float sceneDepth = gFogDepth.Load(int3(pixel, 0)); + float particleDepth = saturate(i.pos.z); + if (sceneDepth < 0.99999) + { + float4 positionSample = gScreenDecalPosition.Load(int3(pixel, 0)); + uint geometryBits = (uint)round(max(positionSample.w, 0.0)); + if ((geometryBits & 1u) == 0u) + { + float sceneDistance = length(positionSample.xyz - gPostFogCameraWorldPos.xyz); + float particleDistance = length(i.world - gPostFogCameraWorldPos.xyz); + float softDepth = max(gScreenParticleSoftDepth, 0.001); + alpha *= saturate((sceneDistance - particleDistance) / softDepth); + if (alpha <= 0.0001) + discard; + } + else if (particleDepth > sceneDepth + 0.00005) + { + discard; + } + } + } + + texel.rgb *= ScreenParticleDirectLighting(i.world); + + return float4(texel.rgb, alpha); +} + float4 PSCopy(VSOut i) : SV_Target0 { return QD3D12_ApplyPostFog(gTex0.Sample(gSamp0, i.uv), i.uv); @@ -4681,7 +4935,8 @@ float4 PSToneMapFinalMax(VSOut i) : SV_Target0 return float4(min(maxBrightness, 65504.0), 0.0, 0.0, 1.0); } - +)HLSL" +R"HLSL( float3 QD3D12_PostSharpenHDR(float2 uv, float3 center) { float amount = saturate(gPostSharpness); @@ -4825,7 +5080,8 @@ float3 QD3D12_EmissiveBloom(float2 uv) return min(bloom * 0.62, 5.0); } - +)HLSL" +R"HLSL( float4 PSAdd(VSOut i) : SV_Target0 { float4 center = gTex0.Sample(gSamp0, i.uv); @@ -7260,6 +7516,7 @@ static bool QD3D12_CanUseDLSSRayReconstructionForLighting(const QD3D12Window& w) static bool QD3D12_UseLightingTextureAsUpscaleInput(const QD3D12Window& w) { return QD3D12_CanUseDLSSRayReconstructionForLighting(w) && + !g_gl.blockLightingUpscaleInputThisFrame && g_gl.raytracedLightingReadyThisFrame && g_lightingTexture[g_lightingReadIndex] && g_lightingTexture[g_lightingReadIndex]->texture; @@ -7795,7 +8052,10 @@ static void QD3D12_SetPostConstants( float toneMapBrightness = 1.0f, bool applySceneFog = false, float screenDecalCount = 0.0f, - float screenDecalIndex = 0.0f) + float screenDecalIndex = 0.0f, + bool forceCameraPosition = false, + float screenParticleSoftDepth = 16.0f, + float screenParticleLightCount = 0.0f) { if (!cl) return; @@ -7824,6 +8084,15 @@ static void QD3D12_SetPostConstants( constants[21] = g_gl.sceneFogCameraWorldPos[1]; constants[22] = g_gl.sceneFogCameraWorldPos[2]; } + else if ( forceCameraPosition && g_gl.cameraState.valid ) + { + constants[13] = 1.0f; + constants[20] = g_gl.cameraState.cameraPos[0]; + constants[21] = g_gl.cameraState.cameraPos[1]; + constants[22] = g_gl.cameraState.cameraPos[2]; + } + constants[14] = screenParticleSoftDepth; + constants[15] = screenParticleLightCount; constants[23] = screenDecalCount; cl->SetGraphicsRoot32BitConstants(QD3D12_PostRootConstants, QD3D12_PostConstantDwords, constants, 0); } @@ -7912,6 +8181,96 @@ static bool QD3D12_EnsureScreenSpaceDecalBuffer() return true; } +static bool QD3D12_EnsureScreenSpaceParticleLightBuffer() +{ + if (!g_gl.device || !g_gl.srvHeap) + return false; + + const UINT64 bufferSize = sizeof(glRaytracingLight_t) * QD3D12_MaxScreenSpaceParticleLights; + if (!g_gl.screenSpaceParticleLightBuffer) + { + D3D12_HEAP_PROPERTIES hp{}; + hp.Type = D3D12_HEAP_TYPE_UPLOAD; + hp.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN; + hp.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN; + hp.CreationNodeMask = 1; + hp.VisibleNodeMask = 1; + + D3D12_RESOURCE_DESC rd{}; + rd.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; + rd.Width = bufferSize; + rd.Height = 1; + rd.DepthOrArraySize = 1; + rd.MipLevels = 1; + rd.Format = DXGI_FORMAT_UNKNOWN; + rd.SampleDesc.Count = 1; + rd.SampleDesc.Quality = 0; + rd.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; + rd.Flags = D3D12_RESOURCE_FLAG_NONE; + + QD3D12_CHECK(g_gl.device->CreateCommittedResource( + &hp, + D3D12_HEAP_FLAG_NONE, + &rd, + D3D12_RESOURCE_STATE_GENERIC_READ, + nullptr, + IID_PPV_ARGS(&g_gl.screenSpaceParticleLightBuffer))); + + QD3D12_CHECK(g_gl.screenSpaceParticleLightBuffer->Map(0, nullptr, &g_gl.screenSpaceParticleLightBufferMapped)); + } + + if (g_gl.screenSpaceParticleLightSrvIndex == UINT_MAX) + { + g_gl.screenSpaceParticleLightSrvIndex = g_gl.nextSrvIndex++; + g_gl.screenSpaceParticleLightSrvCpu = QD3D12_SrvCpu(g_gl.screenSpaceParticleLightSrvIndex); + g_gl.screenSpaceParticleLightSrvGpu = QD3D12_SrvGpu(g_gl.screenSpaceParticleLightSrvIndex); + + D3D12_SHADER_RESOURCE_VIEW_DESC sd{}; + sd.ViewDimension = D3D12_SRV_DIMENSION_BUFFER; + sd.Format = DXGI_FORMAT_UNKNOWN; + sd.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; + sd.Buffer.FirstElement = 0; + sd.Buffer.NumElements = QD3D12_MaxScreenSpaceParticleLights; + sd.Buffer.StructureByteStride = sizeof(glRaytracingLight_t); + sd.Buffer.Flags = D3D12_BUFFER_SRV_FLAG_NONE; + g_gl.device->CreateShaderResourceView(g_gl.screenSpaceParticleLightBuffer.Get(), &sd, g_gl.screenSpaceParticleLightSrvCpu); + } + + if (g_gl.screenSpaceParticleLightBufferMapped) + { + memset(g_gl.screenSpaceParticleLightBufferMapped, 0, bufferSize); + if (g_gl.screenSpaceParticleLightCount > 0) + { + memcpy( + g_gl.screenSpaceParticleLightBufferMapped, + g_gl.screenSpaceParticleLights, + sizeof(glRaytracingLight_t) * g_gl.screenSpaceParticleLightCount); + } + } + + return true; +} + +static bool QD3D12_UpdateScreenSpaceParticleTlasSrv(ID3D12Resource* tlas) +{ + if (!g_gl.device || !g_gl.srvHeap || !tlas) + return false; + + if (g_gl.screenSpaceParticleTlasSrvIndex == UINT_MAX) + { + g_gl.screenSpaceParticleTlasSrvIndex = g_gl.nextSrvIndex++; + g_gl.screenSpaceParticleTlasSrvCpu = QD3D12_SrvCpu(g_gl.screenSpaceParticleTlasSrvIndex); + g_gl.screenSpaceParticleTlasSrvGpu = QD3D12_SrvGpu(g_gl.screenSpaceParticleTlasSrvIndex); + } + + D3D12_SHADER_RESOURCE_VIEW_DESC sd{}; + sd.ViewDimension = D3D12_SRV_DIMENSION_RAYTRACING_ACCELERATION_STRUCTURE; + sd.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; + sd.RaytracingAccelerationStructure.Location = tlas->GetGPUVirtualAddress(); + g_gl.device->CreateShaderResourceView(nullptr, &sd, g_gl.screenSpaceParticleTlasSrvCpu); + return true; +} + static void QD3D12_PostFullscreenPass(ID3D12GraphicsCommandList* cl, ID3D12PipelineState* pso, D3D12_GPU_DESCRIPTOR_HANDLE inputSrv, @@ -8180,6 +8539,135 @@ void APIENTRY glDrawScreenSpaceDecalsQD3D12(void) } } +static void QD3D12_DrawScreenSpaceParticleBatch(const QD3D12ScreenSpaceParticleBatch& batch, ID3D12Resource* tlas) +{ + const glScreenSpaceParticleVertex_t* verts = batch.vertices.empty() ? nullptr : batch.vertices.data(); + const uint32_t vertexCount = (uint32_t)batch.vertices.size(); + const uint32_t textureId = batch.textureId; + const uint32_t blendMode = batch.blendMode; + const float softDepth = batch.softDepth; + + if (!verts || vertexCount == 0) + return; + + QD3D12Window* window = g_currentWindow; + if (!window || !g_gl.device || !g_gl.cmdList || !g_gl.postScreenParticlePSO) + return; + if (!window->sceneColorBuffers[window->frameIndex] || !window->positionBuffers[window->frameIndex] || !window->depthBuffer) + return; + if (!QD3D12_EnsureScreenSpaceParticleLightBuffer() || !QD3D12_UpdateScreenSpaceParticleTlasSrv(tlas)) + return; + + QD3D12_EnsureFrameOpen(); + QD3D12_FlushQueuedBatches(); + QD3D12_ResolveGBufferForCurrentFrame(*window); + QD3D12_UpdateCameraInfoFromCurrentMatrices(); + + const UINT frame = window->frameIndex; + ID3D12GraphicsCommandList* cl = g_gl.cmdList.Get(); + QD3D12_TransitionResource(cl, window->sceneColorBuffers[frame].Get(), window->sceneColorState[frame], D3D12_RESOURCE_STATE_RENDER_TARGET); + QD3D12_TransitionResource(cl, window->depthBuffer.Get(), window->depthState, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); + QD3D12_TransitionResource(cl, window->positionBuffers[frame].Get(), window->positionBufferState[frame], D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); + + const UINT vbBytes = vertexCount * sizeof(glScreenSpaceParticleVertex_t); + UploadAlloc upload = QD3D12_AllocUpload(vbBytes, 256); + memcpy(upload.cpu, verts, vbBytes); + + D3D12_VERTEX_BUFFER_VIEW vbv{}; + vbv.BufferLocation = upload.gpu; + vbv.SizeInBytes = vbBytes; + vbv.StrideInBytes = sizeof(glScreenSpaceParticleVertex_t); + + D3D12_VIEWPORT viewport{}; + viewport.TopLeftX = 0.0f; + viewport.TopLeftY = 0.0f; + viewport.Width = (float)window->renderWidth; + viewport.Height = (float)window->renderHeight; + viewport.MinDepth = 0.0f; + viewport.MaxDepth = 1.0f; + + D3D12_RECT scissor{}; + scissor.left = 0; + scissor.top = 0; + scissor.right = (LONG)window->renderWidth; + scissor.bottom = (LONG)window->renderHeight; + + D3D12_GPU_DESCRIPTOR_HANDLE particleTextureSrv = g_gl.whiteTexture.srvGpu; + TextureResource* particleTexture = textureId ? QD3D12_FindTextureResource(textureId) : nullptr; + if (particleTexture) + { + EnsureTextureResource(*particleTexture); + if (particleTexture->texture && !particleTexture->gpuValid) + { + UploadTexture(*particleTexture); + } + if (particleTexture->texture && particleTexture->srvIndex != UINT_MAX) + { + particleTextureSrv = particleTexture->srvGpu; + } + } + + ID3D12PipelineState* particlePSO = (blendMode == 1 && g_gl.postScreenParticleAdditivePSO.Get() != nullptr) + ? g_gl.postScreenParticleAdditivePSO.Get() + : g_gl.postScreenParticlePSO.Get(); + + ID3D12DescriptorHeap* heaps[] = { g_gl.srvHeap.Get() }; + cl->SetDescriptorHeaps(_countof(heaps), heaps); + cl->SetGraphicsRootSignature(g_gl.postRootSig.Get()); + QD3D12_SetPostConstants(cl, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, false, 0.0f, 0.0f, true, softDepth, (float)g_gl.screenSpaceParticleLightCount); + cl->SetPipelineState(particlePSO); + D3D12_CPU_DESCRIPTOR_HANDLE sceneColorRtv = CurrentResolvedSceneColorRTV(); + cl->OMSetRenderTargets(1, &sceneColorRtv, FALSE, nullptr); + cl->RSSetViewports(1, &viewport); + cl->RSSetScissorRects(1, &scissor); + cl->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); + cl->IASetVertexBuffers(0, 1, &vbv); + cl->SetGraphicsRootDescriptorTable(0, particleTextureSrv); + cl->SetGraphicsRootDescriptorTable(10, window->depthSrvGpu); + cl->SetGraphicsRootDescriptorTable(12, window->positionSrvGpu[frame]); + cl->SetGraphicsRootDescriptorTable(14, g_gl.screenSpaceParticleLightSrvGpu); + cl->SetGraphicsRootDescriptorTable(15, g_gl.screenSpaceParticleTlasSrvGpu); + cl->DrawInstanced(vertexCount, 1, 0, 0); +} + +void APIENTRY glDrawScreenSpaceParticlesQD3D12(const glScreenSpaceParticleVertex_t* verts, uint32_t vertexCount, uint32_t textureId, uint32_t blendMode, float softDepth) +{ + if (!verts || vertexCount == 0) + return; + + QD3D12ScreenSpaceParticleBatch batch; + batch.vertices.assign(verts, verts + vertexCount); + batch.textureId = textureId; + batch.blendMode = blendMode; + batch.softDepth = softDepth; + g_gl.screenSpaceParticleBatches.push_back(std::move(batch)); +} + +void APIENTRY glSetScreenSpaceParticleLightsQD3D12(const glRaytracingLight_t* lights, uint32_t lightCount) +{ + g_gl.screenSpaceParticleLightCount = 0; + memset(g_gl.screenSpaceParticleLights, 0, sizeof(g_gl.screenSpaceParticleLights)); + + if (!lights || lightCount == 0) + return; + + const uint32_t count = std::min(lightCount, QD3D12_MaxScreenSpaceParticleLights); + memcpy(g_gl.screenSpaceParticleLights, lights, sizeof(glRaytracingLight_t) * count); + g_gl.screenSpaceParticleLightCount = count; +} + +static void QD3D12_DrawQueuedScreenSpaceParticles(ID3D12Resource* tlas) +{ + if (g_gl.screenSpaceParticleBatches.empty()) + return; + + for (const QD3D12ScreenSpaceParticleBatch& batch : g_gl.screenSpaceParticleBatches) + { + QD3D12_DrawScreenSpaceParticleBatch(batch, tlas); + } + g_gl.screenSpaceParticleBatches.clear(); +} + static ID3D12Resource* QD3D12_PrepareStreamlineOutputForWrite(QD3D12Window& w) { ID3D12GraphicsCommandList* cl = g_gl.cmdList.Get(); @@ -9427,7 +9915,7 @@ static void QD3D12_DestroyUploadRingForWindow(QD3D12Window& w) w.upload.offset = 0; } -static ComPtr CompileShaderSourceVariant(const char* source, const char* entry, const char* target) +static ComPtr CompileShaderSourceVariant(const char* source, const char* entry, const char* target, LPCWSTR defineName = nullptr) { ComPtr utils; ComPtr compiler; @@ -9486,6 +9974,11 @@ static ComPtr CompileShaderSourceVariant(const char* source, const cha args.push_back(wTarget.c_str()); args.push_back(L"-HV"); args.push_back(L"2021"); + if (defineName && defineName[0]) + { + args.push_back(L"-D"); + args.push_back(defineName); + } #if defined(_DEBUG) args.push_back(L"-Zi"); @@ -9677,6 +10170,8 @@ static void QD3D12_CompileShaders() g_gl.postPsDepthResolveMsaaBlob = CompileShaderSourceVariant(kQD3D12PostHLSL, "PSDepthResolveMS", "ps_6_0"); g_gl.postPsGBufferPointResolveMsaaBlob = CompileShaderSourceVariant(kQD3D12PostHLSL, "PSGBufferPointResolveMS", "ps_6_0"); g_gl.postPsScreenDecalBlob = CompileShaderSourceVariant(kQD3D12PostHLSL, "PSScreenDecals", "ps_6_0"); + g_gl.postVsScreenParticleBlob = CompileShaderSourceVariant(kQD3D12PostHLSL, "VSParticle", "vs_6_0"); + g_gl.postPsScreenParticleBlob = CompileShaderSourceVariant(kQD3D12PostHLSL, "PSScreenParticles", "ps_6_5", L"QD3D12_SCREEN_PARTICLE_RAYQUERY=1"); } static const D3D12_INPUT_ELEMENT_DESC kGLVertexInputLayout[] = @@ -9701,6 +10196,14 @@ static const D3D12_INPUT_ELEMENT_DESC kGLVertexInputLayoutARB[] = { "BINORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, (UINT)offsetof(GLVertex, bx), D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }, }; +static const D3D12_INPUT_ELEMENT_DESC kScreenSpaceParticleInputLayout[] = +{ + { "POSITION", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, (UINT)offsetof(glScreenSpaceParticleVertex_t, clip), D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }, + { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, (UINT)offsetof(glScreenSpaceParticleVertex_t, st), D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }, + { "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, (UINT)offsetof(glScreenSpaceParticleVertex_t, color), D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }, + { "TEXCOORD", 1, DXGI_FORMAT_R32G32B32_FLOAT, 0, (UINT)offsetof(glScreenSpaceParticleVertex_t, world), D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }, +}; + static UINT8 BuildColorWriteMask() { UINT8 mask = 0; @@ -9900,6 +10403,24 @@ static void QD3D12_CreatePSOs() d.BlendState.RenderTarget[0].DestBlendAlpha = D3D12_BLEND_ONE; QD3D12_CHECK(g_gl.device->CreateGraphicsPipelineState(&d, IID_PPV_ARGS(&g_gl.postScreenDecalStainPSO))); + D3D12_GRAPHICS_PIPELINE_STATE_DESC particleDesc = d; + particleDesc.VS = { g_gl.postVsScreenParticleBlob->GetBufferPointer(), g_gl.postVsScreenParticleBlob->GetBufferSize() }; + particleDesc.PS = { g_gl.postPsScreenParticleBlob->GetBufferPointer(), g_gl.postPsScreenParticleBlob->GetBufferSize() }; + particleDesc.InputLayout.pInputElementDescs = kScreenSpaceParticleInputLayout; + particleDesc.InputLayout.NumElements = _countof(kScreenSpaceParticleInputLayout); + particleDesc.BlendState.RenderTarget[0].BlendEnable = TRUE; + particleDesc.BlendState.RenderTarget[0].SrcBlend = D3D12_BLEND_SRC_ALPHA; + particleDesc.BlendState.RenderTarget[0].DestBlend = D3D12_BLEND_INV_SRC_ALPHA; + particleDesc.BlendState.RenderTarget[0].SrcBlendAlpha = D3D12_BLEND_ONE; + particleDesc.BlendState.RenderTarget[0].DestBlendAlpha = D3D12_BLEND_INV_SRC_ALPHA; + QD3D12_CHECK(g_gl.device->CreateGraphicsPipelineState(&particleDesc, IID_PPV_ARGS(&g_gl.postScreenParticlePSO))); + + particleDesc.BlendState.RenderTarget[0].SrcBlend = D3D12_BLEND_SRC_ALPHA; + particleDesc.BlendState.RenderTarget[0].DestBlend = D3D12_BLEND_ONE; + particleDesc.BlendState.RenderTarget[0].SrcBlendAlpha = D3D12_BLEND_ONE; + particleDesc.BlendState.RenderTarget[0].DestBlendAlpha = D3D12_BLEND_ONE; + QD3D12_CHECK(g_gl.device->CreateGraphicsPipelineState(&particleDesc, IID_PPV_ARGS(&g_gl.postScreenParticleAdditivePSO))); + D3D12_GRAPHICS_PIPELINE_STATE_DESC depthDesc{}; depthDesc.pRootSignature = g_gl.postRootSig.Get(); depthDesc.VS = { g_gl.postVsBlob->GetBufferPointer(), g_gl.postVsBlob->GetBufferSize() }; @@ -10476,6 +10997,9 @@ void QD3D12_BeginFrame() g_gl.gbufferResolvedThisFrame = false; g_gl.raytracedLightingReadyThisFrame = false; g_gl.sceneFogValidThisFrame = false; + g_gl.blockLightingUpscaleInputThisFrame = false; + g_gl.screenSpaceParticleBatches.clear(); + g_gl.screenSpaceParticleLightCount = 0; QD3D12_UpdateViewportState(); FrameResources& fr = w.frames[w.frameIndex]; @@ -16714,6 +17238,8 @@ PROC WINAPI qd3d12_wglGetProcAddress(LPCSTR name) { { "QD3D12_ResolveGBufferNow", (PROC)QD3D12_ResolveGBufferNow }, { "glSetScreenSpaceDecalsQD3D12",(PROC)glSetScreenSpaceDecalsQD3D12 }, { "glDrawScreenSpaceDecalsQD3D12",(PROC)glDrawScreenSpaceDecalsQD3D12 }, + { "glSetScreenSpaceParticleLightsQD3D12",(PROC)glSetScreenSpaceParticleLightsQD3D12 }, + { "glDrawScreenSpaceParticlesQD3D12",(PROC)glDrawScreenSpaceParticlesQD3D12 }, { "QD3D12_SetUpscalerBackend", (PROC)QD3D12_SetUpscalerBackend }, { "QD3D12_SetUpscalerQuality", (PROC)QD3D12_SetUpscalerQuality }, { "QD3D12_SetUpscalerSharpness", (PROC)QD3D12_SetUpscalerSharpness }, @@ -17502,6 +18028,14 @@ void glLightScene(glRaytracingSceneHandle_t sceneHandle) QD3D12_RunRayAIDenoiseIfEnabled(cl, *window, writeLightingTex->texture.Get()); + if (!g_gl.screenSpaceParticleBatches.empty()) + { + // The particle pass is a blended graphics pass, so it needs a render-target + // scene color surface. Keep particle frames on the composited scene-color + // path instead of feeding the raw lighting texture straight to RR/upscale. + g_gl.blockLightingUpscaleInputThisFrame = true; + } + // If the denoiser changes the written texture state, restore it to a // shader-readable state before this slot becomes the next read target. // restore the texture to a shader-readable state for the post/upscale pass. @@ -17570,6 +18104,8 @@ void glLightScene(glRaytracingSceneHandle_t sceneHandle) // guide instead of overwriting it here. } + QD3D12_DrawQueuedScreenSpaceParticles(tlas); + QD3D12_ResolveSceneToOutputAndEnterNativePhase(*window); } diff --git a/neo/engine/opengl/opengl.h b/neo/engine/opengl/opengl.h index c738f6b6..a77a3d2f 100644 --- a/neo/engine/opengl/opengl.h +++ b/neo/engine/opengl/opengl.h @@ -1748,6 +1748,15 @@ typedef struct glScreenSpaceDecal_s float pad1[2]; } glScreenSpaceDecal_t; +typedef struct glScreenSpaceParticleVertex_s +{ + float clip[4]; + float st[2]; + float color[4]; + float world[3]; + float pad0; +} glScreenSpaceParticleVertex_t; + int glRaytracingInit(void); void glRaytracingShutdown(void); void glRaytracingClear(void); @@ -1843,6 +1852,8 @@ uint32_t glRaytracingLightingGetLightCount(void); void glLightScene(glRaytracingSceneHandle_t sceneHandle); void glSetScreenSpaceDecalsQD3D12(const glScreenSpaceDecal_t* decals, uint32_t decalCount); void glDrawScreenSpaceDecalsQD3D12(void); +void glSetScreenSpaceParticleLightsQD3D12(const glRaytracingLight_t* lights, uint32_t lightCount); +void glDrawScreenSpaceParticlesQD3D12(const glScreenSpaceParticleVertex_t* verts, uint32_t vertexCount, uint32_t textureId, uint32_t blendMode, float softDepth); ID3D12Device* QD3D12_GetDevice(void); ID3D12CommandQueue* QD3D12_GetQueue(void); diff --git a/neo/engine/renderer/RenderSystem_init.cpp b/neo/engine/renderer/RenderSystem_init.cpp index df0086e1..0d07476b 100644 --- a/neo/engine/renderer/RenderSystem_init.cpp +++ b/neo/engine/renderer/RenderSystem_init.cpp @@ -138,6 +138,7 @@ idCVar r_frontBuffer( "r_frontBuffer", "0", CVAR_RENDERER | CVAR_BOOL, "draw to idCVar r_skipSubviews( "r_skipSubviews", "0", CVAR_RENDERER | CVAR_INTEGER, "1 = don't render any gui elements on surfaces" ); idCVar r_skipGuiShaders( "r_skipGuiShaders", "0", CVAR_RENDERER | CVAR_INTEGER, "1 = skip all gui elements on surfaces, 2 = skip drawing but still handle events, 3 = draw but skip events", 0, 3, idCmdSystem::ArgCompletion_Integer<0,3> ); idCVar r_skipParticles( "r_skipParticles", "0", CVAR_RENDERER | CVAR_INTEGER, "1 = skip all particle systems", 0, 1, idCmdSystem::ArgCompletion_Integer<0,1> ); +idCVar r_screenSpaceParticles( "r_screenSpaceParticles", "1", CVAR_RENDERER | CVAR_INTEGER, "1 = render particles in a D3D12 screen-space pass", 0, 1, idCmdSystem::ArgCompletion_Integer<0,1> ); idCVar r_subviewOnly( "r_subviewOnly", "0", CVAR_RENDERER | CVAR_BOOL, "1 = don't render main view, allowing subviews to be debugged" ); idCVar r_shadows( "r_shadows", "1", CVAR_RENDERER | CVAR_BOOL | CVAR_ARCHIVE, "enable shadows" ); idCVar r_testARBProgram( "r_testARBProgram", "0", CVAR_RENDERER | CVAR_BOOL, "experiment with vertex/fragment programs" ); diff --git a/neo/engine/renderer/draw_common.cpp b/neo/engine/renderer/draw_common.cpp index 7f004241..a19398cb 100644 --- a/neo/engine/renderer/draw_common.cpp +++ b/neo/engine/renderer/draw_common.cpp @@ -367,6 +367,10 @@ void RB_T_FillDepthBuffer(const drawSurf_t* surf) { tri = surf->geo; shader = surf->material; + if ( r_screenSpaceParticles.GetBool() && ( surf->dsFlags & DSF_SCREEN_SPACE_PARTICLE ) ) { + return; + } + // update the clip plane if needed if (backEnd.viewDef->numClipPlanes && surf->space != backEnd.currentSpace) { GL_SelectTexture(1); @@ -802,6 +806,10 @@ void RB_STD_T_RenderShaderPasses( const drawSurf_t *surf ) { tri = surf->geo; shader = surf->material; + if ( r_screenSpaceParticles.GetBool() && ( surf->dsFlags & DSF_SCREEN_SPACE_PARTICLE ) ) { + return; + } + if ( !shader->HasAmbient() ) { return; } @@ -1504,6 +1512,121 @@ void RB_STD_FogAllLights( void ) { //========================================================================================= +static const shaderStage_t *RB_FirstDrawableParticleStage( const drawSurf_t *surf, idImage *&image, uint32_t &blendMode ) { + image = NULL; + blendMode = 0; + + if ( !surf || !surf->material || !surf->shaderRegisters ) { + return NULL; + } + + for ( int stage = 0; stage < surf->material->GetNumStages(); stage++ ) { + const shaderStage_t *pStage = surf->material->GetStage( stage ); + if ( pStage->lighting != SL_AMBIENT ) { + continue; + } + if ( surf->shaderRegisters[pStage->conditionRegister] == 0.0f ) { + continue; + } + + image = pStage->texture.image; + const int srcBlend = pStage->drawStateBits & GLS_SRCBLEND_BITS; + const int dstBlend = pStage->drawStateBits & GLS_DSTBLEND_BITS; + if ( srcBlend == GLS_SRCBLEND_ONE && dstBlend == GLS_DSTBLEND_ONE ) { + blendMode = 1; + } + return pStage; + } + + return NULL; +} + +static void RB_FillScreenSpaceParticleVertex( glScreenSpaceParticleVertex_t &dst, const idDrawVert &src, const viewEntity_t *space, const float *projectionMatrix, const float stageColor[4] ) { + idVec3 world; + idPlane eye, clip; + + R_LocalPointToGlobal( space->modelMatrix, src.xyz, world ); + R_TransformModelToClip( src.xyz, space->modelViewMatrix, projectionMatrix, eye, clip ); + + dst.clip[0] = clip[0]; + dst.clip[1] = clip[1]; + dst.clip[2] = 0.5f * ( clip[2] + clip[3] ); + dst.clip[3] = clip[3]; + dst.st[0] = src.st[0]; + dst.st[1] = src.st[1]; + dst.color[0] = ( src.color[0] * ( 1.0f / 255.0f ) ) * stageColor[0]; + dst.color[1] = ( src.color[1] * ( 1.0f / 255.0f ) ) * stageColor[1]; + dst.color[2] = ( src.color[2] * ( 1.0f / 255.0f ) ) * stageColor[2]; + dst.color[3] = ( src.color[3] * ( 1.0f / 255.0f ) ) * stageColor[3]; + dst.world[0] = world[0]; + dst.world[1] = world[1]; + dst.world[2] = world[2]; + dst.pad0 = 0.0f; +} + +static void RB_SubmitScreenSpaceParticleSurf( const drawSurf_t *surf ) { + if ( !surf || !surf->geo || !surf->space || !surf->material || !surf->geo->verts || !surf->geo->indexes ) { + return; + } + if ( !surf->geo->numIndexes || !surf->geo->numVerts ) { + return; + } + + idImage *image = NULL; + uint32_t blendMode = 0; + const shaderStage_t *stage = RB_FirstDrawableParticleStage( surf, image, blendMode ); + if ( !stage ) { + return; + } + + float stageColor[4]; + for ( int i = 0; i < 4; i++ ) { + stageColor[i] = surf->shaderRegisters[stage->color.registers[i]]; + } + + if ( image ) { + image->Bind(); + } else { + globalImages->whiteImage->Bind(); + image = globalImages->whiteImage; + } + + float projectionMatrix[16]; + memcpy( projectionMatrix, backEnd.viewDef->projectionMatrix, sizeof( projectionMatrix ) ); + if ( surf->space->weaponDepthHack ) { + projectionMatrix[14] *= 0.25f; + } + if ( surf->space->modelDepthHack != 0.0f ) { + projectionMatrix[14] -= surf->space->modelDepthHack; + } + + idList particleVerts; + particleVerts.SetNum( surf->geo->numIndexes, false ); + for ( int i = 0; i < surf->geo->numIndexes; i++ ) { + const int vertIndex = surf->geo->indexes[i]; + if ( vertIndex < 0 || vertIndex >= surf->geo->numVerts ) { + continue; + } + RB_FillScreenSpaceParticleVertex( particleVerts[i], surf->geo->verts[vertIndex], surf->space, projectionMatrix, stageColor ); + } + + glDrawScreenSpaceParticlesQD3D12( particleVerts.Ptr(), particleVerts.Num(), image->texnum, blendMode, 16.0f ); +} + +static void RB_STD_DrawScreenSpaceParticles( drawSurf_t **drawSurfs, int numDrawSurfs ) { + if ( !r_screenSpaceParticles.GetBool() || !backEnd.viewDef->viewEntitys ) { + return; + } + + RB_LogComment( "---------- RB_STD_DrawScreenSpaceParticles ----------\n" ); + + for ( int i = 0; i < numDrawSurfs; i++ ) { + if ( drawSurfs[i]->dsFlags & DSF_SCREEN_SPACE_PARTICLE ) { + RB_SubmitScreenSpaceParticleSurf( drawSurfs[i] ); + } + } +} + /* ============= RB_STD_DrawView @@ -1531,6 +1654,8 @@ void RB_STD_DrawView( void ) { // subviews RB_STD_FillDepthBuffer( drawSurfs, numDrawSurfs ); + RB_STD_DrawScreenSpaceParticles( drawSurfs, numDrawSurfs ); + // main light renderer RB_DXDrawInteractions(); diff --git a/neo/engine/renderer/draw_dx.cpp b/neo/engine/renderer/draw_dx.cpp index a42a26f1..55f9698f 100644 --- a/neo/engine/renderer/draw_dx.cpp +++ b/neo/engine/renderer/draw_dx.cpp @@ -160,6 +160,8 @@ void RB_DXDrawInteractions(void) QD3D12_SetToneMapBrightness(r_brightness.GetFloat()); QD3D12_SetFrameGenerationMultiplier(r_frameGen.GetInteger()); + idList screenParticleLights; + for (vLight = backEnd.viewDef->viewLights; vLight; vLight = vLight->next) { backEnd.vLight = vLight; @@ -240,12 +242,14 @@ void RB_DXDrawInteractions(void) } } glRaytracingLightingAddLight(&light); + screenParticleLights.Append(light); } } if (backEnd.viewDef->renderWorld) { backEnd.viewDef->renderWorld->SubmitWorldDecalsToScreenSpace(); + glSetScreenSpaceParticleLightsQD3D12(screenParticleLights.Ptr(), screenParticleLights.Num()); glLightScene(backEnd.viewDef->renderWorld->dxrWorldId); glRaytracingLightingClearLights(false); } diff --git a/neo/engine/renderer/tr_deform.cpp b/neo/engine/renderer/tr_deform.cpp index bc69766d..bd1fa351 100644 --- a/neo/engine/renderer/tr_deform.cpp +++ b/neo/engine/renderer/tr_deform.cpp @@ -1211,7 +1211,7 @@ static void R_ParticleDeform( drawSurf_t *surf, bool useArea ) { tri->ambientCache = vertexCache.AllocFrameTemp( tri->verts, tri->numVerts * sizeof( idDrawVert ) ); if ( tri->ambientCache ) { // add the drawsurf - R_AddDrawSurf( tri, surf->space, renderEntity, stage->material, surf->scissorRect ); + R_AddDrawSurf( tri, surf->space, renderEntity, stage->material, surf->scissorRect, DSF_SCREEN_SPACE_PARTICLE ); } } } diff --git a/neo/engine/renderer/tr_light.cpp b/neo/engine/renderer/tr_light.cpp index 788f4fda..a0a46317 100644 --- a/neo/engine/renderer/tr_light.cpp +++ b/neo/engine/renderer/tr_light.cpp @@ -1110,7 +1110,7 @@ R_AddDrawSurf ================= */ void R_AddDrawSurf( const srfTriangles_t *tri, const viewEntity_t *space, const renderEntity_t *renderEntity, - const idMaterial *shader, const idScreenRect &scissor ) { + const idMaterial *shader, const idScreenRect &scissor, int dsFlags ) { drawSurf_t *drawSurf; const float *shaderParms; static float refRegs[MAX_EXPRESSION_REGISTERS]; // don't put on stack, or VC++ will do a page touch @@ -1122,7 +1122,7 @@ void R_AddDrawSurf( const srfTriangles_t *tri, const viewEntity_t *space, const drawSurf->material = shader; drawSurf->scissorRect = scissor; drawSurf->sort = shader->GetSort() + tr.sortOffset; - drawSurf->dsFlags = 0; + drawSurf->dsFlags = dsFlags; // bumping this offset each time causes surfaces with equal sort orders to still // deterministically draw in the order they are added @@ -1351,8 +1351,11 @@ static void R_AddAmbientDrawsurfs( viewEntity_t *vEntity ) { R_CreateIndexCache( tri ); } + const int particleFlags = ( def->dynamicModel && + idStr::Icmp( model->Name(), "_ParametricParticle_Snapshot_" ) == 0 ) ? DSF_SCREEN_SPACE_PARTICLE : 0; + // add the surface for drawing - R_AddDrawSurf( tri, vEntity, &vEntity->entityDef->parms, shader, vEntity->scissorRect ); + R_AddDrawSurf( tri, vEntity, &vEntity->entityDef->parms, shader, vEntity->scissorRect, particleFlags ); // ambientViewCount is used to allow light interactions to be rejected // if the ambient surface isn't visible at all diff --git a/neo/engine/renderer/tr_local.h b/neo/engine/renderer/tr_local.h index a9823710..989fbe09 100644 --- a/neo/engine/renderer/tr_local.h +++ b/neo/engine/renderer/tr_local.h @@ -103,7 +103,8 @@ SURFACES // unique srfTriangles_t // drawSurf_t are always allocated and freed every frame, they are never cached -static const int DSF_VIEW_INSIDE_SHADOW = 1; +static const int DSF_VIEW_INSIDE_SHADOW = 1; +static const int DSF_SCREEN_SPACE_PARTICLE = 2; typedef struct drawSurf_s { const srfTriangles_t *geo; @@ -910,6 +911,7 @@ extern idCVar r_skipFogLights; // skip all fog lights extern idCVar r_skipSubviews; // 1 = don't render any mirrors / cameras / etc extern idCVar r_skipGuiShaders; // 1 = don't render any gui elements on surfaces extern idCVar r_skipParticles; // 1 = don't render any particles +extern idCVar r_screenSpaceParticles; // 1 = render particles in the D3D12 screen-space pass extern idCVar r_skipUpdates; // 1 = don't accept any entity or light updates, making everything static extern idCVar r_skipDeforms; // leave all deform materials in their original state extern idCVar r_skipDynamicTextures; // don't dynamically create textures @@ -1199,7 +1201,7 @@ viewEntity_t *R_SetEntityDefViewEntity( idRenderEntityLocal *def ); viewLight_t *R_SetLightDefViewLight( idRenderLightLocal *def ); void R_AddDrawSurf( const srfTriangles_t *tri, const viewEntity_t *space, const renderEntity_t *renderEntity, - const idMaterial *shader, const idScreenRect &scissor ); + const idMaterial *shader, const idScreenRect &scissor, int dsFlags = 0 ); void R_LinkLightSurf( const drawSurf_t **link, const srfTriangles_t *tri, const viewEntity_t *space, const idRenderLightLocal *light, const idMaterial *shader, const idScreenRect &scissor, bool viewInsideShadow ); diff --git a/neo/engine/sound/snd_decoder.cpp b/neo/engine/sound/snd_decoder.cpp index 517173c5..4041151b 100644 --- a/neo/engine/sound/snd_decoder.cpp +++ b/neo/engine/sound/snd_decoder.cpp @@ -521,7 +521,7 @@ int idSampleDecoderLocal::DecodeOGG( idSoundSample *sample, int sampleOffset44k, return 0; } if ( sample->nonCacheData == NULL ) { - assert( false ); // this should never happen +// assert( false ); // this should never happen failed = true; return 0; }