From 036d4b2f4b4ce89498b7394cc819d7dde7663a27 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 3 Nov 2025 23:21:09 +0100 Subject: [PATCH 01/43] Fix error when asset refs picker uses different types --- Source/Editor/CustomEditors/Editors/AssetRefEditor.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Source/Editor/CustomEditors/Editors/AssetRefEditor.cs b/Source/Editor/CustomEditors/Editors/AssetRefEditor.cs index 4fae716f1..b407d9a3c 100644 --- a/Source/Editor/CustomEditors/Editors/AssetRefEditor.cs +++ b/Source/Editor/CustomEditors/Editors/AssetRefEditor.cs @@ -123,6 +123,8 @@ namespace FlaxEditor.CustomEditors.Editors { base.Refresh(); + if (Picker == null) + return; var differentValues = HasDifferentValues; Picker.DifferentValues = differentValues; if (!differentValues) From 5d17d2509d0daf5c3790d1c3d8b26c9968e117a6 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 4 Nov 2025 12:42:08 +0100 Subject: [PATCH 02/43] Fix Global SDF gradient at borders --- Source/Shaders/GlobalSignDistanceField.hlsl | 89 ++++++++++++--------- 1 file changed, 51 insertions(+), 38 deletions(-) diff --git a/Source/Shaders/GlobalSignDistanceField.hlsl b/Source/Shaders/GlobalSignDistanceField.hlsl index a2f09de68..8075c081d 100644 --- a/Source/Shaders/GlobalSignDistanceField.hlsl +++ b/Source/Shaders/GlobalSignDistanceField.hlsl @@ -11,6 +11,7 @@ #define GLOBAL_SDF_WORLD_SIZE 60000.0f #define GLOBAL_SDF_MIN_VALID 0.9f #define GLOBAL_SDF_CHUNK_MARGIN_SCALE 4.0f +#define GLOBAL_SDF_SAMPLER SamplerLinearClamp // Global SDF data for a constant buffer struct GlobalSDFData @@ -74,6 +75,15 @@ void GetGlobalSDFCascadeUV(const GlobalSDFData data, uint cascade, float3 worldP textureUV = float3(((float)cascade + cascadeUV.x) / (float)data.CascadesCount, cascadeUV.y, cascadeUV.z); // Cascades are placed next to each other on X axis } +// Clamps Global SDF cascade UV to ensure it can be sued for gradient sampling (clamps first and last pixels). +void ClampGlobalSDFTextureGradientUV(const GlobalSDFData data, uint cascade, float texelOffset, inout float3 textureUV) +{ + float cascadeSizeUV = 1.0f / data.CascadesCount; + float cascadeUVStart = cascadeSizeUV * cascade + texelOffset; + float cascadeUVEnd = cascadeUVStart + cascadeSizeUV - texelOffset * 3; + textureUV.x = clamp(textureUV.x, cascadeUVStart, cascadeUVEnd); +} + // Gets the Global SDF cascade index for the given world location. uint GetGlobalSDFCascade(const GlobalSDFData data, float3 worldPosition) { @@ -96,7 +106,7 @@ float SampleGlobalSDFCascade(const GlobalSDFData data, Texture3D te float voxelSize = data.CascadeVoxelSize[cascade]; float chunkMargin = voxelSize * (GLOBAL_SDF_CHUNK_MARGIN_SCALE * GLOBAL_SDF_RASTERIZE_CHUNK_MARGIN); float maxDistanceTex = data.CascadeMaxDistanceTex[cascade]; - float distanceTex = tex.SampleLevel(SamplerLinearClamp, textureUV, 0) * maxDistanceTex; + float distanceTex = tex.SampleLevel(GLOBAL_SDF_SAMPLER, textureUV, 0) * maxDistanceTex; if (distanceTex < chunkMargin && all(cascadeUV > 0) && all(cascadeUV < 1)) distance = distanceTex; return distance; @@ -115,7 +125,7 @@ float SampleGlobalSDF(const GlobalSDFData data, Texture3D tex, floa float voxelSize = data.CascadeVoxelSize[cascade]; float chunkMargin = voxelSize * (GLOBAL_SDF_CHUNK_MARGIN_SCALE * GLOBAL_SDF_RASTERIZE_CHUNK_MARGIN); float maxDistanceTex = data.CascadeMaxDistanceTex[cascade]; - float distanceTex = tex.SampleLevel(SamplerLinearClamp, textureUV, 0); + float distanceTex = tex.SampleLevel(GLOBAL_SDF_SAMPLER, textureUV, 0); if (distanceTex < chunkMargin && all(cascadeUV > 0) && all(cascadeUV < 1)) { distance = distanceTex * maxDistanceTex; @@ -140,12 +150,12 @@ float SampleGlobalSDF(const GlobalSDFData data, Texture3D tex, Text float chunkSize = voxelSize * GLOBAL_SDF_RASTERIZE_CHUNK_SIZE; float chunkMargin = voxelSize * (GLOBAL_SDF_CHUNK_MARGIN_SCALE * GLOBAL_SDF_RASTERIZE_CHUNK_MARGIN); float maxDistanceMip = data.CascadeMaxDistanceMip[cascade]; - float distanceMip = mip.SampleLevel(SamplerLinearClamp, textureUV, 0); + float distanceMip = mip.SampleLevel(GLOBAL_SDF_SAMPLER, textureUV, 0); if (distanceMip < chunkSize && all(cascadeUV > 0) && all(cascadeUV < 1)) { distance = distanceMip * maxDistanceMip; float maxDistanceTex = data.CascadeMaxDistanceTex[cascade]; - float distanceTex = tex.SampleLevel(SamplerLinearClamp, textureUV, 0) * maxDistanceTex; + float distanceTex = tex.SampleLevel(GLOBAL_SDF_SAMPLER, textureUV, 0) * maxDistanceTex; if (distanceTex < chunkMargin) distance = distanceTex; break; @@ -169,16 +179,17 @@ float3 SampleGlobalSDFGradient(const GlobalSDFData data, Texture3D float voxelSize = data.CascadeVoxelSize[cascade]; float chunkMargin = voxelSize * (GLOBAL_SDF_CHUNK_MARGIN_SCALE * GLOBAL_SDF_RASTERIZE_CHUNK_MARGIN); float maxDistanceTex = data.CascadeMaxDistanceTex[cascade]; - float distanceTex = tex.SampleLevel(SamplerLinearClamp, textureUV, 0); + float distanceTex = tex.SampleLevel(GLOBAL_SDF_SAMPLER, textureUV, 0); if (distanceTex < chunkMargin && all(cascadeUV > 0) && all(cascadeUV < 1)) { float texelOffset = 1.0f / data.Resolution; - float xp = tex.SampleLevel(SamplerLinearClamp, float3(textureUV.x + texelOffset, textureUV.y, textureUV.z), 0).x; - float xn = tex.SampleLevel(SamplerLinearClamp, float3(textureUV.x - texelOffset, textureUV.y, textureUV.z), 0).x; - float yp = tex.SampleLevel(SamplerLinearClamp, float3(textureUV.x, textureUV.y + texelOffset, textureUV.z), 0).x; - float yn = tex.SampleLevel(SamplerLinearClamp, float3(textureUV.x, textureUV.y - texelOffset, textureUV.z), 0).x; - float zp = tex.SampleLevel(SamplerLinearClamp, float3(textureUV.x, textureUV.y, textureUV.z + texelOffset), 0).x; - float zn = tex.SampleLevel(SamplerLinearClamp, float3(textureUV.x, textureUV.y, textureUV.z - texelOffset), 0).x; + ClampGlobalSDFTextureGradientUV(data, cascade, texelOffset, textureUV); + float xp = tex.SampleLevel(GLOBAL_SDF_SAMPLER, float3(textureUV.x + texelOffset, textureUV.y, textureUV.z), 0).x; + float xn = tex.SampleLevel(GLOBAL_SDF_SAMPLER, float3(textureUV.x - texelOffset, textureUV.y, textureUV.z), 0).x; + float yp = tex.SampleLevel(GLOBAL_SDF_SAMPLER, float3(textureUV.x, textureUV.y + texelOffset, textureUV.z), 0).x; + float yn = tex.SampleLevel(GLOBAL_SDF_SAMPLER, float3(textureUV.x, textureUV.y - texelOffset, textureUV.z), 0).x; + float zp = tex.SampleLevel(GLOBAL_SDF_SAMPLER, float3(textureUV.x, textureUV.y, textureUV.z + texelOffset), 0).x; + float zn = tex.SampleLevel(GLOBAL_SDF_SAMPLER, float3(textureUV.x, textureUV.y, textureUV.z - texelOffset), 0).x; gradient = float3(xp - xn, yp - yn, zp - zn) * maxDistanceTex; distance = distanceTex * maxDistanceTex; break; @@ -203,33 +214,35 @@ float3 SampleGlobalSDFGradient(const GlobalSDFData data, Texture3D float chunkSize = voxelSize * GLOBAL_SDF_RASTERIZE_CHUNK_SIZE; float chunkMargin = voxelSize * (GLOBAL_SDF_CHUNK_MARGIN_SCALE * GLOBAL_SDF_RASTERIZE_CHUNK_MARGIN); float maxDistanceMip = data.CascadeMaxDistanceMip[cascade]; - float distanceMip = mip.SampleLevel(SamplerLinearClamp, textureUV, 0) * maxDistanceMip; + float distanceMip = mip.SampleLevel(GLOBAL_SDF_SAMPLER, textureUV, 0) * maxDistanceMip; if (distanceMip < chunkSize && all(cascadeUV > 0) && all(cascadeUV < 1)) { float maxDistanceTex = data.CascadeMaxDistanceTex[cascade]; - float distanceTex = tex.SampleLevel(SamplerLinearClamp, textureUV, 0) * maxDistanceTex; + float distanceTex = tex.SampleLevel(GLOBAL_SDF_SAMPLER, textureUV, 0) * maxDistanceTex; if (distanceTex < chunkMargin) { distance = distanceTex; float texelOffset = 1.0f / data.Resolution; - float xp = tex.SampleLevel(SamplerLinearClamp, float3(textureUV.x + texelOffset, textureUV.y, textureUV.z), 0).x; - float xn = tex.SampleLevel(SamplerLinearClamp, float3(textureUV.x - texelOffset, textureUV.y, textureUV.z), 0).x; - float yp = tex.SampleLevel(SamplerLinearClamp, float3(textureUV.x, textureUV.y + texelOffset, textureUV.z), 0).x; - float yn = tex.SampleLevel(SamplerLinearClamp, float3(textureUV.x, textureUV.y - texelOffset, textureUV.z), 0).x; - float zp = tex.SampleLevel(SamplerLinearClamp, float3(textureUV.x, textureUV.y, textureUV.z + texelOffset), 0).x; - float zn = tex.SampleLevel(SamplerLinearClamp, float3(textureUV.x, textureUV.y, textureUV.z - texelOffset), 0).x; + ClampGlobalSDFTextureGradientUV(data, cascade, texelOffset, textureUV); + float xp = tex.SampleLevel(GLOBAL_SDF_SAMPLER, float3(textureUV.x + texelOffset, textureUV.y, textureUV.z), 0).x; + float xn = tex.SampleLevel(GLOBAL_SDF_SAMPLER, float3(textureUV.x - texelOffset, textureUV.y, textureUV.z), 0).x; + float yp = tex.SampleLevel(GLOBAL_SDF_SAMPLER, float3(textureUV.x, textureUV.y + texelOffset, textureUV.z), 0).x; + float yn = tex.SampleLevel(GLOBAL_SDF_SAMPLER, float3(textureUV.x, textureUV.y - texelOffset, textureUV.z), 0).x; + float zp = tex.SampleLevel(GLOBAL_SDF_SAMPLER, float3(textureUV.x, textureUV.y, textureUV.z + texelOffset), 0).x; + float zn = tex.SampleLevel(GLOBAL_SDF_SAMPLER, float3(textureUV.x, textureUV.y, textureUV.z - texelOffset), 0).x; gradient = float3(xp - xn, yp - yn, zp - zn) * maxDistanceTex; } else { distance = distanceMip; float texelOffset = (float)GLOBAL_SDF_RASTERIZE_MIP_FACTOR / data.Resolution; - float xp = mip.SampleLevel(SamplerLinearClamp, float3(textureUV.x + texelOffset, textureUV.y, textureUV.z), 0).x; - float xn = mip.SampleLevel(SamplerLinearClamp, float3(textureUV.x - texelOffset, textureUV.y, textureUV.z), 0).x; - float yp = mip.SampleLevel(SamplerLinearClamp, float3(textureUV.x, textureUV.y + texelOffset, textureUV.z), 0).x; - float yn = mip.SampleLevel(SamplerLinearClamp, float3(textureUV.x, textureUV.y - texelOffset, textureUV.z), 0).x; - float zp = mip.SampleLevel(SamplerLinearClamp, float3(textureUV.x, textureUV.y, textureUV.z + texelOffset), 0).x; - float zn = mip.SampleLevel(SamplerLinearClamp, float3(textureUV.x, textureUV.y, textureUV.z - texelOffset), 0).x; + ClampGlobalSDFTextureGradientUV(data, cascade, texelOffset, textureUV); + float xp = mip.SampleLevel(GLOBAL_SDF_SAMPLER, float3(textureUV.x + texelOffset, textureUV.y, textureUV.z), 0).x; + float xn = mip.SampleLevel(GLOBAL_SDF_SAMPLER, float3(textureUV.x - texelOffset, textureUV.y, textureUV.z), 0).x; + float yp = mip.SampleLevel(GLOBAL_SDF_SAMPLER, float3(textureUV.x, textureUV.y + texelOffset, textureUV.z), 0).x; + float yn = mip.SampleLevel(GLOBAL_SDF_SAMPLER, float3(textureUV.x, textureUV.y - texelOffset, textureUV.z), 0).x; + float zp = mip.SampleLevel(GLOBAL_SDF_SAMPLER, float3(textureUV.x, textureUV.y, textureUV.z + texelOffset), 0).x; + float zn = mip.SampleLevel(GLOBAL_SDF_SAMPLER, float3(textureUV.x, textureUV.y, textureUV.z - texelOffset), 0).x; gradient = float3(xp - xn, yp - yn, zp - zn) * maxDistanceMip; } break; @@ -253,15 +266,14 @@ GlobalSDFHit RayTraceGlobalSDF(const GlobalSDFData data, Texture3D float4 cascadePosDistance = data.CascadePosDistance[cascade]; float voxelSize = data.CascadeVoxelSize[cascade]; float voxelExtent = voxelSize * 0.5f; - float3 worldPosition = trace.WorldPosition; // Skip until cascade that contains the start location - if (any(abs(worldPosition - cascadePosDistance.xyz) > cascadePosDistance.w)) + if (any(abs(trace.WorldPosition - cascadePosDistance.xyz) > cascadePosDistance.w)) continue; // Hit the cascade bounds to find the intersection points float traceStartBias = voxelSize * cascadeTraceStartBias; - float2 intersections = LineHitBox(worldPosition, traceEndPosition, cascadePosDistance.xyz - cascadePosDistance.www, cascadePosDistance.xyz + cascadePosDistance.www); + float2 intersections = LineHitBox(trace.WorldPosition, traceEndPosition, cascadePosDistance.xyz - cascadePosDistance.www, cascadePosDistance.xyz + cascadePosDistance.www); intersections.xy *= traceMaxDistance; intersections.x = max(intersections.x, traceStartBias); intersections.x = max(intersections.x, nextIntersectionStart); @@ -280,18 +292,18 @@ GlobalSDFHit RayTraceGlobalSDF(const GlobalSDFData data, Texture3D LOOP for (; step < 250 && stepTime < intersections.y && hit.HitTime < 0.0f; step++) { - float3 stepPosition = worldPosition + trace.WorldDirection * stepTime; + float3 stepPosition = trace.WorldPosition + trace.WorldDirection * stepTime; float stepScale = trace.StepScale; // Sample SDF float stepDistance, voxelSizeScale = (float)GLOBAL_SDF_RASTERIZE_MIP_FACTOR; float3 cascadeUV, textureUV; GetGlobalSDFCascadeUV(data, cascade, stepPosition, cascadeUV, textureUV); - float distanceMip = mip.SampleLevel(SamplerLinearClamp, textureUV, 0) * maxDistanceMip; + float distanceMip = mip.SampleLevel(GLOBAL_SDF_SAMPLER, textureUV, 0) * maxDistanceMip; if (distanceMip < chunkSize) { stepDistance = distanceMip; - float distanceTex = tex.SampleLevel(SamplerLinearClamp, textureUV, 0) * maxDistanceTex; + float distanceTex = tex.SampleLevel(GLOBAL_SDF_SAMPLER, textureUV, 0) * maxDistanceTex; if (distanceTex < chunkMargin) { stepDistance = distanceTex; @@ -301,7 +313,7 @@ GlobalSDFHit RayTraceGlobalSDF(const GlobalSDFData data, Texture3D } else { - // Assume no SDF nearby so perform a jump tto the next chunk + // Assume no SDF nearby so perform a jump to the next chunk stepDistance = chunkSize; voxelSizeScale = 1.0f; } @@ -318,12 +330,13 @@ GlobalSDFHit RayTraceGlobalSDF(const GlobalSDFData data, Texture3D { // Calculate hit normal from SDF gradient float texelOffset = 1.0f / data.Resolution; - float xp = tex.SampleLevel(SamplerLinearClamp, float3(textureUV.x + texelOffset, textureUV.y, textureUV.z), 0).x; - float xn = tex.SampleLevel(SamplerLinearClamp, float3(textureUV.x - texelOffset, textureUV.y, textureUV.z), 0).x; - float yp = tex.SampleLevel(SamplerLinearClamp, float3(textureUV.x, textureUV.y + texelOffset, textureUV.z), 0).x; - float yn = tex.SampleLevel(SamplerLinearClamp, float3(textureUV.x, textureUV.y - texelOffset, textureUV.z), 0).x; - float zp = tex.SampleLevel(SamplerLinearClamp, float3(textureUV.x, textureUV.y, textureUV.z + texelOffset), 0).x; - float zn = tex.SampleLevel(SamplerLinearClamp, float3(textureUV.x, textureUV.y, textureUV.z - texelOffset), 0).x; + ClampGlobalSDFTextureGradientUV(data, cascade, texelOffset, textureUV); + float xp = tex.SampleLevel(GLOBAL_SDF_SAMPLER, float3(textureUV.x + texelOffset, textureUV.y, textureUV.z), 0).x; + float xn = tex.SampleLevel(GLOBAL_SDF_SAMPLER, float3(textureUV.x - texelOffset, textureUV.y, textureUV.z), 0).x; + float yp = tex.SampleLevel(GLOBAL_SDF_SAMPLER, float3(textureUV.x, textureUV.y + texelOffset, textureUV.z), 0).x; + float yn = tex.SampleLevel(GLOBAL_SDF_SAMPLER, float3(textureUV.x, textureUV.y - texelOffset, textureUV.z), 0).x; + float zp = tex.SampleLevel(GLOBAL_SDF_SAMPLER, float3(textureUV.x, textureUV.y, textureUV.z + texelOffset), 0).x; + float zn = tex.SampleLevel(GLOBAL_SDF_SAMPLER, float3(textureUV.x, textureUV.y, textureUV.z - texelOffset), 0).x; hit.HitNormal = normalize(float3(xp - xn, yp - yn, zp - zn)); } } From 85b134b7be0411fcda366ee0ce6a969cdc978b7d Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 4 Nov 2025 13:27:21 +0100 Subject: [PATCH 03/43] Add improved Global SDF quality and precision of rasterization --- Content/Editor/Primitives/Cube.flax | 4 ++-- Source/Engine/Tools/ModelTool/ModelTool.cpp | 2 ++ Source/Shaders/SDF.shader | 2 ++ Source/Shaders/ThirdParty/TressFX/TressFXSDF.hlsl | 8 ++++++++ 4 files changed, 14 insertions(+), 2 deletions(-) diff --git a/Content/Editor/Primitives/Cube.flax b/Content/Editor/Primitives/Cube.flax index 6328a7bbc..b12f75e05 100644 --- a/Content/Editor/Primitives/Cube.flax +++ b/Content/Editor/Primitives/Cube.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b5a18bf58e0b93c8bba9459fd77a92898f0fae373b58d3acbcb9e36f66c89cd7 -size 23537 +oid sha256:8e8d210a74ae373793eaee1ddab1372a6a50a000c489f97b2258a09cd93cc2d0 +size 5031 diff --git a/Source/Engine/Tools/ModelTool/ModelTool.cpp b/Source/Engine/Tools/ModelTool/ModelTool.cpp index 0ae1e0639..78a3fe515 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.cpp @@ -366,9 +366,11 @@ bool ModelTool::GenerateModelSDF(Model* inputModel, const ModelData* modelData, return true; ModelBase::SDFData sdf; sdf.WorldUnitsPerVoxel = METERS_TO_UNITS(0.1f) / Math::Max(resolutionScale, 0.0001f); // 1 voxel per 10 centimeters +#if 0 const float boundsMargin = sdf.WorldUnitsPerVoxel * 0.5f; // Add half-texel margin around the mesh bounds.Minimum -= boundsMargin; bounds.Maximum += boundsMargin; +#endif const Float3 size = bounds.GetSize(); Int3 resolution(Float3::Ceil(Float3::Clamp(size / sdf.WorldUnitsPerVoxel, 4, 256))); Float3 uvwToLocalMul = size; diff --git a/Source/Shaders/SDF.shader b/Source/Shaders/SDF.shader index ba2626ff9..2239e2dbe 100644 --- a/Source/Shaders/SDF.shader +++ b/Source/Shaders/SDF.shader @@ -142,8 +142,10 @@ void CS_RasterizeTriangle(uint3 DispatchThreadId : SV_DispatchThreadID) int voxelIndex = GetVoxelIndex(voxelCoord); float3 voxelPos = GetVoxelPos(voxelCoord); float distance = SignedDistancePointToTriangle(voxelPos, v0, v1, v2); +#if 0 if (distance < -10.0f) // TODO: find a better way to reject negative distance from degenerate triangles that break SDF shape distance = abs(distance); +#endif InterlockedMin(SDF[voxelIndex], FloatFlip3(distance)); } } diff --git a/Source/Shaders/ThirdParty/TressFX/TressFXSDF.hlsl b/Source/Shaders/ThirdParty/TressFX/TressFXSDF.hlsl index 13c28f16d..7128ba9ca 100644 --- a/Source/Shaders/ThirdParty/TressFX/TressFXSDF.hlsl +++ b/Source/Shaders/ThirdParty/TressFX/TressFXSDF.hlsl @@ -52,6 +52,14 @@ uint IFloatFlip3(uint f2) float DistancePointToEdge(float3 p, float3 x0, float3 x1, out float3 n) { + // Hack to swap to ensure the order is correct (.x only for simplicity) + if (x0.x > x1.x) + { + float3 temp = x0; + x0 = x1; + x1 = temp; + } + float3 x10 = x1 - x0; float t = dot(x1 - p, x10) / dot(x10, x10); From 387c3ea2f4a517d609718080d8abc887da8efd03 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 4 Nov 2025 13:27:47 +0100 Subject: [PATCH 04/43] Add better debug view for Global SDF to include surface hit normal --- Source/Shaders/GlobalSignDistanceField.shader | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Source/Shaders/GlobalSignDistanceField.shader b/Source/Shaders/GlobalSignDistanceField.shader index f3a51addd..461dba08d 100644 --- a/Source/Shaders/GlobalSignDistanceField.shader +++ b/Source/Shaders/GlobalSignDistanceField.shader @@ -311,6 +311,7 @@ float4 PS_Debug(Quad_VS2PS input) : SV_Target float3 viewRay = lerp(lerp(ViewFrustumWorldRays[3], ViewFrustumWorldRays[0], input.TexCoord.x), lerp(ViewFrustumWorldRays[2], ViewFrustumWorldRays[1], input.TexCoord.x), 1 - input.TexCoord.y).xyz; viewRay = normalize(viewRay - ViewWorldPos); trace.Init(ViewWorldPos, viewRay, ViewNearPlane, ViewFarPlane); + trace.NeedsHitNormal = true; GlobalSDFHit hit = RayTraceGlobalSDF(GlobalSDF, GlobalSDFTex, GlobalSDFMip, trace); // Debug draw @@ -321,9 +322,14 @@ float4 PS_Debug(Quad_VS2PS input) : SV_Target else { // Debug draw SDF normals - float dst; - color.rgb = normalize(SampleGlobalSDFGradient(GlobalSDF, GlobalSDFTex, hit.GetHitPosition(trace), dst)) * 0.5f + 0.5f; + color.rgb = normalize(hit.HitNormal) * 0.5f + 0.5f; } +#elif 1 + else + { + // Composite with SDF normals + color.rgb *= saturate(normalize(hit.HitNormal) * 0.5f + 0.7f) + 0.1f; + } #endif return float4(color, 1); } From 0f701ec08e94f41718af188aefad3407b989946b Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 4 Nov 2025 13:30:18 +0100 Subject: [PATCH 05/43] Add force Mesh SDF rebuild when holiding `F` key and using Build All Meshes SDF optino in Editor menu --- Source/Editor/Editor.cs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/Source/Editor/Editor.cs b/Source/Editor/Editor.cs index 66328926d..58466e35d 100644 --- a/Source/Editor/Editor.cs +++ b/Source/Editor/Editor.cs @@ -1390,6 +1390,7 @@ namespace FlaxEditor public void BuildAllMeshesSDF() { var models = new List(); + var forceRebuild = Input.GetKey(KeyboardKeys.F); Scene.ExecuteOnGraph(node => { if (node is StaticModelNode staticModelNode && staticModelNode.Actor is StaticModel staticModel) @@ -1399,7 +1400,7 @@ namespace FlaxEditor model != null && !models.Contains(model) && !model.IsVirtual && - model.SDF.Texture == null) + (forceRebuild || model.SDF.Texture == null)) { models.Add(model); } @@ -1412,7 +1413,17 @@ namespace FlaxEditor { var model = models[i]; Log($"[{i}/{models.Count}] Generating SDF for {model}"); - if (!model.GenerateSDF()) + float resolutionScale = 1.0f, backfacesThreshold = 0.6f; + int lodIndex = 6; + bool useGPU = true; + var sdf = model.SDF; + if (sdf.Texture != null) + { + // Preserve options set on this model + resolutionScale = sdf.ResolutionScale; + lodIndex = sdf.LOD; + } + if (!model.GenerateSDF(resolutionScale, lodIndex, true, backfacesThreshold, useGPU)) model.Save(); } }); From 5ec860015d76b4874d178dc284f61e5de2cbd889 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 6 Nov 2025 21:00:16 +0100 Subject: [PATCH 06/43] Add minor adjustments --- Source/Editor/Modules/UIModule.cs | 1 + Source/Engine/Core/Math/CollisionsHelper.cpp | 9 ++------- Source/Engine/Core/Math/Ray.cpp | 5 ----- Source/Engine/Core/Math/Ray.h | 5 ++++- 4 files changed, 7 insertions(+), 13 deletions(-) diff --git a/Source/Editor/Modules/UIModule.cs b/Source/Editor/Modules/UIModule.cs index 0e482441e..c02992041 100644 --- a/Source/Editor/Modules/UIModule.cs +++ b/Source/Editor/Modules/UIModule.cs @@ -714,6 +714,7 @@ namespace FlaxEditor.Modules _menuToolsBuildCSGMesh = cm.AddButton("Build CSG mesh", inputOptions.BuildCSG, Editor.BuildCSG); _menuToolsBuildNavMesh = cm.AddButton("Build Nav Mesh", inputOptions.BuildNav, Editor.BuildNavMesh); _menuToolsBuildAllMeshesSDF = cm.AddButton("Build all meshes SDF", inputOptions.BuildSDF, Editor.BuildAllMeshesSDF); + _menuToolsBuildAllMeshesSDF.LinkTooltip("Generates Sign Distance Field texture for all meshes used in loaded scenes. Use with 'F' key pressed to force rebuild SDF for meshes with existing one."); cm.AddSeparator(); cm.AddButton("Game Cooker", Editor.Windows.GameCookerWin.FocusOrShow); _menuToolsCancelBuilding = cm.AddButton("Cancel building game", () => GameCooker.Cancel()); diff --git a/Source/Engine/Core/Math/CollisionsHelper.cpp b/Source/Engine/Core/Math/CollisionsHelper.cpp index 5a40fae6e..dee415e4a 100644 --- a/Source/Engine/Core/Math/CollisionsHelper.cpp +++ b/Source/Engine/Core/Math/CollisionsHelper.cpp @@ -620,14 +620,9 @@ bool Collision::RayIntersectsTriangle(const Ray& ray, const Vector3& a, const Ve Real rayDistance = edge2.X * distanceCrossEdge1.X + edge2.Y * distanceCrossEdge1.Y + edge2.Z * distanceCrossEdge1.Z; rayDistance *= inverseDeterminant; - // Check if the triangle is behind the ray origin - if (rayDistance < 0.0f) - { - return false; - } - + // Check if the triangle is in front the ray origin distance = rayDistance; - return true; + return rayDistance >= 0.0f; } bool CollisionsHelper::RayIntersectsTriangle(const Ray& ray, const Vector3& vertex1, const Vector3& vertex2, const Vector3& vertex3, Real& distance, Vector3& normal) diff --git a/Source/Engine/Core/Math/Ray.cpp b/Source/Engine/Core/Math/Ray.cpp index 414814c4a..5bee8c2c4 100644 --- a/Source/Engine/Core/Math/Ray.cpp +++ b/Source/Engine/Core/Math/Ray.cpp @@ -12,11 +12,6 @@ String Ray::ToString() const return String::Format(TEXT("{}"), *this); } -Vector3 Ray::GetPoint(Real distance) const -{ - return Position + Direction * distance; -} - Ray Ray::GetPickRay(float x, float y, const Viewport& viewport, const Matrix& vp) { Vector3 nearPoint(x, y, 0.0f); diff --git a/Source/Engine/Core/Math/Ray.h b/Source/Engine/Core/Math/Ray.h index cb2aa6419..65e9f1512 100644 --- a/Source/Engine/Core/Math/Ray.h +++ b/Source/Engine/Core/Math/Ray.h @@ -79,7 +79,10 @@ public: /// /// The distance from ray origin. /// The calculated point. - Vector3 GetPoint(Real distance) const; + FORCE_INLINE Vector3 GetPoint(Real distance) const + { + return Position + Direction * distance; + } /// /// Determines if there is an intersection between ray and a point. From 3a5bb81d3968593155b5609892b0444aade7e64d Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 6 Nov 2025 21:00:29 +0100 Subject: [PATCH 07/43] Add a new splash screen quote --- Source/Editor/Windows/SplashScreen.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/Editor/Windows/SplashScreen.cpp b/Source/Editor/Windows/SplashScreen.cpp index 7e2889ca1..54a1a7418 100644 --- a/Source/Editor/Windows/SplashScreen.cpp +++ b/Source/Editor/Windows/SplashScreen.cpp @@ -137,6 +137,7 @@ const Char* SplashScreenQuotes[] = TEXT("Good Luck Have Fun"), TEXT("GG Well Played"), TEXT("Now with documentation."), + TEXT("We do this not because it is easy,\nbut because we thought it would be easy"), }; SplashScreen::~SplashScreen() From 59643b2fb9dd5564f7406d57116e2574f945defa Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 6 Nov 2025 21:01:02 +0100 Subject: [PATCH 08/43] Add improved local-light shadow raytracing by starting ray from light, not surface --- Source/Shaders/GI/GlobalSurfaceAtlas.shader | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Source/Shaders/GI/GlobalSurfaceAtlas.shader b/Source/Shaders/GI/GlobalSurfaceAtlas.shader index 2efa638f0..387620c5c 100644 --- a/Source/Shaders/GI/GlobalSurfaceAtlas.shader +++ b/Source/Shaders/GI/GlobalSurfaceAtlas.shader @@ -165,11 +165,19 @@ float4 PS_Lighting(AtlasVertexOutput input) : SV_Target BRANCH if (NoL > 0) { +#if RADIAL_LIGHT + // Shot a ray from light to the texel to see if there is any occluder + GlobalSDFTrace trace; + trace.Init(Light.Position, -L, bias, toLightDst); + GlobalSDFHit hit = RayTraceGlobalSDF(GlobalSDF, GlobalSDFTex, GlobalSDFMip, trace, 1.0f); + shadowMask = hit.IsHit() && hit.HitTime < toLightDst - bias * 3 ? LightShadowsStrength : 1; +#else // Shot a ray from texel into the light to see if there is any occluder GlobalSDFTrace trace; trace.Init(gBuffer.WorldPos + gBuffer.Normal * shadowBias, L, bias, toLightDst - bias); GlobalSDFHit hit = RayTraceGlobalSDF(GlobalSDF, GlobalSDFTex, GlobalSDFMip, trace, 2.0f); shadowMask = hit.IsHit() ? LightShadowsStrength : 1; +#endif } else { From 62424215c1124cfb5a4ba2792ec105593636eb6d Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 9 Nov 2025 23:25:16 +0100 Subject: [PATCH 09/43] Fix crash due to missing asset reference inside `MeshAccelerationStructure` --- .../Tools/ModelTool/MeshAccelerationStructure.cpp | 13 +++++++++++++ .../Tools/ModelTool/MeshAccelerationStructure.h | 3 +++ 2 files changed, 16 insertions(+) diff --git a/Source/Engine/Tools/ModelTool/MeshAccelerationStructure.cpp b/Source/Engine/Tools/ModelTool/MeshAccelerationStructure.cpp index 4e551e445..11c449847 100644 --- a/Source/Engine/Tools/ModelTool/MeshAccelerationStructure.cpp +++ b/Source/Engine/Tools/ModelTool/MeshAccelerationStructure.cpp @@ -288,6 +288,15 @@ bool MeshAccelerationStructure::RayCastBVH(int32 node, const Ray& ray, Real& hit return hit; } +MeshAccelerationStructure::~MeshAccelerationStructure() +{ + for (auto& e : _meshes) + { + if (e.Asset) + e.Asset->RemoveReference(); + } +} + void MeshAccelerationStructure::Add(Model* model, int32 lodIndex) { PROFILE_CPU(); @@ -307,6 +316,8 @@ void MeshAccelerationStructure::Add(Model* model, int32 lodIndex) } auto& meshData = _meshes.AddOne(); + meshData.Asset = model; + model->AddReference(); if (model->IsVirtual()) { meshData.Indices = mesh.GetTriangleCount() * 3; @@ -350,6 +361,7 @@ void MeshAccelerationStructure::Add(const ModelData* modelData, int32 lodIndex, } auto& meshData = _meshes.AddOne(); + meshData.Asset = nullptr; meshData.Indices = mesh->Indices.Count(); meshData.Vertices = mesh->Positions.Count(); if (copy) @@ -370,6 +382,7 @@ void MeshAccelerationStructure::Add(const ModelData* modelData, int32 lodIndex, void MeshAccelerationStructure::Add(Float3* vb, int32 vertices, void* ib, int32 indices, bool use16BitIndex, bool copy) { auto& meshData = _meshes.AddOne(); + meshData.Asset = nullptr; if (copy) { meshData.VertexBuffer.Copy((const byte*)vb, vertices * sizeof(Float3)); diff --git a/Source/Engine/Tools/ModelTool/MeshAccelerationStructure.h b/Source/Engine/Tools/ModelTool/MeshAccelerationStructure.h index 7e158e742..c40ebc729 100644 --- a/Source/Engine/Tools/ModelTool/MeshAccelerationStructure.h +++ b/Source/Engine/Tools/ModelTool/MeshAccelerationStructure.h @@ -20,6 +20,7 @@ class FLAXENGINE_API MeshAccelerationStructure private: struct Mesh { + Model* Asset; BytesContainer IndexBuffer, VertexBuffer; int32 Indices, Vertices; bool Use16BitIndexBuffer; @@ -57,6 +58,8 @@ private: bool RayCastBVH(int32 node, const Ray& ray, Real& hitDistance, Vector3& hitNormal, Triangle& hitTriangle) const; public: + ~MeshAccelerationStructure(); + // Adds the model geometry for the build to the structure. void Add(Model* model, int32 lodIndex); From 4805dfbdbaf56c61d281f0be85074db8f42fbf5c Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 10 Nov 2025 15:02:33 +0100 Subject: [PATCH 10/43] Fix issues with model data storage when doing long actions in async (eg. SDF generation) --- Source/Editor/Tools/Terrain/TerrainTools.cpp | 5 ----- Source/Engine/Content/Assets/Model.cpp | 1 + Source/Engine/Content/Assets/ModelBase.cpp | 3 ++- Source/Engine/Content/Storage/FlaxStorage.cpp | 2 -- Source/Engine/Content/Storage/FlaxStorage.h | 6 +++++- Source/Engine/Graphics/Textures/TextureBase.cpp | 2 +- 6 files changed, 9 insertions(+), 10 deletions(-) diff --git a/Source/Editor/Tools/Terrain/TerrainTools.cpp b/Source/Editor/Tools/Terrain/TerrainTools.cpp index 065f7ffeb..861cb975b 100644 --- a/Source/Editor/Tools/Terrain/TerrainTools.cpp +++ b/Source/Editor/Tools/Terrain/TerrainTools.cpp @@ -74,11 +74,6 @@ struct TextureDataResult PixelFormat Format; Int2 Mip0Size; BytesContainer* Mip0DataPtr; - - TextureDataResult() - : Lock(FlaxStorage::LockData::Invalid) - { - } }; bool GetTextureDataForSampling(Texture* texture, TextureDataResult& data, bool hdr = false) diff --git a/Source/Engine/Content/Assets/Model.cpp b/Source/Engine/Content/Assets/Model.cpp index 829eaeb11..66ca0aa2e 100644 --- a/Source/Engine/Content/Assets/Model.cpp +++ b/Source/Engine/Content/Assets/Model.cpp @@ -262,6 +262,7 @@ bool Model::GenerateSDF(float resolutionScale, int32 lodIndex, bool cacheData, f LOG(Warning, "Cannot generate SDF for virtual models on a main thread."); return true; } + auto chunkLocks = Storage ? Storage->Lock() : FlaxStorage::LockData(); lodIndex = Math::Clamp(lodIndex, HighestResidentLODIndex(), LODs.Count() - 1); // Generate SDF diff --git a/Source/Engine/Content/Assets/ModelBase.cpp b/Source/Engine/Content/Assets/ModelBase.cpp index 599caa810..1847e96d9 100644 --- a/Source/Engine/Content/Assets/ModelBase.cpp +++ b/Source/Engine/Content/Assets/ModelBase.cpp @@ -61,7 +61,7 @@ public: model->GetLODData(_lodIndex, data); if (data.IsInvalid()) { - LOG(Warning, "Missing data chunk"); + LOG(Warning, "Missing data chunk with LOD{} for model '{}'", _lodIndex, model->ToString()); return true; } MemoryReadStream stream(data.Get(), data.Length()); @@ -234,6 +234,7 @@ bool ModelBase::Save(bool withMeshDataFromGpu, const StringView& path) LOG(Error, "To save virtual model asset you need to specify 'withMeshDataFromGpu' (it has no other storage container to get data)."); return true; } + auto chunkLocks = Storage ? Storage->Lock() : FlaxStorage::LockData(); ScopeLock lock(Locker); // Use a temporary chunks for data storage for virtual assets diff --git a/Source/Engine/Content/Storage/FlaxStorage.cpp b/Source/Engine/Content/Storage/FlaxStorage.cpp index 1c6be4971..6967bb5ad 100644 --- a/Source/Engine/Content/Storage/FlaxStorage.cpp +++ b/Source/Engine/Content/Storage/FlaxStorage.cpp @@ -75,8 +75,6 @@ FlaxChunk* FlaxChunk::Clone() const const int32 FlaxStorage::MagicCode = 1180124739; -FlaxStorage::LockData FlaxStorage::LockData::Invalid(nullptr); - struct Header { int32 MagicCode; diff --git a/Source/Engine/Content/Storage/FlaxStorage.h b/Source/Engine/Content/Storage/FlaxStorage.h index 6de462214..f01384b68 100644 --- a/Source/Engine/Content/Storage/FlaxStorage.h +++ b/Source/Engine/Content/Storage/FlaxStorage.h @@ -146,7 +146,6 @@ public: struct LockData { friend FlaxStorage; - static LockData Invalid; private: FlaxStorage* _storage; @@ -159,6 +158,11 @@ public: } public: + LockData() + : _storage(nullptr) + { + } + LockData(const LockData& other) : _storage(other._storage) { diff --git a/Source/Engine/Graphics/Textures/TextureBase.cpp b/Source/Engine/Graphics/Textures/TextureBase.cpp index 81b5f159c..eb838fa07 100644 --- a/Source/Engine/Graphics/Textures/TextureBase.cpp +++ b/Source/Engine/Graphics/Textures/TextureBase.cpp @@ -771,7 +771,7 @@ Task* TextureBase::RequestMipDataAsync(int32 mipIndex) FlaxStorage::LockData TextureBase::LockData() { - return _parent->Storage ? _parent->Storage->Lock() : FlaxStorage::LockData::Invalid; + return _parent->Storage ? _parent->Storage->Lock() : FlaxStorage::LockData(); } void TextureBase::GetMipData(int32 mipIndex, BytesContainer& data) const From c7997e0c2ffdbbded2d803172b7ad34eeb5db00b Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 10 Nov 2025 21:50:11 +0100 Subject: [PATCH 11/43] Fix potential error on missing reference object --- Source/Editor/Windows/Profiler/MemoryGPU.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Source/Editor/Windows/Profiler/MemoryGPU.cs b/Source/Editor/Windows/Profiler/MemoryGPU.cs index 74f14b584..11b8a1980 100644 --- a/Source/Editor/Windows/Profiler/MemoryGPU.cs +++ b/Source/Editor/Windows/Profiler/MemoryGPU.cs @@ -296,13 +296,15 @@ namespace FlaxEditor.Windows.Profiler var resources = _resources.Get(_memoryUsageChart.SelectedSampleIndex); if (resources == null || resources.Length == 0) return; - var resourcesOrdered = resources.OrderByDescending(x => x.MemoryUsage); + var resourcesOrdered = resources.OrderByDescending(x => x?.MemoryUsage ?? 0); // Add rows var rowColor2 = Style.Current.Background * 1.4f; int rowIndex = 0; foreach (var e in resourcesOrdered) { + if (e == null) + continue; ClickableRow row; if (_tableRowsCache.Count != 0) { From 49918a1067a710116dac558fa7ca09cee454db07 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Tue, 11 Nov 2025 21:59:16 +0200 Subject: [PATCH 12/43] Fix Windows dotnet root path detection --- Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs b/Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs index f8e215320..28c3958df 100644 --- a/Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs +++ b/Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs @@ -217,9 +217,17 @@ namespace Flax.Build using RegistryKey sdkVersionsKey = baseKey.OpenSubKey($@"SOFTWARE\WOW6432Node\dotnet\Setup\InstalledVersions\{arch}\sdk"); using RegistryKey runtimeKey = baseKey.OpenSubKey(@$"SOFTWARE\WOW6432Node\dotnet\Setup\InstalledVersions\{arch}\sharedfx\Microsoft.NETCore.App"); using RegistryKey hostKey = baseKey.OpenSubKey(@$"SOFTWARE\dotnet\Setup\InstalledVersions\{arch}\sharedhost"); - dotnetPath = (string)hostKey.GetValue("Path"); + dotnetPath = (string)hostKey?.GetValue("Path"); dotnetSdkVersions = sdkVersionsKey?.GetValueNames() ?? Enumerable.Empty(); dotnetRuntimeVersions = runtimeKey?.GetValueNames() ?? Enumerable.Empty(); + + if (string.IsNullOrEmpty(dotnetPath)) + { + // The sharedhost registry key seems to be deprecated, assume the default installation location instead + var defaultPath = Path.Combine(Path.GetPathRoot(Environment.SystemDirectory), "Program Files", "dotnet"); + if (File.Exists(Path.Combine(defaultPath, "dotnet.exe"))) + dotnetPath = defaultPath; + } } #pragma warning restore CA1416 break; From 91ee9f5e05625b4c81c1ee9182cf742f83b406e2 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 13 Nov 2025 22:05:23 +0100 Subject: [PATCH 13/43] Refactor Mesh SDF generation on GPU to use raytracing for more precise results --- Content/Editor/Primitives/Cube.flax | 2 +- Content/Shaders/GlobalSignDistanceField.flax | 4 +- Content/Shaders/SDF.flax | 4 +- Source/Editor/Windows/Assets/ModelWindow.cs | 12 +- .../DirectX/DX11/GPUTimerQueryDX11.cpp | 5 +- .../ModelTool/MeshAccelerationStructure.cpp | 233 ++++++++++++++-- .../ModelTool/MeshAccelerationStructure.h | 56 +++- Source/Engine/Tools/ModelTool/ModelTool.cpp | 262 +++++------------- Source/Shaders/Collisions.hlsl | 55 ++++ Source/Shaders/MeshAccelerationStructure.hlsl | 163 +++++++++++ Source/Shaders/SDF.shader | 237 ++++------------ .../ThirdParty/TressFX/TressFXSDF.hlsl | 129 --------- 12 files changed, 615 insertions(+), 547 deletions(-) create mode 100644 Source/Shaders/MeshAccelerationStructure.hlsl delete mode 100644 Source/Shaders/ThirdParty/TressFX/TressFXSDF.hlsl diff --git a/Content/Editor/Primitives/Cube.flax b/Content/Editor/Primitives/Cube.flax index b12f75e05..d082eaa5b 100644 --- a/Content/Editor/Primitives/Cube.flax +++ b/Content/Editor/Primitives/Cube.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8e8d210a74ae373793eaee1ddab1372a6a50a000c489f97b2258a09cd93cc2d0 +oid sha256:6a56dc14746606f0065d136ad0a69ae1aa41e8732ea380c657d75c187aa09f54 size 5031 diff --git a/Content/Shaders/GlobalSignDistanceField.flax b/Content/Shaders/GlobalSignDistanceField.flax index 2cdf124ca..590e8f3a9 100644 --- a/Content/Shaders/GlobalSignDistanceField.flax +++ b/Content/Shaders/GlobalSignDistanceField.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e593e15c500b14f6e49d5e4ca6156135fd84d9cc1072a4597ee8bcea77d9339e -size 13255 +oid sha256:064f54786958f109222c49cbc0358ff4f345b30010fcd5e8cc1fab7bdc68c4fe +size 13349 diff --git a/Content/Shaders/SDF.flax b/Content/Shaders/SDF.flax index 83009c326..5141c14dd 100644 --- a/Content/Shaders/SDF.flax +++ b/Content/Shaders/SDF.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1abacca0d63b575f0c88d54839f30ce4ec1bb8912478cb81ada8219d417001f9 -size 7891 +oid sha256:d2b1dc1523cb2140db7ce5fed6e97b09d7fcebbe6cc19fca7708b5b882267040 +size 4175 diff --git a/Source/Editor/Windows/Assets/ModelWindow.cs b/Source/Editor/Windows/Assets/ModelWindow.cs index 1dc30cae3..19fc932d5 100644 --- a/Source/Editor/Windows/Assets/ModelWindow.cs +++ b/Source/Editor/Windows/Assets/ModelWindow.cs @@ -90,25 +90,15 @@ namespace FlaxEditor.Windows.Assets var gpu = group.Checkbox("Bake on GPU", "If checked, SDF generation will be calculated using GPU on Compute Shader, otherwise CPU will use Job System. GPU generation is fast but result in artifacts in various meshes (eg. foliage)."); gpu.CheckBox.Checked = sdfOptions.GPU; + gpu.CheckBox.StateChanged += c => { Window._sdfOptions.GPU = c.Checked; }; var backfacesThresholdProp = group.AddPropertyItem("Backfaces Threshold", "Custom threshold (in range 0-1) for adjusting mesh internals detection based on the percentage of test rays hit triangle backfaces. Use lower value for more dense mesh."); var backfacesThreshold = backfacesThresholdProp.FloatValue(); - var backfacesThresholdLabel = backfacesThresholdProp.Labels.Last(); backfacesThreshold.ValueBox.MinValue = 0.001f; backfacesThreshold.ValueBox.MaxValue = 1.0f; backfacesThreshold.ValueBox.Value = sdfOptions.BackfacesThreshold; backfacesThreshold.ValueBox.BoxValueChanged += b => { Window._sdfOptions.BackfacesThreshold = b.Value; }; - // Toggle Backfaces Threshold visibility (CPU-only option) - gpu.CheckBox.StateChanged += c => - { - Window._sdfOptions.GPU = c.Checked; - backfacesThresholdLabel.Visible = !c.Checked; - backfacesThreshold.ValueBox.Visible = !c.Checked; - }; - backfacesThresholdLabel.Visible = !gpu.CheckBox.Checked; - backfacesThreshold.ValueBox.Visible = !gpu.CheckBox.Checked; - var lodIndex = group.IntegerValue("LOD Index", "Index of the model Level of Detail to use for SDF data building. By default uses the lowest quality LOD for fast building."); lodIndex.IntValue.MinValue = 0; lodIndex.IntValue.MaxValue = Asset.LODsCount - 1; diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUTimerQueryDX11.cpp b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUTimerQueryDX11.cpp index 4bfed7b01..5bb825e65 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUTimerQueryDX11.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUTimerQueryDX11.cpp @@ -92,9 +92,8 @@ float GPUTimerQueryDX11::GetResult() { if (!_finalized) { -#if BUILD_DEBUG - ASSERT(HasResult()); -#endif + if (!HasResult()) + return 0; UINT64 timeStart, timeEnd; auto context = _device->GetIM(); diff --git a/Source/Engine/Tools/ModelTool/MeshAccelerationStructure.cpp b/Source/Engine/Tools/ModelTool/MeshAccelerationStructure.cpp index 11c449847..2cc989059 100644 --- a/Source/Engine/Tools/ModelTool/MeshAccelerationStructure.cpp +++ b/Source/Engine/Tools/ModelTool/MeshAccelerationStructure.cpp @@ -3,17 +3,29 @@ #if COMPILE_WITH_MODEL_TOOL #include "MeshAccelerationStructure.h" +#include "Engine/Core/Log.h" #include "Engine/Core/Math/Math.h" #include "Engine/Content/Content.h" #include "Engine/Content/Assets/Model.h" +#include "Engine/Graphics/GPUBuffer.h" #include "Engine/Graphics/Models/ModelData.h" #include "Engine/Profiler/ProfilerCPU.h" -void MeshAccelerationStructure::BuildBVH(int32 node, int32 maxLeafSize, Array& scratch) +PACK_STRUCT(struct GPUBVH { + Float3 BoundsMin; + uint32 Index; + Float3 BoundsMax; + int32 Count; // Negative for non-leaf nodes +}); +static_assert(sizeof(GPUBVH) == sizeof(Float4) * 2, "Invalid BVH structure size for GPU."); + +void MeshAccelerationStructure::BuildBVH(int32 node, BVHBuild& build) { auto& root = _bvh[node]; ASSERT_LOW_LAYER(root.Leaf.IsLeaf); - if (root.Leaf.TriangleCount <= maxLeafSize) + if (build.MaxLeafSize > 0 && root.Leaf.TriangleCount <= build.MaxLeafSize) + return; + if (build.MaxDepth > 0 && build.NodeDepth >= build.MaxDepth) return; // Spawn two leaves @@ -64,8 +76,8 @@ RETRY: { uint16 I0, I1, I2; }; - scratch.Resize(root.Leaf.TriangleCount * sizeof(Tri)); - auto dst = (Tri*)scratch.Get(); + build.Scratch.Resize(root.Leaf.TriangleCount * sizeof(Tri)); + auto dst = (Tri*)build.Scratch.Get(); auto ib16 = meshData.IndexBuffer.Get(); for (int32 i = indexStart; i < indexEnd;) { @@ -90,13 +102,13 @@ RETRY: indexStart = 0; indexEnd = left.Leaf.TriangleCount * 3; for (int32 i = indexStart; i < indexEnd; i++) - left.Bounds.Merge(vb[((uint16*)scratch.Get())[i]]); + left.Bounds.Merge(vb[((uint16*)build.Scratch.Get())[i]]); right.Bounds = BoundingBox(vb[dst[root.Leaf.TriangleCount - 1].I0]); indexStart = left.Leaf.TriangleCount; indexEnd = root.Leaf.TriangleCount * 3; for (int32 i = indexStart; i < indexEnd; i++) - right.Bounds.Merge(vb[((uint16*)scratch.Get())[i]]); + right.Bounds.Merge(vb[((uint16*)build.Scratch.Get())[i]]); } else { @@ -104,8 +116,8 @@ RETRY: { uint32 I0, I1, I2; }; - scratch.Resize(root.Leaf.TriangleCount * sizeof(Tri)); - auto dst = (Tri*)scratch.Get(); + build.Scratch.Resize(root.Leaf.TriangleCount * sizeof(Tri)); + auto dst = (Tri*)build.Scratch.Get(); auto ib32 = meshData.IndexBuffer.Get(); for (int32 i = indexStart; i < indexEnd;) { @@ -130,17 +142,19 @@ RETRY: indexStart = 0; indexEnd = left.Leaf.TriangleCount * 3; for (int32 i = indexStart; i < indexEnd; i++) - left.Bounds.Merge(vb[((uint32*)scratch.Get())[i]]); + left.Bounds.Merge(vb[((uint32*)build.Scratch.Get())[i]]); right.Bounds = BoundingBox(vb[dst[root.Leaf.TriangleCount - 1].I0]); indexStart = left.Leaf.TriangleCount; indexEnd = root.Leaf.TriangleCount * 3; for (int32 i = indexStart; i < indexEnd; i++) - right.Bounds.Merge(vb[((uint32*)scratch.Get())[i]]); + right.Bounds.Merge(vb[((uint32*)build.Scratch.Get())[i]]); } ASSERT_LOW_LAYER(left.Leaf.TriangleCount + right.Leaf.TriangleCount == root.Leaf.TriangleCount); left.Leaf.TriangleIndex = root.Leaf.TriangleIndex; right.Leaf.TriangleIndex = left.Leaf.TriangleIndex + left.Leaf.TriangleCount; + build.MaxNodeTriangles = Math::Max(build.MaxNodeTriangles, (int32)right.Leaf.TriangleCount); + build.MaxNodeTriangles = Math::Max(build.MaxNodeTriangles, (int32)right.Leaf.TriangleCount); // Convert into a node root.Node.IsLeaf = 0; @@ -148,8 +162,11 @@ RETRY: root.Node.ChildrenCount = 2; // Split children - BuildBVH(childIndex, maxLeafSize, scratch); - BuildBVH(childIndex + 1, maxLeafSize, scratch); + build.NodeDepth++; + build.MaxNodeDepth = Math::Max(build.NodeDepth, build.MaxNodeDepth); + BuildBVH(childIndex, build); + BuildBVH(childIndex + 1, build); + build.NodeDepth--; } bool MeshAccelerationStructure::PointQueryBVH(int32 node, const Vector3& point, Real& hitDistance, Vector3& hitPoint, Triangle& hitTriangle) const @@ -160,7 +177,7 @@ bool MeshAccelerationStructure::PointQueryBVH(int32 node, const Vector3& point, { // Find closest triangle Vector3 p; - const Mesh& meshData = _meshes[root.Leaf.MeshIndex]; + const Mesh& meshData = _meshes.Get()[root.Leaf.MeshIndex]; const Float3* vb = meshData.VertexBuffer.Get(); const int32 indexStart = root.Leaf.TriangleIndex * 3; const int32 indexEnd = indexStart + root.Leaf.TriangleCount * 3; @@ -229,7 +246,7 @@ bool MeshAccelerationStructure::RayCastBVH(int32 node, const Ray& ray, Real& hit if (root.Leaf.IsLeaf) { // Ray cast along triangles in the leaf - const Mesh& meshData = _meshes[root.Leaf.MeshIndex]; + const Mesh& meshData = _meshes.Get()[root.Leaf.MeshIndex]; const Float3* vb = meshData.VertexBuffer.Get(); const int32 indexStart = root.Leaf.TriangleIndex * 3; const int32 indexEnd = indexStart + root.Leaf.TriangleCount * 3; @@ -381,6 +398,7 @@ void MeshAccelerationStructure::Add(const ModelData* modelData, int32 lodIndex, void MeshAccelerationStructure::Add(Float3* vb, int32 vertices, void* ib, int32 indices, bool use16BitIndex, bool copy) { + ASSERT(vertices % 3 == 0); auto& meshData = _meshes.AddOne(); meshData.Asset = nullptr; if (copy) @@ -395,43 +413,122 @@ void MeshAccelerationStructure::Add(Float3* vb, int32 vertices, void* ib, int32 meshData.Vertices = vertices; meshData.Indices = indices; meshData.Use16BitIndexBuffer = use16BitIndex; + BoundingBox::FromPoints(meshData.VertexBuffer.Get(), vertices, meshData.Bounds); } -void MeshAccelerationStructure::BuildBVH(int32 maxLeafSize) +void MeshAccelerationStructure::MergeMeshes(bool force16BitIndexBuffer) +{ + if (_meshes.Count() == 0) + return; + if (_meshes.Count() == 1 && (!force16BitIndexBuffer || !_meshes[0].Use16BitIndexBuffer)) + return; + PROFILE_CPU(); + auto meshes = _meshes; + _meshes.Clear(); + _meshes.Resize(1); + auto& mesh = _meshes[0]; + mesh.Asset = nullptr; + mesh.Use16BitIndexBuffer = true; + mesh.Indices = 0; + mesh.Vertices = 0; + mesh.Bounds = meshes[0].Bounds; + for (auto& e : meshes) + { + if (!e.Use16BitIndexBuffer) + mesh.Use16BitIndexBuffer = false; + mesh.Vertices += e.Vertices; + mesh.Indices += e.Indices; + BoundingBox::Merge(mesh.Bounds, e.Bounds, mesh.Bounds); + } + mesh.Use16BitIndexBuffer &= mesh.Indices <= MAX_uint16 && !force16BitIndexBuffer; + mesh.VertexBuffer.Allocate(mesh.Vertices * sizeof(Float3)); + mesh.IndexBuffer.Allocate(mesh.Indices * sizeof(uint32)); + int32 vertexCounter = 0, indexCounter = 0; + for (auto& e : meshes) + { + Platform::MemoryCopy(mesh.VertexBuffer.Get() + vertexCounter * sizeof(Float3), e.VertexBuffer.Get(), e.Vertices * sizeof(Float3)); + if (e.Use16BitIndexBuffer) + { + for (int32 i = 0; i < e.Indices; i++) + { + uint16 index = ((uint16*)e.IndexBuffer.Get())[i]; + ((uint32*)mesh.IndexBuffer.Get())[indexCounter + i] = vertexCounter + index; + } + } + else + { + for (int32 i = 0; i < e.Indices; i++) + { + uint16 index = ((uint32*)e.IndexBuffer.Get())[i]; + ((uint32*)mesh.IndexBuffer.Get())[indexCounter + i] = vertexCounter + index; + } + } + vertexCounter += e.Vertices; + indexCounter += e.Indices; + if (e.Asset) + e.Asset->RemoveReference(); + } +} + +void MeshAccelerationStructure::BuildBVH(int32 maxLeafSize, int32 maxDepth) { if (_meshes.Count() == 0) return; PROFILE_CPU(); + BVHBuild build; + build.MaxLeafSize = maxLeafSize; + build.MaxDepth = maxDepth; + // Estimate memory usage int32 trianglesCount = 0; for (const Mesh& meshData : _meshes) trianglesCount += meshData.Indices / 3; _bvh.Clear(); - _bvh.EnsureCapacity(trianglesCount / maxLeafSize); + _bvh.EnsureCapacity(trianglesCount / Math::Max(maxLeafSize, 16)); - // Init with the root node and all meshes as leaves - auto& root = _bvh.AddOne(); - root.Node.IsLeaf = 0; - root.Node.ChildIndex = 1; - root.Node.ChildrenCount = _meshes.Count(); - root.Bounds = _meshes[0].Bounds; - for (int32 i = 0; i < _meshes.Count(); i++) + // Skip using root node if BVH contains only one mesh + if (_meshes.Count() == 1) { - const Mesh& meshData = _meshes[i]; + const Mesh& meshData = _meshes.First(); auto& child = _bvh.AddOne(); child.Leaf.IsLeaf = 1; - child.Leaf.MeshIndex = i; + child.Leaf.MeshIndex = 0; child.Leaf.TriangleIndex = 0; child.Leaf.TriangleCount = meshData.Indices / 3; child.Bounds = meshData.Bounds; - BoundingBox::Merge(root.Bounds, meshData.Bounds, root.Bounds); + Array scratch; + BuildBVH(0, build); + } + else + { + // Init with the root node and all meshes as leaves + auto& root = _bvh.AddOne(); + root.Node.IsLeaf = 0; + root.Node.ChildIndex = 1; + root.Node.ChildrenCount = _meshes.Count(); + root.Bounds = _meshes[0].Bounds; + for (int32 i = 0; i < _meshes.Count(); i++) + { + const Mesh& meshData = _meshes[i]; + auto& child = _bvh.AddOne(); + child.Leaf.IsLeaf = 1; + child.Leaf.MeshIndex = i; + child.Leaf.TriangleIndex = 0; + child.Leaf.TriangleCount = meshData.Indices / 3; + child.Bounds = meshData.Bounds; + BoundingBox::Merge(root.Bounds, meshData.Bounds, root.Bounds); + } + + // Sub-divide mesh nodes into smaller leaves + build.MaxNodeDepth = build.MaxDepth = 2; + Array scratch; + for (int32 i = 0; i < _meshes.Count(); i++) + BuildBVH(i + 1, build); + build.NodeDepth = 0; } - // Sub-divide mesh nodes into smaller leaves - Array scratch; - for (int32 i = 0; i < _meshes.Count(); i++) - BuildBVH(i + 1, maxLeafSize, scratch); + LOG(Info, "BVH nodes: {}, max depth: {}, max triangles: {}", _bvh.Count(), build.MaxNodeDepth, build.MaxNodeTriangles); } bool MeshAccelerationStructure::PointQuery(const Vector3& point, Real& hitDistance, Vector3& hitPoint, Triangle& hitTriangle, Real maxDistance) const @@ -579,4 +676,80 @@ bool MeshAccelerationStructure::RayCast(const Ray& ray, Real& hitDistance, Vecto } } +MeshAccelerationStructure::GPU::~GPU() +{ + SAFE_DELETE_GPU_RESOURCE(BVHBuffer); + SAFE_DELETE_GPU_RESOURCE(VertexBuffer); + SAFE_DELETE_GPU_RESOURCE(IndexBuffer); +} + +MeshAccelerationStructure::GPU::operator bool() const +{ + // Index buffer is initialized as last one so all other buffers are fine too + return IndexBuffer && IndexBuffer->GetSize() != 0; +} + +MeshAccelerationStructure::GPU MeshAccelerationStructure::ToGPU() +{ + PROFILE_CPU(); + GPU gpu; + + // GPU BVH operates on a single mesh with 32-bit indices + MergeMeshes(true); + + // Construct BVH + const int32 BVH_STACK_SIZE = 32; // This must match HLSL shader + BuildBVH(0, BVH_STACK_SIZE); + + // Upload BVH + { + Array bvhData; + bvhData.Resize(_bvh.Count()); + for (int32 i = 0; i < _bvh.Count(); i++) + { + const auto& src = _bvh.Get()[i]; + auto& dst = bvhData.Get()[i]; + dst.BoundsMin = src.Bounds.Minimum; + dst.BoundsMax = src.Bounds.Maximum; + if (src.Leaf.IsLeaf) + { + dst.Index = src.Leaf.TriangleIndex * 3; + dst.Count = src.Leaf.TriangleCount * 3; + } + else + { + dst.Index = src.Node.ChildIndex; + dst.Count = -(int32)src.Node.ChildrenCount; // Mark as non-leaf + ASSERT(src.Node.ChildrenCount == 2); // GPU shader is hardcoded for 2 children per node + } + } + gpu.BVHBuffer = GPUBuffer::New(); + auto desc =GPUBufferDescription::Structured(_bvh.Count(), sizeof(GPUBVH)); + desc.InitData = bvhData.Get(); + if (gpu.BVHBuffer->Init(desc)) + return gpu; + } + + // Upload vertex buffer + { + const Mesh& mesh = _meshes[0]; + gpu.VertexBuffer = GPUBuffer::New(); + auto desc = GPUBufferDescription::Raw(mesh.Vertices * sizeof(Float3), GPUBufferFlags::ShaderResource); + desc.InitData = mesh.VertexBuffer.Get(); + if (gpu.VertexBuffer->Init(desc)) + return gpu; + } + + // Upload index buffer + { + const Mesh& mesh = _meshes[0]; + gpu.IndexBuffer = GPUBuffer::New(); + auto desc = GPUBufferDescription::Raw(mesh.Indices * sizeof(uint32), GPUBufferFlags::ShaderResource); + desc.InitData = mesh.IndexBuffer.Get(); + gpu.IndexBuffer->Init(desc); + } + + return gpu; +} + #endif diff --git a/Source/Engine/Tools/ModelTool/MeshAccelerationStructure.h b/Source/Engine/Tools/ModelTool/MeshAccelerationStructure.h index c40ebc729..60ff772f7 100644 --- a/Source/Engine/Tools/ModelTool/MeshAccelerationStructure.h +++ b/Source/Engine/Tools/ModelTool/MeshAccelerationStructure.h @@ -11,6 +11,7 @@ class Model; class ModelData; +class GPUBuffer; /// /// Acceleration Structure utility for robust ray tracing mesh geometry with optimized data structure. @@ -50,10 +51,19 @@ private: }; }; + struct BVHBuild + { + int32 MaxLeafSize, MaxDepth; + int32 NodeDepth = 0; + int32 MaxNodeDepth = 0; + int32 MaxNodeTriangles = 0; + Array Scratch; + }; + Array> _meshes; Array _bvh; - void BuildBVH(int32 node, int32 maxLeafSize, Array& scratch); + void BuildBVH(int32 node, BVHBuild& build); bool PointQueryBVH(int32 node, const Vector3& point, Real& hitDistance, Vector3& hitPoint, Triangle& hitTriangle) const; bool RayCastBVH(int32 node, const Ray& ray, Real& hitDistance, Vector3& hitNormal, Triangle& hitTriangle) const; @@ -69,14 +79,56 @@ public: // Adds the triangles geometry for the build to the structure. void Add(Float3* vb, int32 vertices, void* ib, int32 indices, bool use16BitIndex, bool copy = false); + // Merges all added meshes into a single mesh (to reduce number of BVH nodes). Required for GPU BVH build. + void MergeMeshes(bool force16BitIndexBuffer = false); + // Builds Bounding Volume Hierarchy (BVH) structure for accelerated geometry queries. - void BuildBVH(int32 maxLeafSize = 16); + void BuildBVH(int32 maxLeafSize = 16, int32 maxDepth = 0); // Queries the closest triangle. bool PointQuery(const Vector3& point, Real& hitDistance, Vector3& hitPoint, Triangle& hitTriangle, Real maxDistance = MAX_Real) const; // Ray traces the triangles. bool RayCast(const Ray& ray, Real& hitDistance, Vector3& hitNormal, Triangle& hitTriangle, Real maxDistance = MAX_Real) const; + +public: + struct GPU + { + GPUBuffer* BVHBuffer; + GPUBuffer* VertexBuffer; + GPUBuffer* IndexBuffer; + bool Valid; + + GPU() + : BVHBuffer(nullptr) + , VertexBuffer(nullptr) + , IndexBuffer(nullptr) + { + } + + GPU(GPU&& other) noexcept + : BVHBuffer(other.BVHBuffer) + , VertexBuffer(other.VertexBuffer) + , IndexBuffer(other.IndexBuffer) + { + other.BVHBuffer = nullptr; + other.VertexBuffer = nullptr; + other.IndexBuffer = nullptr; + } + + GPU& operator=(GPU other) + { + Swap(*this, other); + return *this; + } + + ~GPU(); + + operator bool() const; + }; + + // Converts the acceleration structure data to GPU format for raytracing inside a shader. + GPU ToGPU(); }; #endif diff --git a/Source/Engine/Tools/ModelTool/ModelTool.cpp b/Source/Engine/Tools/ModelTool/ModelTool.cpp index 78a3fe515..b3b622942 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.cpp @@ -14,6 +14,7 @@ #include "Engine/Threading/Threading.h" #include "Engine/Graphics/GPUDevice.h" #include "Engine/Graphics/GPUBuffer.h" +#include "Engine/Graphics/GPUTimerQuery.h" #include "Engine/Graphics/RenderTools.h" #include "Engine/Graphics/Async/GPUTask.h" #include "Engine/Graphics/Shaders/GPUShader.h" @@ -81,14 +82,18 @@ class GPUModelSDFTask : public GPUTask { ConditionVariable* _signal; AssetReference _shader; + MeshAccelerationStructure* _scene; Model* _inputModel; const ModelData* _modelData; int32 _lodIndex; + float _backfacesThreshold; Int3 _resolution; ModelBase::SDFData* _sdf; - GPUBuffer *_sdfSrc, *_sdfDst; GPUTexture* _sdfResult; Float3 _xyzToLocalMul, _xyzToLocalAdd; +#if GPU_ALLOW_PROFILE_EVENTS + GPUTimerQuery* _timerQuery; +#endif const uint32 ThreadGroupSize = 64; GPU_CB_STRUCT(Data { @@ -96,7 +101,7 @@ class GPUModelSDFTask : public GPUTask uint32 ResolutionSize; float MaxDistance; uint32 VertexStride; - int32 Index16bit; + float BackfacesThreshold; uint32 TriangleCount; Float3 VoxelToPosMul; float WorldUnitsPerVoxel; @@ -105,47 +110,46 @@ class GPUModelSDFTask : public GPUTask }); public: - GPUModelSDFTask(ConditionVariable& signal, Model* inputModel, const ModelData* modelData, int32 lodIndex, const Int3& resolution, ModelBase::SDFData* sdf, GPUTexture* sdfResult, const Float3& xyzToLocalMul, const Float3& xyzToLocalAdd) - : GPUTask(Type::Custom) + GPUModelSDFTask(ConditionVariable& signal, MeshAccelerationStructure* scene, Model* inputModel, const ModelData* modelData, int32 lodIndex, const Int3& resolution, ModelBase::SDFData* sdf, GPUTexture* sdfResult, const Float3& xyzToLocalMul, const Float3& xyzToLocalAdd, float backfacesThreshold) + : GPUTask(Type::Custom, GPU_ALLOW_PROFILE_EVENTS ? 4 : GPU_ASYNC_LATENCY) // Fix timer query result reading with some more latency , _signal(&signal) , _shader(Content::LoadAsyncInternal(TEXT("Shaders/SDF"))) + , _scene(scene) , _inputModel(inputModel) , _modelData(modelData) , _lodIndex(lodIndex) + , _backfacesThreshold(backfacesThreshold) , _resolution(resolution) , _sdf(sdf) - , _sdfSrc(GPUBuffer::New()) - , _sdfDst(GPUBuffer::New()) , _sdfResult(sdfResult) , _xyzToLocalMul(xyzToLocalMul) , _xyzToLocalAdd(xyzToLocalAdd) - { -#if GPU_ENABLE_RESOURCE_NAMING - _sdfSrc->SetName(TEXT("SDFSrc")); - _sdfDst->SetName(TEXT("SDFDst")); +#if GPU_ALLOW_PROFILE_EVENTS + , _timerQuery(GPUDevice::Instance->CreateTimerQuery()) #endif + { } ~GPUModelSDFTask() { - SAFE_DELETE_GPU_RESOURCE(_sdfSrc); - SAFE_DELETE_GPU_RESOURCE(_sdfDst); +#if GPU_ALLOW_PROFILE_EVENTS + SAFE_DELETE_GPU_RESOURCE(_timerQuery); +#endif } Result run(GPUTasksContext* tasksContext) override { PROFILE_GPU_CPU("GPUModelSDFTask"); GPUContext* context = tasksContext->GPU; +#if GPU_ALLOW_PROFILE_EVENTS + _timerQuery->Begin(); +#endif // Allocate resources if (_shader == nullptr || _shader->WaitForLoaded()) return Result::Failed; GPUShader* shader = _shader->GetShader(); const uint32 resolutionSize = _resolution.X * _resolution.Y * _resolution.Z; - auto desc = GPUBufferDescription::Typed(resolutionSize, PixelFormat::R32_UInt, true); - // TODO: use transient texture (single frame) - if (_sdfSrc->Init(desc) || _sdfDst->Init(desc)) - return Result::Failed; auto cb = shader->GetCB(0); Data data; data.Resolution = _resolution; @@ -154,6 +158,13 @@ public: data.WorldUnitsPerVoxel = _sdf->WorldUnitsPerVoxel; data.VoxelToPosMul = _xyzToLocalMul; data.VoxelToPosAdd = _xyzToLocalAdd; + data.BackfacesThreshold = _backfacesThreshold - 0.05f; // Bias a bit + + // Send BVH to the GPU + auto bvh = _scene->ToGPU(); + CHECK_RETURN(bvh.BVHBuffer && bvh.VertexBuffer && bvh.IndexBuffer, Result::Failed); + data.VertexStride = sizeof(Float3); + data.TriangleCount = bvh.IndexBuffer->GetElementsCount() / 3; // Dispatch in 1D and fallback to 2D when using large resolution Int3 threadGroups(Math::CeilToInt((float)resolutionSize / ThreadGroupSize), 1, 1); @@ -165,159 +176,34 @@ public: } data.ThreadGroupsX = threadGroups.X; - // Init SDF volume + // Init constants context->BindCB(0, cb); context->UpdateCB(cb, &data); - context->BindUA(0, _sdfSrc->View()); - context->Dispatch(shader->GetCS("CS_Init"), threadGroups.X, threadGroups.Y, threadGroups.Z); - // Rendering input triangles into the SDF volume - if (_inputModel) - { - PROFILE_GPU_CPU_NAMED("Rasterize"); - const ModelLOD& lod = _inputModel->LODs[Math::Clamp(_lodIndex, _inputModel->HighestResidentLODIndex(), _inputModel->LODs.Count() - 1)]; - GPUBuffer *vbTemp = nullptr, *ibTemp = nullptr; - for (int32 i = 0; i < lod.Meshes.Count(); i++) - { - const Mesh& mesh = lod.Meshes[i]; - const MaterialSlot& materialSlot = _inputModel->MaterialSlots[mesh.GetMaterialSlotIndex()]; - if (materialSlot.Material && !materialSlot.Material->WaitForLoaded()) - { - // Skip transparent materials - if (materialSlot.Material->GetInfo().BlendMode != MaterialBlendMode::Opaque) - continue; - } - - GPUBuffer* vb = mesh.GetVertexBuffer(0); - GPUBuffer* ib = mesh.GetIndexBuffer(); - data.Index16bit = mesh.Use16BitIndexBuffer() ? 1 : 0; - data.VertexStride = vb->GetStride(); - data.TriangleCount = mesh.GetTriangleCount(); - const uint32 groups = Math::CeilToInt((float)data.TriangleCount / ThreadGroupSize); - if (groups > GPU_MAX_CS_DISPATCH_THREAD_GROUPS) - { - // TODO: support larger meshes via 2D dispatch - LOG(Error, "Not supported mesh with {} triangles.", data.TriangleCount); - continue; - } - context->UpdateCB(cb, &data); - if (!EnumHasAllFlags(vb->GetDescription().Flags, GPUBufferFlags::RawBuffer | GPUBufferFlags::ShaderResource)) - { - desc = GPUBufferDescription::Raw(vb->GetSize(), GPUBufferFlags::ShaderResource); - // TODO: use transient buffer (single frame) - if (!vbTemp) - { - vbTemp = GPUBuffer::New(); -#if GPU_ENABLE_RESOURCE_NAMING - vbTemp->SetName(TEXT("SDFvb")); -#endif - } - vbTemp->Init(desc); - context->CopyBuffer(vbTemp, vb, desc.Size); - vb = vbTemp; - } - if (!EnumHasAllFlags(ib->GetDescription().Flags, GPUBufferFlags::RawBuffer | GPUBufferFlags::ShaderResource)) - { - desc = GPUBufferDescription::Raw(ib->GetSize(), GPUBufferFlags::ShaderResource); - // TODO: use transient buffer (single frame) - if (!ibTemp) - { - ibTemp = GPUBuffer::New(); -#if GPU_ENABLE_RESOURCE_NAMING - ibTemp->SetName(TEXT("SDFib")); -#endif - } - ibTemp->Init(desc); - context->CopyBuffer(ibTemp, ib, desc.Size); - ib = ibTemp; - } - context->BindSR(0, vb->View()); - context->BindSR(1, ib->View()); - context->Dispatch(shader->GetCS("CS_RasterizeTriangle"), groups, 1, 1); - } - SAFE_DELETE_GPU_RESOURCE(vbTemp); - SAFE_DELETE_GPU_RESOURCE(ibTemp); - } - else if (_modelData) - { - PROFILE_GPU_CPU_NAMED("Rasterize"); - const ModelLodData& lod = _modelData->LODs[Math::Clamp(_lodIndex, 0, _modelData->LODs.Count() - 1)]; - auto vb = GPUBuffer::New(); - auto ib = GPUBuffer::New(); -#if GPU_ENABLE_RESOURCE_NAMING - vb->SetName(TEXT("SDFvb")); - ib->SetName(TEXT("SDFib")); -#endif - for (int32 i = 0; i < lod.Meshes.Count(); i++) - { - const MeshData* mesh = lod.Meshes[i]; - const MaterialSlotEntry& materialSlot = _modelData->Materials[mesh->MaterialSlotIndex]; - auto material = Content::LoadAsync(materialSlot.AssetID); - if (material && !material->WaitForLoaded()) - { - // Skip transparent materials - if (material->GetInfo().BlendMode != MaterialBlendMode::Opaque) - continue; - } - - data.Index16bit = 0; - data.VertexStride = sizeof(Float3); - data.TriangleCount = mesh->Indices.Count() / 3; - const uint32 groups = Math::CeilToInt((float)data.TriangleCount / ThreadGroupSize); - if (groups > GPU_MAX_CS_DISPATCH_THREAD_GROUPS) - { - // TODO: support larger meshes via 2D dispatch - LOG(Error, "Not supported mesh with {} triangles.", data.TriangleCount); - continue; - } - context->UpdateCB(cb, &data); - desc = GPUBufferDescription::Raw(mesh->Positions.Count() * sizeof(Float3), GPUBufferFlags::ShaderResource); - desc.InitData = mesh->Positions.Get(); - // TODO: use transient buffer (single frame) - vb->Init(desc); - desc = GPUBufferDescription::Raw(mesh->Indices.Count() * sizeof(uint32), GPUBufferFlags::ShaderResource); - desc.InitData = mesh->Indices.Get(); - // TODO: use transient buffer (single frame) - ib->Init(desc); - context->BindSR(0, vb->View()); - context->BindSR(1, ib->View()); - context->Dispatch(shader->GetCS("CS_RasterizeTriangle"), groups, 1, 1); - } - SAFE_DELETE_GPU_RESOURCE(vb); - SAFE_DELETE_GPU_RESOURCE(ib); - } - - // Convert SDF volume data back to floats - context->Dispatch(shader->GetCS("CS_Resolve"), threadGroups.X, threadGroups.Y, threadGroups.Z); - - // Run linear flood-fill loop to populate all voxels with valid distances (spreads the initial values from triangles rasterization) - { - PROFILE_GPU_CPU_NAMED("FloodFill"); - auto csFloodFill = shader->GetCS("CS_FloodFill"); - const int32 floodFillIterations = Math::Max(_resolution.MaxValue() / 2 + 1, 8); - for (int32 floodFill = 0; floodFill < floodFillIterations; floodFill++) - { - context->ResetUA(); - context->BindUA(0, _sdfDst->View()); - context->BindSR(0, _sdfSrc->View()); - context->Dispatch(csFloodFill, threadGroups.X, threadGroups.Y, threadGroups.Z); - Swap(_sdfSrc, _sdfDst); - } - } - - // Encode SDF values into output storage - context->ResetUA(); - context->BindSR(0, _sdfSrc->View()); - // TODO: update GPU SDF texture within this task to skip additional CPU->GPU copy - auto sdfTextureDesc = GPUTextureDescription::New3D(_resolution.X, _resolution.Y, _resolution.Z, PixelFormat::R16_UNorm, GPUTextureFlags::UnorderedAccess | GPUTextureFlags::RenderTarget); + // Allocate output texture + auto sdfTextureDesc = GPUTextureDescription::New3D(_resolution.X, _resolution.Y, _resolution.Z, PixelFormat::R16_UNorm, GPUTextureFlags::UnorderedAccess); // TODO: use transient texture (single frame) auto sdfTexture = GPUTexture::New(); #if GPU_ENABLE_RESOURCE_NAMING sdfTexture->SetName(TEXT("SDFTexture")); #endif sdfTexture->Init(sdfTextureDesc); - context->BindUA(1, sdfTexture->ViewVolume()); - context->Dispatch(shader->GetCS("CS_Encode"), threadGroups.X, threadGroups.Y, threadGroups.Z); + + // Renders directly to the output texture + context->BindUA(0, sdfTexture->ViewVolume()); + + // Init the volume (rasterization mixes with existing contents) + context->Dispatch(shader->GetCS("CS_Init"), threadGroups.X, threadGroups.Y, threadGroups.Z); + + // Render input triangles into the SDF volume + { + PROFILE_GPU("Rasterize"); + context->BindSR(0, bvh.VertexBuffer->View()); + context->BindSR(1, bvh.IndexBuffer->View()); + context->BindSR(2, bvh.BVHBuffer->View()); + auto* rasterizeCS = shader->GetCS("CS_RasterizeTriangles"); + context->Dispatch(rasterizeCS, threadGroups.X, threadGroups.Y, threadGroups.Z); + } // Copy result data into readback buffer if (_sdfResult) @@ -329,6 +215,9 @@ public: SAFE_DELETE_GPU_RESOURCE(sdfTexture); +#if GPU_ALLOW_PROFILE_EVENTS + _timerQuery->End(); +#endif return Result::Ok; } @@ -336,6 +225,10 @@ public: { GPUTask::OnSync(); _signal->NotifyOne(); +#if GPU_ALLOW_PROFILE_EVENTS + if (_timerQuery->HasResult()) + LOG(Info, "GPU SDF generation took {} ms", Utilities::RoundTo1DecimalPlace(_timerQuery->GetResult())); +#endif } void OnFail() override @@ -445,6 +338,13 @@ bool ModelTool::GenerateModelSDF(Model* inputModel, const ModelData* modelData, // http://ramakarl.com/pdfs/2016_Hoetzlein_GVDB.pdf // https://www.cse.chalmers.se/~uffe/HighResolutionSparseVoxelDAGs.pdf + // Setup acceleration structure for fast ray tracing the mesh triangles + MeshAccelerationStructure scene; + if (inputModel) + scene.Add(inputModel, lodIndex); + else if (modelData) + scene.Add(modelData, lodIndex); + // Check if run SDF generation on a GPU via Compute Shader or on a Job System useGPU &= GPUDevice::Instance && GPUDevice::Instance->GetState() == GPUDevice::DeviceState::Ready @@ -465,7 +365,7 @@ bool ModelTool::GenerateModelSDF(Model* inputModel, const ModelData* modelData, // Run SDF generation via GPU async task ConditionVariable signal; CriticalSection mutex; - Task* task = New(signal, inputModel, modelData, lodIndex, resolution, &sdf, sdfResult, xyzToLocalMul, xyzToLocalAdd); + Task* task = New(signal, &scene, inputModel, modelData, lodIndex, resolution, &sdf, sdfResult, xyzToLocalMul, xyzToLocalAdd, backfacesThreshold); task->Start(); mutex.Lock(); signal.Wait(mutex); @@ -489,16 +389,10 @@ bool ModelTool::GenerateModelSDF(Model* inputModel, const ModelData* modelData, } else { - // Setup acceleration structure for fast ray tracing the mesh triangles - MeshAccelerationStructure scene; - if (inputModel) - scene.Add(inputModel, lodIndex); - else if (modelData) - scene.Add(modelData, lodIndex); scene.BuildBVH(); // Brute-force for each voxel to calculate distance to the closest triangle with point query and distance sign by raycasting around the voxel - constexpr int32 sampleCount = 12; + constexpr int32 sampleCount = BUILD_DEBUG ? 6 : 12; Float3 sampleDirections[sampleCount]; { RandomStream rand; @@ -526,36 +420,30 @@ bool ModelTool::GenerateModelSDF(Model* inputModel, const ModelData* modelData, Real minDistance = sdf.MaxDistance; Vector3 voxelPos = Float3((float)x, (float)y, (float)z) * xyzToLocalMul + xyzToLocalAdd; - // Point query to find the distance to the closest surface - scene.PointQuery(voxelPos, minDistance, hitPoint, hitTriangle); - // Raycast samples around voxel to count triangle backfaces hit - int32 hitBackCount = 0, hitCount = 0; + int32 hitBackCount = 0, minBackfaceHitCount = (int32)(sampleCount * backfacesThreshold); for (int32 sample = 0; sample < sampleCount; sample++) { Ray sampleRay(voxelPos, sampleDirections[sample]); sampleRay.Position -= sampleRay.Direction * 0.0001f; // Apply small margin if (scene.RayCast(sampleRay, hitDistance, hitNormal, hitTriangle)) { - if (hitDistance < minDistance) - minDistance = hitDistance; - hitCount++; - const bool backHit = Float3::Dot(sampleRay.Direction, hitTriangle.GetNormal()) > 0; - if (backHit) - hitBackCount++; + minDistance = Math::Min(hitDistance, minDistance); + if (Float3::Dot(sampleRay.Direction, hitTriangle.GetNormal()) > 0) + { + if (++hitBackCount >= minBackfaceHitCount) + break; + } } } - float distance = (float)minDistance; - // TODO: surface thickness threshold? shift reduce distance for all voxels by something like 0.01 to enlarge thin geometry - // if ((float)hitBackCount > (float)hitCount * 0.3f && hitCount != 0) - if ((float)hitBackCount > (float)sampleCount * backfacesThreshold && hitCount != 0) - { - // Voxel is inside the geometry so turn it into negative distance to the surface - distance *= -1; - } + // Point query to find the distance to the closest surface + scene.PointQuery(voxelPos, minDistance, hitPoint, hitTriangle, minDistance); + if (hitBackCount >= minBackfaceHitCount) + minDistance *= -1; // Voxel is inside the geometry so turn it into negative distance to the surface + const int32 xAddress = x + yAddress; - formatWrite(voxels.Get() + xAddress * formatStride, distance * encodeMAD.X + encodeMAD.Y); + formatWrite(voxels.Get() + xAddress * formatStride, minDistance * encodeMAD.X + encodeMAD.Y); } } }; diff --git a/Source/Shaders/Collisions.hlsl b/Source/Shaders/Collisions.hlsl index 2c503be9a..6d61a8c6a 100644 --- a/Source/Shaders/Collisions.hlsl +++ b/Source/Shaders/Collisions.hlsl @@ -18,6 +18,26 @@ bool RayHitRect(float3 r, float3 rectCenter, float3 rectX, float3 rectY, float3 return inExtentX && inExtentY; } +// Determines whether there is an intersection between a ray (rPos and rDir) and a triangle (v0, v1, v2). +// Returns true on intersection and outputs the distance along the ray to the intersection point. +// This method tests if the ray intersects either the front or back of the triangle. +bool RayIntersectsTriangle(float3 rPos, float3 rDir, float3 v0, float3 v1, float3 v2, out float distance) +{ + // [https://stackoverflow.com/a/42752998] + float3 edgeAB = v1 - v0; + float3 edgeAC = v2 - v0; + float3 triFaceVector = cross(edgeAB, edgeAC); + float3 vertRayOffset = rPos - v0; + float3 rayOffsetPerp = cross(vertRayOffset, rDir); + float determinant = -dot(rDir, triFaceVector); + float invDet = 1.0f / determinant; + distance = dot(vertRayOffset, triFaceVector) * invDet; + float u = dot(edgeAC, rayOffsetPerp) * invDet; + float v = -dot(edgeAB, rayOffsetPerp) * invDet; + float w = 1.0f - u - v; + return abs(determinant) >= 1E-8 && distance > 0 && u >= 0 && v >= 0 && w >= 0; +} + // Hits axis-aligned box (boxMin, boxMax) with a line (lineStart, lineEnd). // Returns the intersections on the line (x - closest, y - furthest). // Line hits the box if: intersections.x < intersections.y. @@ -42,4 +62,39 @@ bool BoxIntersectsSphere(float3 boxMin, float3 boxMax, float3 sphereCenter, floa return distance(sphereCenter, clampedCenter) <= sphereRadius; } +// Calculates unsigned distance from point to the AABB. If point is inside it, returns 0. +float PointDistanceBox(float3 boxMin, float3 boxMax, float3 pos) +{ + float3 clampedPos = clamp(pos, boxMin, boxMax); + return length(clampedPos - pos); +} + +float dot2(float3 v) +{ + return dot(v, v); +} + +// Calculates squared distance from point to the triangle. +float DistancePointToTriangle2(float3 p, float3 v1, float3 v2, float3 v3) +{ + // [Inigo Quilez, https://iquilezles.org/articles/triangledistance/] + float3 v21 = v2 - v1; float3 p1 = p - v1; + float3 v32 = v3 - v2; float3 p2 = p - v2; + float3 v13 = v1 - v3; float3 p3 = p - v3; + float3 nor = cross(v21, v13); + return // inside/outside test + (sign(dot(cross(v21, nor), p1)) + + sign(dot(cross(v32, nor), p2)) + + sign(dot(cross(v13, nor), p3)) < 2.0) + ? + // 3 edges + min(min( + dot2(v21 * saturate(dot(v21, p1) / dot2(v21)) - p1), + dot2(v32 * saturate(dot(v32, p2) / dot2(v32)) - p2)), + dot2(v13 * saturate(dot(v13, p3) / dot2(v13)) - p3)) + : + // 1 face + dot(nor, p1) * dot(nor, p1) / dot2(nor); +} + #endif diff --git a/Source/Shaders/MeshAccelerationStructure.hlsl b/Source/Shaders/MeshAccelerationStructure.hlsl new file mode 100644 index 000000000..c0ae24835 --- /dev/null +++ b/Source/Shaders/MeshAccelerationStructure.hlsl @@ -0,0 +1,163 @@ +// Copyright (c) Wojciech Figat. All rights reserved. + +#ifndef __MESH_ACCELERATION_STRUCTURE__ +#define __MESH_ACCELERATION_STRUCTURE__ + +#include "./Flax/Collisions.hlsl" + +// This must match MeshAccelerationStructure::ToGPU +#define BVH_STACK_SIZE 32 + +struct BVHNode +{ + float3 BoundsMin; + uint Index; + float3 BoundsMax; + int Count; // Negative for non-leaf nodes +}; + +struct BVHBuffers +{ + StructuredBuffer BVHBuffer; + ByteAddressBuffer VertexBuffer; + ByteAddressBuffer IndexBuffer; + uint VertexStride; +}; + +struct BVHHit +{ + float Distance; + bool IsBackface; +}; + +float3 LoadVertexBVH(BVHBuffers bvh, uint index) +{ + index = bvh.IndexBuffer.Load(index << 2u); + return asfloat(bvh.VertexBuffer.Load3(index * bvh.VertexStride)); +} + +// [https://tavianator.com/2011/ray_box.html] +float RayTestBoxBVH(float3 rayPos, float3 rayDir, float3 boxMin, float3 boxMax) +{ + float3 rayInvDir = rcp(rayDir); + float3 tMin = (boxMin - rayPos) * rayInvDir; + float3 tMax = (boxMax - rayPos) * rayInvDir; + float3 t1 = min(tMin, tMax); + float tNear = max(max(t1.x, t1.y), t1.z); + float3 t2 = max(tMin, tMax); + float tFar = min(min(t2.x, t2.y), t2.z); + bool hit = tFar >= tNear && tFar > 0; + return hit ? max(tNear, 0) : -1; +} + +// Performs raytracing against the BVH acceleration structure to find the closest intersection with a triangle. +bool RayCastBVH(BVHBuffers bvh, float3 rayPos, float3 rayDir, out BVHHit hit, float maxDistance = 1000000.0f) +{ + hit = (BVHHit)0; + hit.Distance = maxDistance; + + // Stack-based recursion, starts from root node + uint stack[BVH_STACK_SIZE]; + uint stackCount = 1; + stack[0] = 0; + + bool result = false; + LOOP + while (stackCount > 0) + { + BVHNode node = bvh.BVHBuffer[stack[--stackCount]]; + + // Raytrace bounds + float boundsHit = RayTestBoxBVH(rayPos, rayDir, node.BoundsMin, node.BoundsMax); + BRANCH + if (boundsHit >= 0 && boundsHit < hit.Distance) + { + BRANCH + if (node.Count > 0) // Is leaf? + { + // Ray cast along all triangles in the leaf + uint indexStart = node.Index; + uint indexEnd = indexStart + node.Count; + for (uint i = indexStart; i < indexEnd;) + { + // Load triangle + float3 v0 = LoadVertexBVH(bvh, i++); + float3 v1 = LoadVertexBVH(bvh, i++); + float3 v2 = LoadVertexBVH(bvh, i++); + + // Raytrace triangle + float distance; + if (RayIntersectsTriangle(rayPos, rayDir, v0, v1, v2, distance) && distance < hit.Distance) + { + float3 n = normalize(cross(v1 - v0, v2 - v0)); + hit.Distance = distance; + hit.IsBackface = dot(rayDir, n) > 0; + result = true; + } + } + } + else + { + // Push children onto the stack to be tested + stack[stackCount++] = node.Index + 0; + stack[stackCount++] = node.Index + 1; + } + } + } + return result; +} + +// Performs a query against the BVH acceleration structure to find the closest distance to a triangle from a given point. +bool PointQueryBVH(BVHBuffers bvh, float3 pos, out BVHHit hit, float maxDistance = 1000000.0f) +{ + hit = (BVHHit)0; + hit.Distance = maxDistance; + + // Stack-based recursion, starts from root node + uint stack[BVH_STACK_SIZE]; + uint stackCount = 1; + stack[0] = 0; + + bool result = false; + LOOP + while (stackCount > 0) + { + BVHNode node = bvh.BVHBuffer[stack[--stackCount]]; + + // Skip too far nodes + if (PointDistanceBox(node.BoundsMin, node.BoundsMax, pos) >= hit.Distance) + continue; + + BRANCH + if (node.Count > 0) // Is leaf? + { + // Find the closest triangles in the leaf + uint indexStart = node.Index; + uint indexEnd = indexStart + node.Count; + for (uint i = indexStart; i < indexEnd;) + { + // Load triangle + float3 v0 = LoadVertexBVH(bvh, i++); + float3 v1 = LoadVertexBVH(bvh, i++); + float3 v2 = LoadVertexBVH(bvh, i++); + + // Check triangle + float distance = sqrt(DistancePointToTriangle2(pos, v0, v1, v2)); + if (distance < hit.Distance) + { + hit.Distance = distance; + result = true; + } + } + } + else + { + // Push children onto the stack to be tested + stack[stackCount++] = node.Index + 0; + stack[stackCount++] = node.Index + 1; + } + } + return result; +} + +#endif diff --git a/Source/Shaders/SDF.shader b/Source/Shaders/SDF.shader index 2239e2dbe..b2ae928f7 100644 --- a/Source/Shaders/SDF.shader +++ b/Source/Shaders/SDF.shader @@ -1,18 +1,14 @@ // Copyright (c) Wojciech Figat. All rights reserved. -// Mesh SDF generation based on https://github.com/GPUOpen-Effects/TressFX - #include "./Flax/Common.hlsl" -#include "./Flax/ThirdParty/TressFX/TressFXSDF.hlsl" - -#define THREAD_GROUP_SIZE 64 +#include "./Flax/MeshAccelerationStructure.hlsl" META_CB_BEGIN(0, Data) int3 Resolution; uint ResolutionSize; float MaxDistance; uint VertexStride; -bool Index16bit; +float BackfacesThreshold; uint TriangleCount; float3 VoxelToPosMul; float WorldUnitsPerVoxel; @@ -20,21 +16,9 @@ float3 VoxelToPosAdd; uint ThreadGroupsX; META_CB_END -RWBuffer SDF : register(u0); - -uint GetVoxelIndex(uint3 groupId, uint groupIndex) +uint GetVoxelIndex(uint3 groupId, uint groupIndex, uint groupSize) { - return groupIndex + (groupId.x + groupId.y * ThreadGroupsX) * THREAD_GROUP_SIZE; -} - -int3 ClampVoxelCoord(int3 coord) -{ - return clamp(coord, 0, Resolution - 1); -} - -int GetVoxelIndex(int3 coord) -{ - return Resolution.x * Resolution.y * coord.z + Resolution.x * coord.y + coord.x; + return groupIndex + (groupId.x + groupId.y * ThreadGroupsX) * groupSize; } float3 GetVoxelPos(int3 coord) @@ -42,12 +26,6 @@ float3 GetVoxelPos(int3 coord) return float3((float)coord.x, (float)coord.y, (float)coord.z) * VoxelToPosMul + VoxelToPosAdd; } -int3 GetVoxelCoord(float3 pos) -{ - pos = (pos - VoxelToPosAdd) / VoxelToPosMul; - return int3((int)pos.x, (int)pos.y, (int)pos.z); -} - int3 GetVoxelCoord(uint index) { uint sizeX = (uint)Resolution.x; @@ -59,191 +37,90 @@ int3 GetVoxelCoord(uint index) return int3((int)coordX, (int)coordY, (int)coordZ); } -// Clears SDF texture with the initial distance. +#ifdef _CS_Init + +#define THREAD_GROUP_SIZE 64 + +RWTexture3D SDFtex : register(u0); + +// Clears SDF texture with the maximum distance. META_CS(true, FEATURE_LEVEL_SM5) [numthreads(THREAD_GROUP_SIZE, 1, 1)] void CS_Init(uint3 GroupId : SV_GroupID, uint GroupIndex : SV_GroupIndex) { - uint voxelIndex = GetVoxelIndex(GroupId, GroupIndex); + uint voxelIndex = GetVoxelIndex(GroupId, GroupIndex, THREAD_GROUP_SIZE); if (voxelIndex >= ResolutionSize) return; - float distance = MaxDistance * 10.0f; // Start with a very large value - SDF[voxelIndex] = FloatFlip3(distance); + int3 voxelCoord = GetVoxelCoord(voxelIndex); + SDFtex[voxelCoord] = 1.0f; } -// Unpacks SDF texture into distances stores as normal float value (FloatFlip3 is used for interlocked operations on uint). -META_CS(true, FEATURE_LEVEL_SM5) -[numthreads(THREAD_GROUP_SIZE, 1, 1)] -void CS_Resolve(uint3 GroupId : SV_GroupID, uint GroupIndex : SV_GroupIndex) -{ - uint voxelIndex = GetVoxelIndex(GroupId, GroupIndex); - if (voxelIndex >= ResolutionSize) - return; - SDF[voxelIndex] = IFloatFlip3(SDF[voxelIndex]); -} +#endif -#ifdef _CS_RasterizeTriangle +#ifdef _CS_RasterizeTriangles +#define THREAD_GROUP_SIZE 64 + +RWTexture3D SDFtex : register(u0); ByteAddressBuffer VertexBuffer : register(t0); ByteAddressBuffer IndexBuffer : register(t1); - -uint LoadIndex(uint i) -{ - if (Index16bit) - { - uint index = IndexBuffer.Load((i >> 1u) << 2u); - index = (i & 1u) == 1u ? (index >> 16) : index; - return index & 0xffff; - } - return IndexBuffer.Load(i << 2u); -} - -float3 LoadVertex(uint i) -{ - return asfloat(VertexBuffer.Load3(i * VertexStride)); -} +StructuredBuffer BVHBuffer : register(t2); // Renders triangle mesh into the SDF texture by writing minimum distance to the triangle into all intersecting voxels. META_CS(true, FEATURE_LEVEL_SM5) [numthreads(THREAD_GROUP_SIZE, 1, 1)] -void CS_RasterizeTriangle(uint3 DispatchThreadId : SV_DispatchThreadID) +void CS_RasterizeTriangles(uint3 GroupId : SV_GroupID, uint3 GroupThreadID : SV_GroupThreadID, uint GroupIndex : SV_GroupIndex) { - uint triangleIndex = DispatchThreadId.x; - if (triangleIndex >= TriangleCount) + uint voxelIndex = GetVoxelIndex(GroupId, GroupIndex, THREAD_GROUP_SIZE); + if (voxelIndex >= ResolutionSize) return; + int3 voxelCoord = GetVoxelCoord(voxelIndex); + float3 voxelPos = GetVoxelPos(voxelCoord); - // Load triangle - triangleIndex *= 3; - uint i0 = LoadIndex(triangleIndex + 0); - uint i1 = LoadIndex(triangleIndex + 1); - uint i2 = LoadIndex(triangleIndex + 2); - float3 v0 = LoadVertex(i0); - float3 v1 = LoadVertex(i1); - float3 v2 = LoadVertex(i2); + BVHBuffers bvh; + bvh.BVHBuffer = BVHBuffer; + bvh.VertexBuffer = VertexBuffer; + bvh.IndexBuffer = IndexBuffer; + bvh.VertexStride = VertexStride; - // Project triangle into SDF voxels - float3 vMargin = float3(WorldUnitsPerVoxel, WorldUnitsPerVoxel, WorldUnitsPerVoxel); - float3 vMin = min(min(v0, v1), v2) - vMargin; - float3 vMax = max(max(v0, v1), v2) + vMargin; - int3 voxelMargin = int3(1, 1, 1); - int3 voxelMin = GetVoxelCoord(vMin) - voxelMargin; - int3 voxelMax = GetVoxelCoord(vMax) + voxelMargin; - voxelMin = ClampVoxelCoord(voxelMin); - voxelMax = ClampVoxelCoord(voxelMax); + // Point query to find the distance to the closest surface + BVHHit hit; + PointQueryBVH(bvh, voxelPos, hit, MaxDistance); + float sdf = hit.Distance; - // Rasterize into SDF voxels - for (int z = voxelMin.z; z <= voxelMax.z; z++) + // Raycast triangles around voxel to count triangle backfaces hit + #define CLOSEST_CACHE_SIZE 6 + float3 closestDirections[CLOSEST_CACHE_SIZE] = { - for (int y = voxelMin.y; y <= voxelMax.y; y++) + float3(+1, 0, 0), + float3(-1, 0, 0), + float3(0, +1, 0), + float3(0, -1, 0), + float3(0, 0, +1), + float3(0, 0, -1), + }; + uint hitBackCount = 0; + uint minBackfaceHitCount = (uint)(CLOSEST_CACHE_SIZE * BackfacesThreshold); + for (uint i = 0; i < CLOSEST_CACHE_SIZE; i++) + { + float3 rayDir = closestDirections[i]; + if (RayCastBVH(bvh, voxelPos, rayDir, hit, MaxDistance)) { - for (int x = voxelMin.x; x <= voxelMax.x; x++) - { - int3 voxelCoord = int3(x, y, z); - int voxelIndex = GetVoxelIndex(voxelCoord); - float3 voxelPos = GetVoxelPos(voxelCoord); - float distance = SignedDistancePointToTriangle(voxelPos, v0, v1, v2); -#if 0 - if (distance < -10.0f) // TODO: find a better way to reject negative distance from degenerate triangles that break SDF shape - distance = abs(distance); -#endif - InterlockedMin(SDF[voxelIndex], FloatFlip3(distance)); - } + sdf = min(sdf, hit.Distance); + if (hit.IsBackface) + hitBackCount++; } } -} - -#endif - -#if defined(_CS_FloodFill) || defined(_CS_Encode) - -Buffer InSDF : register(t0); - -float GetVoxel(int voxelIndex) -{ - return asfloat(InSDF[voxelIndex]); -} - -float GetVoxel(int3 coord) -{ - coord = ClampVoxelCoord(coord); - int voxelIndex = GetVoxelIndex(coord); - return GetVoxel(voxelIndex); -} - -float CombineSDF(float sdf, int3 nearbyCoord, float nearbyDistance) -{ - // Sample nearby voxel - float sdfNearby = GetVoxel(nearbyCoord); - - // Include distance to that nearby voxel - if (sdfNearby < 0.0f) - nearbyDistance *= -1; - sdfNearby += nearbyDistance; - - if (sdfNearby > MaxDistance) + if (hitBackCount >= minBackfaceHitCount) { - // Ignore if nearby sample is invalid (see CS_Init) + // Voxel is inside the geometry so turn it into negative distance to the surface + sdf *= -1; } - else if (sdf > MaxDistance) - { - // Use nearby sample if current one is invalid (see CS_Init) - sdf = sdfNearby; - } - else - { - // Use distance closer to 0 - sdf = sdf >= 0 ? min(sdf, sdfNearby) : max(sdf, sdfNearby); - } - - return sdf; -} - -// Fills the voxels with minimum distances to the triangles. -META_CS(true, FEATURE_LEVEL_SM5) -[numthreads(THREAD_GROUP_SIZE, 1, 1)] -void CS_FloodFill(uint3 GroupId : SV_GroupID, uint GroupIndex : SV_GroupIndex) -{ - uint voxelIndex = GetVoxelIndex(GroupId, GroupIndex); - if (voxelIndex >= ResolutionSize) - return; - float sdf = GetVoxel(voxelIndex); - - // Skip if the distance is already so small that we know that triangle is nearby - if (abs(sdf) > WorldUnitsPerVoxel * 1.2f) - { - int3 voxelCoord = GetVoxelCoord(voxelIndex); - int3 offset = int3(-1, 0, 1); - - // Sample nearby voxels - float nearbyDistance = WorldUnitsPerVoxel; - sdf = CombineSDF(sdf, voxelCoord + offset.zyy, nearbyDistance); - sdf = CombineSDF(sdf, voxelCoord + offset.yzy, nearbyDistance); - sdf = CombineSDF(sdf, voxelCoord + offset.yyz, nearbyDistance); - sdf = CombineSDF(sdf, voxelCoord + offset.xyy, nearbyDistance); - sdf = CombineSDF(sdf, voxelCoord + offset.yxy, nearbyDistance); - sdf = CombineSDF(sdf, voxelCoord + offset.yyx, nearbyDistance); - } - - SDF[voxelIndex] = asuint(sdf); -} - -RWTexture3D SDFtex : register(u1); - -// Encodes SDF values into the packed format with normalized distances. -META_CS(true, FEATURE_LEVEL_SM5) -[numthreads(THREAD_GROUP_SIZE, 1, 1)] -void CS_Encode(uint3 GroupId : SV_GroupID, uint GroupIndex : SV_GroupIndex) -{ - uint voxelIndex = GetVoxelIndex(GroupId, GroupIndex); - if (voxelIndex >= ResolutionSize) - return; - float sdf = GetVoxel(voxelIndex); - sdf = min(sdf, MaxDistance); // Pack from range [-MaxDistance; +MaxDistance] to [0; 1] + sdf = clamp(sdf, -MaxDistance, MaxDistance); sdf = (sdf / MaxDistance) * 0.5f + 0.5f; - int3 voxelCoord = GetVoxelCoord(voxelIndex); SDFtex[voxelCoord] = sdf; } diff --git a/Source/Shaders/ThirdParty/TressFX/TressFXSDF.hlsl b/Source/Shaders/ThirdParty/TressFX/TressFXSDF.hlsl deleted file mode 100644 index 7128ba9ca..000000000 --- a/Source/Shaders/ThirdParty/TressFX/TressFXSDF.hlsl +++ /dev/null @@ -1,129 +0,0 @@ -// Source: https://github.com/GPUOpen-Effects/TressFX -// License: MIT - -// -// Copyright (c) 2019 Advanced Micro Devices, Inc. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// - -//When building the SDF we want to find the lowest distance at each SDF cell. In order to allow multiple threads to write to the same -//cells, it is necessary to use atomics. However, there is no support for atomics with 32-bit floats so we convert the float into unsigned int -//and use atomic_min() / InterlockedMin() as a workaround. -// -//When used with atomic_min, both FloatFlip2() and FloatFlip3() will store the float with the lowest magnitude. -//The difference is that FloatFlip2() will preper negative values ( InterlockedMin( FloatFlip2(1.0), FloatFlip2(-1.0) ) == -1.0 ), -//while FloatFlip3() prefers positive values ( InterlockedMin( FloatFlip3(1.0), FloatFlip3(-1.0) ) == 1.0 ). -//Using FloatFlip3() seems to result in a SDF with higher quality compared to FloatFlip2(). -uint FloatFlip2(float fl) -{ - uint f = asuint(fl); - return (f << 1) | (f >> 31 ^ 0x00000001); //Rotate sign bit to least significant and Flip sign bit so that (0 == negative) -} -uint IFloatFlip2(uint f2) -{ - return (f2 >> 1) | (f2 << 31 ^ 0x80000000); -} -uint FloatFlip3(float fl) -{ - uint f = asuint(fl); - return (f << 1) | (f >> 31); //Rotate sign bit to least significant -} -uint IFloatFlip3(uint f2) -{ - return (f2 >> 1) | (f2 << 31); -} - -float DistancePointToEdge(float3 p, float3 x0, float3 x1, out float3 n) -{ - // Hack to swap to ensure the order is correct (.x only for simplicity) - if (x0.x > x1.x) - { - float3 temp = x0; - x0 = x1; - x1 = temp; - } - - float3 x10 = x1 - x0; - - float t = dot(x1 - p, x10) / dot(x10, x10); - t = max(0.0f, min(t, 1.0f)); - - float3 a = p - (t*x0 + (1.0f - t)*x1); - float d = length(a); - n = a / (d + 1e-30f); - - return d; -} - -// Check if p is in the positive or negative side of triangle (x0, x1, x2) -// Positive side is where the normal vector of triangle ( (x1-x0) x (x2-x0) ) is pointing to. -float SignedDistancePointToTriangle(float3 p, float3 x0, float3 x1, float3 x2) -{ - float d = 0; - float3 x02 = x0 - x2; - float l0 = length(x02) + 1e-30f; - x02 = x02 / l0; - float3 x12 = x1 - x2; - float l1 = dot(x12, x02); - x12 = x12 - l1*x02; - float l2 = length(x12) + 1e-30f; - x12 = x12 / l2; - float3 px2 = p - x2; - - float b = dot(x12, px2) / l2; - float a = (dot(x02, px2) - l1*b) / l0; - float c = 1 - a - b; - - // normal vector of triangle. Don't need to normalize this yet. - float3 nTri = cross((x1 - x0), (x2 - x0)); - float3 n; - - float tol = 1e-8f; - - if (a >= -tol && b >= -tol && c >= -tol) - { - n = p - (a*x0 + b*x1 + c*x2); - d = length(n); - - float3 n1 = n / d; - float3 n2 = nTri / (length(nTri) + 1e-30f); // if d == 0 - - n = (d > 0) ? n1 : n2; - } - else - { - float3 n_12; - float3 n_02; - d = DistancePointToEdge(p, x0, x1, n); - - float d12 = DistancePointToEdge(p, x1, x2, n_12); - float d02 = DistancePointToEdge(p, x0, x2, n_02); - - d = min(d, d12); - d = min(d, d02); - - n = (d == d12) ? n_12 : n; - n = (d == d02) ? n_02 : n; - } - - d = (dot(p - x0, nTri) < 0.f) ? -d : d; - - return d; -} From 69173803bfc86cbd1265d57cc4d8531497e78464 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 13 Nov 2025 22:09:11 +0100 Subject: [PATCH 14/43] Fix shader warning on Vulkan --- Content/Shaders/GI/GlobalSurfaceAtlas.flax | 4 ++-- Source/Shaders/GI/GlobalSurfaceAtlas.shader | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Content/Shaders/GI/GlobalSurfaceAtlas.flax b/Content/Shaders/GI/GlobalSurfaceAtlas.flax index c0ee573e0..1b0173ba5 100644 --- a/Content/Shaders/GI/GlobalSurfaceAtlas.flax +++ b/Content/Shaders/GI/GlobalSurfaceAtlas.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:64a53e850ac662cb98ce91a8122dece7a43562f55a85c37f830f44324e4d16c5 -size 12925 +oid sha256:0f34bf867df5f4296ca66ac691c2bca4efa168fb9e21ca4e613e8086669575cf +size 13296 diff --git a/Source/Shaders/GI/GlobalSurfaceAtlas.shader b/Source/Shaders/GI/GlobalSurfaceAtlas.shader index 387620c5c..6778a7cd7 100644 --- a/Source/Shaders/GI/GlobalSurfaceAtlas.shader +++ b/Source/Shaders/GI/GlobalSurfaceAtlas.shader @@ -242,7 +242,7 @@ void CS_CullObjects(uint3 DispatchThreadId : SV_DispatchThreadID, uint3 GroupId if (BoxIntersectsSphere(groupMin, groupMax, objectBounds.xyz, objectBounds.w)) { uint sharedIndex; - InterlockedAdd(SharedCulledObjectsCount, 1, sharedIndex); + InterlockedAdd(SharedCulledObjectsCount, 1u, sharedIndex); if (sharedIndex < GLOBAL_SURFACE_ATLAS_SHARED_CULL_SIZE) SharedCulledObjects[sharedIndex] = objectAddress; } @@ -271,7 +271,7 @@ void CS_CullObjects(uint3 DispatchThreadId : SV_DispatchThreadID, uint3 GroupId // Allocate object data size in the buffer uint objectsStart; uint objectsSize = objectsCount + 1; // Include objects count before actual objects data - RWGlobalSurfaceAtlasCulledObjects.InterlockedAdd(0, objectsSize, objectsStart); // Counter at 0 + RWGlobalSurfaceAtlasCulledObjects.InterlockedAdd(0u, objectsSize, objectsStart); // Counter at 0 if (objectsStart + objectsSize > CulledObjectsCapacity) { // Not enough space in the buffer From 3888c4ba218991f7275260db84f496cfd20842da Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 13 Nov 2025 22:53:52 +0100 Subject: [PATCH 15/43] Fix async tasks destruction to wait on the dependencies in chain --- Source/Engine/Threading/Task.cpp | 25 +++++++++++++++++++++++-- Source/Engine/Threading/Task.h | 5 +++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Threading/Task.cpp b/Source/Engine/Threading/Task.cpp index bab794728..a640019d1 100644 --- a/Source/Engine/Threading/Task.cpp +++ b/Source/Engine/Threading/Task.cpp @@ -226,6 +226,27 @@ void Task::OnEnd() { ASSERT(!IsRunning()); - // Add to delete - DeleteObject(30.0f, false); + if (_continueWith && !_continueWith->IsEnded()) + { + // Let next task do the cleanup (to ensure whole tasks chain shares the lifetime) + _continueWith->_rootForRemoval = _rootForRemoval ? _rootForRemoval : this; + } + else + { + constexpr float timeToLive = 30.0f; + + // Remove task chain starting from the root + if (_rootForRemoval) + { + auto task = _rootForRemoval; + while (task != this) + { + task->DeleteObject(timeToLive, false); + task = task->_continueWith; + } + } + + // Add to delete + DeleteObject(timeToLive, false); + } } diff --git a/Source/Engine/Threading/Task.h b/Source/Engine/Threading/Task.h index eb450effd..19fbf4fad 100644 --- a/Source/Engine/Threading/Task.h +++ b/Source/Engine/Threading/Task.h @@ -63,6 +63,11 @@ protected: /// Task* _continueWith = nullptr; + /// + /// The task that's starts removal chain, used to sync whole task chain lifetime. + /// + Task* _rootForRemoval = nullptr; + void SetState(TaskState state) { Platform::AtomicStore((int64 volatile*)&_state, (uint64)state); From 636a1ff9308cd45a091a23da7e137a6e9abad786 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 13 Nov 2025 23:04:24 +0100 Subject: [PATCH 16/43] Fix material shader generation when material layer fails to load --- .../MaterialGenerator.Layer.cpp | 27 +++---------------- .../MaterialGenerator.Layers.cpp | 3 +++ 2 files changed, 7 insertions(+), 23 deletions(-) diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Layer.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Layer.cpp index fd68ccb9b..73b3058a3 100644 --- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Layer.cpp +++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Layer.cpp @@ -43,32 +43,15 @@ void MaterialGenerator::AddLayer(MaterialLayer* layer) MaterialLayer* MaterialGenerator::GetLayer(const Guid& id, Node* caller) { // Find layer first - for (int32 i = 0; i < _layers.Count(); i++) + for (MaterialLayer* layer : _layers) { - if (_layers[i]->ID == id) - { - // Found - return _layers[i]; - } + if (layer->ID == id) + return layer; } // Load asset Asset* asset = Assets.LoadAsync(id); - if (asset == nullptr || asset->WaitForLoaded(30000)) - { - OnError(caller, nullptr, TEXT("Failed to load material asset.")); - return nullptr; - } - - // Special case for engine exit event - if (Engine::ShouldExit()) - { - // End - return nullptr; - } - - // Check if load failed - if (!asset->IsLoaded()) + if (asset == nullptr || asset->WaitForLoaded(10 * 1000)) { OnError(caller, nullptr, TEXT("Failed to load material asset.")); return nullptr; @@ -79,13 +62,11 @@ MaterialLayer* MaterialGenerator::GetLayer(const Guid& id, Node* caller) Asset* iterator = asset; while (material == nullptr) { - // Wait for material to be loaded if (iterator->WaitForLoaded()) { OnError(caller, nullptr, TEXT("Material asset load failed.")); return nullptr; } - if (iterator->GetTypeName() == MaterialInstance::TypeName) { auto instance = ((MaterialInstance*)iterator); diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Layers.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Layers.cpp index ea1f6cc7a..a15551b40 100644 --- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Layers.cpp +++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Layers.cpp @@ -15,12 +15,14 @@ void MaterialGenerator::ProcessGroupLayers(Box* box, Node* node, Value& value) if (!id.IsValid()) { OnError(node, box, TEXT("Missing material.")); + value = MaterialValue::InitForZero(VariantType::Void); break; } ASSERT(GetRootLayer() != nullptr && GetRootLayer()->ID.IsValid()); if (id == GetRootLayer()->ID) { OnError(node, box, TEXT("Cannot use current material as layer.")); + value = MaterialValue::InitForZero(VariantType::Void); break; } @@ -29,6 +31,7 @@ void MaterialGenerator::ProcessGroupLayers(Box* box, Node* node, Value& value) if (layer == nullptr) { OnError(node, box, TEXT("Cannot load material.")); + value = MaterialValue::InitForZero(VariantType::Void); break; } ASSERT(_layers.Contains(layer)); From e9070b30a0aa5fbc62ad88bb7fd7726dd7085bbe Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 13 Nov 2025 23:05:13 +0100 Subject: [PATCH 17/43] Minor tweaks --- Source/Engine/Content/Content.cpp | 1 + Source/Engine/Particles/ParticlesSimulation.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Content/Content.cpp b/Source/Engine/Content/Content.cpp index b82a329a8..66e18a231 100644 --- a/Source/Engine/Content/Content.cpp +++ b/Source/Engine/Content/Content.cpp @@ -700,6 +700,7 @@ Asset* Content::GetAsset(const StringView& outputPath) { if (outputPath.IsEmpty()) return nullptr; + PROFILE_CPU(); ScopeLock lock(AssetsLocker); for (auto i = Assets.Begin(); i.IsNotEnd(); ++i) { diff --git a/Source/Engine/Particles/ParticlesSimulation.cpp b/Source/Engine/Particles/ParticlesSimulation.cpp index d25127e2f..bc1fa9eea 100644 --- a/Source/Engine/Particles/ParticlesSimulation.cpp +++ b/Source/Engine/Particles/ParticlesSimulation.cpp @@ -156,7 +156,7 @@ void ParticleSystemInstance::Sync(ParticleSystem* system) if (GPUParticlesCountReadback) GPUParticlesCountReadback->ReleaseGPU(); } - ASSERT(Emitters.Count() == system->Emitters.Count()); + CHECK(Emitters.Count() == system->Emitters.Count()); } bool ParticleSystemInstance::ContainsEmitter(ParticleEmitter* emitter) const From 4008e19ca91221530bb680c5ee4d4fbe84e7f417 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 14 Nov 2025 00:52:14 -0800 Subject: [PATCH 18/43] Fix various build issuesin uncommon configurations --- Source/Editor/Cooker/Steps/DeployDataStep.cpp | 2 +- Source/Engine/Physics/Colliders/Collider.cpp | 1 - Source/Engine/Physics/PhysicsBackendEmpty.cpp | 32 +++++++------------ Source/Engine/Scripting/ManagedCLR/MClass.h | 4 +++ Source/Engine/Scripting/ManagedCLR/MEvent.h | 2 ++ Source/Engine/Scripting/ManagedCLR/MField.h | 2 ++ Source/Engine/Scripting/ManagedCLR/MMethod.h | 2 ++ .../Engine/Scripting/ManagedCLR/MProperty.h | 2 ++ Source/Engine/Scripting/ScriptingObject.cpp | 2 ++ Source/Engine/Tools/ModelTool/ModelTool.cpp | 2 +- 10 files changed, 27 insertions(+), 24 deletions(-) diff --git a/Source/Editor/Cooker/Steps/DeployDataStep.cpp b/Source/Editor/Cooker/Steps/DeployDataStep.cpp index b5b24c251..775b040de 100644 --- a/Source/Editor/Cooker/Steps/DeployDataStep.cpp +++ b/Source/Editor/Cooker/Steps/DeployDataStep.cpp @@ -265,7 +265,7 @@ bool DeployDataStep::Perform(CookingData& data) } if (version.IsEmpty()) { - data.Error(String::Format(TEXT("Failed to find supported .NET {} version (min {}) for the current host platform."), maxVer, minVer)); + data.Error(String::Format(TEXT("Failed to find supported .NET {} version (min {}) for {} platform."), maxVer, minVer, platformName)); return true; } } diff --git a/Source/Engine/Physics/Colliders/Collider.cpp b/Source/Engine/Physics/Colliders/Collider.cpp index 6980fc4e6..96bd9c7ae 100644 --- a/Source/Engine/Physics/Colliders/Collider.cpp +++ b/Source/Engine/Physics/Colliders/Collider.cpp @@ -194,7 +194,6 @@ void Collider::UpdateLayerBits() // Own layer mask const uint32 mask1 = Physics::LayerMasks[GetLayer()]; - ASSERT(_shape); PhysicsBackend::SetShapeFilterMask(_shape, mask0, mask1); } diff --git a/Source/Engine/Physics/PhysicsBackendEmpty.cpp b/Source/Engine/Physics/PhysicsBackendEmpty.cpp index 2e816157b..03973e6fd 100644 --- a/Source/Engine/Physics/PhysicsBackendEmpty.cpp +++ b/Source/Engine/Physics/PhysicsBackendEmpty.cpp @@ -3,6 +3,7 @@ #if COMPILE_WITH_EMPTY_PHYSICS #include "Engine/Core/Log.h" +#include "Engine/Physics/PhysicsBackend.h" #include "Engine/Physics/CollisionData.h" #include "Engine/Physics/PhysicalMaterial.h" #include "Engine/Physics/PhysicsScene.h" @@ -226,26 +227,6 @@ bool PhysicsBackend::CheckConvex(void* scene, const Vector3& center, const Colli return false; } -bool PhysicsBackend::OverlapBox(void* scene, const Vector3& center, const Vector3& halfExtents, Array& results, const Quaternion& rotation, uint32 layerMask, bool hitTriggers) -{ - return false; -} - -bool PhysicsBackend::OverlapSphere(void* scene, const Vector3& center, const float radius, Array& results, uint32 layerMask, bool hitTriggers) -{ - return false; -} - -bool PhysicsBackend::OverlapCapsule(void* scene, const Vector3& center, const float radius, const float height, Array& results, const Quaternion& rotation, uint32 layerMask, bool hitTriggers) -{ - return false; -} - -bool PhysicsBackend::OverlapConvex(void* scene, const Vector3& center, const CollisionData* convexMesh, const Vector3& scale, Array& results, const Quaternion& rotation, uint32 layerMask, bool hitTriggers) -{ - return false; -} - bool PhysicsBackend::OverlapBox(void* scene, const Vector3& center, const Vector3& halfExtents, Array& results, const Quaternion& rotation, uint32 layerMask, bool hitTriggers) { return false; @@ -353,7 +334,7 @@ Vector3 PhysicsBackend::GetRigidDynamicActorCenterOfMass(void* actor) return Vector3::Zero; } -void PhysicsBackend::SetRigidDynamicActorCenterOfMassOffset(void* actor, const Float3& value) +void PhysicsBackend::AddRigidDynamicActorCenterOfMassOffset(void* actor, const Float3& value) { } @@ -698,6 +679,15 @@ void PhysicsBackend::SetControllerStepOffset(void* controller, float value) { } +Vector3 PhysicsBackend::GetControllerBasePosition(void* controller) +{ + return Vector3::Zero; +} + +void PhysicsBackend::SetControllerBasePosition(void* controller, const Vector3& value) +{ +} + Vector3 PhysicsBackend::GetControllerUpDirection(void* controller) { return Vector3::Up; diff --git a/Source/Engine/Scripting/ManagedCLR/MClass.h b/Source/Engine/Scripting/ManagedCLR/MClass.h index 9484a2c44..61273dae7 100644 --- a/Source/Engine/Scripting/ManagedCLR/MClass.h +++ b/Source/Engine/Scripting/ManagedCLR/MClass.h @@ -23,6 +23,10 @@ private: StringAnsiView _fullname; uint32 _types = 0; mutable uint32 _size = 0; +#else + StringAnsiView _name; + StringAnsiView _namespace; + StringAnsiView _fullname; #endif MAssembly* _assembly; diff --git a/Source/Engine/Scripting/ManagedCLR/MEvent.h b/Source/Engine/Scripting/ManagedCLR/MEvent.h index 9d8551774..e5ef1d6df 100644 --- a/Source/Engine/Scripting/ManagedCLR/MEvent.h +++ b/Source/Engine/Scripting/ManagedCLR/MEvent.h @@ -19,6 +19,8 @@ protected: #elif USE_NETCORE void* _handle; StringAnsiView _name; +#else + StringAnsiView _name; #endif mutable MMethod* _addMethod; diff --git a/Source/Engine/Scripting/ManagedCLR/MField.h b/Source/Engine/Scripting/ManagedCLR/MField.h index 66213d6ab..9ef5271a5 100644 --- a/Source/Engine/Scripting/ManagedCLR/MField.h +++ b/Source/Engine/Scripting/ManagedCLR/MField.h @@ -24,6 +24,8 @@ protected: void* _type; int32 _fieldOffset; StringAnsiView _name; +#else + StringAnsiView _name; #endif MClass* _parentClass; diff --git a/Source/Engine/Scripting/ManagedCLR/MMethod.h b/Source/Engine/Scripting/ManagedCLR/MMethod.h index 700fa9593..51264d1b8 100644 --- a/Source/Engine/Scripting/ManagedCLR/MMethod.h +++ b/Source/Engine/Scripting/ManagedCLR/MMethod.h @@ -30,6 +30,8 @@ protected: mutable void* _returnType; mutable Array> _parameterTypes; void CacheSignature() const; +#else + StringAnsiView _name; #endif MClass* _parentClass; MVisibility _visibility; diff --git a/Source/Engine/Scripting/ManagedCLR/MProperty.h b/Source/Engine/Scripting/ManagedCLR/MProperty.h index e48d98375..7ef7e97dd 100644 --- a/Source/Engine/Scripting/ManagedCLR/MProperty.h +++ b/Source/Engine/Scripting/ManagedCLR/MProperty.h @@ -22,6 +22,8 @@ protected: #elif USE_NETCORE void* _handle; StringAnsiView _name; +#else + StringAnsiView _name; #endif mutable MMethod* _getMethod; diff --git a/Source/Engine/Scripting/ScriptingObject.cpp b/Source/Engine/Scripting/ScriptingObject.cpp index 35bd16eb6..dee97a849 100644 --- a/Source/Engine/Scripting/ScriptingObject.cpp +++ b/Source/Engine/Scripting/ScriptingObject.cpp @@ -93,6 +93,8 @@ ScriptingObject::ScriptingObject(const SpawnParams& params) : _gcHandle((MGCHandle)params.Managed) #elif !COMPILE_WITHOUT_CSHARP : _gcHandle(params.Managed ? MCore::GCHandle::New(params.Managed) : 0) +#else + : _gcHandle(0) #endif , _type(params.Type) , _id(params.ID) diff --git a/Source/Engine/Tools/ModelTool/ModelTool.cpp b/Source/Engine/Tools/ModelTool/ModelTool.cpp index b3b622942..843822b98 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.cpp @@ -8,6 +8,7 @@ #include "Engine/Core/RandomStream.h" #include "Engine/Core/Math/Vector3.h" #include "Engine/Core/Math/Ray.h" +#include "Engine/Core/Utilities.h" #include "Engine/Platform/ConditionVariable.h" #include "Engine/Profiler/Profiler.h" #include "Engine/Threading/JobSystem.h" @@ -27,7 +28,6 @@ #include "Engine/Serialization/MemoryWriteStream.h" #include "Engine/Engine/Units.h" #if USE_EDITOR -#include "Engine/Core/Utilities.h" #include "Engine/Core/Types/StringView.h" #include "Engine/Core/Types/DateTime.h" #include "Engine/Core/Types/TimeSpan.h" From 5e690abd76425889b71033d74ae2b7158254ae86 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 14 Nov 2025 01:06:01 -0800 Subject: [PATCH 19/43] Fix initial state of `DummyVertexBuffer` on DX12 --- Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.cpp index a26584486..b33f94d61 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.cpp @@ -307,6 +307,7 @@ void GPUContextDX12::Reset() _device->DummyVB = _device->CreateBuffer(TEXT("DummyVertexBuffer")); auto* layout = GPUVertexLayout::Get({ { VertexElement::Types::Attribute3, 0, 0, 0, PixelFormat::R32G32B32A32_Float } }); _device->DummyVB->Init(GPUBufferDescription::Vertex(layout, sizeof(Color), 1, &Color::Transparent)); + SetResourceState(_device->DummyVB, D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER, 0); } ((GPUBufferDX12*)_device->DummyVB)->GetVBView(dummyVBView); _commandList->IASetVertexBuffers(GPU_MAX_VB_BINDED, 1, &dummyVBView); From 2a36edf528e8ff36efb6029e6216cff4301d01bc Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 14 Nov 2025 02:11:13 -0800 Subject: [PATCH 20/43] Add option to link OpenMP on GDK platforms if needed --- Source/Tools/Flax.Build/Platforms/GDK/GDKToolchain.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Source/Tools/Flax.Build/Platforms/GDK/GDKToolchain.cs b/Source/Tools/Flax.Build/Platforms/GDK/GDKToolchain.cs index 220a07bef..45e39e63c 100644 --- a/Source/Tools/Flax.Build/Platforms/GDK/GDKToolchain.cs +++ b/Source/Tools/Flax.Build/Platforms/GDK/GDKToolchain.cs @@ -13,6 +13,11 @@ namespace Flax.Build.Platforms /// public abstract class GDKToolchain : WindowsToolchainBase { + /// + /// Enables OpenMP library as dynamic dependency. + /// + protected bool OpenMP = false; + /// /// Gets the version of Xbox Services toolset. /// @@ -74,6 +79,12 @@ namespace Flax.Build.Platforms options.DependencyFiles.Add(Path.Combine(redistToolsPath, "vccorlib140.dll")); options.DependencyFiles.Add(Path.Combine(redistToolsPath, "vcruntime140.dll")); options.DependencyFiles.Add(Path.Combine(redistToolsPath, "vcruntime140_1.dll")); + if (OpenMP) + { + redistToolsPath = Path.Combine(paths[0], "x64", "Microsoft.VC" + (int)crtToolset + ".OpenMP"); + redistToolsPath = Utilities.RemovePathRelativeParts(redistToolsPath); + options.DependencyFiles.Add(Path.Combine(redistToolsPath, "vcomp140.dll")); + } } } } From 2f670495ac51c8b549dc7c897ed826739963ea83 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 14 Nov 2025 14:56:10 +0100 Subject: [PATCH 21/43] Migrate Xbox to using static linking with nethost lib --- Source/ThirdParty/nethost/nethost.Build.cs | 8 +++++--- .../Tools/Flax.Build/Deps/Dependencies/nethost.cs | 14 +++++++++++--- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/Source/ThirdParty/nethost/nethost.Build.cs b/Source/ThirdParty/nethost/nethost.Build.cs index bac529778..69d96d637 100644 --- a/Source/ThirdParty/nethost/nethost.Build.cs +++ b/Source/ThirdParty/nethost/nethost.Build.cs @@ -48,9 +48,6 @@ public class nethost : ThirdPartyModule switch (options.Platform.Target) { case TargetPlatform.Windows: - case TargetPlatform.XboxOne: - case TargetPlatform.XboxScarlett: - case TargetPlatform.UWP: if (hostRuntime.Type == DotNetSdk.HostType.CoreCLR) { options.OutputFiles.Add(Path.Combine(hostRuntime.Path, "nethost.lib")); @@ -63,6 +60,11 @@ public class nethost : ThirdPartyModule options.DependencyFiles.Add(Path.Combine(hostRuntime.Path, "coreclr.dll")); } break; + case TargetPlatform.XboxOne: + case TargetPlatform.XboxScarlett: + options.OutputFiles.Add(Path.Combine(hostRuntime.Path, "monosgen-2.0.lib")); + options.OutputFiles.Add(Path.Combine(hostRuntime.Path, "mono-profiler-aot.lib")); + break; case TargetPlatform.Linux: options.OutputFiles.Add(Path.Combine(hostRuntime.Path, "libnethost.a")); options.DependencyFiles.Add(Path.Combine(hostRuntime.Path, "libnethost.so")); diff --git a/Source/Tools/Flax.Build/Deps/Dependencies/nethost.cs b/Source/Tools/Flax.Build/Deps/Dependencies/nethost.cs index 1991aa605..0a8181903 100644 --- a/Source/Tools/Flax.Build/Deps/Dependencies/nethost.cs +++ b/Source/Tools/Flax.Build/Deps/Dependencies/nethost.cs @@ -89,7 +89,10 @@ namespace Flax.Deps.Dependencies os = "windows"; runtimeFlavor = "Mono"; buildMonoAotCross = true; - buildArgs = $" -subset mono+libs -cmakeargs \"-DDISABLE_JIT=1-DENABLE_PERFTRACING=0-DDISABLE_REFLECTION_EMIT=1-DDISABLE_EVENTPIPE=1-DDISABLE_COM=1-DDISABLE_PROFILER=1-DDISABLE_COMPONENTS=1\" /p:FeaturePerfTracing=false /p:FeatureManagedEtw=false /p:FeatureManagedEtwChannels=false /p:FeatureEtw=false /p:ApiCompatValidateAssemblies=false"; + var defines = "-D_GAMING_XBOX=1-DDISABLE_JIT=1-DENABLE_PERFTRACING=0-DDISABLE_REFLECTION_EMIT=1-DDISABLE_EVENTPIPE=1-DDISABLE_COM=1-DDISABLE_PROFILER=1-DDISABLE_COMPONENTS=1"; + defines += targetPlatform == TargetPlatform.XboxScarlett ? "-D_GAMING_XBOX_SCARLETT=1" : "-D_GAMING_XBOX_XBOXONE=1"; + defines += "-DDISABLE_EXECUTABLES=1-DDISABLE_SHARED_LIBS=1"; + buildArgs = $" -subset mono+libs -cmakeargs \"{defines}\" /p:FeaturePerfTracing=false /p:FeatureWin32Registry=false /p:FeatureCominteropApartmentSupport=false /p:FeatureManagedEtw=false /p:FeatureManagedEtwChannels=false /p:FeatureEtw=false /p:ApiCompatValidateAssemblies=false"; break; case TargetPlatform.Linux: os = "linux"; @@ -241,8 +244,13 @@ namespace Flax.Deps.Dependencies case TargetPlatform.XboxScarlett: libs1 = new[] { - "lib/coreclr.dll", - "lib/coreclr.import.lib", + // When using shared library: + //"lib/coreclr.dll", + //"lib/coreclr.import.lib", + + // When using static library: + "lib/monosgen-2.0.lib", + "lib/mono-profiler-aot.lib", }; libs2 = new string[] { From f640452b7b92d0c7d3ec48ea1631347a748820ee Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Sat, 15 Nov 2025 21:57:14 +0100 Subject: [PATCH 22/43] Fix BVH node splitting using stale pointer to invalidated array memory Ensure BuildBVH refreshes its node pointer after growing _bvh so reallocations no longer leave it operating on freed memory, eliminating the sporadic SDF-generation crash. --- .../ModelTool/MeshAccelerationStructure.cpp | 51 ++++++++++--------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/Source/Engine/Tools/ModelTool/MeshAccelerationStructure.cpp b/Source/Engine/Tools/ModelTool/MeshAccelerationStructure.cpp index 2cc989059..6e1b5ae6f 100644 --- a/Source/Engine/Tools/ModelTool/MeshAccelerationStructure.cpp +++ b/Source/Engine/Tools/ModelTool/MeshAccelerationStructure.cpp @@ -21,9 +21,9 @@ static_assert(sizeof(GPUBVH) == sizeof(Float4) * 2, "Invalid BVH structure size void MeshAccelerationStructure::BuildBVH(int32 node, BVHBuild& build) { - auto& root = _bvh[node]; - ASSERT_LOW_LAYER(root.Leaf.IsLeaf); - if (build.MaxLeafSize > 0 && root.Leaf.TriangleCount <= build.MaxLeafSize) + auto* root = &_bvh[node]; + ASSERT_LOW_LAYER(root->Leaf.IsLeaf); + if (build.MaxLeafSize > 0 && root->Leaf.TriangleCount <= build.MaxLeafSize) return; if (build.MaxDepth > 0 && build.NodeDepth >= build.MaxDepth) return; @@ -31,15 +31,16 @@ void MeshAccelerationStructure::BuildBVH(int32 node, BVHBuild& build) // Spawn two leaves const int32 childIndex = _bvh.Count(); _bvh.AddDefault(2); + root = &_bvh[node]; auto& left = _bvh.Get()[childIndex]; auto& right = _bvh.Get()[childIndex + 1]; left.Leaf.IsLeaf = 1; right.Leaf.IsLeaf = 1; - left.Leaf.MeshIndex = root.Leaf.MeshIndex; - right.Leaf.MeshIndex = root.Leaf.MeshIndex; + left.Leaf.MeshIndex = root->Leaf.MeshIndex; + right.Leaf.MeshIndex = root->Leaf.MeshIndex; // Mid-point splitting based on the largest axis - const Float3 boundsSize = root.Bounds.GetSize(); + const Float3 boundsSize = root->Bounds.GetSize(); int32 axisCount = 0; int32 axis = 0; RETRY: @@ -63,11 +64,11 @@ RETRY: // Go to the next axis axis = (axis + 1) % 3; } - const float midPoint = (float)(root.Bounds.Minimum.Raw[axis] + boundsSize.Raw[axis] * 0.5f); - const Mesh& meshData = _meshes[root.Leaf.MeshIndex]; + const float midPoint = (float)(root->Bounds.Minimum.Raw[axis] + boundsSize.Raw[axis] * 0.5f); + const Mesh& meshData = _meshes[root->Leaf.MeshIndex]; const Float3* vb = meshData.VertexBuffer.Get(); - int32 indexStart = root.Leaf.TriangleIndex * 3; - int32 indexEnd = indexStart + root.Leaf.TriangleCount * 3; + int32 indexStart = root->Leaf.TriangleIndex * 3; + int32 indexEnd = indexStart + root->Leaf.TriangleCount * 3; left.Leaf.TriangleCount = 0; right.Leaf.TriangleCount = 0; if (meshData.Use16BitIndexBuffer) @@ -76,7 +77,7 @@ RETRY: { uint16 I0, I1, I2; }; - build.Scratch.Resize(root.Leaf.TriangleCount * sizeof(Tri)); + build.Scratch.Resize(root->Leaf.TriangleCount * sizeof(Tri)); auto dst = (Tri*)build.Scratch.Get(); auto ib16 = meshData.IndexBuffer.Get(); for (int32 i = indexStart; i < indexEnd;) @@ -89,9 +90,9 @@ RETRY: if (centroid <= midPoint) dst[left.Leaf.TriangleCount++] = tri; // Left else - dst[root.Leaf.TriangleCount - ++right.Leaf.TriangleCount] = tri; // Right + dst[root->Leaf.TriangleCount - ++right.Leaf.TriangleCount] = tri; // Right } - Platform::MemoryCopy(ib16 + indexStart, dst, root.Leaf.TriangleCount * 3 * sizeof(uint16)); + Platform::MemoryCopy(ib16 + indexStart, dst, root->Leaf.TriangleCount * 3 * sizeof(uint16)); if (left.Leaf.TriangleCount == 0 || right.Leaf.TriangleCount == 0) { axisCount++; @@ -104,9 +105,9 @@ RETRY: for (int32 i = indexStart; i < indexEnd; i++) left.Bounds.Merge(vb[((uint16*)build.Scratch.Get())[i]]); - right.Bounds = BoundingBox(vb[dst[root.Leaf.TriangleCount - 1].I0]); + right.Bounds = BoundingBox(vb[dst[root->Leaf.TriangleCount - 1].I0]); indexStart = left.Leaf.TriangleCount; - indexEnd = root.Leaf.TriangleCount * 3; + indexEnd = root->Leaf.TriangleCount * 3; for (int32 i = indexStart; i < indexEnd; i++) right.Bounds.Merge(vb[((uint16*)build.Scratch.Get())[i]]); } @@ -116,7 +117,7 @@ RETRY: { uint32 I0, I1, I2; }; - build.Scratch.Resize(root.Leaf.TriangleCount * sizeof(Tri)); + build.Scratch.Resize(root->Leaf.TriangleCount * sizeof(Tri)); auto dst = (Tri*)build.Scratch.Get(); auto ib32 = meshData.IndexBuffer.Get(); for (int32 i = indexStart; i < indexEnd;) @@ -129,9 +130,9 @@ RETRY: if (centroid <= midPoint) dst[left.Leaf.TriangleCount++] = tri; // Left else - dst[root.Leaf.TriangleCount - ++right.Leaf.TriangleCount] = tri; // Right + dst[root->Leaf.TriangleCount - ++right.Leaf.TriangleCount] = tri; // Right } - Platform::MemoryCopy(ib32 + indexStart, dst, root.Leaf.TriangleCount * 3 * sizeof(uint32)); + Platform::MemoryCopy(ib32 + indexStart, dst, root->Leaf.TriangleCount * 3 * sizeof(uint32)); if (left.Leaf.TriangleCount == 0 || right.Leaf.TriangleCount == 0) { axisCount++; @@ -144,22 +145,22 @@ RETRY: for (int32 i = indexStart; i < indexEnd; i++) left.Bounds.Merge(vb[((uint32*)build.Scratch.Get())[i]]); - right.Bounds = BoundingBox(vb[dst[root.Leaf.TriangleCount - 1].I0]); + right.Bounds = BoundingBox(vb[dst[root->Leaf.TriangleCount - 1].I0]); indexStart = left.Leaf.TriangleCount; - indexEnd = root.Leaf.TriangleCount * 3; + indexEnd = root->Leaf.TriangleCount * 3; for (int32 i = indexStart; i < indexEnd; i++) right.Bounds.Merge(vb[((uint32*)build.Scratch.Get())[i]]); } - ASSERT_LOW_LAYER(left.Leaf.TriangleCount + right.Leaf.TriangleCount == root.Leaf.TriangleCount); - left.Leaf.TriangleIndex = root.Leaf.TriangleIndex; + ASSERT_LOW_LAYER(left.Leaf.TriangleCount + right.Leaf.TriangleCount == root->Leaf.TriangleCount); + left.Leaf.TriangleIndex = root->Leaf.TriangleIndex; right.Leaf.TriangleIndex = left.Leaf.TriangleIndex + left.Leaf.TriangleCount; build.MaxNodeTriangles = Math::Max(build.MaxNodeTriangles, (int32)right.Leaf.TriangleCount); build.MaxNodeTriangles = Math::Max(build.MaxNodeTriangles, (int32)right.Leaf.TriangleCount); // Convert into a node - root.Node.IsLeaf = 0; - root.Node.ChildIndex = childIndex; - root.Node.ChildrenCount = 2; + root->Node.IsLeaf = 0; + root->Node.ChildIndex = childIndex; + root->Node.ChildrenCount = 2; // Split children build.NodeDepth++; From ac3b2c0ef22d0b8f6410a25efd8880d0a2ba91c7 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 15 Nov 2025 22:13:22 +0100 Subject: [PATCH 23/43] Fix shader warning --- Source/Shaders/MeshAccelerationStructure.hlsl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Shaders/MeshAccelerationStructure.hlsl b/Source/Shaders/MeshAccelerationStructure.hlsl index c0ae24835..7c281f02a 100644 --- a/Source/Shaders/MeshAccelerationStructure.hlsl +++ b/Source/Shaders/MeshAccelerationStructure.hlsl @@ -32,8 +32,8 @@ struct BVHHit float3 LoadVertexBVH(BVHBuffers bvh, uint index) { - index = bvh.IndexBuffer.Load(index << 2u); - return asfloat(bvh.VertexBuffer.Load3(index * bvh.VertexStride)); + uint vertexIndex = bvh.IndexBuffer.Load(index << 2u); + return asfloat(bvh.VertexBuffer.Load3(vertexIndex * bvh.VertexStride)); } // [https://tavianator.com/2011/ray_box.html] From 6c29877b20cbfcdf08c916d5ac458cad9ad5c1cc Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sat, 15 Nov 2025 15:58:50 -0600 Subject: [PATCH 24/43] Fix spline bezier drawing. --- Source/Engine/Level/Actors/Spline.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Level/Actors/Spline.cpp b/Source/Engine/Level/Actors/Spline.cpp index 25bdc4ce9..d6c2417c8 100644 --- a/Source/Engine/Level/Actors/Spline.cpp +++ b/Source/Engine/Level/Actors/Spline.cpp @@ -518,7 +518,7 @@ namespace Vector3 nextPos = transform.LocalToWorld(next->Value.Translation); DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(nextPos, NodeSizeByDistance(nextPos, scaleByDistance)), color, 0.0f, depthTest); const float d = (next->Time - prev->Time) / 3.0f; - DEBUG_DRAW_BEZIER(prevPos, prevPos + prev->TangentOut.Translation * d, nextPos + next->TangentIn.Translation * d, nextPos, color, 0.0f, depthTest); + DEBUG_DRAW_BEZIER(prevPos, transform.LocalToWorld(prev->Value.Translation + prev->TangentOut.Translation * d), transform.LocalToWorld(next->Value.Translation + next->TangentIn.Translation * d), nextPos, color, 0.0f, depthTest); prev = next; prevPos = nextPos; } From f91c33e17caaf3009c46b422533a6d0037601efc Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 15 Nov 2025 23:42:03 +0100 Subject: [PATCH 25/43] Another fix for shader compilation on Vulkan --- Source/Shaders/MeshAccelerationStructure.hlsl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Source/Shaders/MeshAccelerationStructure.hlsl b/Source/Shaders/MeshAccelerationStructure.hlsl index 7c281f02a..d98d25404 100644 --- a/Source/Shaders/MeshAccelerationStructure.hlsl +++ b/Source/Shaders/MeshAccelerationStructure.hlsl @@ -32,7 +32,8 @@ struct BVHHit float3 LoadVertexBVH(BVHBuffers bvh, uint index) { - uint vertexIndex = bvh.IndexBuffer.Load(index << 2u); + int addr = index << 2u; + uint vertexIndex = bvh.IndexBuffer.Load(addr); return asfloat(bvh.VertexBuffer.Load3(vertexIndex * bvh.VertexStride)); } From 4aba0153f8f3af4714bd85739f9aae38d957d41a Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 15 Nov 2025 14:57:12 -0800 Subject: [PATCH 26/43] Prioritize Dotnet libs on Mono AOT --- Source/Engine/Scripting/Runtime/DotNet.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Scripting/Runtime/DotNet.cpp b/Source/Engine/Scripting/Runtime/DotNet.cpp index 671b6b31b..1b220b017 100644 --- a/Source/Engine/Scripting/Runtime/DotNet.cpp +++ b/Source/Engine/Scripting/Runtime/DotNet.cpp @@ -2028,13 +2028,13 @@ static MonoAssembly* OnMonoAssemblyLoad(const char* aname) String fileName = name; if (!name.EndsWith(TEXT(".dll")) && !name.EndsWith(TEXT(".exe"))) fileName += TEXT(".dll"); - String path = fileName; + String path = Globals::ProjectFolder / String(TEXT("/Dotnet/")) / fileName; if (!FileSystem::FileExists(path)) { path = Globals::ProjectFolder / String(TEXT("/Dotnet/shared/Microsoft.NETCore.App/")) / fileName; if (!FileSystem::FileExists(path)) { - path = Globals::ProjectFolder / String(TEXT("/Dotnet/")) / fileName; + path = fileName; } } From e79af2fd6033ec8cf8e41a3096dd33a3ed039779 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sat, 15 Nov 2025 21:39:21 -0600 Subject: [PATCH 27/43] Handle additional edge cases for anim event. --- Source/Engine/Animations/Graph/AnimGroup.Animation.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp index c76bddf3f..71b90a1fb 100644 --- a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp +++ b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp @@ -246,11 +246,16 @@ void AnimGraphExecutor::ProcessAnimEvents(AnimGraphNode* node, bool loop, float const float duration = k.Value.Duration > 1 ? k.Value.Duration : 0.0f; #define ADD_OUTGOING_EVENT(type) context.Data->OutgoingEvents.Add({ k.Value.Instance, (AnimatedModel*)context.Data->Object, anim, eventTime, eventDeltaTime, AnimGraphInstanceData::OutgoingEvent::type }) if ((k.Time <= eventTimeMax && eventTimeMin <= k.Time + duration - && (Math::FloorToInt(animPos) != 0 && Math::CeilToInt(animPrevPos) != Math::CeilToInt(anim->GetDuration()) && Math::FloorToInt(animPrevPos) != 0 && Math::CeilToInt(animPos) != Math::CeilToInt(anim->GetDuration()))) + && (Math::FloorToInt(animPos) != 0 && Math::CeilToInt(animPrevPos) != Math::CeilToInt(anim->GetDuration()) + && Math::FloorToInt(animPrevPos) != 0 && Math::CeilToInt(animPos) != Math::CeilToInt(anim->GetDuration()))) // Handle the edge case of an event on 0 or on max animation duration during looping || (loop && duration == 0.0f && Math::CeilToInt(animPos) == Math::CeilToInt(anim->GetDuration()) && k.Time == anim->GetDuration()) || (loop && Math::FloorToInt(animPos) == 0 && Math::CeilToInt(animPrevPos) == Math::CeilToInt(anim->GetDuration()) && k.Time == 0.0f) || (loop && Math::FloorToInt(animPrevPos) == 0 && Math::CeilToInt(animPos) == Math::CeilToInt(anim->GetDuration()) && k.Time == 0.0f) + || (Math::FloorToInt(animPos) == 1 && Math::FloorToInt(animPrevPos) == 0 && k.Time == 1.0f) + || (Math::FloorToInt(animPos) == 0 && Math::FloorToInt(animPrevPos) == 1 && k.Time == 1.0f) + || (Math::CeilToInt(animPos) == Math::CeilToInt(anim->GetDuration()) && Math::CeilToInt(animPrevPos) == Math::CeilToInt(anim->GetDuration()) - 1 && k.Time == Math::CeilToInt(anim->GetDuration()) - 1.0f) + || (Math::CeilToInt(animPos) == Math::CeilToInt(anim->GetDuration()) - 1 && Math::CeilToInt(animPrevPos) == Math::CeilToInt(anim->GetDuration()) && k.Time == Math::CeilToInt(anim->GetDuration()) - 1.0f) ) { int32 stateIndex = -1; From 95629e792d6d628615824f3ce13ae4fa7b438fbc Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sat, 15 Nov 2025 22:12:35 -0600 Subject: [PATCH 28/43] Fix additional edge cases --- Source/Engine/Animations/Graph/AnimGroup.Animation.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp index 71b90a1fb..fd62ec537 100644 --- a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp +++ b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp @@ -249,13 +249,16 @@ void AnimGraphExecutor::ProcessAnimEvents(AnimGraphNode* node, bool loop, float && (Math::FloorToInt(animPos) != 0 && Math::CeilToInt(animPrevPos) != Math::CeilToInt(anim->GetDuration()) && Math::FloorToInt(animPrevPos) != 0 && Math::CeilToInt(animPos) != Math::CeilToInt(anim->GetDuration()))) // Handle the edge case of an event on 0 or on max animation duration during looping - || (loop && duration == 0.0f && Math::CeilToInt(animPos) == Math::CeilToInt(anim->GetDuration()) && k.Time == anim->GetDuration()) + || (!loop && duration == 0.0f && Math::CeilToInt(animPos) == Math::CeilToInt(anim->GetDuration()) && Math::CeilToInt(animPrevPos) == Math::CeilToInt(anim->GetDuration()) - 1 && Math::NearEqual(k.Time, anim->GetDuration())) || (loop && Math::FloorToInt(animPos) == 0 && Math::CeilToInt(animPrevPos) == Math::CeilToInt(anim->GetDuration()) && k.Time == 0.0f) || (loop && Math::FloorToInt(animPrevPos) == 0 && Math::CeilToInt(animPos) == Math::CeilToInt(anim->GetDuration()) && k.Time == 0.0f) + || (loop && Math::FloorToInt(animPos) == 0 && Math::CeilToInt(animPrevPos) == Math::CeilToInt(anim->GetDuration()) && Math::NearEqual(k.Time, anim->GetDuration())) + || (loop && Math::FloorToInt(animPrevPos) == 0 && Math::CeilToInt(animPos) == Math::CeilToInt(anim->GetDuration()) && Math::NearEqual(k.Time, anim->GetDuration())) || (Math::FloorToInt(animPos) == 1 && Math::FloorToInt(animPrevPos) == 0 && k.Time == 1.0f) || (Math::FloorToInt(animPos) == 0 && Math::FloorToInt(animPrevPos) == 1 && k.Time == 1.0f) - || (Math::CeilToInt(animPos) == Math::CeilToInt(anim->GetDuration()) && Math::CeilToInt(animPrevPos) == Math::CeilToInt(anim->GetDuration()) - 1 && k.Time == Math::CeilToInt(anim->GetDuration()) - 1.0f) - || (Math::CeilToInt(animPos) == Math::CeilToInt(anim->GetDuration()) - 1 && Math::CeilToInt(animPrevPos) == Math::CeilToInt(anim->GetDuration()) && k.Time == Math::CeilToInt(anim->GetDuration()) - 1.0f) + || (Math::CeilToInt(animPos) == Math::CeilToInt(anim->GetDuration()) && Math::CeilToInt(animPrevPos) == Math::CeilToInt(anim->GetDuration()) - 1 && Math::NearEqual(k.Time, anim->GetDuration() - 1.0f)) + || (Math::CeilToInt(animPos) == Math::CeilToInt(anim->GetDuration()) - 1 && Math::CeilToInt(animPrevPos) == Math::CeilToInt(anim->GetDuration()) && Math::NearEqual(k.Time, anim->GetDuration() - 1.0f)) + || (Math::FloorToInt(animPos) == 0 && Math::FloorToInt(animPrevPos) == 0 && k.Time == 0.0f) ) { int32 stateIndex = -1; From 371a16e37b1888fb8f07049c43c782e06fc3209d Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 16 Nov 2025 14:50:22 -0800 Subject: [PATCH 29/43] Fixes for Xbox with Mono AOT --- Source/ThirdParty/nethost/nethost.Build.cs | 1 + .../Flax.Build/Deps/Dependencies/nethost.cs | 21 +++++++++++++------ 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/Source/ThirdParty/nethost/nethost.Build.cs b/Source/ThirdParty/nethost/nethost.Build.cs index 69d96d637..2910e401d 100644 --- a/Source/ThirdParty/nethost/nethost.Build.cs +++ b/Source/ThirdParty/nethost/nethost.Build.cs @@ -64,6 +64,7 @@ public class nethost : ThirdPartyModule case TargetPlatform.XboxScarlett: options.OutputFiles.Add(Path.Combine(hostRuntime.Path, "monosgen-2.0.lib")); options.OutputFiles.Add(Path.Combine(hostRuntime.Path, "mono-profiler-aot.lib")); + options.OutputFiles.Add(Path.Combine(hostRuntime.Path, "System.Globalization.Native-Static.lib")); break; case TargetPlatform.Linux: options.OutputFiles.Add(Path.Combine(hostRuntime.Path, "libnethost.a")); diff --git a/Source/Tools/Flax.Build/Deps/Dependencies/nethost.cs b/Source/Tools/Flax.Build/Deps/Dependencies/nethost.cs index 0a8181903..4d6a1aab6 100644 --- a/Source/Tools/Flax.Build/Deps/Dependencies/nethost.cs +++ b/Source/Tools/Flax.Build/Deps/Dependencies/nethost.cs @@ -93,6 +93,7 @@ namespace Flax.Deps.Dependencies defines += targetPlatform == TargetPlatform.XboxScarlett ? "-D_GAMING_XBOX_SCARLETT=1" : "-D_GAMING_XBOX_XBOXONE=1"; defines += "-DDISABLE_EXECUTABLES=1-DDISABLE_SHARED_LIBS=1"; buildArgs = $" -subset mono+libs -cmakeargs \"{defines}\" /p:FeaturePerfTracing=false /p:FeatureWin32Registry=false /p:FeatureCominteropApartmentSupport=false /p:FeatureManagedEtw=false /p:FeatureManagedEtwChannels=false /p:FeatureEtw=false /p:ApiCompatValidateAssemblies=false"; + envVars.Add("_GAMING_XBOX", "1"); break; case TargetPlatform.Linux: os = "linux"; @@ -240,20 +241,28 @@ namespace Flax.Deps.Dependencies switch (targetPlatform) { case TargetPlatform.Windows: + libs1 = new[] + { + "lib/coreclr.dll", + "lib/coreclr.import.lib", + }; + libs2 = new[] + { + "System.Globalization.Native.dll", + "System.IO.Compression.Native.dll", + }; + break; case TargetPlatform.XboxOne: case TargetPlatform.XboxScarlett: libs1 = new[] { - // When using shared library: - //"lib/coreclr.dll", - //"lib/coreclr.import.lib", - - // When using static library: "lib/monosgen-2.0.lib", "lib/mono-profiler-aot.lib", }; - libs2 = new string[] + libs2 = new[] { + "lib/System.Globalization.Native-Static.lib", + "lib/System.IO.Compression.Native-Static.lib", }; break; default: From 7a9c58003d04c81f74fa28ffc4bc462b0df62c04 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 17 Nov 2025 04:41:57 -0800 Subject: [PATCH 30/43] Fix video playback on Xbox --- Source/Engine/Video/MF/VideoBackendMF.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Video/MF/VideoBackendMF.cpp b/Source/Engine/Video/MF/VideoBackendMF.cpp index 75950f3aa..b8ef7ee4e 100644 --- a/Source/Engine/Video/MF/VideoBackendMF.cpp +++ b/Source/Engine/Video/MF/VideoBackendMF.cpp @@ -120,6 +120,14 @@ namespace MF else { // Reconfigure decoder to output supported format by force +#if PLATFORM_XBOX_ONE || PLATFORM_XBOX_SCARLETT + // Xbox supports NV12 via HV decoder + auto fallbackFormat = PixelFormat::NV12; + GUID fallbackFormatGuid = MFVideoFormat_NV12; +#else + auto fallbackFormat = PixelFormat::YUY2; + GUID fallbackFormatGuid = MFVideoFormat_YUY2; +#endif IMFMediaType* customType = nullptr; hr = MFCreateMediaType(&customType); if (FAILED(hr)) @@ -128,7 +136,7 @@ namespace MF goto END; } customType->SetGUID(MF_MT_MAJOR_TYPE, majorType); - customType->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_YUY2); + customType->SetGUID(MF_MT_SUBTYPE, fallbackFormatGuid); MFSetAttributeSize(customType, MF_MT_FRAME_SIZE, width, height); hr = playerMF.SourceReader->SetCurrentMediaType(streamIndex, nullptr, customType); if (FAILED(hr)) @@ -136,7 +144,7 @@ namespace MF VIDEO_API_MF_ERROR(SetCurrentMediaType, hr); goto END; } - player.Format = PixelFormat::YUY2; + player.Format = fallbackFormat; customType->Release(); } } From 329ebb6482999ec0330e5451991a88a9b5de1f1d Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 18 Nov 2025 12:07:39 +0100 Subject: [PATCH 31/43] Add custom shader compiler for Xbox Scarlett --- .../Cooker/Platform/GDK/GDKPlatformTools.cpp | 30 +++-- Source/Editor/Cooker/Steps/CookAssetsStep.cpp | 1 + Source/Engine/ShadersCompilation/Config.h | 8 +- .../DirectX/ShaderCompilerD3D.cpp | 2 +- .../DirectX/ShaderCompilerDX.cpp | 20 ++-- .../DirectX/ShaderCompilerDX.h | 8 +- .../ShadersCompilation/ShaderCompiler.h | 16 ++- .../ShadersCompilation.Build.cs | 2 + .../ShadersCompilation/ShadersCompilation.cpp | 106 +++++++++--------- .../ShadersCompilation/ShadersCompilation.h | 4 +- 10 files changed, 112 insertions(+), 85 deletions(-) diff --git a/Source/Editor/Cooker/Platform/GDK/GDKPlatformTools.cpp b/Source/Editor/Cooker/Platform/GDK/GDKPlatformTools.cpp index f9a8f1b82..fa1ac4d0b 100644 --- a/Source/Editor/Cooker/Platform/GDK/GDKPlatformTools.cpp +++ b/Source/Editor/Cooker/Platform/GDK/GDKPlatformTools.cpp @@ -15,26 +15,32 @@ #include "Editor/ProjectInfo.h" #include "Editor/Utilities/EditorUtilities.h" -GDKPlatformTools::GDKPlatformTools() +String GetGDK() { - // Find GDK - Platform::GetEnvironmentVariable(TEXT("GameDKLatest"), _gdkPath); - if (_gdkPath.IsEmpty() || !FileSystem::DirectoryExists(_gdkPath)) + String gdk; + Platform::GetEnvironmentVariable(TEXT("GameDKLatest"), gdk); + if (gdk.IsEmpty() || !FileSystem::DirectoryExists(gdk)) { - _gdkPath.Clear(); - Platform::GetEnvironmentVariable(TEXT("GRDKLatest"), _gdkPath); - if (_gdkPath.IsEmpty() || !FileSystem::DirectoryExists(_gdkPath)) + gdk.Clear(); + Platform::GetEnvironmentVariable(TEXT("GRDKLatest"), gdk); + if (gdk.IsEmpty() || !FileSystem::DirectoryExists(gdk)) { - _gdkPath.Clear(); + gdk.Clear(); } else { - if (_gdkPath.EndsWith(TEXT("GRDK\\"))) - _gdkPath.Remove(_gdkPath.Length() - 6); - else if (_gdkPath.EndsWith(TEXT("GRDK"))) - _gdkPath.Remove(_gdkPath.Length() - 5); + if (gdk.EndsWith(TEXT("GRDK\\"))) + gdk.Remove(gdk.Length() - 6); + else if (gdk.EndsWith(TEXT("GRDK"))) + gdk.Remove(gdk.Length() - 5); } } + return gdk; +} + +GDKPlatformTools::GDKPlatformTools() +{ + _gdkPath = GetGDK(); } DotNetAOTModes GDKPlatformTools::UseAOT() const diff --git a/Source/Editor/Cooker/Steps/CookAssetsStep.cpp b/Source/Editor/Cooker/Steps/CookAssetsStep.cpp index 030c31c41..57654a6b3 100644 --- a/Source/Editor/Cooker/Steps/CookAssetsStep.cpp +++ b/Source/Editor/Cooker/Steps/CookAssetsStep.cpp @@ -526,6 +526,7 @@ bool ProcessShaderBase(CookAssetsStep::AssetCookData& data, ShaderAssetBase* ass #if PLATFORM_TOOLS_XBOX_SCARLETT case BuildPlatform::XboxScarlett: { + options.Platform = PlatformType::XboxScarlett; const char* platformDefineName = "PLATFORM_XBOX_SCARLETT"; COMPILE_PROFILE(DirectX_SM6, SHADER_FILE_CHUNK_INTERNAL_D3D_SM6_CACHE); break; diff --git a/Source/Engine/ShadersCompilation/Config.h b/Source/Engine/ShadersCompilation/Config.h index 8d9730f70..65d596235 100644 --- a/Source/Engine/ShadersCompilation/Config.h +++ b/Source/Engine/ShadersCompilation/Config.h @@ -15,7 +15,6 @@ class MemoryWriteStream; struct FLAXENGINE_API ShaderCompilationOptions { public: - /// /// Name of the target object (name of the shader or material for better logging readability) /// @@ -37,12 +36,16 @@ public: uint32 SourceLength = 0; public: - /// /// Target shader profile /// ShaderProfile Profile = ShaderProfile::Unknown; + /// + /// Target platform, set to invalid value of 0 if not used (platform-independent compilation). + /// + PlatformType Platform = (PlatformType)0; + /// /// Disables shaders compiler optimizations. Can be used to debug shaders on a target platform or to speed up the shaders compilation time. /// @@ -64,7 +67,6 @@ public: Array Macros; public: - /// /// Output stream to write compiled shader cache to /// diff --git a/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerD3D.cpp b/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerD3D.cpp index 3edad2488..01ce8faef 100644 --- a/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerD3D.cpp +++ b/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerD3D.cpp @@ -196,7 +196,7 @@ bool ShaderCompilerD3D::CompileShader(ShaderFunctionMeta& meta, WritePermutation default: return true; } - if (_profile == ShaderProfile::DirectX_SM5) + if (GetProfile() == ShaderProfile::DirectX_SM5) { profileName += "_5_0"; } diff --git a/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerDX.cpp b/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerDX.cpp index 5e49da710..aa2f70ac7 100644 --- a/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerDX.cpp +++ b/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerDX.cpp @@ -59,7 +59,8 @@ public: *ppIncludeSource = nullptr; const char* source; int32 sourceLength; - const StringAnsi filename(pFilename); + StringAnsi filename(pFilename); + filename.Replace('\\', '/'); if (ShaderCompiler::GetIncludedFileSource(_context, "", filename.Get(), source, sourceLength)) return E_FAIL; IDxcBlobEncoding* textBlob; @@ -70,25 +71,25 @@ public: } }; -ShaderCompilerDX::ShaderCompilerDX(ShaderProfile profile) - : ShaderCompiler(profile) +ShaderCompilerDX::ShaderCompilerDX(ShaderProfile profile, PlatformType platform, void* dxcCreateInstanceProc) + : ShaderCompiler(profile, platform) { IDxcCompiler3* compiler = nullptr; IDxcLibrary* library = nullptr; IDxcContainerReflection* containerReflection = nullptr; - if (FAILED(DxcCreateInstance(CLSID_DxcCompiler, __uuidof(compiler), reinterpret_cast(&compiler))) || - FAILED(DxcCreateInstance(CLSID_DxcLibrary, __uuidof(library), reinterpret_cast(&library))) || - FAILED(DxcCreateInstance(CLSID_DxcContainerReflection, __uuidof(containerReflection), reinterpret_cast(&containerReflection)))) + DxcCreateInstanceProc createInstance = dxcCreateInstanceProc ? (DxcCreateInstanceProc)dxcCreateInstanceProc : &DxcCreateInstance; + if (FAILED(createInstance(CLSID_DxcCompiler, __uuidof(compiler), reinterpret_cast(&compiler))) || + FAILED(createInstance(CLSID_DxcLibrary, __uuidof(library), reinterpret_cast(&library))) || + FAILED(createInstance(CLSID_DxcContainerReflection, __uuidof(containerReflection), reinterpret_cast(&containerReflection)))) { LOG(Error, "DxcCreateInstance failed"); } _compiler = compiler; _library = library; _containerReflection = containerReflection; - static bool PrintVersion = true; - if (PrintVersion) + static HashSet PrintVersions; + if (PrintVersions.Add(createInstance)) { - PrintVersion = false; IDxcVersionInfo* version = nullptr; if (compiler && SUCCEEDED(compiler->QueryInterface(__uuidof(version), reinterpret_cast(&version)))) { @@ -221,6 +222,7 @@ bool ShaderCompilerDX::CompileShader(ShaderFunctionMeta& meta, WritePermutationD argsFull.Add(TEXT("-D")); argsFull.Add(*d); } + GetArgs(argsFull); // Compile ComPtr results; diff --git a/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerDX.h b/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerDX.h index 7973fe397..a3e6d7073 100644 --- a/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerDX.h +++ b/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerDX.h @@ -22,7 +22,9 @@ public: /// Initializes a new instance of the class. /// /// The profile. - ShaderCompilerDX(ShaderProfile profile); + /// The platform. + /// The custom DXC Compiler factory function. + ShaderCompilerDX(ShaderProfile profile, PlatformType platform = (PlatformType)0, void* dxcCreateInstanceProc = nullptr); /// /// Finalizes an instance of the class. @@ -30,6 +32,10 @@ public: ~ShaderCompilerDX(); protected: + virtual void GetArgs(Array> args) + { + } + // [ShaderCompiler] bool CompileShader(ShaderFunctionMeta& meta, WritePermutationData customDataWrite = nullptr) override; bool OnCompileBegin() override; diff --git a/Source/Engine/ShadersCompilation/ShaderCompiler.h b/Source/Engine/ShadersCompilation/ShaderCompiler.h index 79cdb1cae..36fd592e6 100644 --- a/Source/Engine/ShadersCompilation/ShaderCompiler.h +++ b/Source/Engine/ShadersCompilation/ShaderCompiler.h @@ -23,10 +23,11 @@ public: }; private: + ShaderProfile _profile; + PlatformType _platform; Array _funcNameDefineBuffer; protected: - ShaderProfile _profile; ShaderCompilationContext* _context = nullptr; Array _globalMacros; Array _macros; @@ -37,8 +38,10 @@ public: /// Initializes a new instance of the class. /// /// The profile. - ShaderCompiler(ShaderProfile profile) + /// The platform. + ShaderCompiler(ShaderProfile profile, PlatformType platform = (PlatformType)0) : _profile(profile) + , _platform(platform) { } @@ -51,12 +54,19 @@ public: /// /// Gets shader profile supported by this compiler. /// - /// The shader profile. FORCE_INLINE ShaderProfile GetProfile() const { return _profile; } + /// + /// Gets target platform supported by this compiler. Returns invalid value of '0' if any platform works. + /// + FORCE_INLINE PlatformType GetPlatform() const + { + return _platform; + } + /// /// Performs the shader compilation. /// diff --git a/Source/Engine/ShadersCompilation/ShadersCompilation.Build.cs b/Source/Engine/ShadersCompilation/ShadersCompilation.Build.cs index 0e203f656..03ab0a74e 100644 --- a/Source/Engine/ShadersCompilation/ShadersCompilation.Build.cs +++ b/Source/Engine/ShadersCompilation/ShadersCompilation.Build.cs @@ -63,6 +63,8 @@ public class ShadersCompilation : EngineModule options.PrivateDependencies.Add("ShaderCompilerPS4"); if (Sdk.HasValid("PS5Sdk")) options.PrivateDependencies.Add("ShaderCompilerPS5"); + if (Flax.Build.Platform.GetPlatform(TargetPlatform.XboxScarlett, true) != null) + options.PrivateDependencies.Add("ShaderCompilerXboxScarlett"); } /// diff --git a/Source/Engine/ShadersCompilation/ShadersCompilation.cpp b/Source/Engine/ShadersCompilation/ShadersCompilation.cpp index accfeaad5..f7d1367ba 100644 --- a/Source/Engine/ShadersCompilation/ShadersCompilation.cpp +++ b/Source/Engine/ShadersCompilation/ShadersCompilation.cpp @@ -48,6 +48,9 @@ #if COMPILE_WITH_PS5_SHADER_COMPILER #include "Platforms/PS5/Engine/ShaderCompilerPS5/ShaderCompilerPS5.h" #endif +#if COMPILE_WITH_XBOX_SCARLETT_SHADER_COMPILER +#include "Platforms/XboxScarlett/Engine/ShaderCompilerXboxScarlett/ShaderCompilerXboxScarlett.h" +#endif namespace ShadersCompilationImpl { @@ -165,20 +168,16 @@ bool ShadersCompilation::Compile(ShaderCompilationOptions& options) bool result; { ShaderCompilationContext context(&options, &meta); - - // Request shaders compiler - auto compiler = RequestCompiler(options.Profile); + auto compiler = RequestCompiler(options.Profile, options.Platform); if (compiler == nullptr) { LOG(Error, "Shader compiler request failed."); return true; } - ASSERT(compiler->GetProfile() == options.Profile); + ASSERT_LOW_LAYER(compiler->GetProfile() == options.Profile); // Call compilation process result = compiler->Compile(&context); - - // Dismiss compiler FreeCompiler(compiler); #if GPU_USE_SHADERS_DEBUG_LAYER @@ -210,65 +209,65 @@ bool ShadersCompilation::Compile(ShaderCompilationOptions& options) return result; } -ShaderCompiler* ShadersCompilation::CreateCompiler(ShaderProfile profile) +ShaderCompiler* ShadersCompilation::RequestCompiler(ShaderProfile profile, PlatformType platform) { - ShaderCompiler* result = nullptr; - - switch (profile) - { -#if COMPILE_WITH_D3D_SHADER_COMPILER - case ShaderProfile::DirectX_SM4: - case ShaderProfile::DirectX_SM5: - result = New(profile); - break; -#endif -#if COMPILE_WITH_DX_SHADER_COMPILER - case ShaderProfile::DirectX_SM6: - result = New(profile); - break; -#endif -#if COMPILE_WITH_VK_SHADER_COMPILER - case ShaderProfile::Vulkan_SM5: - result = New(profile); - break; -#endif -#if COMPILE_WITH_PS4_SHADER_COMPILER - case ShaderProfile::PS4: - result = New(); - break; -#endif -#if COMPILE_WITH_PS5_SHADER_COMPILER - case ShaderProfile::PS5: - result = New(); - break; -#endif - default: - break; - } - ASSERT_LOW_LAYER(result == nullptr || result->GetProfile() == profile); - - return result; -} - -ShaderCompiler* ShadersCompilation::RequestCompiler(ShaderProfile profile) -{ - ShaderCompiler* compiler; ScopeLock lock(Locker); // Try to find ready compiler + ShaderCompiler* compiler = nullptr; for (int32 i = 0; i < ReadyCompilers.Count(); i++) { - compiler = ReadyCompilers[i]; - if (compiler->GetProfile() == profile) + compiler = ReadyCompilers.Get()[i]; + if (compiler->GetProfile() == profile && + (compiler->GetPlatform() == platform || (int32)compiler->GetPlatform() == 0)) { - // Use it ReadyCompilers.RemoveAt(i); return compiler; } } // Create new compiler for a target profile - compiler = CreateCompiler(profile); + switch (profile) + { +#if COMPILE_WITH_D3D_SHADER_COMPILER + case ShaderProfile::DirectX_SM4: + case ShaderProfile::DirectX_SM5: + compiler = New(profile); + break; +#endif +#if COMPILE_WITH_DX_SHADER_COMPILER + case ShaderProfile::DirectX_SM6: + switch (platform) + { + case PlatformType::XboxScarlett: +#if COMPILE_WITH_XBOX_SCARLETT_SHADER_COMPILER + compiler = New(); +#endif + break; + default: + compiler = New(profile); + break; + } + break; +#endif +#if COMPILE_WITH_VK_SHADER_COMPILER + case ShaderProfile::Vulkan_SM5: + compiler = New(profile); + break; +#endif +#if COMPILE_WITH_PS4_SHADER_COMPILER + case ShaderProfile::PS4: + compiler = New(); + break; +#endif +#if COMPILE_WITH_PS5_SHADER_COMPILER + case ShaderProfile::PS5: + compiler = New(); + break; +#endif + default: + break; + } if (compiler == nullptr) { LOG(Error, "Cannot create Shader Compiler for profile {0}", ::ToString(profile)); @@ -414,7 +413,8 @@ String ShadersCompilation::ResolveShaderPath(StringView path) // Skip to the last root start './' but preserve the leading one for (int32 i = path.Length() - 2; i >= 2; i--) { - if (StringUtils::Compare(path.Get() + i, TEXT("./"), 2) == 0) + const Char* pos = path.Get() + i; + if (pos[0] == '.' && pos[1] == '/') { path = path.Substring(i); break; diff --git a/Source/Engine/ShadersCompilation/ShadersCompilation.h b/Source/Engine/ShadersCompilation/ShadersCompilation.h index 0c251f61e..b32c9f51a 100644 --- a/Source/Engine/ShadersCompilation/ShadersCompilation.h +++ b/Source/Engine/ShadersCompilation/ShadersCompilation.h @@ -48,9 +48,7 @@ public: static String CompactShaderPath(StringView path); private: - - static ShaderCompiler* CreateCompiler(ShaderProfile profile); - static ShaderCompiler* RequestCompiler(ShaderProfile profile); + static ShaderCompiler* RequestCompiler(ShaderProfile profile, PlatformType platform); static void FreeCompiler(ShaderCompiler* compiler); }; From 7e6b040258fde3f6f1e56e2ae5cc1121ca0f7a7b Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 18 Nov 2025 12:08:11 +0100 Subject: [PATCH 32/43] Update DXC shader compiler to 1.8 version (for D3D12) --- .../Windows/Binaries/ThirdParty/x64/d3dcompiler_47.dll | 4 ++-- .../Windows/Binaries/ThirdParty/x64/d3dcompiler_47.lib | 4 ++-- .../Platforms/Windows/Binaries/ThirdParty/x64/dxcompiler.dll | 4 ++-- .../Platforms/Windows/Binaries/ThirdParty/x64/dxcompiler.lib | 4 ++-- Source/Platforms/Windows/Binaries/ThirdParty/x64/dxil.dll | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Source/Platforms/Windows/Binaries/ThirdParty/x64/d3dcompiler_47.dll b/Source/Platforms/Windows/Binaries/ThirdParty/x64/d3dcompiler_47.dll index 0b80ba584..ae087886a 100644 --- a/Source/Platforms/Windows/Binaries/ThirdParty/x64/d3dcompiler_47.dll +++ b/Source/Platforms/Windows/Binaries/ThirdParty/x64/d3dcompiler_47.dll @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:352c4ce7151fe9bc37dd9ceddb958a5ee75851c57df961f6381ec552affba198 -size 4916856 +oid sha256:0c38df5cc6a1263def77ae1c06216e8ec2e57c34be5087efc42e84154afad5cb +size 4741464 diff --git a/Source/Platforms/Windows/Binaries/ThirdParty/x64/d3dcompiler_47.lib b/Source/Platforms/Windows/Binaries/ThirdParty/x64/d3dcompiler_47.lib index 95e6bc166..21d7c5f14 100644 --- a/Source/Platforms/Windows/Binaries/ThirdParty/x64/d3dcompiler_47.lib +++ b/Source/Platforms/Windows/Binaries/ThirdParty/x64/d3dcompiler_47.lib @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:be5917a82c8dc43cad798f7a6c11b1dd885eec8ee9675057804ec367f322e7ad -size 8314 +oid sha256:2529bc6d166df699df5fd0f77d9101a2bd24f3231b4040ef72feac5851801674 +size 7930 diff --git a/Source/Platforms/Windows/Binaries/ThirdParty/x64/dxcompiler.dll b/Source/Platforms/Windows/Binaries/ThirdParty/x64/dxcompiler.dll index 4e56df12a..2c0773566 100644 --- a/Source/Platforms/Windows/Binaries/ThirdParty/x64/dxcompiler.dll +++ b/Source/Platforms/Windows/Binaries/ThirdParty/x64/dxcompiler.dll @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7f504ba5f5173c50ba4ce2771c3d32618f886e1c990960ba706f58872224541b -size 14722664 +oid sha256:c56309c84bc59540b1087ad4ea1930752fa37d10d730919fc7676749a4653ed0 +size 14316936 diff --git a/Source/Platforms/Windows/Binaries/ThirdParty/x64/dxcompiler.lib b/Source/Platforms/Windows/Binaries/ThirdParty/x64/dxcompiler.lib index c77fc6661..a83957886 100644 --- a/Source/Platforms/Windows/Binaries/ThirdParty/x64/dxcompiler.lib +++ b/Source/Platforms/Windows/Binaries/ThirdParty/x64/dxcompiler.lib @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:17dfb5b37d5aec03943083c04899b0815556d7f10f020fa8f9f9061c971dc010 -size 2086 +oid sha256:b40e1d5069fdd07c0e7675b2a48860588b89732760a5ff348c5a4de61aa084cd +size 2002 diff --git a/Source/Platforms/Windows/Binaries/ThirdParty/x64/dxil.dll b/Source/Platforms/Windows/Binaries/ThirdParty/x64/dxil.dll index ec6f74a5b..9dbf6a623 100644 --- a/Source/Platforms/Windows/Binaries/ThirdParty/x64/dxil.dll +++ b/Source/Platforms/Windows/Binaries/ThirdParty/x64/dxil.dll @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6c37738cd2fb4d659b0f49dead8311ae75c93b8c6602b991c00e070f7be20bc1 -size 1508472 +oid sha256:39dbd950f39f75f5c37bcd224c524accba76686adaa372b8287facfc46892b1c +size 1509720 From 5f0e1253cca20e757d2fdb91eb344ae32faf91aa Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 18 Nov 2025 12:08:54 +0100 Subject: [PATCH 33/43] Refactor DX12 Root Signature creation to support offline construction Fix running D3D12 on integrated AMD GPU --- Source/Engine/Core/Types/StringBuilder.h | 5 + .../DirectX/DX12/GPUContextDX12.cpp | 1 + .../DirectX/DX12/GPUDeviceDX12.cpp | 480 ++++++++++++------ .../DirectX/DX12/GPUDeviceDX12.h | 5 - .../DirectX/DX12/RootSignatureDX12.h | 33 ++ 5 files changed, 354 insertions(+), 170 deletions(-) create mode 100644 Source/Engine/GraphicsDevice/DirectX/DX12/RootSignatureDX12.h diff --git a/Source/Engine/Core/Types/StringBuilder.h b/Source/Engine/Core/Types/StringBuilder.h index 925111cc3..620ac8a13 100644 --- a/Source/Engine/Core/Types/StringBuilder.h +++ b/Source/Engine/Core/Types/StringBuilder.h @@ -215,6 +215,11 @@ public: return String(_data.Get(), _data.Count()); } + StringAnsi ToStringAnsi() const + { + return StringAnsi(_data.Get(), _data.Count()); + } + StringView ToStringView() const; }; diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.cpp index b33f94d61..13769f2dd 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.cpp @@ -29,6 +29,7 @@ #include "GPUVertexLayoutDX12.h" #include "CommandQueueDX12.h" #include "DescriptorHeapDX12.h" +#include "RootSignatureDX12.h" #include "Engine/Graphics/RenderTask.h" #include "Engine/GraphicsDevice/DirectX/RenderToolsDX.h" #include "Engine/Debug/Exceptions/NotImplementedException.h" diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp index 88334be60..62070ecda 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp @@ -12,6 +12,9 @@ #include "GPUSamplerDX12.h" #include "GPUVertexLayoutDX12.h" #include "GPUSwapChainDX12.h" +#include "RootSignatureDX12.h" +#include "UploadBufferDX12.h" +#include "CommandQueueDX12.h" #include "Engine/Engine/Engine.h" #include "Engine/Engine/CommandLine.h" #include "Engine/Graphics/RenderTask.h" @@ -21,20 +24,23 @@ #include "Engine/Profiler/ProfilerMemory.h" #include "Engine/Core/Log.h" #include "Engine/Core/Config/PlatformSettings.h" -#include "UploadBufferDX12.h" -#include "CommandQueueDX12.h" +#include "Engine/Core/Types/StringBuilder.h" #include "Engine/Core/Utilities.h" #include "Engine/Threading/Threading.h" #include "CommandSignatureDX12.h" static bool CheckDX12Support(IDXGIAdapter* adapter) { +#if PLATFORM_XBOX_SCARLETT || PLATFORM_XBOX_ONE + return true; +#else // Try to create device if (SUCCEEDED(D3D12CreateDevice(adapter, D3D_FEATURE_LEVEL_11_0, _uuidof(ID3D12Device), nullptr))) { return true; } return false; +#endif } GPUVertexLayoutDX12::GPUVertexLayoutDX12(GPUDeviceDX12* device, const Elements& elements, bool explicitOffsets) @@ -55,6 +61,310 @@ GPUVertexLayoutDX12::GPUVertexLayoutDX12(GPUDeviceDX12* device, const Elements& } } +RootSignatureDX12::RootSignatureDX12() +{ + // Clear structures + Platform::MemoryClear(this, sizeof(*this)); + + // Descriptor tables + { + // SRVs + D3D12_DESCRIPTOR_RANGE& range = _ranges[0]; + range.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV; + range.NumDescriptors = GPU_MAX_SR_BINDED; + range.BaseShaderRegister = 0; + range.RegisterSpace = 0; + range.OffsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND; + } + { + // UAVs + D3D12_DESCRIPTOR_RANGE& range = _ranges[1]; + range.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_UAV; + range.NumDescriptors = GPU_MAX_UA_BINDED; + range.BaseShaderRegister = 0; + range.RegisterSpace = 0; + range.OffsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND; + } + { + // Samplers + D3D12_DESCRIPTOR_RANGE& range = _ranges[2]; + range.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER; + range.NumDescriptors = GPU_MAX_SAMPLER_BINDED - GPU_STATIC_SAMPLERS_COUNT; + range.BaseShaderRegister = GPU_STATIC_SAMPLERS_COUNT; + range.RegisterSpace = 0; + range.OffsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND; + } + + // Root parameters + for (int32 i = 0; i < GPU_MAX_CB_BINDED; i++) + { + // CBs + D3D12_ROOT_PARAMETER& param = _parameters[DX12_ROOT_SIGNATURE_CB + i]; + param.ParameterType = D3D12_ROOT_PARAMETER_TYPE_CBV; + param.ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL; + param.Descriptor.ShaderRegister = i; + param.Descriptor.RegisterSpace = 0; + } + { + // SRVs + D3D12_ROOT_PARAMETER& param = _parameters[DX12_ROOT_SIGNATURE_SR]; + param.ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE; + param.ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL; + param.DescriptorTable.NumDescriptorRanges = 1; + param.DescriptorTable.pDescriptorRanges = &_ranges[0]; + } + { + // UAVs + D3D12_ROOT_PARAMETER& param = _parameters[DX12_ROOT_SIGNATURE_UA]; + param.ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE; + param.ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL; + param.DescriptorTable.NumDescriptorRanges = 1; + param.DescriptorTable.pDescriptorRanges = &_ranges[1]; + } + { + // Samplers + D3D12_ROOT_PARAMETER& param = _parameters[DX12_ROOT_SIGNATURE_SAMPLER]; + param.ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE; + param.ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL; + param.DescriptorTable.NumDescriptorRanges = 1; + param.DescriptorTable.pDescriptorRanges = &_ranges[2]; + } + + // Static samplers + static_assert(GPU_STATIC_SAMPLERS_COUNT == ARRAY_COUNT(_staticSamplers), "Update static samplers setup."); + // Linear Clamp + InitSampler(0, D3D12_FILTER_MIN_MAG_MIP_LINEAR, D3D12_TEXTURE_ADDRESS_MODE_CLAMP); + // Point Clamp + InitSampler(1, D3D12_FILTER_MIN_MAG_MIP_POINT, D3D12_TEXTURE_ADDRESS_MODE_CLAMP); + // Linear Wrap + InitSampler(2, D3D12_FILTER_MIN_MAG_MIP_LINEAR, D3D12_TEXTURE_ADDRESS_MODE_WRAP); + // Point Wrap + InitSampler(3, D3D12_FILTER_MIN_MAG_MIP_POINT, D3D12_TEXTURE_ADDRESS_MODE_WRAP); + // Shadow + InitSampler(4, D3D12_FILTER_COMPARISON_MIN_MAG_MIP_POINT, D3D12_TEXTURE_ADDRESS_MODE_CLAMP, D3D12_COMPARISON_FUNC_LESS_EQUAL); + // Shadow PCF + InitSampler(5, D3D12_FILTER_COMPARISON_MIN_MAG_MIP_LINEAR, D3D12_TEXTURE_ADDRESS_MODE_CLAMP, D3D12_COMPARISON_FUNC_LESS_EQUAL); + + // Init + _desc.NumParameters = ARRAY_COUNT(_parameters); + _desc.pParameters = _parameters; + _desc.NumStaticSamplers = ARRAY_COUNT(_staticSamplers); + _desc.pStaticSamplers = _staticSamplers; + _desc.Flags = D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT; +} + +void RootSignatureDX12::InitSampler(int32 i, D3D12_FILTER filter, D3D12_TEXTURE_ADDRESS_MODE address, D3D12_COMPARISON_FUNC comparisonFunc) +{ + auto& sampler = _staticSamplers[i]; + sampler.Filter = filter; + sampler.AddressU = address; + sampler.AddressV = address; + sampler.AddressW = address; + sampler.MipLODBias = 0.0f; + sampler.MaxAnisotropy = 1; + sampler.ComparisonFunc = comparisonFunc; + sampler.BorderColor = D3D12_STATIC_BORDER_COLOR_OPAQUE_BLACK; + sampler.MinLOD = 0; + sampler.MaxLOD = D3D12_FLOAT32_MAX; + sampler.ShaderRegister = i; + sampler.RegisterSpace = 0; + sampler.ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL; +} + +ComPtr RootSignatureDX12::Serialize() const +{ + ComPtr signature; + ComPtr error; + VALIDATE_DIRECTX_CALL(D3D12SerializeRootSignature(&_desc, D3D_ROOT_SIGNATURE_VERSION_1_0, &signature, &error)); + if (error.Get()) + { + LOG(Error, "D3D12SerializeRootSignature failed with error: {}", String((const char*)error->GetBufferPointer())); + } + return signature; +} + +#if USE_EDITOR + +const Char* GetRootSignatureShaderVisibility(D3D12_SHADER_VISIBILITY visibility) +{ + switch (visibility) + { + case D3D12_SHADER_VISIBILITY_VERTEX: + return TEXT(", visibility=SHADER_VISIBILITY_VERTEX"); + case D3D12_SHADER_VISIBILITY_HULL: + return TEXT(", visibility=SHADER_VISIBILITY_HULL"); + case D3D12_SHADER_VISIBILITY_DOMAIN: + return TEXT(", visibility=SHADER_VISIBILITY_DOMAIN"); + case D3D12_SHADER_VISIBILITY_GEOMETRY: + return TEXT(", visibility=SHADER_VISIBILITY_GEOMETRY"); + case D3D12_SHADER_VISIBILITY_PIXEL: + return TEXT(", visibility=SHADER_VISIBILITY_PIXEL"); + case D3D12_SHADER_VISIBILITY_AMPLIFICATION: + return TEXT(", visibility=SHADER_VISIBILITY_AMPLIFICATION"); + case D3D12_SHADER_VISIBILITY_MESH: + return TEXT(", visibility=SHADER_VISIBILITY_MESH"); + case D3D12_SHADER_VISIBILITY_ALL: + default: + return TEXT(""); // Default + } +} + +const Char* GetRootSignatureSamplerFilter(D3D12_FILTER filter) +{ + switch (filter) + { + case D3D12_FILTER_MIN_MAG_MIP_POINT: + return TEXT("FILTER_MIN_MAG_MIP_POINT"); + case D3D12_FILTER_MIN_MAG_MIP_LINEAR: + return TEXT("FILTER_MIN_MAG_MIP_LINEAR"); + case D3D12_FILTER_ANISOTROPIC: + return TEXT("FILTER_ANISOTROPIC"); + case D3D12_FILTER_COMPARISON_MIN_MAG_MIP_POINT: + return TEXT("FILTER_COMPARISON_MIN_MAG_MIP_POINT"); + case D3D12_FILTER_COMPARISON_MIN_MAG_MIP_LINEAR: + return TEXT("FILTER_COMPARISON_MIN_MAG_MIP_LINEAR"); + default: + CRASH; // Not implemented + } +} + +const Char* GetRootSignatureSamplerAddress(D3D12_TEXTURE_ADDRESS_MODE address) +{ + switch (address) + { + case D3D12_TEXTURE_ADDRESS_MODE_WRAP: + return TEXT("TEXTURE_ADDRESS_WRAP"); + case D3D12_TEXTURE_ADDRESS_MODE_MIRROR: + return TEXT("TEXTURE_ADDRESS_MIRROR"); + case D3D12_TEXTURE_ADDRESS_MODE_CLAMP: + return TEXT("TEXTURE_ADDRESS_CLAMP"); + case D3D12_TEXTURE_ADDRESS_MODE_BORDER: + return TEXT("TEXTURE_ADDRESS_BORDER"); + case D3D12_TEXTURE_ADDRESS_MODE_MIRROR_ONCE: + return TEXT("TEXTURE_ADDRESS_MIRROR_ONCE"); + default: + return TEXT(""); + } +} + +const Char* GetRootSignatureSamplerComparisonFunc(D3D12_COMPARISON_FUNC func) +{ + switch (func) + { + case D3D12_COMPARISON_FUNC_NEVER: + return TEXT("COMPARISON_NEVER"); + case D3D12_COMPARISON_FUNC_LESS: + return TEXT("COMPARISON_LESS"); + case D3D12_COMPARISON_FUNC_EQUAL: + return TEXT("COMPARISON_EQUAL"); + case D3D12_COMPARISON_FUNC_LESS_EQUAL: + return TEXT("COMPARISON_LESS_EQUAL"); + case D3D12_COMPARISON_FUNC_GREATER: + return TEXT("COMPARISON_GREATER"); + case D3D12_COMPARISON_FUNC_NOT_EQUAL: + return TEXT("COMPARISON_NOT_EQUAL"); + case D3D12_COMPARISON_FUNC_GREATER_EQUAL: + return TEXT("COMPARISON_GREATER_EQUAL"); + case D3D12_COMPARISON_FUNC_ALWAYS: + default: + return TEXT("COMPARISON_ALWAYS"); + } +} + +void RootSignatureDX12::ToString(StringBuilder& sb, bool singleLine) const +{ + // Flags + sb.Append(TEXT("RootFlags(ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT)")); + + // Parameters + const Char newLine = singleLine ? ' ' : '\n'; + for (const D3D12_ROOT_PARAMETER& param : _parameters) + { + const Char* visibility = GetRootSignatureShaderVisibility(param.ShaderVisibility); + switch (param.ParameterType) + { + case D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE: + sb.AppendFormat(TEXT(",{}DescriptorTable("), newLine); + for (uint32 rangeIndex = 0; rangeIndex < param.DescriptorTable.NumDescriptorRanges; rangeIndex++) + { + if (rangeIndex) + sb.Append(TEXT(", ")); + const D3D12_DESCRIPTOR_RANGE& range = param.DescriptorTable.pDescriptorRanges[rangeIndex]; + switch (range.RangeType) + { + case D3D12_DESCRIPTOR_RANGE_TYPE_SRV: + sb.AppendFormat(TEXT("SRV(t{}"), range.BaseShaderRegister); + break; + case D3D12_DESCRIPTOR_RANGE_TYPE_UAV: + sb.AppendFormat(TEXT("UAV(u{}"), range.BaseShaderRegister); + break; + case D3D12_DESCRIPTOR_RANGE_TYPE_CBV: + sb.AppendFormat(TEXT("CBV(b{}"), range.BaseShaderRegister); + break; + case D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER: + sb.AppendFormat(TEXT("Sampler(s{}"), range.BaseShaderRegister); + break; + } + if (range.NumDescriptors != 1) + { + if (range.NumDescriptors == UINT_MAX) + sb.Append(TEXT(", numDescriptors=unbounded")); + else + sb.AppendFormat(TEXT(", numDescriptors={}"), range.NumDescriptors); + } + if (range.OffsetInDescriptorsFromTableStart != D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND) + sb.AppendFormat(TEXT(", offset={}"), range.OffsetInDescriptorsFromTableStart); + sb.Append(')'); + } + sb.AppendFormat(TEXT("{})"), visibility); + break; + case D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS: + sb.AppendFormat(TEXT(",{}RootConstants(num32BitConstants={}, b{}{})"), newLine, param.Constants.Num32BitValues, param.Constants.ShaderRegister, visibility); + break; + case D3D12_ROOT_PARAMETER_TYPE_CBV: + sb.AppendFormat(TEXT(",{}CBV(b{}{})"), newLine, param.Descriptor.ShaderRegister, visibility); + break; + case D3D12_ROOT_PARAMETER_TYPE_SRV: + sb.AppendFormat(TEXT(",{}SRV(t{}{})"), newLine, param.Descriptor.ShaderRegister, visibility); + break; + case D3D12_ROOT_PARAMETER_TYPE_UAV: + sb.AppendFormat(TEXT(",{}UAV(u{}{})"), newLine, param.Descriptor.ShaderRegister, visibility); + break; + } + } + + // Static Samplers + for (const D3D12_STATIC_SAMPLER_DESC& sampler : _staticSamplers) + { + const Char* visibility = GetRootSignatureShaderVisibility(sampler.ShaderVisibility); + sb.AppendFormat(TEXT(",{}StaticSampler(s{}"), newLine, sampler.ShaderRegister); + sb.AppendFormat(TEXT(", filter={}"), GetRootSignatureSamplerFilter(sampler.Filter)); + sb.AppendFormat(TEXT(", addressU={}"), GetRootSignatureSamplerAddress(sampler.AddressU)); + sb.AppendFormat(TEXT(", addressV={}"), GetRootSignatureSamplerAddress(sampler.AddressV)); + sb.AppendFormat(TEXT(", addressW={}"), GetRootSignatureSamplerAddress(sampler.AddressW)); + sb.AppendFormat(TEXT(", comparisonFunc={}"), GetRootSignatureSamplerComparisonFunc(sampler.ComparisonFunc)); + sb.AppendFormat(TEXT(", maxAnisotropy={}"), sampler.MaxAnisotropy); + sb.Append(TEXT(", borderColor=STATIC_BORDER_COLOR_OPAQUE_BLACK")); + sb.AppendFormat(TEXT("{})"), visibility); + } +} + +String RootSignatureDX12::ToString() const +{ + StringBuilder sb; + ToString(sb); + return sb.ToString(); +} + +StringAnsi RootSignatureDX12::ToStringAnsi() const +{ + StringBuilder sb; + ToString(sb); + return sb.ToStringAnsi(); +} + +#endif + GPUDevice* GPUDeviceDX12::Create() { #if PLATFORM_XBOX_SCARLETT || PLATFORM_XBOX_ONE @@ -561,170 +871,10 @@ bool GPUDeviceDX12::Init() } // Create root signature - // TODO: maybe create set of different root signatures? for UAVs, for compute, for simple drawing, for post fx? { - // Descriptor tables - D3D12_DESCRIPTOR_RANGE r[3]; // SRV+UAV+Sampler - { - D3D12_DESCRIPTOR_RANGE& range = r[0]; - range.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV; - range.NumDescriptors = GPU_MAX_SR_BINDED; - range.BaseShaderRegister = 0; - range.RegisterSpace = 0; - range.OffsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND; - } - { - D3D12_DESCRIPTOR_RANGE& range = r[1]; - range.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_UAV; - range.NumDescriptors = GPU_MAX_UA_BINDED; - range.BaseShaderRegister = 0; - range.RegisterSpace = 0; - range.OffsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND; - } - { - D3D12_DESCRIPTOR_RANGE& range = r[2]; - range.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER; - range.NumDescriptors = GPU_MAX_SAMPLER_BINDED - GPU_STATIC_SAMPLERS_COUNT; - range.BaseShaderRegister = GPU_STATIC_SAMPLERS_COUNT; - range.RegisterSpace = 0; - range.OffsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND; - } - - // Root parameters - D3D12_ROOT_PARAMETER rootParameters[GPU_MAX_CB_BINDED + 3]; - for (int32 i = 0; i < GPU_MAX_CB_BINDED; i++) - { - // CB - D3D12_ROOT_PARAMETER& rootParam = rootParameters[DX12_ROOT_SIGNATURE_CB + i]; - rootParam.ParameterType = D3D12_ROOT_PARAMETER_TYPE_CBV; - rootParam.ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL; - rootParam.Descriptor.ShaderRegister = i; - rootParam.Descriptor.RegisterSpace = 0; - } - { - // SRVs - D3D12_ROOT_PARAMETER& rootParam = rootParameters[DX12_ROOT_SIGNATURE_SR]; - rootParam.ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE; - rootParam.ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL; - rootParam.DescriptorTable.NumDescriptorRanges = 1; - rootParam.DescriptorTable.pDescriptorRanges = &r[0]; - } - { - // UAVs - D3D12_ROOT_PARAMETER& rootParam = rootParameters[DX12_ROOT_SIGNATURE_UA]; - rootParam.ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE; - rootParam.ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL; - rootParam.DescriptorTable.NumDescriptorRanges = 1; - rootParam.DescriptorTable.pDescriptorRanges = &r[1]; - } - { - // Samplers - D3D12_ROOT_PARAMETER& rootParam = rootParameters[DX12_ROOT_SIGNATURE_SAMPLER]; - rootParam.ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE; - rootParam.ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL; - rootParam.DescriptorTable.NumDescriptorRanges = 1; - rootParam.DescriptorTable.pDescriptorRanges = &r[2]; - } - - // Static samplers - D3D12_STATIC_SAMPLER_DESC staticSamplers[6]; - static_assert(GPU_STATIC_SAMPLERS_COUNT == ARRAY_COUNT(staticSamplers), "Update static samplers setup."); - // Linear Clamp - staticSamplers[0].Filter = D3D12_FILTER_MIN_MAG_MIP_LINEAR; - staticSamplers[0].AddressU = D3D12_TEXTURE_ADDRESS_MODE_CLAMP; - staticSamplers[0].AddressV = D3D12_TEXTURE_ADDRESS_MODE_CLAMP; - staticSamplers[0].AddressW = D3D12_TEXTURE_ADDRESS_MODE_CLAMP; - staticSamplers[0].MipLODBias = 0.0f; - staticSamplers[0].MaxAnisotropy = 1; - staticSamplers[0].BorderColor = D3D12_STATIC_BORDER_COLOR_OPAQUE_BLACK; - staticSamplers[0].MinLOD = 0; - staticSamplers[0].MaxLOD = D3D12_FLOAT32_MAX; - staticSamplers[0].ShaderRegister = 0; - staticSamplers[0].RegisterSpace = 0; - staticSamplers[0].ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL; - // Point Clamp - staticSamplers[1].Filter = D3D12_FILTER_MIN_MAG_MIP_POINT; - staticSamplers[1].AddressU = D3D12_TEXTURE_ADDRESS_MODE_CLAMP; - staticSamplers[1].AddressV = D3D12_TEXTURE_ADDRESS_MODE_CLAMP; - staticSamplers[1].AddressW = D3D12_TEXTURE_ADDRESS_MODE_CLAMP; - staticSamplers[1].MipLODBias = 0.0f; - staticSamplers[1].MaxAnisotropy = 1; - staticSamplers[1].BorderColor = D3D12_STATIC_BORDER_COLOR_OPAQUE_BLACK; - staticSamplers[1].MinLOD = 0; - staticSamplers[1].MaxLOD = D3D12_FLOAT32_MAX; - staticSamplers[1].ShaderRegister = 1; - staticSamplers[1].RegisterSpace = 0; - staticSamplers[1].ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL; - // Linear Wrap - staticSamplers[2].Filter = D3D12_FILTER_MIN_MAG_MIP_LINEAR; - staticSamplers[2].AddressU = D3D12_TEXTURE_ADDRESS_MODE_WRAP; - staticSamplers[2].AddressV = D3D12_TEXTURE_ADDRESS_MODE_WRAP; - staticSamplers[2].AddressW = D3D12_TEXTURE_ADDRESS_MODE_WRAP; - staticSamplers[2].MipLODBias = 0.0f; - staticSamplers[2].MaxAnisotropy = 1; - staticSamplers[2].BorderColor = D3D12_STATIC_BORDER_COLOR_OPAQUE_BLACK; - staticSamplers[2].MinLOD = 0; - staticSamplers[2].MaxLOD = D3D12_FLOAT32_MAX; - staticSamplers[2].ShaderRegister = 2; - staticSamplers[2].RegisterSpace = 0; - staticSamplers[2].ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL; - // Point Wrap - staticSamplers[3].Filter = D3D12_FILTER_MIN_MAG_MIP_POINT; - staticSamplers[3].AddressU = D3D12_TEXTURE_ADDRESS_MODE_WRAP; - staticSamplers[3].AddressV = D3D12_TEXTURE_ADDRESS_MODE_WRAP; - staticSamplers[3].AddressW = D3D12_TEXTURE_ADDRESS_MODE_WRAP; - staticSamplers[3].MipLODBias = 0.0f; - staticSamplers[3].MaxAnisotropy = 1; - staticSamplers[3].BorderColor = D3D12_STATIC_BORDER_COLOR_OPAQUE_BLACK; - staticSamplers[3].MinLOD = 0; - staticSamplers[3].MaxLOD = D3D12_FLOAT32_MAX; - staticSamplers[3].ShaderRegister = 3; - staticSamplers[3].RegisterSpace = 0; - staticSamplers[3].ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL; - // Shadow - staticSamplers[4].Filter = D3D12_FILTER_COMPARISON_MIN_MAG_MIP_POINT; - staticSamplers[4].AddressU = D3D12_TEXTURE_ADDRESS_MODE_CLAMP; - staticSamplers[4].AddressV = D3D12_TEXTURE_ADDRESS_MODE_CLAMP; - staticSamplers[4].AddressW = D3D12_TEXTURE_ADDRESS_MODE_CLAMP; - staticSamplers[4].MipLODBias = 0.0f; - staticSamplers[4].MaxAnisotropy = 1; - staticSamplers[4].ComparisonFunc = D3D12_COMPARISON_FUNC_LESS_EQUAL; - staticSamplers[4].BorderColor = D3D12_STATIC_BORDER_COLOR_OPAQUE_BLACK; - staticSamplers[4].MinLOD = 0; - staticSamplers[4].MaxLOD = D3D12_FLOAT32_MAX; - staticSamplers[4].ShaderRegister = 4; - staticSamplers[4].RegisterSpace = 0; - staticSamplers[4].ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL; - // Shadow PCF - staticSamplers[5].Filter = D3D12_FILTER_COMPARISON_MIN_MAG_MIP_LINEAR; - staticSamplers[5].AddressU = D3D12_TEXTURE_ADDRESS_MODE_CLAMP; - staticSamplers[5].AddressV = D3D12_TEXTURE_ADDRESS_MODE_CLAMP; - staticSamplers[5].AddressW = D3D12_TEXTURE_ADDRESS_MODE_CLAMP; - staticSamplers[5].MipLODBias = 0.0f; - staticSamplers[5].MaxAnisotropy = 1; - staticSamplers[5].ComparisonFunc = D3D12_COMPARISON_FUNC_LESS_EQUAL; - staticSamplers[5].BorderColor = D3D12_STATIC_BORDER_COLOR_OPAQUE_BLACK; - staticSamplers[5].MinLOD = 0; - staticSamplers[5].MaxLOD = D3D12_FLOAT32_MAX; - staticSamplers[5].ShaderRegister = 5; - staticSamplers[5].RegisterSpace = 0; - staticSamplers[5].ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL; - - // Init - D3D12_ROOT_SIGNATURE_DESC rootSignatureDesc; - rootSignatureDesc.NumParameters = ARRAY_COUNT(rootParameters); - rootSignatureDesc.pParameters = rootParameters; - rootSignatureDesc.NumStaticSamplers = ARRAY_COUNT(staticSamplers); - rootSignatureDesc.pStaticSamplers = staticSamplers; - rootSignatureDesc.Flags = D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT; - - // Serialize - ComPtr signature; - ComPtr error; - VALIDATE_DIRECTX_CALL(D3D12SerializeRootSignature(&rootSignatureDesc, D3D_ROOT_SIGNATURE_VERSION_1, &signature, &error)); - - // Create - VALIDATE_DIRECTX_CALL(_device->CreateRootSignature(0, signature->GetBufferPointer(), signature->GetBufferSize(), IID_PPV_ARGS(&_rootSignature))); + RootSignatureDX12 signature; + ComPtr signatureBlob = signature.Serialize(); + VALIDATE_DIRECTX_CALL(_device->CreateRootSignature(0, signatureBlob->GetBufferPointer(), signatureBlob->GetBufferSize(), IID_PPV_ARGS(&_rootSignature))); } if (TimestampQueryHeap.Init()) diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.h b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.h index 064ed9a01..bb5d53458 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.h +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.h @@ -18,11 +18,6 @@ #define DX12_BACK_BUFFER_COUNT 2 #endif -#define DX12_ROOT_SIGNATURE_CB 0 -#define DX12_ROOT_SIGNATURE_SR (GPU_MAX_CB_BINDED+0) -#define DX12_ROOT_SIGNATURE_UA (GPU_MAX_CB_BINDED+1) -#define DX12_ROOT_SIGNATURE_SAMPLER (GPU_MAX_CB_BINDED+2) - class Engine; class WindowsWindow; class GPUContextDX12; diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/RootSignatureDX12.h b/Source/Engine/GraphicsDevice/DirectX/DX12/RootSignatureDX12.h new file mode 100644 index 000000000..156b6ae04 --- /dev/null +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/RootSignatureDX12.h @@ -0,0 +1,33 @@ +// Copyright (c) Wojciech Figat. All rights reserved. + +#pragma once + +#include "Engine/Graphics/Config.h" +#include "../IncludeDirectXHeaders.h" + +#define DX12_ROOT_SIGNATURE_CB 0 +#define DX12_ROOT_SIGNATURE_SR (GPU_MAX_CB_BINDED+0) +#define DX12_ROOT_SIGNATURE_UA (GPU_MAX_CB_BINDED+1) +#define DX12_ROOT_SIGNATURE_SAMPLER (GPU_MAX_CB_BINDED+2) + +struct RootSignatureDX12 +{ +private: + D3D12_ROOT_SIGNATURE_DESC _desc; + D3D12_DESCRIPTOR_RANGE _ranges[3]; + D3D12_ROOT_PARAMETER _parameters[GPU_MAX_CB_BINDED + 3]; + D3D12_STATIC_SAMPLER_DESC _staticSamplers[6]; + +public: + RootSignatureDX12(); + + ComPtr Serialize() const; +#if USE_EDITOR + void ToString(class StringBuilder& sb, bool singleLine = false) const; + String ToString() const; + StringAnsi ToStringAnsi() const; +#endif + +private: + void InitSampler(int32 i, D3D12_FILTER filter, D3D12_TEXTURE_ADDRESS_MODE address, D3D12_COMPARISON_FUNC comparisonFunc = D3D12_COMPARISON_FUNC_LESS_EQUAL); +}; From e03d0f332298e95242ce267e2c0a61474516c88c Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 18 Nov 2025 12:09:20 +0100 Subject: [PATCH 34/43] Fix shader compilation with HLSL 2021 --- Content/Shaders/VolumetricFog.flax | 4 +-- Source/Shaders/Common.hlsl | 34 +++++++++++++++++++++++ Source/Shaders/GammaCorrectionCommon.hlsl | 2 +- Source/Shaders/Math.hlsl | 2 +- Source/Shaders/VolumetricFog.shader | 2 +- 5 files changed, 39 insertions(+), 5 deletions(-) diff --git a/Content/Shaders/VolumetricFog.flax b/Content/Shaders/VolumetricFog.flax index 6f8776336..6dfecbb26 100644 --- a/Content/Shaders/VolumetricFog.flax +++ b/Content/Shaders/VolumetricFog.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4a3e331b7d688d4d3a11da6bb50d4608dbc6437978433a83cc79a365f520bc58 -size 13206 +oid sha256:872ac3560279bfd0aeb989ebac1b49750dd142b985bc40058888dfd2b63fe9b2 +size 13214 diff --git a/Source/Shaders/Common.hlsl b/Source/Shaders/Common.hlsl index 834348ade..10b2855f4 100644 --- a/Source/Shaders/Common.hlsl +++ b/Source/Shaders/Common.hlsl @@ -93,6 +93,40 @@ #endif +// Compiler support for HLSL 2021 that is stricter (need to use or/and/select for vector-based logical operators) +#if !defined(__DXC_VERSION_MAJOR) || (__DXC_VERSION_MAJOR <= 1 && __DXC_VERSION_MINOR < 7) + +bool InternalAnd(bool a, bool b) { return bool(a && b); } +bool2 InternalAnd(bool2 a, bool2 b) { return bool2(a.x && b.x, a.y && b.y); } +bool3 InternalAnd(bool3 a, bool3 b) { return bool3(a.x && b.x, a.y && b.y, a.z && b.z); } +bool4 InternalAnd(bool4 a, bool4 b) { return bool4(a.x && b.x, a.y && b.y, a.z && b.z, a.w && b.w); } + +bool InternalOr(bool a, bool b) { return bool(a || b); } +bool2 InternalOr(bool2 a, bool2 b) { return bool2(a.x || b.x, a.y || b.y); } +bool3 InternalOr(bool3 a, bool3 b) { return bool3(a.x || b.x, a.y || b.y, a.z || b.z); } +bool4 InternalOr(bool4 a, bool4 b) { return bool4(a.x || b.x, a.y || b.y, a.z || b.z, a.w || b.w); } + +#define SELECT_INTERNAL(type) \ + type InternalSelect(bool c, type a, type b) { return type (c ? a.x : b.x); } \ + type##2 InternalSelect(bool c, type##2 a, type##2 b) { return type##2(c ? a.x : b.x, c ? a.y : b.y); } \ + type##2 InternalSelect(bool2 c, type a, type b) { return type##2(c.x ? a : b, c.y ? a : b); } \ + type##2 InternalSelect(bool2 c, type##2 a, type##2 b) { return type##2(c.x ? a.x : b.x, c.y ? a.y : b.y); } \ + type##3 InternalSelect(bool c, type##3 a, type##3 b) { return type##3(c ? a.x : b.x, c ? a.y : b.y, c ? a.z : b.z); } \ + type##3 InternalSelect(bool3 c, type a, type b) { return type##3(c.x ? a : b, c.y ? a : b, c.z ? a : b); } \ + type##3 InternalSelect(bool3 c, type##3 a, type##3 b) { return type##3(c.x ? a.x : b.x, c.y ? a.y : b.y, c.z ? a.z : b.z); } \ + type##4 InternalSelect(bool c, type##4 a, type##4 b) { return type##4(c ? a.x : b.x, c ? a.y : b.y, c ? a.z : b.z, c ? a.w : b.w); } \ + type##4 InternalSelect(bool4 c, type a, type b) { return type##4(c.x ? a : b, c.y ? a : b, c.z ? a : b, c.w ? a : b); } \ + type##4 InternalSelect(bool4 c, type##4 a, type##4 b) { return type##4(c.x ? a.x : b.x, c.y ? a.y : b.y, c.z ? a.z : b.z, c.w ? a.w : b.w); } +SELECT_INTERNAL(uint) +SELECT_INTERNAL(float) +#undef SELECT_INTERNAL + +#define and(a, b) InternalAnd(a, b) +#define or(a, b) InternalOr(a, b) +#define select(c, a, b) InternalSelect(c, a, b) + +#endif + // Compiler attribute fallback #ifndef UNROLL #define UNROLL diff --git a/Source/Shaders/GammaCorrectionCommon.hlsl b/Source/Shaders/GammaCorrectionCommon.hlsl index e410400ad..df188b347 100644 --- a/Source/Shaders/GammaCorrectionCommon.hlsl +++ b/Source/Shaders/GammaCorrectionCommon.hlsl @@ -52,7 +52,7 @@ float3 LinearToSrgb(float3 linearColor) float3 sRGBToLinear(float3 color) { color = max(6.10352e-5, color); - return color > 0.04045 ? pow(color * (1.0 / 1.055) + 0.0521327, 2.4) : color * (1.0 / 12.92); + return select(color > 0.04045, pow(color * (1.0 / 1.055) + 0.0521327, 2.4), color * (1.0 / 12.92)); } float3 LogToLinear(float3 logColor) diff --git a/Source/Shaders/Math.hlsl b/Source/Shaders/Math.hlsl index 4ba7bbbbb..b590b60b4 100644 --- a/Source/Shaders/Math.hlsl +++ b/Source/Shaders/Math.hlsl @@ -8,7 +8,7 @@ uint NextPow2(uint value) { - uint mask = (1 << firstbithigh(value)) - 1; + uint mask = (1u << firstbithigh(value)) - 1u; return (value + mask) & ~mask; } diff --git a/Source/Shaders/VolumetricFog.shader b/Source/Shaders/VolumetricFog.shader index d3885c20b..c55242996 100644 --- a/Source/Shaders/VolumetricFog.shader +++ b/Source/Shaders/VolumetricFog.shader @@ -341,7 +341,7 @@ void CS_LightScattering(uint3 GroupId : SV_GroupID, uint3 DispatchThreadId : SV_ if (all(gridCoordinate < GridSizeInt)) { - scatteringAndExtinction = isnan(scatteringAndExtinction) || isinf(scatteringAndExtinction) ? 0 : scatteringAndExtinction; + scatteringAndExtinction = select(or(isnan(scatteringAndExtinction), isinf(scatteringAndExtinction)), 0, scatteringAndExtinction); RWLightScattering[gridCoordinate] = max(scatteringAndExtinction, 0); } } From 1e4f96486fe4038d39b5e8e9c663d2c1e97be936 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 17 Nov 2025 09:58:08 -0800 Subject: [PATCH 35/43] Fix audio playback bug from video on Xbox --- Source/Engine/Video/MF/VideoBackendMF.cpp | 11 ++++++++++- Source/Engine/Video/Video.Build.cs | 11 +++++++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/Source/Engine/Video/MF/VideoBackendMF.cpp b/Source/Engine/Video/MF/VideoBackendMF.cpp index b8ef7ee4e..df24f5eed 100644 --- a/Source/Engine/Video/MF/VideoBackendMF.cpp +++ b/Source/Engine/Video/MF/VideoBackendMF.cpp @@ -12,6 +12,12 @@ #if USE_EDITOR #include "Editor/Editor.h" #endif +#if PLATFORM_XBOX_ONE || PLATFORM_XBOX_SCARLETT +#define USE_STOCKD3D 0 +#include +#include +#include +#else #include #if WINVER >= _WIN32_WINNT_WINBLUE && WINVER < _WIN32_WINNT_WIN10 // Fix compilation for Windows 8.1 on the latest Windows SDK @@ -24,6 +30,7 @@ typedef enum _MFVideoSphericalFormat { } MFVideoSphericalFormat; #include #include #include +#endif #define VIDEO_API_MF_ERROR(api, err) LOG(Warning, "[VideoBackendMF] {} failed with error 0x{:x}", TEXT(#api), (uint64)err) @@ -111,7 +118,7 @@ namespace MF player.Format = PixelFormat::NV12; else if (subtype == MFVideoFormat_YUY2) player.Format = PixelFormat::YUY2; -#if (WDK_NTDDI_VERSION >= NTDDI_WIN10) +#if (WDK_NTDDI_VERSION >= NTDDI_WIN10) && PLATFORM_WINDOWS else if (subtype == MFVideoFormat_A2R10G10B10) player.Format = PixelFormat::R10G10B10A2_UNorm; else if (subtype == MFVideoFormat_A16B16G16R16F) @@ -150,6 +157,7 @@ namespace MF } else if (majorType == MFMediaType_Audio) { +#if !(PLATFORM_XBOX_ONE || PLATFORM_XBOX_SCARLETT) // TODO: fix missing MFAudioFormat_PCM/MFAudioFormat_Float convertion on Xbox (bug?) player.AudioInfo.SampleRate = MFGetAttributeUINT32(mediaType, MF_MT_AUDIO_SAMPLES_PER_SECOND, 0); player.AudioInfo.NumChannels = MFGetAttributeUINT32(mediaType, MF_MT_AUDIO_NUM_CHANNELS, 0); player.AudioInfo.BitDepth = MFGetAttributeUINT32(mediaType, MF_MT_AUDIO_BITS_PER_SAMPLE, 16); @@ -173,6 +181,7 @@ namespace MF } customType->Release(); } +#endif } result = false; diff --git a/Source/Engine/Video/Video.Build.cs b/Source/Engine/Video/Video.Build.cs index fe99c1433..3975480ee 100644 --- a/Source/Engine/Video/Video.Build.cs +++ b/Source/Engine/Video/Video.Build.cs @@ -22,8 +22,6 @@ public class Video : EngineModule { case TargetPlatform.Windows: case TargetPlatform.UWP: - case TargetPlatform.XboxOne: - case TargetPlatform.XboxScarlett: // Media Foundation options.SourcePaths.Add(Path.Combine(FolderPath, "MF")); options.CompileEnv.PreprocessorDefinitions.Add("VIDEO_API_MF"); @@ -34,6 +32,15 @@ public class Video : EngineModule options.OutputFiles.Add("mfreadwrite.lib"); options.OutputFiles.Add("mfuuid.lib"); break; + case TargetPlatform.XboxOne: + case TargetPlatform.XboxScarlett: + // Media Foundation + options.SourcePaths.Add(Path.Combine(FolderPath, "MF")); + options.CompileEnv.PreprocessorDefinitions.Add("VIDEO_API_MF"); + options.OutputFiles.Add("mfplat.lib"); + options.OutputFiles.Add("mfreadwrite.lib"); + options.OutputFiles.Add("mfuuid.lib"); + break; case TargetPlatform.Mac: case TargetPlatform.iOS: // AVFoundation From c437daf9be4f2643eb70ac490c8346fd669850f8 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 18 Nov 2025 05:42:02 -0800 Subject: [PATCH 36/43] Fix new compiler DXC changes --- Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.cpp | 4 +++- Source/Engine/ShadersCompilation/DirectX/ShaderCompilerDX.cpp | 2 +- Source/Engine/ShadersCompilation/DirectX/ShaderCompilerDX.h | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.cpp index 13769f2dd..6752a9b8d 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.cpp @@ -630,7 +630,9 @@ void GPUContextDX12::flushPS() LOG(Error, "Missing Vertex Layout (not assigned to GPUBuffer). Vertex Shader won't read valid data resulting incorrect visuals."); } #endif - _commandList->SetPipelineState(_currentState->GetState(_rtDepth, _rtCount, _rtHandles, _vertexLayout)); + ID3D12PipelineState* pso = _currentState->GetState(_rtDepth, _rtCount, _rtHandles, _vertexLayout); + ASSERT(pso); + _commandList->SetPipelineState(pso); if (_primitiveTopology != _currentState->PrimitiveTopology) { _primitiveTopology = _currentState->PrimitiveTopology; diff --git a/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerDX.cpp b/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerDX.cpp index aa2f70ac7..ca04c916d 100644 --- a/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerDX.cpp +++ b/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerDX.cpp @@ -222,7 +222,7 @@ bool ShaderCompilerDX::CompileShader(ShaderFunctionMeta& meta, WritePermutationD argsFull.Add(TEXT("-D")); argsFull.Add(*d); } - GetArgs(argsFull); + GetArgs(meta, argsFull); // Compile ComPtr results; diff --git a/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerDX.h b/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerDX.h index a3e6d7073..bf522cf3b 100644 --- a/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerDX.h +++ b/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerDX.h @@ -32,7 +32,7 @@ public: ~ShaderCompilerDX(); protected: - virtual void GetArgs(Array> args) + virtual void GetArgs(ShaderFunctionMeta& meta, Array>& args) { } From de9e282bad4430b7f634908a6c1d772351cfc0b8 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 18 Nov 2025 05:42:21 -0800 Subject: [PATCH 37/43] Fix config version in GDK manifest --- Source/Editor/Cooker/Platform/GDK/GDKPlatformTools.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Editor/Cooker/Platform/GDK/GDKPlatformTools.cpp b/Source/Editor/Cooker/Platform/GDK/GDKPlatformTools.cpp index fa1ac4d0b..256a3478d 100644 --- a/Source/Editor/Cooker/Platform/GDK/GDKPlatformTools.cpp +++ b/Source/Editor/Cooker/Platform/GDK/GDKPlatformTools.cpp @@ -127,7 +127,7 @@ bool GDKPlatformTools::OnPostProcess(CookingData& data, GDKPlatformSettings* pla validName.Add('\0'); sb.Append(TEXT("\n")); - sb.Append(TEXT("\n")); + sb.Append(TEXT("\n")); sb.AppendFormat(TEXT(" \n"), validName.Get(), platformSettings->PublisherName.HasChars() ? platformSettings->PublisherName : TEXT("CN=") + gameSettings->CompanyName, From 2ca435a414c92ba7b08440130d90f40fcbb4d2a3 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 18 Nov 2025 16:11:31 +0100 Subject: [PATCH 38/43] Fix shader graph assets loading to wait for async task #3802 --- Source/Engine/Content/AssetsContainer.h | 6 ++++-- .../Graph/GPU/ParticleEmitterGraph.GPU.Particles.cpp | 8 ++++---- .../Tools/MaterialGenerator/MaterialGenerator.Layer.cpp | 4 ++-- .../MaterialGenerator/MaterialGenerator.Material.cpp | 8 ++++---- Source/Engine/Visject/ShaderGraph.cpp | 4 ++-- 5 files changed, 16 insertions(+), 14 deletions(-) diff --git a/Source/Engine/Content/AssetsContainer.h b/Source/Engine/Content/AssetsContainer.h index aceba1f07..dd9b549b5 100644 --- a/Source/Engine/Content/AssetsContainer.h +++ b/Source/Engine/Content/AssetsContainer.h @@ -18,7 +18,7 @@ public: /// The asset id. /// Loaded asset of null. template - T* LoadAsync(const Guid& id) + T* Load(const Guid& id) { for (auto& e : *this) { @@ -26,8 +26,10 @@ public: return (T*)e.Get(); } auto asset = (T*)::LoadAsset(id, T::TypeInitializer); - if (asset) + if (asset && !asset->WaitForLoaded()) Add(asset); + else + asset = nullptr; return asset; } diff --git a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.Particles.cpp b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.Particles.cpp index 197b53e13..6ae810d82 100644 --- a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.Particles.cpp +++ b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.Particles.cpp @@ -425,8 +425,8 @@ void ParticleEmitterGPUGenerator::ProcessGroupParticles(Box* box, Node* node, Va case 300: { // Load function asset - const auto function = Assets.LoadAsync((Guid)node->Values[0]); - if (!function || function->WaitForLoaded()) + const auto function = Assets.Load((Guid)node->Values[0]); + if (!function) { OnError(node, box, TEXT("Missing or invalid function.")); value = Value::Zero; @@ -439,7 +439,7 @@ void ParticleEmitterGPUGenerator::ProcessGroupParticles(Box* box, Node* node, Va { if (_callStack[i]->Type == GRAPH_NODE_MAKE_TYPE(14, 300)) { - const auto callFunc = Assets.LoadAsync((Guid)_callStack[i]->Values[0]); + const auto callFunc = Assets.Load((Guid)_callStack[i]->Values[0]); if (callFunc == function) { OnError(node, box, String::Format(TEXT("Recursive call to function '{0}'!"), function->ToString())); @@ -514,7 +514,7 @@ void ParticleEmitterGPUGenerator::ProcessGroupFunction(Box* box, Node* node, Val value = Value::Zero; break; } - const auto function = Assets.LoadAsync((Guid)functionCallNode->Values[0]); + const auto function = Assets.Load((Guid)functionCallNode->Values[0]); if (!_functions.TryGet(functionCallNode, graph) || !function) { OnError(node, box, TEXT("Missing calling function graph.")); diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Layer.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Layer.cpp index 73b3058a3..c0f1ede7e 100644 --- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Layer.cpp +++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Layer.cpp @@ -50,8 +50,8 @@ MaterialLayer* MaterialGenerator::GetLayer(const Guid& id, Node* caller) } // Load asset - Asset* asset = Assets.LoadAsync(id); - if (asset == nullptr || asset->WaitForLoaded(10 * 1000)) + Asset* asset = Assets.Load(id); + if (asset == nullptr) { OnError(caller, nullptr, TEXT("Failed to load material asset.")); return nullptr; diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp index 4594446ec..c778c03ee 100644 --- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp +++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp @@ -285,8 +285,8 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value) case 24: { // Load function asset - const auto function = Assets.LoadAsync((Guid)node->Values[0]); - if (!function || function->WaitForLoaded()) + const auto function = Assets.Load((Guid)node->Values[0]); + if (!function) { OnError(node, box, TEXT("Missing or invalid function.")); value = Value::Zero; @@ -299,7 +299,7 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value) { if (_callStack[i]->Type == GRAPH_NODE_MAKE_TYPE(1, 24)) { - const auto callFunc = Assets.LoadAsync((Guid)_callStack[i]->Values[0]); + const auto callFunc = Assets.Load((Guid)_callStack[i]->Values[0]); if (callFunc == function) { OnError(node, box, String::Format(TEXT("Recursive call to function '{0}'!"), function->ToString())); @@ -808,7 +808,7 @@ void MaterialGenerator::ProcessGroupFunction(Box* box, Node* node, Value& value) value = Value::Zero; break; } - const auto function = Assets.LoadAsync((Guid)functionCallNode->Values[0]); + const auto function = Assets.Load((Guid)functionCallNode->Values[0]); if (!_functions.TryGet(functionCallNode, graph) || !function) { OnError(node, box, TEXT("Missing calling function graph.")); diff --git a/Source/Engine/Visject/ShaderGraph.cpp b/Source/Engine/Visject/ShaderGraph.cpp index b6616d159..688e46382 100644 --- a/Source/Engine/Visject/ShaderGraph.cpp +++ b/Source/Engine/Visject/ShaderGraph.cpp @@ -704,8 +704,8 @@ void ShaderGenerator::ProcessGroupTools(Box* box, Node* node, Value& value) case 16: { // Get the variable type - auto asset = Assets.LoadAsync((Guid)node->Values[0]); - if (!asset || asset->WaitForLoaded()) + auto asset = Assets.Load((Guid)node->Values[0]); + if (!asset) { OnError(node, box, TEXT("Failed to load Gameplay Global asset.")); value = Value::Zero; From ed408917c6a1298e103ef73a147b01737c3695e8 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 18 Nov 2025 16:41:24 +0100 Subject: [PATCH 39/43] Fix Visject Surface node dependent connection types init on load #3802 --- Source/Editor/Surface/SurfaceNode.cs | 21 ------------------ .../VisjectSurfaceContext.Serialization.cs | 22 ++++++++++++++----- 2 files changed, 17 insertions(+), 26 deletions(-) diff --git a/Source/Editor/Surface/SurfaceNode.cs b/Source/Editor/Surface/SurfaceNode.cs index e1d7c131f..6312bd68d 100644 --- a/Source/Editor/Surface/SurfaceNode.cs +++ b/Source/Editor/Surface/SurfaceNode.cs @@ -431,27 +431,6 @@ namespace FlaxEditor.Surface /// public bool HasIndependentBoxes => Archetype.IndependentBoxes != null; - /// - /// Gets a value indicating whether this node has dependent boxes with assigned valid types. Otherwise any box has no dependent type assigned. - /// - public bool HasDependentBoxesSetup - { - get - { - if (Archetype.DependentBoxes == null || Archetype.IndependentBoxes == null) - return true; - - for (int i = 0; i < Archetype.DependentBoxes.Length; i++) - { - var b = GetBox(Archetype.DependentBoxes[i]); - if (b != null && b.CurrentType == b.DefaultType) - return false; - } - - return true; - } - } - private static readonly List UpdateStack = new List(); /// diff --git a/Source/Editor/Surface/VisjectSurfaceContext.Serialization.cs b/Source/Editor/Surface/VisjectSurfaceContext.Serialization.cs index a00d37aef..ed19b937f 100644 --- a/Source/Editor/Surface/VisjectSurfaceContext.Serialization.cs +++ b/Source/Editor/Surface/VisjectSurfaceContext.Serialization.cs @@ -178,19 +178,31 @@ namespace FlaxEditor.Surface // Update boxes types for nodes that dependant box types based on incoming connections { - bool keepUpdating = false; - int updateLimit = 100; + bool keepUpdating = true; + int updatesMin = 2, updatesMax = 100; do { + keepUpdating = false; for (int i = 0; i < RootControl.Children.Count; i++) { - if (RootControl.Children[i] is SurfaceNode node && !node.HasDependentBoxesSetup) + if (RootControl.Children[i] is SurfaceNode node) { node.UpdateBoxesTypes(); - keepUpdating = true; + var arch = node.Archetype; + if (arch.DependentBoxes != null && arch.IndependentBoxes != null) + { + foreach (var boxId in arch.DependentBoxes) + { + var b = node.GetBox(boxId); + if (b != null && b.CurrentType == b.DefaultType) + { + keepUpdating = true; + } + } + } } } - } while (keepUpdating && updateLimit-- > 0); + } while ((keepUpdating && --updatesMax > 0) || --updatesMin > 0); } Loaded?.Invoke(this); From 3efb981f00be75f8b82d45791a5935cc9e93713c Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 18 Nov 2025 16:51:43 +0100 Subject: [PATCH 40/43] Fix rare issues on input bugs in Visject --- Source/Editor/Surface/VisjectSurface.Input.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Source/Editor/Surface/VisjectSurface.Input.cs b/Source/Editor/Surface/VisjectSurface.Input.cs index 51fd96ad6..cbc041c09 100644 --- a/Source/Editor/Surface/VisjectSurface.Input.cs +++ b/Source/Editor/Surface/VisjectSurface.Input.cs @@ -469,7 +469,8 @@ namespace FlaxEditor.Surface bool handled = base.OnMouseDown(location, button); if (!handled) CustomMouseDown?.Invoke(ref location, button, ref handled); - if (handled) + var root = Root; + if (handled || root == null) { // Clear flags _isMovingSelection = false; @@ -523,11 +524,11 @@ namespace FlaxEditor.Surface if (_leftMouseDown && controlUnderMouse.CanSelect(ref cLocation)) { // Check if user is pressing control - if (Root.GetKey(KeyboardKeys.Control)) + if (root.GetKey(KeyboardKeys.Control)) { AddToSelection(controlUnderMouse); } - else if (Root.GetKey(KeyboardKeys.Shift)) + else if (root.GetKey(KeyboardKeys.Shift)) { RemoveFromSelection(controlUnderMouse); } @@ -539,7 +540,7 @@ namespace FlaxEditor.Surface } // Start moving selected nodes - if (!Root.GetKey(KeyboardKeys.Shift)) + if (!root.GetKey(KeyboardKeys.Shift)) { StartMouseCapture(); _movingSelectionViewPos = _rootControl.Location; @@ -559,7 +560,7 @@ namespace FlaxEditor.Surface // Start selecting or commenting StartMouseCapture(); - if (!Root.GetKey(KeyboardKeys.Control) && !Root.GetKey(KeyboardKeys.Shift)) + if (!root.GetKey(KeyboardKeys.Control) && !root.GetKey(KeyboardKeys.Shift)) { ClearSelection(); } From c0dda45c7b2cd95235e408900ddebaf5eac9490f Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 18 Nov 2025 18:00:02 +0100 Subject: [PATCH 41/43] Fix crash on exit when C# code was bound to asset unloading event called after C# shutdown --- Source/Engine/Scripting/ManagedCLR/MCore.cpp | 6 ++++++ Source/Engine/Scripting/ManagedCLR/MCore.h | 4 ++++ Source/Engine/Scripting/Runtime/DotNet.cpp | 4 +++- Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs | 1 + 4 files changed, 14 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Scripting/ManagedCLR/MCore.cpp b/Source/Engine/Scripting/ManagedCLR/MCore.cpp index 4300434e3..350cc39d2 100644 --- a/Source/Engine/Scripting/ManagedCLR/MCore.cpp +++ b/Source/Engine/Scripting/ManagedCLR/MCore.cpp @@ -21,6 +21,7 @@ MDomain* MRootDomain = nullptr; MDomain* MActiveDomain = nullptr; Array> MDomains; +bool MCore::Ready = false; MClass* MCore::TypeCache::Void = nullptr; MClass* MCore::TypeCache::Object = nullptr; @@ -301,6 +302,11 @@ bool MProperty::IsStatic() const return false; } +void MCore::OnManagedEventAfterShutdown(const char* eventName) +{ + LOG(Error, "Found a binding leak on '{}' event used by C# scripting after shutdown. Ensure to unregister scripting events from objects during disposing.", ::String(eventName)); +} + MDomain* MCore::GetRootDomain() { return MRootDomain; diff --git a/Source/Engine/Scripting/ManagedCLR/MCore.h b/Source/Engine/Scripting/ManagedCLR/MCore.h index cedebe7b8..1f972af0d 100644 --- a/Source/Engine/Scripting/ManagedCLR/MCore.h +++ b/Source/Engine/Scripting/ManagedCLR/MCore.h @@ -57,6 +57,10 @@ public: static void UnloadScriptingAssemblyLoadContext(); #endif + // Utility for guarding against using C# scripting runtime after shutdown (eg. when asset delegate is not properly disposed). + static bool Ready; + static void OnManagedEventAfterShutdown(const char* eventName); + public: /// /// Utilities for C# object management. diff --git a/Source/Engine/Scripting/Runtime/DotNet.cpp b/Source/Engine/Scripting/Runtime/DotNet.cpp index 1b220b017..d8e5acaff 100644 --- a/Source/Engine/Scripting/Runtime/DotNet.cpp +++ b/Source/Engine/Scripting/Runtime/DotNet.cpp @@ -316,7 +316,8 @@ bool MCore::LoadEngine() char* buildInfo = CallStaticMethod(GetStaticMethodPointer(TEXT("GetRuntimeInformation"))); LOG(Info, ".NET runtime version: {0}", ::String(buildInfo)); - MCore::GC::FreeMemory(buildInfo); + GC::FreeMemory(buildInfo); + Ready = true; return false; } @@ -327,6 +328,7 @@ void MCore::UnloadEngine() return; PROFILE_CPU(); CallStaticMethod(GetStaticMethodPointer(TEXT("Exit"))); + Ready = false; MDomains.ClearDelete(); MRootDomain = nullptr; ShutdownHostfxr(); diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs index 2f251932f..190036af4 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs @@ -2043,6 +2043,7 @@ namespace Flax.Build.Bindings contents.Append(')').AppendLine(); contents.Append(" {").AppendLine(); contents.Append(" static MMethod* method = nullptr;").AppendLine(); + contents.AppendFormat(" if (!MCore::Ready) {{ MCore::OnManagedEventAfterShutdown(\"{0}.{1}\"); return; }}", classTypeNameManaged, eventInfo.Name).AppendLine(); contents.AppendFormat(" if (!method) {{ method = {1}::TypeInitializer.GetClass()->GetMethod(\"Internal_{0}_Invoke\", {2}); CHECK(method); }}", eventInfo.Name, classTypeNameNative, paramsCount).AppendLine(); contents.Append(" MObject* exception = nullptr;").AppendLine(); if (paramsCount == 0) From 8f56ab9534c24a18906c07e9fff1ed900fe024b8 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 18 Nov 2025 18:45:43 +0100 Subject: [PATCH 42/43] Fix crash when creating empty material instance --- Source/Engine/Content/Assets/MaterialInstance.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Content/Assets/MaterialInstance.cpp b/Source/Engine/Content/Assets/MaterialInstance.cpp index 78a276a8a..8d142626e 100644 --- a/Source/Engine/Content/Assets/MaterialInstance.cpp +++ b/Source/Engine/Content/Assets/MaterialInstance.cpp @@ -243,7 +243,8 @@ Asset::LoadResult MaterialInstance::load() ParamsChanged(); } - baseMaterial->RemoveReference(); + if (baseMaterial) + baseMaterial->RemoveReference(); return LoadResult::Ok; } From dfb1fb91a51a90228b38adb7975f43d3a147c172 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 18 Nov 2025 21:29:51 +0100 Subject: [PATCH 43/43] Fix ability to override material instance hidden parameters #3802 --- Source/Editor/Windows/Assets/MaterialInstanceWindow.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Source/Editor/Windows/Assets/MaterialInstanceWindow.cs b/Source/Editor/Windows/Assets/MaterialInstanceWindow.cs index 4a896c674..cb39930f9 100644 --- a/Source/Editor/Windows/Assets/MaterialInstanceWindow.cs +++ b/Source/Editor/Windows/Assets/MaterialInstanceWindow.cs @@ -14,7 +14,6 @@ using FlaxEditor.Surface; using FlaxEditor.Viewport.Previews; using FlaxEngine; using FlaxEngine.GUI; -using FlaxEngine.Utilities; namespace FlaxEditor.Windows.Assets { @@ -430,7 +429,7 @@ namespace FlaxEditor.Windows.Assets for (var i = 0; i < parameters.Length; i++) { var p = parameters[i]; - if (p.IsOverride) + if (p.IsOverride && p.IsPublic) { p.IsOverride = false; actions.Add(new EditParamOverrideAction