diff --git a/Content/Editor/Camera/M_Camera.flax b/Content/Editor/Camera/M_Camera.flax index b5473caa3..94890c96c 100644 --- a/Content/Editor/Camera/M_Camera.flax +++ b/Content/Editor/Camera/M_Camera.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b8b77651be81af931c1e63c1cb411b9f2764b4b0be4a7fa51e2c3c043a063435 -size 38085 +oid sha256:7b4752ca9986784f54afbad450edacdb3f87ed93d6e904c76ba40bb467e58d25 +size 30027 diff --git a/Content/Editor/CubeTexturePreviewMaterial.flax b/Content/Editor/CubeTexturePreviewMaterial.flax index 90bc9f368..9825995fb 100644 --- a/Content/Editor/CubeTexturePreviewMaterial.flax +++ b/Content/Editor/CubeTexturePreviewMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:959f9d4006dc11b0ecbd57cbb406213b2519a388de0d798bff69d61c2042c89b -size 39859 +oid sha256:6740a5d3ecc88c95f296dc0b77d8e177fcf3cc303dbaf9d1602aaf7008eb8b65 +size 31584 diff --git a/Content/Editor/DefaultFontMaterial.flax b/Content/Editor/DefaultFontMaterial.flax index a0f703a85..f464ad768 100644 --- a/Content/Editor/DefaultFontMaterial.flax +++ b/Content/Editor/DefaultFontMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e424d2436f4973a0ecef28c241e222e7e5f992a2ae017e943f4ba033608b3bd7 -size 38383 +oid sha256:c9c2e7cc6393d6f2079d4d064daa0a2663ec84c25be7778b73da22e9c66c226e +size 30108 diff --git a/Content/Editor/Gizmo/FoliageBrushMaterial.flax b/Content/Editor/Gizmo/FoliageBrushMaterial.flax index d8cc3fb6a..af6e065a2 100644 --- a/Content/Editor/Gizmo/FoliageBrushMaterial.flax +++ b/Content/Editor/Gizmo/FoliageBrushMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e3b239a3122fe40c386c3161576cf74c94248d92a3d501b18ed2d658f18742a2 -size 41087 +oid sha256:15e7562cb1f6b58f9030fbd66477b131b577f9d7c08f0d6b67078f02d05e6015 +size 34541 diff --git a/Content/Editor/Gizmo/Material.flax b/Content/Editor/Gizmo/Material.flax index b4917f502..64145ecec 100644 --- a/Content/Editor/Gizmo/Material.flax +++ b/Content/Editor/Gizmo/Material.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:85435f0342a96eeebad705692df74ec19871e28bc35a3d3046f8f63dee4d9334 -size 35945 +oid sha256:26a9333914f7ddf750e8d33be03c739bd3cc4fb455ebecbe83018b63ab8277b2 +size 31067 diff --git a/Content/Editor/Gizmo/MaterialWire.flax b/Content/Editor/Gizmo/MaterialWire.flax index 888846500..4e85879a0 100644 --- a/Content/Editor/Gizmo/MaterialWire.flax +++ b/Content/Editor/Gizmo/MaterialWire.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9ab58f495cdd9fee4c81332a291bbee4f1adc327d7d14b5158d4360aac1ef8fa -size 35158 +oid sha256:405fdb8843f7836807a0efc40564c0ab46eab62072d04b6e209d140e4bedab66 +size 30280 diff --git a/Content/Editor/Gizmo/SelectionOutlineMaterial.flax b/Content/Editor/Gizmo/SelectionOutlineMaterial.flax index 48a87ba88..f3b1474d9 100644 --- a/Content/Editor/Gizmo/SelectionOutlineMaterial.flax +++ b/Content/Editor/Gizmo/SelectionOutlineMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f578d4b9e0dbd295de0674cf46cfc5d2894db225fd2c3cfc443c6f2e9c7167d3 -size 15941 +oid sha256:74ab675aa70e0bfa43e7eedf8b813c8e0a1c792fa59289ef50cfe2cf2505b900 +size 15863 diff --git a/Content/Editor/Gizmo/VertexColorsPreviewMaterial.flax b/Content/Editor/Gizmo/VertexColorsPreviewMaterial.flax index e42b74129..574773030 100644 --- a/Content/Editor/Gizmo/VertexColorsPreviewMaterial.flax +++ b/Content/Editor/Gizmo/VertexColorsPreviewMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c2abe350d858eeb42158b6c5fa8b0875a332bc003038fe846bf63aa3a9117577 -size 39316 +oid sha256:4e79a6d2c67d7dab5a6ef26e433c21db3533c54605627baf16d17cd33e998bb3 +size 31042 diff --git a/Content/Editor/Highlight Material.flax b/Content/Editor/Highlight Material.flax index 9f1de8fcd..6fb6aa10d 100644 --- a/Content/Editor/Highlight Material.flax +++ b/Content/Editor/Highlight Material.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0eba0cb1115fb6510ed32159a8fa5a81bbfabfd0e6ec00f4d5ccbfa0426fad97 -size 35356 +oid sha256:f1a371e03bfc916068cbc2863f3c92dcab3ac0ba3a4839ac0efa5e10e43807b2 +size 28875 diff --git a/Content/Editor/Icons/IconsMaterial.flax b/Content/Editor/Icons/IconsMaterial.flax index 91bba0296..2cf1d5cb7 100644 --- a/Content/Editor/Icons/IconsMaterial.flax +++ b/Content/Editor/Icons/IconsMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:51bceae1877ea1859c8757a1e21abade8cf21eda8857d3204bf4b0239fc66d18 -size 35289 +oid sha256:df1097033d79b531c8f09c2cb95bbb85a8190c6cd0649b77f05dd617410a0b8e +size 28807 diff --git a/Content/Editor/IesProfilePreviewMaterial.flax b/Content/Editor/IesProfilePreviewMaterial.flax index f9e249e81..5466742ea 100644 --- a/Content/Editor/IesProfilePreviewMaterial.flax +++ b/Content/Editor/IesProfilePreviewMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:946c1c4723c46a55c2d47a67009810a58c6e790b0af27137507e2c5cb9f3d64e -size 18669 +oid sha256:b02d31b97be388836c675b7d5814fc5f46623446c9ffd7cff837901694141ad8 +size 18415 diff --git a/Content/Editor/MaterialTemplates/Decal.shader b/Content/Editor/MaterialTemplates/Decal.shader index e0253efd6..452937955 100644 --- a/Content/Editor/MaterialTemplates/Decal.shader +++ b/Content/Editor/MaterialTemplates/Decal.shader @@ -108,15 +108,14 @@ float4 GetVertexColor(MaterialInput input) return 1; } +@8 + // Get material properties function (for pixel shader) Material GetMaterialPS(MaterialInput input) { @4 } -// Fix line for errors/warnings for shader code from template -#line 1000 - // Input macro specified by the material: DECAL_BLEND_MODE #define DECAL_BLEND_MODE_TRANSLUCENT 0 @@ -211,3 +210,5 @@ void PS_Decal( #error "Invalid decal blending mode" #endif } + +@9 diff --git a/Content/Editor/MaterialTemplates/Deformable.shader b/Content/Editor/MaterialTemplates/Deformable.shader new file mode 100644 index 000000000..044f7184c --- /dev/null +++ b/Content/Editor/MaterialTemplates/Deformable.shader @@ -0,0 +1,377 @@ +// File generated by Flax Materials Editor +// Version: @0 + +#define MATERIAL 1 +@3 +#include "./Flax/Common.hlsl" +#include "./Flax/MaterialCommon.hlsl" +#include "./Flax/GBufferCommon.hlsl" +@7 +// Primary constant buffer (with additional material parameters) +META_CB_BEGIN(0, Data) +float4x4 ViewProjectionMatrix; +float4x4 WorldMatrix; +float4x4 LocalMatrix; +float4x4 ViewMatrix; +float3 ViewPos; +float ViewFar; +float3 ViewDir; +float TimeParam; +float4 ViewInfo; +float4 ScreenSize; +float3 Dummy0; +float WorldDeterminantSign; +float MeshMinZ; +float Segment; +float ChunksPerSegment; +float PerInstanceRandom; +float4 TemporalAAJitter; +float3 GeometrySize; +float MeshMaxZ; +@1META_CB_END + +// Shader resources +@2 +// The spline deformation buffer (stored as 4x3, 3 float4 behind each other) +Buffer SplineDeformation : register(t0); + +// Geometry data passed though the graphics rendering stages up to the pixel shader +struct GeometryData +{ + float3 WorldPosition : TEXCOORD0; + float2 TexCoord : TEXCOORD1; +#if USE_VERTEX_COLOR + half4 VertexColor : COLOR; +#endif + float3 WorldNormal : TEXCOORD2; + float4 WorldTangent : TEXCOORD3; +}; + +// Interpolants passed from the vertex shader +struct VertexOutput +{ + float4 Position : SV_Position; + GeometryData Geometry; +#if USE_CUSTOM_VERTEX_INTERPOLATORS + float4 CustomVSToPS[CUSTOM_VERTEX_INTERPOLATORS_COUNT] : TEXCOORD9; +#endif +#if USE_TESSELLATION + float TessellationMultiplier : TESS; +#endif +}; + +// Interpolants passed to the pixel shader +struct PixelInput +{ + float4 Position : SV_Position; + GeometryData Geometry; +#if USE_CUSTOM_VERTEX_INTERPOLATORS + float4 CustomVSToPS[CUSTOM_VERTEX_INTERPOLATORS_COUNT] : TEXCOORD9; +#endif + bool IsFrontFace : SV_IsFrontFace; +}; + +// Material properties generation input +struct MaterialInput +{ + float3 WorldPosition; + float TwoSidedSign; + float2 TexCoord; +#if USE_VERTEX_COLOR + half4 VertexColor; +#endif + float3x3 TBN; + float4 SvPosition; + float3 PreSkinnedPosition; + float3 PreSkinnedNormal; +#if USE_CUSTOM_VERTEX_INTERPOLATORS + float4 CustomVSToPS[CUSTOM_VERTEX_INTERPOLATORS_COUNT]; +#endif +}; + +// Extracts geometry data to the material input +MaterialInput GetGeometryMaterialInput(GeometryData geometry) +{ + MaterialInput output = (MaterialInput)0; + output.WorldPosition = geometry.WorldPosition; + output.TexCoord = geometry.TexCoord; +#if USE_VERTEX_COLOR + output.VertexColor = geometry.VertexColor; +#endif + output.TBN = CalcTangentBasis(geometry.WorldNormal, geometry.WorldTangent); + return output; +} + +#if USE_TESSELLATION + +// Interpolates the geometry positions data only (used by the tessallation when generating vertices) +#define InterpolateGeometryPositions(output, p0, w0, p1, w1, p2, w2, offset) output.WorldPosition = p0.WorldPosition * w0 + p1.WorldPosition * w1 + p2.WorldPosition * w2 + offset + +// Offsets the geometry positions data only (used by the tessallation when generating vertices) +#define OffsetGeometryPositions(geometry, offset) geometry.WorldPosition += offset + +// Applies the Phong tessallation to the geometry positions (used by the tessallation when doing Phong tess) +#define ApplyGeometryPositionsPhongTess(geometry, p0, p1, p2, U, V, W) \ + float3 posProjectedU = TessalationProjectOntoPlane(p0.WorldNormal, p0.WorldPosition, geometry.WorldPosition); \ + float3 posProjectedV = TessalationProjectOntoPlane(p1.WorldNormal, p1.WorldPosition, geometry.WorldPosition); \ + float3 posProjectedW = TessalationProjectOntoPlane(p2.WorldNormal, p2.WorldPosition, geometry.WorldPosition); \ + geometry.WorldPosition = U * posProjectedU + V * posProjectedV + W * posProjectedW + +// Interpolates the geometry data except positions (used by the tessallation when generating vertices) +GeometryData InterpolateGeometry(GeometryData p0, float w0, GeometryData p1, float w1, GeometryData p2, float w2) +{ + GeometryData output = (GeometryData)0; + output.TexCoord = p0.TexCoord * w0 + p1.TexCoord * w1 + p2.TexCoord * w2; +#if USE_VERTEX_COLOR + output.VertexColor = p0.VertexColor * w0 + p1.VertexColor * w1 + p2.VertexColor * w2; +#endif + output.WorldNormal = p0.WorldNormal * w0 + p1.WorldNormal * w1 + p2.WorldNormal * w2; + output.WorldNormal = normalize(output.WorldNormal); + output.WorldTangent = p0.WorldTangent * w0 + p1.WorldTangent * w1 + p2.WorldTangent * w2; + output.WorldTangent.xyz = normalize(output.WorldTangent.xyz); + return output; +} + +#endif + +MaterialInput GetMaterialInput(PixelInput input) +{ + MaterialInput output = GetGeometryMaterialInput(input.Geometry); + output.TwoSidedSign = WorldDeterminantSign * (input.IsFrontFace ? 1.0 : -1.0); + output.SvPosition = input.Position; +#if USE_CUSTOM_VERTEX_INTERPOLATORS + output.CustomVSToPS = input.CustomVSToPS; +#endif + return output; +} + +// Removes the scale vector from the local to world transformation matrix +float3x3 RemoveScaleFromLocalToWorld(float3x3 localToWorld) +{ + // Extract per axis scales from localToWorld transform + float scaleX = length(localToWorld[0]); + float scaleY = length(localToWorld[1]); + float scaleZ = length(localToWorld[2]); + float3 invScale = float3( + scaleX > 0.00001f ? 1.0f / scaleX : 0.0f, + scaleY > 0.00001f ? 1.0f / scaleY : 0.0f, + scaleZ > 0.00001f ? 1.0f / scaleZ : 0.0f); + localToWorld[0] *= invScale.x; + localToWorld[1] *= invScale.y; + localToWorld[2] *= invScale.z; + return localToWorld; +} + +// Transforms a vector from tangent space to world space +float3 TransformTangentVectorToWorld(MaterialInput input, float3 tangentVector) +{ + return mul(tangentVector, input.TBN); +} + +// Transforms a vector from world space to tangent space +float3 TransformWorldVectorToTangent(MaterialInput input, float3 worldVector) +{ + return mul(input.TBN, worldVector); +} + +// Transforms a vector from world space to view space +float3 TransformWorldVectorToView(MaterialInput input, float3 worldVector) +{ + return mul(worldVector, (float3x3)ViewMatrix); +} + +// Transforms a vector from view space to world space +float3 TransformViewVectorToWorld(MaterialInput input, float3 viewVector) +{ + return mul((float3x3)ViewMatrix, viewVector); +} + +// Transforms a vector from local space to world space +float3 TransformLocalVectorToWorld(MaterialInput input, float3 localVector) +{ + float3x3 localToWorld = (float3x3)WorldMatrix; + //localToWorld = RemoveScaleFromLocalToWorld(localToWorld); + return mul(localVector, localToWorld); +} + +// Transforms a vector from local space to world space +float3 TransformWorldVectorToLocal(MaterialInput input, float3 worldVector) +{ + float3x3 localToWorld = (float3x3)WorldMatrix; + //localToWorld = RemoveScaleFromLocalToWorld(localToWorld); + return mul(localToWorld, worldVector); +} + +// Gets the current object position +float3 GetObjectPosition(MaterialInput input) +{ + return WorldMatrix[3].xyz; +} + +// Gets the current object size +float3 GetObjectSize(MaterialInput input) +{ + float4x4 world = WorldMatrix; + return GeometrySize * float3(world._m00, world._m11, world._m22); +} + +// Get the current object random value +float GetPerInstanceRandom(MaterialInput input) +{ + return PerInstanceRandom; +} + +// Get the current object LOD transition dither factor +float GetLODDitherFactor(MaterialInput input) +{ + return 0; +} + +// Gets the interpolated vertex color (in linear space) +float4 GetVertexColor(MaterialInput input) +{ +#if USE_VERTEX_COLOR + return input.VertexColor; +#else + return 1; +#endif +} + +float3 SampleLightmap(Material material, MaterialInput materialInput) +{ + return 0; +} + +@8 + +// Get material properties function (for vertex shader) +Material GetMaterialVS(MaterialInput input) +{ +@5 +} + +// Get material properties function (for domain shader) +Material GetMaterialDS(MaterialInput input) +{ +@6 +} + +// Get material properties function (for pixel shader) +Material GetMaterialPS(MaterialInput input) +{ +@4 +} + +// Calculates the transform matrix from mesh tangent space to local space +float3x3 CalcTangentToLocal(ModelInput input) +{ + float bitangentSign = input.Tangent.w ? -1.0f : +1.0f; + float3 normal = input.Normal.xyz * 2.0 - 1.0; + float3 tangent = input.Tangent.xyz * 2.0 - 1.0; + float3 bitangent = cross(normal, tangent) * bitangentSign; + return float3x3(tangent, bitangent, normal); +} + +// Vertex Shader function for GBuffer Pass and Depth Pass (with full vertex data) +META_VS(true, FEATURE_LEVEL_ES2) +META_VS_IN_ELEMENT(POSITION, 0, R32G32B32_FLOAT, 0, 0, PER_VERTEX, 0, true) +META_VS_IN_ELEMENT(TEXCOORD, 0, R16G16_FLOAT, 1, 0, PER_VERTEX, 0, true) +META_VS_IN_ELEMENT(NORMAL, 0, R10G10B10A2_UNORM, 1, ALIGN, PER_VERTEX, 0, true) +META_VS_IN_ELEMENT(TANGENT, 0, R10G10B10A2_UNORM, 1, ALIGN, PER_VERTEX, 0, true) +META_VS_IN_ELEMENT(TEXCOORD, 1, R16G16_FLOAT, 1, ALIGN, PER_VERTEX, 0, true) +META_VS_IN_ELEMENT(COLOR, 0, R8G8B8A8_UNORM, 2, 0, PER_VERTEX, 0, USE_VERTEX_COLOR) +VertexOutput VS_SplineModel(ModelInput input) +{ + VertexOutput output; + + // Apply local transformation of the geometry before deformation + float3 position = mul(float4(input.Position, 1), LocalMatrix).xyz; + float4x4 world = LocalMatrix; + + // Apply spline curve deformation + float splineAlpha = saturate((position.z - MeshMinZ) / (MeshMaxZ - MeshMinZ)); + int splineIndex = (int)((Segment + splineAlpha) * ChunksPerSegment); + position.z = splineAlpha; + float3x4 splineMatrix = float3x4(SplineDeformation[splineIndex * 3], SplineDeformation[splineIndex * 3 + 1], SplineDeformation[splineIndex * 3 + 2]); + position = mul(splineMatrix, float4(position, 1)); + float4x3 splineMatrixT = transpose(splineMatrix); + world = mul(world, float4x4(float4(splineMatrixT[0], 0), float4(splineMatrixT[1], 0), float4(splineMatrixT[2], 0), float4(splineMatrixT[3], 1))); + + // Compute world space vertex position + output.Geometry.WorldPosition = mul(float4(position, 1), WorldMatrix).xyz; + world = mul(world, WorldMatrix); + + // Compute clip space position + output.Position = mul(float4(output.Geometry.WorldPosition, 1), ViewProjectionMatrix); + + // Pass vertex attributes + output.Geometry.TexCoord = input.TexCoord; +#if USE_VERTEX_COLOR + output.Geometry.VertexColor = input.Color; +#endif + + // Calculate tanget space to world space transformation matrix for unit vectors + float3x3 tangentToLocal = CalcTangentToLocal(input); + float3x3 localToWorld = RemoveScaleFromLocalToWorld((float3x3)world); + float3x3 tangentToWorld = mul(tangentToLocal, localToWorld); + output.Geometry.WorldNormal = tangentToWorld[2]; + output.Geometry.WorldTangent.xyz = tangentToWorld[0]; + output.Geometry.WorldTangent.w = input.Tangent.w ? -1.0f : +1.0f; + + // Get material input params if need to evaluate any material property +#if USE_POSITION_OFFSET || USE_TESSELLATION || USE_CUSTOM_VERTEX_INTERPOLATORS + MaterialInput materialInput = GetGeometryMaterialInput(output.Geometry); + materialInput.TwoSidedSign = WorldDeterminantSign; + materialInput.SvPosition = output.Position; + materialInput.PreSkinnedPosition = input.Position.xyz; + materialInput.PreSkinnedNormal = tangentToLocal[2].xyz; + Material material = GetMaterialVS(materialInput); +#endif + + // Apply world position offset per-vertex +#if USE_POSITION_OFFSET + output.Geometry.WorldPosition += material.PositionOffset; + output.Position = mul(float4(output.Geometry.WorldPosition, 1), ViewProjectionMatrix); +#endif + + // Get tessalation multiplier (per vertex) +#if USE_TESSELLATION + output.TessellationMultiplier = material.TessellationMultiplier; +#endif + + // Copy interpolants for other shader stages +#if USE_CUSTOM_VERTEX_INTERPOLATORS + output.CustomVSToPS = material.CustomVSToPS; +#endif + + return output; +} + +#if USE_DITHERED_LOD_TRANSITION + +void ClipLODTransition(PixelInput input) +{ +} + +#endif + +// Pixel Shader function for Depth Pass +META_PS(true, FEATURE_LEVEL_ES2) +void PS_Depth(PixelInput input) +{ +#if MATERIAL_MASKED || MATERIAL_BLEND != MATERIAL_BLEND_OPAQUE + // Get material parameters + MaterialInput materialInput = GetMaterialInput(input); + Material material = GetMaterialPS(materialInput); + + // Perform per pixel clipping +#if MATERIAL_MASKED + clip(material.Mask - MATERIAL_MASK_THRESHOLD); +#endif +#if MATERIAL_BLEND != MATERIAL_BLEND_OPAQUE + clip(material.Opacity - MATERIAL_OPACITY_THRESHOLD); +#endif +#endif +} + +@9 diff --git a/Content/Editor/MaterialTemplates/Features/DeferredShading.hlsl b/Content/Editor/MaterialTemplates/Features/DeferredShading.hlsl new file mode 100644 index 000000000..d3a7a2518 --- /dev/null +++ b/Content/Editor/MaterialTemplates/Features/DeferredShading.hlsl @@ -0,0 +1,84 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +@0// Deferred Shading: Defines +@1// Deferred Shading: Includes +@2// Deferred Shading: Constants +@3// Deferred Shading: Resources +@4// Deferred Shading: Utilities +@5// Deferred Shading: Shaders + +// Pixel Shader function for GBuffer Pass +META_PS(true, FEATURE_LEVEL_ES2) +META_PERMUTATION_1(USE_LIGHTMAP=0) +META_PERMUTATION_1(USE_LIGHTMAP=1) +void PS_GBuffer( + in PixelInput input + ,out float4 Light : SV_Target0 +#if MATERIAL_BLEND == MATERIAL_BLEND_OPAQUE + // GBuffer + ,out float4 RT0 : SV_Target1 + ,out float4 RT1 : SV_Target2 + ,out float4 RT2 : SV_Target3 +#if USE_GBUFFER_CUSTOM_DATA + ,out float4 RT3 : SV_Target4 +#endif +#endif + ) +{ + Light = 0; + +#if USE_DITHERED_LOD_TRANSITION + // LOD masking + ClipLODTransition(input); +#endif + + // Get material parameters + MaterialInput materialInput = GetMaterialInput(input); + Material material = GetMaterialPS(materialInput); + + // Masking +#if MATERIAL_MASKED + clip(material.Mask - MATERIAL_MASK_THRESHOLD); +#endif + +#if USE_LIGHTMAP + float3 diffuseColor = GetDiffuseColor(material.Color, material.Metalness); + float3 specularColor = GetSpecularColor(material.Color, material.Specular, material.Metalness); + + // Sample lightmap + float3 diffuseIndirectLighting = SampleLightmap(material, materialInput); + + // Apply static indirect light + Light.rgb = diffuseColor * diffuseIndirectLighting * AOMultiBounce(material.AO, diffuseColor); +#endif + +#if MATERIAL_BLEND == MATERIAL_BLEND_OPAQUE + + // Pack material properties to GBuffer + RT0 = float4(material.Color, material.AO); + RT1 = float4(material.WorldNormal * 0.5 + 0.5, MATERIAL_SHADING_MODEL * (1.0 / 3.0)); + RT2 = float4(material.Roughness, material.Metalness, material.Specular, 0); + + // Custom data +#if USE_GBUFFER_CUSTOM_DATA +#if MATERIAL_SHADING_MODEL == SHADING_MODEL_SUBSURFACE + RT3 = float4(material.SubsurfaceColor, material.Opacity); +#elif MATERIAL_SHADING_MODEL == SHADING_MODEL_FOLIAGE + RT3 = float4(material.SubsurfaceColor, material.Opacity); +#else + RT3 = float4(0, 0, 0, 0); +#endif +#endif + + // Add light emission +#if USE_EMISSIVE + Light.rgb += material.Emissive; +#endif + +#else + + // Handle blending as faked forward pass (use Light buffer and skip GBuffer modification) + Light = float4(material.Emissive, material.Opacity); + +#endif +} diff --git a/Content/Editor/MaterialTemplates/Features/Distortion.hlsl b/Content/Editor/MaterialTemplates/Features/Distortion.hlsl new file mode 100644 index 000000000..c74014cca --- /dev/null +++ b/Content/Editor/MaterialTemplates/Features/Distortion.hlsl @@ -0,0 +1,50 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +@0// Distortion: Defines +@1// Distortion: Includes +@2// Distortion: Constants +@3// Distortion: Resources +@4// Distortion: Utilities +@5// Distortion: Shaders +#if USE_DISTORTION + +// Pixel Shader function for Distortion Pass +META_PS(USE_DISTORTION, FEATURE_LEVEL_ES2) +float4 PS_Distortion(PixelInput input) : SV_Target0 +{ +#if USE_DITHERED_LOD_TRANSITION + // LOD masking + ClipLODTransition(input); +#endif + + // Get material parameters + MaterialInput materialInput = GetMaterialInput(input); + Material material = GetMaterialPS(materialInput); + + // Masking +#if MATERIAL_MASKED + clip(material.Mask - MATERIAL_MASK_THRESHOLD); +#endif + + float3 viewNormal = normalize(TransformWorldVectorToView(materialInput, material.WorldNormal)); + float airIOR = 1.0f; +#if USE_PIXEL_NORMAL_OFFSET_REFRACTION + float3 viewVertexNormal = TransformWorldVectorToView(materialInput, TransformTangentVectorToWorld(materialInput, float3(0, 0, 1))); + float2 distortion = (viewVertexNormal.xy - viewNormal.xy) * (material.Refraction - airIOR); +#else + float2 distortion = viewNormal.xy * (material.Refraction - airIOR); +#endif + + // Clip if the distortion distance (squared) is too small to be noticed + clip(dot(distortion, distortion) - 0.00001); + + // Scale up for better precision in low/subtle refractions at the expense of artefacts at higher refraction + distortion *= 4.0f; + + // Use separate storage for positive and negative offsets + float2 addOffset = max(distortion, 0); + float2 subOffset = abs(min(distortion, 0)); + return float4(addOffset.x, addOffset.y, subOffset.x, subOffset.y); +} + +#endif diff --git a/Content/Editor/MaterialTemplates/Features/ForwardShading.hlsl b/Content/Editor/MaterialTemplates/Features/ForwardShading.hlsl new file mode 100644 index 000000000..c21fe37d2 --- /dev/null +++ b/Content/Editor/MaterialTemplates/Features/ForwardShading.hlsl @@ -0,0 +1,124 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +@0// Forward Shading: Defines +#define MAX_LOCAL_LIGHTS 4 +@1// Forward Shading: Includes +#include "./Flax/LightingCommon.hlsl" +#if USE_REFLECTIONS +#include "./Flax/ReflectionsCommon.hlsl" +#endif +#include "./Flax/Lighting.hlsl" +#include "./Flax/ShadowsSampling.hlsl" +#include "./Flax/ExponentialHeightFog.hlsl" +@2// Forward Shading: Constants +LightData DirectionalLight; +LightShadowData DirectionalLightShadow; +LightData SkyLight; +ProbeData EnvironmentProbe; +ExponentialHeightFogData ExponentialHeightFog; +float3 Dummy2; +uint LocalLightsCount; +LightData LocalLights[MAX_LOCAL_LIGHTS]; +@3// Forward Shading: Resources +TextureCube EnvProbe : register(t__SRV__); +TextureCube SkyLightTexture : register(t__SRV__); +Texture2DArray DirectionalLightShadowMap : register(t__SRV__); +@4// Forward Shading: Utilities +DECLARE_LIGHTSHADOWDATA_ACCESS(DirectionalLightShadow); +@5// Forward Shading: Shaders + +// Pixel Shader function for Forward Pass +META_PS(USE_FORWARD, FEATURE_LEVEL_ES2) +float4 PS_Forward(PixelInput input) : SV_Target0 +{ + float4 output = 0; + +#if USE_DITHERED_LOD_TRANSITION + // LOD masking + ClipLODTransition(input); +#endif + + // Get material parameters + MaterialInput materialInput = GetMaterialInput(input); + Material material = GetMaterialPS(materialInput); + + // Masking +#if MATERIAL_MASKED + clip(material.Mask - MATERIAL_MASK_THRESHOLD); +#endif + + // Add emissive light + output = float4(material.Emissive, material.Opacity); + +#if MATERIAL_SHADING_MODEL != SHADING_MODEL_UNLIT + + // Setup GBuffer data as proxy for lighting + GBufferSample gBuffer; + gBuffer.Normal = material.WorldNormal; + gBuffer.Roughness = material.Roughness; + gBuffer.Metalness = material.Metalness; + gBuffer.Color = material.Color; + gBuffer.Specular = material.Specular; + gBuffer.AO = material.AO; + gBuffer.ViewPos = mul(float4(materialInput.WorldPosition, 1), ViewMatrix).xyz; +#if MATERIAL_SHADING_MODEL == SHADING_MODEL_SUBSURFACE + gBuffer.CustomData = float4(material.SubsurfaceColor, material.Opacity); +#elif MATERIAL_SHADING_MODEL == SHADING_MODEL_FOLIAGE + gBuffer.CustomData = float4(material.SubsurfaceColor, material.Opacity); +#else + gBuffer.CustomData = float4(0, 0, 0, 0); +#endif + gBuffer.WorldPos = materialInput.WorldPosition; + gBuffer.ShadingModel = MATERIAL_SHADING_MODEL; + + // Calculate lighting from a single directional light + float4 shadowMask = 1.0f; + if (DirectionalLight.CastShadows > 0) + { + LightShadowData directionalLightShadowData = GetDirectionalLightShadowData(); + shadowMask.r = SampleShadow(DirectionalLight, directionalLightShadowData, DirectionalLightShadowMap, gBuffer, shadowMask.g); + } + float4 light = GetLighting(ViewPos, DirectionalLight, gBuffer, shadowMask, false, false); + + // Calculate lighting from sky light + light += GetSkyLightLighting(SkyLight, gBuffer, SkyLightTexture); + + // Calculate lighting from local lights + LOOP + for (uint localLightIndex = 0; localLightIndex < LocalLightsCount; localLightIndex++) + { + const LightData localLight = LocalLights[localLightIndex]; + bool isSpotLight = localLight.SpotAngles.x > -2.0f; + shadowMask = 1.0f; + light += GetLighting(ViewPos, localLight, gBuffer, shadowMask, true, isSpotLight); + } + +#if USE_REFLECTIONS + // Calculate reflections + light.rgb += GetEnvProbeLighting(ViewPos, EnvProbe, EnvironmentProbe, gBuffer) * light.a; +#endif + + // Add lighting (apply ambient occlusion) + output.rgb += light.rgb * gBuffer.AO; + +#if USE_FOG + // Calculate exponential height fog + float4 fog = GetExponentialHeightFog(ExponentialHeightFog, materialInput.WorldPosition, ViewPos, 0); + + // Apply fog to the output color +#if MATERIAL_BLEND == MATERIAL_BLEND_OPAQUE + output = float4(output.rgb * fog.a + fog.rgb, output.a); +#elif MATERIAL_BLEND == MATERIAL_BLEND_TRANSPARENT + output = float4(output.rgb * fog.a + fog.rgb, output.a); +#elif MATERIAL_BLEND == MATERIAL_BLEND_ADDITIVE + output = float4(output.rgb * fog.a + fog.rgb, output.a * fog.a); +#elif MATERIAL_BLEND == MATERIAL_BLEND_MULTIPLY + output = float4(lerp(float3(1, 1, 1), output.rgb, fog.aaa * fog.aaa), output.a); +#endif + +#endif + +#endif + + return output; +} diff --git a/Content/Editor/MaterialTemplates/Features/Lightmap.hlsl b/Content/Editor/MaterialTemplates/Features/Lightmap.hlsl new file mode 100644 index 000000000..f4583fa7f --- /dev/null +++ b/Content/Editor/MaterialTemplates/Features/Lightmap.hlsl @@ -0,0 +1,54 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +@0// Lightmap: Defines +#define CAN_USE_LIGHTMAP 1 +@1// Lightmap: Includes +@2// Lightmap: Constants +float4 LightmapArea; +@3// Lightmap: Resources +#if USE_LIGHTMAP +// Irradiance and directionality prebaked lightmaps +Texture2D Lightmap0 : register(t__SRV__); +Texture2D Lightmap1 : register(t__SRV__); +Texture2D Lightmap2 : register(t__SRV__); +#endif +@4// Lightmap: Utilities +#if USE_LIGHTMAP + +// Evaluates the H-Basis coefficients in the tangent space normal direction +float3 GetHBasisIrradiance(float3 n, float3 h0, float3 h1, float3 h2, float3 h3) +{ + // Band 0 + float3 color = h0 * (1.0f / sqrt(2.0f * PI)); + + // Band 1 + color += h1 * -sqrt(1.5f / PI) * n.y; + color += h2 * sqrt(1.5f / PI) * (2 * n.z - 1.0f); + color += h3 * -sqrt(1.5f / PI) * n.x; + + return color; +} + +float3 SampleLightmap(Material material, MaterialInput materialInput) +{ + // Sample lightmaps + float4 lightmap0 = Lightmap0.Sample(SamplerLinearClamp, materialInput.LightmapUV); + float4 lightmap1 = Lightmap1.Sample(SamplerLinearClamp, materialInput.LightmapUV); + float4 lightmap2 = Lightmap2.Sample(SamplerLinearClamp, materialInput.LightmapUV); + + // Unpack H-basis + float3 h0 = float3(lightmap0.x, lightmap1.x, lightmap2.x); + float3 h1 = float3(lightmap0.y, lightmap1.y, lightmap2.y); + float3 h2 = float3(lightmap0.z, lightmap1.z, lightmap2.z); + float3 h3 = float3(lightmap0.w, lightmap1.w, lightmap2.w); + + // Sample baked diffuse irradiance from the H-basis coefficients + float3 normal = material.TangentNormal; +#if MATERIAL_SHADING_MODEL == SHADING_MODEL_FOLIAGE + normal *= material.TangentNormal; +#endif + return GetHBasisIrradiance(normal, h0, h1, h2, h3) / PI; +} + +#endif +@5// Lightmap: Shaders diff --git a/Content/Editor/MaterialTemplates/Features/MotionVectors.hlsl b/Content/Editor/MaterialTemplates/Features/MotionVectors.hlsl new file mode 100644 index 000000000..992eb6805 --- /dev/null +++ b/Content/Editor/MaterialTemplates/Features/MotionVectors.hlsl @@ -0,0 +1,44 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +@0// Motion Vectors: Defines +@1// Motion Vectors: Includes +@2// Motion Vectors: Constants +@3// Motion Vectors: Resources +@4// Motion Vectors: Utilities +@5// Motion Vectors: Shaders + +// Pixel Shader function for Motion Vectors Pass +META_PS(true, FEATURE_LEVEL_ES2) +float4 PS_MotionVectors(PixelInput input) : SV_Target0 +{ +#if USE_DITHERED_LOD_TRANSITION + // LOD masking + ClipLODTransition(input); +#endif + +#if MATERIAL_MASKED + // Perform per pixel clipping if material requries it + MaterialInput materialInput = GetMaterialInput(input); + Material material = GetMaterialPS(materialInput); + clip(material.Mask - MATERIAL_MASK_THRESHOLD); +#endif + + // Calculate this and previosu frame pixel locations in clip space + float4 prevClipPos = mul(float4(input.Geometry.PrevWorldPosition, 1), PrevViewProjectionMatrix); + float4 curClipPos = mul(float4(input.Geometry.WorldPosition, 1), ViewProjectionMatrix); + float2 prevHPos = prevClipPos.xy / prevClipPos.w; + float2 curHPos = curClipPos.xy / curClipPos.w; + + // Revert temporal jitter offset + prevHPos -= TemporalAAJitter.zw; + curHPos -= TemporalAAJitter.xy; + + // Clip Space -> UV Space + float2 vPosPrev = prevHPos.xy * 0.5f + 0.5f; + float2 vPosCur = curHPos.xy * 0.5f + 0.5f; + vPosPrev.y = 1.0 - vPosPrev.y; + vPosCur.y = 1.0 - vPosCur.y; + + // Calculate per-pixel motion vector + return float4(vPosCur - vPosPrev, 0, 1); +} diff --git a/Content/Editor/MaterialTemplates/Features/Tessellation.hlsl b/Content/Editor/MaterialTemplates/Features/Tessellation.hlsl new file mode 100644 index 000000000..e2e0b54ea --- /dev/null +++ b/Content/Editor/MaterialTemplates/Features/Tessellation.hlsl @@ -0,0 +1,184 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +@0// Tessellation: Defines +#define TessalationProjectOntoPlane(planeNormal, planePosition, pointToProject) pointToProject - dot(pointToProject - planePosition, planeNormal) * planeNormal +@1// Tessellation: Includes +@2// Tessellation: Constants +@3// Tessellation: Resources +@4// Tessellation: Utilities +@5// Tessellation: Shaders +#if USE_TESSELLATION + +// Interpolants passed from the hull shader to the domain shader +struct TessalationHSToDS +{ + float4 Position : SV_Position; + GeometryData Geometry; +#if USE_CUSTOM_VERTEX_INTERPOLATORS + float4 CustomVSToPS[CUSTOM_VERTEX_INTERPOLATORS_COUNT] : TEXCOORD9; +#endif + float TessellationMultiplier : TESS; +}; + +// Interpolants passed from the domain shader and to the pixel shader +struct TessalationDSToPS +{ + float4 Position : SV_Position; + GeometryData Geometry; +#if USE_CUSTOM_VERTEX_INTERPOLATORS + float4 CustomVSToPS[CUSTOM_VERTEX_INTERPOLATORS_COUNT] : TEXCOORD9; +#endif +}; + +MaterialInput GetMaterialInput(TessalationDSToPS input) +{ + MaterialInput output = GetGeometryMaterialInput(input.Geometry); + output.SvPosition = input.Position; + output.TwoSidedSign = WorldDeterminantSign; +#if USE_CUSTOM_VERTEX_INTERPOLATORS + output.CustomVSToPS = input.CustomVSToPS; +#endif + return output; +} + +struct TessalationPatch +{ + float EdgeTessFactor[3] : SV_TessFactor; + float InsideTessFactor : SV_InsideTessFactor; +#if MATERIAL_TESSELLATION == MATERIAL_TESSELLATION_PN + float3 B210 : POSITION4; + float3 B120 : POSITION5; + float3 B021 : POSITION6; + float3 B012 : POSITION7; + float3 B102 : POSITION8; + float3 B201 : POSITION9; + float3 B111 : CENTER; +#endif +}; + +TessalationPatch HS_PatchConstant(InputPatch input) +{ + TessalationPatch output; + + // Average tess factors along edges, and pick an edge tess factor for the interior tessellation + float4 tessellationMultipliers; + tessellationMultipliers.x = 0.5f * (input[1].TessellationMultiplier + input[2].TessellationMultiplier); + tessellationMultipliers.y = 0.5f * (input[2].TessellationMultiplier + input[0].TessellationMultiplier); + tessellationMultipliers.z = 0.5f * (input[0].TessellationMultiplier + input[1].TessellationMultiplier); + tessellationMultipliers.w = 0.333f * (input[0].TessellationMultiplier + input[1].TessellationMultiplier + input[2].TessellationMultiplier); + tessellationMultipliers = clamp(tessellationMultipliers, 1, MAX_TESSELLATION_FACTOR); + + output.EdgeTessFactor[0] = tessellationMultipliers.x; // 1->2 edge + output.EdgeTessFactor[1] = tessellationMultipliers.y; // 2->0 edge + output.EdgeTessFactor[2] = tessellationMultipliers.z; // 0->1 edge + output.InsideTessFactor = tessellationMultipliers.w; + +#if MATERIAL_TESSELLATION == MATERIAL_TESSELLATION_PN + // Calculate PN Triangle control points + // Reference: [Vlachos 2001] + float3 p1 = input[0].Geometry.WorldPosition; + float3 p2 = input[1].Geometry.WorldPosition; + float3 p3 = input[2].Geometry.WorldPosition; + float3 n1 = input[0].Geometry.WorldNormal; + float3 n2 = input[1].Geometry.WorldNormal; + float3 n3 = input[2].Geometry.WorldNormal; + output.B210 = (2.0f * p1 + p2 - dot((p2 - p1), n1) * n1) / 3.0f; + output.B120 = (2.0f * p2 + p1 - dot((p1 - p2), n2) * n2) / 3.0f; + output.B021 = (2.0f * p2 + p3 - dot((p3 - p2), n2) * n2) / 3.0f; + output.B012 = (2.0f * p3 + p2 - dot((p2 - p3), n3) * n3) / 3.0f; + output.B102 = (2.0f * p3 + p1 - dot((p1 - p3), n3) * n3) / 3.0f; + output.B201 = (2.0f * p1 + p3 - dot((p3 - p1), n1) * n1) / 3.0f; + float3 e = (output.B210 + output.B120 + output.B021 + output.B012 + output.B102 + output.B201) / 6.0f; + float3 v = (p1 + p2 + p3) / 3.0f; + output.B111 = e + ((e - v) / 2.0f); +#endif + + return output; +} + +META_HS(USE_TESSELLATION, FEATURE_LEVEL_SM5) +META_HS_PATCH(TESSELLATION_IN_CONTROL_POINTS) +[domain("tri")] +[partitioning("fractional_odd")] +[outputtopology("triangle_cw")] +[maxtessfactor(MAX_TESSELLATION_FACTOR)] +[outputcontrolpoints(3)] +[patchconstantfunc("HS_PatchConstant")] +TessalationHSToDS HS(InputPatch input, uint ControlPointID : SV_OutputControlPointID) +{ + TessalationHSToDS output; + + // Pass through shader +#define COPY(thing) output.thing = input[ControlPointID].thing; + COPY(Position); + COPY(Geometry); + COPY(TessellationMultiplier); +#if USE_CUSTOM_VERTEX_INTERPOLATORS + COPY(CustomVSToPS); +#endif +#undef COPY + + return output; +} + +META_DS(USE_TESSELLATION, FEATURE_LEVEL_SM5) +[domain("tri")] +TessalationDSToPS DS(TessalationPatch constantData, float3 barycentricCoords : SV_DomainLocation, const OutputPatch input) +{ + TessalationDSToPS output; + + // Get the barycentric coords + float U = barycentricCoords.x; + float V = barycentricCoords.y; + float W = barycentricCoords.z; + + // Interpolate patch attributes to generated vertices + output.Geometry = InterpolateGeometry(input[0].Geometry, U, input[1].Geometry, V, input[2].Geometry, W); +#define INTERPOLATE(thing) output.thing = U * input[0].thing + V * input[1].thing + W * input[2].thing + INTERPOLATE(Position); +#if MATERIAL_TESSELLATION == MATERIAL_TESSELLATION_PN + // Interpolate using barycentric coordinates and PN Triangle control points + float UU = U * U; + float VV = V * V; + float WW = W * W; + float UU3 = UU * 3.0f; + float VV3 = VV * 3.0f; + float WW3 = WW * 3.0f; + float3 offset = + constantData.B210 * UU3 * V + + constantData.B120 * VV3 * U + + constantData.B021 * VV3 * W + + constantData.B012 * WW3 * V + + constantData.B102 * WW3 * U + + constantData.B201 * UU3 * W + + constantData.B111 * 6.0f * W * U * V; + InterpolateGeometryPositions(output.Geometry, input[0].Geometry, UU * U, input[1].Geometry, VV * V, input[2].Geometry, WW * W, offset); +#else + InterpolateGeometryPositions(output.Geometry, input[0].Geometry, U, input[1].Geometry, V, input[2].Geometry, W, float3(0, 0, 0)); +#endif +#if USE_CUSTOM_VERTEX_INTERPOLATORS + UNROLL + for (int i = 0; i < CUSTOM_VERTEX_INTERPOLATORS_COUNT; i++) + INTERPOLATE(CustomVSToPS[i]); +#endif +#undef INTERPOLATE + +#if MATERIAL_TESSELLATION == MATERIAL_TESSELLATION_PHONG + // Orthogonal projection in the tangent planes with interpolation + ApplyGeometryPositionsPhongTess(output.Geometry, input[0].Geometry, input[1].Geometry, input[2].Geometry, U, V, W); +#endif + + // Perform displacement mapping +#if USE_DISPLACEMENT + MaterialInput materialInput = GetMaterialInput(output); + Material material = GetMaterialDS(materialInput); + OffsetGeometryPositions(output.Geometry, material.WorldDisplacement); +#endif + + // Recalculate the clip space position + output.Position = mul(float4(output.Geometry.WorldPosition, 1), ViewProjectionMatrix); + + return output; +} + +#endif diff --git a/Content/Editor/MaterialTemplates/GUI.shader b/Content/Editor/MaterialTemplates/GUI.shader index 1457a0bc3..7eced6390 100644 --- a/Content/Editor/MaterialTemplates/GUI.shader +++ b/Content/Editor/MaterialTemplates/GUI.shader @@ -3,7 +3,6 @@ #define MATERIAL 1 @3 - #include "./Flax/Common.hlsl" #include "./Flax/MaterialCommon.hlsl" #include "./Flax/GBufferCommon.hlsl" @@ -22,7 +21,7 @@ float4 ViewInfo; float4 ScreenSize; @1META_CB_END -// Material shader resources +// Shader resources @2 // Interpolants passed from the vertex shader struct VertexOutput @@ -185,6 +184,8 @@ float4 GetVertexColor(MaterialInput input) #endif } +@8 + // Get material properties function (for vertex shader) Material GetMaterialVS(MaterialInput input) { @@ -197,9 +198,6 @@ Material GetMaterialPS(MaterialInput input) @4 } -// Fix line for errors/warnings for shader code from template -#line 1000 - // Vertex Shader function for GUI materials rendering META_VS(true, FEATURE_LEVEL_ES2) META_VS_IN_ELEMENT(POSITION, 0, R32G32_FLOAT, 0, ALIGN, PER_VERTEX, 0, true) @@ -257,3 +255,5 @@ float4 PS_GUI(PixelInput input) : SV_Target0 return float4(material.Emissive, material.Opacity); } + +@9 diff --git a/Content/Editor/MaterialTemplates/Particle.shader b/Content/Editor/MaterialTemplates/Particle.shader index 5fd161645..726ad225a 100644 --- a/Content/Editor/MaterialTemplates/Particle.shader +++ b/Content/Editor/MaterialTemplates/Particle.shader @@ -2,19 +2,13 @@ // Version: @0 #define MATERIAL 1 -#define MAX_LOCAL_LIGHTS 4 @3 +// Ribbons don't use sorted indices so overlap the segment distances buffer on the slot +#define HAS_SORTED_INDICES (!defined(_VS_Ribbon)) #include "./Flax/Common.hlsl" #include "./Flax/MaterialCommon.hlsl" #include "./Flax/GBufferCommon.hlsl" -#include "./Flax/LightingCommon.hlsl" -#if USE_REFLECTIONS -#include "./Flax/ReflectionsCommon.hlsl" -#endif -#include "./Flax/Lighting.hlsl" -#include "./Flax/ShadowsSampling.hlsl" -#include "./Flax/ExponentialHeightFog.hlsl" #include "./Flax/Matrix.hlsl" @7 struct SpriteInput @@ -55,44 +49,19 @@ uint RibbonSegmentCount; float4x4 WorldMatrixInverseTransposed; @1META_CB_END -// Secondary constantant buffer (for lighting) -META_CB_BEGIN(1, LightingData) -LightData DirectionalLight; -LightShadowData DirectionalLightShadow; -LightData SkyLight; -ProbeData EnvironmentProbe; -ExponentialHeightFogData ExponentialHeightFog; -float3 Dummy1; -uint LocalLightsCount; -LightData LocalLights[MAX_LOCAL_LIGHTS]; -META_CB_END - -DECLARE_LIGHTSHADOWDATA_ACCESS(DirectionalLightShadow); - // Particles attributes buffer ByteAddressBuffer ParticlesData : register(t0); -// Ribbons don't use sorted indices so overlap the segment distances buffer on the slot -#define HAS_SORTED_INDICES (!defined(_VS_Ribbon)) - #if HAS_SORTED_INDICES - // Sorted particles indices Buffer SortedIndices : register(t1); - #else - // Ribbon particles segments distances buffer Buffer SegmentDistances : register(t1); - #endif // Shader resources -TextureCube EnvProbe : register(t2); -TextureCube SkyLightTexture : register(t3); -Texture2DArray DirectionalLightShadowMap : register(t4); @2 - // Interpolants passed from the vertex shader struct VertexOutput { @@ -172,14 +141,11 @@ MaterialInput GetMaterialInput(PixelInput input) } // Gets the local to world transform matrix (supports instancing) -float4x4 GetInstanceTransform(ModelInput input) -{ - return WorldMatrix; -} -float4x4 GetInstanceTransform(MaterialInput input) -{ - return WorldMatrix; -} +#if USE_INSTANCING +#define GetInstanceTransform(input) float4x4(float4(input.InstanceTransform1.xyz, 0.0f), float4(input.InstanceTransform2.xyz, 0.0f), float4(input.InstanceTransform3.xyz, 0.0f), float4(input.InstanceOrigin.xyz, 1.0f)) +#else +#define GetInstanceTransform(input) WorldMatrix; +#endif // Removes the scale vector from the local to world transformation matrix (supports instancing) float3x3 RemoveScaleFromLocalToWorld(float3x3 localToWorld) @@ -312,6 +278,8 @@ float3 TransformParticleVector(float3 input) return mul(float4(input, 0.0f), WorldMatrixInverseTransposed).xyz; } +@8 + // Get material properties function (for vertex shader) Material GetMaterialVS(MaterialInput input) { @@ -330,9 +298,6 @@ Material GetMaterialPS(MaterialInput input) @4 } -// Fix line for errors/warnings for shader code from template -#line 1000 - // Calculates the transform matrix from mesh tangent space to local space half3x3 CalcTangentToLocal(ModelInput input) { @@ -712,142 +677,9 @@ VertexOutput VS_Ribbon(uint vertexIndex : SV_VertexID) return output; } -// Pixel Shader function for Forward Pass -META_PS(USE_FORWARD, FEATURE_LEVEL_ES2) -float4 PS_Forward(PixelInput input) : SV_Target0 -{ - float4 output = 0; - - // Get material parameters - MaterialInput materialInput = GetMaterialInput(input); - Material material = GetMaterialPS(materialInput); - - // Masking -#if MATERIAL_MASKED - clip(material.Mask - MATERIAL_MASK_THRESHOLD); -#endif - - // Add emissive light - output = float4(material.Emissive, material.Opacity); - -#if MATERIAL_SHADING_MODEL != SHADING_MODEL_UNLIT - - // Setup GBuffer data as proxy for lighting - GBufferSample gBuffer; - gBuffer.Normal = material.WorldNormal; - gBuffer.Roughness = material.Roughness; - gBuffer.Metalness = material.Metalness; - gBuffer.Color = material.Color; - gBuffer.Specular = material.Specular; - gBuffer.AO = material.AO; - gBuffer.ViewPos = mul(float4(materialInput.WorldPosition, 1), ViewMatrix).xyz; -#if MATERIAL_SHADING_MODEL == SHADING_MODEL_SUBSURFACE - gBuffer.CustomData = float4(material.SubsurfaceColor, material.Opacity); -#elif MATERIAL_SHADING_MODEL == SHADING_MODEL_FOLIAGE - gBuffer.CustomData = float4(material.SubsurfaceColor, material.Opacity); -#else - gBuffer.CustomData = float4(0, 0, 0, 0); -#endif - gBuffer.WorldPos = materialInput.WorldPosition; - gBuffer.ShadingModel = MATERIAL_SHADING_MODEL; - - // Calculate lighting from a single directional light - float4 shadowMask = 1.0f; - if (DirectionalLight.CastShadows > 0) - { - LightShadowData directionalLightShadowData = GetDirectionalLightShadowData(); - shadowMask.r = SampleShadow(DirectionalLight, directionalLightShadowData, DirectionalLightShadowMap, gBuffer, shadowMask.g); - } - float4 light = GetLighting(ViewPos, DirectionalLight, gBuffer, shadowMask, false, false); - - // Calculate lighting from sky light - light += GetSkyLightLighting(SkyLight, gBuffer, SkyLightTexture); - - // Calculate lighting from local lights - LOOP - for (uint localLightIndex = 0; localLightIndex < LocalLightsCount; localLightIndex++) - { - const LightData localLight = LocalLights[localLightIndex]; - bool isSpotLight = localLight.SpotAngles.x > -2.0f; - shadowMask = 1.0f; - light += GetLighting(ViewPos, localLight, gBuffer, shadowMask, true, isSpotLight); - } - -#if USE_REFLECTIONS - // Calculate reflections - light.rgb += GetEnvProbeLighting(ViewPos, EnvProbe, EnvironmentProbe, gBuffer) * light.a; -#endif - - // Add lighting (apply ambient occlusion) - output.rgb += light.rgb * gBuffer.AO; - -#if USE_FOG - // Calculate exponential height fog - float4 fog = GetExponentialHeightFog(ExponentialHeightFog, materialInput.WorldPosition, ViewPos, 0); - - // Apply fog to the output color -#if MATERIAL_BLEND == MATERIAL_BLEND_OPAQUE - output = float4(output.rgb * fog.a + fog.rgb, output.a); -#elif MATERIAL_BLEND == MATERIAL_BLEND_TRANSPARENT - output = float4(output.rgb * fog.a + fog.rgb, output.a); -#elif MATERIAL_BLEND == MATERIAL_BLEND_ADDITIVE - output = float4(output.rgb * fog.a + fog.rgb, output.a * fog.a); -#elif MATERIAL_BLEND == MATERIAL_BLEND_MULTIPLY - output = float4(lerp(float3(1, 1, 1), output.rgb, fog.aaa * fog.aaa), output.a); -#endif - -#endif - -#endif - - return output; -} - -#if USE_DISTORTION - -// Pixel Shader function for Distortion Pass -META_PS(USE_DISTORTION, FEATURE_LEVEL_ES2) -float4 PS_Distortion(PixelInput input) : SV_Target0 -{ - // Get material parameters - MaterialInput materialInput = GetMaterialInput(input); - Material material = GetMaterialPS(materialInput); - - // Masking -#if MATERIAL_MASKED - clip(material.Mask - MATERIAL_MASK_THRESHOLD); -#endif - - float3 viewNormal = normalize(TransformWorldVectorToView(materialInput, material.WorldNormal)); - float airIOR = 1.0f; -#if USE_PIXEL_NORMAL_OFFSET_REFRACTION - float3 viewVertexNormal = TransformWorldVectorToView(materialInput, TransformTangentVectorToWorld(materialInput, float3(0, 0, 1))); - float2 distortion = (viewVertexNormal.xy - viewNormal.xy) * (material.Refraction - airIOR); -#else - float2 distortion = viewNormal.xy * (material.Refraction - airIOR); -#endif - - // Clip if the distortion distance (squared) is too small to be noticed - clip(dot(distortion, distortion) - 0.00001); - - // Scale up for better precision in low/subtle refractions at the expense of artefacts at higher refraction - distortion *= 4.0f; - - // Use separate storage for positive and negative offsets - float2 addOffset = max(distortion, 0); - float2 subOffset = abs(min(distortion, 0)); - return float4(addOffset.x, addOffset.y, subOffset.x, subOffset.y); -} - -#endif - // Pixel Shader function for Depth Pass META_PS(true, FEATURE_LEVEL_ES2) -void PS_Depth(PixelInput input -#if GLSL - , out float4 OutColor : SV_Target0 -#endif - ) +void PS_Depth(PixelInput input) { // Get material parameters MaterialInput materialInput = GetMaterialInput(input); @@ -860,8 +692,6 @@ void PS_Depth(PixelInput input #if MATERIAL_BLEND == MATERIAL_BLEND_TRANSPARENT clip(material.Opacity - MATERIAL_OPACITY_THRESHOLD); #endif - -#if GLSL - OutColor = 0; -#endif } + +@9 diff --git a/Content/Editor/MaterialTemplates/PostProcess.shader b/Content/Editor/MaterialTemplates/PostProcess.shader index dad9795af..f20bdc610 100644 --- a/Content/Editor/MaterialTemplates/PostProcess.shader +++ b/Content/Editor/MaterialTemplates/PostProcess.shader @@ -20,7 +20,7 @@ float4 ScreenSize; float4 TemporalAAJitter; @1META_CB_END -// Material shader resources +// Shader resources @2 // Interpolants passed to the pixel shader struct PixelInput @@ -128,15 +128,14 @@ float4 GetVertexColor(MaterialInput input) return 1; } +@8 + // Get material properties function (for pixel shader) Material GetMaterialPS(MaterialInput input) { @4 } -// Fix line for errors/warnings for shader code from template -#line 1000 - // Pixel Shader function for PostFx materials rendering META_PS(true, FEATURE_LEVEL_ES2) float4 PS_PostFx(PixelInput input) : SV_Target0 @@ -147,3 +146,5 @@ float4 PS_PostFx(PixelInput input) : SV_Target0 return float4(material.Emissive, material.Opacity); } + +@9 diff --git a/Content/Editor/MaterialTemplates/Surface.shader b/Content/Editor/MaterialTemplates/Surface.shader new file mode 100644 index 000000000..4eee27691 --- /dev/null +++ b/Content/Editor/MaterialTemplates/Surface.shader @@ -0,0 +1,620 @@ +// File generated by Flax Materials Editor +// Version: @0 + +#define MATERIAL 1 +@3 +#include "./Flax/Common.hlsl" +#include "./Flax/MaterialCommon.hlsl" +#include "./Flax/GBufferCommon.hlsl" +@7 +// Primary constant buffer (with additional material parameters) +META_CB_BEGIN(0, Data) +float4x4 ViewProjectionMatrix; +float4x4 WorldMatrix; +float4x4 ViewMatrix; +float4x4 PrevViewProjectionMatrix; +float4x4 PrevWorldMatrix; +float3 ViewPos; +float ViewFar; +float3 ViewDir; +float TimeParam; +float4 ViewInfo; +float4 ScreenSize; +float3 WorldInvScale; +float WorldDeterminantSign; +float2 Dummy0; +float LODDitherFactor; +float PerInstanceRandom; +float4 TemporalAAJitter; +float3 GeometrySize; +float Dummy1; +@1META_CB_END + +// Shader resources +@2 +// Geometry data passed though the graphics rendering stages up to the pixel shader +struct GeometryData +{ + float3 WorldPosition : TEXCOORD0; + float2 TexCoord : TEXCOORD1; + float2 LightmapUV : TEXCOORD2; +#if USE_VERTEX_COLOR + half4 VertexColor : COLOR; +#endif + float3 WorldNormal : TEXCOORD3; + float4 WorldTangent : TEXCOORD4; + float3 InstanceOrigin : TEXCOORD5; + float2 InstanceParams : TEXCOORD6; // x-PerInstanceRandom, y-LODDitherFactor + float3 PrevWorldPosition : TEXCOORD7; +}; + +// Interpolants passed from the vertex shader +struct VertexOutput +{ + float4 Position : SV_Position; + GeometryData Geometry; +#if USE_CUSTOM_VERTEX_INTERPOLATORS + float4 CustomVSToPS[CUSTOM_VERTEX_INTERPOLATORS_COUNT] : TEXCOORD9; +#endif +#if USE_TESSELLATION + float TessellationMultiplier : TESS; +#endif +}; + +// Interpolants passed to the pixel shader +struct PixelInput +{ + float4 Position : SV_Position; + GeometryData Geometry; +#if USE_CUSTOM_VERTEX_INTERPOLATORS + float4 CustomVSToPS[CUSTOM_VERTEX_INTERPOLATORS_COUNT] : TEXCOORD9; +#endif + bool IsFrontFace : SV_IsFrontFace; +}; + +// Material properties generation input +struct MaterialInput +{ + float3 WorldPosition; + float TwoSidedSign; + float2 TexCoord; +#if USE_LIGHTMAP + float2 LightmapUV; +#endif +#if USE_VERTEX_COLOR + half4 VertexColor; +#endif + float3x3 TBN; + float4 SvPosition; + float3 PreSkinnedPosition; + float3 PreSkinnedNormal; + float3 InstanceOrigin; + float2 InstanceParams; +#if USE_INSTANCING + float3 InstanceTransform1; + float3 InstanceTransform2; + float3 InstanceTransform3; +#endif +#if USE_CUSTOM_VERTEX_INTERPOLATORS + float4 CustomVSToPS[CUSTOM_VERTEX_INTERPOLATORS_COUNT]; +#endif +}; + +// Extracts geometry data to the material input +MaterialInput GetGeometryMaterialInput(GeometryData geometry) +{ + MaterialInput output = (MaterialInput)0; + output.WorldPosition = geometry.WorldPosition; + output.TexCoord = geometry.TexCoord; +#if USE_LIGHTMAP + output.LightmapUV = geometry.LightmapUV; +#endif +#if USE_VERTEX_COLOR + output.VertexColor = geometry.VertexColor; +#endif + output.TBN = CalcTangentBasis(geometry.WorldNormal, geometry.WorldTangent); + output.InstanceOrigin = geometry.InstanceOrigin; + output.InstanceParams = geometry.InstanceParams; + return output; +} + +#if USE_TESSELLATION + +// Interpolates the geometry positions data only (used by the tessallation when generating vertices) +#define InterpolateGeometryPositions(output, p0, w0, p1, w1, p2, w2, offset) output.WorldPosition = p0.WorldPosition * w0 + p1.WorldPosition * w1 + p2.WorldPosition * w2 + offset; output.PrevWorldPosition = p0.PrevWorldPosition * w0 + p1.PrevWorldPosition * w1 + p2.PrevWorldPosition * w2 + offset + +// Offsets the geometry positions data only (used by the tessallation when generating vertices) +#define OffsetGeometryPositions(geometry, offset) geometry.WorldPosition += offset; geometry.PrevWorldPosition += offset + +// Applies the Phong tessallation to the geometry positions (used by the tessallation when doing Phong tess) +#define ApplyGeometryPositionsPhongTess(geometry, p0, p1, p2, U, V, W) \ + float3 posProjectedU = TessalationProjectOntoPlane(p0.WorldNormal, p0.WorldPosition, geometry.WorldPosition); \ + float3 posProjectedV = TessalationProjectOntoPlane(p1.WorldNormal, p1.WorldPosition, geometry.WorldPosition); \ + float3 posProjectedW = TessalationProjectOntoPlane(p2.WorldNormal, p2.WorldPosition, geometry.WorldPosition); \ + geometry.WorldPosition = U * posProjectedU + V * posProjectedV + W * posProjectedW; \ + posProjectedU = TessalationProjectOntoPlane(p0.WorldNormal, p0.PrevWorldPosition, geometry.PrevWorldPosition); \ + posProjectedV = TessalationProjectOntoPlane(p1.WorldNormal, p1.PrevWorldPosition, geometry.PrevWorldPosition); \ + posProjectedW = TessalationProjectOntoPlane(p2.WorldNormal, p2.PrevWorldPosition, geometry.PrevWorldPosition); \ + geometry.PrevWorldPosition = U * posProjectedU + V * posProjectedV + W * posProjectedW + +// Interpolates the geometry data except positions (used by the tessallation when generating vertices) +GeometryData InterpolateGeometry(GeometryData p0, float w0, GeometryData p1, float w1, GeometryData p2, float w2) +{ + GeometryData output = (GeometryData)0; + output.TexCoord = p0.TexCoord * w0 + p1.TexCoord * w1 + p2.TexCoord * w2; +#if USE_LIGHTMAP + output.LightmapUV = p0.LightmapUV * w0 + p1.LightmapUV * w1 + p2.LightmapUV * w2; +#endif +#if USE_VERTEX_COLOR + output.VertexColor = p0.VertexColor * w0 + p1.VertexColor * w1 + p2.VertexColor * w2; +#endif + output.WorldNormal = p0.WorldNormal * w0 + p1.WorldNormal * w1 + p2.WorldNormal * w2; + output.WorldNormal = normalize(output.WorldNormal); + output.WorldTangent = p0.WorldTangent * w0 + p1.WorldTangent * w1 + p2.WorldTangent * w2; + output.WorldTangent.xyz = normalize(output.WorldTangent.xyz); + output.InstanceOrigin = p0.InstanceOrigin; + output.InstanceParams = p0.InstanceParams; + return output; +} + +#endif + +MaterialInput GetMaterialInput(PixelInput input) +{ + MaterialInput output = GetGeometryMaterialInput(input.Geometry); + output.TwoSidedSign = WorldDeterminantSign * (input.IsFrontFace ? 1.0 : -1.0); + output.SvPosition = input.Position; +#if USE_CUSTOM_VERTEX_INTERPOLATORS + output.CustomVSToPS = input.CustomVSToPS; +#endif + return output; +} + +// Gets the local to world transform matrix (supports instancing) +#if USE_INSTANCING +#define GetInstanceTransform(input) float4x4(float4(input.InstanceTransform1.xyz, 0.0f), float4(input.InstanceTransform2.xyz, 0.0f), float4(input.InstanceTransform3.xyz, 0.0f), float4(input.InstanceOrigin.xyz, 1.0f)) +#else +#define GetInstanceTransform(input) WorldMatrix; +#endif + +// Removes the scale vector from the local to world transformation matrix (supports instancing) +float3x3 RemoveScaleFromLocalToWorld(float3x3 localToWorld) +{ +#if USE_INSTANCING + // Extract per axis scales from localToWorld transform + float scaleX = length(localToWorld[0]); + float scaleY = length(localToWorld[1]); + float scaleZ = length(localToWorld[2]); + float3 invScale = float3( + scaleX > 0.00001f ? 1.0f / scaleX : 0.0f, + scaleY > 0.00001f ? 1.0f / scaleY : 0.0f, + scaleZ > 0.00001f ? 1.0f / scaleZ : 0.0f); +#else + float3 invScale = WorldInvScale; +#endif + localToWorld[0] *= invScale.x; + localToWorld[1] *= invScale.y; + localToWorld[2] *= invScale.z; + return localToWorld; +} + +// Transforms a vector from tangent space to world space +float3 TransformTangentVectorToWorld(MaterialInput input, float3 tangentVector) +{ + return mul(tangentVector, input.TBN); +} + +// Transforms a vector from world space to tangent space +float3 TransformWorldVectorToTangent(MaterialInput input, float3 worldVector) +{ + return mul(input.TBN, worldVector); +} + +// Transforms a vector from world space to view space +float3 TransformWorldVectorToView(MaterialInput input, float3 worldVector) +{ + return mul(worldVector, (float3x3)ViewMatrix); +} + +// Transforms a vector from view space to world space +float3 TransformViewVectorToWorld(MaterialInput input, float3 viewVector) +{ + return mul((float3x3)ViewMatrix, viewVector); +} + +// Transforms a vector from local space to world space +float3 TransformLocalVectorToWorld(MaterialInput input, float3 localVector) +{ + float3x3 localToWorld = (float3x3)GetInstanceTransform(input); + //localToWorld = RemoveScaleFromLocalToWorld(localToWorld); + return mul(localVector, localToWorld); +} + +// Transforms a vector from local space to world space +float3 TransformWorldVectorToLocal(MaterialInput input, float3 worldVector) +{ + float3x3 localToWorld = (float3x3)GetInstanceTransform(input); + //localToWorld = RemoveScaleFromLocalToWorld(localToWorld); + return mul(localToWorld, worldVector); +} + +// Gets the current object position (supports instancing) +float3 GetObjectPosition(MaterialInput input) +{ + return input.InstanceOrigin.xyz; +} + +// Gets the current object size (supports instancing) +float3 GetObjectSize(MaterialInput input) +{ + float4x4 world = GetInstanceTransform(input); + return GeometrySize * float3(world._m00, world._m11, world._m22); +} + +// Get the current object random value (supports instancing) +float GetPerInstanceRandom(MaterialInput input) +{ + return input.InstanceParams.x; +} + +// Get the current object LOD transition dither factor (supports instancing) +float GetLODDitherFactor(MaterialInput input) +{ +#if USE_DITHERED_LOD_TRANSITION + return input.InstanceParams.y; +#else + return 0; +#endif +} + +// Gets the interpolated vertex color (in linear space) +float4 GetVertexColor(MaterialInput input) +{ +#if USE_VERTEX_COLOR + return input.VertexColor; +#else + return 1; +#endif +} + +@8 + +// Get material properties function (for vertex shader) +Material GetMaterialVS(MaterialInput input) +{ +@5 +} + +// Get material properties function (for domain shader) +Material GetMaterialDS(MaterialInput input) +{ +@6 +} + +// Get material properties function (for pixel shader) +Material GetMaterialPS(MaterialInput input) +{ +@4 +} + +// Calculates the transform matrix from mesh tangent space to local space +float3x3 CalcTangentToLocal(ModelInput input) +{ + float bitangentSign = input.Tangent.w ? -1.0f : +1.0f; + float3 normal = input.Normal.xyz * 2.0 - 1.0; + float3 tangent = input.Tangent.xyz * 2.0 - 1.0; + float3 bitangent = cross(normal, tangent) * bitangentSign; + return float3x3(tangent, bitangent, normal); +} + +float3x3 CalcTangentToWorld(float4x4 world, float3x3 tangentToLocal) +{ + float3x3 localToWorld = RemoveScaleFromLocalToWorld((float3x3)world); + return mul(tangentToLocal, localToWorld); +} + +// Vertex Shader function for GBuffer Pass and Depth Pass (with full vertex data) +META_VS(true, FEATURE_LEVEL_ES2) +META_PERMUTATION_1(USE_INSTANCING=0) +META_PERMUTATION_1(USE_INSTANCING=1) +META_VS_IN_ELEMENT(POSITION, 0, R32G32B32_FLOAT, 0, 0, PER_VERTEX, 0, true) +META_VS_IN_ELEMENT(TEXCOORD, 0, R16G16_FLOAT, 1, 0, PER_VERTEX, 0, true) +META_VS_IN_ELEMENT(NORMAL, 0, R10G10B10A2_UNORM, 1, ALIGN, PER_VERTEX, 0, true) +META_VS_IN_ELEMENT(TANGENT, 0, R10G10B10A2_UNORM, 1, ALIGN, PER_VERTEX, 0, true) +META_VS_IN_ELEMENT(TEXCOORD, 1, R16G16_FLOAT, 1, ALIGN, PER_VERTEX, 0, true) +META_VS_IN_ELEMENT(COLOR, 0, R8G8B8A8_UNORM, 2, 0, PER_VERTEX, 0, USE_VERTEX_COLOR) +META_VS_IN_ELEMENT(ATTRIBUTE,0, R32G32B32A32_FLOAT,3, 0, PER_INSTANCE, 1, USE_INSTANCING) +META_VS_IN_ELEMENT(ATTRIBUTE,1, R32G32B32A32_FLOAT,3, ALIGN, PER_INSTANCE, 1, USE_INSTANCING) +META_VS_IN_ELEMENT(ATTRIBUTE,2, R32G32B32_FLOAT, 3, ALIGN, PER_INSTANCE, 1, USE_INSTANCING) +META_VS_IN_ELEMENT(ATTRIBUTE,3, R32G32B32_FLOAT, 3, ALIGN, PER_INSTANCE, 1, USE_INSTANCING) +META_VS_IN_ELEMENT(ATTRIBUTE,4, R16G16B16A16_FLOAT,3, ALIGN, PER_INSTANCE, 1, USE_INSTANCING) +VertexOutput VS(ModelInput input) +{ + VertexOutput output; + + // Compute world space vertex position + float4x4 world = GetInstanceTransform(input); + output.Geometry.WorldPosition = mul(float4(input.Position.xyz, 1), world).xyz; + output.Geometry.PrevWorldPosition = mul(float4(input.Position.xyz, 1), PrevWorldMatrix).xyz; + + // Compute clip space position + output.Position = mul(float4(output.Geometry.WorldPosition, 1), ViewProjectionMatrix); + + // Pass vertex attributes + output.Geometry.TexCoord = input.TexCoord; +#if USE_VERTEX_COLOR + output.Geometry.VertexColor = input.Color; +#endif + output.Geometry.InstanceOrigin = world[3].xyz; +#if USE_INSTANCING + output.Geometry.LightmapUV = input.LightmapUV * input.InstanceLightmapArea.zw + input.InstanceLightmapArea.xy; + output.Geometry.InstanceParams = float2(input.InstanceOrigin.w, input.InstanceTransform1.w); +#else +#if CAN_USE_LIGHTMAP + output.Geometry.LightmapUV = input.LightmapUV * LightmapArea.zw + LightmapArea.xy; +#else + output.Geometry.LightmapUV = input.LightmapUV; +#endif + output.Geometry.InstanceParams = float2(PerInstanceRandom, LODDitherFactor); +#endif + + // Calculate tanget space to world space transformation matrix for unit vectors + float3x3 tangentToLocal = CalcTangentToLocal(input); + float3x3 tangentToWorld = CalcTangentToWorld(world, tangentToLocal); + output.Geometry.WorldNormal = tangentToWorld[2]; + output.Geometry.WorldTangent.xyz = tangentToWorld[0]; + output.Geometry.WorldTangent.w = input.Tangent.w ? -1.0f : +1.0f; + + // Get material input params if need to evaluate any material property +#if USE_POSITION_OFFSET || USE_TESSELLATION || USE_CUSTOM_VERTEX_INTERPOLATORS + MaterialInput materialInput = GetGeometryMaterialInput(output.Geometry); + materialInput.TwoSidedSign = WorldDeterminantSign; + materialInput.SvPosition = output.Position; + materialInput.PreSkinnedPosition = input.Position.xyz; + materialInput.PreSkinnedNormal = tangentToLocal[2].xyz; +#if USE_INSTANCING + materialInput.InstanceTransform1 = input.InstanceTransform1.xyz; + materialInput.InstanceTransform2 = input.InstanceTransform2.xyz; + materialInput.InstanceTransform3 = input.InstanceTransform3.xyz; +#endif + Material material = GetMaterialVS(materialInput); +#endif + + // Apply world position offset per-vertex +#if USE_POSITION_OFFSET + output.Geometry.WorldPosition += material.PositionOffset; + output.Position = mul(float4(output.Geometry.WorldPosition, 1), ViewProjectionMatrix); +#endif + + // Get tessalation multiplier (per vertex) +#if USE_TESSELLATION + output.TessellationMultiplier = material.TessellationMultiplier; +#endif + + // Copy interpolants for other shader stages +#if USE_CUSTOM_VERTEX_INTERPOLATORS + output.CustomVSToPS = material.CustomVSToPS; +#endif + + return output; +} + +// Vertex Shader function for Depth Pass +META_VS(true, FEATURE_LEVEL_ES2) +META_PERMUTATION_1(USE_INSTANCING=0) +META_PERMUTATION_1(USE_INSTANCING=1) +META_VS_IN_ELEMENT(POSITION, 0, R32G32B32_FLOAT, 0, 0, PER_VERTEX, 0, true) +META_VS_IN_ELEMENT(ATTRIBUTE,0, R32G32B32A32_FLOAT,3, 0, PER_INSTANCE, 1, USE_INSTANCING) +META_VS_IN_ELEMENT(ATTRIBUTE,1, R32G32B32A32_FLOAT,3, ALIGN, PER_INSTANCE, 1, USE_INSTANCING) +META_VS_IN_ELEMENT(ATTRIBUTE,2, R32G32B32_FLOAT, 3, ALIGN, PER_INSTANCE, 1, USE_INSTANCING) +META_VS_IN_ELEMENT(ATTRIBUTE,3, R32G32B32_FLOAT, 3, ALIGN, PER_INSTANCE, 1, USE_INSTANCING) +META_VS_IN_ELEMENT(ATTRIBUTE,4, R16G16B16A16_FLOAT,3, ALIGN, PER_INSTANCE, 1, USE_INSTANCING) +float4 VS_Depth(ModelInput_PosOnly input) : SV_Position +{ + float4x4 world = GetInstanceTransform(input); + float3 worldPosition = mul(float4(input.Position.xyz, 1), world).xyz; + float4 position = mul(float4(worldPosition, 1), ViewProjectionMatrix); + return position; +} + +#if USE_SKINNING + +// The skeletal bones matrix buffer (stored as 4x3, 3 float4 behind each other) +Buffer BoneMatrices : register(t0); + +#if PER_BONE_MOTION_BLUR + +// The skeletal bones matrix buffer from the previous frame +Buffer PrevBoneMatrices : register(t1); + +float3x4 GetPrevBoneMatrix(int index) +{ + float4 a = PrevBoneMatrices[index * 3]; + float4 b = PrevBoneMatrices[index * 3 + 1]; + float4 c = PrevBoneMatrices[index * 3 + 2]; + return float3x4(a, b, c); +} + +float3 SkinPrevPosition(ModelInput_Skinned input) +{ + float4 position = float4(input.Position.xyz, 1); + float3x4 boneMatrix = input.BlendWeights.x * GetPrevBoneMatrix(input.BlendIndices.x); + boneMatrix += input.BlendWeights.y * GetPrevBoneMatrix(input.BlendIndices.y); + boneMatrix += input.BlendWeights.z * GetPrevBoneMatrix(input.BlendIndices.z); + boneMatrix += input.BlendWeights.w * GetPrevBoneMatrix(input.BlendIndices.w); + return mul(boneMatrix, position); +} + +#endif + +// Cached skinning data to avoid multiple calculation +struct SkinningData +{ + float3x4 BlendMatrix; +}; + +// Calculates the transposed transform matrix for the given bone index +float3x4 GetBoneMatrix(int index) +{ + float4 a = BoneMatrices[index * 3]; + float4 b = BoneMatrices[index * 3 + 1]; + float4 c = BoneMatrices[index * 3 + 2]; + return float3x4(a, b, c); +} + +// Calculates the transposed transform matrix for the given vertex (uses blending) +float3x4 GetBoneMatrix(ModelInput_Skinned input) +{ + float3x4 boneMatrix = input.BlendWeights.x * GetBoneMatrix(input.BlendIndices.x); + boneMatrix += input.BlendWeights.y * GetBoneMatrix(input.BlendIndices.y); + boneMatrix += input.BlendWeights.z * GetBoneMatrix(input.BlendIndices.z); + boneMatrix += input.BlendWeights.w * GetBoneMatrix(input.BlendIndices.w); + return boneMatrix; +} + +// Transforms the vertex position by weighted sum of the skinning matrices +float3 SkinPosition(ModelInput_Skinned input, SkinningData data) +{ + return mul(data.BlendMatrix, float4(input.Position.xyz, 1)); +} + +// Transforms the vertex position by weighted sum of the skinning matrices +float3x3 SkinTangents(ModelInput_Skinned input, SkinningData data) +{ + // Unpack vertex tangent frame + float bitangentSign = input.Tangent.w ? -1.0f : +1.0f; + float3 normal = input.Normal.xyz * 2.0 - 1.0; + float3 tangent = input.Tangent.xyz * 2.0 - 1.0; + + // Apply skinning + tangent = mul(data.BlendMatrix, float4(tangent, 0)); + normal = mul(data.BlendMatrix, float4(normal, 0)); + + float3 bitangent = cross(normal, tangent) * bitangentSign; + return float3x3(tangent, bitangent, normal); +} + +// Vertex Shader function for GBuffers/Depth Pass (skinned mesh rendering) +META_VS(true, FEATURE_LEVEL_ES2) +META_PERMUTATION_1(USE_SKINNING=1) +META_PERMUTATION_2(USE_SKINNING=1, PER_BONE_MOTION_BLUR=1) +META_VS_IN_ELEMENT(POSITION, 0, R32G32B32_FLOAT, 0, 0, PER_VERTEX, 0, true) +META_VS_IN_ELEMENT(TEXCOORD, 0, R16G16_FLOAT, 0, ALIGN, PER_VERTEX, 0, true) +META_VS_IN_ELEMENT(NORMAL, 0, R10G10B10A2_UNORM, 0, ALIGN, PER_VERTEX, 0, true) +META_VS_IN_ELEMENT(TANGENT, 0, R10G10B10A2_UNORM, 0, ALIGN, PER_VERTEX, 0, true) +META_VS_IN_ELEMENT(BLENDINDICES, 0, R8G8B8A8_UINT, 0, ALIGN, PER_VERTEX, 0, true) +META_VS_IN_ELEMENT(BLENDWEIGHT, 0, R16G16B16A16_FLOAT,0, ALIGN, PER_VERTEX, 0, true) +VertexOutput VS_Skinned(ModelInput_Skinned input) +{ + VertexOutput output; + + // Perform skinning + SkinningData data; + data.BlendMatrix = GetBoneMatrix(input); + float3 position = SkinPosition(input, data); + float3x3 tangentToLocal = SkinTangents(input, data); + + // Compute world space vertex position + float4x4 world = GetInstanceTransform(input); + output.Geometry.WorldPosition = mul(float4(position, 1), world).xyz; +#if PER_BONE_MOTION_BLUR + float3 prevPosition = SkinPrevPosition(input); + output.Geometry.PrevWorldPosition = mul(float4(prevPosition, 1), PrevWorldMatrix).xyz; +#else + output.Geometry.PrevWorldPosition = mul(float4(position, 1), PrevWorldMatrix).xyz; +#endif + + // Compute clip space position + output.Position = mul(float4(output.Geometry.WorldPosition, 1), ViewProjectionMatrix); + + // Pass vertex attributes + output.Geometry.TexCoord = input.TexCoord; +#if USE_VERTEX_COLOR + output.Geometry.VertexColor = float4(0, 0, 0, 1); +#endif + output.Geometry.LightmapUV = float2(0, 0); + output.Geometry.InstanceOrigin = world[3].xyz; +#if USE_INSTANCING + output.Geometry.InstanceParams = float2(input.InstanceOrigin.w, input.InstanceTransform1.w); +#else + output.Geometry.InstanceParams = float2(PerInstanceRandom, LODDitherFactor); +#endif + + // Calculate tanget space to world space transformation matrix for unit vectors + float3x3 tangentToWorld = CalcTangentToWorld(world, tangentToLocal); + output.Geometry.WorldNormal = tangentToWorld[2]; + output.Geometry.WorldTangent.xyz = tangentToWorld[0]; + output.Geometry.WorldTangent.w = input.Tangent.w ? -1.0f : +1.0f; + + // Get material input params if need to evaluate any material property +#if USE_POSITION_OFFSET || USE_TESSELLATION || USE_CUSTOM_VERTEX_INTERPOLATORS + MaterialInput materialInput = GetGeometryMaterialInput(output.Geometry); + materialInput.TwoSidedSign = WorldDeterminantSign; + materialInput.SvPosition = output.Position; + materialInput.PreSkinnedPosition = input.Position.xyz; + materialInput.PreSkinnedNormal = tangentToLocal[2].xyz; + Material material = GetMaterialVS(materialInput); +#endif + + // Apply world position offset per-vertex +#if USE_POSITION_OFFSET + output.Geometry.WorldPosition += material.PositionOffset; + output.Position = mul(float4(output.Geometry.WorldPosition, 1), ViewProjectionMatrix); +#endif + + // Get tessalation multiplier (per vertex) +#if USE_TESSELLATION + output.TessellationMultiplier = material.TessellationMultiplier; +#endif + + // Copy interpolants for other shader stages +#if USE_CUSTOM_VERTEX_INTERPOLATORS + output.CustomVSToPS = material.CustomVSToPS; +#endif + + return output; +} + +#endif + +#if USE_DITHERED_LOD_TRANSITION + +void ClipLODTransition(PixelInput input) +{ + float ditherFactor = input.InstanceParams.y; + if (abs(ditherFactor) > 0.001) + { + float randGrid = cos(dot(floor(input.Position.xy), float2(347.83452793, 3343.28371863))); + float randGridFrac = frac(randGrid * 1000.0); + half mask = (ditherFactor < 0.0) ? (ditherFactor + 1.0 > randGridFrac) : (ditherFactor < randGridFrac); + clip(mask - 0.001); + } +} + +#endif + +// Pixel Shader function for Depth Pass +META_PS(true, FEATURE_LEVEL_ES2) +void PS_Depth(PixelInput input) +{ +#if USE_DITHERED_LOD_TRANSITION + // LOD masking + ClipLODTransition(input); +#endif + +#if MATERIAL_MASKED || MATERIAL_BLEND != MATERIAL_BLEND_OPAQUE + // Get material parameters + MaterialInput materialInput = GetMaterialInput(input); + Material material = GetMaterialPS(materialInput); + + // Perform per pixel clipping +#if MATERIAL_MASKED + clip(material.Mask - MATERIAL_MASK_THRESHOLD); +#endif +#if MATERIAL_BLEND != MATERIAL_BLEND_OPAQUE + clip(material.Opacity - MATERIAL_OPACITY_THRESHOLD); +#endif +#endif +} + +@9 diff --git a/Content/Editor/MaterialTemplates/SurfaceDeferred.shader b/Content/Editor/MaterialTemplates/SurfaceDeferred.shader deleted file mode 100644 index 6f393464b..000000000 --- a/Content/Editor/MaterialTemplates/SurfaceDeferred.shader +++ /dev/null @@ -1,1083 +0,0 @@ -// File generated by Flax Materials Editor -// Version: @0 - -#define MATERIAL 1 -@3 - -#include "./Flax/Common.hlsl" -#include "./Flax/MaterialCommon.hlsl" -#include "./Flax/GBufferCommon.hlsl" -@7 -// Primary constant buffer (with additional material parameters) -META_CB_BEGIN(0, Data) -float4x4 ViewProjectionMatrix; -float4x4 WorldMatrix; -float4x4 ViewMatrix; -float4x4 PrevViewProjectionMatrix; -float4x4 PrevWorldMatrix; -float3 ViewPos; -float ViewFar; -float3 ViewDir; -float TimeParam; -float4 ViewInfo; -float4 ScreenSize; -float4 LightmapArea; -float3 WorldInvScale; -float WorldDeterminantSign; -float2 Dummy0; -float LODDitherFactor; -float PerInstanceRandom; -float4 TemporalAAJitter; -float3 GeometrySize; -float Dummy1; -@1META_CB_END - -#if CAN_USE_LIGHTMAP - -// Irradiance and directionality prebaked lightmaps -Texture2D Lightmap0 : register(t0); -Texture2D Lightmap1 : register(t1); -Texture2D Lightmap2 : register(t2); - -#endif - -// Material shader resources -@2 - -// Interpolants passed from the vertex shader -struct VertexOutput -{ - float4 Position : SV_Position; - float3 WorldPosition : TEXCOORD0; - float2 TexCoord : TEXCOORD1; - float2 LightmapUV : TEXCOORD2; -#if USE_VERTEX_COLOR - half4 VertexColor : COLOR; -#endif - float3 WorldNormal : TEXCOORD3; - float4 WorldTangent : TEXCOORD4; -#if USE_CUSTOM_VERTEX_INTERPOLATORS - float4 CustomVSToPS[CUSTOM_VERTEX_INTERPOLATORS_COUNT] : TEXCOORD9; -#endif - float3 InstanceOrigin : TEXCOORD6; - float2 InstanceParams : TEXCOORD7; // x-PerInstanceRandom, y-LODDitherFactor -#if IS_MOTION_VECTORS_PASS - float3 PrevWorldPosition : TEXCOORD8; -#endif -#if USE_TESSELLATION - float TessellationMultiplier : TESS; -#endif -}; - -// Interpolants passed to the pixel shader -struct PixelInput -{ - float4 Position : SV_Position; - float3 WorldPosition : TEXCOORD0; - float2 TexCoord : TEXCOORD1; - float2 LightmapUV : TEXCOORD2; -#if USE_VERTEX_COLOR - half4 VertexColor : COLOR; -#endif - float3 WorldNormal : TEXCOORD3; - float4 WorldTangent : TEXCOORD4; -#if USE_CUSTOM_VERTEX_INTERPOLATORS - float4 CustomVSToPS[CUSTOM_VERTEX_INTERPOLATORS_COUNT] : TEXCOORD9; -#endif - float3 InstanceOrigin : TEXCOORD6; - float2 InstanceParams : TEXCOORD7; // x-PerInstanceRandom, y-LODDitherFactor -#if IS_MOTION_VECTORS_PASS - float3 PrevWorldPosition : TEXCOORD8; -#endif - bool IsFrontFace : SV_IsFrontFace; -}; - -// Material properties generation input -struct MaterialInput -{ - float3 WorldPosition; - float TwoSidedSign; - float2 TexCoord; -#if USE_LIGHTMAP - float2 LightmapUV; -#endif -#if USE_VERTEX_COLOR - half4 VertexColor; -#endif - float3x3 TBN; - float4 SvPosition; - float3 PreSkinnedPosition; - float3 PreSkinnedNormal; - float3 InstanceOrigin; - float2 InstanceParams; -#if USE_INSTANCING - float3 InstanceTransform1; - float3 InstanceTransform2; - float3 InstanceTransform3; -#endif -#if USE_CUSTOM_VERTEX_INTERPOLATORS - float4 CustomVSToPS[CUSTOM_VERTEX_INTERPOLATORS_COUNT]; -#endif -}; - -float3x3 CalcTangentBasis(float3 normal, float4 tangent) -{ - float3 bitangent = cross(normal, tangent.xyz) * tangent.w; - return float3x3(tangent.xyz, bitangent, normal); -} - -MaterialInput GetMaterialInput(ModelInput input, VertexOutput output, float3 localNormal) -{ - MaterialInput result = (MaterialInput)0; - result.WorldPosition = output.WorldPosition; - result.TexCoord = output.TexCoord; -#if USE_LIGHTMAP - result.LightmapUV = output.LightmapUV; -#endif -#if USE_VERTEX_COLOR - result.VertexColor = output.VertexColor; -#endif - result.TBN = CalcTangentBasis(output.WorldNormal, output.WorldTangent); - result.TwoSidedSign = WorldDeterminantSign; - result.SvPosition = output.Position; - result.PreSkinnedPosition = input.Position.xyz; - result.PreSkinnedNormal = localNormal; -#if USE_INSTANCING - result.InstanceOrigin = input.InstanceOrigin.xyz; - result.InstanceParams = float2(input.InstanceOrigin.w, input.InstanceTransform1.w); - result.InstanceTransform1 = input.InstanceTransform1.xyz; - result.InstanceTransform2 = input.InstanceTransform2.xyz; - result.InstanceTransform3 = input.InstanceTransform3.xyz; -#else - result.InstanceOrigin = WorldMatrix[3].xyz; - result.InstanceParams = float2(PerInstanceRandom, LODDitherFactor); -#endif - return result; -} - -MaterialInput GetMaterialInput(VertexOutput output, float3 localPosition, float3 localNormal) -{ - MaterialInput result = (MaterialInput)0; - result.WorldPosition = output.WorldPosition; - result.TexCoord = output.TexCoord; -#if USE_LIGHTMAP - result.LightmapUV = output.LightmapUV; -#endif -#if USE_VERTEX_COLOR - result.VertexColor = output.VertexColor; -#endif - result.TBN = CalcTangentBasis(output.WorldNormal, output.WorldTangent); - result.TwoSidedSign = WorldDeterminantSign; - result.InstanceOrigin = WorldMatrix[3].xyz; - result.InstanceParams = float2(PerInstanceRandom, LODDitherFactor); - result.SvPosition = output.Position; - result.PreSkinnedPosition = localPosition; - result.PreSkinnedNormal = localNormal; - return result; -} - -MaterialInput GetMaterialInput(PixelInput input) -{ - MaterialInput result = (MaterialInput)0; - result.WorldPosition = input.WorldPosition; - result.TexCoord = input.TexCoord; -#if USE_LIGHTMAP - result.LightmapUV = input.LightmapUV; -#endif -#if USE_VERTEX_COLOR - result.VertexColor = input.VertexColor; -#endif - result.TBN = CalcTangentBasis(input.WorldNormal, input.WorldTangent); - result.TwoSidedSign = WorldDeterminantSign * (input.IsFrontFace ? 1.0 : -1.0); - result.InstanceOrigin = input.InstanceOrigin; - result.InstanceParams = input.InstanceParams; - result.SvPosition = input.Position; -#if USE_CUSTOM_VERTEX_INTERPOLATORS - result.CustomVSToPS = input.CustomVSToPS; -#endif - return result; -} - -#if USE_INSTANCING -#define INSTANCE_TRANS_WORLD float4x4(float4(input.InstanceTransform1.xyz, 0.0f), float4(input.InstanceTransform2.xyz, 0.0f), float4(input.InstanceTransform3.xyz, 0.0f), float4(input.InstanceOrigin.xyz, 1.0f)) -#else -#define INSTANCE_TRANS_WORLD WorldMatrix -#endif - -// Gets the local to world transform matrix (supports instancing) -float4x4 GetInstanceTransform(ModelInput input) -{ - return INSTANCE_TRANS_WORLD; -} -float4x4 GetInstanceTransform(ModelInput_Skinned input) -{ - return INSTANCE_TRANS_WORLD; -} -float4x4 GetInstanceTransform(ModelInput_PosOnly input) -{ - return INSTANCE_TRANS_WORLD; -} -float4x4 GetInstanceTransform(MaterialInput input) -{ - return INSTANCE_TRANS_WORLD; -} - -// Removes the scale vector from the local to world transformation matrix (supports instancing) -float3x3 RemoveScaleFromLocalToWorld(float3x3 localToWorld) -{ -#if USE_INSTANCING - // Extract per axis scales from localToWorld transform - float scaleX = length(localToWorld[0]); - float scaleY = length(localToWorld[1]); - float scaleZ = length(localToWorld[2]); - float3 invScale = float3( - scaleX > 0.00001f ? 1.0f / scaleX : 0.0f, - scaleY > 0.00001f ? 1.0f / scaleY : 0.0f, - scaleZ > 0.00001f ? 1.0f / scaleZ : 0.0f); -#else - float3 invScale = WorldInvScale; -#endif - localToWorld[0] *= invScale.x; - localToWorld[1] *= invScale.y; - localToWorld[2] *= invScale.z; - return localToWorld; -} - -// Transforms a vector from tangent space to world space -float3 TransformTangentVectorToWorld(MaterialInput input, float3 tangentVector) -{ - return mul(tangentVector, input.TBN); -} - -// Transforms a vector from world space to tangent space -float3 TransformWorldVectorToTangent(MaterialInput input, float3 worldVector) -{ - return mul(input.TBN, worldVector); -} - -// Transforms a vector from world space to view space -float3 TransformWorldVectorToView(MaterialInput input, float3 worldVector) -{ - return mul(worldVector, (float3x3)ViewMatrix); -} - -// Transforms a vector from view space to world space -float3 TransformViewVectorToWorld(MaterialInput input, float3 viewVector) -{ - return mul((float3x3)ViewMatrix, viewVector); -} - -// Transforms a vector from local space to world space -float3 TransformLocalVectorToWorld(MaterialInput input, float3 localVector) -{ - float3x3 localToWorld = (float3x3)GetInstanceTransform(input); - //localToWorld = RemoveScaleFromLocalToWorld(localToWorld); - return mul(localVector, localToWorld); -} - -// Transforms a vector from local space to world space -float3 TransformWorldVectorToLocal(MaterialInput input, float3 worldVector) -{ - float3x3 localToWorld = (float3x3)GetInstanceTransform(input); - //localToWorld = RemoveScaleFromLocalToWorld(localToWorld); - return mul(localToWorld, worldVector); -} - -// Gets the current object position (supports instancing) -float3 GetObjectPosition(MaterialInput input) -{ - return input.InstanceOrigin.xyz; -} - -// Gets the current object size (supports instancing) -float3 GetObjectSize(MaterialInput input) -{ - float4x4 world = GetInstanceTransform(input); - return GeometrySize * float3(world._m00, world._m11, world._m22); -} - -// Get the current object random value (supports instancing) -float GetPerInstanceRandom(MaterialInput input) -{ - return input.InstanceParams.x; -} - -// Get the current object LOD transition dither factor (supports instancing) -float GetLODDitherFactor(MaterialInput input) -{ -#if USE_DITHERED_LOD_TRANSITION - return input.InstanceParams.y; -#else - return 0; -#endif -} - -// Gets the interpolated vertex color (in linear space) -float4 GetVertexColor(MaterialInput input) -{ -#if USE_VERTEX_COLOR - return input.VertexColor; -#else - return 1; -#endif -} - -// Get material properties function (for vertex shader) -Material GetMaterialVS(MaterialInput input) -{ -@5 -} - -// Get material properties function (for domain shader) -Material GetMaterialDS(MaterialInput input) -{ -@6 -} - -// Get material properties function (for pixel shader) -Material GetMaterialPS(MaterialInput input) -{ -@4 -} - -// Fix line for errors/warnings for shader code from template -#line 1000 - -// Calculates the transform matrix from mesh tangent space to local space -float3x3 CalcTangentToLocal(ModelInput input) -{ - float bitangentSign = input.Tangent.w ? -1.0f : +1.0f; - float3 normal = input.Normal.xyz * 2.0 - 1.0; - float3 tangent = input.Tangent.xyz * 2.0 - 1.0; - float3 bitangent = cross(normal, tangent) * bitangentSign; - return float3x3(tangent, bitangent, normal); -} - -float3x3 CalcTangentToWorld(float4x4 world, float3x3 tangentToLocal) -{ - float3x3 localToWorld = RemoveScaleFromLocalToWorld((float3x3)world); - return mul(tangentToLocal, localToWorld); -} - -// Vertex Shader function for GBuffer Pass and Depth Pass (with full vertex data) -META_VS(IS_SURFACE, FEATURE_LEVEL_ES2) -META_PERMUTATION_1(USE_INSTANCING=0) -META_PERMUTATION_1(USE_INSTANCING=1) -META_PERMUTATION_2(USE_INSTANCING=0, IS_MOTION_VECTORS_PASS=1) -META_VS_IN_ELEMENT(POSITION, 0, R32G32B32_FLOAT, 0, 0, PER_VERTEX, 0, true) -META_VS_IN_ELEMENT(TEXCOORD, 0, R16G16_FLOAT, 1, 0, PER_VERTEX, 0, true) -META_VS_IN_ELEMENT(NORMAL, 0, R10G10B10A2_UNORM, 1, ALIGN, PER_VERTEX, 0, true) -META_VS_IN_ELEMENT(TANGENT, 0, R10G10B10A2_UNORM, 1, ALIGN, PER_VERTEX, 0, true) -META_VS_IN_ELEMENT(TEXCOORD, 1, R16G16_FLOAT, 1, ALIGN, PER_VERTEX, 0, true) -META_VS_IN_ELEMENT(COLOR, 0, R8G8B8A8_UNORM, 2, 0, PER_VERTEX, 0, USE_VERTEX_COLOR) -META_VS_IN_ELEMENT(ATTRIBUTE,0, R32G32B32A32_FLOAT,3, 0, PER_INSTANCE, 1, USE_INSTANCING) -META_VS_IN_ELEMENT(ATTRIBUTE,1, R32G32B32A32_FLOAT,3, ALIGN, PER_INSTANCE, 1, USE_INSTANCING) -META_VS_IN_ELEMENT(ATTRIBUTE,2, R32G32B32_FLOAT, 3, ALIGN, PER_INSTANCE, 1, USE_INSTANCING) -META_VS_IN_ELEMENT(ATTRIBUTE,3, R32G32B32_FLOAT, 3, ALIGN, PER_INSTANCE, 1, USE_INSTANCING) -META_VS_IN_ELEMENT(ATTRIBUTE,4, R16G16B16A16_FLOAT,3, ALIGN, PER_INSTANCE, 1, USE_INSTANCING) -VertexOutput VS(ModelInput input) -{ - VertexOutput output; - - // Compute world space vertex position - float4x4 world = GetInstanceTransform(input); - output.WorldPosition = mul(float4(input.Position.xyz, 1), world).xyz; -#if IS_MOTION_VECTORS_PASS - output.PrevWorldPosition = mul(float4(input.Position.xyz, 1), PrevWorldMatrix).xyz; -#endif - - // Compute clip space position - output.Position = mul(float4(output.WorldPosition.xyz, 1), ViewProjectionMatrix); - - // Pass vertex attributes - output.TexCoord = input.TexCoord; -#if USE_VERTEX_COLOR - output.VertexColor = input.Color; -#endif - output.InstanceOrigin = world[3].xyz; -#if USE_INSTANCING - output.LightmapUV = input.LightmapUV * input.InstanceLightmapArea.zw + input.InstanceLightmapArea.xy; - output.InstanceParams = float2(input.InstanceOrigin.w, input.InstanceTransform1.w); -#else - output.LightmapUV = input.LightmapUV * LightmapArea.zw + LightmapArea.xy; - output.InstanceParams = float2(PerInstanceRandom, LODDitherFactor); -#endif - - // Calculate tanget space to world space transformation matrix for unit vectors - float3x3 tangentToLocal = CalcTangentToLocal(input); - float3x3 tangentToWorld = CalcTangentToWorld(world, tangentToLocal); - output.WorldNormal = tangentToWorld[2]; - output.WorldTangent.xyz = tangentToWorld[0]; - output.WorldTangent.w = input.Tangent.w ? -1.0f : +1.0f; - - // Get material input params if need to evaluate any material property -#if USE_POSITION_OFFSET || USE_TESSELLATION || USE_CUSTOM_VERTEX_INTERPOLATORS - MaterialInput materialInput = GetMaterialInput(input, output, tangentToLocal[2].xyz); - Material material = GetMaterialVS(materialInput); -#endif - - // Apply world position offset per-vertex -#if USE_POSITION_OFFSET - output.WorldPosition += material.PositionOffset; - output.Position = mul(float4(output.WorldPosition.xyz, 1), ViewProjectionMatrix); -#endif - - // Get tessalation multiplier (per vertex) -#if USE_TESSELLATION - output.TessellationMultiplier = material.TessellationMultiplier; -#endif - - // Copy interpolants for other shader stages -#if USE_CUSTOM_VERTEX_INTERPOLATORS - output.CustomVSToPS = material.CustomVSToPS; -#endif - - return output; -} - -// Vertex Shader function for Depth Pass -META_VS(IS_SURFACE, FEATURE_LEVEL_ES2) -META_PERMUTATION_1(USE_INSTANCING=0) -META_PERMUTATION_1(USE_INSTANCING=1) -META_VS_IN_ELEMENT(POSITION, 0, R32G32B32_FLOAT, 0, 0, PER_VERTEX, 0, true) -META_VS_IN_ELEMENT(ATTRIBUTE,0, R32G32B32A32_FLOAT,3, 0, PER_INSTANCE, 1, USE_INSTANCING) -META_VS_IN_ELEMENT(ATTRIBUTE,1, R32G32B32A32_FLOAT,3, ALIGN, PER_INSTANCE, 1, USE_INSTANCING) -META_VS_IN_ELEMENT(ATTRIBUTE,2, R32G32B32_FLOAT, 3, ALIGN, PER_INSTANCE, 1, USE_INSTANCING) -META_VS_IN_ELEMENT(ATTRIBUTE,3, R32G32B32_FLOAT, 3, ALIGN, PER_INSTANCE, 1, USE_INSTANCING) -META_VS_IN_ELEMENT(ATTRIBUTE,4, R16G16B16A16_FLOAT,3, ALIGN, PER_INSTANCE, 1, USE_INSTANCING) -float4 VS_Depth(ModelInput_PosOnly input) : SV_Position -{ - float4x4 world = GetInstanceTransform(input); - float3 worldPosition = mul(float4(input.Position.xyz, 1), world).xyz; - float4 position = mul(float4(worldPosition.xyz, 1), ViewProjectionMatrix); - return position; -} - -#if USE_SKINNING - -// The skeletal bones matrix buffer (stored as 4x3, 3 float4 behind each other) -Buffer BoneMatrices : register(t0); - -#if PER_BONE_MOTION_BLUR - -// The skeletal bones matrix buffer from the previous frame -Buffer PrevBoneMatrices : register(t1); - -float3x4 GetPrevBoneMatrix(int index) -{ - float4 a = PrevBoneMatrices[index * 3]; - float4 b = PrevBoneMatrices[index * 3 + 1]; - float4 c = PrevBoneMatrices[index * 3 + 2]; - return float3x4(a, b, c); -} - -float3 SkinPrevPosition(ModelInput_Skinned input) -{ - float4 position = float4(input.Position.xyz, 1); - float3x4 boneMatrix = input.BlendWeights.x * GetPrevBoneMatrix(input.BlendIndices.x); - boneMatrix += input.BlendWeights.y * GetPrevBoneMatrix(input.BlendIndices.y); - boneMatrix += input.BlendWeights.z * GetPrevBoneMatrix(input.BlendIndices.z); - boneMatrix += input.BlendWeights.w * GetPrevBoneMatrix(input.BlendIndices.w); - return mul(boneMatrix, position); -} - -#endif - -// Cached skinning data to avoid multiple calculation -struct SkinningData -{ - float3x4 BlendMatrix; -}; - -// Calculates the transposed transform matrix for the given bone index -float3x4 GetBoneMatrix(int index) -{ - float4 a = BoneMatrices[index * 3]; - float4 b = BoneMatrices[index * 3 + 1]; - float4 c = BoneMatrices[index * 3 + 2]; - return float3x4(a, b, c); -} - -// Calculates the transposed transform matrix for the given vertex (uses blending) -float3x4 GetBoneMatrix(ModelInput_Skinned input) -{ - float3x4 boneMatrix = input.BlendWeights.x * GetBoneMatrix(input.BlendIndices.x); - boneMatrix += input.BlendWeights.y * GetBoneMatrix(input.BlendIndices.y); - boneMatrix += input.BlendWeights.z * GetBoneMatrix(input.BlendIndices.z); - boneMatrix += input.BlendWeights.w * GetBoneMatrix(input.BlendIndices.w); - return boneMatrix; -} - -// Transforms the vertex position by weighted sum of the skinning matrices -float3 SkinPosition(ModelInput_Skinned input, SkinningData data) -{ - float4 position = float4(input.Position.xyz, 1); - return mul(data.BlendMatrix, position); -} - -// Transforms the vertex position by weighted sum of the skinning matrices -float3x3 SkinTangents(ModelInput_Skinned input, SkinningData data) -{ - // Unpack vertex tangent frame - float bitangentSign = input.Tangent.w ? -1.0f : +1.0f; - float3 normal = input.Normal.xyz * 2.0 - 1.0; - float3 tangent = input.Tangent.xyz * 2.0 - 1.0; - - // Apply skinning - tangent = mul(data.BlendMatrix, float4(tangent, 0)); - normal = mul(data.BlendMatrix, float4(normal, 0)); - - float3 bitangent = cross(normal, tangent) * bitangentSign; - return float3x3(tangent, bitangent, normal); -} - -// Vertex Shader function for GBuffers/Depth Pass (skinned mesh rendering) -META_VS(IS_SURFACE, FEATURE_LEVEL_ES2) -META_PERMUTATION_1(USE_SKINNING=1) -META_PERMUTATION_2(USE_SKINNING=1, IS_MOTION_VECTORS_PASS=1) -META_PERMUTATION_3(USE_SKINNING=1, IS_MOTION_VECTORS_PASS=1, PER_BONE_MOTION_BLUR=1) -META_VS_IN_ELEMENT(POSITION, 0, R32G32B32_FLOAT, 0, 0, PER_VERTEX, 0, true) -META_VS_IN_ELEMENT(TEXCOORD, 0, R16G16_FLOAT, 0, ALIGN, PER_VERTEX, 0, true) -META_VS_IN_ELEMENT(NORMAL, 0, R10G10B10A2_UNORM, 0, ALIGN, PER_VERTEX, 0, true) -META_VS_IN_ELEMENT(TANGENT, 0, R10G10B10A2_UNORM, 0, ALIGN, PER_VERTEX, 0, true) -META_VS_IN_ELEMENT(BLENDINDICES, 0, R8G8B8A8_UINT, 0, ALIGN, PER_VERTEX, 0, true) -META_VS_IN_ELEMENT(BLENDWEIGHT, 0, R16G16B16A16_FLOAT,0, ALIGN, PER_VERTEX, 0, true) -VertexOutput VS_Skinned(ModelInput_Skinned input) -{ - VertexOutput output; - - // Perform skinning - SkinningData data; - data.BlendMatrix = GetBoneMatrix(input); - float3 position = SkinPosition(input, data); - float3x3 tangentToLocal = SkinTangents(input, data); - - // Compute world space vertex position - float4x4 world = GetInstanceTransform(input); - output.WorldPosition = mul(float4(position, 1), world).xyz; -#if IS_MOTION_VECTORS_PASS -#if PER_BONE_MOTION_BLUR - float3 prevPosition = SkinPrevPosition(input); - output.PrevWorldPosition = mul(float4(prevPosition, 1), PrevWorldMatrix).xyz; -#else - output.PrevWorldPosition = mul(float4(position, 1), PrevWorldMatrix).xyz; -#endif -#endif - - // Compute clip space position - output.Position = mul(float4(output.WorldPosition.xyz, 1), ViewProjectionMatrix); - - // Pass vertex attributes - output.TexCoord = input.TexCoord; -#if USE_VERTEX_COLOR - output.VertexColor = float4(0, 0, 0, 1); -#endif - output.LightmapUV = float2(0, 0); - output.InstanceOrigin = world[3].xyz; -#if USE_INSTANCING - output.InstanceParams = float2(input.InstanceOrigin.w, input.InstanceTransform1.w); -#else - output.InstanceParams = float2(PerInstanceRandom, LODDitherFactor); -#endif - - // Calculate tanget space to world space transformation matrix for unit vectors - float3x3 tangentToWorld = CalcTangentToWorld(world, tangentToLocal); - output.WorldNormal = tangentToWorld[2]; - output.WorldTangent.xyz = tangentToWorld[0]; - output.WorldTangent.w = input.Tangent.w ? -1.0f : +1.0f; - - // Get material input params if need to evaluate any material property -#if USE_POSITION_OFFSET || USE_TESSELLATION || USE_CUSTOM_VERTEX_INTERPOLATORS - MaterialInput materialInput = GetMaterialInput(output, input.Position.xyz, tangentToLocal[2].xyz); - Material material = GetMaterialVS(materialInput); -#endif - - // Apply world position offset per-vertex -#if USE_POSITION_OFFSET - output.WorldPosition += material.PositionOffset; - output.Position = mul(float4(output.WorldPosition.xyz, 1), ViewProjectionMatrix); -#endif - - // Get tessalation multiplier (per vertex) -#if USE_TESSELLATION - output.TessellationMultiplier = material.TessellationMultiplier; -#endif - - // Copy interpolants for other shader stages -#if USE_CUSTOM_VERTEX_INTERPOLATORS - output.CustomVSToPS = material.CustomVSToPS; -#endif - - return output; -} - -#endif - -#if USE_TESSELLATION - -// Interpolants passed from the hull shader to the domain shader -struct TessalationHSToDS -{ - float4 Position : SV_Position; - float3 WorldPosition : TEXCOORD0; - float2 TexCoord : TEXCOORD1; - float2 LightmapUV : TEXCOORD2; -#if USE_VERTEX_COLOR - half4 VertexColor : COLOR; -#endif - float3 WorldNormal : TEXCOORD3; - float4 WorldTangent : TEXCOORD4; -#if USE_CUSTOM_VERTEX_INTERPOLATORS - float4 CustomVSToPS[CUSTOM_VERTEX_INTERPOLATORS_COUNT] : TEXCOORD9; -#endif - float3 InstanceOrigin : TEXCOORD6; - float2 InstanceParams : TEXCOORD7; -#if IS_MOTION_VECTORS_PASS - float3 PrevWorldPosition : TEXCOORD8; -#endif - float TessellationMultiplier : TESS; -}; - -// Interpolants passed from the domain shader and to the pixel shader -struct TessalationDSToPS -{ - float4 Position : SV_Position; - float3 WorldPosition : TEXCOORD0; - float2 TexCoord : TEXCOORD1; - float2 LightmapUV : TEXCOORD2; -#if USE_VERTEX_COLOR - half4 VertexColor : COLOR; -#endif - float3 WorldNormal : TEXCOORD3; - float4 WorldTangent : TEXCOORD4; -#if USE_CUSTOM_VERTEX_INTERPOLATORS - float4 CustomVSToPS[CUSTOM_VERTEX_INTERPOLATORS_COUNT] : TEXCOORD9; -#endif - float3 InstanceOrigin : TEXCOORD6; - float2 InstanceParams : TEXCOORD7; -#if IS_MOTION_VECTORS_PASS - float3 PrevWorldPosition : TEXCOORD8; -#endif -}; - -MaterialInput GetMaterialInput(TessalationDSToPS input) -{ - MaterialInput result = (MaterialInput)0; - result.WorldPosition = input.WorldPosition; - result.TexCoord = input.TexCoord; -#if USE_LIGHTMAP - result.LightmapUV = input.LightmapUV; -#endif -#if USE_VERTEX_COLOR - result.VertexColor = input.VertexColor; -#endif - result.TBN = CalcTangentBasis(input.WorldNormal, input.WorldTangent); - result.TwoSidedSign = WorldDeterminantSign; - result.InstanceOrigin = input.InstanceOrigin; - result.InstanceParams = input.InstanceParams; - result.SvPosition = input.Position; -#if USE_CUSTOM_VERTEX_INTERPOLATORS - result.CustomVSToPS = input.CustomVSToPS; -#endif - return result; -} - -struct TessalationPatch -{ - float EdgeTessFactor[3] : SV_TessFactor; - float InsideTessFactor : SV_InsideTessFactor; -#if MATERIAL_TESSELLATION == MATERIAL_TESSELLATION_PN - float3 B210 : POSITION4; - float3 B120 : POSITION5; - float3 B021 : POSITION6; - float3 B012 : POSITION7; - float3 B102 : POSITION8; - float3 B201 : POSITION9; - float3 B111 : CENTER; -#endif -}; - -TessalationPatch HS_PatchConstant(InputPatch input) -{ - TessalationPatch output; - - // Average tess factors along edges, and pick an edge tess factor for the interior tessellation - float4 tessellationMultipliers; - tessellationMultipliers.x = 0.5f * (input[1].TessellationMultiplier + input[2].TessellationMultiplier); - tessellationMultipliers.y = 0.5f * (input[2].TessellationMultiplier + input[0].TessellationMultiplier); - tessellationMultipliers.z = 0.5f * (input[0].TessellationMultiplier + input[1].TessellationMultiplier); - tessellationMultipliers.w = 0.333f * (input[0].TessellationMultiplier + input[1].TessellationMultiplier + input[2].TessellationMultiplier); - tessellationMultipliers = clamp(tessellationMultipliers, 1, MAX_TESSELLATION_FACTOR); - - output.EdgeTessFactor[0] = tessellationMultipliers.x; // 1->2 edge - output.EdgeTessFactor[1] = tessellationMultipliers.y; // 2->0 edge - output.EdgeTessFactor[2] = tessellationMultipliers.z; // 0->1 edge - output.InsideTessFactor = tessellationMultipliers.w; - -#if MATERIAL_TESSELLATION == MATERIAL_TESSELLATION_PN - // Calculate PN-Triangle coefficients - // Refer to Vlachos 2001 for the original formula - float3 p1 = input[0].WorldPosition; - float3 p2 = input[1].WorldPosition; - float3 p3 = input[2].WorldPosition; - float3 n1 = input[0].WorldNormal; - float3 n2 = input[1].WorldNormal; - float3 n3 = input[2].WorldNormal; - - // Calculate control points - output.B210 = (2.0f * p1 + p2 - dot((p2 - p1), n1) * n1) / 3.0f; - output.B120 = (2.0f * p2 + p1 - dot((p1 - p2), n2) * n2) / 3.0f; - output.B021 = (2.0f * p2 + p3 - dot((p3 - p2), n2) * n2) / 3.0f; - output.B012 = (2.0f * p3 + p2 - dot((p2 - p3), n3) * n3) / 3.0f; - output.B102 = (2.0f * p3 + p1 - dot((p1 - p3), n3) * n3) / 3.0f; - output.B201 = (2.0f * p1 + p3 - dot((p3 - p1), n1) * n1) / 3.0f; - float3 e = (output.B210 + output.B120 + output.B021 + - output.B012 + output.B102 + output.B201) / 6.0f; - float3 v = (p1 + p2 + p3) / 3.0f; - output.B111 = e + ((e - v) / 2.0f); -#endif - - return output; -} - -META_HS(USE_TESSELLATION, FEATURE_LEVEL_SM5) -META_PERMUTATION_1(IS_MOTION_VECTORS_PASS=0) -META_PERMUTATION_1(IS_MOTION_VECTORS_PASS=1) -META_HS_PATCH(TESSELLATION_IN_CONTROL_POINTS) -[domain("tri")] -[partitioning("fractional_odd")] -[outputtopology("triangle_cw")] -[maxtessfactor(MAX_TESSELLATION_FACTOR)] -[outputcontrolpoints(3)] -[patchconstantfunc("HS_PatchConstant")] -TessalationHSToDS HS(InputPatch input, uint ControlPointID : SV_OutputControlPointID) -{ - TessalationHSToDS output; - - // Pass through shader -#define COPY(thing) output.thing = input[ControlPointID].thing; - COPY(Position); - COPY(WorldPosition); - COPY(TexCoord); - COPY(LightmapUV); -#if USE_VERTEX_COLOR - COPY(VertexColor); -#endif - COPY(WorldNormal); - COPY(WorldTangent); - COPY(InstanceOrigin); - COPY(InstanceParams); -#if IS_MOTION_VECTORS_PASS - COPY(PrevWorldPosition); -#endif - COPY(TessellationMultiplier); -#if USE_CUSTOM_VERTEX_INTERPOLATORS - COPY(CustomVSToPS); -#endif -#undef COPY - - return output; -} - -#if MATERIAL_TESSELLATION == MATERIAL_TESSELLATION_PHONG - -// Orthogonal projection on to plane -float3 ProjectOntoPlane(float3 planeNormal, float3 planePoint, float3 pointToProject) -{ - return pointToProject - dot(pointToProject-planePoint, planeNormal) * planeNormal; -} - -#endif - -META_DS(USE_TESSELLATION, FEATURE_LEVEL_SM5) -META_PERMUTATION_1(IS_MOTION_VECTORS_PASS=0) -META_PERMUTATION_1(IS_MOTION_VECTORS_PASS=1) -[domain("tri")] -TessalationDSToPS DS(TessalationPatch constantData, float3 barycentricCoords : SV_DomainLocation, const OutputPatch input) -{ - TessalationDSToPS output; - - // Get the barycentric coords - float U = barycentricCoords.x; - float V = barycentricCoords.y; - float W = barycentricCoords.z; - - // Interpolate patch attributes to generated vertices -#define INTERPOLATE(thing) output.thing = U * input[0].thing + V * input[1].thing + W * input[2].thing -#define COPY(thing) output.thing = input[0].thing - INTERPOLATE(Position); -#if MATERIAL_TESSELLATION == MATERIAL_TESSELLATION_PN - float UU = U * U; - float VV = V * V; - float WW = W * W; - float UU3 = UU * 3.0f; - float VV3 = VV * 3.0f; - float WW3 = WW * 3.0f; - - // Interpolate using barycentric coordinates and PN Triangle control points - output.WorldPosition = - input[0].WorldPosition * UU * U + - input[1].WorldPosition * VV * V + - input[2].WorldPosition * WW * W + - constantData.B210 * UU3 * V + - constantData.B120 * VV3 * U + - constantData.B021 * VV3 * W + - constantData.B012 * WW3 * V + - constantData.B102 * WW3 * U + - constantData.B201 * UU3 * W + - constantData.B111 * 6.0f * W * U * V; -#if IS_MOTION_VECTORS_PASS - output.PrevWorldPosition = - input[0].PrevWorldPosition * UU * U + - input[1].PrevWorldPosition * VV * V + - input[2].PrevWorldPosition * WW * W + - constantData.B210 * UU3 * V + - constantData.B120 * VV3 * U + - constantData.B021 * VV3 * W + - constantData.B012 * WW3 * V + - constantData.B102 * WW3 * U + - constantData.B201 * UU3 * W + - constantData.B111 * 6.0f * W * U * V; -#endif -#else - INTERPOLATE(WorldPosition); -#if IS_MOTION_VECTORS_PASS - INTERPOLATE(PrevWorldPosition); -#endif -#endif - INTERPOLATE(TexCoord); - INTERPOLATE(LightmapUV); -#if USE_VERTEX_COLOR - INTERPOLATE(VertexColor); -#endif - INTERPOLATE(WorldNormal); - INTERPOLATE(WorldTangent); - COPY(InstanceOrigin); - COPY(InstanceParams); -#if USE_CUSTOM_VERTEX_INTERPOLATORS - UNROLL - for (int i = 0; i < CUSTOM_VERTEX_INTERPOLATORS_COUNT; i++) - { - INTERPOLATE(CustomVSToPS[i]); - } -#endif -#undef INTERPOLATE -#undef COPY - - // Interpolating tangents can unnormalize it, so normalize it - output.WorldNormal = normalize(output.WorldNormal); - output.WorldTangent.xyz = normalize(output.WorldTangent.xyz); - -#if MATERIAL_TESSELLATION == MATERIAL_TESSELLATION_PHONG - // Orthogonal projection in the tangent planes - float3 posProjectedU = ProjectOntoPlane(input[0].WorldNormal, input[0].WorldPosition, output.WorldPosition); - float3 posProjectedV = ProjectOntoPlane(input[1].WorldNormal, input[1].WorldPosition, output.WorldPosition); - float3 posProjectedW = ProjectOntoPlane(input[2].WorldNormal, input[2].WorldPosition, output.WorldPosition); - - // Interpolate the projected points - output.WorldPosition = U * posProjectedU + V * posProjectedV + W * posProjectedW; -#if IS_MOTION_VECTORS_PASS - posProjectedU = ProjectOntoPlane(input[0].WorldNormal, input[0].PrevWorldPosition, output.PrevWorldPosition); - posProjectedV = ProjectOntoPlane(input[1].WorldNormal, input[1].PrevWorldPosition, output.PrevWorldPosition); - posProjectedW = ProjectOntoPlane(input[2].WorldNormal, input[2].PrevWorldPosition, output.PrevWorldPosition); - output.PrevWorldPosition = U * posProjectedU + V * posProjectedV + W * posProjectedW; -#endif -#endif - - // Perform displacement mapping -#if USE_DISPLACEMENT - MaterialInput materialInput = GetMaterialInput(output); - Material material = GetMaterialDS(materialInput); - output.WorldPosition += material.WorldDisplacement; -#if IS_MOTION_VECTORS_PASS - output.PrevWorldPosition += material.WorldDisplacement; -#endif -#endif - - // Recalculate the clip space position - output.Position = mul(float4(output.WorldPosition, 1), ViewProjectionMatrix); - - return output; -} - -#endif - -#if USE_LIGHTMAP - -float3 SampleLightmap(Material material, MaterialInput materialInput) -{ - // Sample lightmaps - float4 lightmap0 = Lightmap0.Sample(SamplerLinearClamp, materialInput.LightmapUV); - float4 lightmap1 = Lightmap1.Sample(SamplerLinearClamp, materialInput.LightmapUV); - float4 lightmap2 = Lightmap2.Sample(SamplerLinearClamp, materialInput.LightmapUV); - - // Unpack H-basis - float3 h0 = float3(lightmap0.x, lightmap1.x, lightmap2.x); - float3 h1 = float3(lightmap0.y, lightmap1.y, lightmap2.y); - float3 h2 = float3(lightmap0.z, lightmap1.z, lightmap2.z); - float3 h3 = float3(lightmap0.w, lightmap1.w, lightmap2.w); - - // Sample baked diffuse irradiance from the H-basis coefficients - float3 normal = material.TangentNormal; -#if MATERIAL_SHADING_MODEL == SHADING_MODEL_FOLIAGE - normal *= material.TangentNormal; -#endif - return GetHBasisIrradiance(normal, h0, h1, h2, h3) / PI; -} - -#endif - -#if USE_DITHERED_LOD_TRANSITION - -void ClipLODTransition(PixelInput input) -{ - float ditherFactor = input.InstanceParams.y; - if (abs(ditherFactor) > 0.001) - { - float randGrid = cos(dot(floor(input.Position.xy), float2(347.83452793, 3343.28371863))); - float randGridFrac = frac(randGrid * 1000.0); - half mask = (ditherFactor < 0.0) ? (ditherFactor + 1.0 > randGridFrac) : (ditherFactor < randGridFrac); - clip(mask - 0.001); - } -} - -#else - -void ClipLODTransition(PixelInput input) -{ -} - -#endif - -// Pixel Shader function for GBuffer Pass -META_PS(true, FEATURE_LEVEL_ES2) -META_PERMUTATION_1(USE_LIGHTMAP=0) -META_PERMUTATION_1(USE_LIGHTMAP=1) -void PS_GBuffer( - in PixelInput input - ,out float4 Light : SV_Target0 -#if MATERIAL_DOMAIN == MATERIAL_DOMAIN_SURFACE - // GBuffer - ,out float4 RT0 : SV_Target1 - ,out float4 RT1 : SV_Target2 - ,out float4 RT2 : SV_Target3 -#if USE_GBUFFER_CUSTOM_DATA - ,out float4 RT3 : SV_Target4 -#endif -#endif - ) -{ - Light = 0; - - // LOD masking - ClipLODTransition(input); - - // Get material parameters - MaterialInput materialInput = GetMaterialInput(input); - Material material = GetMaterialPS(materialInput); - -#if MATERIAL_DOMAIN == MATERIAL_DOMAIN_SURFACE - - // Masking -#if MATERIAL_MASKED - clip(material.Mask - MATERIAL_MASK_THRESHOLD); -#endif - -#if USE_LIGHTMAP - - float3 diffuseColor = GetDiffuseColor(material.Color, material.Metalness); - float3 specularColor = GetSpecularColor(material.Color, material.Specular, material.Metalness); - - // Sample lightmap - float3 diffuseIndirectLighting = SampleLightmap(material, materialInput); - - // Apply static indirect light - Light.rgb = diffuseColor * diffuseIndirectLighting * AOMultiBounce(material.AO, diffuseColor); - -#endif - - // Pack material properties to GBuffer - RT0 = float4(material.Color, material.AO); - RT1 = float4(material.WorldNormal * 0.5 + 0.5, MATERIAL_SHADING_MODEL * (1.0 / 3.0)); - RT2 = float4(material.Roughness, material.Metalness, material.Specular, 0); - - // Custom data -#if USE_GBUFFER_CUSTOM_DATA -#if MATERIAL_SHADING_MODEL == SHADING_MODEL_SUBSURFACE - RT3 = float4(material.SubsurfaceColor, material.Opacity); -#elif MATERIAL_SHADING_MODEL == SHADING_MODEL_FOLIAGE - RT3 = float4(material.SubsurfaceColor, material.Opacity); -#else - RT3 = float4(0, 0, 0, 0); -#endif -#endif - -#endif - - // Add light emission -#if USE_EMISSIVE - Light.rgb += material.Emissive; -#endif -} - -// Pixel Shader function for Depth Pass -META_PS(IS_SURFACE, FEATURE_LEVEL_ES2) -void PS_Depth(PixelInput input -#if GLSL - , out float4 OutColor : SV_Target0 -#endif - ) -{ - // LOD masking - ClipLODTransition(input); - -#if MATERIAL_MASKED - // Perform per pixel clipping if material requries it - MaterialInput materialInput = GetMaterialInput(input); - Material material = GetMaterialPS(materialInput); - clip(material.Mask - MATERIAL_MASK_THRESHOLD); -#endif - -#if GLSL - OutColor = 0; -#endif -} - -// Pixel Shader function for Motion Vectors Pass -META_PS(true, FEATURE_LEVEL_ES2) -META_PERMUTATION_1(IS_MOTION_VECTORS_PASS=1) -float4 PS_MotionVectors(PixelInput input) : SV_Target0 -{ -#if IS_MOTION_VECTORS_PASS - // LOD masking - ClipLODTransition(input); - -#if MATERIAL_MASKED - // Perform per pixel clipping if material requries it - MaterialInput materialInput = GetMaterialInput(input); - Material material = GetMaterialPS(materialInput); - clip(material.Mask - MATERIAL_MASK_THRESHOLD); -#endif - - // Calculate this and previosu frame pixel locations in clip space - float4 prevClipPos = mul(float4(input.PrevWorldPosition, 1), PrevViewProjectionMatrix); - float4 curClipPos = mul(float4(input.WorldPosition, 1), ViewProjectionMatrix); - float2 prevHPos = prevClipPos.xy / prevClipPos.w; - float2 curHPos = curClipPos.xy / curClipPos.w; - - // Revert temporal jitter offset - prevHPos -= TemporalAAJitter.zw; - curHPos -= TemporalAAJitter.xy; - - // Clip Space -> UV Space - float2 vPosPrev = prevHPos.xy * 0.5f + 0.5f; - float2 vPosCur = curHPos.xy * 0.5f + 0.5f; - vPosPrev.y = 1.0 - vPosPrev.y; - vPosCur.y = 1.0 - vPosCur.y; - - // Calculate per-pixel motion vector - return float4(vPosCur - vPosPrev, 0, 1); -#else - return float4(0, 0, 0, 1); -#endif -} diff --git a/Content/Editor/MaterialTemplates/SurfaceForward.shader b/Content/Editor/MaterialTemplates/SurfaceForward.shader deleted file mode 100644 index 7a97e5e5a..000000000 --- a/Content/Editor/MaterialTemplates/SurfaceForward.shader +++ /dev/null @@ -1,1011 +0,0 @@ -// File generated by Flax Materials Editor -// Version: @0 - -#define MATERIAL 1 -#define MAX_LOCAL_LIGHTS 4 -@3 - -#include "./Flax/Common.hlsl" -#include "./Flax/MaterialCommon.hlsl" -#include "./Flax/GBufferCommon.hlsl" -#include "./Flax/LightingCommon.hlsl" -#if USE_REFLECTIONS -#include "./Flax/ReflectionsCommon.hlsl" -#endif -#include "./Flax/Lighting.hlsl" -#include "./Flax/ShadowsSampling.hlsl" -#include "./Flax/ExponentialHeightFog.hlsl" -@7 -// Primary constant buffer (with additional material parameters) -META_CB_BEGIN(0, Data) -float4x4 ViewProjectionMatrix; -float4x4 WorldMatrix; -float4x4 ViewMatrix; -float4x4 PrevViewProjectionMatrix; -float4x4 PrevWorldMatrix; -float3 ViewPos; -float ViewFar; -float3 ViewDir; -float TimeParam; -float4 ViewInfo; -float4 ScreenSize; -float4 LightmapArea; -float3 WorldInvScale; -float WorldDeterminantSign; -float2 Dummy0; -float LODDitherFactor; -float PerInstanceRandom; -float3 GeometrySize; -float Dummy1; -@1META_CB_END - -// Secondary constantant buffer (for lighting) -META_CB_BEGIN(1, LightingData) -LightData DirectionalLight; -LightShadowData DirectionalLightShadow; -LightData SkyLight; -ProbeData EnvironmentProbe; -ExponentialHeightFogData ExponentialHeightFog; -float3 Dummy2; -uint LocalLightsCount; -LightData LocalLights[MAX_LOCAL_LIGHTS]; -META_CB_END - -DECLARE_LIGHTSHADOWDATA_ACCESS(DirectionalLightShadow); - -// Shader resources -TextureCube EnvProbe : register(t0); -TextureCube SkyLightTexture : register(t1); -Texture2DArray DirectionalLightShadowMap : register(t2); -@2 - -// Interpolants passed from the vertex shader -struct VertexOutput -{ - float4 Position : SV_Position; - float3 WorldPosition : TEXCOORD0; - float2 TexCoord : TEXCOORD1; - float2 LightmapUV : TEXCOORD2; -#if USE_VERTEX_COLOR - half4 VertexColor : COLOR; -#endif - float3x3 TBN : TEXCOORD3; -#if USE_CUSTOM_VERTEX_INTERPOLATORS - float4 CustomVSToPS[CUSTOM_VERTEX_INTERPOLATORS_COUNT] : TEXCOORD9; -#endif - float3 InstanceOrigin : TEXCOORD6; - float2 InstanceParams : TEXCOORD7; // x-PerInstanceRandom, y-LODDitherFactor -#if USE_TESSELLATION - float TessellationMultiplier : TESS; -#endif -}; - -// Interpolants passed to the pixel shader -struct PixelInput -{ - float4 Position : SV_Position; - float3 WorldPosition : TEXCOORD0; - float2 TexCoord : TEXCOORD1; - float2 LightmapUV : TEXCOORD2; -#if USE_VERTEX_COLOR - half4 VertexColor : COLOR; -#endif - float3x3 TBN : TEXCOORD3; -#if USE_CUSTOM_VERTEX_INTERPOLATORS - float4 CustomVSToPS[CUSTOM_VERTEX_INTERPOLATORS_COUNT] : TEXCOORD9; -#endif - float3 InstanceOrigin : TEXCOORD6; - float2 InstanceParams : TEXCOORD7; // x-PerInstanceRandom, y-LODDitherFactor - bool IsFrontFace : SV_IsFrontFace; -}; - -// Material properties generation input -struct MaterialInput -{ - float3 WorldPosition; - float TwoSidedSign; - float2 TexCoord; -#if USE_LIGHTMAP - float2 LightmapUV; -#endif -#if USE_VERTEX_COLOR - half4 VertexColor; -#endif - float3x3 TBN; - float4 SvPosition; - float3 PreSkinnedPosition; - float3 PreSkinnedNormal; - float3 InstanceOrigin; - float2 InstanceParams; -#if USE_INSTANCING - float3 InstanceTransform1; - float3 InstanceTransform2; - float3 InstanceTransform3; -#endif -#if USE_CUSTOM_VERTEX_INTERPOLATORS - float4 CustomVSToPS[CUSTOM_VERTEX_INTERPOLATORS_COUNT]; -#endif -}; - -MaterialInput GetMaterialInput(ModelInput input, VertexOutput output, float3 localNormal) -{ - MaterialInput result = (MaterialInput)0; - result.WorldPosition = output.WorldPosition; - result.TexCoord = output.TexCoord; -#if USE_LIGHTMAP - result.LightmapUV = output.LightmapUV; -#endif -#if USE_VERTEX_COLOR - result.VertexColor = output.VertexColor; -#endif - result.TBN = output.TBN; - result.TwoSidedSign = WorldDeterminantSign; - result.SvPosition = output.Position; - result.PreSkinnedPosition = input.Position.xyz; - result.PreSkinnedNormal = localNormal; -#if USE_INSTANCING - result.InstanceOrigin = input.InstanceOrigin.xyz; - result.InstanceParams = float2(input.InstanceOrigin.w, input.InstanceTransform1.w); - result.InstanceTransform1 = input.InstanceTransform1.xyz; - result.InstanceTransform2 = input.InstanceTransform2.xyz; - result.InstanceTransform3 = input.InstanceTransform3.xyz; -#else - result.InstanceOrigin = WorldMatrix[3].xyz; - result.InstanceParams = float2(PerInstanceRandom, LODDitherFactor); -#endif - return result; -} - -MaterialInput GetMaterialInput(VertexOutput output, float3 localPosition, float3 localNormal) -{ - MaterialInput result = (MaterialInput)0; - result.WorldPosition = output.WorldPosition; - result.TexCoord = output.TexCoord; -#if USE_LIGHTMAP - result.LightmapUV = output.LightmapUV; -#endif -#if USE_VERTEX_COLOR - result.VertexColor = output.VertexColor; -#endif - result.TBN = output.TBN; - result.TwoSidedSign = WorldDeterminantSign; - result.InstanceOrigin = WorldMatrix[3].xyz; - result.InstanceParams = float2(PerInstanceRandom, LODDitherFactor); - result.SvPosition = output.Position; - result.PreSkinnedPosition = localPosition; - result.PreSkinnedNormal = localNormal; - return result; -} - -MaterialInput GetMaterialInput(PixelInput input) -{ - MaterialInput result = (MaterialInput)0; - result.WorldPosition = input.WorldPosition; - result.TexCoord = input.TexCoord; -#if USE_LIGHTMAP - result.LightmapUV = input.LightmapUV; -#endif -#if USE_VERTEX_COLOR - result.VertexColor = input.VertexColor; -#endif - result.TBN = input.TBN; - result.TwoSidedSign = WorldDeterminantSign * (input.IsFrontFace ? 1.0 : -1.0); - result.InstanceOrigin = input.InstanceOrigin; - result.InstanceParams = input.InstanceParams; - result.SvPosition = input.Position; -#if USE_CUSTOM_VERTEX_INTERPOLATORS - result.CustomVSToPS = input.CustomVSToPS; -#endif - return result; -} - -#if USE_INSTANCING -#define INSTANCE_TRANS_WORLD float4x4(float4(input.InstanceTransform1.xyz, 0.0f), float4(input.InstanceTransform2.xyz, 0.0f), float4(input.InstanceTransform3.xyz, 0.0f), float4(input.InstanceOrigin.xyz, 1.0f)) -#else -#define INSTANCE_TRANS_WORLD WorldMatrix -#endif - -// Gets the local to world transform matrix (supports instancing) -float4x4 GetInstanceTransform(ModelInput input) -{ - return INSTANCE_TRANS_WORLD; -} -float4x4 GetInstanceTransform(ModelInput_Skinned input) -{ - return INSTANCE_TRANS_WORLD; -} -float4x4 GetInstanceTransform(MaterialInput input) -{ - return INSTANCE_TRANS_WORLD; -} - -// Removes the scale vector from the local to world transformation matrix (supports instancing) -float3x3 RemoveScaleFromLocalToWorld(float3x3 localToWorld) -{ -#if USE_INSTANCING - // Extract per axis scales from localToWorld transform - float scaleX = length(localToWorld[0]); - float scaleY = length(localToWorld[1]); - float scaleZ = length(localToWorld[2]); - float3 invScale = float3( - scaleX > 0.00001f ? 1.0f / scaleX : 0.0f, - scaleY > 0.00001f ? 1.0f / scaleY : 0.0f, - scaleZ > 0.00001f ? 1.0f / scaleZ : 0.0f); -#else - float3 invScale = WorldInvScale; -#endif - localToWorld[0] *= invScale.x; - localToWorld[1] *= invScale.y; - localToWorld[2] *= invScale.z; - return localToWorld; -} - -// Transforms a vector from tangent space to world space -float3 TransformTangentVectorToWorld(MaterialInput input, float3 tangentVector) -{ - return mul(tangentVector, input.TBN); -} - -// Transforms a vector from world space to tangent space -float3 TransformWorldVectorToTangent(MaterialInput input, float3 worldVector) -{ - return mul(input.TBN, worldVector); -} - -// Transforms a vector from world space to view space -float3 TransformWorldVectorToView(MaterialInput input, float3 worldVector) -{ - return mul(worldVector, (float3x3)ViewMatrix); -} - -// Transforms a vector from view space to world space -float3 TransformViewVectorToWorld(MaterialInput input, float3 viewVector) -{ - return mul((float3x3)ViewMatrix, viewVector); -} - -// Transforms a vector from local space to world space -float3 TransformLocalVectorToWorld(MaterialInput input, float3 localVector) -{ - float3x3 localToWorld = (float3x3)GetInstanceTransform(input); - //localToWorld = RemoveScaleFromLocalToWorld(localToWorld); - return mul(localVector, localToWorld); -} - -// Transforms a vector from local space to world space -float3 TransformWorldVectorToLocal(MaterialInput input, float3 worldVector) -{ - float3x3 localToWorld = (float3x3)GetInstanceTransform(input); - //localToWorld = RemoveScaleFromLocalToWorld(localToWorld); - return mul(localToWorld, worldVector); -} - -// Gets the current object position (supports instancing) -float3 GetObjectPosition(MaterialInput input) -{ - return input.InstanceOrigin.xyz; -} - -// Gets the current object size (supports instancing) -float3 GetObjectSize(MaterialInput input) -{ - float4x4 world = GetInstanceTransform(input); - return GeometrySize * float3(world._m00, world._m11, world._m22); -} - -// Get the current object random value (supports instancing) -float GetPerInstanceRandom(MaterialInput input) -{ - return input.InstanceParams.x; -} - -// Get the current object LOD transition dither factor (supports instancing) -float GetLODDitherFactor(MaterialInput input) -{ -#if USE_DITHERED_LOD_TRANSITION - return input.InstanceParams.y; -#else - return 0; -#endif -} - -// Gets the interpolated vertex color (in linear space) -float4 GetVertexColor(MaterialInput input) -{ -#if USE_VERTEX_COLOR - return input.VertexColor; -#else - return 1; -#endif -} - -// Get material properties function (for vertex shader) -Material GetMaterialVS(MaterialInput input) -{ -@5 -} - -// Get material properties function (for domain shader) -Material GetMaterialDS(MaterialInput input) -{ -@6 -} - -// Get material properties function (for pixel shader) -Material GetMaterialPS(MaterialInput input) -{ -@4 -} - -// Fix line for errors/warnings for shader code from template -#line 1000 - -// Calculates the transform matrix from mesh tangent space to local space -half3x3 CalcTangentToLocal(ModelInput input) -{ - float bitangentSign = input.Tangent.w ? -1.0f : +1.0f; - float3 normal = input.Normal.xyz * 2.0 - 1.0; - float3 tangent = input.Tangent.xyz * 2.0 - 1.0; - float3 bitangent = cross(normal, tangent) * bitangentSign; - return float3x3(tangent, bitangent, normal); -} - -half3x3 CalcTangentToWorld(in float4x4 world, in half3x3 tangentToLocal) -{ - half3x3 localToWorld = RemoveScaleFromLocalToWorld((float3x3)world); - return mul(tangentToLocal, localToWorld); -} - -// Vertex Shader function for Forward/Depth Pass -META_VS(IS_SURFACE, FEATURE_LEVEL_ES2) -META_PERMUTATION_1(USE_INSTANCING=0) -META_PERMUTATION_1(USE_INSTANCING=1) -META_VS_IN_ELEMENT(POSITION, 0, R32G32B32_FLOAT, 0, 0, PER_VERTEX, 0, true) -META_VS_IN_ELEMENT(TEXCOORD, 0, R16G16_FLOAT, 1, 0, PER_VERTEX, 0, true) -META_VS_IN_ELEMENT(NORMAL, 0, R10G10B10A2_UNORM, 1, ALIGN, PER_VERTEX, 0, true) -META_VS_IN_ELEMENT(TANGENT, 0, R10G10B10A2_UNORM, 1, ALIGN, PER_VERTEX, 0, true) -META_VS_IN_ELEMENT(TEXCOORD, 1, R16G16_FLOAT, 1, ALIGN, PER_VERTEX, 0, true) -META_VS_IN_ELEMENT(COLOR, 0, R8G8B8A8_UNORM, 2, 0, PER_VERTEX, 0, USE_VERTEX_COLOR) -META_VS_IN_ELEMENT(ATTRIBUTE,0, R32G32B32A32_FLOAT,3, 0, PER_INSTANCE, 1, USE_INSTANCING) -META_VS_IN_ELEMENT(ATTRIBUTE,1, R32G32B32A32_FLOAT,3, ALIGN, PER_INSTANCE, 1, USE_INSTANCING) -META_VS_IN_ELEMENT(ATTRIBUTE,2, R32G32B32_FLOAT, 3, ALIGN, PER_INSTANCE, 1, USE_INSTANCING) -META_VS_IN_ELEMENT(ATTRIBUTE,3, R32G32B32_FLOAT, 3, ALIGN, PER_INSTANCE, 1, USE_INSTANCING) -META_VS_IN_ELEMENT(ATTRIBUTE,4, R16G16B16A16_FLOAT,3, ALIGN, PER_INSTANCE, 1, USE_INSTANCING) -VertexOutput VS(ModelInput input) -{ - VertexOutput output; - - // Compute world space vertex position - float4x4 world = GetInstanceTransform(input); - output.WorldPosition = mul(float4(input.Position.xyz, 1), world).xyz; - - // Compute clip space position - output.Position = mul(float4(output.WorldPosition.xyz, 1), ViewProjectionMatrix); - - // Pass vertex attributes - output.TexCoord = input.TexCoord; -#if USE_VERTEX_COLOR - output.VertexColor = input.Color; -#endif -#if USE_INSTANCING - output.LightmapUV = input.LightmapUV * input.InstanceLightmapArea.zw + input.InstanceLightmapArea.xy; - output.InstanceOrigin = world[3].xyz; - output.InstanceParams = float2(input.InstanceOrigin.w, input.InstanceTransform1.w); -#else - output.LightmapUV = input.LightmapUV * LightmapArea.zw + LightmapArea.xy; - output.InstanceOrigin = WorldMatrix[3].xyz; - output.InstanceParams = float2(PerInstanceRandom, LODDitherFactor); -#endif - - // Calculate tanget space to world space transformation matrix for unit vectors - half3x3 tangentToLocal = CalcTangentToLocal(input); - half3x3 tangentToWorld = CalcTangentToWorld(world, tangentToLocal); - output.TBN = tangentToWorld; - - // Get material input params if need to evaluate any material property -#if USE_POSITION_OFFSET || USE_TESSELLATION || USE_CUSTOM_VERTEX_INTERPOLATORS - MaterialInput materialInput = GetMaterialInput(input, output, tangentToLocal[2].xyz); - Material material = GetMaterialVS(materialInput); -#endif - - // Apply world position offset per-vertex -#if USE_POSITION_OFFSET - output.WorldPosition += material.PositionOffset; - output.Position = mul(float4(output.WorldPosition.xyz, 1), ViewProjectionMatrix); -#endif - - // Get tessalation multiplier (per vertex) -#if USE_TESSELLATION - output.TessellationMultiplier = material.TessellationMultiplier; -#endif - - // Copy interpolants for other shader stages -#if USE_CUSTOM_VERTEX_INTERPOLATORS - output.CustomVSToPS = material.CustomVSToPS; -#endif - - return output; -} - -#if USE_SKINNING - -// The skeletal bones matrix buffer (stored as 4x3, 3 float4 behind each other) -Buffer BoneMatrices : register(t0); - -#if PER_BONE_MOTION_BLUR - -// The skeletal bones matrix buffer from the previous frame -Buffer PrevBoneMatrices : register(t1); - -float3x4 GetPrevBoneMatrix(int index) -{ - float4 a = PrevBoneMatrices[index * 3]; - float4 b = PrevBoneMatrices[index * 3 + 1]; - float4 c = PrevBoneMatrices[index * 3 + 2]; - return float3x4(a, b, c); -} - -float3 SkinPrevPosition(ModelInput_Skinned input) -{ - float4 position = float4(input.Position.xyz, 1); - float3x4 boneMatrix = input.BlendWeights.x * GetPrevBoneMatrix(input.BlendIndices.x); - boneMatrix += input.BlendWeights.y * GetPrevBoneMatrix(input.BlendIndices.y); - boneMatrix += input.BlendWeights.z * GetPrevBoneMatrix(input.BlendIndices.z); - boneMatrix += input.BlendWeights.w * GetPrevBoneMatrix(input.BlendIndices.w); - return mul(boneMatrix, position); -} - -#endif - -// Cached skinning data to avoid multiple calculation -struct SkinningData -{ - float3x4 BlendMatrix; -}; - -// Calculates the transposed transform matrix for the given bone index -float3x4 GetBoneMatrix(int index) -{ - float4 a = BoneMatrices[index * 3]; - float4 b = BoneMatrices[index * 3 + 1]; - float4 c = BoneMatrices[index * 3 + 2]; - return float3x4(a, b, c); -} - -// Calculates the transposed transform matrix for the given vertex (uses blending) -float3x4 GetBoneMatrix(ModelInput_Skinned input) -{ - float3x4 boneMatrix = input.BlendWeights.x * GetBoneMatrix(input.BlendIndices.x); - boneMatrix += input.BlendWeights.y * GetBoneMatrix(input.BlendIndices.y); - boneMatrix += input.BlendWeights.z * GetBoneMatrix(input.BlendIndices.z); - boneMatrix += input.BlendWeights.w * GetBoneMatrix(input.BlendIndices.w); - return boneMatrix; -} - -// Transforms the vertex position by weighted sum of the skinning matrices -float3 SkinPosition(ModelInput_Skinned input, SkinningData data) -{ - float4 position = float4(input.Position.xyz, 1); - return mul(data.BlendMatrix, position); -} - -// Transforms the vertex position by weighted sum of the skinning matrices -half3x3 SkinTangents(ModelInput_Skinned input, SkinningData data) -{ - // Unpack vertex tangent frame - float bitangentSign = input.Tangent.w ? -1.0f : +1.0f; - float3 normal = input.Normal.xyz * 2.0 - 1.0; - float3 tangent = input.Tangent.xyz * 2.0 - 1.0; - - // Apply skinning - tangent = mul(data.BlendMatrix, float4(tangent, 0)); - normal = mul(data.BlendMatrix, float4(normal, 0)); - - float3 bitangent = cross(normal, tangent) * bitangentSign; - return half3x3(tangent, bitangent, normal); -} - -// Vertex Shader function for Forward/Depth Pass (skinned mesh rendering) -META_VS(IS_SURFACE, FEATURE_LEVEL_ES2) -META_PERMUTATION_1(USE_SKINNING=1) -META_VS_IN_ELEMENT(POSITION, 0, R32G32B32_FLOAT, 0, 0, PER_VERTEX, 0, true) -META_VS_IN_ELEMENT(TEXCOORD, 0, R16G16_FLOAT, 0, ALIGN, PER_VERTEX, 0, true) -META_VS_IN_ELEMENT(NORMAL, 0, R10G10B10A2_UNORM, 0, ALIGN, PER_VERTEX, 0, true) -META_VS_IN_ELEMENT(TANGENT, 0, R10G10B10A2_UNORM, 0, ALIGN, PER_VERTEX, 0, true) -META_VS_IN_ELEMENT(BLENDINDICES, 0, R8G8B8A8_UINT, 0, ALIGN, PER_VERTEX, 0, true) -META_VS_IN_ELEMENT(BLENDWEIGHT, 0, R16G16B16A16_FLOAT,0, ALIGN, PER_VERTEX, 0, true) -VertexOutput VS_Skinned(ModelInput_Skinned input) -{ - VertexOutput output; - - // Perform skinning - SkinningData data; - data.BlendMatrix = GetBoneMatrix(input); - float3 position = SkinPosition(input, data); - half3x3 tangentToLocal = SkinTangents(input, data); - - // Compute world space vertex position - float4x4 world = GetInstanceTransform(input); - output.WorldPosition = mul(float4(position, 1), world).xyz; - - // Compute clip space position - output.Position = mul(float4(output.WorldPosition.xyz, 1), ViewProjectionMatrix); - - // Pass vertex attributes - output.TexCoord = input.TexCoord; -#if USE_VERTEX_COLOR - output.VertexColor = float4(0, 0, 0, 1); -#endif - output.LightmapUV = float2(0, 0); -#if USE_INSTANCING - output.InstanceOrigin = world[3].xyz; - output.InstanceParams = float2(input.InstanceOrigin.w, input.InstanceTransform1.w); -#else - output.InstanceOrigin = WorldMatrix[3].xyz; - output.InstanceParams = float2(PerInstanceRandom, LODDitherFactor); -#endif - - // Calculate tanget space to world space transformation matrix for unit vectors - half3x3 tangentToWorld = CalcTangentToWorld(world, tangentToLocal); - output.TBN = tangentToWorld; - - // Get material input params if need to evaluate any material property -#if USE_POSITION_OFFSET || USE_TESSELLATION || USE_CUSTOM_VERTEX_INTERPOLATORS - MaterialInput materialInput = GetMaterialInput(output, input.Position.xyz, tangentToLocal[2].xyz); - Material material = GetMaterialVS(materialInput); -#endif - - // Apply world position offset per-vertex -#if USE_POSITION_OFFSET - output.WorldPosition += material.PositionOffset; - output.Position = mul(float4(output.WorldPosition.xyz, 1), ViewProjectionMatrix); -#endif - - // Get tessalation multiplier (per vertex) -#if USE_TESSELLATION - output.TessellationMultiplier = material.TessellationMultiplier; -#endif - - // Copy interpolants for other shader stages -#if USE_CUSTOM_VERTEX_INTERPOLATORS - output.CustomVSToPS = material.CustomVSToPS; -#endif - - return output; -} - -#endif - -#if USE_TESSELLATION - -// Interpolants passed from the hull shader to the domain shader -struct TessalationHSToDS -{ - float4 Position : SV_Position; - float3 WorldPosition : TEXCOORD0; - float2 TexCoord : TEXCOORD1; - float2 LightmapUV : TEXCOORD2; -#if USE_VERTEX_COLOR - half4 VertexColor : COLOR; -#endif - float3x3 TBN : TEXCOORD3; -#if USE_CUSTOM_VERTEX_INTERPOLATORS - float4 CustomVSToPS[CUSTOM_VERTEX_INTERPOLATORS_COUNT] : TEXCOORD9; -#endif - float3 InstanceOrigin : TEXCOORD6; - float2 InstanceParams : TEXCOORD7; - float TessellationMultiplier : TESS; -}; - -// Interpolants passed from the domain shader and to the pixel shader -struct TessalationDSToPS -{ - float4 Position : SV_Position; - float3 WorldPosition : TEXCOORD0; - float2 TexCoord : TEXCOORD1; - float2 LightmapUV : TEXCOORD2; -#if USE_VERTEX_COLOR - half4 VertexColor : COLOR; -#endif - float3x3 TBN : TEXCOORD3; -#if USE_CUSTOM_VERTEX_INTERPOLATORS - float4 CustomVSToPS[CUSTOM_VERTEX_INTERPOLATORS_COUNT] : TEXCOORD9; -#endif - float3 InstanceOrigin : TEXCOORD6; - float2 InstanceParams : TEXCOORD7; -}; - -MaterialInput GetMaterialInput(TessalationDSToPS input) -{ - MaterialInput result = (MaterialInput)0; - result.WorldPosition = input.WorldPosition; - result.TexCoord = input.TexCoord; -#if USE_LIGHTMAP - result.LightmapUV = input.LightmapUV; -#endif -#if USE_VERTEX_COLOR - result.VertexColor = input.VertexColor; -#endif - result.TBN = input.TBN; - result.TwoSidedSign = WorldDeterminantSign; - result.InstanceOrigin = input.InstanceOrigin; - result.InstanceParams = input.InstanceParams; - result.SvPosition = input.Position; -#if USE_CUSTOM_VERTEX_INTERPOLATORS - result.CustomVSToPS = input.CustomVSToPS; -#endif - return result; -} - -struct TessalationPatch -{ - float EdgeTessFactor[3] : SV_TessFactor; - float InsideTessFactor : SV_InsideTessFactor; -#if MATERIAL_TESSELLATION == MATERIAL_TESSELLATION_PN - float3 B210 : POSITION4; - float3 B120 : POSITION5; - float3 B021 : POSITION6; - float3 B012 : POSITION7; - float3 B102 : POSITION8; - float3 B201 : POSITION9; - float3 B111 : CENTER; -#endif -}; - -TessalationPatch HS_PatchConstant(InputPatch input) -{ - TessalationPatch output; - - // Average tess factors along edges, and pick an edge tess factor for the interior tessellation - float4 tessellationMultipliers; - tessellationMultipliers.x = 0.5f * (input[1].TessellationMultiplier + input[2].TessellationMultiplier); - tessellationMultipliers.y = 0.5f * (input[2].TessellationMultiplier + input[0].TessellationMultiplier); - tessellationMultipliers.z = 0.5f * (input[0].TessellationMultiplier + input[1].TessellationMultiplier); - tessellationMultipliers.w = 0.333f * (input[0].TessellationMultiplier + input[1].TessellationMultiplier + input[2].TessellationMultiplier); - tessellationMultipliers = clamp(tessellationMultipliers, 1, MAX_TESSELLATION_FACTOR); - - output.EdgeTessFactor[0] = tessellationMultipliers.x; // 1->2 edge - output.EdgeTessFactor[1] = tessellationMultipliers.y; // 2->0 edge - output.EdgeTessFactor[2] = tessellationMultipliers.z; // 0->1 edge - output.InsideTessFactor = tessellationMultipliers.w; - -#if MATERIAL_TESSELLATION == MATERIAL_TESSELLATION_PN - // Calculate PN-Triangle coefficients - // Refer to Vlachos 2001 for the original formula - float3 p1 = input[0].WorldPosition; - float3 p2 = input[1].WorldPosition; - float3 p3 = input[2].WorldPosition; - float3 n1 = input[0].TBN[2]; - float3 n2 = input[1].TBN[2]; - float3 n3 = input[2].TBN[2]; - - // Calculate control points - output.B210 = (2.0f * p1 + p2 - dot((p2 - p1), n1) * n1) / 3.0f; - output.B120 = (2.0f * p2 + p1 - dot((p1 - p2), n2) * n2) / 3.0f; - output.B021 = (2.0f * p2 + p3 - dot((p3 - p2), n2) * n2) / 3.0f; - output.B012 = (2.0f * p3 + p2 - dot((p2 - p3), n3) * n3) / 3.0f; - output.B102 = (2.0f * p3 + p1 - dot((p1 - p3), n3) * n3) / 3.0f; - output.B201 = (2.0f * p1 + p3 - dot((p3 - p1), n1) * n1) / 3.0f; - float3 e = (output.B210 + output.B120 + output.B021 + - output.B012 + output.B102 + output.B201) / 6.0f; - float3 v = (p1 + p2 + p3) / 3.0f; - output.B111 = e + ((e - v) / 2.0f); -#endif - - return output; -} - -META_HS(USE_TESSELLATION, FEATURE_LEVEL_SM5) -META_HS_PATCH(TESSELLATION_IN_CONTROL_POINTS) -[domain("tri")] -[partitioning("fractional_odd")] -[outputtopology("triangle_cw")] -[maxtessfactor(MAX_TESSELLATION_FACTOR)] -[outputcontrolpoints(3)] -[patchconstantfunc("HS_PatchConstant")] -TessalationHSToDS HS(InputPatch input, uint ControlPointID : SV_OutputControlPointID) -{ - TessalationHSToDS output; - - // Pass through shader -#define COPY(thing) output.thing = input[ControlPointID].thing; - COPY(Position); - COPY(WorldPosition); - COPY(TexCoord); - COPY(LightmapUV); -#if USE_VERTEX_COLOR - COPY(VertexColor); -#endif - COPY(TBN); - COPY(InstanceOrigin); - COPY(InstanceParams); - COPY(TessellationMultiplier); -#if USE_CUSTOM_VERTEX_INTERPOLATORS - COPY(CustomVSToPS); -#endif -#undef COPY - - return output; -} - -#if MATERIAL_TESSELLATION == MATERIAL_TESSELLATION_PHONG - -// Orthogonal projection on to plane -float3 ProjectOntoPlane(float3 planeNormal, float3 planePoint, float3 pointToProject) -{ - return pointToProject - dot(pointToProject-planePoint, planeNormal) * planeNormal; -} - -#endif - -META_DS(USE_TESSELLATION, FEATURE_LEVEL_SM5) -[domain("tri")] -TessalationDSToPS DS(TessalationPatch constantData, float3 barycentricCoords : SV_DomainLocation, const OutputPatch input) -{ - TessalationDSToPS output; - - // Get the barycentric coords - float U = barycentricCoords.x; - float V = barycentricCoords.y; - float W = barycentricCoords.z; - - // Interpolate patch attributes to generated vertices -#define INTERPOLATE(thing) output.thing = U * input[0].thing + V * input[1].thing + W * input[2].thing -#define COPY(thing) output.thing = input[0].thing - INTERPOLATE(Position); -#if MATERIAL_TESSELLATION == MATERIAL_TESSELLATION_PN - float UU = U * U; - float VV = V * V; - float WW = W * W; - float UU3 = UU * 3.0f; - float VV3 = VV * 3.0f; - float WW3 = WW * 3.0f; - - // Interpolate using barycentric coordinates and PN Triangle control points - output.WorldPosition = - input[0].WorldPosition * UU * U + - input[1].WorldPosition * VV * V + - input[2].WorldPosition * WW * W + - constantData.B210 * UU3 * V + - constantData.B120 * VV3 * U + - constantData.B021 * VV3 * W + - constantData.B012 * WW3 * V + - constantData.B102 * WW3 * U + - constantData.B201 * UU3 * W + - constantData.B111 * 6.0f * W * U * V; -#else - INTERPOLATE(WorldPosition); -#endif - INTERPOLATE(TexCoord); - INTERPOLATE(LightmapUV); -#if USE_VERTEX_COLOR - INTERPOLATE(VertexColor); -#endif - INTERPOLATE(TBN[0]); - INTERPOLATE(TBN[1]); - INTERPOLATE(TBN[2]); - COPY(InstanceOrigin); - COPY(InstanceParams); -#if USE_CUSTOM_VERTEX_INTERPOLATORS - UNROLL - for (int i = 0; i < CUSTOM_VERTEX_INTERPOLATORS_COUNT; i++) - { - INTERPOLATE(CustomVSToPS[i]); - } -#endif -#undef INTERPOLATE -#undef COPY - - // Interpolating normal can unnormalize it, so normalize it - output.TBN[0] = normalize(output.TBN[0]); - output.TBN[1] = normalize(output.TBN[1]); - output.TBN[2] = normalize(output.TBN[2]); - -#if MATERIAL_TESSELLATION == MATERIAL_TESSELLATION_PHONG - // Orthogonal projection in the tangent planes - float3 posProjectedU = ProjectOntoPlane(input[0].TBN[2], input[0].WorldPosition, output.WorldPosition); - float3 posProjectedV = ProjectOntoPlane(input[1].TBN[2], input[1].WorldPosition, output.WorldPosition); - float3 posProjectedW = ProjectOntoPlane(input[2].TBN[2], input[2].WorldPosition, output.WorldPosition); - - // Interpolate the projected points - output.WorldPosition = U * posProjectedU + V * posProjectedV + W * posProjectedW; -#endif - - // Perform displacement mapping -#if USE_DISPLACEMENT - MaterialInput materialInput = GetMaterialInput(output); - Material material = GetMaterialDS(materialInput); - output.WorldPosition += material.WorldDisplacement; -#endif - - // Recalculate the clip space position - output.Position = mul(float4(output.WorldPosition, 1), ViewProjectionMatrix); - - return output; -} - -#endif - -#if USE_DITHERED_LOD_TRANSITION - -void ClipLODTransition(PixelInput input) -{ - float ditherFactor = input.InstanceParams.y; - if (abs(ditherFactor) > 0.001) - { - float randGrid = cos(dot(floor(input.Position.xy), float2(347.83452793, 3343.28371863))); - float randGridFrac = frac(randGrid * 1000.0); - half mask = (ditherFactor < 0.0) ? (ditherFactor + 1.0 > randGridFrac) : (ditherFactor < randGridFrac); - clip(mask - 0.001); - } -} - -#else - -void ClipLODTransition(PixelInput input) -{ -} - -#endif - -// Pixel Shader function for Forward Pass -META_PS(USE_FORWARD, FEATURE_LEVEL_ES2) -float4 PS_Forward(PixelInput input) : SV_Target0 -{ - float4 output = 0; - - // LOD masking - ClipLODTransition(input); - // TODO: make model LOD transition smoother for transparent materials by using opacity to reduce aliasing - - // Get material parameters - MaterialInput materialInput = GetMaterialInput(input); - Material material = GetMaterialPS(materialInput); - - // Masking -#if MATERIAL_MASKED - clip(material.Mask - MATERIAL_MASK_THRESHOLD); -#endif - - // Add emissive light - output = float4(material.Emissive, material.Opacity); - -#if MATERIAL_SHADING_MODEL != SHADING_MODEL_UNLIT - - // Setup GBuffer data as proxy for lighting - GBufferSample gBuffer; - gBuffer.Normal = material.WorldNormal; - gBuffer.Roughness = material.Roughness; - gBuffer.Metalness = material.Metalness; - gBuffer.Color = material.Color; - gBuffer.Specular = material.Specular; - gBuffer.AO = material.AO; - gBuffer.ViewPos = mul(float4(materialInput.WorldPosition, 1), ViewMatrix).xyz; -#if MATERIAL_SHADING_MODEL == SHADING_MODEL_SUBSURFACE - gBuffer.CustomData = float4(material.SubsurfaceColor, material.Opacity); -#elif MATERIAL_SHADING_MODEL == SHADING_MODEL_FOLIAGE - gBuffer.CustomData = float4(material.SubsurfaceColor, material.Opacity); -#else - gBuffer.CustomData = float4(0, 0, 0, 0); -#endif - gBuffer.WorldPos = materialInput.WorldPosition; - gBuffer.ShadingModel = MATERIAL_SHADING_MODEL; - - // Calculate lighting from a single directional light - float4 shadowMask = 1.0f; - if (DirectionalLight.CastShadows > 0) - { - LightShadowData directionalLightShadowData = GetDirectionalLightShadowData(); - shadowMask.r = SampleShadow(DirectionalLight, directionalLightShadowData, DirectionalLightShadowMap, gBuffer, shadowMask.g); - } - float4 light = GetLighting(ViewPos, DirectionalLight, gBuffer, shadowMask, false, false); - - // Calculate lighting from sky light - light += GetSkyLightLighting(SkyLight, gBuffer, SkyLightTexture); - - // Calculate lighting from local lights - LOOP - for (uint localLightIndex = 0; localLightIndex < LocalLightsCount; localLightIndex++) - { - const LightData localLight = LocalLights[localLightIndex]; - bool isSpotLight = localLight.SpotAngles.x > -2.0f; - shadowMask = 1.0f; - light += GetLighting(ViewPos, localLight, gBuffer, shadowMask, true, isSpotLight); - } - -#if USE_REFLECTIONS - // Calculate reflections - light.rgb += GetEnvProbeLighting(ViewPos, EnvProbe, EnvironmentProbe, gBuffer) * light.a; -#endif - - // Add lighting (apply ambient occlusion) - output.rgb += light.rgb * gBuffer.AO; - -#if USE_FOG - // Calculate exponential height fog - float4 fog = GetExponentialHeightFog(ExponentialHeightFog, materialInput.WorldPosition, ViewPos, 0); - - // Apply fog to the output color -#if MATERIAL_BLEND == MATERIAL_BLEND_OPAQUE - output = float4(output.rgb * fog.a + fog.rgb, output.a); -#elif MATERIAL_BLEND == MATERIAL_BLEND_TRANSPARENT - output = float4(output.rgb * fog.a + fog.rgb, output.a); -#elif MATERIAL_BLEND == MATERIAL_BLEND_ADDITIVE - output = float4(output.rgb * fog.a + fog.rgb, output.a * fog.a); -#elif MATERIAL_BLEND == MATERIAL_BLEND_MULTIPLY - output = float4(lerp(float3(1, 1, 1), output.rgb, fog.aaa * fog.aaa), output.a); -#endif - -#endif - -#endif - - return output; -} - -#if USE_DISTORTION - -// Pixel Shader function for Distortion Pass -META_PS(USE_DISTORTION, FEATURE_LEVEL_ES2) -float4 PS_Distortion(PixelInput input) : SV_Target0 -{ - // LOD masking - ClipLODTransition(input); - - // Get material parameters - MaterialInput materialInput = GetMaterialInput(input); - Material material = GetMaterialPS(materialInput); - - // Masking -#if MATERIAL_MASKED - clip(material.Mask - MATERIAL_MASK_THRESHOLD); -#endif - - float3 viewNormal = normalize(TransformWorldVectorToView(materialInput, material.WorldNormal)); - float airIOR = 1.0f; -#if USE_PIXEL_NORMAL_OFFSET_REFRACTION - float3 viewVertexNormal = TransformWorldVectorToView(materialInput, TransformTangentVectorToWorld(materialInput, float3(0, 0, 1))); - float2 distortion = (viewVertexNormal.xy - viewNormal.xy) * (material.Refraction - airIOR); -#else - float2 distortion = viewNormal.xy * (material.Refraction - airIOR); -#endif - - // Clip if the distortion distance (squared) is too small to be noticed - clip(dot(distortion, distortion) - 0.00001); - - // Scale up for better precision in low/subtle refractions at the expense of artefacts at higher refraction - distortion *= 4.0f; - - // Use separate storage for positive and negative offsets - float2 addOffset = max(distortion, 0); - float2 subOffset = abs(min(distortion, 0)); - return float4(addOffset.x, addOffset.y, subOffset.x, subOffset.y); -} - -#endif - -// Pixel Shader function for Depth Pass -META_PS(true, FEATURE_LEVEL_ES2) -void PS_Depth(PixelInput input -#if GLSL - , out float4 OutColor : SV_Target0 -#endif - ) -{ - // LOD masking - ClipLODTransition(input); - - // Get material parameters - MaterialInput materialInput = GetMaterialInput(input); - Material material = GetMaterialPS(materialInput); - - // Perform per pixel clipping -#if MATERIAL_MASKED - clip(material.Mask - MATERIAL_MASK_THRESHOLD); -#endif - clip(material.Opacity - MATERIAL_OPACITY_THRESHOLD); - -#if GLSL - OutColor = 0; -#endif -} diff --git a/Content/Editor/MaterialTemplates/Terrain.shader b/Content/Editor/MaterialTemplates/Terrain.shader index 03a2081bf..b1c3a2910 100644 --- a/Content/Editor/MaterialTemplates/Terrain.shader +++ b/Content/Editor/MaterialTemplates/Terrain.shader @@ -3,7 +3,6 @@ #define MATERIAL 1 @3 - // Enables/disables smooth terrain chunks LOD transitions (with morphing higher LOD near edges to the lower LOD in the neighbour) #define USE_SMOOTH_LOD_TRANSITION 1 @@ -26,7 +25,6 @@ float3 ViewDir; float TimeParam; float4 ViewInfo; float4 ScreenSize; -float4 LightmapArea; float3 WorldInvScale; float WorldDeterminantSign; float PerInstanceRandom; @@ -39,35 +37,31 @@ float2 OffsetUV; float2 Dummy0; @1META_CB_END -#if CAN_USE_LIGHTMAP - -// Irradiance and directionality prebaked lightmaps -Texture2D Lightmap0 : register(t0); -Texture2D Lightmap1 : register(t1); -Texture2D Lightmap2 : register(t2); - -#endif - // Terrain data -Texture2D Heightmap : register(t3); -Texture2D Splatmap0 : register(t4); -Texture2D Splatmap1 : register(t5); +Texture2D Heightmap : register(t0); +Texture2D Splatmap0 : register(t1); +Texture2D Splatmap1 : register(t2); -// Material shader resources +// Shader resources @2 +// Geometry data passed though the graphics rendering stages up to the pixel shader +struct GeometryData +{ + float3 WorldPosition : TEXCOORD0; + float2 TexCoord : TEXCOORD1; + float2 LightmapUV : TEXCOORD2; + float3 WorldNormal : TEXCOORD3; + float HolesMask : TEXCOORD4; +#if USE_TERRAIN_LAYERS + float4 Layers[TERRAIN_LAYERS_DATA_SIZE] : TEXCOORD5; +#endif +}; // Interpolants passed from the vertex shader struct VertexOutput { - float4 Position : SV_Position; - float3 WorldPosition : TEXCOORD0; - float2 TexCoord : TEXCOORD1; - float2 LightmapUV : TEXCOORD2; - float3 WorldNormal : TEXCOORD3; - float HolesMask : TEXCOORD4; -#if USE_TERRAIN_LAYERS - float4 Layers[TERRAIN_LAYERS_DATA_SIZE] : TEXCOORD5; -#endif + float4 Position : SV_Position; + GeometryData Geometry; #if USE_CUSTOM_VERTEX_INTERPOLATORS float4 CustomVSToPS[CUSTOM_VERTEX_INTERPOLATORS_COUNT] : TEXCOORD9; #endif @@ -79,19 +73,12 @@ struct VertexOutput // Interpolants passed to the pixel shader struct PixelInput { - float4 Position : SV_Position; - float3 WorldPosition : TEXCOORD0; - float2 TexCoord : TEXCOORD1; - float2 LightmapUV : TEXCOORD2; - float3 WorldNormal : TEXCOORD3; - float HolesMask : TEXCOORD4; -#if USE_TERRAIN_LAYERS - float4 Layers[TERRAIN_LAYERS_DATA_SIZE] : TEXCOORD5; -#endif + float4 Position : SV_Position; + GeometryData Geometry; #if USE_CUSTOM_VERTEX_INTERPOLATORS float4 CustomVSToPS[CUSTOM_VERTEX_INTERPOLATORS_COUNT] : TEXCOORD9; #endif - bool IsFrontFace : SV_IsFrontFace; + bool IsFrontFace : SV_IsFrontFace; }; // Material properties generation input @@ -116,25 +103,66 @@ struct MaterialInput #endif }; +// Extracts geometry data to the material input +MaterialInput GetGeometryMaterialInput(GeometryData geometry) +{ + MaterialInput output = (MaterialInput)0; + output.WorldPosition = geometry.WorldPosition; + output.TexCoord = geometry.TexCoord; +#if USE_LIGHTMAP + output.LightmapUV = geometry.LightmapUV; +#endif + output.TBN = CalcTangentBasisFromWorldNormal(geometry.WorldNormal); + output.HolesMask = geometry.HolesMask; +#if USE_TERRAIN_LAYERS + output.Layers = geometry.Layers; +#endif + return output; +} + +#if USE_TESSELLATION + +// Interpolates the geometry positions data only (used by the tessallation when generating vertices) +#define InterpolateGeometryPositions(output, p0, w0, p1, w1, p2, w2, offset) output.WorldPosition = p0.WorldPosition * w0 + p1.WorldPosition * w1 + p2.WorldPosition * w2 + offset + +// Offsets the geometry positions data only (used by the tessallation when generating vertices) +#define OffsetGeometryPositions(geometry, offset) geometry.WorldPosition += offset + +// Applies the Phong tessallation to the geometry positions (used by the tessallation when doing Phong tess) +#define ApplyGeometryPositionsPhongTess(geometry, p0, p1, p2, U, V, W) \ + float3 posProjectedU = TessalationProjectOntoPlane(p0.WorldNormal, p0.WorldPosition, geometry.WorldPosition); \ + float3 posProjectedV = TessalationProjectOntoPlane(p1.WorldNormal, p1.WorldPosition, geometry.WorldPosition); \ + float3 posProjectedW = TessalationProjectOntoPlane(p2.WorldNormal, p2.WorldPosition, geometry.WorldPosition); \ + geometry.WorldPosition = U * posProjectedU + V * posProjectedV + W * posProjectedW + +// Interpolates the geometry data except positions (used by the tessallation when generating vertices) +GeometryData InterpolateGeometry(GeometryData p0, float w0, GeometryData p1, float w1, GeometryData p2, float w2) +{ + GeometryData output = (GeometryData)0; + output.TexCoord = p0.TexCoord * w0 + p1.TexCoord * w1 + p2.TexCoord * w2; + output.LightmapUV = p0.LightmapUV * w0 + p1.LightmapUV * w1 + p2.LightmapUV * w2; + output.WorldNormal = p0.WorldNormal * w0 + p1.WorldNormal * w1 + p2.WorldNormal * w2; + output.WorldNormal = normalize(output.WorldNormal); + output.HolesMask = p0.HolesMask * w0 + p1.HolesMask * w1 + p2.HolesMask * w2; +#if USE_TERRAIN_LAYERS + UNROLL + for (int i = 0; i < TERRAIN_LAYERS_DATA_SIZE; i++) + output.Layers[i] = p0.Layers[i] * w0 + p1.Layers[i] * w1 + p2.Layers[i] * w2; +#endif + return output; +} + +#endif + MaterialInput GetMaterialInput(PixelInput input) { - MaterialInput result = (MaterialInput)0; - result.WorldPosition = input.WorldPosition; - result.TexCoord = input.TexCoord; -#if USE_LIGHTMAP - result.LightmapUV = input.LightmapUV; -#endif - result.TBN = CalcTangentBasisFromWorldNormal(input.WorldNormal); - result.TwoSidedSign = WorldDeterminantSign * (input.IsFrontFace ? 1.0 : -1.0); - result.SvPosition = input.Position; - result.HolesMask = input.HolesMask; -#if USE_TERRAIN_LAYERS - result.Layers = input.Layers; -#endif + MaterialInput output = GetGeometryMaterialInput(input.Geometry); + output.TwoSidedSign = WorldDeterminantSign * (input.IsFrontFace ? 1.0 : -1.0); + output.SvPosition = input.Position; #if USE_CUSTOM_VERTEX_INTERPOLATORS - result.CustomVSToPS = input.CustomVSToPS; + output.CustomVSToPS = input.CustomVSToPS; #endif - return result; + return output; } // Removes the scale vector from the local to world transformation matrix @@ -216,6 +244,8 @@ float4 GetVertexColor(MaterialInput input) return 1; } +@8 + // Get material properties function (for vertex shader) Material GetMaterialVS(MaterialInput input) { @@ -234,9 +264,6 @@ Material GetMaterialPS(MaterialInput input) @4 } -// Fix line for errors/warnings for shader code from template -#line 1000 - // Calculates LOD value (with fractional part for blending) float CalcLOD(float2 xy, float4 morph) { @@ -285,7 +312,7 @@ float3x3 CalcTangentToWorld(float4x4 world, float3x3 tangentToLocal) struct TerrainVertexInput { float2 TexCoord : TEXCOORD0; - float4 Morph : TEXCOORD1; + float4 Morph : TEXCOORD1; }; // Vertex Shader function for terrain rendering @@ -336,7 +363,7 @@ VertexOutput VS(TerrainVertexInput input) float2 normalTemp = float2(heightmapValue.b, heightmapValue.a) * 2.0f - 1.0f; float3 normal = float3(normalTemp.x, sqrt(1.0 - saturate(dot(normalTemp, normalTemp))), normalTemp.y); normal = normalize(normal); - output.HolesMask = isHole ? 0 : 1; + output.Geometry.HolesMask = isHole ? 0 : 1; if (isHole) { normal = float3(0, 1, 0); @@ -353,10 +380,10 @@ VertexOutput VS(TerrainVertexInput input) float3 position = float3(positionXZ.x, height, positionXZ.y); // Compute world space vertex position - output.WorldPosition = mul(float4(position, 1), WorldMatrix).xyz; + output.Geometry.WorldPosition = mul(float4(position, 1), WorldMatrix).xyz; // Compute clip space position - output.Position = mul(float4(output.WorldPosition.xyz, 1), ViewProjectionMatrix); + output.Position = mul(float4(output.Geometry.WorldPosition, 1), ViewProjectionMatrix); // Pass vertex attributes #if USE_SMOOTH_LOD_TRANSITION @@ -364,46 +391,46 @@ VertexOutput VS(TerrainVertexInput input) #else float2 texCoord = input.TexCoord; #endif - output.TexCoord = positionXZ * (1.0f / TerrainChunkSizeLOD0) + OffsetUV; - output.LightmapUV = texCoord * LightmapArea.zw + LightmapArea.xy; + output.Geometry.TexCoord = positionXZ * (1.0f / TerrainChunkSizeLOD0) + OffsetUV; + output.Geometry.LightmapUV = texCoord * LightmapArea.zw + LightmapArea.xy; // Extract terrain layers weights from the splatmap #if USE_TERRAIN_LAYERS - output.Layers[0] = splatmap0Value; + output.Geometry.Layers[0] = splatmap0Value; #if TERRAIN_LAYERS_DATA_SIZE > 1 - output.Layers[1] = splatmap1Value; + output.Geometry.Layers[1] = splatmap1Value; #endif #endif // Compute world space normal vector float3x3 tangentToLocal = CalcTangentBasisFromWorldNormal(normal); float3x3 tangentToWorld = CalcTangentToWorld(WorldMatrix, tangentToLocal); - output.WorldNormal = tangentToWorld[2]; + output.Geometry.WorldNormal = tangentToWorld[2]; // Get material input params if need to evaluate any material property #if USE_POSITION_OFFSET || USE_TESSELLATION || USE_CUSTOM_VERTEX_INTERPOLATORS MaterialInput materialInput = (MaterialInput)0; - materialInput.WorldPosition = output.WorldPosition; - materialInput.TexCoord = output.TexCoord; + materialInput.WorldPosition = output.Geometry.WorldPosition; + materialInput.TexCoord = output.Geometry.TexCoord; #if USE_LIGHTMAP - materialInput.LightmapUV = output.LightmapUV; + materialInput.LightmapUV = output.Geometry.LightmapUV; #endif - materialInput.TBN = CalcTangentBasisFromWorldNormal(output.WorldNormal); + materialInput.TBN = CalcTangentBasisFromWorldNormal(output.Geometry.WorldNormal); materialInput.TwoSidedSign = WorldDeterminantSign; materialInput.SvPosition = output.Position; materialInput.PreSkinnedPosition = position; materialInput.PreSkinnedNormal = normal; - materialInput.HolesMask = output.HolesMask; + materialInput.HolesMask = output.Geometry.HolesMask; #if USE_TERRAIN_LAYERS - materialInput.Layers = output.Layers; + materialInput.Layers = output.Geometry.Layers; #endif Material material = GetMaterialVS(materialInput); #endif // Apply world position offset per-vertex #if USE_POSITION_OFFSET - output.WorldPosition += material.PositionOffset; - output.Position = mul(float4(output.WorldPosition.xyz, 1), ViewProjectionMatrix); + output.Geometry.WorldPosition += material.PositionOffset; + output.Position = mul(float4(output.Geometry.WorldPosition, 1), ViewProjectionMatrix); #endif // Get tessalation multiplier (per vertex) @@ -419,356 +446,9 @@ VertexOutput VS(TerrainVertexInput input) return output; } -#if USE_TESSELLATION - -// Interpolants passed from the hull shader to the domain shader -struct TessalationHSToDS -{ - float4 Position : SV_Position; - float3 WorldPosition : TEXCOORD0; - float2 TexCoord : TEXCOORD1; - float2 LightmapUV : TEXCOORD2; - float3 WorldNormal : TEXCOORD3; - float HolesMask : TEXCOORD4; -#if USE_TERRAIN_LAYERS - float4 Layers[TERRAIN_LAYERS_DATA_SIZE] : TEXCOORD5; -#endif -#if USE_CUSTOM_VERTEX_INTERPOLATORS - float4 CustomVSToPS[CUSTOM_VERTEX_INTERPOLATORS_COUNT] : TEXCOORD9; -#endif - float TessellationMultiplier : TESS; -}; - -// Interpolants passed from the domain shader and to the pixel shader -struct TessalationDSToPS -{ - float4 Position : SV_Position; - float3 WorldPosition : TEXCOORD0; - float2 TexCoord : TEXCOORD1; - float2 LightmapUV : TEXCOORD2; - float3 WorldNormal : TEXCOORD3; - float HolesMask : TEXCOORD4; -#if USE_TERRAIN_LAYERS - float4 Layers[TERRAIN_LAYERS_DATA_SIZE] : TEXCOORD5; -#endif -#if USE_CUSTOM_VERTEX_INTERPOLATORS - float4 CustomVSToPS[CUSTOM_VERTEX_INTERPOLATORS_COUNT] : TEXCOORD9; -#endif -}; - -MaterialInput GetMaterialInput(TessalationDSToPS input) -{ - MaterialInput result = (MaterialInput)0; - result.WorldPosition = input.WorldPosition; - result.TexCoord = input.TexCoord; -#if USE_LIGHTMAP - result.LightmapUV = input.LightmapUV; -#endif - result.TBN = CalcTangentBasisFromWorldNormal(input.WorldNormal); - result.TwoSidedSign = WorldDeterminantSign; - result.SvPosition = input.Position; - result.HolesMask = input.HolesMask; -#if USE_TERRAIN_LAYERS - result.Layers = input.Layers; -#endif -#if USE_CUSTOM_VERTEX_INTERPOLATORS - result.CustomVSToPS = input.CustomVSToPS; -#endif - return result; -} - -struct TessalationPatch -{ - float EdgeTessFactor[3] : SV_TessFactor; - float InsideTessFactor : SV_InsideTessFactor; -#if MATERIAL_TESSELLATION == MATERIAL_TESSELLATION_PN - float3 B210 : POSITION4; - float3 B120 : POSITION5; - float3 B021 : POSITION6; - float3 B012 : POSITION7; - float3 B102 : POSITION8; - float3 B201 : POSITION9; - float3 B111 : CENTER; -#endif -}; - -TessalationPatch HS_PatchConstant(InputPatch input) -{ - TessalationPatch output; - - // Average tess factors along edges, and pick an edge tess factor for the interior tessellation - float4 TessellationMultipliers; - TessellationMultipliers.x = 0.5f * (input[1].TessellationMultiplier + input[2].TessellationMultiplier); - TessellationMultipliers.y = 0.5f * (input[2].TessellationMultiplier + input[0].TessellationMultiplier); - TessellationMultipliers.z = 0.5f * (input[0].TessellationMultiplier + input[1].TessellationMultiplier); - TessellationMultipliers.w = 0.333f * (input[0].TessellationMultiplier + input[1].TessellationMultiplier + input[2].TessellationMultiplier); - - TessellationMultipliers = clamp(TessellationMultipliers, 1, MAX_TESSELLATION_FACTOR); - - output.EdgeTessFactor[0] = TessellationMultipliers.x; // 1->2 edge - output.EdgeTessFactor[1] = TessellationMultipliers.y; // 2->0 edge - output.EdgeTessFactor[2] = TessellationMultipliers.z; // 0->1 edge - output.InsideTessFactor = TessellationMultipliers.w; - -#if MATERIAL_TESSELLATION == MATERIAL_TESSELLATION_PN - // Calculate PN-Triangle coefficients - // Refer to Vlachos 2001 for the original formula - float3 p1 = input[0].WorldPosition; - float3 p2 = input[1].WorldPosition; - float3 p3 = input[2].WorldPosition; - float3 n1 = input[0].WorldNormal; - float3 n2 = input[1].WorldNormal; - float3 n3 = input[2].WorldNormal; - - // Calculate control points - output.B210 = (2.0f * p1 + p2 - dot((p2 - p1), n1) * n1) / 3.0f; - output.B120 = (2.0f * p2 + p1 - dot((p1 - p2), n2) * n2) / 3.0f; - output.B021 = (2.0f * p2 + p3 - dot((p3 - p2), n2) * n2) / 3.0f; - output.B012 = (2.0f * p3 + p2 - dot((p2 - p3), n3) * n3) / 3.0f; - output.B102 = (2.0f * p3 + p1 - dot((p1 - p3), n3) * n3) / 3.0f; - output.B201 = (2.0f * p1 + p3 - dot((p3 - p1), n1) * n1) / 3.0f; - float3 e = (output.B210 + output.B120 + output.B021 + - output.B012 + output.B102 + output.B201) / 6.0f; - float3 v = (p1 + p2 + p3) / 3.0f; - output.B111 = e + ((e - v) / 2.0f); -#endif - - return output; -} - -META_HS(USE_TESSELLATION, FEATURE_LEVEL_SM5) -META_HS_PATCH(TESSELLATION_IN_CONTROL_POINTS) -[domain("tri")] -[partitioning("fractional_odd")] -[outputtopology("triangle_cw")] -[maxtessfactor(MAX_TESSELLATION_FACTOR)] -[outputcontrolpoints(3)] -[patchconstantfunc("HS_PatchConstant")] -TessalationHSToDS HS(InputPatch input, uint ControlPointID : SV_OutputControlPointID) -{ - TessalationHSToDS output; - - // Pass through shader -#define COPY(thing) output.thing = input[ControlPointID].thing; - COPY(Position); - COPY(WorldPosition); - COPY(TexCoord); - COPY(LightmapUV); - COPY(WorldNormal); - COPY(HolesMask); - COPY(TessellationMultiplier); -#if USE_TERRAIN_LAYERS - COPY(Layers); -#endif -#if USE_CUSTOM_VERTEX_INTERPOLATORS - COPY(CustomVSToPS); -#endif -#undef COPY - - return output; -} - -#if MATERIAL_TESSELLATION == MATERIAL_TESSELLATION_PHONG - -// Orthogonal projection on to plane -float3 ProjectOntoPlane(float3 planeNormal, float3 planePoint, float3 pointToProject) -{ - return pointToProject - dot(pointToProject-planePoint, planeNormal) * planeNormal; -} - -#endif - -META_DS(USE_TESSELLATION, FEATURE_LEVEL_SM5) -[domain("tri")] -TessalationDSToPS DS(TessalationPatch constantData, float3 barycentricCoords : SV_DomainLocation, const OutputPatch input) -{ - TessalationDSToPS output; - - // Get the barycentric coords - float U = barycentricCoords.x; - float V = barycentricCoords.y; - float W = barycentricCoords.z; - - // Interpolate patch attributes to generated vertices -#define INTERPOLATE(thing) output.thing = U * input[0].thing + V * input[1].thing + W * input[2].thing -#define COPY(thing) output.thing = input[0].thing - INTERPOLATE(Position); -#if MATERIAL_TESSELLATION == MATERIAL_TESSELLATION_PN - float UU = U * U; - float VV = V * V; - float WW = W * W; - float UU3 = UU * 3.0f; - float VV3 = VV * 3.0f; - float WW3 = WW * 3.0f; - - // Interpolate using barycentric coordinates and PN Triangle control points - output.WorldPosition = - input[0].WorldPosition * UU * U + - input[1].WorldPosition * VV * V + - input[2].WorldPosition * WW * W + - constantData.B210 * UU3 * V + - constantData.B120 * VV3 * U + - constantData.B021 * VV3 * W + - constantData.B012 * WW3 * V + - constantData.B102 * WW3 * U + - constantData.B201 * UU3 * W + - constantData.B111 * 6.0f * W * U * V; -#else - INTERPOLATE(WorldPosition); -#endif - INTERPOLATE(TexCoord); - INTERPOLATE(LightmapUV); - INTERPOLATE(WorldNormal); - INTERPOLATE(HolesMask); -#if USE_TERRAIN_LAYERS - UNROLL - for (int i = 0; i < TERRAIN_LAYERS_DATA_SIZE; i++) - { - INTERPOLATE(Layers[i]); - } -#endif -#if USE_CUSTOM_VERTEX_INTERPOLATORS - UNROLL - for (int i = 0; i < CUSTOM_VERTEX_INTERPOLATORS_COUNT; i++) - { - INTERPOLATE(CustomVSToPS[i]); - } -#endif -#undef INTERPOLATE -#undef COPY - - // Interpolating normal can unnormalize it, so normalize it - output.WorldNormal = normalize(output.WorldNormal); - -#if MATERIAL_TESSELLATION == MATERIAL_TESSELLATION_PHONG - // Orthogonal projection in the tangent planes - float3 posProjectedU = ProjectOntoPlane(input[0].WorldNormal, input[0].WorldPosition, output.WorldPosition); - float3 posProjectedV = ProjectOntoPlane(input[1].WorldNormal, input[1].WorldPosition, output.WorldPosition); - float3 posProjectedW = ProjectOntoPlane(input[2].WorldNormal, input[2].WorldPosition, output.WorldPosition); - - // Interpolate the projected points - output.WorldPosition = U * posProjectedU + V * posProjectedV + W * posProjectedW; -#endif - - // Perform displacement mapping -#if USE_DISPLACEMENT - MaterialInput materialInput = GetMaterialInput(output); - Material material = GetMaterialDS(materialInput); - output.WorldPosition += material.WorldDisplacement; -#endif - - // Recalculate the clip space position - output.Position = mul(float4(output.WorldPosition, 1), ViewProjectionMatrix); - - return output; -} - -#endif - -#if USE_LIGHTMAP - -float3 SampleLightmap(Material material, MaterialInput materialInput) -{ - // Sample lightmaps - float4 lightmap0 = Lightmap0.Sample(SamplerLinearClamp, materialInput.LightmapUV); - float4 lightmap1 = Lightmap1.Sample(SamplerLinearClamp, materialInput.LightmapUV); - float4 lightmap2 = Lightmap2.Sample(SamplerLinearClamp, materialInput.LightmapUV); - - // Unpack H-basis - float3 h0 = float3(lightmap0.x, lightmap1.x, lightmap2.x); - float3 h1 = float3(lightmap0.y, lightmap1.y, lightmap2.y); - float3 h2 = float3(lightmap0.z, lightmap1.z, lightmap2.z); - float3 h3 = float3(lightmap0.w, lightmap1.w, lightmap2.w); - - // Sample baked diffuse irradiance from the H-basis coefficients - float3 normal = material.TangentNormal; -#if MATERIAL_SHADING_MODEL == SHADING_MODEL_FOLIAGE - normal *= material.TangentNormal; -#endif - return GetHBasisIrradiance(normal, h0, h1, h2, h3) / PI; -} - -#endif - -// Pixel Shader function for GBuffer Pass -META_PS(true, FEATURE_LEVEL_ES2) -META_PERMUTATION_1(USE_LIGHTMAP=0) -META_PERMUTATION_1(USE_LIGHTMAP=1) -void PS_GBuffer( - in PixelInput input - ,out float4 Light : SV_Target0 -#if MATERIAL_BLEND == MATERIAL_BLEND_OPAQUE - ,out float4 RT0 : SV_Target1 - ,out float4 RT1 : SV_Target2 - ,out float4 RT2 : SV_Target3 -#if USE_GBUFFER_CUSTOM_DATA - ,out float4 RT3 : SV_Target4 -#endif -#endif - ) -{ - Light = 0; - - // Get material parameters - MaterialInput materialInput = GetMaterialInput(input); - Material material = GetMaterialPS(materialInput); - - // Masking -#if MATERIAL_MASKED - clip(material.Mask - MATERIAL_MASK_THRESHOLD); -#endif - -#if USE_LIGHTMAP - - float3 diffuseColor = GetDiffuseColor(material.Color, material.Metalness); - float3 specularColor = GetSpecularColor(material.Color, material.Specular, material.Metalness); - - // Sample lightmap - float3 diffuseIndirectLighting = SampleLightmap(material, materialInput); - - // Apply static indirect light - Light.rgb = diffuseColor * diffuseIndirectLighting * AOMultiBounce(material.AO, diffuseColor); - -#endif - -#if MATERIAL_BLEND == MATERIAL_BLEND_OPAQUE - - // Pack material properties to GBuffer - RT0 = float4(material.Color, material.AO); - RT1 = float4(material.WorldNormal * 0.5 + 0.5, MATERIAL_SHADING_MODEL * (1.0 / 3.0)); - RT2 = float4(material.Roughness, material.Metalness, material.Specular, 0); - - // Custom data -#if USE_GBUFFER_CUSTOM_DATA -#if MATERIAL_SHADING_MODEL == SHADING_MODEL_SUBSURFACE - RT3 = float4(material.SubsurfaceColor, material.Opacity); -#elif MATERIAL_SHADING_MODEL == SHADING_MODEL_FOLIAGE - RT3 = float4(material.SubsurfaceColor, material.Opacity); -#else - RT3 = float4(0, 0, 0, 0); -#endif -#endif - - // Add light emission -#if USE_EMISSIVE - Light.rgb += material.Emissive; -#endif - -#else - - // Handle blending as faked forward pass (use Light buffer and skip GBuffer modification) - Light = float4(material.Emissive, material.Opacity); - -#endif -} - // Pixel Shader function for Depth Pass META_PS(true, FEATURE_LEVEL_ES2) -void PS_Depth(PixelInput input -#if GLSL - , out float4 OutColor : SV_Target0 -#endif - ) +void PS_Depth(PixelInput input) { #if MATERIAL_MASKED // Perform per pixel clipping if material requries it @@ -776,8 +456,6 @@ void PS_Depth(PixelInput input Material material = GetMaterialPS(materialInput); clip(material.Mask - MATERIAL_MASK_THRESHOLD); #endif - -#if GLSL - OutColor = 0; -#endif } + +@9 diff --git a/Content/Editor/Particles/Particle Material Color.flax b/Content/Editor/Particles/Particle Material Color.flax index 6c83bfc35..30b136e26 100644 --- a/Content/Editor/Particles/Particle Material Color.flax +++ b/Content/Editor/Particles/Particle Material Color.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0bf5ada0acae289fa7fbbbd6135779ef544d36999de9df0f29b2b9ce10ad52bb -size 30474 +oid sha256:de2cfa951e89d51dbe8453525c94a527cb9d1ad9fbd77166b56e6e4eb35f2806 +size 29232 diff --git a/Content/Editor/Particles/Smoke Material.flax b/Content/Editor/Particles/Smoke Material.flax index a3ae100ca..a0c1124e0 100644 --- a/Content/Editor/Particles/Smoke Material.flax +++ b/Content/Editor/Particles/Smoke Material.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4c8642a2a7024ae38ae371d95c3411d8e362f42ef98c54dc9da9c061d2a568c1 -size 36552 +oid sha256:d55de18eb0aba44d084546b4a5aabf3482219c9b2c14930e9446fe86b711e61c +size 35331 diff --git a/Content/Editor/Terrain/Circle Brush Material.flax b/Content/Editor/Terrain/Circle Brush Material.flax index dc30e39fe..1a9236d73 100644 --- a/Content/Editor/Terrain/Circle Brush Material.flax +++ b/Content/Editor/Terrain/Circle Brush Material.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bd0529d1088670eb9edd3533f9493d259bcc9797d0e01f12922f6f3b05daef4d -size 33136 +oid sha256:b6cd8240338f9b506ebb6ef42646958d72f6ed10ceae1ec1fa49291e2f4deeb4 +size 27548 diff --git a/Content/Editor/Terrain/Highlight Terrain Material.flax b/Content/Editor/Terrain/Highlight Terrain Material.flax index 49f76cb07..34de2594f 100644 --- a/Content/Editor/Terrain/Highlight Terrain Material.flax +++ b/Content/Editor/Terrain/Highlight Terrain Material.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:820f0265ca7249f84f0c91b402f817dc5ca12a0a5884756b6d0bf712c12a9b06 -size 26815 +oid sha256:ec51e766fb690f614663e39dc466c38d23b44ed08eec927c49f49cd46795e07e +size 21227 diff --git a/Content/Editor/TexturePreviewMaterial.flax b/Content/Editor/TexturePreviewMaterial.flax index 0a68763cd..7a6c1e659 100644 --- a/Content/Editor/TexturePreviewMaterial.flax +++ b/Content/Editor/TexturePreviewMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9029bd6c09c686f551356800b6f16c60f7392fb3a396d2fe583e6cb1f83c46f7 -size 10907 +oid sha256:d36c699f69d20d9d9aaf03011bdcaa2cbdfd42e6ceed7defe3dd70fc8b9e9a78 +size 10653 diff --git a/Content/Editor/Wires Debug Material.flax b/Content/Editor/Wires Debug Material.flax index 9dedbfea2..b1af939b3 100644 --- a/Content/Editor/Wires Debug Material.flax +++ b/Content/Editor/Wires Debug Material.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cb08e24a379cd67db22025bf5e3ae84979f0686179db871387d388f11aae20f0 -size 35356 +oid sha256:da9378517b42e1c7c4e9c7a79d1f86d41f979655d6f8d2900708ae668517e486 +size 28875 diff --git a/Content/Engine/DefaultDeformableMaterial.flax b/Content/Engine/DefaultDeformableMaterial.flax new file mode 100644 index 000000000..197d82b3f --- /dev/null +++ b/Content/Engine/DefaultDeformableMaterial.flax @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d9835db8c0994006b43042b26164ace74c8a2027a9586edc3ec80d16ca75f680 +size 18800 diff --git a/Content/Engine/DefaultMaterial.flax b/Content/Engine/DefaultMaterial.flax index cd7cd19e0..4212fadbd 100644 --- a/Content/Engine/DefaultMaterial.flax +++ b/Content/Engine/DefaultMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d195b3434472636eff8e34a2052f1ef5d02b1415802414bad6e15f21d0b16de8 -size 39864 +oid sha256:757da751c813fd60224d2000faa2de848f36b8555163791b9c852ac7e1c6b59a +size 31806 diff --git a/Content/Engine/DefaultTerrainMaterial.flax b/Content/Engine/DefaultTerrainMaterial.flax index be5145720..2a98d9c36 100644 --- a/Content/Engine/DefaultTerrainMaterial.flax +++ b/Content/Engine/DefaultTerrainMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:98a8347591e895fccbe327f393d6376ad1113321daa8a7ad4f47d0753c2e76a1 -size 28681 +oid sha256:c0a0cd43b9fb5a84e85c4376049db1ef3d2c64dd0cca338ff8f790e6aa1f86a9 +size 23307 diff --git a/Content/Engine/SingleColorMaterial.flax b/Content/Engine/SingleColorMaterial.flax index d11046007..a22fabbb8 100644 --- a/Content/Engine/SingleColorMaterial.flax +++ b/Content/Engine/SingleColorMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:502f451e0b232b4288a85a274d81d22a7053c84f174286a05043707dc75fe193 -size 38381 +oid sha256:889439f17a4523cf18a17c46f4348654cce5bc25c1ab1928e411405d8eacfd99 +size 30130 diff --git a/Content/Engine/SkyboxMaterial.flax b/Content/Engine/SkyboxMaterial.flax index 24d56521b..46e0c9304 100644 --- a/Content/Engine/SkyboxMaterial.flax +++ b/Content/Engine/SkyboxMaterial.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e4b60416f9e2deb1f717860ae9a4cc1a74be0517db921526d21a79434b7506e7 -size 39603 +oid sha256:1a5bfb7e11eeccf5c44ee2a284b3d0479a752cb8af470b5b93fd8fe8213947be +size 31328 diff --git a/Source/Editor/Content/Items/VisualScriptItem.cs b/Source/Editor/Content/Items/VisualScriptItem.cs index 86be2e3de..fd23887ec 100644 --- a/Source/Editor/Content/Items/VisualScriptItem.cs +++ b/Source/Editor/Content/Items/VisualScriptItem.cs @@ -50,6 +50,9 @@ namespace FlaxEditor.Content /// public bool IsMethod => false; + /// + public bool IsEvent => false; + /// public bool HasGet => true; @@ -174,6 +177,9 @@ namespace FlaxEditor.Content /// public bool IsMethod => true; + /// + public bool IsEvent => false; + /// public bool HasGet => false; diff --git a/Source/Editor/Content/Settings/AndroidPlatformSettings.cs b/Source/Editor/Content/Settings/AndroidPlatformSettings.cs deleted file mode 100644 index 2c540109f..000000000 --- a/Source/Editor/Content/Settings/AndroidPlatformSettings.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. - -using FlaxEngine; - -namespace FlaxEditor.Content.Settings -{ - /// - /// The Android platform settings asset archetype. Allows to edit asset via editor. - /// - public class AndroidPlatformSettings : SettingsBase - { - /// - /// The application package name (eg. com.company.product). Custom tokens: ${PROJECT_NAME}, ${COMPANY_NAME}. - /// - [EditorOrder(0), EditorDisplay("General"), Tooltip("The application package name (eg. com.company.product). Custom tokens: ${PROJECT_NAME}, ${COMPANY_NAME}.")] - public string PackageName = "com.${COMPANY_NAME}.${PROJECT_NAME}"; - - /// - /// The application permissions list (eg. android.media.action.IMAGE_CAPTURE). Added to the generated manifest file. - /// - [EditorOrder(100), EditorDisplay("General"), Tooltip("The application permissions list (eg. android.media.action.IMAGE_CAPTURE). Added to the generated manifest file.")] - public string[] Permissions; - - /// - /// Custom icon texture to use for the application (overrides the default one). - /// - [EditorOrder(1030), EditorDisplay("Other"), Tooltip("Custom icon texture to use for the application (overrides the default one).")] - public Texture OverrideIcon; - } -} diff --git a/Source/Editor/Content/Settings/AudioSettings.cs b/Source/Editor/Content/Settings/AudioSettings.cs deleted file mode 100644 index f011cad46..000000000 --- a/Source/Editor/Content/Settings/AudioSettings.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. - -using System.ComponentModel; -using FlaxEngine; - -namespace FlaxEditor.Content.Settings -{ - /// - /// The audio payback engine settings container. Allows to edit asset via editor. - /// - public sealed class AudioSettings : SettingsBase - { - /// - /// If checked, audio playback will be disabled in build game. Can be used if game uses custom audio playback engine. - /// - [DefaultValue(false)] - [EditorOrder(0), EditorDisplay("General"), Tooltip("If checked, audio playback will be disabled in build game. Can be used if game uses custom audio playback engine.")] - public bool DisableAudio; - - /// - /// The doppler doppler effect factor. Scale for source and listener velocities. Default is 1. - /// - [DefaultValue(1.0f)] - [EditorOrder(100), EditorDisplay("General"), Limit(0, 10.0f, 0.01f), Tooltip("The doppler doppler effect factor. Scale for source and listener velocities. Default is 1.")] - public float DopplerFactor = 1.0f; - - /// - /// True if mute all audio playback when game has no use focus. - /// - [DefaultValue(true)] - [EditorOrder(200), EditorDisplay("General", "Mute On Focus Loss"), Tooltip("If checked, engine will mute all audio playback when game has no use focus.")] - public bool MuteOnFocusLoss = true; - } -} diff --git a/Source/Editor/Content/Settings/BuildSettings.cs b/Source/Editor/Content/Settings/BuildSettings.cs index e74a5fd41..5dc6310c3 100644 --- a/Source/Editor/Content/Settings/BuildSettings.cs +++ b/Source/Editor/Content/Settings/BuildSettings.cs @@ -1,77 +1,12 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using System; -using System.ComponentModel; using FlaxEngine; namespace FlaxEditor.Content.Settings { - /// - /// The game building settings container. Allows to edit asset via editor. - /// - public sealed class BuildSettings : SettingsBase + partial class BuildSettings { - /// - /// The maximum amount of assets to include into a single assets package. Assets will be split into several packages if need to. - /// - [DefaultValue(4096)] - [EditorOrder(10), Limit(32, short.MaxValue), EditorDisplay("General", "Max assets per package"), Tooltip("The maximum amount of assets to include into a single assets package. Assets will be split into several packages if need to.")] - public int MaxAssetsPerPackage = 4096; - - /// - /// The maximum size of the single assets package (in megabytes). Assets will be split into several packages if need to. - /// - [DefaultValue(1024)] - [EditorOrder(20), Limit(16, short.MaxValue), EditorDisplay("General", "Max package size (in MB)"), Tooltip("The maximum size of the single assets package (in megabytes). Assets will be split into several packages if need to.")] - public int MaxPackageSizeMB = 1024; - - /// - /// The game content cooking Keys. Use the same value for a game and DLC packages to support loading them by the build game. Use 0 to randomize it during building. - /// - [DefaultValue(0)] - [EditorOrder(30), EditorDisplay("General"), Tooltip("The game content cooking Keys. Use the same value for a game and DLC packages to support loading them by the build game. Use 0 to randomize it during building.")] - public int ContentKey = 0; - - /// - /// If checked, the builds produced by the Game Cooker will be treated as for final game distribution (eg. for game store upload). Builds done this way cannot be tested on console devkits (eg. Xbox One, Xbox Scarlett). - /// - [DefaultValue(false)] - [EditorOrder(40), EditorDisplay("General"), Tooltip("If checked, the builds produced by the Game Cooker will be treated as for final game distribution (eg. for game store upload). Builds done this way cannot be tested on console devkits (eg. Xbox One, Xbox Scarlett).")] - public bool ForDistribution; - - /// - /// If checked, the output build files won't be packaged for the destination platform. Useful when debugging build from local PC. - /// - [DefaultValue(false)] - [EditorOrder(50), EditorDisplay("General"), Tooltip("If checked, the output build files won't be packaged for the destination platform. Useful when debugging build from local PC.")] - public bool SkipPackaging; - - /// - /// The additional assets to include into build (into root assets set). - /// - [EditorOrder(1000), EditorDisplay("Additional Data"), Tooltip("The additional assets to include into build (into root assets set).")] - public Asset[] AdditionalAssets; - - /// - /// The additional folders with assets to include into build (into root assets set). List of paths relative to the project directory (or absolute). - /// - [EditorOrder(1010), EditorDisplay("Additional Data"), Tooltip("The additional folders with assets to include into build (to root assets set). List of paths relative to the project directory (or absolute).")] - public string[] AdditionalAssetFolders; - - /// - /// Disables shaders compiler optimizations in cooked game. Can be used to debug shaders on a target platform or to speed up the shaders compilation time. - /// - [DefaultValue(false)] - [EditorOrder(2000), EditorDisplay("Content", "Shaders No Optimize"), Tooltip("Disables shaders compiler optimizations in cooked game. Can be used to debug shaders on a target platform or to speed up the shaders compilation time.")] - public bool ShadersNoOptimize; - - /// - /// Enables shader debug data generation for shaders in cooked game (depends on the target platform rendering backend). - /// - [DefaultValue(false)] - [EditorOrder(2010), EditorDisplay("Content"), Tooltip("Enables shader debug data generation for shaders in cooked game (depends on the target platform rendering backend).")] - public bool ShadersGenerateDebugData; - /// /// The build presets. /// @@ -90,13 +25,6 @@ namespace FlaxEditor.Content.Settings Platform = BuildPlatform.Windows64, Mode = BuildConfiguration.Development, }, - new BuildTarget - { - Name = "Windows 32bit", - Output = "Output\\Win32", - Platform = BuildPlatform.Windows32, - Mode = BuildConfiguration.Development, - }, } }, new BuildPreset @@ -109,14 +37,7 @@ namespace FlaxEditor.Content.Settings Name = "Windows 64bit", Output = "Output\\Win64", Platform = BuildPlatform.Windows64, - Mode = BuildConfiguration.Development, - }, - new BuildTarget - { - Name = "Windows 32bit", - Output = "Output\\Win32", - Platform = BuildPlatform.Windows32, - Mode = BuildConfiguration.Development, + Mode = BuildConfiguration.Release, }, } }, diff --git a/Source/Editor/Content/Settings/BuildTarget.cs b/Source/Editor/Content/Settings/BuildTarget.cs index 7cdc500f6..97c0b8c3d 100644 --- a/Source/Editor/Content/Settings/BuildTarget.cs +++ b/Source/Editor/Content/Settings/BuildTarget.cs @@ -35,6 +35,12 @@ namespace FlaxEditor.Content.Settings [EditorOrder(30), Tooltip("Configuration build mode")] public BuildConfiguration Mode; + /// + /// The list of custom defines passed to the build tool when compiling project scripts. Can be used in build scripts for configuration (Configuration.CustomDefines). + /// + [EditorOrder(90), Tooltip("The list of custom defines passed to the build tool when compiling project scripts. Can be used in build scripts for configuration (Configuration.CustomDefines).")] + public string[] CustomDefines; + /// /// The pre-build action command line. /// @@ -46,11 +52,5 @@ namespace FlaxEditor.Content.Settings /// [EditorOrder(110)] public string PostBuildAction; - - /// - /// Gets the build options computed from the target configuration. - /// - [HideInEditor, NoSerialize] - public virtual BuildOptions Options => BuildOptions.None; } } diff --git a/Source/Editor/Content/Settings/GameSettings.cs b/Source/Editor/Content/Settings/GameSettings.cs index 28c13c905..eaca85c55 100644 --- a/Source/Editor/Content/Settings/GameSettings.cs +++ b/Source/Editor/Content/Settings/GameSettings.cs @@ -7,32 +7,11 @@ using FlaxEngine; namespace FlaxEditor.Content.Settings { - /// - /// The game settings asset archetype. Allows to edit asset via editor. - /// - public sealed class GameSettings : SettingsBase + partial class GameSettings { internal const string PS4PlatformSettingsTypename = "FlaxEditor.Content.Settings.PS4PlatformSettings"; internal const string XboxScarlettPlatformSettingsTypename = "FlaxEditor.Content.Settings.XboxScarlettPlatformSettings"; - /// - /// The product full name. - /// - [EditorOrder(0), EditorDisplay("General"), Tooltip("The name of your product.")] - public string ProductName; - - /// - /// The company full name. - /// - [EditorOrder(10), EditorDisplay("General"), Tooltip("The name of your company or organization.")] - public string CompanyName; - - /// - /// The copyright note used for content signing (eg. source code header). - /// - [EditorOrder(15), EditorDisplay("General"), Tooltip("The copyright note used for content signing (eg. source code header).")] - public string CopyrightNotice; - /// /// The default application icon. /// diff --git a/Source/Editor/Content/Settings/GraphicsSettings.cs b/Source/Editor/Content/Settings/GraphicsSettings.cs deleted file mode 100644 index 731bc750d..000000000 --- a/Source/Editor/Content/Settings/GraphicsSettings.cs +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. - -using FlaxEngine; - -namespace FlaxEditor.Content.Settings -{ - /// - /// The graphics rendering settings container. Allows to edit asset via editor. To modify those settings at runtime use . - /// - /// - public sealed class GraphicsSettings : SettingsBase - { - /// - /// Enables rendering synchronization with the refresh rate of the display device to avoid "tearing" artifacts. - /// - [EditorOrder(20), EditorDisplay("General", "Use V-Sync"), Tooltip("Enables rendering synchronization with the refresh rate of the display device to avoid \"tearing\" artifacts.")] - public bool UseVSync = false; - - /// - /// Anti Aliasing quality setting. - /// - [EditorOrder(1000), EditorDisplay("Quality", "AA Quality"), Tooltip("Anti Aliasing quality.")] - public Quality AAQuality = Quality.Medium; - - /// - /// Screen Space Reflections quality. - /// - [EditorOrder(1100), EditorDisplay("Quality", "SSR Quality"), Tooltip("Screen Space Reflections quality.")] - public Quality SSRQuality = Quality.Medium; - - /// - /// Screen Space Ambient Occlusion quality setting. - /// - [EditorOrder(1200), EditorDisplay("Quality", "SSAO Quality"), Tooltip("Screen Space Ambient Occlusion quality setting.")] - public Quality SSAOQuality = Quality.Medium; - - /// - /// Volumetric Fog quality setting. - /// - [EditorOrder(1250), EditorDisplay("Quality", "Volumetric Fog Quality"), Tooltip("Volumetric Fog quality setting.")] - public Quality VolumetricFogQuality = Quality.High; - - /// - /// The shadows quality. - /// - [EditorOrder(1300), EditorDisplay("Quality", "Shadows Quality"), Tooltip("The shadows quality.")] - public Quality ShadowsQuality = Quality.Medium; - - /// - /// The shadow maps quality (textures resolution). - /// - [EditorOrder(1310), EditorDisplay("Quality", "Shadow Maps Quality"), Tooltip("The shadow maps quality (textures resolution).")] - public Quality ShadowMapsQuality = Quality.Medium; - - /// - /// Enables cascades splits blending for directional light shadows. - /// - [EditorOrder(1320), EditorDisplay("Quality", "Allow CSM Blending"), Tooltip("Enables cascades splits blending for directional light shadows.")] - public bool AllowCSMBlending = false; - } -} diff --git a/Source/Editor/Content/Settings/InputSettings.cs b/Source/Editor/Content/Settings/InputSettings.cs index 69a8dfba3..03ed4b7c7 100644 --- a/Source/Editor/Content/Settings/InputSettings.cs +++ b/Source/Editor/Content/Settings/InputSettings.cs @@ -4,10 +4,7 @@ using FlaxEngine; namespace FlaxEditor.Content.Settings { - /// - /// The input settings container. Allows to edit asset via editor. - /// - public sealed class InputSettings : SettingsBase + partial class InputSettings { /// /// Maps a discrete button or key press events to a "friendly name" that will later be bound to event-driven behavior. The end effect is that pressing (and/or releasing) a key, mouse button, or keypad button. diff --git a/Source/Editor/Content/Settings/LayersAndTagsSettings.cs b/Source/Editor/Content/Settings/LayersAndTagsSettings.cs index 5f3884ee1..31e8dfff8 100644 --- a/Source/Editor/Content/Settings/LayersAndTagsSettings.cs +++ b/Source/Editor/Content/Settings/LayersAndTagsSettings.cs @@ -6,10 +6,7 @@ using FlaxEngine; namespace FlaxEditor.Content.Settings { - /// - /// The layers and objects tags settings. Allows to edit asset via editor. - /// - public sealed class LayersAndTagsSettings : SettingsBase + partial class LayersAndTagsSettings { /// /// The tag names. diff --git a/Source/Editor/Content/Settings/LinuxPlatformSettings.cs b/Source/Editor/Content/Settings/LinuxPlatformSettings.cs deleted file mode 100644 index 8b26e77d4..000000000 --- a/Source/Editor/Content/Settings/LinuxPlatformSettings.cs +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. - -using FlaxEngine; - -namespace FlaxEditor.Content.Settings -{ - /// - /// The Linux platform settings asset archetype. Allows to edit asset via editor. - /// - public class LinuxPlatformSettings : SettingsBase - { - /// - /// The default game window mode. - /// - [EditorOrder(10), EditorDisplay("Window"), Tooltip("The default game window mode.")] - public GameWindowMode WindowMode = GameWindowMode.Windowed; - - /// - /// The default game window width (in pixels). - /// - [EditorOrder(20), EditorDisplay("Window"), Tooltip("The default game window width (in pixels).")] - public int ScreenWidth = 1280; - - /// - /// The default game window height (in pixels). - /// - [EditorOrder(30), EditorDisplay("Window"), Tooltip("The default game window height (in pixels).")] - public int ScreenHeight = 720; - - /// - /// Enables resizing the game window by the user. - /// - [EditorOrder(40), EditorDisplay("Window"), Tooltip("Enables resizing the game window by the user.")] - public bool ResizableWindow = false; - - /// - /// Enables game running when application window loses focus. - /// - [EditorOrder(1010), EditorDisplay("Other", "Run In Background"), Tooltip("Enables game running when application window loses focus.")] - public bool RunInBackground = false; - - /// - /// Limits maximum amount of concurrent game instances running to one, otherwise user may launch application more than once. - /// - [EditorOrder(1020), EditorDisplay("Other"), Tooltip("Limits maximum amount of concurrent game instances running to one, otherwise user may launch application more than once.")] - public bool ForceSingleInstance = false; - - /// - /// Custom icon texture to use for the application (overrides the default one). - /// - [EditorOrder(1030), EditorDisplay("Other"), Tooltip("Custom icon texture to use for the application (overrides the default one).")] - public Texture OverrideIcon; - - /// - /// Enables support for Vulkan. Disabling it reduces compiled shaders count. - /// - [EditorOrder(2020), EditorDisplay("Graphics", "Support Vulkan"), Tooltip("Enables support for Vulkan. Disabling it reduces compiled shaders count.")] - public bool SupportVulkan = true; - } -} diff --git a/Source/Editor/Content/Settings/NavigationSettings.cs b/Source/Editor/Content/Settings/NavigationSettings.cs deleted file mode 100644 index cba8f2688..000000000 --- a/Source/Editor/Content/Settings/NavigationSettings.cs +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. - -using System.ComponentModel; -using FlaxEngine; - -namespace FlaxEditor.Content.Settings -{ - /// - /// The navigation system settings container. - /// - public sealed class NavigationSettings : SettingsBase - { - /// - /// The height of a grid cell in the navigation mesh building steps using heightfields. - /// A lower number means higher precision on the vertical axis but longer build times. - /// - [DefaultValue(10.0f), Limit(1, 400)] - [EditorOrder(10), EditorDisplay("Nav Mesh Options"), Tooltip("The height of a grid cell in the navigation mesh building steps using heightfields. A lower number means higher precision on the vertical axis but longer build times.")] - public float CellHeight = 10.0f; - - /// - /// The width/height of a grid cell in the navigation mesh building steps using heightfields. - /// A lower number means higher precision on the horizontal axes but longer build times. - /// - [DefaultValue(30.0f), Limit(1, 400)] - [EditorOrder(20), EditorDisplay("Nav Mesh Options"), Tooltip("The width/height of a grid cell in the navigation mesh building steps using heightfields. A lower number means higher precision on the vertical axis but longer build times.")] - public float CellSize = 30.0f; - - /// - /// Tile size used for Navigation mesh tiles, the final size of a tile is CellSize*TileSize. - /// - [DefaultValue(64), Limit(8, 4096)] - [EditorOrder(30), EditorDisplay("Nav Mesh Options"), Tooltip("Tile size used for Navigation mesh tiles, the final size of a tile is CellSize*TileSize.")] - public int TileSize = 64; - - /// - /// The minimum number of cells allowed to form isolated island areas. - /// - [DefaultValue(0), Limit(0, 100)] - [EditorOrder(40), EditorDisplay("Nav Mesh Options"), Tooltip("The minimum number of cells allowed to form isolated island areas.")] - public int MinRegionArea = 0; - - /// - /// Any regions with a span count smaller than this value will, if possible, be merged with larger regions. - /// - [DefaultValue(20), Limit(0, 100)] - [EditorOrder(50), EditorDisplay("Nav Mesh Options"), Tooltip("Any regions with a span count smaller than this value will, if possible, be merged with larger regions.")] - public int MergeRegionArea = 20; - - /// - /// The maximum allowed length for contour edges along the border of the mesh. - /// - [DefaultValue(1200.0f), Limit(100)] - [EditorOrder(60), EditorDisplay("Nav Mesh Options", "Max Edge Length"), Tooltip("The maximum allowed length for contour edges along the border of the mesh.")] - public float MaxEdgeLen = 1200.0f; - - /// - /// The maximum distance a simplified contour's border edges should deviate from the original raw contour. - /// - [DefaultValue(1.3f), Limit(0.1f, 4)] - [EditorOrder(70), EditorDisplay("Nav Mesh Options"), Tooltip("The maximum distance a simplified contour's border edges should deviate from the original raw contour.")] - public float MaxEdgeError = 1.3f; - - /// - /// The sampling distance to use when generating the detail mesh. For height detail only. - /// - [DefaultValue(600.0f), Limit(1)] - [EditorOrder(80), EditorDisplay("Nav Mesh Options", "Detail Sampling Distance"), Tooltip("The sampling distance to use when generating the detail mesh.")] - public float DetailSamplingDist = 600.0f; - - /// - /// The maximum distance the detail mesh surface should deviate from heightfield data. For height detail only. - /// - [DefaultValue(1.0f), Limit(0, 3)] - [EditorOrder(90), EditorDisplay("Nav Mesh Options"), Tooltip("The maximum distance the detail mesh surface should deviate from heightfield data.")] - public float MaxDetailSamplingError = 1.0f; - - /// - /// The radius of the smallest objects to traverse this nav mesh. Objects can't pass through gaps of less than twice the radius. - /// - [DefaultValue(34.0f), Limit(0)] - [EditorOrder(1000), EditorDisplay("Agent Options"), Tooltip("The radius of the smallest objects to traverse this nav mesh. Objects can't pass through gaps of less than twice the radius.")] - public float WalkableRadius = 34.0f; - - /// - /// The height of the smallest objects to traverse this nav mesh. Objects can't enter areas with ceilings lower than this value. - /// - [DefaultValue(144.0f), Limit(0)] - [EditorOrder(1010), EditorDisplay("Agent Options"), Tooltip("The height of the smallest objects to traverse this nav mesh. Objects can't enter areas with ceilings lower than this value.")] - public float WalkableHeight = 144.0f; - - /// - /// The maximum ledge height that is considered to still be traversable. - /// - [DefaultValue(35.0f), Limit(0)] - [EditorOrder(1020), EditorDisplay("Agent Options"), Tooltip("The maximum ledge height that is considered to still be traversable.")] - public float WalkableMaxClimb = 35.0f; - - /// - /// The maximum slope that is considered walkable (in degrees). Objects can't go up or down slopes higher than this value. - /// - [DefaultValue(60.0f), Limit(0, 89.0f)] - [EditorOrder(1030), EditorDisplay("Agent Options"), Tooltip("The maximum slope that is considered walkable (in degrees). Objects can't go up or down slopes higher than this value.")] - public float WalkableMaxSlopeAngle = 60.0f; - } -} diff --git a/Source/Editor/Content/Settings/PhysicsSettings.cs b/Source/Editor/Content/Settings/PhysicsSettings.cs index 540b11772..d217512b3 100644 --- a/Source/Editor/Content/Settings/PhysicsSettings.cs +++ b/Source/Editor/Content/Settings/PhysicsSettings.cs @@ -1,112 +1,17 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. -using System.ComponentModel; using FlaxEngine; namespace FlaxEditor.Content.Settings { - /// - /// The physics simulation settings container. Allows to edit asset via editor. - /// - public sealed class PhysicsSettings : SettingsBase + partial class PhysicsSettings { - /// - /// The default gravity force value (in cm^2/s). - /// - [DefaultValue(typeof(Vector3), "0,-981.0,0")] - [EditorOrder(0), EditorDisplay("Simulation"), Tooltip("The default gravity force value (in cm^2/s).")] - public Vector3 DefaultGravity = new Vector3(0, -981.0f, 0); - - /// - /// If enabled, any Raycast or other scene query that intersects with a Collider marked as a Trigger will returns with a hit. Individual raycasts can override this behavior. - /// - [DefaultValue(true)] - [EditorOrder(10), EditorDisplay("Simulation"), Tooltip("If enabled, any Raycast or other scene query that intersects with a Collider marked as a Trigger will returns with a hit. Individual raycasts can override this behavior.")] - public bool QueriesHitTriggers = true; - - /// - /// Triangles from triangle meshes (CSG) with an area less than or equal to this value will be removed from physics collision data. Set to less than or equal 0 to disable. - /// - [DefaultValue(5.0f)] - [EditorOrder(20), EditorDisplay("Simulation"), Limit(-1, 10), Tooltip("Triangles from triangle meshes (CSG) with an area less than or equal to this value will be removed from physics collision data. Set to less than or equal 0 to disable.")] - public float TriangleMeshTriangleMinAreaThreshold = 5.0f; - - /// - /// Minimum relative velocity required for an object to bounce. A typical value for simulation stability is about 0.2 * gravity - /// - [DefaultValue(200.0f)] - [EditorOrder(30), EditorDisplay("Simulation"), Limit(0), Tooltip("Minimum relative velocity required for an object to bounce. A typical value for simulation stability is about 0.2 * gravity")] - public float BounceThresholdVelocity = 200.0f; - - /// - /// Default friction combine mode, controls how friction is computed for multiple materials. - /// - [DefaultValue(PhysicsCombineMode.Average)] - [EditorOrder(40), EditorDisplay("Simulation"), Tooltip("Default friction combine mode, controls how friction is computed for multiple materials.")] - public PhysicsCombineMode FrictionCombineMode = PhysicsCombineMode.Average; - - /// - /// Default restitution combine mode, controls how restitution is computed for multiple materials. - /// - [DefaultValue(PhysicsCombineMode.Average)] - [EditorOrder(50), EditorDisplay("Simulation"), Tooltip("Default restitution combine mode, controls how restitution is computed for multiple materials.")] - public PhysicsCombineMode RestitutionCombineMode = PhysicsCombineMode.Average; - - /// - /// If true CCD will be ignored. This is an optimization when CCD is never used which removes the need for PhysX to check it internally. - /// - [DefaultValue(false)] - [EditorOrder(70), EditorDisplay("Simulation", "Disable CCD"), Tooltip("If true CCD will be ignored. This is an optimization when CCD is never used which removes the need for PhysX to check it internally.")] - public bool DisableCCD; - - /// - /// Enables adaptive forces to accelerate convergence of the solver. Can improve physics simulation performance but lead to artifacts. - /// - [DefaultValue(false)] - [EditorOrder(80), EditorDisplay("Simulation"), Tooltip("Enables adaptive forces to accelerate convergence of the solver. Can improve physics simulation performance but lead to artifacts.")] - public bool EnableAdaptiveForce; - - /// - /// The maximum allowed delta time (in seconds) for the physics simulation step. - /// - [DefaultValue(1.0f / 10.0f)] - [EditorOrder(1000), EditorDisplay("Framerate"), Limit(0.0013f, 2.0f), Tooltip("The maximum allowed delta time (in seconds) for the physics simulation step.")] - public float MaxDeltaTime = 1.0f / 10.0f; - - /// - /// Whether to substep the physics simulation. - /// - [DefaultValue(false)] - [EditorOrder(1005), EditorDisplay("Framerate"), Tooltip("Whether to substep the physics simulation.")] - public bool EnableSubstepping; - - /// - /// Delta time (in seconds) for an individual simulation substep. - /// - [DefaultValue(1.0f / 120.0f)] - [EditorOrder(1010), EditorDisplay("Framerate"), Limit(0.0013f, 1.0f), Tooltip("Delta time (in seconds) for an individual simulation substep.")] - public float SubstepDeltaTime = 1.0f / 120.0f; - - /// - /// The maximum number of substeps for physics simulation. - /// - [DefaultValue(5)] - [EditorOrder(1020), EditorDisplay("Framerate"), Limit(1, 16), Tooltip("The maximum number of substeps for physics simulation.")] - public int MaxSubsteps = 5; - /// /// The collision layers masks. Used to define layer-based collision detection. /// [EditorOrder(1040), EditorDisplay("Layers Matrix"), CustomEditor(typeof(FlaxEditor.CustomEditors.Dedicated.LayersMatrixEditor))] public uint[] LayerMasks = new uint[32]; - /// - /// Enables support for cooking physical collision shapes geometry at runtime. Use it to enable generating runtime terrain collision or convex mesh colliders. - /// - [DefaultValue(false)] - [EditorOrder(1100), EditorDisplay("Other", "Support Cooking At Runtime"), Tooltip("Enables support for cooking physical collision shapes geometry at runtime. Use it to enable generating runtime terrain collision or convex mesh colliders.")] - public bool SupportCookingAtRuntime; - /// /// Initializes a new instance of the class. /// diff --git a/Source/Editor/Content/Settings/TimeSettings.cs b/Source/Editor/Content/Settings/TimeSettings.cs deleted file mode 100644 index af352f5e1..000000000 --- a/Source/Editor/Content/Settings/TimeSettings.cs +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. - -using System.ComponentModel; -using FlaxEngine; - -namespace FlaxEditor.Content.Settings -{ - /// - /// The time settings asset archetype. Allows to edit asset via editor. - /// - public sealed class TimeSettings : SettingsBase - { - /// - /// The target amount of the game logic updates per second (script updates frequency). Use 0 for infinity. - /// - [DefaultValue(30.0f)] - [EditorOrder(1), Limit(0, 1000), EditorDisplay(null, "Update FPS"), Tooltip("Target amount of the game logic updates per second (script updates frequency). Use 0 for infinity.")] - public float UpdateFPS = 30.0f; - - /// - /// The target amount of the physics simulation updates per second (also fixed updates frequency). Use 0 for infinity. - /// - [DefaultValue(60.0f)] - [EditorOrder(2), Limit(0, 1000), EditorDisplay(null, "Physics FPS"), Tooltip("Target amount of the physics simulation updates per second (also fixed updates frequency). Use 0 for infinity.")] - public float PhysicsFPS = 60.0f; - - /// - /// The target amount of the frames rendered per second (actual game FPS). Use 0 for infinity. - /// - [DefaultValue(60.0f)] - [EditorOrder(3), Limit(0, 1000), EditorDisplay(null, "Draw FPS"), Tooltip("Target amount of the frames rendered per second (actual game FPS). Use 0 for infinity.")] - public float DrawFPS = 60.0f; - - /// - /// The game time scale factor. Default is 1. - /// - [DefaultValue(1.0f)] - [EditorOrder(10), Limit(0, 1000.0f, 0.1f), Tooltip("Game time scaling factor. Default is 1 for real-time simulation.")] - public float TimeScale = 1.0f; - - /// - /// The maximum allowed delta time (in seconds) for the game logic update step. - /// - [DefaultValue(1.0f / 10.0f)] - [EditorOrder(20), Limit(0.1f, 1000.0f, 0.01f), Tooltip("The maximum allowed delta time (in seconds) for the game logic update step.")] - public float MaxUpdateDeltaTime = 1.0f / 10.0f; - } -} diff --git a/Source/Editor/Content/Settings/UWPPlatformSettings.cs b/Source/Editor/Content/Settings/UWPPlatformSettings.cs deleted file mode 100644 index 5c5696936..000000000 --- a/Source/Editor/Content/Settings/UWPPlatformSettings.cs +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. - -using System; -using System.ComponentModel; -using FlaxEngine; - -namespace FlaxEditor.Content.Settings -{ - /// - /// The Universal Windows Platform (UWP) platform settings asset archetype. Allows to edit asset via editor. - /// - public class UWPPlatformSettings : SettingsBase - { - /// - /// The preferred launch windowing mode. - /// - public enum WindowMode - { - /// - /// The full screen mode - /// - FullScreen = 0, - - /// - /// The view size. - /// - ViewSize = 1, - } - - /// - /// The display orientation modes. Can be combined as flags. - /// - [Flags] - public enum DisplayOrientations - { - /// - /// The none. - /// - None = 0, - - /// - /// The landscape. - /// - Landscape = 1, - - /// - /// The landscape flipped. - /// - LandscapeFlipped = 2, - - /// - /// The portrait. - /// - Portrait = 4, - - /// - /// The portrait flipped. - /// - PortraitFlipped = 8, - } - - /// - /// The preferred launch windowing mode. Always fullscreen on Xbox. - /// - [DefaultValue(WindowMode.FullScreen)] - [EditorOrder(10), EditorDisplay("Window"), Tooltip("The preferred launch windowing mode. Always fullscreen on Xbox.")] - public WindowMode PreferredLaunchWindowingMode = WindowMode.FullScreen; - - /// - /// The display orientation modes. Can be combined as flags. - /// - [DefaultValue(DisplayOrientations.Landscape | DisplayOrientations.LandscapeFlipped | DisplayOrientations.Portrait | DisplayOrientations.PortraitFlipped)] - [EditorOrder(20), EditorDisplay("Window"), Tooltip("The display orientation modes. Can be combined as flags.")] - public DisplayOrientations AutoRotationPreferences = DisplayOrientations.Landscape | DisplayOrientations.LandscapeFlipped | DisplayOrientations.Portrait | DisplayOrientations.PortraitFlipped; - - /// - /// The location of the package certificate (relative to the project). - /// - [DefaultValue("")] - [EditorOrder(1010), EditorDisplay("Other"), Tooltip("The location of the package certificate (relative to the project).")] - public string CertificateLocation = string.Empty; - - /// - /// Enables support for DirectX 11. Disabling it reduces compiled shaders count. - /// - [DefaultValue(true)] - [EditorOrder(2000), EditorDisplay("Graphics", "Support DirectX 11"), Tooltip("Enables support for DirectX 11. Disabling it reduces compiled shaders count.")] - public bool SupportDX11 = true; - - /// - /// Enables support for DirectX 10 and DirectX 10.1. Disabling it reduces compiled shaders count. - /// - [DefaultValue(false)] - [EditorOrder(2010), EditorDisplay("Graphics", "Support DirectX 10"), Tooltip("Enables support for DirectX 10 and DirectX 10.1. Disabling it reduces compiled shaders count.")] - public bool SupportDX10 = false; - } -} diff --git a/Source/Editor/Content/Settings/WindowsPlatformSettings.cs b/Source/Editor/Content/Settings/WindowsPlatformSettings.cs deleted file mode 100644 index dc873e9f4..000000000 --- a/Source/Editor/Content/Settings/WindowsPlatformSettings.cs +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. - -using System.ComponentModel; -using FlaxEngine; - -namespace FlaxEditor.Content.Settings -{ - /// - /// The Windows platform settings asset archetype. Allows to edit asset via editor. - /// - public class WindowsPlatformSettings : SettingsBase - { - /// - /// The default game window mode. - /// - [DefaultValue(GameWindowMode.Windowed)] - [EditorOrder(10), EditorDisplay("Window"), Tooltip("The default game window mode.")] - public GameWindowMode WindowMode = GameWindowMode.Windowed; - - /// - /// The default game window width (in pixels). - /// - [DefaultValue(1280)] - [EditorOrder(20), EditorDisplay("Window"), Tooltip("The default game window width (in pixels).")] - public int ScreenWidth = 1280; - - /// - /// The default game window height (in pixels). - /// - [DefaultValue(720)] - [EditorOrder(30), EditorDisplay("Window"), Tooltip("The default game window height (in pixels).")] - public int ScreenHeight = 720; - - /// - /// Enables resizing the game window by the user. - /// - [DefaultValue(false)] - [EditorOrder(40), EditorDisplay("Window"), Tooltip("Enables resizing the game window by the user.")] - public bool ResizableWindow = false; - - /// - /// Enables game running when application window loses focus. - /// - [DefaultValue(false)] - [EditorOrder(1010), EditorDisplay("Other", "Run In Background"), Tooltip("Enables game running when application window loses focus.")] - public bool RunInBackground = false; - - /// - /// Limits maximum amount of concurrent game instances running to one, otherwise user may launch application more than once. - /// - [DefaultValue(false)] - [EditorOrder(1020), EditorDisplay("Other"), Tooltip("Limits maximum amount of concurrent game instances running to one, otherwise user may launch application more than once.")] - public bool ForceSingleInstance = false; - - /// - /// Custom icon texture to use for the application (overrides the default one). - /// - [DefaultValue(null)] - [EditorOrder(1030), EditorDisplay("Other"), Tooltip("Custom icon texture to use for the application (overrides the default one).")] - public Texture OverrideIcon; - - /// - /// Enables support for DirectX 12. Disabling it reduces compiled shaders count. - /// - [DefaultValue(false)] - [EditorOrder(2000), EditorDisplay("Graphics", "Support DirectX 12"), Tooltip("Enables support for DirectX 12. Disabling it reduces compiled shaders count.")] - public bool SupportDX12 = false; - - /// - /// Enables support for DirectX 11. Disabling it reduces compiled shaders count. - /// - [DefaultValue(true)] - [EditorOrder(2010), EditorDisplay("Graphics", "Support DirectX 11"), Tooltip("Enables support for DirectX 11. Disabling it reduces compiled shaders count.")] - public bool SupportDX11 = true; - - /// - /// Enables support for DirectX 10 and DirectX 10.1. Disabling it reduces compiled shaders count. - /// - [DefaultValue(false)] - [EditorOrder(2020), EditorDisplay("Graphics", "Support DirectX 10"), Tooltip("Enables support for DirectX 10 and DirectX 10.1. Disabling it reduces compiled shaders count.")] - public bool SupportDX10 = false; - - /// - /// Enables support for Vulkan. Disabling it reduces compiled shaders count. - /// - [DefaultValue(false)] - [EditorOrder(2030), EditorDisplay("Graphics", "Support Vulkan"), Tooltip("Enables support for Vulkan. Disabling it reduces compiled shaders count.")] - public bool SupportVulkan = false; - } -} diff --git a/Source/Editor/Cooker/CookingData.h b/Source/Editor/Cooker/CookingData.h index b46b8ff72..56b15af23 100644 --- a/Source/Editor/Cooker/CookingData.h +++ b/Source/Editor/Cooker/CookingData.h @@ -174,6 +174,11 @@ struct FLAXENGINE_API CookingData /// BuildOptions Options; + /// + /// The list of custom defines passed to the build tool when compiling project scripts. Can be used in build scripts for configuration (Configuration.CustomDefines). + /// + Array CustomDefines; + /// /// The original output path (actual OutputPath could be modified by the Platform Tools or a plugin for additional layout customizations or packaging). This path is preserved. /// diff --git a/Source/Editor/Cooker/GameCooker.cpp b/Source/Editor/Cooker/GameCooker.cpp index 1fdf0755f..efd6763d8 100644 --- a/Source/Editor/Cooker/GameCooker.cpp +++ b/Source/Editor/Cooker/GameCooker.cpp @@ -43,53 +43,11 @@ #endif #if PLATFORM_TOOLS_XBOX_SCARLETT #include "Platforms/XboxScarlett/Editor/PlatformTools/XboxScarlettPlatformTools.h" -#include "Platforms/XboxScarlett/Engine/Platform/XboxScarlettPlatformSettings.h" #endif #if PLATFORM_TOOLS_ANDROID #include "Platform/Android/AndroidPlatformTools.h" -#include "Engine/Platform/Android/AndroidPlatformSettings.h" #endif -void LoadPlatformSettingsEditor(ISerializable::DeserializeStream& data) -{ -#define LOAD_SETTINGS(nodeName, settingsType) \ - { \ - Guid id = JsonTools::GetGuid(data, nodeName); \ - if (id.IsValid()) \ - { \ - AssetReference subAsset = Content::LoadAsync(id); \ - if (subAsset) \ - { \ - if (!subAsset->WaitForLoaded()) \ - { \ - settingsType::Instance()->Deserialize(*subAsset->Data, nullptr); \ - settingsType::Instance()->Apply(); \ - } \ - } \ - else \ - { LOG(Warning, "Cannot load " nodeName " settings"); } \ - } \ - } -#if PLATFORM_TOOLS_WINDOWS - LOAD_SETTINGS("WindowsPlatform", WindowsPlatformSettings); -#endif -#if PLATFORM_TOOLS_UWP || PLATFORM_TOOLS_XBOX_ONE - LOAD_SETTINGS("UWPPlatform", UWPPlatformSettings); -#endif -#if PLATFORM_TOOLS_LINUX - LOAD_SETTINGS("LinuxPlatform", LinuxPlatformSettings); -#endif -#if PLATFORM_TOOLS_PS4 - LOAD_SETTINGS("PS4Platform", PS4PlatformSettings); -#endif -#if PLATFORM_TOOLS_XBOX_SCARLETT - LOAD_SETTINGS("XboxScarlettPlatform", XboxScarlettPlatformSettings); -#endif -#if PLATFORM_TOOLS_ANDROID - LOAD_SETTINGS("AndroidPlatform", AndroidPlatformSettings); -#endif -} - namespace GameCookerImpl { MMethod* Internal_OnEvent = nullptr; @@ -299,7 +257,7 @@ PlatformTools* GameCooker::GetTools(BuildPlatform platform) return result; } -void GameCooker::Build(BuildPlatform platform, BuildConfiguration configuration, const StringView& outputPath, BuildOptions options) +void GameCooker::Build(BuildPlatform platform, BuildConfiguration configuration, const StringView& outputPath, BuildOptions options, const Array& customDefines) { if (IsRunning()) { @@ -323,6 +281,7 @@ void GameCooker::Build(BuildPlatform platform, BuildConfiguration configuration, data.Platform = platform; data.Configuration = configuration; data.Options = options; + data.CustomDefines = customDefines; data.OutputPath = outputPath; FileSystem::NormalizePath(data.OutputPath); data.OutputPath = data.OriginalOutputPath = FileSystem::ConvertRelativePathToAbsolute(Globals::ProjectFolder, data.OutputPath); diff --git a/Source/Editor/Cooker/GameCooker.h b/Source/Editor/Cooker/GameCooker.h index 3defe51ff..f1bfc4ad3 100644 --- a/Source/Editor/Cooker/GameCooker.h +++ b/Source/Editor/Cooker/GameCooker.h @@ -84,7 +84,8 @@ public: /// The build configuration. /// The output path (output directory). /// The build options. - API_FUNCTION() static void Build(BuildPlatform platform, BuildConfiguration configuration, const StringView& outputPath, BuildOptions options); + /// The list of custom defines passed to the build tool when compiling project scripts. Can be used in build scripts for configuration (Configuration.CustomDefines). + API_FUNCTION() static void Build(BuildPlatform platform, BuildConfiguration configuration, const StringView& outputPath, BuildOptions options, const Array& customDefines); /// /// Sends a cancel event to the game building service. diff --git a/Source/Editor/Cooker/Platform/Android/AndroidPlatformTools.cpp b/Source/Editor/Cooker/Platform/Android/AndroidPlatformTools.cpp index b4365b4bd..6a42450d5 100644 --- a/Source/Editor/Cooker/Platform/Android/AndroidPlatformTools.cpp +++ b/Source/Editor/Cooker/Platform/Android/AndroidPlatformTools.cpp @@ -12,6 +12,10 @@ #include "Engine/Core/Config/GameSettings.h" #include "Engine/Core/Config/BuildSettings.h" #include "Editor/Utilities/EditorUtilities.h" +#include "Engine/Content/Content.h" +#include "Engine/Content/JsonAsset.h" + +IMPLEMENT_SETTINGS_GETTER(AndroidPlatformSettings, AndroidPlatform); namespace { @@ -106,7 +110,8 @@ void AndroidPlatformTools::OnBuildStarted(CookingData& data) bool AndroidPlatformTools::OnPostProcess(CookingData& data) { - const auto platformSettings = AndroidPlatformSettings::Instance(); + const auto gameSettings = GameSettings::Get(); + const auto platformSettings = AndroidPlatformSettings::Get(); const auto platformDataPath = data.GetPlatformBinariesRoot(); const auto assetsPath = data.OutputPath; const auto jniLibsPath = data.OriginalOutputPath / TEXT("app/jniLibs"); @@ -125,11 +130,11 @@ bool AndroidPlatformTools::OnPostProcess(CookingData& data) // Setup package name (eg. com.company.project) String packageName = platformSettings->PackageName; { - String productName = GameSettings::ProductName; + String productName = gameSettings->ProductName; productName.Replace(TEXT(" "), TEXT("")); productName.Replace(TEXT("."), TEXT("")); productName.Replace(TEXT("-"), TEXT("")); - String companyName = GameSettings::CompanyName; + String companyName = gameSettings->CompanyName; companyName.Replace(TEXT(" "), TEXT("")); companyName.Replace(TEXT("."), TEXT("")); companyName.Replace(TEXT("-"), TEXT("")); @@ -235,7 +240,7 @@ bool AndroidPlatformTools::OnPostProcess(CookingData& data) EditorUtilities::ReplaceInFile(manifestPath, TEXT("${AndroidPermissions}"), permissions); EditorUtilities::ReplaceInFile(manifestPath, TEXT("${AndroidAttributes}"), attributes); const String stringsPath = data.OriginalOutputPath / TEXT("app/src/main/res/values/strings.xml"); - EditorUtilities::ReplaceInFile(stringsPath, TEXT("${ProjectName}"), GameSettings::ProductName); + EditorUtilities::ReplaceInFile(stringsPath, TEXT("${ProjectName}"), gameSettings->ProductName); // Deploy native binaries to the output location (per-ABI) const String abiBinariesPath = jniLibsPath / abi; @@ -256,7 +261,8 @@ bool AndroidPlatformTools::OnPostProcess(CookingData& data) // TODO: expose event to inject custom gradle and manifest options or custom binaries into app - if (BuildSettings::Instance()->SkipPackaging) + const auto buildSettings = BuildSettings::Get(); + if (buildSettings->SkipPackaging) { return false; } @@ -286,7 +292,7 @@ bool AndroidPlatformTools::OnPostProcess(CookingData& data) #else const Char* gradlew = TEXT("gradlew"); #endif - const bool distributionPackage = BuildSettings::Instance()->ForDistribution; + const bool distributionPackage = buildSettings->ForDistribution; const String gradleCommand = String::Format(TEXT("\"{0}\" {1}"), data.OriginalOutputPath / gradlew, distributionPackage ? TEXT("assemble") : TEXT("assembleDebug")); const int32 result = Platform::RunProcess(gradleCommand, data.OriginalOutputPath, envVars, true); if (result != 0) @@ -297,7 +303,7 @@ bool AndroidPlatformTools::OnPostProcess(CookingData& data) // Copy result package const String apk = data.OriginalOutputPath / (distributionPackage ? TEXT("app/build/outputs/apk/release/app-release-unsigned.apk") : TEXT("app/build/outputs/apk/debug/app-debug.apk")); - const String outputApk = data.OriginalOutputPath / GameSettings::ProductName + TEXT(".apk"); + const String outputApk = data.OriginalOutputPath / gameSettings->ProductName + TEXT(".apk"); if (FileSystem::CopyFile(outputApk, apk)) { LOG(Error, "Failed to copy package from {0} to {1}", apk, outputApk); diff --git a/Source/Editor/Cooker/Platform/Linux/LinuxPlatformTools.cpp b/Source/Editor/Cooker/Platform/Linux/LinuxPlatformTools.cpp index eccc299bb..fa47c5ee5 100644 --- a/Source/Editor/Cooker/Platform/Linux/LinuxPlatformTools.cpp +++ b/Source/Editor/Cooker/Platform/Linux/LinuxPlatformTools.cpp @@ -9,6 +9,10 @@ #include "Editor/Utilities/EditorUtilities.h" #include "Engine/Tools/TextureTool/TextureTool.h" #include "Engine/Graphics/Textures/TextureData.h" +#include "Engine/Content/Content.h" +#include "Engine/Content/JsonAsset.h" + +IMPLEMENT_SETTINGS_GETTER(LinuxPlatformSettings, LinuxPlatform); const Char* LinuxPlatformTools::GetDisplayName() const { @@ -32,7 +36,8 @@ ArchitectureType LinuxPlatformTools::GetArchitecture() const bool LinuxPlatformTools::OnDeployBinaries(CookingData& data) { - const auto platformSettings = LinuxPlatformSettings::Instance(); + const auto gameSettings = GameSettings::Get(); + const auto platformSettings = LinuxPlatformSettings::Get(); const auto outputPath = data.OutputPath; // Copy binaries @@ -60,7 +65,7 @@ bool LinuxPlatformTools::OnDeployBinaries(CookingData& data) // Apply game executable file name #if !BUILD_DEBUG const String outputExePath = outputPath / TEXT("FlaxGame"); - const String gameExePath = outputPath / GameSettings::ProductName; + const String gameExePath = outputPath / gameSettings->ProductName; if (FileSystem::FileExists(outputExePath) && gameExePath.Compare(outputExePath, StringSearchCase::IgnoreCase) == 0) { if (FileSystem::MoveFile(gameExePath, outputExePath, true)) diff --git a/Source/Editor/Cooker/Platform/UWP/UWPPlatformTools.cpp b/Source/Editor/Cooker/Platform/UWP/UWPPlatformTools.cpp index d9d2fe612..c8f9e45a7 100644 --- a/Source/Editor/Cooker/Platform/UWP/UWPPlatformTools.cpp +++ b/Source/Editor/Cooker/Platform/UWP/UWPPlatformTools.cpp @@ -11,6 +11,10 @@ #include "Engine/Serialization/FileWriteStream.h" #include "Editor/Utilities/EditorUtilities.h" #include "Engine/Engine/Globals.h" +#include "Engine/Content/Content.h" +#include "Engine/Content/JsonAsset.h" + +IMPLEMENT_SETTINGS_GETTER(UWPPlatformSettings, UWPPlatform); bool UWPPlatformTools::UseAOT() const { @@ -37,7 +41,8 @@ bool UWPPlatformTools::OnDeployBinaries(CookingData& data) bool isXboxOne = data.Platform == BuildPlatform::XboxOne; const auto platformDataPath = Globals::StartupFolder / TEXT("Source/Platforms"); const auto uwpDataPath = platformDataPath / (isXboxOne ? TEXT("XboxOne") : TEXT("UWP")) / TEXT("Binaries"); - const auto platformSettings = UWPPlatformSettings::Instance(); + const auto gameSettings = GameSettings::Get(); + const auto platformSettings = UWPPlatformSettings::Get(); Array fileTemplate; // Copy binaries @@ -66,7 +71,7 @@ bool UWPPlatformTools::OnDeployBinaries(CookingData& data) } } - const auto projectName = GameSettings::ProductName; + const auto projectName = gameSettings->ProductName; auto defaultNamespace = projectName; ScriptsBuilder::FilterNamespaceText(defaultNamespace); const StringAnsi projectGuid = "{3A9A2246-71DD-4567-9ABF-3E040310E30E}"; @@ -102,7 +107,7 @@ bool UWPPlatformTools::OnDeployBinaries(CookingData& data) // Generate new temp cert if missing if (!FileSystem::FileExists(dstCertificatePath)) { - if (EditorUtilities::GenerateCertificate(GameSettings::CompanyName, dstCertificatePath)) + if (EditorUtilities::GenerateCertificate(gameSettings->CompanyName, dstCertificatePath)) { LOG(Warning, "Failed to create certificate."); } @@ -159,8 +164,8 @@ bool UWPPlatformTools::OnDeployBinaries(CookingData& data) auto now = DateTime::Now(); file->WriteTextFormatted( (char*)fileTemplate.Get() - , GameSettings::ProductName.ToStringAnsi() - , GameSettings::CompanyName.ToStringAnsi() + , gameSettings->ProductName.ToStringAnsi() + , gameSettings->CompanyName.ToStringAnsi() , now.GetYear() ); hasError = file->HasError(); @@ -382,7 +387,7 @@ bool UWPPlatformTools::OnDeployBinaries(CookingData& data) file->WriteTextFormatted( (char*)fileTemplate.Get() , projectName.ToStringAnsi() // {0} Display Name - , GameSettings::CompanyName.ToStringAnsi() // {1} Company Name + , gameSettings->CompanyName.ToStringAnsi() // {1} Company Name , productId.ToStringAnsi() // {2} Product ID , defaultNamespace.ToStringAnsi() // {3} Default Namespace ); diff --git a/Source/Editor/Cooker/Platform/Windows/WindowsPlatformTools.cpp b/Source/Editor/Cooker/Platform/Windows/WindowsPlatformTools.cpp index 35ed27639..348930dd5 100644 --- a/Source/Editor/Cooker/Platform/Windows/WindowsPlatformTools.cpp +++ b/Source/Editor/Cooker/Platform/Windows/WindowsPlatformTools.cpp @@ -8,6 +8,10 @@ #include "Engine/Core/Config/GameSettings.h" #include "Editor/Utilities/EditorUtilities.h" #include "Engine/Graphics/Textures/TextureData.h" +#include "Engine/Content/Content.h" +#include "Engine/Content/JsonAsset.h" + +IMPLEMENT_SETTINGS_GETTER(WindowsPlatformSettings, WindowsPlatform); const Char* WindowsPlatformTools::GetDisplayName() const { @@ -31,7 +35,7 @@ ArchitectureType WindowsPlatformTools::GetArchitecture() const bool WindowsPlatformTools::OnDeployBinaries(CookingData& data) { - const auto platformSettings = WindowsPlatformSettings::Instance(); + const auto platformSettings = WindowsPlatformSettings::Get(); const auto& outputPath = data.OutputPath; // Apply executable icon diff --git a/Source/Editor/Cooker/Steps/CompileScriptsStep.cpp b/Source/Editor/Cooker/Steps/CompileScriptsStep.cpp index 3bb2e1eb6..dd7c77a94 100644 --- a/Source/Editor/Cooker/Steps/CompileScriptsStep.cpp +++ b/Source/Editor/Cooker/Steps/CompileScriptsStep.cpp @@ -217,6 +217,11 @@ bool CompileScriptsStep::Perform(CookingData& data) args += TEXT(" -SkipTargets=FlaxGame"); } #endif + for (auto& define : data.CustomDefines) + { + args += TEXT(" -D"); + args += define; + } if (ScriptsBuilder::RunBuildTool(args)) { data.Error(TEXT("Failed to compile game scripts.")); diff --git a/Source/Editor/Cooker/Steps/CookAssetsStep.cpp b/Source/Editor/Cooker/Steps/CookAssetsStep.cpp index 396626fc1..7f2824c42 100644 --- a/Source/Editor/Cooker/Steps/CookAssetsStep.cpp +++ b/Source/Editor/Cooker/Steps/CookAssetsStep.cpp @@ -160,8 +160,9 @@ void CookAssetsStep::CacheData::Load(CookingData& data) // Invalidate shaders and assets with shaders if need to rebuild them bool invalidateShaders = false; - const bool shadersNoOptimize = BuildSettings::Instance()->ShadersNoOptimize; - const bool shadersGenerateDebugData = BuildSettings::Instance()->ShadersGenerateDebugData; + const auto buildSettings = BuildSettings::Get(); + const bool shadersNoOptimize = buildSettings->ShadersNoOptimize; + const bool shadersGenerateDebugData = buildSettings->ShadersGenerateDebugData; if (shadersNoOptimize != Settings.Global.ShadersNoOptimize) { LOG(Info, "ShadersNoOptimize option has been modified."); @@ -175,7 +176,7 @@ void CookAssetsStep::CacheData::Load(CookingData& data) #if PLATFORM_TOOLS_WINDOWS if (data.Platform == BuildPlatform::Windows32 || data.Platform == BuildPlatform::Windows64) { - const auto settings = WindowsPlatformSettings::Instance(); + const auto settings = WindowsPlatformSettings::Get(); const bool modified = Settings.Windows.SupportDX11 != settings->SupportDX11 || Settings.Windows.SupportDX10 != settings->SupportDX10 || @@ -190,7 +191,7 @@ void CookAssetsStep::CacheData::Load(CookingData& data) #if PLATFORM_TOOLS_UWP if (data.Platform == BuildPlatform::UWPx86 || data.Platform == BuildPlatform::UWPx64) { - const auto settings = UWPPlatformSettings::Instance(); + const auto settings = UWPPlatformSettings::Get(); const bool modified = Settings.UWP.SupportDX11 != settings->SupportDX11 || Settings.UWP.SupportDX10 != settings->SupportDX10; @@ -204,7 +205,7 @@ void CookAssetsStep::CacheData::Load(CookingData& data) #if PLATFORM_TOOLS_LINUX if (data.Platform == BuildPlatform::LinuxX64) { - const auto settings = LinuxPlatformSettings::Instance(); + const auto settings = LinuxPlatformSettings::Get(); const bool modified = Settings.Linux.SupportVulkan != settings->SupportVulkan; if (modified) @@ -322,6 +323,8 @@ bool ProcessShaderBase(CookAssetsStep::AssetCookData& data, ShaderAssetBase* ass auto sourceLength = sourceChunk->Size(); Encryption::DecryptBytes((byte*)source, sourceLength); source[sourceLength - 1] = 0; + while (sourceLength > 2 && source[sourceLength - 1] == 0) + sourceLength--; // Init shader cache output stream // TODO: reuse MemoryWriteStream per cooking process to reduce dynamic memory allocations @@ -369,7 +372,7 @@ bool ProcessShaderBase(CookAssetsStep::AssetCookData& data, ShaderAssetBase* ass case BuildPlatform::Windows64: { const char* platformDefineName = "PLATFORM_WINDOWS"; - const auto settings = WindowsPlatformSettings::Instance(); + const auto settings = WindowsPlatformSettings::Get(); if (settings->SupportDX12) { COMPILE_PROFILE(DirectX_SM6, SHADER_FILE_CHUNK_INTERNAL_D3D_SM6_CACHE); @@ -393,7 +396,7 @@ bool ProcessShaderBase(CookAssetsStep::AssetCookData& data, ShaderAssetBase* ass case BuildPlatform::UWPx64: { const char* platformDefineName = "PLATFORM_UWP"; - const auto settings = UWPPlatformSettings::Instance(); + const auto settings = UWPPlatformSettings::Get(); if (settings->SupportDX11) { COMPILE_PROFILE(DirectX_SM5, SHADER_FILE_CHUNK_INTERNAL_D3D_SM5_CACHE); @@ -415,7 +418,7 @@ bool ProcessShaderBase(CookAssetsStep::AssetCookData& data, ShaderAssetBase* ass case BuildPlatform::LinuxX64: { const char* platformDefineName = "PLATFORM_LINUX"; - const auto settings = LinuxPlatformSettings::Instance(); + const auto settings = LinuxPlatformSettings::Get(); if (settings->SupportVulkan) { COMPILE_PROFILE(Vulkan_SM5, SHADER_FILE_CHUNK_INTERNAL_VULKAN_SM5_CACHE); @@ -881,7 +884,8 @@ bool CookAssetsStep::Perform(CookingData& data) data.StepProgress(TEXT("Loading build cache"), 0); // Prepare - const auto buildSettings = BuildSettings::Instance(); + const auto gameSettings = GameSettings::Get(); + const auto buildSettings = BuildSettings::Get(); const int32 contentKey = buildSettings->ContentKey == 0 ? rand() : buildSettings->ContentKey; AssetsRegistry.Clear(); AssetPathsMapping.Clear(); @@ -892,22 +896,21 @@ bool CookAssetsStep::Perform(CookingData& data) // Update build settings { - const auto settings = WindowsPlatformSettings::Instance(); + const auto settings = WindowsPlatformSettings::Get(); cache.Settings.Windows.SupportDX11 = settings->SupportDX11; cache.Settings.Windows.SupportDX10 = settings->SupportDX10; cache.Settings.Windows.SupportVulkan = settings->SupportVulkan; } { - const auto settings = UWPPlatformSettings::Instance(); + const auto settings = UWPPlatformSettings::Get(); cache.Settings.UWP.SupportDX11 = settings->SupportDX11; cache.Settings.UWP.SupportDX10 = settings->SupportDX10; } { - const auto settings = LinuxPlatformSettings::Instance(); + const auto settings = LinuxPlatformSettings::Get(); cache.Settings.Linux.SupportVulkan = settings->SupportVulkan; } { - const auto buildSettings = BuildSettings::Instance(); cache.Settings.Global.ShadersNoOptimize = buildSettings->ShadersNoOptimize; cache.Settings.Global.ShadersGenerateDebugData = buildSettings->ShadersGenerateDebugData; } @@ -1004,7 +1007,7 @@ bool CookAssetsStep::Perform(CookingData& data) // Create build game header { GameHeaderFlags gameFlags = GameHeaderFlags::None; - if (!GameSettings::NoSplashScreen) + if (!gameSettings->NoSplashScreen) gameFlags |= GameHeaderFlags::ShowSplashScreen; // Open file @@ -1022,17 +1025,17 @@ bool CookAssetsStep::Perform(CookingData& data) Array bytes; bytes.Resize(808 + sizeof(Guid)); Platform::MemoryClear(bytes.Get(), bytes.Count()); - int32 length = sizeof(Char) * GameSettings::ProductName.Length(); - Platform::MemoryCopy(bytes.Get() + 0, GameSettings::ProductName.Get(), length); + int32 length = sizeof(Char) * gameSettings->ProductName.Length(); + Platform::MemoryCopy(bytes.Get() + 0, gameSettings->ProductName.Get(), length); bytes[length] = 0; bytes[length + 1] = 0; - length = sizeof(Char) * GameSettings::CompanyName.Length(); - Platform::MemoryCopy(bytes.Get() + 400, GameSettings::CompanyName.Get(), length); + length = sizeof(Char) * gameSettings->CompanyName.Length(); + Platform::MemoryCopy(bytes.Get() + 400, gameSettings->CompanyName.Get(), length); bytes[length + 400] = 0; bytes[length + 401] = 0; *(int32*)(bytes.Get() + 800) = (int32)gameFlags; *(int32*)(bytes.Get() + 804) = contentKey; - *(Guid*)(bytes.Get() + 808) = GameSettings::SplashScreen; + *(Guid*)(bytes.Get() + 808) = gameSettings->SplashScreen; Encryption::EncryptBytes(bytes.Get(), bytes.Count()); stream->WriteArray(bytes); diff --git a/Source/Editor/Cooker/Steps/DeployDataStep.cpp b/Source/Editor/Cooker/Steps/DeployDataStep.cpp index b5ea2d268..96f8bfad2 100644 --- a/Source/Editor/Cooker/Steps/DeployDataStep.cpp +++ b/Source/Editor/Cooker/Steps/DeployDataStep.cpp @@ -12,6 +12,7 @@ bool DeployDataStep::Perform(CookingData& data) { data.StepProgress(TEXT("Deploying engine data"), 0); const String depsRoot = data.GetPlatformBinariesRoot(); + const auto gameSettings = GameSettings::Get(); // Setup output folders and copy required data const auto contentDir = data.OutputPath / TEXT("Content"); @@ -73,8 +74,9 @@ bool DeployDataStep::Perform(CookingData& data) data.AddRootEngineAsset(TEXT("Shaders/SSR")); data.AddRootEngineAsset(TEXT("Shaders/VolumetricFog")); data.AddRootEngineAsset(TEXT("Engine/DefaultMaterial")); + data.AddRootEngineAsset(TEXT("Engine/DefaultDeformableMaterial")); data.AddRootEngineAsset(TEXT("Engine/DefaultTerrainMaterial")); - if (!GameSettings::NoSplashScreen && !GameSettings::SplashScreen.IsValid()) + if (!gameSettings->NoSplashScreen && !gameSettings->SplashScreen.IsValid()) data.AddRootEngineAsset(TEXT("Engine/Textures/Logo")); data.AddRootEngineAsset(TEXT("Engine/Textures/NormalTexture")); data.AddRootEngineAsset(TEXT("Engine/Textures/BlackTexture")); @@ -98,7 +100,7 @@ bool DeployDataStep::Perform(CookingData& data) // Register game assets data.StepProgress(TEXT("Deploying game data"), 50); - auto& buildSettings = *BuildSettings::Instance(); + auto& buildSettings = *BuildSettings::Get(); for (auto& e : buildSettings.AdditionalAssets) data.AddRootAsset(e.GetID()); Array files; diff --git a/Source/Editor/Cooker/Steps/ValidateStep.cpp b/Source/Editor/Cooker/Steps/ValidateStep.cpp index fdebca0a7..915f7839d 100644 --- a/Source/Editor/Cooker/Steps/ValidateStep.cpp +++ b/Source/Editor/Cooker/Steps/ValidateStep.cpp @@ -38,18 +38,23 @@ bool ValidateStep::Perform(CookingData& data) #endif // Load game settings (may be modified via editor) - GameSettings::Load(); + if (GameSettings::Load()) + { + data.Error(TEXT("Failed to load game settings.")); + return true; + } data.AddRootAsset(Globals::ProjectContentFolder / TEXT("GameSettings.json")); // Validate game settings + auto gameSettings = GameSettings::Get(); { - if (GameSettings::ProductName.IsEmpty()) + if (gameSettings->ProductName.IsEmpty()) { data.Error(TEXT("Missing product name.")); return true; } - if (GameSettings::CompanyName.IsEmpty()) + if (gameSettings->CompanyName.IsEmpty()) { data.Error(TEXT("Missing company name.")); return true; @@ -58,7 +63,7 @@ bool ValidateStep::Perform(CookingData& data) // TODO: validate version AssetInfo info; - if (!Content::GetAssetInfo(GameSettings::FirstScene, info)) + if (!Content::GetAssetInfo(gameSettings->FirstScene, info)) { data.Error(TEXT("Missing first scene. Set it in the game settings.")); return true; diff --git a/Source/Editor/CustomEditors/CustomEditor.cs b/Source/Editor/CustomEditors/CustomEditor.cs index 5f992729f..347eb608f 100644 --- a/Source/Editor/CustomEditors/CustomEditor.cs +++ b/Source/Editor/CustomEditors/CustomEditor.cs @@ -624,7 +624,7 @@ namespace FlaxEditor.CustomEditors return FindPrefabRoot(actor.Parent); } - private ISceneObject FindObjectWithPrefabObjectId(Actor actor, ref Guid prefabObjectId) + private SceneObject FindObjectWithPrefabObjectId(Actor actor, ref Guid prefabObjectId) { if (actor.PrefabObjectID == prefabObjectId) return actor; @@ -667,7 +667,7 @@ namespace FlaxEditor.CustomEditors { // Special case for object references // If prefab object has reference to other object in prefab needs to revert to matching prefab instance object not the reference prefab object value - if (Values.ReferenceValue is ISceneObject referenceSceneObject && referenceSceneObject.HasPrefabLink) + if (Values.ReferenceValue is SceneObject referenceSceneObject && referenceSceneObject.HasPrefabLink) { if (Values.Count > 1) { diff --git a/Source/Editor/CustomEditors/Dedicated/ActorEditor.cs b/Source/Editor/CustomEditors/Dedicated/ActorEditor.cs index ad974c55a..b8491abfa 100644 --- a/Source/Editor/CustomEditors/Dedicated/ActorEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/ActorEditor.cs @@ -24,43 +24,6 @@ namespace FlaxEditor.CustomEditors.Dedicated { private Guid _linkedPrefabId; - /// - protected override void SpawnProperty(LayoutElementsContainer itemLayout, ValueContainer itemValues, ItemInfo item) - { - // Note: we cannot specify actor properties editor types directly because we want to keep editor classes in FlaxEditor assembly - int order = item.Order?.Order ?? int.MinValue; - switch (order) - { - // Override static flags editor - case -80: - item.CustomEditor = new CustomEditorAttribute(typeof(ActorStaticFlagsEditor)); - break; - - // Override layer editor - case -69: - item.CustomEditor = new CustomEditorAttribute(typeof(ActorLayerEditor)); - break; - - // Override tag editor - case -68: - item.CustomEditor = new CustomEditorAttribute(typeof(ActorTagEditor)); - break; - - // Override position/scale editor - case -30: - case -10: - item.CustomEditor = new CustomEditorAttribute(typeof(ActorTransformEditor.PositionScaleEditor)); - break; - - // Override orientation editor - case -20: - item.CustomEditor = new CustomEditorAttribute(typeof(ActorTransformEditor.OrientationEditor)); - break; - } - - base.SpawnProperty(itemLayout, itemValues, item); - } - /// protected override List GetItemsForType(ScriptType type) { @@ -243,7 +206,7 @@ namespace FlaxEditor.CustomEditors.Dedicated node.Text = CustomEditorsUtil.GetPropertyNameUI(removed.PrefabObject.GetType().Name); } // Actor or Script - else if (editor.Values[0] is ISceneObject sceneObject) + else if (editor.Values[0] is SceneObject sceneObject) { node.TextColor = sceneObject.HasPrefabLink ? FlaxEngine.GUI.Style.Current.ProgressNormal : FlaxEngine.GUI.Style.Current.BackgroundSelected; node.Text = CustomEditorsUtil.GetPropertyNameUI(sceneObject.GetType().Name); diff --git a/Source/Editor/CustomEditors/Dedicated/NavAgentMaskEditor.cs b/Source/Editor/CustomEditors/Dedicated/NavAgentMaskEditor.cs new file mode 100644 index 000000000..ab0151bb9 --- /dev/null +++ b/Source/Editor/CustomEditors/Dedicated/NavAgentMaskEditor.cs @@ -0,0 +1,92 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +using System.Linq; +using FlaxEditor.Content.Settings; +using FlaxEngine; +using FlaxEngine.GUI; + +namespace FlaxEditor.CustomEditors.Dedicated +{ + /// + /// Custom editor for . + /// + /// + [CustomEditor(typeof(NavAgentMask)), DefaultEditor] + internal class NavAgentMaskEditor : CustomEditor + { + private CheckBox[] _checkBoxes; + + /// + public override void Initialize(LayoutElementsContainer layout) + { + var settings = GameSettings.Load(); + if (settings.NavMeshes == null || settings.NavMeshes.Length == 0) + { + layout.Label("Missing navmesh settings"); + return; + } + + _checkBoxes = new CheckBox[settings.NavMeshes.Length]; + for (int i = 0; i < settings.NavMeshes.Length; i++) + { + ref var navmesh = ref settings.NavMeshes[i]; + var property = layout.AddPropertyItem(navmesh.Name, navmesh.Agent.ToString()); + property.Labels.Last().TextColorHighlighted = navmesh.Color; + var checkbox = property.Checkbox().CheckBox; + UpdateCheckbox(checkbox, i); + checkbox.Tag = i; + checkbox.StateChanged += OnCheckboxStateChanged; + _checkBoxes[i] = checkbox; + } + } + + /// + protected override void Deinitialize() + { + _checkBoxes = null; + + base.Deinitialize(); + } + + /// + public override void Refresh() + { + if (_checkBoxes != null) + { + for (int i = 0; i < _checkBoxes.Length; i++) + { + UpdateCheckbox(_checkBoxes[i], i); + } + } + + base.Refresh(); + } + + private void OnCheckboxStateChanged(CheckBox checkBox) + { + var i = (int)checkBox.Tag; + var value = (NavAgentMask)Values[0]; + var mask = 1u << i; + value.Mask &= ~mask; + value.Mask |= checkBox.Checked ? mask : 0; + SetValue(value); + } + + private void UpdateCheckbox(CheckBox checkbox, int i) + { + for (var j = 0; j < Values.Count; j++) + { + var value = (((NavAgentMask)Values[j]).Mask & (1 << i)) != 0; + if (j == 0) + { + checkbox.Checked = value; + } + else if (checkbox.State != CheckBoxState.Intermediate) + { + if (checkbox.Checked != value) + checkbox.State = CheckBoxState.Intermediate; + } + } + } + } +} diff --git a/Source/Editor/CustomEditors/Dedicated/SplineEditor.cs b/Source/Editor/CustomEditors/Dedicated/SplineEditor.cs new file mode 100644 index 000000000..51d137ea9 --- /dev/null +++ b/Source/Editor/CustomEditors/Dedicated/SplineEditor.cs @@ -0,0 +1,67 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +using FlaxEditor.Actions; +using FlaxEditor.SceneGraph.Actors; +using FlaxEngine; +using FlaxEngine.GUI; + +namespace FlaxEditor.CustomEditors.Dedicated +{ + /// + /// Custom editor for . + /// + /// + [CustomEditor(typeof(Spline)), DefaultEditor] + public class SplineEditor : ActorEditor + { + /// + public override void Initialize(LayoutElementsContainer layout) + { + base.Initialize(layout); + + if (Values.HasDifferentTypes == false) + { + layout.Space(10); + var grid = layout.CustomContainer(); + grid.CustomControl.SlotsHorizontally = 2; + grid.CustomControl.SlotsVertically = 1; + grid.Button("Set Linear Tangents").Button.Clicked += OnSetTangentsLinear; + grid.Button("Set Smooth Tangents").Button.Clicked += OnSetTangentsSmooth; + } + } + + private void OnSetTangentsLinear() + { + var enableUndo = Presenter.Undo != null && Presenter.Undo.Enabled; + for (int i = 0; i < Values.Count; i++) + { + if (Values[i] is Spline spline) + { + var before = enableUndo ? (BezierCurve.Keyframe[])spline.SplineKeyframes.Clone() : null; + spline.SetTangentsLinear(); + if (enableUndo) + Presenter.Undo.AddAction(new EditSplineAction(spline, before)); + SplineNode.OnSplineEdited(spline); + Editor.Instance.Scene.MarkSceneEdited(spline.Scene); + } + } + } + + private void OnSetTangentsSmooth() + { + var enableUndo = Presenter.Undo != null && Presenter.Undo.Enabled; + for (int i = 0; i < Values.Count; i++) + { + if (Values[i] is Spline spline) + { + var before = enableUndo ? (BezierCurve.Keyframe[])spline.SplineKeyframes.Clone() : null; + spline.SetTangentsSmooth(); + if (enableUndo) + Presenter.Undo.AddAction(new EditSplineAction(spline, before)); + SplineNode.OnSplineEdited(spline); + Editor.Instance.Scene.MarkSceneEdited(spline.Scene); + } + } + } + } +} diff --git a/Source/Editor/CustomEditors/Editors/ArrayEditor.cs b/Source/Editor/CustomEditors/Editors/ArrayEditor.cs index f12dadde1..335fd64a7 100644 --- a/Source/Editor/CustomEditors/Editors/ArrayEditor.cs +++ b/Source/Editor/CustomEditors/Editors/ArrayEditor.cs @@ -2,6 +2,7 @@ using System; using System.Collections; +using FlaxEditor.Scripting; using FlaxEngine; namespace FlaxEditor.CustomEditors.Editors @@ -48,6 +49,15 @@ namespace FlaxEditor.CustomEditors.Editors Array.Copy(array, oldSize - 1, newValues, i, 1); } } + else if (newSize > 0) + { + // Initialize new entries with default values + var defaultValue = TypeUtils.GetDefaultValue(new ScriptType(elementType)); + for (int i = 0; i < newSize; i++) + { + newValues.SetValue(defaultValue, i); + } + } SetValue(newValues); } diff --git a/Source/Editor/CustomEditors/Editors/CollectionEditor.cs b/Source/Editor/CustomEditors/Editors/CollectionEditor.cs index ecbbef0f9..2e5aeade7 100644 --- a/Source/Editor/CustomEditors/Editors/CollectionEditor.cs +++ b/Source/Editor/CustomEditors/Editors/CollectionEditor.cs @@ -116,7 +116,7 @@ namespace FlaxEditor.CustomEditors.Editors // Try get CollectionAttribute for collection editor meta var attributes = Values.GetAttributes(); Type overrideEditorType = null; - float spacing = 0.0f; + float spacing = 10.0f; var collection = (CollectionAttribute)attributes?.FirstOrDefault(x => x is CollectionAttribute); if (collection != null) { diff --git a/Source/Editor/CustomEditors/Editors/GenericEditor.cs b/Source/Editor/CustomEditors/Editors/GenericEditor.cs index 485a9b735..9e7295e7e 100644 --- a/Source/Editor/CustomEditors/Editors/GenericEditor.cs +++ b/Source/Editor/CustomEditors/Editors/GenericEditor.cs @@ -517,23 +517,22 @@ namespace FlaxEditor.CustomEditors.Editors if (item.Header != null) itemLayout.Header(item.Header.Text); - // Peek values - ValueContainer itemValues; try { - itemValues = item.GetValues(Values); + // Peek values + ValueContainer itemValues = item.GetValues(Values); + + // Spawn property editor + SpawnProperty(itemLayout, itemValues, item); } catch (Exception ex) { - Editor.LogWarning("Failed to get object values for item " + item); + Editor.LogWarning("Failed to setup values and UI for item " + item); Editor.LogWarning(ex.Message); Editor.LogWarning(ex.StackTrace); return; } - // Spawn property editor - SpawnProperty(itemLayout, itemValues, item); - // Expand all parent groups if need to if (item.ExpandGroups) { diff --git a/Source/Editor/CustomEditors/Editors/ListEditor.cs b/Source/Editor/CustomEditors/Editors/ListEditor.cs index 305d2e2c9..19ed9675b 100644 --- a/Source/Editor/CustomEditors/Editors/ListEditor.cs +++ b/Source/Editor/CustomEditors/Editors/ListEditor.cs @@ -58,7 +58,7 @@ namespace FlaxEditor.CustomEditors.Editors } else if (newSize > 0) { - // Fill new entries + // Fill new entries with default value var defaultValue = Scripting.TypeUtils.GetDefaultValue(ElementType); for (int i = oldSize; i < newSize; i++) { diff --git a/Source/Editor/CustomEditors/LayoutElementsContainer.cs b/Source/Editor/CustomEditors/LayoutElementsContainer.cs index 9df2de8be..59058056d 100644 --- a/Source/Editor/CustomEditors/LayoutElementsContainer.cs +++ b/Source/Editor/CustomEditors/LayoutElementsContainer.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using FlaxEditor.CustomEditors.Elements; using FlaxEditor.CustomEditors.GUI; using FlaxEditor.GUI; +using FlaxEditor.GUI.ContextMenu; using FlaxEngine; using FlaxEngine.Assertions; using FlaxEngine.GUI; @@ -553,6 +554,8 @@ namespace FlaxEditor.CustomEditors var group = Group(name, true); group.Panel.Close(false); group.Panel.TooltipText = tooltip; + group.Panel.Tag = editor; + group.Panel.MouseButtonRightClicked += OnGroupPanelMouseButtonRightClicked; return group.Object(values, editor); } @@ -560,6 +563,23 @@ namespace FlaxEditor.CustomEditors return property.Object(values, editor); } + private void OnGroupPanelMouseButtonRightClicked(DropPanel groupPanel, Vector2 location) + { + var linkedEditor = (CustomEditor)groupPanel.Tag; + var menu = new ContextMenu(); + + var revertToPrefab = menu.AddButton("Revert to Prefab", linkedEditor.RevertToReferenceValue); + revertToPrefab.Enabled = linkedEditor.CanRevertReferenceValue; + var resetToDefault = menu.AddButton("Reset to default", linkedEditor.RevertToDefaultValue); + resetToDefault.Enabled = linkedEditor.CanRevertDefaultValue; + menu.AddSeparator(); + menu.AddButton("Copy", linkedEditor.Copy); + var paste = menu.AddButton("Paste", linkedEditor.Paste); + paste.Enabled = linkedEditor.CanPaste; + + menu.Show(groupPanel, location); + } + /// /// Adds object property editor. Selects proper based on overrides. /// diff --git a/Source/Editor/CustomEditors/Values/ValueContainer.cs b/Source/Editor/CustomEditors/Values/ValueContainer.cs index b2f26bb7b..f8cecb801 100644 --- a/Source/Editor/CustomEditors/Values/ValueContainer.cs +++ b/Source/Editor/CustomEditors/Values/ValueContainer.cs @@ -167,14 +167,14 @@ namespace FlaxEditor.CustomEditors { if (_hasReferenceValue) { - if (_referenceValue is ISceneObject referenceSceneObject && referenceSceneObject.HasPrefabLink) + if (_referenceValue is SceneObject referenceSceneObject && referenceSceneObject.HasPrefabLink) { for (int i = 0; i < Count; i++) { if (this[i] == referenceSceneObject) continue; - if (this[i] == null || (this[i] is ISceneObject valueSceneObject && valueSceneObject.PrefabObjectID != referenceSceneObject.PrefabObjectID)) + if (this[i] == null || (this[i] is SceneObject valueSceneObject && valueSceneObject.PrefabObjectID != referenceSceneObject.PrefabObjectID)) return true; } } diff --git a/Source/Editor/Editor.Build.cs b/Source/Editor/Editor.Build.cs index a767ecac9..4d7d33d65 100644 --- a/Source/Editor/Editor.Build.cs +++ b/Source/Editor/Editor.Build.cs @@ -26,7 +26,7 @@ public class Editor : EditorModule // Platform Tools inside external platform implementation location options.PrivateDefinitions.Add(macro); options.SourcePaths.Add(externalPath); - options.SourceFiles.Add(Path.Combine(Globals.EngineRoot, "Source", "Platforms", platform, "Engine", "Platform", platform + "PlatformSettings.cs")); + AddSourceFileIfExists(options, Path.Combine(Globals.EngineRoot, "Source", "Platforms", platform, "Engine", "Platform", platform + "PlatformSettings.cs")); } } } diff --git a/Source/Editor/Editor.cs b/Source/Editor/Editor.cs index f81f81ddd..3aa8346c8 100644 --- a/Source/Editor/Editor.cs +++ b/Source/Editor/Editor.cs @@ -242,8 +242,6 @@ namespace FlaxEditor StateMachine = new EditorStateMachine(this); Undo = new EditorUndo(this); - ScriptsBuilder.ScriptsReloadBegin += ScriptsBuilder_ScriptsReloadBegin; - ScriptsBuilder.ScriptsReloadEnd += ScriptsBuilder_ScriptsReloadEnd; UIControl.FallbackParentGetDelegate += OnUIControlFallbackParentGet; } @@ -260,18 +258,6 @@ namespace FlaxEditor return null; } - private void ScriptsBuilder_ScriptsReloadBegin() - { - EnsureState(); - StateMachine.GoToState(); - } - - private void ScriptsBuilder_ScriptsReloadEnd() - { - EnsureState(); - StateMachine.GoToState(); - } - internal void RegisterModule(EditorModule module) { Log("Register Editor module " + module); @@ -497,9 +483,6 @@ namespace FlaxEditor Surface.VisualScriptSurface.NodesCache.Clear(); Instance = null; - ScriptsBuilder.ScriptsReloadBegin -= ScriptsBuilder_ScriptsReloadBegin; - ScriptsBuilder.ScriptsReloadEnd -= ScriptsBuilder_ScriptsReloadEnd; - // Invoke new instance if need to open a project if (!string.IsNullOrEmpty(_projectToOpen)) { diff --git a/Source/Editor/GUI/CurveEditor.cs b/Source/Editor/GUI/CurveEditor.cs index cef9a9dec..3819b5758 100644 --- a/Source/Editor/GUI/CurveEditor.cs +++ b/Source/Editor/GUI/CurveEditor.cs @@ -16,6 +16,7 @@ namespace FlaxEditor.GUI /// /// The base class for editors. Allows to use generic curve editor without type information at compile-time. /// + [HideInEditor] public abstract class CurveEditorBase : ContainerControl { /// diff --git a/Source/Editor/GUI/Tabs/Tabs.cs b/Source/Editor/GUI/Tabs/Tabs.cs index 377c6827d..5b1317929 100644 --- a/Source/Editor/GUI/Tabs/Tabs.cs +++ b/Source/Editor/GUI/Tabs/Tabs.cs @@ -11,6 +11,7 @@ namespace FlaxEditor.GUI.Tabs /// Represents control which contains collection of . /// /// + [HideInEditor] public class Tabs : ContainerControl { /// diff --git a/Source/Editor/GUI/Timeline/GUI/KeyframesEditor.cs b/Source/Editor/GUI/Timeline/GUI/KeyframesEditor.cs index a12a71e21..25f6ffb0a 100644 --- a/Source/Editor/GUI/Timeline/GUI/KeyframesEditor.cs +++ b/Source/Editor/GUI/Timeline/GUI/KeyframesEditor.cs @@ -14,6 +14,7 @@ namespace FlaxEditor.GUI /// The generic keyframes animation editor control. /// /// + [HideInEditor] public class KeyframesEditor : ContainerControl { /// diff --git a/Source/Editor/GUI/Timeline/Timeline.cs b/Source/Editor/GUI/Timeline/Timeline.cs index 149f4a3c8..e0e4083c1 100644 --- a/Source/Editor/GUI/Timeline/Timeline.cs +++ b/Source/Editor/GUI/Timeline/Timeline.cs @@ -20,6 +20,7 @@ namespace FlaxEditor.GUI.Timeline /// The timeline control that contains tracks section and headers. Can be used to create time-based media interface for camera tracks editing, audio mixing and events tracking. /// /// + [HideInEditor] public class Timeline : ContainerControl { private static readonly KeyValuePair[] FPSValues = diff --git a/Source/Editor/Gizmo/TransformGizmo.cs b/Source/Editor/Gizmo/TransformGizmo.cs index eca5475ed..f7aed5597 100644 --- a/Source/Editor/Gizmo/TransformGizmo.cs +++ b/Source/Editor/Gizmo/TransformGizmo.cs @@ -71,7 +71,7 @@ namespace FlaxEditor.Gizmo if (hit != null) { // For child actor nodes (mesh, link or sth) we need to select it's owning actor node first or any other child node (but not a child actor) - if (hit is ActorChildNode actorChildNode) + if (hit is ActorChildNode actorChildNode && !actorChildNode.CanBeSelectedDirectly) { var parentNode = actorChildNode.ParentNode; bool canChildBeSelected = sceneEditing.Selection.Contains(parentNode); diff --git a/Source/Editor/Managed/ManagedEditor.Internal.cpp b/Source/Editor/Managed/ManagedEditor.Internal.cpp index 4084c7f01..28d4df32a 100644 --- a/Source/Editor/Managed/ManagedEditor.Internal.cpp +++ b/Source/Editor/Managed/ManagedEditor.Internal.cpp @@ -24,7 +24,7 @@ #include "Engine/ContentImporters/ImportAudio.h" #include "Engine/ContentImporters/CreateCollisionData.h" #include "Engine/ContentImporters/CreateJson.h" -#include "Engine/Core/Config/LayersTagsSettings.h" +#include "Engine/Level/Level.h" #include "Engine/Core/Config/GameSettings.h" #include "Engine/Core/Cache.h" #include "Engine/CSG/CSGBuilder.h" @@ -302,22 +302,20 @@ namespace CustomEditorsUtilInternal } } -namespace LayersAndTagsSettingsInternal +namespace LayersAndTagsSettingsInternal1 { MonoArray* GetCurrentTags() { - auto settings = LayersAndTagsSettings::Instance(); - return MUtils::ToArray(settings->Tags); + return MUtils::ToArray(Level::Tags); } MonoArray* GetCurrentLayers() { - const auto settings = LayersAndTagsSettings::Instance(); - return MUtils::ToArray(Span(settings->Layers, Math::Max(1, settings->GetNonEmptyLayerNamesCount()))); + return MUtils::ToArray(Span(Level::Layers, Math::Max(1, Level::GetNonEmptyLayerNamesCount()))); } } -namespace GameSettingsInternal +namespace GameSettingsInternal1 { void Apply() { @@ -1054,9 +1052,9 @@ public: ADD_INTERNAL_CALL("FlaxEditor.Content.Import.TextureImportEntry::Internal_GetTextureImportOptions", &GetTextureImportOptions); ADD_INTERNAL_CALL("FlaxEditor.Content.Import.ModelImportEntry::Internal_GetModelImportOptions", &GetModelImportOptions); ADD_INTERNAL_CALL("FlaxEditor.Content.Import.AudioImportEntry::Internal_GetAudioImportOptions", &GetAudioImportOptions); - ADD_INTERNAL_CALL("FlaxEditor.Content.Settings.LayersAndTagsSettings::GetCurrentTags", &LayersAndTagsSettingsInternal::GetCurrentTags); - ADD_INTERNAL_CALL("FlaxEditor.Content.Settings.LayersAndTagsSettings::GetCurrentLayers", &LayersAndTagsSettingsInternal::GetCurrentLayers); - ADD_INTERNAL_CALL("FlaxEditor.Content.Settings.GameSettings::Apply", &GameSettingsInternal::Apply); + ADD_INTERNAL_CALL("FlaxEditor.Content.Settings.LayersAndTagsSettings::GetCurrentTags", &LayersAndTagsSettingsInternal1::GetCurrentTags); + ADD_INTERNAL_CALL("FlaxEditor.Content.Settings.LayersAndTagsSettings::GetCurrentLayers", &LayersAndTagsSettingsInternal1::GetCurrentLayers); + ADD_INTERNAL_CALL("FlaxEditor.Content.Settings.GameSettings::Apply", &GameSettingsInternal1::Apply); ADD_INTERNAL_CALL("FlaxEditor.Editor::Internal_CloseSplashScreen", &CloseSplashScreen); ADD_INTERNAL_CALL("FlaxEditor.Editor::Internal_CreateAsset", &CreateAsset); ADD_INTERNAL_CALL("FlaxEditor.Editor::Internal_CreateVisualScript", &CreateVisualScript); diff --git a/Source/Editor/Modules/SceneEditingModule.cs b/Source/Editor/Modules/SceneEditingModule.cs index 19edaf7f7..3f3c4cb70 100644 --- a/Source/Editor/Modules/SceneEditingModule.cs +++ b/Source/Editor/Modules/SceneEditingModule.cs @@ -200,7 +200,8 @@ namespace FlaxEditor.Modules /// /// The actor. /// The parent actor. Set null as default. - public void Spawn(Actor actor, Actor parent = null) + /// True if automatically select the spawned actor, otherwise false. + public void Spawn(Actor actor, Actor parent = null, bool autoSelect = true) { bool isPlayMode = Editor.StateMachine.IsPlayMode; @@ -225,7 +226,15 @@ namespace FlaxEditor.Modules actorNode.PostSpawn(); // Create undo action - var action = new DeleteActorsAction(new List(1) { actorNode }, true); + IUndoAction action = new DeleteActorsAction(new List(1) { actorNode }, true); + if (autoSelect) + { + var before = Selection.ToArray(); + Selection.Clear(); + Selection.Add(actorNode); + OnSelectionChanged(); + action = new MultiUndoAction(action, new SelectionChangeAction(before, Selection.ToArray(), OnSelectionUndo)); + } Undo.AddAction(action); // Mark scene as dirty @@ -449,7 +458,15 @@ namespace FlaxEditor.Modules var pasteAction = PasteActorsAction.Paste(data, pasteTargetActor?.ID ?? Guid.Empty); if (pasteAction != null) { - OnPasteAction(pasteAction); + pasteAction.Do(out _, out var nodeParents); + + // Select spawned objects (parents only) + var selectAction = new SelectionChangeAction(Selection.ToArray(), nodeParents.Cast().ToArray(), OnSelectionUndo); + selectAction.Do(); + + // Build single compound undo action that pastes the actors and selects the created objects (parents only) + Undo.AddAction(new MultiUndoAction(pasteAction, selectAction)); + OnSelectionChanged(); } } @@ -468,12 +485,57 @@ namespace FlaxEditor.Modules public void Duplicate() { // Peek things that can be copied (copy all actors) - var objects = Selection.Where(x => x.CanCopyPaste).ToList().BuildAllNodes().Where(x => x.CanCopyPaste && x is ActorNode).ToList(); - if (objects.Count == 0) + var nodes = Selection.Where(x => x.CanDuplicate).ToList().BuildAllNodes(); + if (nodes.Count == 0) return; + var actors = new List(); + var newSelection = new List(); + List customUndoActions = null; + foreach (var node in nodes) + { + if (node.CanDuplicate) + { + if (node is ActorNode actorNode) + { + actors.Add(actorNode.Actor); + } + else + { + var customDuplicatedObject = node.Duplicate(out var customUndoAction); + if (customDuplicatedObject != null) + newSelection.Add(customDuplicatedObject); + if (customUndoAction != null) + { + if (customUndoActions == null) + customUndoActions = new List(); + customUndoActions.Add(customUndoAction); + } + } + } + } + if (actors.Count == 0) + { + // Duplicate custom scene graph nodes only without actors + if (newSelection.Count != 0) + { + // Select spawned objects (parents only) + var selectAction = new SelectionChangeAction(Selection.ToArray(), newSelection.ToArray(), OnSelectionUndo); + selectAction.Do(); + + // Build a single compound undo action that pastes the actors, pastes custom stuff (scene graph extension) and selects the created objects (parents only) + var customUndoActionsCount = customUndoActions?.Count ?? 0; + var undoActions = new IUndoAction[1 + customUndoActionsCount]; + for (int i = 0; i < customUndoActionsCount; i++) + undoActions[i] = customUndoActions[i]; + undoActions[undoActions.Length - 1] = selectAction; + + Undo.AddAction(new MultiUndoAction(undoActions)); + OnSelectionChanged(); + } + return; + } // Serialize actors - var actors = objects.ConvertAll(x => ((ActorNode)x).Actor); var data = Actor.ToBytes(actors.ToArray()); if (data == null) { @@ -485,22 +547,26 @@ namespace FlaxEditor.Modules var pasteAction = PasteActorsAction.Duplicate(data, Guid.Empty); if (pasteAction != null) { - OnPasteAction(pasteAction); + pasteAction.Do(out _, out var nodeParents); + + // Select spawned objects (parents only) + newSelection.AddRange(nodeParents); + var selectAction = new SelectionChangeAction(Selection.ToArray(), newSelection.ToArray(), OnSelectionUndo); + selectAction.Do(); + + // Build a single compound undo action that pastes the actors, pastes custom stuff (scene graph extension) and selects the created objects (parents only) + var customUndoActionsCount = customUndoActions?.Count ?? 0; + var undoActions = new IUndoAction[2 + customUndoActionsCount]; + undoActions[0] = pasteAction; + for (int i = 0; i < customUndoActionsCount; i++) + undoActions[i + 1] = customUndoActions[i]; + undoActions[undoActions.Length - 1] = selectAction; + + Undo.AddAction(new MultiUndoAction(undoActions)); + OnSelectionChanged(); } } - private void OnPasteAction(PasteActorsAction pasteAction) - { - pasteAction.Do(out _, out var nodeParents); - - // Select spawned objects - var selectAction = new SelectionChangeAction(Selection.ToArray(), nodeParents.Cast().ToArray(), OnSelectionUndo); - selectAction.Do(); - - Undo.AddAction(new MultiUndoAction(pasteAction, selectAction)); - OnSelectionChanged(); - } - /// /// Called when selection gets changed. Invokes the other events and updates editor. Call it when you manually modify selected objects collection. /// diff --git a/Source/Editor/Modules/UIModule.cs b/Source/Editor/Modules/UIModule.cs index 6b01986a9..99d8f6e2a 100644 --- a/Source/Editor/Modules/UIModule.cs +++ b/Source/Editor/Modules/UIModule.cs @@ -507,7 +507,7 @@ namespace FlaxEditor.Modules _toolStripRotate = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Rotate32, () => Editor.MainTransformGizmo.ActiveMode = TransformGizmoBase.Mode.Rotate).LinkTooltip("Change Gizmo tool mode to Rotate (2)"); _toolStripScale = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Scale32, () => Editor.MainTransformGizmo.ActiveMode = TransformGizmoBase.Mode.Scale).LinkTooltip("Change Gizmo tool mode to Scale (3)"); ToolStrip.AddSeparator(); - _toolStripBuildScenes = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Build32, Editor.BuildScenesOrCancel).LinkTooltip("Build scenes data - CSG, navmesh, static lighting, env probes (Ctrl+F10)"); + _toolStripBuildScenes = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Build32, Editor.BuildScenesOrCancel).LinkTooltip("Build scenes data - CSG, navmesh, static lighting, env probes - configurable via Build Actions in editor options (Ctrl+F10)"); ToolStrip.AddSeparator(); _toolStripPlay = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Play32, Editor.Simulation.RequestPlayOrStopPlay).LinkTooltip("Start/Stop game (F5)"); _toolStripPause = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Pause32, Editor.Simulation.RequestResumeOrPause).LinkTooltip("Pause/Resume game(F6)"); diff --git a/Source/Editor/Options/GeneralOptions.cs b/Source/Editor/Options/GeneralOptions.cs index 9b5450f45..85174030f 100644 --- a/Source/Editor/Options/GeneralOptions.cs +++ b/Source/Editor/Options/GeneralOptions.cs @@ -32,6 +32,42 @@ namespace FlaxEditor.Options LastOpened, } + /// + /// The build actions. + /// + public enum BuildAction + { + /// + /// Builds Constructive Solid Geometry brushes into meshes. + /// + [Tooltip("Builds Constructive Solid Geometry brushes into meshes.")] + CSG, + + /// + /// Builds Env Probes and Sky Lights to prerendered cube textures. + /// + [Tooltip("Builds Env Probes and Sky Lights to prerendered cube textures.")] + EnvProbes, + + /// + /// Builds static lighting into lightmaps. + /// + [Tooltip("Builds static lighting into lightmaps.")] + StaticLighting, + + /// + /// Builds navigation meshes. + /// + [Tooltip("Builds navigation meshes.")] + NavMesh, + + /// + /// Compiles the scripts. + /// + [Tooltip("Compiles the scripts.")] + CompileScripts, + } + /// /// Gets or sets the scene to load on editor startup. /// @@ -53,6 +89,19 @@ namespace FlaxEditor.Options [EditorDisplay("General", "Editor FPS"), EditorOrder(110), Tooltip("Limit for the editor draw/update frames per second rate (FPS). Use higher values if you need more responsive interface or lower values to use less device power. Value 0 disables any limits.")] public float EditorFPS { get; set; } = 60.0f; + /// + /// Gets or sets the sequence of actions to perform when using Build Scenes button. Can be used to configure this as button (eg. compile code or just update navmesh). + /// + [EditorDisplay("General"), EditorOrder(200), Tooltip("The sequence of actions to perform when using Build Scenes button. Can be used to configure this as button (eg. compile code or just update navmesh).")] + public BuildAction[] BuildActions { get; set; } = + { + BuildAction.CSG, + BuildAction.EnvProbes, + BuildAction.StaticLighting, + BuildAction.EnvProbes, + BuildAction.NavMesh, + }; + /// /// Gets or sets a value indicating whether perform automatic scripts reload on main window focus. /// diff --git a/Source/Editor/Progress/ProgressHandler.cs b/Source/Editor/Progress/ProgressHandler.cs index 958612e51..c83258091 100644 --- a/Source/Editor/Progress/ProgressHandler.cs +++ b/Source/Editor/Progress/ProgressHandler.cs @@ -8,6 +8,7 @@ namespace FlaxEditor.Progress /// /// Base class for all editor handlers used to report actions progress to the user. /// + [HideInEditor] public abstract class ProgressHandler { /// diff --git a/Source/Editor/SceneGraph/ActorChildNode.cs b/Source/Editor/SceneGraph/ActorChildNode.cs index 0711aa20a..d514534ca 100644 --- a/Source/Editor/SceneGraph/ActorChildNode.cs +++ b/Source/Editor/SceneGraph/ActorChildNode.cs @@ -19,6 +19,11 @@ namespace FlaxEditor.SceneGraph /// public readonly int Index; + /// + /// Gets a value indicating whether this node can be selected directly without selecting parent actor node first. + /// + public virtual bool CanBeSelectedDirectly => false; + /// /// Initializes a new instance of the class. /// @@ -58,6 +63,9 @@ namespace FlaxEditor.SceneGraph /// public override bool CanCopyPaste => false; + /// + public override bool CanDuplicate => false; + /// public override bool CanDrag => false; @@ -66,6 +74,18 @@ namespace FlaxEditor.SceneGraph /// public override object UndoRecordObject => ParentNode.UndoRecordObject; + + /// + public override void Dispose() + { + // Unlink from the parent + if (parentNode is ActorNode parentActorNode && parentActorNode.ActorChildNodes != null) + { + parentActorNode.ActorChildNodes.Remove(this); + } + + base.Dispose(); + } } /// @@ -77,20 +97,28 @@ namespace FlaxEditor.SceneGraph public abstract class ActorChildNode : ActorChildNode where T : ActorNode { /// - /// The actor. + /// The actor node. /// - protected readonly T _actor; + protected T _node; /// /// Initializes a new instance of the class. /// - /// The parent actor. + /// The parent actor node. /// The child id. /// The child index. - protected ActorChildNode(T actor, Guid id, int index) + protected ActorChildNode(T node, Guid id, int index) : base(id, index) { - _actor = actor; + _node = node; + } + + /// + public override void OnDispose() + { + _node = null; + + base.OnDispose(); } } } diff --git a/Source/Editor/SceneGraph/ActorNode.cs b/Source/Editor/SceneGraph/ActorNode.cs index 753440378..07ff6a3b5 100644 --- a/Source/Editor/SceneGraph/ActorNode.cs +++ b/Source/Editor/SceneGraph/ActorNode.cs @@ -41,7 +41,7 @@ namespace FlaxEditor.SceneGraph /// /// The actor child nodes used to represent special parts of the actor (meshes, links, surfaces). /// - public readonly List ActorChildNodes = new List(); + public List ActorChildNodes; /// /// Initializes a new instance of the class. @@ -108,6 +108,8 @@ namespace FlaxEditor.SceneGraph /// The node public ActorChildNode AddChildNode(ActorChildNode node) { + if (ActorChildNodes == null) + ActorChildNodes = new List(); ActorChildNodes.Add(node); node.ParentNode = this; return node; @@ -125,9 +127,12 @@ namespace FlaxEditor.SceneGraph root.OnActorChildNodesDispose(this); } - for (int i = 0; i < ActorChildNodes.Count; i++) - ActorChildNodes[i].Dispose(); - ActorChildNodes.Clear(); + if (ActorChildNodes != null) + { + for (int i = 0; i < ActorChildNodes.Count; i++) + ActorChildNodes[i].Dispose(); + ActorChildNodes.Clear(); + } } /// @@ -177,6 +182,9 @@ namespace FlaxEditor.SceneGraph /// public override bool CanCopyPaste => (_actor.HideFlags & HideFlags.HideInHierarchy) == 0; + /// + public override bool CanDuplicate => (_actor.HideFlags & HideFlags.HideInHierarchy) == 0; + /// public override bool IsActive => _actor.IsActive; @@ -275,8 +283,12 @@ namespace FlaxEditor.SceneGraph /// public override void Dispose() { - // Cleanup UI _treeNode.Dispose(); + if (ActorChildNodes != null) + { + ActorChildNodes.Clear(); + ActorChildNodes = null; + } base.Dispose(); } diff --git a/Source/Editor/SceneGraph/Actors/BoxVolumeNode.cs b/Source/Editor/SceneGraph/Actors/BoxVolumeNode.cs index 92b252138..ec62ebd47 100644 --- a/Source/Editor/SceneGraph/Actors/BoxVolumeNode.cs +++ b/Source/Editor/SceneGraph/Actors/BoxVolumeNode.cs @@ -78,14 +78,14 @@ namespace FlaxEditor.SceneGraph.Actors { get { - var actor = (BoxVolume)((BoxVolumeNode)ParentNode).Actor; + var actor = (BoxVolume)_node.Actor; var localOffset = _offset * actor.Size; Transform localTrans = new Transform(localOffset); return actor.Transform.LocalToWorld(localTrans); } set { - var actor = (BoxVolume)((BoxVolumeNode)ParentNode).Actor; + var actor = (BoxVolume)_node.Actor; Transform localTrans = actor.Transform.WorldToLocal(value); var prevLocalOffset = _offset * actor.Size; var localOffset = Vector3.Abs(_offset) * 2.0f * localTrans.Translation; diff --git a/Source/Editor/SceneGraph/Actors/NavLinkNode.cs b/Source/Editor/SceneGraph/Actors/NavLinkNode.cs index 8eb5fbc8b..62bc0a2e7 100644 --- a/Source/Editor/SceneGraph/Actors/NavLinkNode.cs +++ b/Source/Editor/SceneGraph/Actors/NavLinkNode.cs @@ -37,13 +37,13 @@ namespace FlaxEditor.SceneGraph.Actors { get { - var actor = (NavLink)((NavLinkNode)ParentNode).Actor; + var actor = (NavLink)_node.Actor; Transform localTrans = new Transform(_isStart ? actor.Start : actor.End); return actor.Transform.LocalToWorld(localTrans); } set { - var actor = (NavLink)((NavLinkNode)ParentNode).Actor; + var actor = (NavLink)_node.Actor; Transform localTrans = actor.Transform.WorldToLocal(value); if (_isStart) actor.Start = localTrans.Translation; diff --git a/Source/Editor/SceneGraph/Actors/NavModifierVolumeNode.cs b/Source/Editor/SceneGraph/Actors/NavModifierVolumeNode.cs new file mode 100644 index 000000000..38faba8ef --- /dev/null +++ b/Source/Editor/SceneGraph/Actors/NavModifierVolumeNode.cs @@ -0,0 +1,20 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +using FlaxEngine; + +namespace FlaxEditor.SceneGraph.Actors +{ + /// + /// Actor node for . + /// + /// + [HideInEditor] + public sealed class NavModifierVolumeNode : BoxVolumeNode + { + /// + public NavModifierVolumeNode(Actor actor) + : base(actor) + { + } + } +} diff --git a/Source/Editor/SceneGraph/Actors/SceneNode.cs b/Source/Editor/SceneGraph/Actors/SceneNode.cs index 359d3d207..35c71bf02 100644 --- a/Source/Editor/SceneGraph/Actors/SceneNode.cs +++ b/Source/Editor/SceneGraph/Actors/SceneNode.cs @@ -57,6 +57,9 @@ namespace FlaxEditor.SceneGraph.Actors /// public override bool CanCopyPaste => false; + /// + public override bool CanDuplicate => false; + /// public override bool CanDelete => false; diff --git a/Source/Editor/SceneGraph/Actors/SplineNode.cs b/Source/Editor/SceneGraph/Actors/SplineNode.cs new file mode 100644 index 000000000..3d41d0904 --- /dev/null +++ b/Source/Editor/SceneGraph/Actors/SplineNode.cs @@ -0,0 +1,372 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +using System; +using FlaxEditor.GUI.ContextMenu; +using FlaxEditor.Modules; +using FlaxEngine; +using FlaxEngine.Json; +using Object = FlaxEngine.Object; + +namespace FlaxEditor.SceneGraph.Actors +{ + /// + /// Scene tree node for actor type. + /// + [HideInEditor] + public sealed class SplineNode : ActorNode + { + private sealed class SplinePointNode : ActorChildNode + { + public unsafe SplinePointNode(SplineNode node, Guid id, int index) + : base(node, id, index) + { + var g = (JsonSerializer.GuidInterop*)&id; + g->D++; + AddChild(new SplinePointTangentNode(node, id, index, true)); + g->D++; + AddChild(new SplinePointTangentNode(node, id, index, false)); + } + + public override bool CanBeSelectedDirectly => true; + + public override bool CanDuplicate => true; + + public override bool CanDelete => true; + + public override bool CanUseState => true; + + public override Transform Transform + { + get + { + var actor = (Spline)_node.Actor; + return actor.GetSplineTransform(Index); + } + set + { + var actor = (Spline)_node.Actor; + actor.SetSplineTransform(Index, value); + OnSplineEdited(actor); + } + } + + struct Data + { + public Guid Spline; + public int Index; + public BezierCurve.Keyframe Keyframe; + } + + public override StateData State + { + get + { + var actor = (Spline)_node.Actor; + return new StateData + { + TypeName = typeof(SplinePointNode).FullName, + CreateMethodName = nameof(Create), + State = JsonSerializer.Serialize(new Data + { + Spline = actor.ID, + Index = Index, + Keyframe = actor.GetSplineKeyframe(Index), + }), + }; + } + set => throw new NotImplementedException(); + } + + public override void Delete() + { + var actor = (Spline)_node.Actor; + actor.RemoveSplinePoint(Index); + } + + class DuplicateUndoAction : IUndoAction, ISceneEditAction + { + public Guid SplineId; + public int Index; + public float Time; + public Transform Value; + + public string ActionString => "Duplicate spline point"; + + public void Dispose() + { + } + + public void Do() + { + var spline = Object.Find(ref SplineId); + spline.InsertSplineLocalPoint(Index, Time, Value); + } + + public void Undo() + { + var spline = Object.Find(ref SplineId); + spline.RemoveSplinePoint(Index); + } + + public void MarkSceneEdited(SceneModule sceneModule) + { + var spline = Object.Find(ref SplineId); + sceneModule.MarkSceneEdited(spline.Scene); + OnSplineEdited(spline); + } + } + + public override SceneGraphNode Duplicate(out IUndoAction undoAction) + { + var actor = (Spline)_node.Actor; + int newIndex; + float newTime; + if (Index == actor.SplinePointsCount - 1) + { + // Append to the end + newIndex = actor.SplinePointsCount; + newTime = actor.GetSplineTime(newIndex - 1) + 1.0f; + } + else + { + // Insert between this and next point + newIndex = Index + 1; + newTime = (actor.GetSplineTime(Index) + actor.GetSplineTime(Index + 1)) * 0.5f; + } + var action = new DuplicateUndoAction + { + SplineId = actor.ID, + Index = newIndex, + Time = newTime, + Value = actor.GetSplineLocalTransform(Index), + }; + actor.InsertSplineLocalPoint(newIndex, newTime, action.Value); + undoAction = action; + var splineNode = (SplineNode)SceneGraphFactory.FindNode(action.SplineId); + splineNode.OnUpdate(); + OnSplineEdited(actor); + return splineNode.ActorChildNodes[newIndex]; + } + + public override bool RayCastSelf(ref RayCastData ray, out float distance, out Vector3 normal) + { + var actor = (Spline)_node.Actor; + var pos = actor.GetSplinePoint(Index); + normal = -ray.Ray.Direction; + return new BoundingSphere(pos, 7.0f).Intersects(ref ray.Ray, out distance); + } + + public override void OnDebugDraw(ViewportDebugDrawData data) + { + var actor = (Spline)_node.Actor; + var pos = actor.GetSplinePoint(Index); + var tangentIn = actor.GetSplineTangent(Index, true).Translation; + var tangentOut = actor.GetSplineTangent(Index, false).Translation; + + // Draw spline path + ParentNode.OnDebugDraw(data); + + // Draw selected point highlight + DebugDraw.DrawSphere(new BoundingSphere(pos, 5.0f), Color.Yellow, 0, false); + + // Draw tangent points + if (tangentIn != pos) + { + DebugDraw.DrawLine(pos, tangentIn, Color.White.AlphaMultiplied(0.6f), 0, false); + DebugDraw.DrawWireSphere(new BoundingSphere(tangentIn, 4.0f), Color.White, 0, false); + } + if (tangentIn != pos) + { + DebugDraw.DrawLine(pos, tangentOut, Color.White.AlphaMultiplied(0.6f), 0, false); + DebugDraw.DrawWireSphere(new BoundingSphere(tangentOut, 4.0f), Color.White, 0, false); + } + } + + public override void OnContextMenu(ContextMenu contextMenu) + { + ParentNode.OnContextMenu(contextMenu); + } + + public static SceneGraphNode Create(StateData state) + { + var data = JsonSerializer.Deserialize(state.State); + var spline = Object.Find(ref data.Spline); + spline.InsertSplineLocalPoint(data.Index, data.Keyframe.Time, data.Keyframe.Value, false); + spline.SetSplineKeyframe(data.Index, data.Keyframe); + var splineNode = (SplineNode)SceneGraphFactory.FindNode(data.Spline); + if (splineNode == null) + return null; + splineNode.OnUpdate(); + OnSplineEdited(spline); + return splineNode.ActorChildNodes[data.Index]; + } + } + + private sealed class SplinePointTangentNode : ActorChildNode + { + private SplineNode _node; + private int _index; + private bool _isIn; + + public SplinePointTangentNode(SplineNode node, Guid id, int index, bool isIn) + : base(id, isIn ? 0 : 1) + { + _node = node; + _index = index; + _isIn = isIn; + } + + public override Transform Transform + { + get + { + var actor = (Spline)_node.Actor; + return actor.GetSplineTangent(_index, _isIn); + } + set + { + var actor = (Spline)_node.Actor; + actor.SetSplineTangent(_index, value, _isIn); + } + } + + public override bool RayCastSelf(ref RayCastData ray, out float distance, out Vector3 normal) + { + var actor = (Spline)_node.Actor; + var pos = actor.GetSplineTangent(_index, _isIn).Translation; + normal = -ray.Ray.Direction; + return new BoundingSphere(pos, 7.0f).Intersects(ref ray.Ray, out distance); + } + + public override void OnDebugDraw(ViewportDebugDrawData data) + { + // Draw spline and spline point + ParentNode.OnDebugDraw(data); + + // Draw selected tangent highlight + var actor = (Spline)_node.Actor; + var pos = actor.GetSplineTangent(_index, _isIn).Translation; + DebugDraw.DrawSphere(new BoundingSphere(pos, 5.0f), Color.YellowGreen, 0, false); + } + + public override void OnContextMenu(ContextMenu contextMenu) + { + ParentNode.OnContextMenu(contextMenu); + } + + public override void OnDispose() + { + _node = null; + + base.OnDispose(); + } + } + + /// + public SplineNode(Actor actor) + : base(actor) + { + OnUpdate(); + FlaxEngine.Scripting.Update += OnUpdate; + } + + private unsafe void OnUpdate() + { + // Sync spline points with gizmo handles + var actor = (Spline)Actor; + var dstCount = actor.SplinePointsCount; + if (dstCount > 1 && actor.IsLoop) + dstCount--; // The last point is the same as the first one for loop mode + var srcCount = ActorChildNodes?.Count ?? 0; + if (dstCount != srcCount) + { + // Remove unused points + while (srcCount > dstCount) + ActorChildNodes[srcCount-- - 1].Dispose(); + + // Add new points + var id = ID; + var g = (JsonSerializer.GuidInterop*)&id; + g->D += (uint)srcCount * 3; + while (srcCount < dstCount) + { + g->D += 3; + AddChildNode(new SplinePointNode(this, id, srcCount++)); + } + } + } + + /// + public override void PostSpawn() + { + base.PostSpawn(); + + // Setup for an initial spline + var spline = (Spline)Actor; + spline.AddSplineLocalPoint(Vector3.Zero, false); + spline.AddSplineLocalPoint(new Vector3(0, 0, 100.0f)); + } + + /// + public override void OnContextMenu(ContextMenu contextMenu) + { + base.OnContextMenu(contextMenu); + + contextMenu.AddButton("Add spline model", OnAddSplineModel); + contextMenu.AddButton("Add spline collider", OnAddSplineCollider); + contextMenu.AddButton("Add spline rope body", OnAddSplineRopeBody); + } + + private void OnAddSplineModel() + { + var actor = new SplineModel + { + StaticFlags = Actor.StaticFlags, + Transform = Actor.Transform, + }; + Editor.Instance.SceneEditing.Spawn(actor, Actor); + } + + private void OnAddSplineCollider() + { + var actor = new SplineCollider + { + StaticFlags = Actor.StaticFlags, + Transform = Actor.Transform, + }; + // TODO: auto pick the collision data if already using spline model + Editor.Instance.SceneEditing.Spawn(actor, Actor); + } + + private void OnAddSplineRopeBody() + { + var actor = new SplineRopeBody + { + StaticFlags = StaticFlags.None, + Transform = Actor.Transform, + }; + Editor.Instance.SceneEditing.Spawn(actor, Actor); + } + + internal static void OnSplineEdited(Spline spline) + { + var collider = spline.GetChild(); + if (collider && collider.Scene && collider.IsActiveInHierarchy && collider.HasStaticFlag(StaticFlags.Navigation) && !Editor.IsPlayMode) + { + var options = Editor.Instance.Options.Options.General; + if (options.AutoRebuildNavMesh) + { + Navigation.BuildNavMesh(collider.Scene, collider.Box, options.AutoRebuildNavMeshTimeoutMs); + } + } + } + + /// + public override void OnDispose() + { + FlaxEngine.Scripting.Update -= OnUpdate; + + base.OnDispose(); + } + } +} diff --git a/Source/Editor/SceneGraph/RootNode.cs b/Source/Editor/SceneGraph/RootNode.cs index fec8f68ad..10af88489 100644 --- a/Source/Editor/SceneGraph/RootNode.cs +++ b/Source/Editor/SceneGraph/RootNode.cs @@ -56,6 +56,9 @@ namespace FlaxEditor.SceneGraph /// public override bool CanCopyPaste => false; + /// + public override bool CanDuplicate => false; + /// public override bool CanDelete => false; diff --git a/Source/Editor/SceneGraph/SceneGraphFactory.cs b/Source/Editor/SceneGraph/SceneGraphFactory.cs index 68cb627f0..8103f5f05 100644 --- a/Source/Editor/SceneGraph/SceneGraphFactory.cs +++ b/Source/Editor/SceneGraph/SceneGraphFactory.cs @@ -62,8 +62,14 @@ namespace FlaxEditor.SceneGraph CustomNodesTypes.Add(typeof(NavMeshBoundsVolume), typeof(NavMeshBoundsVolumeNode)); CustomNodesTypes.Add(typeof(BoxVolume), typeof(BoxVolumeNode)); CustomNodesTypes.Add(typeof(NavLink), typeof(NavLinkNode)); + CustomNodesTypes.Add(typeof(NavModifierVolume), typeof(NavModifierVolumeNode)); CustomNodesTypes.Add(typeof(ParticleEffect), typeof(ParticleEffectNode)); CustomNodesTypes.Add(typeof(SceneAnimationPlayer), typeof(SceneAnimationPlayerNode)); + CustomNodesTypes.Add(typeof(Spline), typeof(SplineNode)); + CustomNodesTypes.Add(typeof(SplineModel), typeof(ActorNode)); + CustomNodesTypes.Add(typeof(SplineCollider), typeof(ColliderNode)); + CustomNodesTypes.Add(typeof(SplineRopeBody), typeof(ActorNode)); + CustomNodesTypes.Add(typeof(NavMesh), typeof(ActorNode)); } /// diff --git a/Source/Editor/SceneGraph/SceneGraphNode.cs b/Source/Editor/SceneGraph/SceneGraphNode.cs index 755aea139..835295147 100644 --- a/Source/Editor/SceneGraph/SceneGraphNode.cs +++ b/Source/Editor/SceneGraph/SceneGraphNode.cs @@ -15,7 +15,7 @@ namespace FlaxEditor.SceneGraph /// A class is responsible for Scene Graph management. /// [HideInEditor] - public abstract class SceneGraphNode : ITransformable + public abstract class SceneGraphNode { /// /// The parent node. @@ -65,6 +65,11 @@ namespace FlaxEditor.SceneGraph /// public virtual bool CanCopyPaste => true; + /// + /// Gets a value indicating whether this instance can be duplicated by the user. + /// + public virtual bool CanDuplicate => true; + /// /// Gets a value indicating whether this node can be deleted by the user. /// @@ -312,6 +317,53 @@ namespace FlaxEditor.SceneGraph { } + /// + /// Called when scene tree window wants to show the context menu. Allows to add custom options. + /// + public virtual void OnContextMenu(FlaxEditor.GUI.ContextMenu.ContextMenu contextMenu) + { + } + + /// + /// The scene graph node state container. Used for Editor undo actions (eg. restoring deleted node). + /// + public struct StateData + { + /// + /// The name of the scene graph node type (full). + /// + public string TypeName; + + /// + /// The name of the method (in ) that takes this state as a parameter and returns the created scene graph node. Used by the undo actions to restore deleted objects. + /// + public string CreateMethodName; + + /// + /// The custom state data (as string). + /// + public string State; + + /// + /// The custom state data (as raw bytes). + /// + public byte[] StateRaw; + } + + /// + /// Gets a value indicating whether this node can use property for editor undo operations. + /// + public virtual bool CanUseState => false; + + /// + /// Gets or sets the node state. + /// + public virtual StateData State + { + get => throw new NotImplementedException(); + set => throw new NotImplementedException(); + } + /// /// Deletes object represented by this node eg. actor. /// @@ -319,6 +371,17 @@ namespace FlaxEditor.SceneGraph { } + /// + /// Duplicates this object. Valid only if returns true. + /// + /// The undo action that duplicated the object (already performed), null if skip it. + /// The duplicated object node. + public virtual SceneGraphNode Duplicate(out IUndoAction undoAction) + { + undoAction = null; + return null; + } + /// /// Releases the node and the child tree. Disposed all GUI parts and used resources. /// diff --git a/Source/Editor/Scripting/ScriptType.cs b/Source/Editor/Scripting/ScriptType.cs index 038c1b745..53c84a1b5 100644 --- a/Source/Editor/Scripting/ScriptType.cs +++ b/Source/Editor/Scripting/ScriptType.cs @@ -53,6 +53,8 @@ namespace FlaxEditor.Scripting return fieldInfo.IsPublic; if (_managed is PropertyInfo propertyInfo) return (propertyInfo.GetMethod == null || propertyInfo.GetMethod.IsPublic) && (propertyInfo.SetMethod == null || propertyInfo.SetMethod.IsPublic); + if (_managed is EventInfo eventInfo) + return eventInfo.GetAddMethod().IsPublic; if (_custom != null) return _custom.IsPublic; return false; @@ -72,6 +74,8 @@ namespace FlaxEditor.Scripting return fieldInfo.IsStatic; if (_managed is PropertyInfo propertyInfo) return (propertyInfo.GetMethod == null || propertyInfo.GetMethod.IsStatic) && (propertyInfo.SetMethod == null || propertyInfo.SetMethod.IsStatic); + if (_managed is EventInfo eventInfo) + return eventInfo.GetAddMethod().IsStatic; if (_custom != null) return _custom.IsStatic; return false; @@ -138,6 +142,11 @@ namespace FlaxEditor.Scripting /// public bool IsMethod => _managed is MethodInfo || (_custom?.IsMethod ?? false); + /// + /// Gets a value indicating whether this member is an event. + /// + public bool IsEvent => _managed is EventInfo || (_custom?.IsEvent ?? false); + /// /// Gets a value indicating whether this member value can be gathered (via getter method or directly from the field). /// @@ -173,7 +182,7 @@ namespace FlaxEditor.Scripting } /// - /// Gets the method parameters count (valid for methods only). + /// Gets the method parameters count (valid for methods and events only). /// public int ParametersCount { @@ -181,6 +190,8 @@ namespace FlaxEditor.Scripting { if (_managed is MethodInfo methodInfo) return methodInfo.GetParameters().Length; + if (_managed is EventInfo eventInfo) + return eventInfo.EventHandlerType.GetMethod("Invoke").GetParameters().Length; if (_custom != null) return _custom.ParametersCount; return 0; @@ -383,7 +394,7 @@ namespace FlaxEditor.Scripting } /// - /// Gets the method parameters metadata. + /// Gets the method parameters metadata (or event delegate signature parameters). /// public Parameter[] GetParameters() { @@ -416,6 +427,11 @@ namespace FlaxEditor.Scripting } return result; } + if (_managed is EventInfo eventInfo) + { + var invokeMethod = eventInfo.EventHandlerType.GetMethod("Invoke"); + return new ScriptMemberInfo(invokeMethod).GetParameters(); + } return _custom.GetParameters(); } @@ -634,6 +650,11 @@ namespace FlaxEditor.Scripting /// public static readonly ScriptType Null; + /// + /// A that is System.Void. + /// + public static readonly ScriptType Void = new ScriptType(typeof(void)); + /// /// Gets the type of the script as . /// @@ -1463,6 +1484,11 @@ namespace FlaxEditor.Scripting /// bool IsMethod { get; } + /// + /// Gets a value indicating whether this member is an event. + /// + bool IsEvent { get; } + /// /// Gets a value indicating whether this member value can be gathered (via getter method or directly from the field). /// @@ -1504,7 +1530,7 @@ namespace FlaxEditor.Scripting object[] GetAttributes(bool inherit); /// - /// Gets the method parameters metadata. + /// Gets the method parameters metadata (or event delegate signature parameters). /// ScriptMemberInfo.Parameter[] GetParameters(); diff --git a/Source/Editor/Scripting/TypeUtils.cs b/Source/Editor/Scripting/TypeUtils.cs index 70108f06f..60f64ab8e 100644 --- a/Source/Editor/Scripting/TypeUtils.cs +++ b/Source/Editor/Scripting/TypeUtils.cs @@ -59,11 +59,19 @@ namespace FlaxEditor.Scripting if (type.Type == typeof(MaterialSceneTextures)) return MaterialSceneTextures.BaseColor; if (type.IsValueType) - return type.CreateInstance(); + { + var value = type.CreateInstance(); + Utilities.Utils.InitDefaultValues(value); + return value; + } if (new ScriptType(typeof(object)).IsAssignableFrom(type)) return null; if (type.CanCreateInstance) - return type.CreateInstance(); + { + var value = type.CreateInstance(); + Utilities.Utils.InitDefaultValues(value); + return value; + } throw new NotSupportedException("Cannot create default value for type " + type); } diff --git a/Source/Editor/States/BuildingScenesState.cs b/Source/Editor/States/BuildingScenesState.cs index 998292626..40ccff829 100644 --- a/Source/Editor/States/BuildingScenesState.cs +++ b/Source/Editor/States/BuildingScenesState.cs @@ -1,6 +1,9 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using System; +using System.Collections.Generic; +using System.Linq; +using FlaxEditor.Options; using FlaxEditor.SceneGraph.Actors; using FlaxEngine; using FlaxEngine.Utilities; @@ -16,6 +19,9 @@ namespace FlaxEditor.States { private sealed class SubStateMachine : StateMachine { + public int ActionIndex = -1; + public readonly List Actions = new List(); + protected override void SwitchState(State nextState) { if (CurrentState != null && nextState != null) @@ -27,10 +33,40 @@ namespace FlaxEditor.States private abstract class SubState : State { + public virtual bool DirtyScenes => true; + + public virtual bool CanReloadScripts => false; + + public virtual void Before() + { + } + public virtual void Update() { } + public virtual void Done() + { + var stateMachine = (SubStateMachine)StateMachine; + stateMachine.ActionIndex++; + if (stateMachine.ActionIndex < stateMachine.Actions.Count) + { + var action = stateMachine.Actions[stateMachine.ActionIndex]; + var state = stateMachine.States.FirstOrDefault(x => x is ActionState a && a.Action == action); + if (state != null) + { + StateMachine.GoToState(state); + } + else + { + Editor.LogError($"Missing or invalid build scene action {action}."); + } + return; + } + + StateMachine.GoToState(); + } + public virtual void Cancel() { StateMachine.GoToState(); @@ -45,18 +81,31 @@ namespace FlaxEditor.States { public override void OnEnter() { - var editor = Editor.Instance; - foreach (var scene in Level.Scenes) + var stateMachine = (SubStateMachine)StateMachine; + var scenesDirty = false; + foreach (var state in stateMachine.States) { - scene.ClearLightmaps(); - editor.Scene.MarkSceneEdited(scene); + ((SubState)state).Before(); + scenesDirty |= ((SubState)state).DirtyScenes; } - StateMachine.GoToState(); + if (scenesDirty) + { + foreach (var scene in Level.Scenes) + Editor.Instance.Scene.MarkSceneEdited(scene); + } + Done(); } } - private sealed class CSGState : SubState + private abstract class ActionState : SubState { + public abstract GeneralOptions.BuildAction Action { get; } + } + + private sealed class CSGState : ActionState + { + public override GeneralOptions.BuildAction Action => GeneralOptions.BuildAction.CSG; + public override void OnEnter() { foreach (var scene in Level.Scenes) @@ -68,13 +117,14 @@ namespace FlaxEditor.States public override void Update() { if (!Editor.Internal_GetIsCSGActive()) - StateMachine.GoToState(); + Done(); } } - - private class EnvProbesNoGIState : SubState + private class EnvProbesState : ActionState { + public override GeneralOptions.BuildAction Action => GeneralOptions.BuildAction.EnvProbes; + public override void OnEnter() { Editor.Instance.Scene.ExecuteOnGraph(node => @@ -94,12 +144,20 @@ namespace FlaxEditor.States public override void Update() { if (!Editor.Instance.ProgressReporting.BakeEnvProbes.IsActive) - StateMachine.GoToState(); + Done(); } } - private sealed class StaticLightingState : SubState + private sealed class StaticLightingState : ActionState { + public override GeneralOptions.BuildAction Action => GeneralOptions.BuildAction.StaticLighting; + + public override void Before() + { + foreach (var scene in Level.Scenes) + scene.ClearLightmaps(); + } + public override void OnEnter() { Editor.LightmapsBakeEnd += OnLightmapsBakeEnd; @@ -110,7 +168,6 @@ namespace FlaxEditor.States OnLightmapsBakeEnd(false); } - /// public override void Cancel() { Editor.Internal_BakeLightmaps(true); @@ -125,21 +182,14 @@ namespace FlaxEditor.States private void OnLightmapsBakeEnd(bool failed) { - StateMachine.GoToState(); + Done(); } } - private sealed class EnvProbesWithGIState : EnvProbesNoGIState + private sealed class NavMeshState : ActionState { - public override void Update() - { - if (!Editor.Instance.ProgressReporting.BakeEnvProbes.IsActive) - StateMachine.GoToState(); - } - } + public override GeneralOptions.BuildAction Action => GeneralOptions.BuildAction.NavMesh; - private sealed class NavMeshState : SubState - { public override void OnEnter() { foreach (var scene in Level.Scenes) @@ -151,7 +201,58 @@ namespace FlaxEditor.States public override void Update() { if (!Navigation.IsBuildingNavMesh) - StateMachine.GoToState(); + Done(); + } + } + + private sealed class CompileScriptsState : ActionState + { + private bool _compiled, _reloaded; + + public override GeneralOptions.BuildAction Action => GeneralOptions.BuildAction.CompileScripts; + + public override bool DirtyScenes => false; + + public override bool CanReloadScripts => true; + + public override void OnEnter() + { + _compiled = _reloaded = false; + ScriptsBuilder.Compile(); + + ScriptsBuilder.CompilationSuccess += OnCompilationSuccess; + ScriptsBuilder.CompilationFailed += OnCompilationFailed; + ScriptsBuilder.ScriptsReloadEnd += OnScriptsReloadEnd; + } + + public override void OnExit(State nextState) + { + ScriptsBuilder.CompilationSuccess -= OnCompilationSuccess; + ScriptsBuilder.CompilationFailed -= OnCompilationFailed; + ScriptsBuilder.ScriptsReloadEnd -= OnScriptsReloadEnd; + + base.OnExit(nextState); + } + + private void OnCompilationSuccess() + { + _compiled = true; + } + + private void OnCompilationFailed() + { + Cancel(); + } + + private void OnScriptsReloadEnd() + { + _reloaded = true; + } + + public override void Update() + { + if (_compiled && _reloaded) + Done(); } } @@ -173,10 +274,10 @@ namespace FlaxEditor.States _stateMachine.AddState(new BeginState()); _stateMachine.AddState(new SetupState()); _stateMachine.AddState(new CSGState()); - _stateMachine.AddState(new EnvProbesNoGIState()); + _stateMachine.AddState(new EnvProbesState()); _stateMachine.AddState(new StaticLightingState()); - _stateMachine.AddState(new EnvProbesWithGIState()); _stateMachine.AddState(new NavMeshState()); + _stateMachine.AddState(new CompileScriptsState()); _stateMachine.AddState(new EndState()); _stateMachine.GoToState(); } @@ -192,6 +293,9 @@ namespace FlaxEditor.States /// public override bool CanEditContent => false; + /// + public override bool CanReloadScripts => ((SubState)_stateMachine.CurrentState).CanReloadScripts; + /// public override bool IsPerformanceHeavy => true; @@ -215,6 +319,11 @@ namespace FlaxEditor.States { Editor.Log("Starting scenes build..."); _startTime = DateTime.Now; + _stateMachine.ActionIndex = -1; + _stateMachine.Actions.Clear(); + var actions = (GeneralOptions.BuildAction[])Editor.Options.Options.General.BuildActions?.Clone(); + if (actions != null) + _stateMachine.Actions.AddRange(actions); _stateMachine.GoToState(); } diff --git a/Source/Editor/States/EditingSceneState.cs b/Source/Editor/States/EditingSceneState.cs index 7aa9ce13d..176039948 100644 --- a/Source/Editor/States/EditingSceneState.cs +++ b/Source/Editor/States/EditingSceneState.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using FlaxEngine; +using FlaxEngine.Utilities; namespace FlaxEditor.States { @@ -39,5 +40,26 @@ namespace FlaxEditor.States { UpdateFPS(); } + + /// + public override void OnEnter() + { + base.OnEnter(); + + ScriptsBuilder.ScriptsReloadBegin += OnScriptsReloadBegin; + } + + /// + public override void OnExit(State nextState) + { + ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin; + + base.OnExit(nextState); + } + + private void OnScriptsReloadBegin() + { + StateMachine.GoToState(); + } } } diff --git a/Source/Editor/States/ReloadingScriptsState.cs b/Source/Editor/States/ReloadingScriptsState.cs index 17f1abdab..1fb34317d 100644 --- a/Source/Editor/States/ReloadingScriptsState.cs +++ b/Source/Editor/States/ReloadingScriptsState.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using FlaxEngine; +using FlaxEngine.Utilities; namespace FlaxEditor.States { @@ -18,5 +19,26 @@ namespace FlaxEditor.States : base(editor) { } + + /// + public override void OnEnter() + { + base.OnEnter(); + + ScriptsBuilder.ScriptsReloadEnd += OnScriptsReloadEnd; + } + + /// + public override void OnExit(State nextState) + { + ScriptsBuilder.ScriptsReloadEnd -= OnScriptsReloadEnd; + + base.OnExit(nextState); + } + + private void OnScriptsReloadEnd() + { + StateMachine.GoToState(); + } } } diff --git a/Source/Editor/Surface/Archetypes/Function.cs b/Source/Editor/Surface/Archetypes/Function.cs index 9f7e3757a..c5707d1dd 100644 --- a/Source/Editor/Surface/Archetypes/Function.cs +++ b/Source/Editor/Surface/Archetypes/Function.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; @@ -1187,6 +1188,12 @@ namespace FlaxEditor.Surface.Archetypes [TypeReference(typeof(object), nameof(IsTypeValid))] public ScriptType Type; + public Parameter(ref ScriptMemberInfo.Parameter param) + { + Name = param.Name; + Type = param.Type; + } + private static bool IsTypeValid(ScriptType type) { return SurfaceUtils.IsValidVisualScriptFunctionType(type) && !type.IsVoid; @@ -1394,6 +1401,9 @@ namespace FlaxEditor.Surface.Archetypes } } + /// + /// The cached signature. This might not be loaded if called from other not initialization (eg. node initialized before this function node). Then use GetSignature method. + /// internal Signature _signature; /// @@ -1402,6 +1412,13 @@ namespace FlaxEditor.Surface.Archetypes { } + public void GetSignature(out Signature signature) + { + if (_signature.Node == null) + LoadSignature(); + signature = _signature; + } + private void SaveSignature() { using (var stream = new MemoryStream()) @@ -1575,6 +1592,13 @@ namespace FlaxEditor.Surface.Archetypes // Update node interface UpdateUI(); + + // Send event + for (int i = 0; i < Surface.Nodes.Count; i++) + { + if (Surface.Nodes[i] is IFunctionsDependantNode node) + node.OnFunctionEdited(this); + } }; editor.Show(this, Vector2.Zero); } @@ -1629,6 +1653,13 @@ namespace FlaxEditor.Surface.Archetypes base.OnLoaded(); LoadSignature(); + + // Send event + for (int i = 0; i < Surface.Nodes.Count; i++) + { + if (Surface.Nodes[i] is IFunctionsDependantNode node) + node.OnFunctionCreated(this); + } } /// @@ -1637,6 +1668,7 @@ namespace FlaxEditor.Surface.Archetypes base.OnSpawned(); // Setup initial signature + var defaultSignature = _signature.Node == null; CheckFunctionName(ref _signature.Name); if (_signature.ReturnType == ScriptType.Null) _signature.ReturnType = new ScriptType(typeof(void)); @@ -1644,8 +1676,31 @@ namespace FlaxEditor.Surface.Archetypes SaveSignature(); UpdateUI(); - // Start editing - OnEditSignature(); + if (defaultSignature) + { + // Start editing + OnEditSignature(); + } + + // Send event + for (int i = 0; i < Surface.Nodes.Count; i++) + { + if (Surface.Nodes[i] is IFunctionsDependantNode node) + node.OnFunctionCreated(this); + } + } + + /// + public override void OnDeleted() + { + // Send event + for (int i = 0; i < Surface.Nodes.Count; i++) + { + if (Surface.Nodes[i] is IFunctionsDependantNode node) + node.OnFunctionDeleted(this); + } + + base.OnDeleted(); } /// @@ -1654,6 +1709,13 @@ namespace FlaxEditor.Surface.Archetypes base.OnValuesChanged(); LoadSignature(); + + // Send event + for (int i = 0; i < Surface.Nodes.Count; i++) + { + if (Surface.Nodes[i] is IFunctionsDependantNode node) + node.OnFunctionEdited(this); + } } /// @@ -1759,7 +1821,6 @@ namespace FlaxEditor.Surface.Archetypes { private bool _isTypesChangedEventRegistered; - /// public SetFieldNode(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch) : base(id, context, nodeArch, groupArch) { @@ -1847,6 +1908,254 @@ namespace FlaxEditor.Surface.Archetypes } } + private abstract class EventBaseNode : SurfaceNode, IFunctionsDependantNode + { + private ComboBoxElement _combobox; + private Image _helperButton; + private bool _isBind; + private bool _isUpdateLocked = true; + private List _tooltips = new List(); + private List _functionNodesIds = new List(); + private ScriptMemberInfo.Parameter[] _signature; + + protected EventBaseNode(bool isBind, uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch) + : base(id, context, nodeArch, groupArch) + { + _isBind = isBind; + } + + private bool IsValidFunctionSignature(ref VisualScriptFunctionNode.Signature sig) + { + if (!sig.ReturnType.IsVoid || sig.Parameters == null || sig.Parameters.Length != _signature.Length) + return false; + for (int i = 0; i < _signature.Length; i++) + { + if (_signature[i].Type != sig.Parameters[i].Type) + return false; + } + return true; + } + + private void UpdateUI() + { + if (_isUpdateLocked) + return; + _isUpdateLocked = true; + if (_combobox == null) + { + _combobox = (ComboBoxElement)_children[4]; + _combobox.TooltipText = _isBind ? "Select the function to call when the event occurs" : "Select the function to unbind from the event"; + _combobox.SelectedIndexChanged += OnSelectedChanged; + _helperButton = new Image + { + Location = _combobox.UpperRight + new Vector2(4, 3), + Size = new Vector2(12.0f), + Parent = this, + }; + _helperButton.Clicked += OnHelperButtonClicked; + } + int toSelect = -1; + var handlerFunctionNodeId = Convert.ToUInt32(Values[2]); + _combobox.ClearItems(); + _tooltips.Clear(); + _functionNodesIds.Clear(); + var nodes = Surface.Nodes; + var count = _signature != null ? nodes.Count : 0; + for (int i = 0; i < count; i++) + { + if (nodes[i] is VisualScriptFunctionNode functionNode) + { + // Get if function signature matches the event signature + functionNode.GetSignature(out var functionSig); + if (IsValidFunctionSignature(ref functionSig)) + { + if (functionNode.ID == handlerFunctionNodeId) + toSelect = _functionNodesIds.Count; + _functionNodesIds.Add(functionNode.ID); + _tooltips.Add(functionNode.TooltipText); + _combobox.AddItem(functionSig.ToString()); + } + } + } + _combobox.Tooltips = _tooltips.Count != 0 ? _tooltips.ToArray() : null; + _combobox.Enabled = _tooltips.Count != 0; + _combobox.SelectedIndex = toSelect; + if (toSelect != -1) + { + _helperButton.Brush = new SpriteBrush(Editor.Instance.Icons.Search12); + _helperButton.Color = Color.White; + _helperButton.TooltipText = "Navigate to the handler function"; + } + else if (_isBind) + { + _helperButton.Brush = new SpriteBrush(Editor.Instance.Icons.Add48); + _helperButton.Color = Color.Red; + _helperButton.TooltipText = "Add new handler function and bind it to this event"; + _helperButton.Enabled = _signature != null; + } + else + { + _helperButton.Enabled = false; + } + ResizeAuto(); + _isUpdateLocked = false; + } + + private void OnHelperButtonClicked(Image img, MouseButton mouseButton) + { + if (mouseButton != MouseButton.Left) + return; + if (_combobox.SelectedIndex != -1) + { + // Focus selected function + var handlerFunctionNodeId = Convert.ToUInt32(Values[2]); + var handlerFunctionNode = Surface.FindNode(handlerFunctionNodeId); + Surface.FocusNode(handlerFunctionNode); + } + else if (_isBind) + { + // Create new function that matches the event signature + var surfaceBounds = Surface.AllNodesBounds; + Surface.ShowArea(new Rectangle(surfaceBounds.BottomLeft, new Vector2(200, 150)).MakeExpanded(400.0f)); + var node = Surface.Context.SpawnNode(16, 6, surfaceBounds.BottomLeft + new Vector2(0, 50), null, OnBeforeSpawnedNewHandler); + Surface.Select(node); + + // Bind this function + SetValue(2, node.ID); + } + } + + private void OnBeforeSpawnedNewHandler(SurfaceNode node) + { + // Initialize signature to match the event + var functionNode = (VisualScriptFunctionNode)node; + functionNode._signature = new VisualScriptFunctionNode.Signature + { + Name = "On" + (string)Values[1], + IsStatic = false, + IsVirtual = false, + Node = functionNode, + ReturnType = ScriptType.Void, + Parameters = new VisualScriptFunctionNode.Parameter[_signature.Length], + }; + for (int i = 0; i < _signature.Length; i++) + functionNode._signature.Parameters[i] = new VisualScriptFunctionNode.Parameter(ref _signature[i]); + } + + private void OnSelectedChanged(ComboBox cb) + { + if (_isUpdateLocked) + return; + var handlerFunctionNodeId = Convert.ToUInt32(Values[2]); + var selectedID = cb.SelectedIndex != -1 ? _functionNodesIds[cb.SelectedIndex] : 0u; + if (selectedID != handlerFunctionNodeId) + { + SetValue(2, selectedID); + UpdateUI(); + } + } + + public void OnFunctionCreated(SurfaceNode node) + { + UpdateUI(); + } + + public void OnFunctionEdited(SurfaceNode node) + { + UpdateUI(); + } + + public void OnFunctionDeleted(SurfaceNode node) + { + // Deselect if that function was selected + var handlerFunctionNodeId = Convert.ToUInt32(Values[2]); + if (node.ID == handlerFunctionNodeId) + _combobox.SelectedIndex = -1; + + UpdateUI(); + } + + public override void OnSurfaceLoaded() + { + base.OnSurfaceLoaded(); + + // Find reflection information about event + _signature = null; + var isStatic = false; + var eventName = (string)Values[1]; + var eventType = TypeUtils.GetType((string)Values[0]); + var member = eventType.GetMember(eventName, MemberTypes.Event, BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance); + if (member && SurfaceUtils.IsValidVisualScriptEvent(member)) + { + isStatic = member.IsStatic; + _signature = member.GetParameters(); + TooltipText = SurfaceUtils.GetVisualScriptMemberInfoDescription(member); + } + + // Setup instance box (static events don't need it) + var instanceBox = GetBox(1); + instanceBox.Visible = !isStatic; + if (isStatic) + instanceBox.RemoveConnections(); + else + instanceBox.CurrentType = eventType; + + _isUpdateLocked = false; + UpdateUI(); + } + + public override void OnValuesChanged() + { + base.OnValuesChanged(); + + UpdateUI(); + } + + /// + public override void OnDestroy() + { + _combobox = null; + _helperButton = null; + _tooltips.Clear(); + _tooltips = null; + _functionNodesIds.Clear(); + _functionNodesIds = null; + _signature = null; + + base.OnDestroy(); + } + } + + private sealed class BindEventNode : EventBaseNode + { + public BindEventNode(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch) + : base(true, id, context, nodeArch, groupArch) + { + } + + public override void OnSurfaceLoaded() + { + Title = "Bind " + (string)Values[1]; + + base.OnSurfaceLoaded(); + } + } + + private sealed class UnbindEventNode : EventBaseNode + { + public UnbindEventNode(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch) + : base(false, id, context, nodeArch, groupArch) + { + } + + public override void OnSurfaceLoaded() + { + Title = "Unbind " + (string)Values[1]; + + base.OnSurfaceLoaded(); + } + } + /// /// The nodes for that group. /// @@ -1988,6 +2297,50 @@ namespace FlaxEditor.Surface.Archetypes null, // Default value }, }, + new NodeArchetype + { + TypeID = 9, + Create = (id, context, arch, groupArch) => new BindEventNode(id, context, arch, groupArch), + Title = string.Empty, + Flags = NodeFlags.VisualScriptGraph | NodeFlags.NoSpawnViaGUI, + Size = new Vector2(260, 60), + DefaultValues = new object[] + { + string.Empty, // Event type + string.Empty, // Event name + (uint)0, // Handler function nodeId + }, + Elements = new[] + { + NodeElementArchetype.Factory.Input(0, string.Empty, true, typeof(void), 0), + NodeElementArchetype.Factory.Input(2, "Instance", true, typeof(object), 1), + NodeElementArchetype.Factory.Output(0, string.Empty, typeof(void), 2, true), + NodeElementArchetype.Factory.Text(2, 20, "Handler function:"), + NodeElementArchetype.Factory.ComboBox(100, 20, 140), + } + }, + new NodeArchetype + { + TypeID = 10, + Create = (id, context, arch, groupArch) => new UnbindEventNode(id, context, arch, groupArch), + Title = string.Empty, + Flags = NodeFlags.VisualScriptGraph | NodeFlags.NoSpawnViaGUI, + Size = new Vector2(260, 60), + DefaultValues = new object[] + { + string.Empty, // Event type + string.Empty, // Event name + (uint)0, // Handler function nodeId + }, + Elements = new[] + { + NodeElementArchetype.Factory.Input(0, string.Empty, true, typeof(void), 0), + NodeElementArchetype.Factory.Input(2, "Instance", true, typeof(object), 1), + NodeElementArchetype.Factory.Output(0, string.Empty, typeof(void), 2, true), + NodeElementArchetype.Factory.Text(2, 20, "Handler function:"), + NodeElementArchetype.Factory.ComboBox(100, 20, 140), + } + }, }; } } diff --git a/Source/Editor/Surface/Archetypes/Material.cs b/Source/Editor/Surface/Archetypes/Material.cs index ceb9921cb..4d65c760c 100644 --- a/Source/Editor/Surface/Archetypes/Material.cs +++ b/Source/Editor/Surface/Archetypes/Material.cs @@ -156,6 +156,7 @@ namespace FlaxEditor.Surface.Archetypes case MaterialDomain.Surface: case MaterialDomain.Terrain: case MaterialDomain.Particle: + case MaterialDomain.Deformable: { bool isNotUnlit = info.ShadingModel != MaterialShadingModel.Unlit; bool isTransparent = info.BlendMode == MaterialBlendMode.Transparent; diff --git a/Source/Editor/Surface/IFunctionDependantNode.cs b/Source/Editor/Surface/IFunctionDependantNode.cs new file mode 100644 index 000000000..e66c10e92 --- /dev/null +++ b/Source/Editor/Surface/IFunctionDependantNode.cs @@ -0,0 +1,31 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +using FlaxEngine; + +namespace FlaxEditor.Surface +{ + /// + /// Interface for surface nodes that depend on surface function nodes collection. + /// + [HideInEditor] + public interface IFunctionsDependantNode + { + /// + /// On function created. + /// + /// The function node. + void OnFunctionCreated(SurfaceNode node); + + /// + /// On function signature changed (new name or parameters change). + /// + /// The function node. + void OnFunctionEdited(SurfaceNode node); + + /// + /// On function removed. + /// + /// The function node. + void OnFunctionDeleted(SurfaceNode node); + } +} diff --git a/Source/Editor/Surface/SurfaceNode.cs b/Source/Editor/Surface/SurfaceNode.cs index abbd6a6a0..bdce94513 100644 --- a/Source/Editor/Surface/SurfaceNode.cs +++ b/Source/Editor/Surface/SurfaceNode.cs @@ -187,7 +187,10 @@ namespace FlaxEditor.Surface var titleLabelFont = Style.Current.FontLarge; for (int i = 0; i < Children.Count; i++) { - if (Children[i] is InputBox inputBox) + var child = Children[i]; + if (!child.Visible) + continue; + if (child is InputBox inputBox) { var boxWidth = boxLabelFont.MeasureText(inputBox.Text).X + 20; if (inputBox.DefaultValueEditor != null) @@ -195,12 +198,12 @@ namespace FlaxEditor.Surface leftWidth = Mathf.Max(leftWidth, boxWidth); leftHeight = Mathf.Max(leftHeight, inputBox.Archetype.Position.Y - Constants.NodeMarginY - Constants.NodeHeaderSize + 20.0f); } - else if (Children[i] is OutputBox outputBox) + else if (child is OutputBox outputBox) { rightWidth = Mathf.Max(rightWidth, boxLabelFont.MeasureText(outputBox.Text).X + 20); rightHeight = Mathf.Max(rightHeight, outputBox.Archetype.Position.Y - Constants.NodeMarginY - Constants.NodeHeaderSize + 20.0f); } - else if (Children[i] is Control control) + else if (child is Control control) { if (control.AnchorPreset == AnchorPresets.TopLeft) { @@ -1004,7 +1007,7 @@ namespace FlaxEditor.Surface } // Secondary Context Menu - if (button == MouseButton.Right) + if (button == MouseButton.Right && false) { if (!IsSelected) Surface.Select(this); diff --git a/Source/Editor/Surface/SurfaceUtils.cs b/Source/Editor/Surface/SurfaceUtils.cs index 18f2e9693..74f1d6130 100644 --- a/Source/Editor/Surface/SurfaceUtils.cs +++ b/Source/Editor/Surface/SurfaceUtils.cs @@ -406,6 +406,11 @@ namespace FlaxEditor.Surface return member.IsField && IsValidVisualScriptType(member.ValueType); } + internal static bool IsValidVisualScriptEvent(ScriptMemberInfo member) + { + return member.IsEvent && member.HasAttribute(typeof(UnmanagedAttribute)); + } + internal static bool IsValidVisualScriptType(ScriptType scriptType) { if (scriptType.IsGenericType || !scriptType.IsPublic || scriptType.HasAttribute(typeof(HideInEditorAttribute), true)) diff --git a/Source/Editor/Surface/VisjectSurfaceContext.cs b/Source/Editor/Surface/VisjectSurfaceContext.cs index d436cd91e..81b72bf4e 100644 --- a/Source/Editor/Surface/VisjectSurfaceContext.cs +++ b/Source/Editor/Surface/VisjectSurfaceContext.cs @@ -331,12 +331,13 @@ namespace FlaxEditor.Surface /// The node archetype ID. /// The location. /// The custom values array. Must match node archetype size. Pass null to use default values. + /// The custom callback action to call after node creation but just before invoking spawn event. Can be used to initialize custom node data. /// Created node. - public SurfaceNode SpawnNode(ushort groupID, ushort typeID, Vector2 location, object[] customValues = null) + public SurfaceNode SpawnNode(ushort groupID, ushort typeID, Vector2 location, object[] customValues = null, Action beforeSpawned = null) { if (NodeFactory.GetArchetype(_surface.NodeArchetypes, groupID, typeID, out var groupArchetype, out var nodeArchetype)) { - return SpawnNode(groupArchetype, nodeArchetype, location, customValues); + return SpawnNode(groupArchetype, nodeArchetype, location, customValues, beforeSpawned); } return null; } @@ -348,8 +349,9 @@ namespace FlaxEditor.Surface /// The node archetype. /// The location. /// The custom values array. Must match node archetype size. Pass null to use default values. + /// The custom callback action to call after node creation but just before invoking spawn event. Can be used to initialize custom node data. /// Created node. - public SurfaceNode SpawnNode(GroupArchetype groupArchetype, NodeArchetype nodeArchetype, Vector2 location, object[] customValues = null) + public SurfaceNode SpawnNode(GroupArchetype groupArchetype, NodeArchetype nodeArchetype, Vector2 location, object[] customValues = null, Action beforeSpawned = null) { if (groupArchetype == null || nodeArchetype == null) throw new ArgumentNullException(); @@ -387,6 +389,7 @@ namespace FlaxEditor.Surface } node.Location = location; OnControlLoaded(node); + beforeSpawned?.Invoke(node); node.OnSurfaceLoaded(); OnControlSpawned(node); diff --git a/Source/Editor/Surface/VisualScriptSurface.cs b/Source/Editor/Surface/VisualScriptSurface.cs index 5a5a2e250..b919c7c37 100644 --- a/Source/Editor/Surface/VisualScriptSurface.cs +++ b/Source/Editor/Surface/VisualScriptSurface.cs @@ -2,6 +2,11 @@ //#define DEBUG_INVOKE_METHODS_SEARCHING //#define DEBUG_FIELDS_SEARCHING +//#define DEBUG_EVENTS_SEARCHING + +#if DEBUG_INVOKE_METHODS_SEARCHING || DEBUG_FIELDS_SEARCHING || DEBUG_EVENTS_SEARCHING +#define DEBUG_SEARCH_TIME +#endif using System; using System.Collections.Generic; @@ -103,7 +108,7 @@ namespace FlaxEditor.Surface private static void OnActiveContextMenuShowAsync() { Profiler.BeginEvent("Setup Visual Script Context Menu (async)"); -#if DEBUG_INVOKE_METHODS_SEARCHING || DEBUG_FIELDS_SEARCHING +#if DEBUG_SEARCH_TIME var searchStartTime = DateTime.Now; var searchHitsCount = 0; #endif @@ -338,13 +343,65 @@ namespace FlaxEditor.Surface } } } + else if (member.IsEvent) + { + var name = member.Name; + + // Skip if searching by name doesn't return a match + var members = scriptType.GetMembers(name, MemberTypes.Event, BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly); + if (!members.Contains(member)) + continue; + + // Check if field is valid for Visual Script usage + if (SurfaceUtils.IsValidVisualScriptEvent(member)) + { + var groupKey = new KeyValuePair(scriptTypeName, 16); + if (!_cache.TryGetValue(groupKey, out var group)) + { + group = new GroupArchetype + { + GroupID = groupKey.Value, + Name = groupKey.Key, + Color = new Color(109, 160, 24), + Tag = _version, + Archetypes = new List(), + }; + _cache.Add(groupKey, group); + } + + // Add Bind event node + var bindNode = (NodeArchetype)Archetypes.Function.Nodes[8].Clone(); + bindNode.DefaultValues[0] = scriptTypeTypeName; + bindNode.DefaultValues[1] = name; + bindNode.Flags &= ~NodeFlags.NoSpawnViaGUI; + bindNode.Title = "Bind " + name; + bindNode.Description = SurfaceUtils.GetVisualScriptMemberInfoDescription(member); + bindNode.SubTitle = string.Format(" (in {0})", scriptTypeName); + ((IList)group.Archetypes).Add(bindNode); + + // Add Unbind event node + var unbindNode = (NodeArchetype)Archetypes.Function.Nodes[9].Clone(); + unbindNode.DefaultValues[0] = scriptTypeTypeName; + unbindNode.DefaultValues[1] = name; + unbindNode.Flags &= ~NodeFlags.NoSpawnViaGUI; + unbindNode.Title = "Unbind " + name; + unbindNode.Description = bindNode.Description; + unbindNode.SubTitle = bindNode.SubTitle; + ((IList)group.Archetypes).Add(unbindNode); + +#if DEBUG_EVENTS_SEARCHING + Editor.LogWarning(scriptTypeTypeName + " -> " + member.GetSignature()); + searchHitsCount++; +#endif + } + } } } // Add group to context menu (on a main thread) FlaxEngine.Scripting.InvokeOnUpdate(() => { -#if DEBUG_INVOKE_METHODS_SEARCHING || DEBUG_FIELDS_SEARCHING +#if DEBUG_SEARCH_TIME var addStartTime = DateTime.Now; #endif lock (_locker) @@ -352,12 +409,12 @@ namespace FlaxEditor.Surface _taskContextMenu.AddGroups(_cache.Values); _taskContextMenu = null; } -#if DEBUG_INVOKE_METHODS_SEARCHING || DEBUG_FIELDS_SEARCHING +#if DEBUG_SEARCH_TIME Editor.LogError($"Added items to VisjectCM in: {(DateTime.Now - addStartTime).TotalMilliseconds} ms"); #endif }); -#if DEBUG_INVOKE_METHODS_SEARCHING || DEBUG_FIELDS_SEARCHING +#if DEBUG_SEARCH_TIME Editor.LogError($"Collected {searchHitsCount} items in: {(DateTime.Now - searchStartTime).TotalMilliseconds} ms"); #endif Profiler.EndEvent(); diff --git a/Source/Editor/Tools/Foliage/FoliageTab.cs b/Source/Editor/Tools/Foliage/FoliageTab.cs index fe4120035..faf71bf1f 100644 --- a/Source/Editor/Tools/Foliage/FoliageTab.cs +++ b/Source/Editor/Tools/Foliage/FoliageTab.cs @@ -183,11 +183,8 @@ namespace FlaxEditor.Tools.Foliage actor.StaticFlags = StaticFlags.FullyStatic; actor.Name = "Foliage"; - // Spawn + // Spawn and select Editor.SceneEditing.Spawn(actor); - - // Select - Editor.SceneEditing.Select(actor); } private void OnSelectionChanged() diff --git a/Source/Editor/Undo/Actions/DeleteActorsAction.cs b/Source/Editor/Undo/Actions/DeleteActorsAction.cs index fa319e6f3..41dac3ccc 100644 --- a/Source/Editor/Undo/Actions/DeleteActorsAction.cs +++ b/Source/Editor/Undo/Actions/DeleteActorsAction.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using FlaxEditor.SceneGraph; +using FlaxEditor.Scripting; using FlaxEngine; namespace FlaxEditor.Actions @@ -15,7 +16,13 @@ namespace FlaxEditor.Actions class DeleteActorsAction : IUndoAction { [Serialize] - private byte[] _data; + private byte[] _actorsData; + + [Serialize] + private List _nodesData; + + [Serialize] + private Guid[] _nodeParentsIDs; [Serialize] private Guid[] _prefabIds; @@ -30,31 +37,51 @@ namespace FlaxEditor.Actions /// The node parents. /// [Serialize] - protected List _nodeParents; + protected List _nodeParents; /// /// Initializes a new instance of the class. /// - /// The objects. + /// The objects. /// If set to true action will be inverted - instead of delete it will be create actors. - internal DeleteActorsAction(List objects, bool isInverted = false) + internal DeleteActorsAction(List nodes, bool isInverted = false) { _isInverted = isInverted; - _nodeParents = new List(objects.Count); - var actorNodes = new List(objects.Count); - var actors = new List(objects.Count); - for (int i = 0; i < objects.Count; i++) + + // Collect nodes to delete + var deleteNodes = new List(nodes.Count); + var actors = new List(nodes.Count); + for (int i = 0; i < nodes.Count; i++) { - if (objects[i] is ActorNode node) + var node = nodes[i]; + if (node is ActorNode actorNode) { - actorNodes.Add(node); - actors.Add(node.Actor); + deleteNodes.Add(actorNode); + actors.Add(actorNode.Actor); + } + else + { + deleteNodes.Add(node); + if (node.CanUseState) + { + if (_nodesData == null) + _nodesData = new List(); + _nodesData.Add(node.State); + } } } - actorNodes.BuildNodesParents(_nodeParents); - _data = Actor.ToBytes(actors.ToArray()); + // Collect parent nodes to delete + _nodeParents = new List(nodes.Count); + deleteNodes.BuildNodesParents(_nodeParents); + _nodeParentsIDs = new Guid[_nodeParents.Count]; + for (int i = 0; i < _nodeParentsIDs.Length; i++) + _nodeParentsIDs[i] = _nodeParents[i].ID; + // Serialize actors + _actorsData = Actor.ToBytes(actors.ToArray()); + + // Cache actors linkage to prefab objects _prefabIds = new Guid[actors.Count]; _prefabObjectIds = new Guid[actors.Count]; for (int i = 0; i < actors.Count; i++) @@ -88,9 +115,11 @@ namespace FlaxEditor.Actions /// public void Dispose() { - _data = null; + _actorsData = null; + _nodeParentsIDs = null; _prefabIds = null; _prefabObjectIds = null; + _nodeParents.Clear(); } /// @@ -123,28 +152,64 @@ namespace FlaxEditor.Actions /// protected virtual void Create() { - // Restore objects - var actors = Actor.FromBytes(_data); - if (actors == null) - return; - for (int i = 0; i < actors.Length; i++) + var nodes = new List(); + + // Restore actors + var actors = Actor.FromBytes(_actorsData); + if (actors != null) { - Guid prefabId = _prefabIds[i]; - if (prefabId != Guid.Empty) + nodes.Capacity = Math.Max(nodes.Capacity, actors.Length); + + // Preserve prefab objects linkage + for (int i = 0; i < actors.Length; i++) { - Actor.Internal_LinkPrefab(FlaxEngine.Object.GetUnmanagedPtr(actors[i]), ref prefabId, ref _prefabObjectIds[i]); + Guid prefabId = _prefabIds[i]; + if (prefabId != Guid.Empty) + { + Actor.Internal_LinkPrefab(FlaxEngine.Object.GetUnmanagedPtr(actors[i]), ref prefabId, ref _prefabObjectIds[i]); + } } } - var actorNodes = new List(actors.Length); - for (int i = 0; i < actors.Length; i++) + + // Restore nodes state + if (_nodesData != null) { - var foundNode = GetNode(actors[i].ID); + for (int i = 0; i < _nodesData.Count; i++) + { + var state = _nodesData[i]; + var type = TypeUtils.GetManagedType(state.TypeName); + if (type == null) + { + Editor.LogError($"Missing type {state.TypeName} for scene graph node undo state restore."); + continue; + } + var method = type.GetMethod(state.CreateMethodName); + if (method == null) + { + Editor.LogError($"Missing method {state.CreateMethodName} from type {state.TypeName} for scene graph node undo state restore."); + continue; + } + var node = method.Invoke(null, new object[] { state }); + if (node == null) + { + Editor.LogError($"Failed to restore scene graph node state via method {state.CreateMethodName} from type {state.TypeName}."); + continue; + } + } + } + + // Cache parent nodes ids + for (int i = 0; i < _nodeParentsIDs.Length; i++) + { + var foundNode = GetNode(_nodeParentsIDs[i]); if (foundNode is ActorNode node) { - actorNodes.Add(node); + nodes.Add(node); } } - actorNodes.BuildNodesParents(_nodeParents); + nodes.BuildNodesParents(_nodeParents); + + // Mark scenes as modified for (int i = 0; i < _nodeParents.Count; i++) { Editor.Instance.Scene.MarkSceneEdited(_nodeParents[i].ParentScene); diff --git a/Source/Editor/Undo/Actions/EditSplineAction.cs b/Source/Editor/Undo/Actions/EditSplineAction.cs new file mode 100644 index 000000000..7fb836459 --- /dev/null +++ b/Source/Editor/Undo/Actions/EditSplineAction.cs @@ -0,0 +1,76 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +using System; +using FlaxEditor.Modules; +using FlaxEditor.SceneGraph.Actors; +using FlaxEngine; + +namespace FlaxEditor.Actions +{ + /// + /// Change keyframes undo action. + /// + /// + /// + [Serializable] + public class EditSplineAction : IUndoAction, ISceneEditAction + { + [Serialize] + private Guid _splineId; + + [Serialize] + private BezierCurve.Keyframe[] _before; + + [Serialize] + private BezierCurve.Keyframe[] _after; + + /// + /// Initializes a new instance of the class. + /// + /// The spline. + /// The spline keyframes state before editing it. + public EditSplineAction(Spline spline, BezierCurve.Keyframe[] before) + { + _splineId = spline.ID; + _before = before; + _after = (BezierCurve.Keyframe[])spline.SplineKeyframes.Clone(); + } + + /// + public string ActionString => "Edit spline keyframes"; + + /// + public void Do() + { + var spline = FlaxEngine.Object.Find(ref _splineId); + if (spline == null) + return; + spline.SplineKeyframes = _after; + SplineNode.OnSplineEdited(spline); + } + + /// + public void Undo() + { + var spline = FlaxEngine.Object.Find(ref _splineId); + if (spline == null) + return; + spline.SplineKeyframes = _before; + SplineNode.OnSplineEdited(spline); + } + + /// + public void Dispose() + { + _before = _after = null; + } + + /// + public void MarkSceneEdited(SceneModule sceneModule) + { + var spline = FlaxEngine.Object.Find(ref _splineId); + if (spline != null) + sceneModule.MarkSceneEdited(spline.Scene); + } + } +} diff --git a/Source/Editor/Utilities/EditorUtilities.cpp b/Source/Editor/Utilities/EditorUtilities.cpp index 538b78173..aaa0ed34c 100644 --- a/Source/Editor/Utilities/EditorUtilities.cpp +++ b/Source/Editor/Utilities/EditorUtilities.cpp @@ -505,7 +505,11 @@ bool EditorUtilities::GetApplicationImage(const Guid& imageId, TextureData& imag AssetReference icon = Content::LoadAsync(imageId); if (icon == nullptr) { - icon = Content::LoadAsync(GameSettings::Icon); + const auto gameSettings = GameSettings::Get(); + if (gameSettings) + { + icon = Content::LoadAsync(gameSettings->Icon); + } } if (icon == nullptr) { diff --git a/Source/Editor/Utilities/Utils.cs b/Source/Editor/Utilities/Utils.cs index 26c5ec92b..95ad9ade5 100644 --- a/Source/Editor/Utilities/Utils.cs +++ b/Source/Editor/Utilities/Utils.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; +using System.Reflection; using System.Runtime.InteropServices; using FlaxEditor.SceneGraph; using FlaxEditor.Scripting; @@ -1645,7 +1646,8 @@ namespace FlaxEditor.Utilities /// /// The source code. /// The window title. - public static void ShowSourceCodeWindow(string source, string title) + /// The context control used to show source code window popup in a proper location. + public static void ShowSourceCodeWindow(string source, string title, Control control = null) { if (string.IsNullOrEmpty(source)) { @@ -1658,8 +1660,9 @@ namespace FlaxEditor.Utilities settings.AllowMaximize = false; settings.AllowMinimize = false; settings.HasSizingFrame = false; - settings.StartPosition = WindowStartPosition.CenterScreen; + settings.StartPosition = WindowStartPosition.CenterParent; settings.Size = new Vector2(500, 600) * Platform.DpiScale; + settings.Parent = control?.RootWindow?.Window ?? Editor.Instance.Windows.MainWindow; settings.Title = title; var dialog = Platform.CreateWindow(ref settings); @@ -1736,5 +1739,44 @@ namespace FlaxEditor.Utilities distance = 0; return false; } + + /// + /// Initializes the object fields and properties with their default values based on . + /// + /// The object. + public static void InitDefaultValues(object obj) + { + var scriptType = TypeUtils.GetObjectType(obj); + if (!scriptType) + return; + var isStructure = scriptType.IsStructure; + + var fields = scriptType.GetFields(BindingFlags.Default | BindingFlags.Instance | BindingFlags.Public); + for (var i = 0; i < fields.Length; i++) + { + var field = fields[i]; + var attr = field.GetAttribute(); + if (attr != null) + { + field.SetValue(obj, attr.Value); + } + else if (isStructure) + { + // C# doesn't support default values for structure members so initialize them + field.SetValue(obj, TypeUtils.GetDefaultValue(field.ValueType)); + } + } + + var properties = scriptType.GetProperties(BindingFlags.Default | BindingFlags.Instance | BindingFlags.Public); + for (var i = 0; i < properties.Length; i++) + { + var property = properties[i]; + var attr = property.GetAttribute(); + if (attr != null) + { + property.SetValue(obj, attr.Value); + } + } + } } } diff --git a/Source/Editor/Utilities/ViewportIconsRenderer.cpp b/Source/Editor/Utilities/ViewportIconsRenderer.cpp index 631e8b56f..889d94052 100644 --- a/Source/Editor/Utilities/ViewportIconsRenderer.cpp +++ b/Source/Editor/Utilities/ViewportIconsRenderer.cpp @@ -138,7 +138,7 @@ bool ViewportIconsRendererService::Init() void ViewportIconsRendererService::Dispose() { - QuadModel.Unlink(); + QuadModel = nullptr; for (int32 i = 0; i < ARRAY_COUNT(InstanceBuffers); i++) InstanceBuffers[i].Release(); ActorTypeToIconType.Clear(); diff --git a/Source/Editor/Viewport/PrefabWindowViewport.cs b/Source/Editor/Viewport/PrefabWindowViewport.cs index dd6f8fce3..1276fc56d 100644 --- a/Source/Editor/Viewport/PrefabWindowViewport.cs +++ b/Source/Editor/Viewport/PrefabWindowViewport.cs @@ -530,7 +530,7 @@ namespace FlaxEditor.Viewport if (hit != null) { // For child actor nodes (mesh, link or sth) we need to select it's owning actor node first or any other child node (but not a child actor) - if (hit is ActorChildNode actorChildNode) + if (hit is ActorChildNode actorChildNode && !actorChildNode.CanBeSelectedDirectly) { var parentNode = actorChildNode.ParentNode; bool canChildBeSelected = _window.Selection.Contains(parentNode); diff --git a/Source/Editor/Viewport/Previews/MaterialPreview.cs b/Source/Editor/Viewport/Previews/MaterialPreview.cs index 6ff695b69..4f7d71b18 100644 --- a/Source/Editor/Viewport/Previews/MaterialPreview.cs +++ b/Source/Editor/Viewport/Previews/MaterialPreview.cs @@ -35,6 +35,8 @@ namespace FlaxEditor.Viewport.Previews private StaticModel _previewModel; private Decal _decal; private Terrain _terrain; + private Spline _spline; + private SplineModel _splineModel; private ParticleEffect _particleEffect; private MaterialBase _particleEffectMaterial; private ParticleEmitter _particleEffectEmitter; @@ -140,6 +142,7 @@ namespace FlaxEditor.Viewport.Previews MaterialBase guiMaterial = null; MaterialBase terrainMaterial = null; MaterialBase particleMaterial = null; + MaterialBase deformableMaterial = null; bool usePreviewActor = true; if (_material != null) { @@ -172,6 +175,10 @@ namespace FlaxEditor.Viewport.Previews usePreviewActor = false; particleMaterial = _material; break; + case MaterialDomain.Deformable: + usePreviewActor = false; + deformableMaterial = _material; + break; default: throw new ArgumentOutOfRangeException(); } } @@ -279,6 +286,25 @@ namespace FlaxEditor.Viewport.Previews } } } + + // Deformable + if (deformableMaterial && _spline == null) + { + _spline = new Spline(); + _spline.AddSplineLocalPoint(new Vector3(0, 0, -50.0f), false); + _spline.AddSplineLocalPoint(new Vector3(0, 0, 50.0f)); + _splineModel = new SplineModel + { + Scale = new Vector3(0.45f), + Parent = _spline, + }; + Task.AddCustomActor(_spline); + } + if (_splineModel != null) + { + _splineModel.Model = _previewModel.Model; + _splineModel.SetMaterial(0, deformableMaterial); + } } /// @@ -295,6 +321,8 @@ namespace FlaxEditor.Viewport.Previews Object.Destroy(ref _previewModel); Object.Destroy(ref _decal); Object.Destroy(ref _terrain); + Object.Destroy(ref _spline); + Object.Destroy(ref _splineModel); Object.Destroy(ref _particleEffect); Object.Destroy(ref _particleEffectEmitter); Object.Destroy(ref _particleEffectSystem); diff --git a/Source/Editor/Windows/Assets/MaterialWindow.cs b/Source/Editor/Windows/Assets/MaterialWindow.cs index f49e6a79c..53d85f493 100644 --- a/Source/Editor/Windows/Assets/MaterialWindow.cs +++ b/Source/Editor/Windows/Assets/MaterialWindow.cs @@ -233,18 +233,14 @@ namespace FlaxEditor.Windows.Assets // Toolstrip _toolstrip.AddSeparator(); - _toolstrip.AddButton(editor.Icons.BracketsSlash32, () => ShowSourceCode(_asset)).LinkTooltip("Show generated shader source code"); + _toolstrip.AddButton(editor.Icons.BracketsSlash32, ShowSourceCode).LinkTooltip("Show generated shader source code"); _toolstrip.AddButton(editor.Icons.Docs32, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/graphics/materials/index.html")).LinkTooltip("See documentation to learn more"); } - /// - /// Shows the material source code window. - /// - /// The material asset. - public static void ShowSourceCode(Material material) + private void ShowSourceCode() { - var source = Editor.GetShaderSourceCode(material); - Utilities.Utils.ShowSourceCodeWindow(source, "Material Source"); + var source = Editor.GetShaderSourceCode(_asset); + Utilities.Utils.ShowSourceCodeWindow(source, "Material Source", this); } /// diff --git a/Source/Editor/Windows/Assets/ParticleEmitterWindow.cs b/Source/Editor/Windows/Assets/ParticleEmitterWindow.cs index 17b5a928d..ab61a9d68 100644 --- a/Source/Editor/Windows/Assets/ParticleEmitterWindow.cs +++ b/Source/Editor/Windows/Assets/ParticleEmitterWindow.cs @@ -139,18 +139,14 @@ namespace FlaxEditor.Windows.Assets // Toolstrip _toolstrip.AddSeparator(); - _toolstrip.AddButton(editor.Icons.BracketsSlash32, () => ShowSourceCode(_asset)).LinkTooltip("Show generated shader source code"); + _toolstrip.AddButton(editor.Icons.BracketsSlash32, ShowSourceCode).LinkTooltip("Show generated shader source code"); _toolstrip.AddButton(editor.Icons.Docs32, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/particles/index.html")).LinkTooltip("See documentation to learn more"); } - /// - /// Shows the ParticleEmitter source code window. - /// - /// The ParticleEmitter asset. - public static void ShowSourceCode(ParticleEmitter particleEmitter) + private void ShowSourceCode() { - var source = Editor.GetShaderSourceCode(particleEmitter); - Utilities.Utils.ShowSourceCodeWindow(source, "Particle Emitter GPU Simulation Source"); + var source = Editor.GetShaderSourceCode(_asset); + Utilities.Utils.ShowSourceCodeWindow(source, "Particle Emitter GPU Simulation Source", this); } /// diff --git a/Source/Editor/Windows/Assets/PrefabWindow.Actions.cs b/Source/Editor/Windows/Assets/PrefabWindow.Actions.cs index 00a96910f..af568cd3f 100644 --- a/Source/Editor/Windows/Assets/PrefabWindow.Actions.cs +++ b/Source/Editor/Windows/Assets/PrefabWindow.Actions.cs @@ -159,7 +159,7 @@ namespace FlaxEditor.Windows.Assets public void Duplicate() { // Peek things that can be copied (copy all actors) - var objects = Selection.Where(x => x.CanCopyPaste && x != Graph.Main).ToList().BuildAllNodes().Where(x => x.CanCopyPaste && x is ActorNode).ToList(); + var objects = Selection.Where(x => x.CanDuplicate && x != Graph.Main).ToList().BuildAllNodes().Where(x => x.CanDuplicate && x is ActorNode).ToList(); if (objects.Count == 0) return; @@ -194,8 +194,8 @@ namespace FlaxEditor.Windows.Assets private class CustomDeleteActorsAction : DeleteActorsAction { - public CustomDeleteActorsAction(List objects, bool isInverted = false) - : base(objects, isInverted) + public CustomDeleteActorsAction(List nodes, bool isInverted = false) + : base(nodes, isInverted) { } @@ -207,7 +207,8 @@ namespace FlaxEditor.Windows.Assets // Unlink nodes from parents (actors spawned for prefab editing are not in a gameplay and may not send some important events) for (int i = 0; i < nodes.Length; i++) { - nodes[i].Actor.Parent = null; + if (nodes[i] is ActorNode actorNode) + actorNode.Actor.Parent = null; } base.Delete(); diff --git a/Source/Editor/Windows/GameCookerWindow.cs b/Source/Editor/Windows/GameCookerWindow.cs index 442cc4c34..278ce69d3 100644 --- a/Source/Editor/Windows/GameCookerWindow.cs +++ b/Source/Editor/Windows/GameCookerWindow.cs @@ -73,6 +73,9 @@ namespace FlaxEditor.Windows [EditorOrder(20), Tooltip("Configuration build mode")] public BuildConfiguration ConfigurationMode = BuildConfiguration.Development; + [EditorOrder(90), Tooltip("The list of custom defines passed to the build tool when compiling project scripts. Can be used in build scripts for configuration (Configuration.CustomDefines).")] + public string[] CustomDefines; + protected abstract BuildPlatform BuildPlatform { get; } protected virtual BuildOptions Options @@ -126,7 +129,7 @@ namespace FlaxEditor.Windows public virtual void Build() { var output = StringUtils.ConvertRelativePathToAbsolute(Globals.ProjectFolder, StringUtils.NormalizePath(Output)); - GameCooker.Build(BuildPlatform, ConfigurationMode, output, Options); + GameCooker.Build(BuildPlatform, ConfigurationMode, output, Options, CustomDefines); } } @@ -793,7 +796,7 @@ namespace FlaxEditor.Windows _preBuildAction = target.PreBuildAction; _postBuildAction = target.PostBuildAction; - GameCooker.Build(target.Platform, target.Mode, target.Output, target.Options); + GameCooker.Build(target.Platform, target.Mode, target.Output, BuildOptions.None, target.CustomDefines); } else if (_exitOnBuildEnd) { diff --git a/Source/Editor/Windows/SceneTreeWindow.Actors.cs b/Source/Editor/Windows/SceneTreeWindow.Actors.cs index d3fff78d8..8970b6a48 100644 --- a/Source/Editor/Windows/SceneTreeWindow.Actors.cs +++ b/Source/Editor/Windows/SceneTreeWindow.Actors.cs @@ -97,7 +97,9 @@ namespace FlaxEditor.Windows new KeyValuePair("Audio Listener", typeof(AudioListener)), new KeyValuePair("Scene Animation", typeof(SceneAnimationPlayer)), new KeyValuePair("Nav Mesh Bounds Volume", typeof(NavMeshBoundsVolume)), - new KeyValuePair("Nav Mesh Link", typeof(NavLink)), + new KeyValuePair("Nav Link", typeof(NavLink)), + new KeyValuePair("Nav Modifier Volume", typeof(NavModifierVolume)), + new KeyValuePair("Spline", typeof(Spline)), } }, new ActorsGroup diff --git a/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs b/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs index aee456d81..262bfd0bb 100644 --- a/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs +++ b/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using System; +using System.Collections.Generic; using FlaxEditor.GUI.ContextMenu; using FlaxEditor.SceneGraph; using FlaxEngine; @@ -137,6 +138,24 @@ namespace FlaxEditor.Windows // Custom options + bool showCustomNodeOptions = Editor.SceneEditing.Selection.Count == 1; + if (!showCustomNodeOptions && Editor.SceneEditing.Selection.Count != 0) + { + showCustomNodeOptions = true; + for (int i = 1; i < Editor.SceneEditing.Selection.Count; i++) + { + if (Editor.SceneEditing.Selection[0].GetType() != Editor.SceneEditing.Selection[i].GetType()) + { + showCustomNodeOptions = false; + break; + } + } + } + if (showCustomNodeOptions) + { + Editor.SceneEditing.Selection[0].OnContextMenu(contextMenu); + } + ContextMenuShow?.Invoke(contextMenu); return contextMenu; diff --git a/Source/Editor/Windows/ToolboxWindow.cs b/Source/Editor/Windows/ToolboxWindow.cs index d10a111b6..63e1f1f05 100644 --- a/Source/Editor/Windows/ToolboxWindow.cs +++ b/Source/Editor/Windows/ToolboxWindow.cs @@ -165,7 +165,9 @@ namespace FlaxEditor.Windows groupOther.AddChild(CreateActorItem("Empty Actor", typeof(EmptyActor))); groupOther.AddChild(CreateActorItem("Scene Animation", typeof(SceneAnimationPlayer))); groupOther.AddChild(CreateActorItem("Nav Mesh Bounds Volume", typeof(NavMeshBoundsVolume))); - groupOther.AddChild(CreateActorItem("Nav Mesh Link", typeof(NavLink))); + groupOther.AddChild(CreateActorItem("Nav Link", typeof(NavLink))); + groupOther.AddChild(CreateActorItem("Nav Modifier Volume", typeof(NavModifierVolume))); + groupOther.AddChild(CreateActorItem("Spline", typeof(Spline))); var groupGui = CreateGroupWithList(actorGroups, "GUI"); groupGui.AddChild(CreateActorItem("UI Control", typeof(UIControl))); diff --git a/Source/Engine/Animations/AnimationUtils.h b/Source/Engine/Animations/AnimationUtils.h index 248efea0c..5df8cc2c0 100644 --- a/Source/Engine/Animations/AnimationUtils.h +++ b/Source/Engine/Animations/AnimationUtils.h @@ -7,6 +7,7 @@ #include "Engine/Core/Math/Vector2.h" #include "Engine/Core/Math/Vector3.h" #include "Engine/Core/Math/Quaternion.h" +#include "Engine/Core/Math/Transform.h" namespace AnimationUtils { @@ -40,6 +41,12 @@ namespace AnimationUtils return Quaternion::Identity; } + template<> + FORCE_INLINE Transform GetZero() + { + return Transform::Identity; + } + template<> FORCE_INLINE Color GetZero() { @@ -63,25 +70,43 @@ namespace AnimationUtils FORCE_INLINE void GetTangent(const Quaternion& a, const Quaternion& b, float length, Quaternion& result) { const float oneThird = 1.0f / 3.0f; - Quaternion::Slerp(a, b, oneThird, result); + Quaternion::Slerp(a, b, length * oneThird, result); + } + + template<> + FORCE_INLINE void GetTangent(const Transform& a, const Transform& b, float length, Transform& result) + { + const float oneThird = 1.0f / 3.0f; + const float oneThirdLength = length * oneThird; + result.Translation = a.Translation + b.Translation * oneThirdLength; + Quaternion::Slerp(a.Orientation, b.Orientation, oneThirdLength, result.Orientation); + result.Scale = a.Scale + (b.Scale - a.Scale) * oneThirdLength; } template - FORCE_INLINE static void Interpolate(const T& a, const T& b, float alpha, T& result) + FORCE_INLINE static void Interpolate(const T& a, const T& b, float t, T& result) { - result = (T)(a + alpha * (b - a)); + result = (T)(a + t * (b - a)); } template<> - FORCE_INLINE void Interpolate(const Vector3& a, const Vector3& b, float alpha, Vector3& result) + FORCE_INLINE void Interpolate(const Vector3& a, const Vector3& b, float t, Vector3& result) { - Vector3::Lerp(a, b, alpha, result); + Vector3::Lerp(a, b, t, result); } template<> - FORCE_INLINE void Interpolate(const Quaternion& a, const Quaternion& b, float alpha, Quaternion& result) + FORCE_INLINE void Interpolate(const Quaternion& a, const Quaternion& b, float t, Quaternion& result) { - Quaternion::Slerp(a, b, alpha, result); + Quaternion::Slerp(a, b, t, result); + } + + template<> + FORCE_INLINE void Interpolate(const Transform& a, const Transform& b, float t, Transform& result) + { + Vector3::Lerp(a.Translation, b.Translation, t, result.Translation); + Quaternion::Slerp(a.Orientation, b.Orientation, t, result.Orientation); + Vector3::Lerp(a.Scale, b.Scale, t, result.Scale); } static void WrapTime(float& time, float start, float end, bool loop) @@ -117,90 +142,111 @@ namespace AnimationUtils /// Evaluates a cubic Hermite curve at a specific point. /// /// The time parameter that at which to evaluate the curve, in range [0, 1]. - /// The starting point (at t=0). - /// The ending point (at t=1). - /// The starting tangent (at t=0). - /// The ending tangent (at t = 1). + /// The starting point (at t=0). + /// The ending point (at t=1). + /// The starting tangent (at t=0). + /// The ending tangent (at t = 1). /// The evaluated value. template - static void CubicHermite(const float t, const T& pointA, const T& pointB, const T& tangentA, const T& tangentB, T* result) + static void CubicHermite(const T& p0, const T& p1, const T& t0, const T& t1, float t, T& result) { - const float t2 = t * t; - const float t3 = t2 * t; - - float a = 2 * t3 - 3 * t2 + 1; - float b = t3 - 2 * t2 + t; - float c = -2 * t3 + 3 * t2; - float d = t3 - t2; - - *result = a * pointA + b * tangentA + c * pointB + d * tangentB; + const float tt = t * t; + const float ttt = tt * t; + result = (2 * ttt - 3 * tt + 1) * p0 + (ttt - 2 * tt + t) * t0 + (-2 * ttt + 3 * tt) * p1 + (ttt - tt) * t1; } /// /// Evaluates the first derivative of a cubic Hermite curve at a specific point. /// /// The time parameter that at which to evaluate the curve, in range [0, 1]. - /// The starting point (at t=0). - /// The ending point (at t=1). - /// The starting tangent (at t=0). - /// The ending tangent (at t = 1). + /// The starting point (at t=0). + /// The ending point (at t=1). + /// The starting tangent (at t=0). + /// The ending tangent (at t=1). /// The evaluated value. template - static void CubicHermiteD1(const float t, const T& pointA, const T& pointB, const T& tangentA, const T& tangentB, T* result) + static void CubicHermiteFirstDerivative(const T& p0, const T& p1, const T& t0, const T& t1, float t, T& result) { - const float t2 = t * t; - - float a = 6 * t2 - 6 * t; - float b = 3 * t2 - 4 * t + 1; - float c = -6 * t2 + 6 * t; - float d = 3 * t2 - 2 * t; - - *result = a * pointA + b * tangentA + c * pointB + d * tangentB; + const float tt = t * t; + result = (6 * tt - 6 * t) * p0 + (3 * tt - 4 * t + 1) * t0 + (-6 * tt + 6 * t) * p1 + (3 * tt - 2 * t) * t1; } template - static void Bezier(const T& p0, const T& p1, const T& p2, const T& p3, float alpha, T& result) + static void Bezier(const T& p0, const T& p1, const T& p2, const T& p3, float t, T& result) { T p01, p12, p23, p012, p123; - Interpolate(p0, p1, alpha, p01); - Interpolate(p1, p2, alpha, p12); - Interpolate(p2, p3, alpha, p23); - Interpolate(p01, p12, alpha, p012); - Interpolate(p12, p23, alpha, p123); - Interpolate(p012, p123, alpha, result); + Interpolate(p0, p1, t, p01); + Interpolate(p1, p2, t, p12); + Interpolate(p2, p3, t, p23); + Interpolate(p01, p12, t, p012); + Interpolate(p12, p23, t, p123); + Interpolate(p012, p123, t, result); } template<> - void Bezier(const Vector2& p0, const Vector2& p1, const Vector2& p2, const Vector2& p3, float alpha, Vector2& result) + void Bezier(const Vector2& p0, const Vector2& p1, const Vector2& p2, const Vector2& p3, float t, Vector2& result) { - const float u = 1.0f - alpha; - const float tt = alpha * alpha; + const float u = 1.0f - t; + const float tt = t * t; const float uu = u * u; const float uuu = uu * u; - const float ttt = tt * alpha; - result = uuu * p0 + 3 * uu * alpha * p1 + 3 * u * tt * p2 + ttt * p3; + const float ttt = tt * t; + result = uuu * p0 + 3 * uu * t * p1 + 3 * u * tt * p2 + ttt * p3; } template<> - void Bezier(const Vector3& p0, const Vector3& p1, const Vector3& p2, const Vector3& p3, float alpha, Vector3& result) + void Bezier(const Vector3& p0, const Vector3& p1, const Vector3& p2, const Vector3& p3, float t, Vector3& result) { - const float u = 1.0f - alpha; - const float tt = alpha * alpha; + const float u = 1.0f - t; + const float tt = t * t; const float uu = u * u; const float uuu = uu * u; - const float ttt = tt * alpha; - result = uuu * p0 + 3 * uu * alpha * p1 + 3 * u * tt * p2 + ttt * p3; + const float ttt = tt * t; + result = uuu * p0 + 3 * uu * t * p1 + 3 * u * tt * p2 + ttt * p3; } template<> - void Bezier(const Quaternion& p0, const Quaternion& p1, const Quaternion& p2, const Quaternion& p3, float alpha, Quaternion& result) + void Bezier(const Quaternion& p0, const Quaternion& p1, const Quaternion& p2, const Quaternion& p3, float t, Quaternion& result) { Quaternion p01, p12, p23, p012, p123; - Quaternion::Slerp(p0, p1, alpha, p01); - Quaternion::Slerp(p1, p2, alpha, p12); - Quaternion::Slerp(p2, p3, alpha, p23); - Quaternion::Slerp(p01, p12, alpha, p012); - Quaternion::Slerp(p12, p23, alpha, p123); - Quaternion::Slerp(p012, p123, alpha, result); + Quaternion::Slerp(p0, p1, t, p01); + Quaternion::Slerp(p1, p2, t, p12); + Quaternion::Slerp(p2, p3, t, p23); + Quaternion::Slerp(p01, p12, t, p012); + Quaternion::Slerp(p12, p23, t, p123); + Quaternion::Slerp(p012, p123, t, result); + } + + template<> + void Bezier(const Transform& p0, const Transform& p1, const Transform& p2, const Transform& p3, float t, Transform& result) + { + Bezier(p0.Translation, p1.Translation, p2.Translation, p3.Translation, t, result.Translation); + Bezier(p0.Orientation, p1.Orientation, p2.Orientation, p3.Orientation, t, result.Orientation); + Bezier(p0.Scale, p1.Scale, p2.Scale, p3.Scale, t, result.Scale); + } + + template + static void BezierFirstDerivative(const T& p0, const T& p1, const T& p2, const T& p3, float t, T& result) + { + const float u = 1.0f - t; + const float tt = t * t; + const float uu = u * u; + result = 3.0f * uu * (p1 - p0) + 6.0f * u * t * (p2 - p1) + 3.0f * tt * (p3 - p2); + } + + template<> + static void BezierFirstDerivative(const Quaternion& p0, const Quaternion& p1, const Quaternion& p2, const Quaternion& p3, float t, Quaternion& result) + { + Vector3 euler; + BezierFirstDerivative(p0.GetEuler(), p1.GetEuler(), p2.GetEuler(), p3.GetEuler(), t, euler); + result = Quaternion::Euler(euler); + } + + template<> + static void BezierFirstDerivative(const Transform& p0, const Transform& p1, const Transform& p2, const Transform& p3, float t, Transform& result) + { + BezierFirstDerivative(p0.Translation, p1.Translation, p2.Translation, p3.Translation, t, result.Translation); + BezierFirstDerivative(p0.Orientation, p1.Orientation, p2.Orientation, p3.Orientation, t, result.Orientation); + BezierFirstDerivative(p0.Scale, p1.Scale, p2.Scale, p3.Scale, t, result.Scale); } } diff --git a/Source/Engine/Animations/Curve.cs b/Source/Engine/Animations/Curve.cs index d9762d05f..3a8ddc100 100644 --- a/Source/Engine/Animations/Curve.cs +++ b/Source/Engine/Animations/Curve.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.InteropServices; namespace FlaxEngine { @@ -338,6 +339,7 @@ namespace FlaxEngine /// /// A single keyframe that can be injected into linear curve. /// + [StructLayout(LayoutKind.Sequential)] public struct Keyframe : IComparable, IComparable { /// @@ -586,6 +588,7 @@ namespace FlaxEngine /// /// A single keyframe that can be injected into Bezier curve. /// + [StructLayout(LayoutKind.Sequential)] public struct Keyframe : IComparable, IComparable { /// diff --git a/Source/Engine/Animations/Curve.h b/Source/Engine/Animations/Curve.h index 8261dea5b..be5c268ef 100644 --- a/Source/Engine/Animations/Curve.h +++ b/Source/Engine/Animations/Curve.h @@ -4,9 +4,7 @@ #include "AnimationUtils.h" #include "Engine/Core/Collections/Array.h" -#include "Engine/Core/Types/DataContainer.h" -#include "Engine/Serialization/ReadStream.h" -#include "Engine/Serialization/WriteStream.h" +#include "Engine/Core/Types/Span.h" // @formatter:off @@ -48,10 +46,20 @@ public: result = a.Value; } + FORCE_INLINE static void InterpolateFirstDerivative(const StepCurveKeyframe& a, const StepCurveKeyframe& b, float alpha, float length, T& result) + { + result = AnimationUtils::GetZero(); + } + FORCE_INLINE static void InterpolateKey(const StepCurveKeyframe& a, const StepCurveKeyframe& b, float alpha, float length, StepCurveKeyframe& result) { result = a; } + + bool operator==(const StepCurveKeyframe& other) const + { + return Math::NearEqual(Time, other.Time) && Math::NearEqual(Value, other.Value); + } } PACK_END(); /// @@ -92,11 +100,21 @@ public: AnimationUtils::Interpolate(a.Value, b.Value, alpha, result); } + FORCE_INLINE static void InterpolateFirstDerivative(const LinearCurveKeyframe& a, const LinearCurveKeyframe& b, float alpha, float length, T& result) + { + result = b.Value - a.Value; + } + FORCE_INLINE static void InterpolateKey(const LinearCurveKeyframe& a, const LinearCurveKeyframe& b, float alpha, float length, LinearCurveKeyframe& result) { result.Time = a.Time + (b.Time - a.Time) * alpha; AnimationUtils::Interpolate(a.Value, b.Value, alpha, result.Value); } + + bool operator==(const LinearCurveKeyframe& other) const + { + return Math::NearEqual(Time, other.Time) && Math::NearEqual(Value, other.Value); + } } PACK_END(); /// @@ -147,23 +165,31 @@ public: { T leftTangent = a.Value + a.TangentOut * length; T rightTangent = b.Value + b.TangentIn * length; + AnimationUtils::CubicHermite( a.Value, b.Value, leftTangent, rightTangent, alpha, result); + } - AnimationUtils::CubicHermite(alpha, a.Value, b.Value, leftTangent, rightTangent, result); + static void InterpolateFirstDerivative(const HermiteCurveKeyframe& a, const HermiteCurveKeyframe& b, float alpha, float length, T& result) + { + T leftTangent = a.Value + a.TangentOut * length; + T rightTangent = b.Value + b.TangentIn * length; + AnimationUtils::CubicHermiteFirstDerivative( a.Value, b.Value, leftTangent, rightTangent, alpha, result); } static void InterpolateKey(const HermiteCurveKeyframe& a, const HermiteCurveKeyframe& b, float alpha, float length, HermiteCurveKeyframe& result) { result.Time = a.Time + length * alpha; - T leftTangent = a.Value + a.TangentOut * length; T rightTangent = b.Value + b.TangentIn * length; - - AnimationUtils::CubicHermite(alpha, a.Value, b.Value, leftTangent, rightTangent, result.Value); - AnimationUtils::CubicHermiteD1(alpha, a.Value, b.Value, leftTangent, rightTangent, result.TangentIn); - + AnimationUtils::CubicHermite(a.Value, b.Value, leftTangent, rightTangent, alpha, result.Value); + AnimationUtils::CubicHermiteFirstDerivative(a.Value, b.Value, leftTangent, rightTangent, alpha, result.TangentIn); result.TangentIn /= length; result.TangentOut = result.TangentIn; } + + bool operator==(const HermiteCurveKeyframe& other) const + { + return Math::NearEqual(Time, other.Time) && Math::NearEqual(Value, other.Value) && Math::NearEqual(TangentIn, other.TangentIn) && Math::NearEqual(TangentOut, other.TangentOut); + } } PACK_END(); /// @@ -223,23 +249,32 @@ public: T leftTangent, rightTangent; AnimationUtils::GetTangent(a.Value, a.TangentOut, length, leftTangent); AnimationUtils::GetTangent(b.Value, b.TangentIn, length, rightTangent); - AnimationUtils::Bezier(a.Value, leftTangent, rightTangent, b.Value, alpha, result); } + static void InterpolateFirstDerivative(const BezierCurveKeyframe& a, const BezierCurveKeyframe& b, float alpha, float length, T& result) + { + T leftTangent, rightTangent; + AnimationUtils::GetTangent(a.Value, a.TangentOut, length, leftTangent); + AnimationUtils::GetTangent(b.Value, b.TangentIn, length, rightTangent); + AnimationUtils::BezierFirstDerivative(a.Value, leftTangent, rightTangent, b.Value, alpha, result); + } + static void InterpolateKey(const BezierCurveKeyframe& a, const BezierCurveKeyframe& b, float alpha, float length, BezierCurveKeyframe& result) { result.Time = a.Time + length * alpha; - T leftTangent, rightTangent; AnimationUtils::GetTangent(a.Value, a.TangentOut, length, leftTangent); AnimationUtils::GetTangent(b.Value, b.TangentIn, length, rightTangent); - AnimationUtils::Bezier(a.Value, leftTangent, rightTangent, b.Value, alpha, result.Value); - result.TangentIn = a.TangentOut; result.TangentOut = b.TangentIn; } + + bool operator==(const BezierCurveKeyframe& other) const + { + return Math::NearEqual(Time, other.Time) && Math::NearEqual(Value, other.Value) && Math::NearEqual(TangentIn, other.TangentIn) && Math::NearEqual(TangentOut, other.TangentOut); + } } PACK_END(); // @formatter:on @@ -252,7 +287,7 @@ class CurveBase { public: - typedef DataContainer KeyFrameData; + typedef Span KeyFrameData; protected: @@ -346,6 +381,48 @@ public: KeyFrame::Interpolate(leftKey, rightKey, t, length, result); } + /// + /// Evaluates the first derivative of the animation curve at the specified time (aka velocity). + /// + /// The keyframes data container. + /// The calculated first derivative from the curve at provided time. + /// The time to evaluate the curve at. + /// If true the curve will loop when it goes past the end or beginning. Otherwise the curve value will be clamped. + void EvaluateFirstDerivative(const KeyFrameData& data, T& result, float time, bool loop = true) const + { + const int32 count = data.Length(); + if (count == 0) + { + result = _default; + return; + } + + const float start = 0; + const float end = data[count - 1].Time; + AnimationUtils::WrapTime(time, start, end, loop); + + int32 leftKeyIdx; + int32 rightKeyIdx; + FindKeys(data, time, leftKeyIdx, rightKeyIdx); + + const KeyFrame& leftKey = data[leftKeyIdx]; + const KeyFrame& rightKey = data[rightKeyIdx]; + + if (leftKeyIdx == rightKeyIdx) + { + result = leftKey.Value; + return; + } + + const float length = rightKey.Time - leftKey.Time; + + // Scale from arbitrary range to [0, 1] + float t = Math::NearEqual(length, 0.0f) ? 0.0f : (time - leftKey.Time) / length; + + // Evaluate the derivative at the curve + KeyFrame::InterpolateFirstDerivative(leftKey, rightKey, t, length, result); + } + /// /// Evaluates the animation curve key at the specified time. /// @@ -562,10 +639,22 @@ public: /// If true the curve will loop when it goes past the end or beginning. Otherwise the curve value will be clamped. void Evaluate(T& result, float time, bool loop = true) const { - typename Base::KeyFrameData data(_keyframes); + typename Base::KeyFrameData data(_keyframes.Get(), _keyframes.Count()); Base::Evaluate(data, result, time, loop); } + /// + /// Evaluates the first derivative of the animation curve at the specified time (aka velocity). + /// + /// The calculated first derivative from the curve at provided time. + /// The time to evaluate the curve at. + /// If true the curve will loop when it goes past the end or beginning. Otherwise the curve value will be clamped. + void EvaluateFirstDerivative(T& result, float time, bool loop = true) const + { + typename Base::KeyFrameData data(_keyframes.Get(), _keyframes.Count()); + Base::EvaluateFirstDerivative(data, result, time, loop); + } + /// /// Evaluates the animation curve key at the specified time. /// @@ -574,7 +663,7 @@ public: /// If true the curve will loop when it goes past the end or beginning. Otherwise the curve value will be clamped. void EvaluateKey(KeyFrame& result, float time, bool loop = true) const { - typename Base::KeyFrameData data(_keyframes); + typename Base::KeyFrameData data(_keyframes.Get(), _keyframes.Count()); Base::EvaluateKey(data, result, time, loop); } @@ -595,7 +684,7 @@ public: return; } - typename Base::KeyFrameData data(_keyframes); + typename Base::KeyFrameData data(_keyframes.Get(), _keyframes.Count()); KeyFrame startValue, endValue; Base::EvaluateKey(data, startValue, start, false); Base::EvaluateKey(data, endValue, end, false); @@ -660,53 +749,26 @@ public: public: - /// - /// Serializes the curve data to the stream. - /// - /// The output stream. - void Serialize(WriteStream& stream) const + FORCE_INLINE KeyFrame& operator[](int32 index) { - // Version - if (_keyframes.IsEmpty()) - { - stream.WriteInt32(0); - return; - } - stream.WriteInt32(1); - - // TODO: support compression (serialize compression mode) - - // Raw keyframes data - stream.WriteInt32(_keyframes.Count()); - stream.WriteBytes(_keyframes.Get(), _keyframes.Count() * sizeof(KeyFrame)); + return _keyframes[index]; } - /// - /// Deserializes the curve data from the stream. - /// - /// The input stream. - bool Deserialize(ReadStream& stream) + FORCE_INLINE const KeyFrame& operator[](int32 index) const { - // Cleanup - _keyframes.Resize(0); + return _keyframes[index]; + } - // Version - int32 version; - stream.ReadInt32(&version); - if (version == 0) + bool operator==(const Curve& other) const + { + if (_keyframes.Count() != other._keyframes.Count()) return false; - if (version != 1) + for (int32 i = 0; i < _keyframes.Count(); i++) { - return true; + if (!(_keyframes[i] == other._keyframes[i])) + return false; } - - // Raw keyframes data - int32 keyframesCount; - stream.ReadInt32(&keyframesCount); - _keyframes.Resize(keyframesCount, false); - stream.ReadBytes(_keyframes.Get(), _keyframes.Count() * sizeof(KeyFrame)); - - return false; + return true; } }; diff --git a/Source/Engine/Animations/CurveSerialization.h b/Source/Engine/Animations/CurveSerialization.h new file mode 100644 index 000000000..4af89ceca --- /dev/null +++ b/Source/Engine/Animations/CurveSerialization.h @@ -0,0 +1,218 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Curve.h" +#include "Engine/Core/Types/DataContainer.h" +#include "Engine/Serialization/ReadStream.h" +#include "Engine/Serialization/WriteStream.h" +#include "Engine/Serialization/Serialization.h" + +// @formatter:off + +namespace Serialization +{ + // StepCurveKeyframe + + template + inline bool ShouldSerialize(const StepCurveKeyframe& v, const void* otherObj) + { + if (!otherObj) + return true; + const auto other = (const StepCurveKeyframe*)otherObj; + return !(v == *other); + } + template + inline void Serialize(ISerializable::SerializeStream& stream, const StepCurveKeyframe& v, const void* otherObj) + { + stream.StartObject(); + stream.JKEY("Time"); + Serialize(stream, v.Time, nullptr); + stream.JKEY("Value"); + Serialize(stream, v.Value, nullptr); + stream.EndObject(); + } + template + inline void Deserialize(ISerializable::DeserializeStream& stream, StepCurveKeyframe& v, ISerializeModifier* modifier) + { + DESERIALIZE_MEMBER(Time, v.Time); + DESERIALIZE_MEMBER(Value, v.Value); + } + + // LinearCurveKeyframe + + template + inline bool ShouldSerialize(const LinearCurveKeyframe& v, const void* otherObj) + { + if (!otherObj) + return true; + const auto other = (const LinearCurveKeyframe*)otherObj; + return !(v == *other); + } + template + inline void Serialize(ISerializable::SerializeStream& stream, const LinearCurveKeyframe& v, const void* otherObj) + { + stream.StartObject(); + stream.JKEY("Time"); + Serialize(stream, v.Time, nullptr); + stream.JKEY("Value"); + Serialize(stream, v.Value, nullptr); + stream.EndObject(); + } + template + inline void Deserialize(ISerializable::DeserializeStream& stream, LinearCurveKeyframe& v, ISerializeModifier* modifier) + { + DESERIALIZE_MEMBER(Time, v.Time); + DESERIALIZE_MEMBER(Value, v.Value); + } + + // HermiteCurveKeyframe + + template + inline bool ShouldSerialize(const HermiteCurveKeyframe& v, const void* otherObj) + { + if (!otherObj) + return true; + const auto other = (const HermiteCurveKeyframe*)otherObj; + return !(v == *other); + } + template + inline void Serialize(ISerializable::SerializeStream& stream, const HermiteCurveKeyframe& v, const void* otherObj) + { + stream.StartObject(); + stream.JKEY("Time"); + Serialize(stream, v.Time, nullptr); + stream.JKEY("Value"); + Serialize(stream, v.Value, nullptr); + stream.JKEY("TangentIn"); + Serialize(stream, v.TangentIn, nullptr); + stream.JKEY("TangentOut"); + Serialize(stream, v.TangentOut, nullptr); + stream.EndObject(); + } + template + inline void Deserialize(ISerializable::DeserializeStream& stream, HermiteCurveKeyframe& v, ISerializeModifier* modifier) + { + DESERIALIZE_MEMBER(Time, v.Time); + DESERIALIZE_MEMBER(Value, v.Value); + DESERIALIZE_MEMBER(TangentIn, v.TangentIn); + DESERIALIZE_MEMBER(TangentOut, v.TangentOut); + } + + // BezierCurveKeyframe + + template + inline bool ShouldSerialize(const BezierCurveKeyframe& v, const void* otherObj) + { + if (!otherObj) + return true; + const auto other = (const BezierCurveKeyframe*)otherObj; + return !(v == *other); + } + template + inline void Serialize(ISerializable::SerializeStream& stream, const BezierCurveKeyframe& v, const void* otherObj) + { + stream.StartObject(); + stream.JKEY("Time"); + Serialize(stream, v.Time, nullptr); + stream.JKEY("Value"); + Serialize(stream, v.Value, nullptr); + stream.JKEY("TangentIn"); + Serialize(stream, v.TangentIn, nullptr); + stream.JKEY("TangentOut"); + Serialize(stream, v.TangentOut, nullptr); + stream.EndObject(); + } + template + inline void Deserialize(ISerializable::DeserializeStream& stream, BezierCurveKeyframe& v, ISerializeModifier* modifier) + { + DESERIALIZE_MEMBER(Time, v.Time); + DESERIALIZE_MEMBER(Value, v.Value); + DESERIALIZE_MEMBER(TangentIn, v.TangentIn); + DESERIALIZE_MEMBER(TangentOut, v.TangentOut); + } + + // Curve + + template + inline bool ShouldSerialize(const Curve& v, const void* otherObj) + { + if (!otherObj) + return true; + const auto other = (const Curve*)otherObj; + return !(v == *other); + } + template + inline void Serialize(ISerializable::SerializeStream& stream, const Curve& v, const void* otherObj) + { + stream.StartObject(); + stream.JKEY("Keyframes"); + stream.StartArray(); + for (auto& k : v.GetKeyframes()) + Serialize(stream, k, nullptr); + stream.EndArray(); + stream.EndObject(); + } + template + inline void Deserialize(ISerializable::DeserializeStream& stream, Curve& v, ISerializeModifier* modifier) + { + if (!stream.IsObject()) + return; + const auto mKeyframes = SERIALIZE_FIND_MEMBER(stream, "Keyframes"); + if (mKeyframes != stream.MemberEnd()) + { + const auto& keyframesArray = mKeyframes->value.GetArray(); + auto& keyframes = v.GetKeyframes(); + keyframes.Resize(keyframesArray.Size()); + for (rapidjson::SizeType i = 0; i < keyframesArray.Size(); i++) + Deserialize(keyframesArray[i], keyframes[i], modifier); + } + } + + template + inline void Serialize(WriteStream& stream, const Curve& v) + { + auto& keyframes = v.GetKeyframes(); + + // Version + if (keyframes.IsEmpty()) + { + stream.WriteInt32(0); + return; + } + stream.WriteInt32(1); + + // TODO: support compression (serialize compression mode) + + // Raw keyframes data + stream.WriteInt32(keyframes.Count()); + stream.WriteBytes(keyframes.Get(), keyframes.Count() * sizeof(KeyFrame)); + } + + template + inline bool Deserialize(ReadStream& stream, Curve& v) + { + auto& keyframes = v.GetKeyframes(); + keyframes.Resize(0); + + // Version + int32 version; + stream.ReadInt32(&version); + if (version == 0) + return false; + if (version != 1) + { + return true; + } + + // Raw keyframes data + int32 keyframesCount; + stream.ReadInt32(&keyframesCount); + keyframes.Resize(keyframesCount, false); + stream.ReadBytes(keyframes.Get(), keyframes.Count() * sizeof(KeyFrame)); + + return false; + } +} + +// @formatter:on diff --git a/Source/Engine/Audio/Audio.cpp b/Source/Engine/Audio/Audio.cpp index 1dd0393b4..51f674e73 100644 --- a/Source/Engine/Audio/Audio.cpp +++ b/Source/Engine/Audio/Audio.cpp @@ -44,9 +44,14 @@ Array Audio::Devices; Action Audio::DevicesChanged; Action Audio::ActiveDeviceChanged; AudioBackend* AudioBackend::Instance = nullptr; -float MasterVolume = 1.0f; -float Volume = 1.0f; -int32 ActiveDeviceIndex = -1; + +namespace +{ + float MasterVolume = 1.0f; + float Volume = 1.0f; + int32 ActiveDeviceIndex = -1; + bool MuteOnFocusLoss = true; +} class AudioService : public EngineService { @@ -77,6 +82,11 @@ namespace } } +void AudioSettings::Apply() +{ + ::MuteOnFocusLoss = MuteOnFocusLoss; +} + AudioDevice* Audio::GetActiveDevice() { return &Devices[ActiveDeviceIndex]; @@ -161,7 +171,7 @@ void Audio::OnRemoveSource(AudioSource* source) bool AudioService::Init() { - const auto settings = AudioSettings::Instance(); + const auto settings = AudioSettings::Get(); const bool mute = CommandLine::Options.Mute.IsTrue() || settings->DisableAudio; // Pick a backend to use @@ -225,11 +235,9 @@ void AudioService::Update() { PROFILE_CPU(); - const auto settings = AudioSettings::Instance(); - // Update the master volume float masterVolume = MasterVolume; - if (settings->MuteOnFocusLoss && !Engine::HasFocus) + if (MuteOnFocusLoss && !Engine::HasFocus) { // Mute audio if app has no user focus masterVolume = 0.0f; diff --git a/Source/Engine/Audio/AudioClip.cpp b/Source/Engine/Audio/AudioClip.cpp index 4005e46b9..29dd2a153 100644 --- a/Source/Engine/Audio/AudioClip.cpp +++ b/Source/Engine/Audio/AudioClip.cpp @@ -125,7 +125,7 @@ void AudioClip::StreamingTask::OnEnd() { ASSERT(_asset->_streamingTask == this); _asset->_streamingTask = nullptr; - _asset.Unlink(); + _asset = nullptr; } _dataLock.Release(); diff --git a/Source/Engine/Audio/AudioSettings.h b/Source/Engine/Audio/AudioSettings.h index 2f73e3d1d..b5efe9524 100644 --- a/Source/Engine/Audio/AudioSettings.h +++ b/Source/Engine/Audio/AudioSettings.h @@ -8,35 +8,38 @@ /// /// Audio settings container. /// -/// -class AudioSettings : public Settings +API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API AudioSettings : public SettingsBase { +DECLARE_SCRIPTING_TYPE_MINIMAL(AudioSettings); public: /// /// If checked, audio playback will be disabled in build game. Can be used if game uses custom audio playback engine. /// + API_FIELD(Attributes="EditorOrder(0), DefaultValue(false), EditorDisplay(\"General\")") bool DisableAudio = false; /// /// The doppler effect factor. Scale for source and listener velocities. Default is 1. /// + API_FIELD(Attributes="EditorOrder(100), DefaultValue(1.0f), EditorDisplay(\"General\")") float DopplerFactor = 1.0f; /// /// True if mute all audio playback when game has no use focus. /// + API_FIELD(Attributes="EditorOrder(200), DefaultValue(true), EditorDisplay(\"General\", \"Mute On Focus Loss\")") bool MuteOnFocusLoss = true; public: - // [Settings] - void RestoreDefault() final override - { - DisableAudio = false; - DopplerFactor = 1.0f; - MuteOnFocusLoss = true; - } + /// + /// Gets the instance of the settings asset (default value if missing). Object returned by this method is always loaded with valid data to use. + /// + static AudioSettings* Get(); + + // [SettingsBase] + void Apply() override; void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override { diff --git a/Source/Engine/Audio/OpenAL/AudioBackendOAL.cpp b/Source/Engine/Audio/OpenAL/AudioBackendOAL.cpp index e06ab35aa..89c8f3d02 100644 --- a/Source/Engine/Audio/OpenAL/AudioBackendOAL.cpp +++ b/Source/Engine/Audio/OpenAL/AudioBackendOAL.cpp @@ -878,7 +878,7 @@ bool AudioBackendOAL::Base_Init() // Init ALC::AL_EXT_float32 = ALC::IsExtensionSupported("AL_EXT_float32"); - SetDopplerFactor(AudioSettings::Instance()->DopplerFactor); + SetDopplerFactor(AudioSettings::Get()->DopplerFactor); ALC::RebuildContexts(true); Audio::SetActiveDeviceIndex(activeDeviceIndex); diff --git a/Source/Engine/Audio/XAudio2/AudioBackendXAudio2.cpp b/Source/Engine/Audio/XAudio2/AudioBackendXAudio2.cpp index a201253ac..aac3979ce 100644 --- a/Source/Engine/Audio/XAudio2/AudioBackendXAudio2.cpp +++ b/Source/Engine/Audio/XAudio2/AudioBackendXAudio2.cpp @@ -794,7 +794,7 @@ void AudioBackendXAudio2::Base_Update() } // Update dirty voices - const float dopplerFactor = AudioSettings::Instance()->DopplerFactor; + const float dopplerFactor = AudioSettings::Get()->DopplerFactor; X3DAUDIO_DSP_SETTINGS dsp = { 0 }; dsp.DstChannelCount = XAudio2::Channels; dsp.pMatrixCoefficients = XAudio2::MatrixCoefficients; diff --git a/Source/Engine/Content/AssetReference.h b/Source/Engine/Content/AssetReference.h index c78aa5aef..658545d1a 100644 --- a/Source/Engine/Content/AssetReference.h +++ b/Source/Engine/Content/AssetReference.h @@ -78,14 +78,6 @@ public: return _asset ? _asset->GetOrCreateManagedInstance() : nullptr; } - /// - /// Clears the asset reference. - /// - FORCE_INLINE void Unlink() - { - OnSet(nullptr); - } - /// /// Gets the asset property value as string. /// @@ -135,7 +127,7 @@ protected: if (_asset == asset) { Unload(); - Unlink(); + OnSet(nullptr); } } }; diff --git a/Source/Engine/Content/Assets/Animation.cpp b/Source/Engine/Content/Assets/Animation.cpp index c6927530e..963ddaceb 100644 --- a/Source/Engine/Content/Assets/Animation.cpp +++ b/Source/Engine/Content/Assets/Animation.cpp @@ -5,6 +5,7 @@ #include "Engine/Core/Log.h" #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Content/Factories/BinaryAssetFactory.h" +#include "Engine/Animations/CurveSerialization.h" #include "Engine/Serialization/MemoryReadStream.h" #if USE_EDITOR #include "Engine/Serialization/MemoryWriteStream.h" @@ -358,9 +359,9 @@ bool Animation::Save(const StringView& path) { auto& anim = Data.Channels[i]; stream.WriteString(anim.NodeName, 172); - anim.Position.Serialize(stream); - anim.Rotation.Serialize(stream); - anim.Scale.Serialize(stream); + Serialization::Serialize(stream, anim.Position); + Serialization::Serialize(stream, anim.Rotation); + Serialization::Serialize(stream, anim.Scale); } // Set data to the chunk asset @@ -442,9 +443,9 @@ Asset::LoadResult Animation::load() auto& anim = Data.Channels[i]; stream.ReadString(&anim.NodeName, 172); - bool failed = anim.Position.Deserialize(stream); - failed |= anim.Rotation.Deserialize(stream); - failed |= anim.Scale.Deserialize(stream); + bool failed = Serialization::Deserialize(stream, anim.Position); + failed |= Serialization::Deserialize(stream, anim.Rotation); + failed |= Serialization::Deserialize(stream, anim.Scale); if (failed) { diff --git a/Source/Engine/Content/Assets/Material.cpp b/Source/Engine/Content/Assets/Material.cpp index b40e1e9c0..4d1e5d283 100644 --- a/Source/Engine/Content/Assets/Material.cpp +++ b/Source/Engine/Content/Assets/Material.cpp @@ -61,9 +61,9 @@ bool Material::CanUseLightmap() const return _materialShader && _materialShader->CanUseLightmap(); } -bool Material::CanUseInstancing() const +bool Material::CanUseInstancing(InstancingHandler& handler) const { - return _materialShader && _materialShader->CanUseInstancing(); + return _materialShader && _materialShader->CanUseInstancing(handler); } void Material::Bind(BindParameters& params) @@ -390,14 +390,14 @@ void Material::InitCompilationOptions(ShaderCompilationOptions& options) // Prepare auto& info = _shaderHeader.Material.Info; - const bool isSurfaceOrTerrain = info.Domain == MaterialDomain::Surface || info.Domain == MaterialDomain::Terrain; + const bool isSurfaceOrTerrainOrDeformable = info.Domain == MaterialDomain::Surface || info.Domain == MaterialDomain::Terrain || info.Domain == MaterialDomain::Deformable; const bool useCustomData = info.ShadingModel == MaterialShadingModel::Subsurface || info.ShadingModel == MaterialShadingModel::Foliage; - const bool useForward = (info.Domain == MaterialDomain::Surface && info.BlendMode != MaterialBlendMode::Opaque) || info.Domain == MaterialDomain::Particle; + const bool useForward = ((info.Domain == MaterialDomain::Surface || info.Domain == MaterialDomain::Deformable) && info.BlendMode != MaterialBlendMode::Opaque) || info.Domain == MaterialDomain::Particle; const bool useTess = info.TessellationMode != TessellationMethod::None && - RenderTools::CanSupportTessellation(options.Profile) && isSurfaceOrTerrain; + RenderTools::CanSupportTessellation(options.Profile) && isSurfaceOrTerrainOrDeformable; const bool useDistortion = - (info.Domain == MaterialDomain::Surface || info.Domain == MaterialDomain::Particle) && + (info.Domain == MaterialDomain::Surface || info.Domain == MaterialDomain::Deformable || info.Domain == MaterialDomain::Particle) && info.BlendMode != MaterialBlendMode::Opaque && (info.UsageFlags & MaterialUsageFlags::UseRefraction) != 0 && (info.FeaturesFlags & MaterialFeaturesFlags::DisableDistortion) == 0; @@ -455,10 +455,10 @@ void Material::InitCompilationOptions(ShaderCompilationOptions& options) options.Macros.Add({ "IS_DECAL", Numbers[info.Domain == MaterialDomain::Decal ? 1 : 0] }); options.Macros.Add({ "IS_TERRAIN", Numbers[info.Domain == MaterialDomain::Terrain ? 1 : 0] }); options.Macros.Add({ "IS_PARTICLE", Numbers[info.Domain == MaterialDomain::Particle ? 1 : 0] }); + options.Macros.Add({ "IS_DEFORMABLE", Numbers[info.Domain == MaterialDomain::Deformable ? 1 : 0] }); options.Macros.Add({ "USE_FORWARD", Numbers[useForward ? 1 : 0] }); - options.Macros.Add({ "USE_DEFERRED", Numbers[isSurfaceOrTerrain && info.BlendMode == MaterialBlendMode::Opaque ? 1 : 0] }); + options.Macros.Add({ "USE_DEFERRED", Numbers[isSurfaceOrTerrainOrDeformable && info.BlendMode == MaterialBlendMode::Opaque ? 1 : 0] }); options.Macros.Add({ "USE_DISTORTION", Numbers[useDistortion ? 1 : 0] }); - options.Macros.Add({ "CAN_USE_LIGHTMAP", Numbers[isSurfaceOrTerrain ? 1 : 0] }); #endif } diff --git a/Source/Engine/Content/Assets/Material.h b/Source/Engine/Content/Assets/Material.h index bdaba7abb..0a9ad1dbc 100644 --- a/Source/Engine/Content/Assets/Material.h +++ b/Source/Engine/Content/Assets/Material.h @@ -48,7 +48,7 @@ public: bool IsReady() const override; DrawPass GetDrawModes() const override; bool CanUseLightmap() const override; - bool CanUseInstancing() const override; + bool CanUseInstancing(InstancingHandler& handler) const override; void Bind(BindParameters& params) override; // [ShaderAssetBase] diff --git a/Source/Engine/Content/Assets/MaterialInstance.cpp b/Source/Engine/Content/Assets/MaterialInstance.cpp index ace2fcee9..0678f9992 100644 --- a/Source/Engine/Content/Assets/MaterialInstance.cpp +++ b/Source/Engine/Content/Assets/MaterialInstance.cpp @@ -167,9 +167,9 @@ bool MaterialInstance::CanUseLightmap() const return _baseMaterial && _baseMaterial->CanUseLightmap(); } -bool MaterialInstance::CanUseInstancing() const +bool MaterialInstance::CanUseInstancing(InstancingHandler& handler) const { - return _baseMaterial && _baseMaterial->CanUseInstancing(); + return _baseMaterial && _baseMaterial->CanUseInstancing(handler); } void MaterialInstance::Bind(BindParameters& params) diff --git a/Source/Engine/Content/Assets/MaterialInstance.h b/Source/Engine/Content/Assets/MaterialInstance.h index 9fe92e39c..3cf74db03 100644 --- a/Source/Engine/Content/Assets/MaterialInstance.h +++ b/Source/Engine/Content/Assets/MaterialInstance.h @@ -64,7 +64,7 @@ public: bool IsReady() const override; DrawPass GetDrawModes() const override; bool CanUseLightmap() const override; - bool CanUseInstancing() const override; + bool CanUseInstancing(InstancingHandler& handler) const override; void Bind(BindParameters& params) override; protected: diff --git a/Source/Engine/Content/Assets/Model.cpp b/Source/Engine/Content/Assets/Model.cpp index 897e589a8..8cdd0601e 100644 --- a/Source/Engine/Content/Assets/Model.cpp +++ b/Source/Engine/Content/Assets/Model.cpp @@ -104,7 +104,7 @@ protected: { ASSERT(_asset->_streamingTask == this); _asset->_streamingTask = nullptr; - _asset.Unlink(); + _asset = nullptr; } _dataLock.Release(); diff --git a/Source/Engine/Content/Assets/SkeletonMask.cpp b/Source/Engine/Content/Assets/SkeletonMask.cpp index 4747e0e44..0742e2951 100644 --- a/Source/Engine/Content/Assets/SkeletonMask.cpp +++ b/Source/Engine/Content/Assets/SkeletonMask.cpp @@ -38,7 +38,7 @@ Asset::LoadResult SkeletonMask::load() void SkeletonMask::unload(bool isReloading) { - Skeleton.Unlink(); + Skeleton = nullptr; _maskedNodes.Resize(0); _mask.Resize(0); } diff --git a/Source/Engine/Content/Assets/SkinnedModel.cpp b/Source/Engine/Content/Assets/SkinnedModel.cpp index 5309cf6b9..dfccfec19 100644 --- a/Source/Engine/Content/Assets/SkinnedModel.cpp +++ b/Source/Engine/Content/Assets/SkinnedModel.cpp @@ -97,7 +97,7 @@ protected: { ASSERT(_asset->_streamingTask == this); _asset->_streamingTask = nullptr; - _asset.Unlink(); + _asset = nullptr; } _dataLock.Release(); diff --git a/Source/Engine/Content/Assets/VisualScript.cpp b/Source/Engine/Content/Assets/VisualScript.cpp index 65a75d1ef..da1b97687 100644 --- a/Source/Engine/Content/Assets/VisualScript.cpp +++ b/Source/Engine/Content/Assets/VisualScript.cpp @@ -6,6 +6,7 @@ #include "Engine/Content/Factories/BinaryAssetFactory.h" #include "Engine/Scripting/MException.h" #include "Engine/Scripting/Scripting.h" +#include "Engine/Scripting/Events.h" #include "Engine/Scripting/ManagedCLR/MClass.h" #include "Engine/Scripting/ManagedCLR/MMethod.h" #include "Engine/Scripting/ManagedCLR/MField.h" @@ -347,7 +348,7 @@ void VisualScriptExecutor::ProcessGroupParameters(Box* box, Node* node, Value& v const auto instanceParams = stack.Stack->Script->_instances.Find(stack.Stack->Instance->GetID()); if (param && instanceParams) { - value = instanceParams->Value[paramIndex]; + value = instanceParams->Value.Params[paramIndex]; } else { @@ -371,7 +372,7 @@ void VisualScriptExecutor::ProcessGroupParameters(Box* box, Node* node, Value& v const auto instanceParams = stack.Stack->Script->_instances.Find(stack.Stack->Instance->GetID()); if (param && instanceParams) { - instanceParams->Value[paramIndex] = tryGetValue(node->GetBox(1), 1, Value::Zero); + instanceParams->Value.Params[paramIndex] = tryGetValue(node->GetBox(1), 1, Value::Zero); } else { @@ -1004,6 +1005,125 @@ void VisualScriptExecutor::ProcessGroupFunction(Box* boxBase, Node* node, Value& if (returnedImpulse && returnedImpulse->HasConnection()) eatBox(node, returnedImpulse->FirstConnection()); break; + } + // Bind/Unbind + case 9: + case 10: + { + const bool bind = node->TypeID == 9; + auto& stack = ThreadStacks.Get(); + if (!stack.Stack->Instance) + { + // TODO: add support for binding to events in static Visual Script + LOG(Error, "Cannot bind to event in static Visual Script."); + PrintStack(LogType::Error); + break; + } + const auto object = stack.Stack->Instance; + + // Find method to bind + VisualScriptGraphNode* methodNode = nullptr; + const auto graph = stack.Stack && stack.Stack->Script ? &stack.Stack->Script->Graph : nullptr; + if (graph) + methodNode = graph->GetNode((uint32)node->Values[2]); + if (!methodNode) + { + LOG(Error, "Missing function handler to bind to the event."); + PrintStack(LogType::Error); + break; + } + VisualScript::Method* method = nullptr; + for (auto& m : stack.Stack->Script->_methods) + { + if (m.Node == methodNode) + { + method = &m; + break; + } + } + if (!method) + { + LOG(Error, "Missing method to bind to the event."); + PrintStack(LogType::Error); + break; + } + + // Find event + const StringView eventTypeName(node->Values[0]); + const StringView eventName(node->Values[1]); + const StringAsANSI<100> eventTypeNameAnsi(eventTypeName.Get(), eventTypeName.Length()); + const ScriptingTypeHandle eventType = Scripting::FindScriptingType(StringAnsiView(eventTypeNameAnsi.Get(), eventTypeName.Length())); + + // Find event binding callback + auto eventBinder = ScriptingEvents::EventsTable.TryGet(Pair(eventType, eventName)); + if (!eventBinder) + { + LOG(Error, "Cannot bind to missing event {0} from type {1}.", eventName, eventTypeName); + PrintStack(LogType::Error); + break; + } + + // Evaluate object instance + const auto box = node->GetBox(1); + Variant instance; + if (box->HasConnection()) + instance = eatBox(node, box->FirstConnection()); + else + instance.SetObject(object); + if (!instance.AsObject) + { + LOG(Error, "Cannot bind event to null object."); + PrintStack(LogType::Error); + break; + } + // TODO: check if instance is of event type (including inheritance) + + // Add Visual Script method to the event bindings table + const auto& type = object->GetType(); + Guid id; + if (Guid::Parse(type.Fullname, id)) + break; + if (const auto visualScript = (VisualScript*)Content::GetAsset(id)) + { + if (auto i = visualScript->GetScriptInstance(object)) + { + VisualScript::EventBinding* eventBinding = nullptr; + for (auto& b : i->EventBindings) + { + if (b.Type == eventType && b.Name == eventName) + { + eventBinding = &b; + break; + } + } + if (bind) + { + // Bind to the event + if (!eventBinding) + { + eventBinding = &i->EventBindings.AddOne(); + eventBinding->Type = eventType; + eventBinding->Name = eventName; + } + eventBinding->BindedMethods.Add(method); + if (eventBinding->BindedMethods.Count() == 1) + (*eventBinder)(instance.AsObject, object, true); + } + else if (eventBinding) + { + // Unbind from the event + if (eventBinding->BindedMethods.Count() == 1) + (*eventBinder)(instance.AsObject, object, false); + eventBinding->BindedMethods.Remove(method); + } + } + } + + // Call graph further + const auto returnedImpulse = &node->Boxes[2]; + if (returnedImpulse && returnedImpulse->HasConnection()) + eatBox(node, returnedImpulse->FirstConnection()); + break; } default: break; @@ -1290,11 +1410,11 @@ Asset::LoadResult VisualScript::load() // Hack vtable similarly to VisualScriptObjectSpawn ScriptingType& visualScriptType = (ScriptingType&)object->GetType(); - if (visualScriptType.Class.ScriptVTable) + if (visualScriptType.Script.ScriptVTable) { // Override object vtable with hacked one that has Visual Script functions calls - ASSERT(visualScriptType.Class.VTable); - *(void**)object = visualScriptType.Class.VTable; + ASSERT(visualScriptType.Script.VTable); + *(void**)object = visualScriptType.Script.VTable; } } const int32 oldCount = _oldParamsLayout.Count(); @@ -1304,7 +1424,7 @@ Asset::LoadResult VisualScript::load() // Update instanced data from previous format to the current graph parameters scheme for (auto& e : _instances) { - auto& instanceParams = e.Value; + auto& instanceParams = e.Value.Params; Array valuesCache(MoveTemp(instanceParams)); instanceParams.Resize(count); for (int32 i = 0; i < count; i++) @@ -1319,7 +1439,7 @@ Asset::LoadResult VisualScript::load() // Reset instances values to defaults for (auto& e : _instances) { - auto& instanceParams = e.Value; + auto& instanceParams = e.Value.Params; instanceParams.Resize(count); for (int32 i = 0; i < count; i++) instanceParams[i] = Graph.Parameters[i].Value; @@ -1361,10 +1481,10 @@ void VisualScript::unload(bool isReloading) if (_scriptingTypeHandle) { auto& type = VisualScriptingModule.Types[_scriptingTypeHandle.TypeIndex]; - if (type.Class.DefaultInstance) + if (type.Script.DefaultInstance) { - Delete(type.Class.DefaultInstance); - type.Class.DefaultInstance = nullptr; + Delete(type.Script.DefaultInstance); + type.Script.DefaultInstance = nullptr; } VisualScriptingModule.TypeNameToTypeIndex.RemoveValue(_scriptingTypeHandle.TypeIndex); VisualScriptingModule.Scripts[_scriptingTypeHandle.TypeIndex] = nullptr; @@ -1389,7 +1509,7 @@ void VisualScript::CacheScriptingType() { // Find first native base C++ class of this Visual Script class ScriptingTypeHandle nativeType = baseType; - while (nativeType && nativeType.GetType().Class.ScriptVTable) + while (nativeType && nativeType.GetType().Script.ScriptVTable) { nativeType = nativeType.GetType().GetBaseType(); } @@ -1421,13 +1541,17 @@ void VisualScript::CacheScriptingType() _scriptingTypeHandle = ScriptingTypeHandle(&binaryModule, typeIndex); binaryModule.Scripts.Add(this); -#if USE_EDITOR - // When first Visual Script gets loaded register for other modules unload to clear runtime execution cache + // Special initialization when the first Visual Script gets loaded if (typeIndex == 0) { +#if USE_EDITOR + // Register for other modules unload to clear runtime execution cache Scripting::ScriptsReloading.Bind(&binaryModule); - } #endif + + // Register for scripting events + ScriptingEvents::Event.Bind(VisualScriptingBinaryModule::OnEvent); + } } auto& type = _scriptingTypeHandle.Module->Types[_scriptingTypeHandle.TypeIndex]; type.ManagedClass = baseType.GetType().ManagedClass; @@ -1437,14 +1561,14 @@ void VisualScript::CacheScriptingType() for (ScriptingTypeHandle e = nativeType; e;) { const ScriptingType& eType = e.GetType(); - if (eType.Class.SetupScriptVTable) + if (eType.Script.SetupScriptVTable) { ASSERT(eType.ManagedClass); - eType.Class.SetupScriptVTable(eType.ManagedClass, type.Class.ScriptVTable, type.Class.ScriptVTableBase); + eType.Script.SetupScriptVTable(eType.ManagedClass, type.Script.ScriptVTable, type.Script.ScriptVTableBase); } e = eType.GetBaseType(); } - MMethod** scriptVTable = (MMethod**)type.Class.ScriptVTable; + MMethod** scriptVTable = (MMethod**)type.Script.ScriptVTable; while (scriptVTable && *scriptVTable) { const MMethod* referenceMethod = *scriptVTable; @@ -1498,12 +1622,12 @@ ScriptingObject* VisualScriptingBinaryModule::VisualScriptObjectSpawn(const Scri ScriptingType& visualScriptType = (ScriptingType&)params.Type.GetType(); ScriptingTypeHandle baseTypeHandle = visualScriptType.GetBaseType(); const ScriptingType* baseTypePtr = &baseTypeHandle.GetType(); - while (baseTypePtr->Class.Spawn == &VisualScriptObjectSpawn) + while (baseTypePtr->Script.Spawn == &VisualScriptObjectSpawn) { baseTypeHandle = baseTypePtr->GetBaseType(); baseTypePtr = &baseTypeHandle.GetType(); } - ScriptingObject* object = baseTypePtr->Class.Spawn(params); + ScriptingObject* object = baseTypePtr->Script.Spawn(params); if (!object) { return nullptr; @@ -1514,9 +1638,9 @@ ScriptingObject* VisualScriptingBinaryModule::VisualScriptObjectSpawn(const Scri // We create a custom vtable for the Visual Script objects that use a native class object with virtual functions overrides. // To make it easy to use in C++ we inject custom wrapper methods into C++ object vtable to execute Visual Script graph from them. // Because virtual member functions calls are C++ ABI and impl-defined this is quite hard. But works. - if (visualScriptType.Class.ScriptVTable) + if (visualScriptType.Script.ScriptVTable) { - if (!visualScriptType.Class.VTable) + if (!visualScriptType.Script.VTable) { // Duplicate vtable void** vtable = *(void***)object; @@ -1525,21 +1649,21 @@ ScriptingObject* VisualScriptingBinaryModule::VisualScriptObjectSpawn(const Scri while (vtable[entriesCount] && entriesCount < 200) entriesCount++; const int32 size = entriesCount * sizeof(void*); - visualScriptType.Class.VTable = (void**)((byte*)Platform::Allocate(prefixSize + size, 16) + prefixSize); - Platform::MemoryCopy((byte*)visualScriptType.Class.VTable - prefixSize, (byte*)vtable - prefixSize, prefixSize + size); + visualScriptType.Script.VTable = (void**)((byte*)Platform::Allocate(prefixSize + size, 16) + prefixSize); + Platform::MemoryCopy((byte*)visualScriptType.Script.VTable - prefixSize, (byte*)vtable - prefixSize, prefixSize + size); // Override vtable entries by the class for (ScriptingTypeHandle e = baseTypeHandle; e;) { const ScriptingType& eType = e.GetType(); - if (eType.Class.SetupScriptObjectVTable) - eType.Class.SetupScriptObjectVTable(visualScriptType.Class.ScriptVTable, visualScriptType.Class.ScriptVTableBase, visualScriptType.Class.VTable, entriesCount, 1); + if (eType.Script.SetupScriptObjectVTable) + eType.Script.SetupScriptObjectVTable(visualScriptType.Script.ScriptVTable, visualScriptType.Script.ScriptVTableBase, visualScriptType.Script.VTable, entriesCount, 1); e = eType.GetBaseType(); } } // Override object vtable with hacked one that has Visual Script functions calls - *(void**)object = visualScriptType.Class.VTable; + *(void**)object = visualScriptType.Script.VTable; } // Mark as custom scripting type @@ -1550,7 +1674,7 @@ ScriptingObject* VisualScriptingBinaryModule::VisualScriptObjectSpawn(const Scri VisualScript* visualScript = VisualScriptingModule.Scripts[params.Type.TypeIndex]; // Initialize instance data - auto& instanceParams = visualScript->_instances[object->GetID()]; + auto& instanceParams = visualScript->_instances[object->GetID()].Params; instanceParams.Resize(visualScript->Graph.Parameters.Count()); for (int32 i = 0; i < instanceParams.Count(); i++) instanceParams[i] = visualScript->Graph.Parameters[i].Value; @@ -1573,10 +1697,10 @@ void VisualScriptingBinaryModule::OnScriptsReloading() if (script->_scriptingTypeHandle) { auto& type = VisualScriptingModule.Types[script->_scriptingTypeHandle.TypeIndex]; - if (type.Class.DefaultInstance) + if (type.Script.DefaultInstance) { - Delete(type.Class.DefaultInstance); - type.Class.DefaultInstance = nullptr; + Delete(type.Script.DefaultInstance); + type.Script.DefaultInstance = nullptr; } VisualScriptingModule.TypeNameToTypeIndex.RemoveValue(script->_scriptingTypeHandle.TypeIndex); script->_scriptingTypeHandleCached = script->_scriptingTypeHandle; @@ -1608,6 +1732,56 @@ void VisualScriptingBinaryModule::OnScriptsReloading() #endif +void VisualScriptingBinaryModule::OnEvent(ScriptingObject* object, Span& parameters, const ScriptingTypeHandle& eventType, const StringView& eventName) +{ + if (object) + { + // Object event + const auto& type = object->GetType(); + Guid id; + if (Guid::Parse(type.Fullname, id)) + return; + if (const auto visualScript = (VisualScript*)Content::GetAsset(id)) + { + if (auto instance = visualScript->GetScriptInstance(object)) + { + for (auto& b : instance->EventBindings) + { + if (b.Type != eventType || b.Name != eventName) + continue; + for (auto& m : b.BindedMethods) + { + VisualScripting::Invoke(m, object, parameters); + } + } + } + } + } + else + { + // Static event + for (auto& asset : Content::GetAssetsRaw()) + { + if (const auto visualScript = ScriptingObject::Cast(asset.Value)) + { + for (auto& e : visualScript->_instances) + { + auto instance = &e.Value; + for (auto& b : instance->EventBindings) + { + if (b.Type != eventType || b.Name != eventName) + continue; + for (auto& m : b.BindedMethods) + { + VisualScripting::Invoke(m, object, parameters); + } + } + } + } + } + } +} + const StringAnsi& VisualScriptingBinaryModule::GetName() const { return _name; @@ -1702,7 +1876,7 @@ bool VisualScriptingBinaryModule::GetFieldValue(void* field, const Variant& inst LOG(Error, "Missing parameters for the object instance."); return true; } - result = instanceParams->Value[vsFiled->Index]; + result = instanceParams->Value.Params[vsFiled->Index]; return false; } @@ -1721,7 +1895,7 @@ bool VisualScriptingBinaryModule::SetFieldValue(void* field, const Variant& inst LOG(Error, "Missing parameters for the object instance."); return true; } - instanceParams->Value[vsFiled->Index] = value; + instanceParams->Value.Params[vsFiled->Index] = value; return false; } @@ -1735,7 +1909,7 @@ void VisualScriptingBinaryModule::SerializeObject(JsonWriter& stream, ScriptingO const auto instanceParams = asset->_instances.Find(object->GetID()); if (instanceParams) { - auto& params = instanceParams->Value; + auto& params = instanceParams->Value.Params; if (otherObj) { // Serialize parameters diff @@ -1746,7 +1920,7 @@ void VisualScriptingBinaryModule::SerializeObject(JsonWriter& stream, ScriptingO { auto& param = asset->Graph.Parameters[paramIndex]; auto& value = params[paramIndex]; - auto& otherValue = otherParams->Value[paramIndex]; + auto& otherValue = otherParams->Value.Params[paramIndex]; if (value != otherValue) { param.Identifier.ToString(idName, Guid::FormatType::N); @@ -1798,7 +1972,7 @@ void VisualScriptingBinaryModule::DeserializeObject(ISerializable::DeserializeSt if (instanceParams) { // Deserialize all parameters - auto& params = instanceParams->Value; + auto& params = instanceParams->Value.Params; for (auto i = stream.MemberBegin(); i != stream.MemberEnd(); ++i) { StringAnsiView idNameAnsi(i->name.GetString(), i->name.GetStringLength()); @@ -1862,7 +2036,12 @@ ScriptingTypeHandle VisualScript::GetScriptingType() ScriptingObject* VisualScript::CreateInstance() { const auto scriptingTypeHandle = GetScriptingType(); - return scriptingTypeHandle ? scriptingTypeHandle.GetType().Class.Spawn(ScriptingObjectSpawnParams(Guid::New(), scriptingTypeHandle)) : nullptr; + return scriptingTypeHandle ? scriptingTypeHandle.GetType().Script.Spawn(ScriptingObjectSpawnParams(Guid::New(), scriptingTypeHandle)) : nullptr; +} + +VisualScript::Instance* VisualScript::GetScriptInstance(ScriptingObject* instance) const +{ + return instance ? _instances.TryGet(instance->GetID()) : nullptr; } Variant VisualScript::GetScriptInstanceParameterValue(const StringView& name, ScriptingObject* instance) const @@ -1874,7 +2053,7 @@ Variant VisualScript::GetScriptInstanceParameterValue(const StringView& name, Sc { const auto instanceParams = _instances.Find(instance->GetID()); if (instanceParams) - return instanceParams->Value[paramIndex]; + return instanceParams->Value.Params[paramIndex]; LOG(Error, "Failed to access Visual Script parameter {1} for {0}.", instance->ToString(), name); return Graph.Parameters[paramIndex].Value; } @@ -1893,7 +2072,7 @@ void VisualScript::SetScriptInstanceParameterValue(const StringView& name, Scrip const auto instanceParams = _instances.Find(instance->GetID()); if (instanceParams) { - instanceParams->Value[paramIndex] = value; + instanceParams->Value.Params[paramIndex] = value; return; } LOG(Error, "Failed to access Visual Script parameter {1} for {0}.", instance->ToString(), name); @@ -1913,7 +2092,7 @@ void VisualScript::SetScriptInstanceParameterValue(const StringView& name, Scrip const auto instanceParams = _instances.Find(instance->GetID()); if (instanceParams) { - instanceParams->Value[paramIndex] = MoveTemp(value); + instanceParams->Value.Params[paramIndex] = MoveTemp(value); return; } } diff --git a/Source/Engine/Content/Assets/VisualScript.h b/Source/Engine/Content/Assets/VisualScript.h index 0a7f34b1c..cf8dccce3 100644 --- a/Source/Engine/Content/Assets/VisualScript.h +++ b/Source/Engine/Content/Assets/VisualScript.h @@ -125,9 +125,22 @@ public: StringAnsi Name; }; + struct EventBinding + { + ScriptingTypeHandle Type; + String Name; + Array> BindedMethods; + }; + + struct Instance + { + Array Params; + Array EventBindings; + }; + private: - Dictionary> _instances; + Dictionary _instances; ScriptingTypeHandle _scriptingTypeHandle; ScriptingTypeHandle _scriptingTypeHandleCached; StringAnsiView _typename; @@ -179,6 +192,13 @@ public: /// The created instance or null if failed. API_FUNCTION() ScriptingObject* CreateInstance(); + /// + /// Gets the Visual Script instance data. + /// + /// The object instance. + /// The data or invalid instance (not VS or missing). + Instance* GetScriptInstance(ScriptingObject* instance) const; + /// /// Gets the value of the Visual Script parameter of the given instance. /// @@ -307,6 +327,7 @@ private: #if USE_EDITOR void OnScriptsReloading(); #endif + static void OnEvent(ScriptingObject* object, Span& parameters, const ScriptingTypeHandle& eventType, const StringView& eventName); public: diff --git a/Source/Engine/Content/BinaryAsset.cpp b/Source/Engine/Content/BinaryAsset.cpp index 7160cb287..1dbc672f2 100644 --- a/Source/Engine/Content/BinaryAsset.cpp +++ b/Source/Engine/Content/BinaryAsset.cpp @@ -491,7 +491,7 @@ protected: void OnEnd() override { _dataLock.Release(); - _asset.Unlink(); + _asset = nullptr; // Base ContentLoadTask::OnEnd(); diff --git a/Source/Engine/Content/Content.cpp b/Source/Engine/Content/Content.cpp index c56c2e658..63cb5ffc4 100644 --- a/Source/Engine/Content/Content.cpp +++ b/Source/Engine/Content/Content.cpp @@ -876,7 +876,7 @@ Asset* Content::LoadAsync(const Guid& id, const ScriptingTypeHandle& type) if (result) { // Validate type - if (IsAssetTypeIdInvalid(type, result->GetTypeHandle())) + if (IsAssetTypeIdInvalid(type, result->GetTypeHandle()) && !result->Is(type)) { LOG(Warning, "Different loaded asset type! Asset: \'{0}\'. Expected type: {1}", result->ToString(), type.ToString()); return nullptr; @@ -949,15 +949,6 @@ Asset* Content::load(const Guid& id, const ScriptingTypeHandle& type, AssetInfo& return nullptr; } - // Validate type - const StringAsANSI<> typeNameStd(assetInfo.TypeName.Get(), assetInfo.TypeName.Length()); - const auto assetType = Scripting::FindScriptingType(StringAnsiView(typeNameStd.Get(), assetInfo.TypeName.Length())); - if (IsAssetTypeIdInvalid(type, assetType)) - { - LOG(Error, "Different loaded asset type! Asset: '{0}'. Expected type: {1}", assetInfo.ToString(), type.ToString()); - return nullptr; - } - #endif // Find asset factory based in its type @@ -976,6 +967,18 @@ Asset* Content::load(const Guid& id, const ScriptingTypeHandle& type, AssetInfo& return nullptr; } +#if ASSETS_LOADING_EXTRA_VERIFICATION + + // Validate type + if (IsAssetTypeIdInvalid(type, result->GetTypeHandle()) && !result->Is(type)) + { + LOG(Error, "Different loaded asset type! Asset: '{0}'. Expected type: {1}", assetInfo.ToString(), type.ToString()); + result->DeleteObject(); + return nullptr; + } + +#endif + // Register asset { AssetsLocker.Lock(); diff --git a/Source/Engine/Content/JsonAsset.cpp b/Source/Engine/Content/JsonAsset.cpp index 3612a88b8..821c43f87 100644 --- a/Source/Engine/Content/JsonAsset.cpp +++ b/Source/Engine/Content/JsonAsset.cpp @@ -10,7 +10,10 @@ #include "Engine/Core/Log.h" #include "Engine/Serialization/JsonTools.h" #include "Engine/Content/Factories/JsonAssetFactory.h" +#include "Engine/Core/Cache.h" #include "Engine/Debug/Exceptions/JsonParseException.h" +#include "Engine/Scripting/Scripting.h" +#include "Engine/Utilities/StringConverter.h" JsonAssetBase::JsonAssetBase(const SpawnParams& params, const AssetInfo* info) : Asset(params, info) @@ -175,53 +178,55 @@ void JsonAssetBase::onRename(const StringView& newPath) REGISTER_JSON_ASSET(JsonAsset, "FlaxEngine.JsonAsset"); -//////////////////////////////////////////////////////////////////////////////////// - -#include "Engine/Physics/PhysicalMaterial.h" - -// Unmanaged json asset types that are serialized to JsonAsset and should be created by auto by asset. -// This allows to reuse JsonAsset without creating dedicated asset types. It has been designed for lightweight resources. - -typedef ISerializable* (*UnmanagedJsonInstanceCreator)(); - -template -ISerializable* Create() -{ - return New(); -} - -Dictionary UnmanagedTypes(32); - -void InitUnmanagedJsonTypes() -{ - ASSERT(UnmanagedTypes.IsEmpty()); - - // Key: managed class typename, Value: unmanaged instance spawner function - UnmanagedTypes[TEXT("FlaxEngine.PhysicalMaterial")] = &Create; -} - -//////////////////////////////////////////////////////////////////////////////////// - JsonAsset::JsonAsset(const SpawnParams& params, const AssetInfo* info) : JsonAssetBase(params, info) , Instance(nullptr) { - if (UnmanagedTypes.IsEmpty()) - InitUnmanagedJsonTypes(); } Asset::LoadResult JsonAsset::loadAsset() { // Base auto result = JsonAssetBase::loadAsset(); - if (result != LoadResult::Ok) + if (result != LoadResult::Ok || IsInternalType()) return result; - UnmanagedJsonInstanceCreator instanceSpawner = nullptr; - if (UnmanagedTypes.TryGet(DataTypeName, instanceSpawner)) + // Try to scripting type for this data + const StringAsANSI<> dataTypeNameAnsi(DataTypeName.Get(), DataTypeName.Length()); + const auto typeHandle = Scripting::FindScriptingType(StringAnsiView(dataTypeNameAnsi.Get(), DataTypeName.Length())); + if (typeHandle) { - Instance = instanceSpawner(); - Instance->Deserialize(*Data, nullptr); + auto& type = typeHandle.GetType(); + switch (type.Type) + { + case ScriptingTypes::Class: + { + // Ensure that object can deserialized + const ScriptingType::InterfaceImplementation* interfaces = type.GetInterface(&ISerializable::TypeInitializer); + if (!interfaces) + { + LOG(Warning, "Cannot deserialize {0} from Json Asset because it doesn't implement ISerializable interface.", type.ToString()); + break; + } + + // Allocate object + const auto instance = Allocator::Allocate(type.Size); + if (!instance) + return LoadResult::Failed; + Instance = instance; + InstanceType = typeHandle; + _dtor = type.Class.Dtor; + type.Class.Ctor(instance); + + // Deserialize object + auto modifier = Cache::ISerializeModifier.Get(); + modifier->EngineBuild = DataEngineBuild; + ((ISerializable*)((byte*)instance + interfaces->VTableOffset))->Deserialize(*Data, modifier.Value); + // TODO: delete object when containing BinaryModule gets unloaded + break; + } + default: ; + } } return result; @@ -232,5 +237,12 @@ void JsonAsset::unload(bool isReloading) // Base JsonAssetBase::unload(isReloading); - SAFE_DELETE(Instance); + if (Instance) + { + InstanceType = ScriptingTypeHandle(); + _dtor(Instance); + Allocator::Free(Instance); + Instance = nullptr; + _dtor = nullptr; + } } diff --git a/Source/Engine/Content/JsonAsset.h b/Source/Engine/Content/JsonAsset.h index c74c4a045..d865e8b96 100644 --- a/Source/Engine/Content/JsonAsset.h +++ b/Source/Engine/Content/JsonAsset.h @@ -78,13 +78,20 @@ protected: API_CLASS(NoSpawn) class FLAXENGINE_API JsonAsset : public JsonAssetBase { DECLARE_ASSET_HEADER(JsonAsset); +private: + ScriptingType::Dtor _dtor; public: + /// + /// The scripting type of the deserialized unmanaged object instance (e.g. PhysicalMaterial). + /// + ScriptingTypeHandle InstanceType; + /// /// The deserialized unmanaged object instance (e.g. PhysicalMaterial). /// - ISerializable* Instance; + void* Instance; protected: diff --git a/Source/Engine/Content/Loading/Tasks/LoadAssetDataTask.h b/Source/Engine/Content/Loading/Tasks/LoadAssetDataTask.h index 9d97a439f..5d552b996 100644 --- a/Source/Engine/Content/Loading/Tasks/LoadAssetDataTask.h +++ b/Source/Engine/Content/Loading/Tasks/LoadAssetDataTask.h @@ -82,7 +82,7 @@ protected: void OnEnd() override { _dataLock.Release(); - _asset.Unlink(); + _asset = nullptr; // Base ContentLoadTask::OnEnd(); diff --git a/Source/Engine/Content/Loading/Tasks/LoadAssetTask.h b/Source/Engine/Content/Loading/Tasks/LoadAssetTask.h index 7eed30399..7a7ea285f 100644 --- a/Source/Engine/Content/Loading/Tasks/LoadAssetTask.h +++ b/Source/Engine/Content/Loading/Tasks/LoadAssetTask.h @@ -68,7 +68,7 @@ protected: void OnEnd() override { - _asset.Unlink(); + _asset = nullptr; // Base ContentLoadTask::OnEnd(); diff --git a/Source/Engine/Content/WeakAssetReference.h b/Source/Engine/Content/WeakAssetReference.h index 9ed81b521..76f6b1fff 100644 --- a/Source/Engine/Content/WeakAssetReference.h +++ b/Source/Engine/Content/WeakAssetReference.h @@ -66,14 +66,6 @@ public: return _asset ? _asset->GetOrCreateManagedInstance() : nullptr; } - /// - /// Clears the asset reference. - /// - FORCE_INLINE void Unlink() - { - OnSet(nullptr); - } - /// /// Gets the asset property value as string. /// @@ -103,7 +95,8 @@ protected: { ASSERT(_asset == asset); Unload(); - Unlink(); + asset->OnUnloaded.Unbind(this); + asset = nullptr; } }; diff --git a/Source/Engine/Core/Collections/Dictionary.h b/Source/Engine/Core/Collections/Dictionary.h index e61cfc4d9..99bad0bef 100644 --- a/Source/Engine/Core/Collections/Dictionary.h +++ b/Source/Engine/Core/Collections/Dictionary.h @@ -13,7 +13,6 @@ template API_CLASS(InBuild) class Dictionary { friend Dictionary; - public: /// @@ -23,94 +22,73 @@ public: { friend Dictionary; - public: - enum State : byte { - Empty, - Deleted, - Occupied, + Empty = 0, + Deleted = 1, + Occupied = 2, }; public: - /// The key. KeyType Key; /// The value. ValueType Value; private: - State _state; - public: - - Bucket() - : _state(Empty) - { - } - - ~Bucket() - { - } - - public: - void Free() { + if (_state == Occupied) + { + Memory::DestructItem(&Key); + Memory::DestructItem(&Value); + } _state = Empty; } void Delete() { _state = Deleted; + Memory::DestructItem(&Key); + Memory::DestructItem(&Value); } template void Occupy(const KeyComparableType& key) { - Key = key; + Memory::ConstructItems(&Key, &key, 1); + Memory::ConstructItem(&Value); _state = Occupied; } template void Occupy(const KeyComparableType& key, const ValueType& value) { - Key = key; - Value = value; + Memory::ConstructItems(&Key, &key, 1); + Memory::ConstructItems(&Value, &value, 1); _state = Occupied; } template void Occupy(const KeyComparableType& key, ValueType&& value) { - Key = key; - Value = MoveTemp(value); + Memory::ConstructItems(&Key, &key, 1); + Memory::MoveItems(&Value, &value, 1); _state = Occupied; } - public: - FORCE_INLINE bool IsEmpty() const { return _state == Empty; } - FORCE_INLINE bool IsNotEmpty() const - { - return _state != Empty; - } - FORCE_INLINE bool IsDeleted() const { return _state == Deleted; } - FORCE_INLINE bool IsNotDeleted() const - { - return _state != Deleted; - } - FORCE_INLINE bool IsOccupied() const { return _state == Occupied; @@ -182,7 +160,6 @@ public: /// /// Gets the amount of the elements in the collection. /// - /// The amount of elements in the collection. FORCE_INLINE int32 Count() const { return _elementsCount; @@ -191,7 +168,6 @@ public: /// /// Gets the amount of the elements that can be contained by the collection. /// - /// The capacity of the collection. FORCE_INLINE int32 Capacity() const { return _tableSize; @@ -200,7 +176,6 @@ public: /// /// Returns true if collection is empty. /// - /// True if is empty, otherwise false. FORCE_INLINE bool IsEmpty() const { return _elementsCount == 0; @@ -209,7 +184,6 @@ public: /// /// Returns true if collection has one or more elements. /// - /// True if isn't empty, otherwise false. FORCE_INLINE bool HasItems() const { return _elementsCount != 0; @@ -218,12 +192,11 @@ public: public: /// - /// The dictionary collection iterator. + /// The Dictionary collection iterator. /// struct Iterator { friend Dictionary; - private: Dictionary& _collection; @@ -254,7 +227,6 @@ public: /// /// Checks if iterator is in the end of the collection. /// - /// True if is in the end, otherwise false. FORCE_INLINE bool IsEnd() const { return _index == _collection._tableSize; @@ -263,7 +235,6 @@ public: /// /// Checks if iterator is not in the end of the collection. /// - /// True if is not in the end, otherwise false. FORCE_INLINE bool IsNotEnd() const { return _index != _collection._tableSize; @@ -286,7 +257,7 @@ public: return _index >= 0 && _index < _collection._tableSize; } - FORCE_INLINE bool operator !() const + FORCE_INLINE bool operator!() const { return !(bool)*this; } @@ -316,7 +287,7 @@ public: return *this; } - Iterator operator++(int) + Iterator operator++(int) const { Iterator i = *this; ++i; @@ -335,7 +306,7 @@ public: return *this; } - Iterator operator--(int) + Iterator operator--(int) const { Iterator i = *this; --i; @@ -428,7 +399,6 @@ public: if (pos.ObjectIndex == -1) return false; - result = _table[pos.ObjectIndex].Value; return true; } @@ -449,7 +419,6 @@ public: if (pos.ObjectIndex == -1) return nullptr; - return &_table[pos.ObjectIndex].Value; } @@ -463,11 +432,8 @@ public: if (_table) { // Free all buckets - // Note: this will not clear allocated objects space! for (int32 i = 0; i < _tableSize; i++) - { _table[i].Free(); - } _elementsCount = _deletedCount = 0; } } @@ -492,16 +458,14 @@ public: /// Enables preserving collection contents during resizing. void SetCapacity(int32 capacity, bool preserveContents = true) { - // Validate input - ASSERT(capacity >= 0); - // Check if capacity won't change if (capacity == Capacity()) return; // Cache previous state + ASSERT(capacity >= 0); Bucket* oldTable = _table; - int32 oldTableSize = _tableSize; + const int32 oldTableSize = _tableSize; // Clear elements counters const int32 oldElementsCount = _elementsCount; @@ -523,10 +487,10 @@ public: } // Allocate new table - _table = NewArray(capacity); + _table = (Bucket*)Allocator::Allocate(capacity * sizeof(Bucket)); _tableSize = capacity; - - // Check if preserve content + for (int32 i = 0; i < capacity; i++) + _table[i]._state = Bucket::Empty; if (oldElementsCount != 0 && preserveContents) { // Try to preserve all pairs in the collection @@ -548,7 +512,9 @@ public: // Delete old table if (oldTable) { - DeleteArray(oldTable, oldTableSize); + for (int32 i = 0; i < oldTableSize; i++) + oldTable[i].Free(); + Allocator::Free(oldTable); } } @@ -571,11 +537,10 @@ public: if (Capacity() >= minCapacity) return; - // TODO: improve this, better collection growing and shrinking on remove - int32 num = Capacity() == 0 ? DICTIONARY_DEFAULT_CAPACITY : Capacity() * 2; - if (num < minCapacity) - num = minCapacity; - SetCapacity(num); + int32 capacity = Capacity() == 0 ? DICTIONARY_DEFAULT_CAPACITY : Capacity() * 2; + if (capacity < minCapacity) + capacity = minCapacity; + SetCapacity(capacity); } /// @@ -676,7 +641,6 @@ public: _deletedCount++; return true; } - return false; } @@ -733,9 +697,7 @@ public: for (int32 i = 0; i < _tableSize; i++) { if (_table[i].IsOccupied() && _table[i].Key == key) - { return Iterator(*this, i); - } } } return End(); @@ -754,7 +716,6 @@ public: FindPositionResult pos; FindPosition(key, pos); - return pos.ObjectIndex != -1; } @@ -809,12 +770,9 @@ public: void Clone(const Dictionary& other) { Clear(); - SetCapacity(other.Capacity(), false); - for (auto i = other.Begin(); i != other.End(); ++i) Add(i); - ASSERT(Count() == other.Count()); ASSERT(Capacity() == other.Capacity()); } @@ -909,14 +867,13 @@ protected: void FindPosition(const KeyComparableType& key, FindPositionResult& result) const { ASSERT(_table); - const int32 tableSizeMinusOne = _tableSize - 1; int32 bucketIndex = GetHash(key) & tableSizeMinusOne; int32 insertPos = -1; - int32 numChecks = 0; + int32 checksCount = 0; result.FreeSlotIndex = -1; - while (numChecks < _tableSize) + while (checksCount < _tableSize) { // Empty bucket if (_table[bucketIndex].IsEmpty()) @@ -941,8 +898,8 @@ protected: return; } - numChecks++; - bucketIndex = (bucketIndex + DICTIONARY_PROB_FUNC(_tableSize, numChecks)) & tableSizeMinusOne; + checksCount++; + bucketIndex = (bucketIndex + DICTIONARY_PROB_FUNC(_tableSize, checksCount)) & tableSizeMinusOne; } result.ObjectIndex = -1; diff --git a/Source/Engine/Core/Config.h b/Source/Engine/Core/Config.h index 0a9a10b3d..0628374f2 100644 --- a/Source/Engine/Core/Config.h +++ b/Source/Engine/Core/Config.h @@ -44,6 +44,7 @@ // Scripting API defines (see C++ scripting documentation for more info) #define API_ENUM(...) #define API_CLASS(...) +#define API_INTERFACE(...) #define API_STRUCT(...) #define API_FUNCTION(...) #define API_PROPERTY(...) @@ -52,3 +53,4 @@ #define API_PARAM(...) #define API_INJECT_CPP_CODE(...) #define API_AUTO_SERIALIZATION(...) public: void Serialize(SerializeStream& stream, const void* otherObj) override; void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; +#define DECLARE_SCRIPTING_TYPE_MINIMAL(type) public: friend class type##Internal; static struct ScriptingTypeInitializer TypeInitializer; diff --git a/Source/Engine/Core/Config/BuildSettings.h b/Source/Engine/Core/Config/BuildSettings.h index d0ae40e0f..529171639 100644 --- a/Source/Engine/Core/Config/BuildSettings.h +++ b/Source/Engine/Core/Config/BuildSettings.h @@ -10,72 +10,73 @@ /// /// The game building rendering settings. /// -/// -class BuildSettings : public Settings +API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API BuildSettings : public SettingsBase { +DECLARE_SCRIPTING_TYPE_MINIMAL(BuildSettings); public: /// /// The maximum amount of assets to include into a single assets package. Asset packages will split into several packages if need to. /// - int32 MaxAssetsPerPackage = 1024; + API_FIELD(Attributes="EditorOrder(10), DefaultValue(4096), Limit(1, 32, ushort.MaxValue), EditorDisplay(\"General\", \"Max assets per package\")") + int32 MaxAssetsPerPackage = 4096; /// /// The maximum size of the single assets package (in megabytes). Asset packages will split into several packages if need to. /// + API_FIELD(Attributes="EditorOrder(20), DefaultValue(1024), Limit(1, 16, ushort.MaxValue), EditorDisplay(\"General\", \"Max package size (in MB)\")") int32 MaxPackageSizeMB = 1024; /// /// The game content cooking keycode. Use the same value for a game and DLC packages to support loading them by the build game. Use 0 to randomize it during building. /// + API_FIELD(Attributes="EditorOrder(30), DefaultValue(0), Limit(1, 16, ushort.MaxValue), EditorDisplay(\"General\")") int32 ContentKey = 0; /// /// If checked, the builds produced by the Game Cooker will be treated as for final game distribution (eg. for game store upload). Builds done this way cannot be tested on console devkits (eg. Xbox One, Xbox Scarlett). /// + API_FIELD(Attributes="EditorOrder(40), DefaultValue(false), EditorDisplay(\"General\")") bool ForDistribution = false; /// /// If checked, the output build files won't be packaged for the destination platform. Useful when debugging build from local PC. /// + API_FIELD(Attributes="EditorOrder(50), DefaultValue(false), EditorDisplay(\"General\")") bool SkipPackaging = false; /// /// The list of additional assets to include into build (into root assets set). /// + API_FIELD(Attributes="EditorOrder(1000), EditorDisplay(\"Additional Data\")") Array> AdditionalAssets; /// /// The list of additional folders with assets to include into build (into root assets set). Paths relative to the project directory (or absolute). /// + API_FIELD(Attributes="EditorOrder(1010), EditorDisplay(\"Additional Data\")") Array AdditionalAssetFolders; /// /// Disables shaders compiler optimizations in cooked game. Can be used to debug shaders on a target platform or to speed up the shaders compilation time. /// + API_FIELD(Attributes="EditorOrder(2000), DefaultValue(false), EditorDisplay(\"Content\", \"Shaders No Optimize\")") bool ShadersNoOptimize = false; /// /// Enables shader debug data generation for shaders in cooked game (depends on the target platform rendering backend). /// + API_FIELD(Attributes="EditorOrder(2010), DefaultValue(false), EditorDisplay(\"Content\")") bool ShadersGenerateDebugData = false; public: - // [Settings] - void RestoreDefault() final override - { - MaxAssetsPerPackage = 1024; - MaxPackageSizeMB = 1024; - ContentKey = 0; - ForDistribution = false; - SkipPackaging = false; - AdditionalAssets.Clear(); - AdditionalAssetFolders.Clear(); - ShadersNoOptimize = false; - ShadersGenerateDebugData = false; - } + /// + /// Gets the instance of the settings asset (default value if missing). Object returned by this method is always loaded with valid data to use. + /// + static BuildSettings* Get(); + // [SettingsBase] void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override { DESERIALIZE(MaxAssetsPerPackage); diff --git a/Source/Engine/Core/Config/GameSettings.cpp b/Source/Engine/Core/Config/GameSettings.cpp index 38e036de6..5ea356d61 100644 --- a/Source/Engine/Core/Config/GameSettings.cpp +++ b/Source/Engine/Core/Config/GameSettings.cpp @@ -2,6 +2,7 @@ #include "GameSettings.h" #include "Engine/Serialization/JsonTools.h" +#include "Engine/Scripting/ScriptingType.h" #include "Engine/Physics/PhysicsSettings.h" #include "Engine/Core/Log.h" #include "LayersTagsSettings.h" @@ -17,30 +18,6 @@ #include "Engine/Content/AssetReference.h" #include "Engine/Engine/EngineService.h" #include "Engine/Engine/Globals.h" -#include "Engine/Engine/Time.h" - -String GameSettings::ProductName; -String GameSettings::CompanyName; -String GameSettings::CopyrightNotice; -Guid GameSettings::Icon; -Guid GameSettings::FirstScene; -bool GameSettings::NoSplashScreen = false; -Guid GameSettings::SplashScreen; -Dictionary GameSettings::CustomSettings; - -Array SettingsBase::Containers(32); - -#if USE_EDITOR -extern void LoadPlatformSettingsEditor(ISerializable::DeserializeStream& data); -#endif - -void TimeSettings::Apply() -{ - Time::UpdateFPS = UpdateFPS; - Time::PhysicsFPS = PhysicsFPS; - Time::DrawFPS = DrawFPS; - Time::TimeScale = TimeScale; -} class GameSettingsService : public EngineService { @@ -49,9 +26,6 @@ public: GameSettingsService() : EngineService(TEXT("GameSettings"), -70) { - GameSettings::Icon = Guid::Empty; - GameSettings::FirstScene = Guid::Empty; - GameSettings::SplashScreen = Guid::Empty; } bool Init() override @@ -60,62 +34,141 @@ public: } }; +IMPLEMENT_SETTINGS_GETTER(BuildSettings, GameCooking); +IMPLEMENT_SETTINGS_GETTER(GraphicsSettings, Graphics); +IMPLEMENT_SETTINGS_GETTER(LayersAndTagsSettings, LayersAndTags); +IMPLEMENT_SETTINGS_GETTER(TimeSettings, Time); +IMPLEMENT_SETTINGS_GETTER(AudioSettings, Audio); +IMPLEMENT_SETTINGS_GETTER(PhysicsSettings, Physics); +IMPLEMENT_SETTINGS_GETTER(InputSettings, Input); + +#if !USE_EDITOR +#if PLATFORM_WINDOWS +IMPLEMENT_SETTINGS_GETTER(WindowsPlatformSettings, WindowsPlatform); +#elif PLATFORM_UWP || PLATFORM_XBOX_ONE +IMPLEMENT_SETTINGS_GETTER(UWPPlatformSettings, UWPPlatform); +#elif PLATFORM_LINUX +IMPLEMENT_SETTINGS_GETTER(LinuxPlatformSettings, LinuxPlatform); +#elif PLATFORM_PS4 +IMPLEMENT_SETTINGS_GETTER(PS4PlatformSettings, PS4Platform); +#elif PLATFORM_XBOX_SCARLETT +IMPLEMENT_SETTINGS_GETTER(XboxScarlettPlatformSettings, XboxScarlettPlatform); +#elif PLATFORM_ANDROID +IMPLEMENT_SETTINGS_GETTER(AndroidPlatformSettings, AndroidPlatform); +#else +#error Unknown platform +#endif +#endif + GameSettingsService GameSettingsServiceInstance; +AssetReference GameSettingsAsset; + +GameSettings* GameSettings::Get() +{ + if (!GameSettingsAsset) + { + // Load root game settings asset. + // It may be missing in editor during dev but must be ready in the build game. + const auto assetPath = Globals::ProjectContentFolder / TEXT("GameSettings.json"); + GameSettingsAsset = Content::LoadAsync(assetPath); + if (GameSettingsAsset == nullptr) + { + LOG(Error, "Missing game settings asset."); + return nullptr; + } + if (GameSettingsAsset->WaitForLoaded()) + { + return nullptr; + } + if (GameSettingsAsset->InstanceType != GameSettings::TypeInitializer) + { + LOG(Error, "Invalid game settings asset data type."); + return nullptr; + } + } + auto asset = GameSettingsAsset.Get(); + if (asset && asset->WaitForLoaded()) + asset = nullptr; + return asset ? (GameSettings*)asset->Instance : nullptr; +} bool GameSettings::Load() { + // Load main settings asset + auto settings = Get(); + if (!settings) + { + return true; + } + + // Preload all settings assets +#define PRELOAD_SETTINGS(type) \ + { \ + if (settings->type) \ + { \ + Content::LoadAsync(settings->type); \ + } \ + else \ + { \ + LOG(Warning, "Missing {0} settings", TEXT(#type)); \ + } \ + } + PRELOAD_SETTINGS(Time); + PRELOAD_SETTINGS(Audio); + PRELOAD_SETTINGS(LayersAndTags); + PRELOAD_SETTINGS(Physics); + PRELOAD_SETTINGS(Input); + PRELOAD_SETTINGS(Graphics); + PRELOAD_SETTINGS(Navigation); + PRELOAD_SETTINGS(GameCooking); +#undef PRELOAD_SETTINGS + + // Apply the game settings to the engine + settings->Apply(); + + return false; +} + +void GameSettings::Apply() +{ + // TODO: impl this +#define APPLY_SETTINGS(type) \ + { \ + type* obj = type::Get(); \ + if (obj) \ + { \ + obj->Apply(); \ + } \ + else \ + { \ + LOG(Warning, "Missing {0} settings", TEXT(#type)); \ + } \ + } + APPLY_SETTINGS(TimeSettings); + APPLY_SETTINGS(AudioSettings); + APPLY_SETTINGS(LayersAndTagsSettings); + APPLY_SETTINGS(PhysicsSettings); + APPLY_SETTINGS(InputSettings); + APPLY_SETTINGS(GraphicsSettings); + APPLY_SETTINGS(NavigationSettings); + APPLY_SETTINGS(BuildSettings); + APPLY_SETTINGS(PlatformSettings); +#undef APPLY_SETTINGS +} + +void GameSettings::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) +{ + // Load properties + ProductName = JsonTools::GetString(stream, "ProductName"); + CompanyName = JsonTools::GetString(stream, "CompanyName"); + CopyrightNotice = JsonTools::GetString(stream, "CopyrightNotice"); + Icon = JsonTools::GetGuid(stream, "Icon"); + FirstScene = JsonTools::GetGuid(stream, "FirstScene"); + NoSplashScreen = JsonTools::GetBool(stream, "NoSplashScreen", NoSplashScreen); + SplashScreen = JsonTools::GetGuid(stream, "SplashScreen"); CustomSettings.Clear(); - -#if USE_EDITOR -#define END_POINT(msg) LOG(Warning, msg " Using default values."); return false -#else -#define END_POINT(msg) LOG(Fatal, msg); return true -#endif - -#define LOAD_SETTINGS(nodeName, settingsType) \ - { \ - Guid id = JsonTools::GetGuid(data, nodeName); \ - if (id.IsValid()) \ - { \ - AssetReference subAsset = Content::LoadAsync(id); \ - if (subAsset && !subAsset->WaitForLoaded()) \ - { \ - settingsType::Instance()->Deserialize(*subAsset->Data, nullptr); \ - settingsType::Instance()->Apply(); \ - } \ - else \ - { LOG(Warning, "Cannot load " nodeName " settings"); } \ - } \ - else \ - { LOG(Warning, "Missing " nodeName " settings"); } \ - } - - // Load root game settings asset. - // It may be missing in editor during dev but must be ready in the build game. - const auto assetPath = Globals::ProjectContentFolder / TEXT("GameSettings.json"); - AssetReference asset = Content::LoadAsync(assetPath); - if (asset == nullptr) - { - END_POINT("Missing game settings asset."); - } - if (asset->WaitForLoaded() - || asset->DataTypeName != TEXT("FlaxEditor.Content.Settings.GameSettings") - || asset->Data == nullptr) - { - END_POINT("Cannot load game settings asset."); - } - auto& data = *asset->Data; - - // Load settings - ProductName = JsonTools::GetString(data, "ProductName"); - CompanyName = JsonTools::GetString(data, "CompanyName"); - CopyrightNotice = JsonTools::GetString(data, "CopyrightNotice"); - Icon = JsonTools::GetGuid(data, "Icon"); - FirstScene = JsonTools::GetGuid(data, "FirstScene"); - NoSplashScreen = JsonTools::GetBool(data, "NoSplashScreen", NoSplashScreen); - SplashScreen = JsonTools::GetGuid(data, "SplashScreen"); - const auto customSettings = data.FindMember("CustomSettings"); - if (customSettings != data.MemberEnd()) + const auto customSettings = stream.FindMember("CustomSettings"); + if (customSettings != stream.MemberEnd()) { auto& items = customSettings->value; for (auto it = items.MemberBegin(); it != items.MemberEnd(); ++it) @@ -129,39 +182,21 @@ bool GameSettings::Load() } } - // Load child settings - LOAD_SETTINGS("Time", TimeSettings); - LOAD_SETTINGS("Physics", PhysicsSettings); - LOAD_SETTINGS("LayersAndTags", LayersAndTagsSettings); - LOAD_SETTINGS("Graphics", GraphicsSettings); - LOAD_SETTINGS("GameCooking", BuildSettings); - LOAD_SETTINGS("Input", InputSettings); - LOAD_SETTINGS("Audio", AudioSettings); - LOAD_SETTINGS("Navigation", NavigationSettings); + // Settings containers + DESERIALIZE(Time); + DESERIALIZE(Audio); + DESERIALIZE(LayersAndTags); + DESERIALIZE(Physics); + DESERIALIZE(Input); + DESERIALIZE(Graphics); + DESERIALIZE(Navigation); + DESERIALIZE(GameCooking); - // Load platform settings -#if PLATFORM_WINDOWS - LOAD_SETTINGS("WindowsPlatform", WindowsPlatformSettings); -#endif -#if PLATFORM_UWP - LOAD_SETTINGS("UWPPlatform", UWPPlatformSettings); -#endif -#if PLATFORM_LINUX - LOAD_SETTINGS("LinuxPlatform", LinuxPlatformSettings); -#endif -#if PLATFORM_PS4 - LOAD_SETTINGS("PS4Platform", PS4PlatformSettings); -#endif -#if PLATFORM_XBOX_SCARLETT - LOAD_SETTINGS("XboxScarlettPlatform", XboxScarlettPlatformSettings); -#endif -#if PLATFORM_ANDROID - LOAD_SETTINGS("AndroidPlatform", AndroidPlatformSettings); -#endif -#if USE_EDITOR - LoadPlatformSettingsEditor(data); -#endif - - return false; -#undef END_POINT + // Per-platform settings containers + DESERIALIZE(WindowsPlatform); + DESERIALIZE(UWPPlatform); + DESERIALIZE(LinuxPlatform); + DESERIALIZE(PS4Platform); + DESERIALIZE(XboxScarlettPlatform); + DESERIALIZE(AndroidPlatform); } diff --git a/Source/Engine/Core/Config/GameSettings.h b/Source/Engine/Core/Config/GameSettings.h index 6870d8c22..16e1fe7b5 100644 --- a/Source/Engine/Core/Config/GameSettings.h +++ b/Source/Engine/Core/Config/GameSettings.h @@ -2,60 +2,96 @@ #pragma once +#include "Settings.h" #include "Engine/Core/Types/Guid.h" #include "Engine/Core/Types/String.h" #include "Engine/Core/Collections/Dictionary.h" /// -/// Main engine configuration service. Loads and applies game configuration. +/// The main game engine configuration service. Loads and applies game configuration. /// -class GameSettings +API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API GameSettings : public SettingsBase { +DECLARE_SCRIPTING_TYPE_MINIMAL(GameSettings); public: /// /// The product full name. /// - static String ProductName; + API_FIELD(Attributes="EditorOrder(0), EditorDisplay(\"General\")") + String ProductName; /// /// The company full name. /// - static String CompanyName; + API_FIELD(Attributes="EditorOrder(10), EditorDisplay(\"General\")") + String CompanyName; /// /// The copyright note used for content signing (eg. source code header). /// - static String CopyrightNotice; + API_FIELD(Attributes="EditorOrder(15), EditorDisplay(\"General\")") + String CopyrightNotice; /// /// The default application icon. /// - static Guid Icon; + Guid Icon = Guid::Empty; /// /// Reference to the first scene to load on a game startup. /// - static Guid FirstScene; + Guid FirstScene = Guid::Empty; /// /// True if skip showing splash screen image on the game startup. /// - static bool NoSplashScreen; + bool NoSplashScreen = false; /// /// Reference to the splash screen image to show on a game startup. /// - static Guid SplashScreen; + Guid SplashScreen = Guid::Empty; /// /// The custom settings to use with a game. Can be specified by the user to define game-specific options and be used by the external plugins (used as key-value pair). /// - static Dictionary CustomSettings; + Dictionary CustomSettings; + +public: + + // Settings containers + Guid Time; + Guid Audio; + Guid LayersAndTags; + Guid Physics; + Guid Input; + Guid Graphics; + Guid Navigation; + Guid GameCooking; + + // Per-platform settings containers + Guid WindowsPlatform; + Guid UWPPlatform; + Guid LinuxPlatform; + Guid PS4Platform; + Guid XboxScarlettPlatform; + Guid AndroidPlatform; + +public: + + /// + /// Gets the instance of the game settings asset (null if missing). Object returned by this method is always loaded with valid data to use. + /// + static GameSettings* Get(); /// /// Loads the game settings (including other settings such as Physics, Input, etc.). /// /// True if failed, otherwise false. static bool Load(); + + // [SettingsBase] + void Apply() override; + void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override; }; diff --git a/Source/Engine/Core/Config/GraphicsSettings.h b/Source/Engine/Core/Config/GraphicsSettings.h index b13972cc7..17c342e89 100644 --- a/Source/Engine/Core/Config/GraphicsSettings.h +++ b/Source/Engine/Core/Config/GraphicsSettings.h @@ -9,68 +9,68 @@ /// /// Graphics rendering settings. /// -/// -class GraphicsSettings : public Settings +API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API GraphicsSettings : public SettingsBase { +DECLARE_SCRIPTING_TYPE_MINIMAL(GraphicsSettings); public: /// /// Enables rendering synchronization with the refresh rate of the display device to avoid "tearing" artifacts. /// + API_FIELD(Attributes="EditorOrder(20), DefaultValue(false), EditorDisplay(\"General\", \"Use V-Sync\")") bool UseVSync = false; /// /// Anti Aliasing quality setting. /// + API_FIELD(Attributes="EditorOrder(1000), DefaultValue(Quality.Medium), EditorDisplay(\"Quality\", \"AA Quality\")") Quality AAQuality = Quality::Medium; /// /// Screen Space Reflections quality setting. /// + API_FIELD(Attributes="EditorOrder(1100), DefaultValue(Quality.Medium), EditorDisplay(\"Quality\", \"SSR Quality\")") Quality SSRQuality = Quality::Medium; /// /// Screen Space Ambient Occlusion quality setting. /// + API_FIELD(Attributes="EditorOrder(1200), DefaultValue(Quality.Medium), EditorDisplay(\"Quality\", \"SSAO Quality\")") Quality SSAOQuality = Quality::Medium; /// /// Volumetric Fog quality setting. /// + API_FIELD(Attributes="EditorOrder(1250), DefaultValue(Quality.High), EditorDisplay(\"Quality\")") Quality VolumetricFogQuality = Quality::High; /// /// The shadows quality. /// + API_FIELD(Attributes="EditorOrder(1300), DefaultValue(Quality.Medium), EditorDisplay(\"Quality\")") Quality ShadowsQuality = Quality::Medium; /// /// The shadow maps quality (textures resolution). /// + API_FIELD(Attributes="EditorOrder(1310), DefaultValue(Quality.Medium), EditorDisplay(\"Quality\")") Quality ShadowMapsQuality = Quality::Medium; /// /// Enables cascades splits blending for directional light shadows. /// + API_FIELD(Attributes="EditorOrder(1320), DefaultValue(false), EditorDisplay(\"Quality\", \"Allow CSM Blending\")") bool AllowCSMBlending = false; public: - // [Settings] + /// + /// Gets the instance of the settings asset (default value if missing). Object returned by this method is always loaded with valid data to use. + /// + static GraphicsSettings* Get(); + + // [SettingsBase] void Apply() override; - - void RestoreDefault() final override - { - UseVSync = false; - AAQuality = Quality::Medium; - SSRQuality = Quality::Medium; - SSAOQuality = Quality::Medium; - VolumetricFogQuality = Quality::High; - ShadowsQuality = Quality::Medium; - ShadowMapsQuality = Quality::Medium; - AllowCSMBlending = false; - } - void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override { DESERIALIZE(UseVSync); diff --git a/Source/Engine/Core/Config/LayersTagsSettings.h b/Source/Engine/Core/Config/LayersTagsSettings.h index c01f915aa..945a24fbf 100644 --- a/Source/Engine/Core/Config/LayersTagsSettings.h +++ b/Source/Engine/Core/Config/LayersTagsSettings.h @@ -8,9 +8,9 @@ /// /// Layers and objects tags settings. /// -/// -class LayersAndTagsSettings : public Settings +API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API LayersAndTagsSettings : public SettingsBase { +DECLARE_SCRIPTING_TYPE_MINIMAL(LayersAndTagsSettings); public: /// @@ -26,43 +26,11 @@ public: public: /// - /// Gets or adds the tag (returns the tag index). + /// Gets the instance of the settings asset (default value if missing). Object returned by this method is always loaded with valid data to use. /// - /// The tag. - /// The tag index. - int32 GetOrAddTag(const String& tag) - { - int32 index = Tags.Find(tag); - if (index == INVALID_INDEX) - { - index = Tags.Count(); - Tags.Add(tag); - } - return index; - } + static LayersAndTagsSettings* Get(); - /// - /// Gets the amount of non empty layer names (from the beginning, trims the last ones). - /// - /// The layers count. - int32 GetNonEmptyLayerNamesCount() const - { - int32 result = 31; - while (result >= 0 && Layers[result].IsEmpty()) - result--; - return result + 1; - } - -public: - - // [Settings] - void RestoreDefault() override - { - Tags.Clear(); - for (int32 i = 0; i < 32; i++) - Layers[i].Clear(); - } - + // [SettingsBase] void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override { const auto tags = stream.FindMember("Tags"); diff --git a/Source/Engine/Core/Config/Settings.h b/Source/Engine/Core/Config/Settings.h index e21e558fc..2822f4a6a 100644 --- a/Source/Engine/Core/Config/Settings.h +++ b/Source/Engine/Core/Config/Settings.h @@ -2,81 +2,46 @@ #pragma once -#include "Engine/Core/Singleton.h" -#include "Engine/Core/Collections/Array.h" #include "Engine/Serialization/ISerializable.h" /// -/// Base class for all global settings containers for the engine. Helps to apply, store and expose properties to c#. +/// Base class for all global settings containers for the engine. Helps to apply, store and expose properties to engine/game. /// -class FLAXENGINE_API SettingsBase +API_CLASS(Abstract) class FLAXENGINE_API SettingsBase : public ISerializable { +DECLARE_SCRIPTING_TYPE_MINIMAL(SettingsBase); public: /// - /// The settings containers. - /// - static Array Containers; - - /// - /// Restores the default settings for all the registered containers. - /// - static void RestoreDefaultAll() - { - for (int32 i = 0; i < Containers.Count(); i++) - Containers[i]->RestoreDefault(); - } - -private: - - // Disable copy/move - SettingsBase(const SettingsBase&) = delete; - SettingsBase& operator=(const SettingsBase&) = delete; - -protected: - - SettingsBase() - { - Containers.Add(this); - } - -public: - - virtual ~SettingsBase() = default; - -public: - - typedef ISerializable::DeserializeStream DeserializeStream; - - /// - /// Applies the settings to the target services. + /// Applies the settings to the target system. /// virtual void Apply() { } - /// - /// Restores the default settings. - /// - virtual void RestoreDefault() = 0; +public: - /// - /// Deserializes the settings container. - /// - /// The input data stream. - /// The deserialization modifier object. Always valid. - virtual void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) = 0; -}; - -/// -/// Base class for all global settings containers for the engine. Helps to apply, store and expose properties to c#. -/// -template -class Settings : public SettingsBase, public Singleton -{ -protected: - - Settings() + // [ISerializable] + void Serialize(SerializeStream& stream, const void* otherObj) override { + // Not supported (Editor C# edits settings data) } }; + +// Helper utility define for settings getter implementation code +#define IMPLEMENT_SETTINGS_GETTER(type, field) \ + type* type::Get() \ + { \ + static type DefaultInstance; \ + type* result = &DefaultInstance; \ + const auto gameSettings = GameSettings::Get(); \ + if (gameSettings) \ + { \ + const auto asset = Content::Load(gameSettings->field); \ + if (asset && asset->Instance && asset->InstanceType == type::TypeInitializer) \ + { \ + result = static_cast(asset->Instance); \ + } \ + } \ + return result; \ + } diff --git a/Source/Engine/Core/Config/TimeSettings.h b/Source/Engine/Core/Config/TimeSettings.h index 8ac7ae741..7e383380f 100644 --- a/Source/Engine/Core/Config/TimeSettings.h +++ b/Source/Engine/Core/Config/TimeSettings.h @@ -3,61 +3,53 @@ #pragma once #include "Engine/Core/Config/Settings.h" -#include "Engine/Serialization/Serialization.h" /// /// Time and game simulation settings container. /// -/// -class TimeSettings : public Settings +API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API TimeSettings : public SettingsBase { +DECLARE_SCRIPTING_TYPE_MINIMAL(TimeSettings); public: /// /// The target amount of the game logic updates per second (script updates frequency). /// + API_FIELD(Attributes="EditorOrder(1), DefaultValue(30.0f), Limit(0, 1000), EditorDisplay(\"General\", \"Update FPS\")") float UpdateFPS = 30.0f; /// /// The target amount of the physics simulation updates per second (also fixed updates frequency). /// + API_FIELD(Attributes="EditorOrder(2), DefaultValue(60.0f), Limit(0, 1000), EditorDisplay(\"General\", \"Physics FPS\")") float PhysicsFPS = 60.0f; /// /// The target amount of the frames rendered per second (actual game FPS). /// + API_FIELD(Attributes="EditorOrder(3), DefaultValue(60.0f), Limit(0, 1000), EditorDisplay(\"General\", \"Draw FPS\")") float DrawFPS = 60.0f; /// /// The game time scale factor. Default is 1. /// + API_FIELD(Attributes="EditorOrder(10), DefaultValue(1.0f), Limit(0, 1000.0f, 0.1f), EditorDisplay(\"General\")") float TimeScale = 1.0f; /// /// The maximum allowed delta time (in seconds) for the game logic update step. /// - float MaxUpdateDeltaTime = (1.0f / 10.0f); + API_FIELD(Attributes="EditorOrder(20), DefaultValue(0.1f), Limit(0.1f, 1000.0f, 0.01f), EditorDisplay(\"General\")") + float MaxUpdateDeltaTime = 0.1f; public: - // [Settings] + /// + /// Gets the instance of the settings asset (default value if missing). Object returned by this method is always loaded with valid data to use. + /// + static TimeSettings* Get(); + + // [SettingsBase] void Apply() override; - - void RestoreDefault() override - { - UpdateFPS = 30.0f; - PhysicsFPS = 60.0f; - DrawFPS = 60.0f; - TimeScale = 1.0f; - MaxUpdateDeltaTime = 1.0f / 10.0f; - } - - void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override - { - DESERIALIZE(UpdateFPS); - DESERIALIZE(PhysicsFPS); - DESERIALIZE(DrawFPS); - DESERIALIZE(TimeScale); - DESERIALIZE(MaxUpdateDeltaTime); - } + void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override; }; diff --git a/Source/Engine/Core/ISerializable.h b/Source/Engine/Core/ISerializable.h new file mode 100644 index 000000000..cfc5e1976 --- /dev/null +++ b/Source/Engine/Core/ISerializable.h @@ -0,0 +1,60 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Engine/Serialization/JsonFwd.h" +#include "Engine/Core/Compiler.h" +#include "Engine/Core/Config.h" + +class JsonWriter; +class ISerializeModifier; + +/// +/// Interface for objects that can be serialized/deserialized to/from JSON format. +/// +API_INTERFACE() class FLAXENGINE_API ISerializable +{ +DECLARE_SCRIPTING_TYPE_MINIMAL(ISerializable); +public: + + typedef rapidjson_flax::Document SerializeDocument; + + /// + /// Serialization output stream + /// + typedef rapidjson_flax::Value DeserializeStream; + + /// + /// Serialization input stream + /// + typedef JsonWriter SerializeStream; + +public: + + /// + /// Finalizes an instance of the class. + /// + virtual ~ISerializable() = default; + + /// + /// Serialize object to the output stream compared to the values of the other object instance (eg. default class object). If other object is null then serialize all properties. + /// + /// The output stream. + /// The instance of the object to compare with and serialize only the modified properties. If null, then serialize all properties. + virtual void Serialize(SerializeStream& stream, const void* otherObj) = 0; + + /// + /// Deserialize object from the input stream + /// + /// The input stream. + /// The deserialization modifier object. Always valid. + virtual void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) = 0; + + /// + /// Deserialize object from the input stream child member. Won't deserialize it if member is missing. + /// + /// The input stream. + /// The input stream member to lookup. + /// The deserialization modifier object. Always valid. + void DeserializeIfExists(DeserializeStream& stream, const char* memberName, ISerializeModifier* modifier); +}; diff --git a/Source/Engine/Core/Math/Color.cpp b/Source/Engine/Core/Math/Color.cpp index 168db71a5..5a00a52e5 100644 --- a/Source/Engine/Core/Math/Color.cpp +++ b/Source/Engine/Core/Math/Color.cpp @@ -137,6 +137,26 @@ String Color::ToHexString() const return String(result, 6); } +bool Color::IsTransparent() const +{ + return Math::IsZero(R + G + B + A); +} + +bool Color::HasOpacity() const +{ + return !Math::IsOne(A); +} + +bool Color::NearEqual(const Color& a, const Color& b) +{ + return Math::NearEqual(a.R, b.R) && Math::NearEqual(a.G, b.G) && Math::NearEqual(a.B, b.B) && Math::NearEqual(a.A, b.A); +} + +bool Color::NearEqual(const Color& a, const Color& b, float epsilon) +{ + return Math::NearEqual(a.R, b.R, epsilon) && Math::NearEqual(a.G, b.G, epsilon) && Math::NearEqual(a.B, b.B, epsilon) && Math::NearEqual(a.A, b.A, epsilon); +} + Vector3 Color::ToVector3() const { return Vector3(R, G, B); @@ -158,6 +178,21 @@ Vector3 Color::ToHSV() const return Vector3(hue, saturation, value); } +void Color::Lerp(const Color& start, const Color& end, float amount, Color& result) +{ + result.R = Math::Lerp(start.R, end.R, amount); + result.G = Math::Lerp(start.G, end.G, amount); + result.B = Math::Lerp(start.B, end.B, amount); + result.A = Math::Lerp(start.A, end.A, amount); +} + +Color Color::Lerp(const Color& start, const Color& end, float amount) +{ + Color result; + Lerp(start, end, amount, result); + return result; +} + Color Color::LinearToSrgb(const Color& linear) { #define LINEAR_TO_SRGB(value) value < 0.00313067f ? value * 12.92f : Math::Pow(value, (1.0f / 2.4f)) * 1.055f - 0.055f diff --git a/Source/Engine/Core/Math/Color.cs b/Source/Engine/Core/Math/Color.cs index a865bde15..96376092e 100644 --- a/Source/Engine/Core/Math/Color.cs +++ b/Source/Engine/Core/Math/Color.cs @@ -716,7 +716,7 @@ namespace FlaxEngine } /// - /// Converts the color to HSV color space (returned as vector). + /// Gets Hue[0-360], Saturation[0-1] and Value[0-1] from RGB color. /// /// The HSV color. public Vector3 ToHSV() @@ -743,51 +743,6 @@ namespace FlaxEngine return new Vector3(hue, saturation, value); } - /// - /// Convert color from the RGB color space to HSV color space. - /// - /// Color of the RGB. - /// The output Hue. - /// The output Saturation. - /// The output Value. - public static void RGBToHSV(Color rgbColor, out float h, out float s, out float v) - { - if ((rgbColor.B > rgbColor.G) && (rgbColor.B > rgbColor.R)) - RGBToHSVHelper(4f, rgbColor.B, rgbColor.R, rgbColor.G, out h, out s, out v); - else if (rgbColor.G <= rgbColor.R) - RGBToHSVHelper(0f, rgbColor.R, rgbColor.G, rgbColor.B, out h, out s, out v); - else - RGBToHSVHelper(2f, rgbColor.G, rgbColor.B, rgbColor.R, out h, out s, out v); - } - - private static void RGBToHSVHelper(float offset, float dominantcolor, float colorone, float colortwo, out float h, out float s, out float v) - { - v = dominantcolor; - if (Mathf.IsZero(v)) - { - s = 0f; - h = 0f; - } - else - { - var single = colorone <= colortwo ? colorone : colortwo; - float vv = v - single; - if (Mathf.IsZero(vv)) - { - s = 0f; - h = offset + (colorone - colortwo); - } - else - { - s = vv / v; - h = offset + (colorone - colortwo) / vv; - } - h = h / 6f; - if (h < 0f) - h = h + 1f; - } - } - /// /// Adjusts the contrast of a color. /// diff --git a/Source/Engine/Core/Math/Color.h b/Source/Engine/Core/Math/Color.h index d11097d5c..5a7986517 100644 --- a/Source/Engine/Core/Math/Color.h +++ b/Source/Engine/Core/Math/Color.h @@ -166,7 +166,7 @@ public: /// Creates RGB color from Hue[0-360], Saturation[0-1] and Value[0-1] packed to XYZ vector. /// /// The HSV color. - /// The alpha value. Default is 1. + /// The alpha value. Default is 1. /// The RGB color. static Color FromHSV(const Vector3& hsv, float alpha = 1.0f); @@ -264,28 +264,15 @@ public: } // Returns true if color is fully transparent (all components are equal zero). - bool IsTransparent() const - { - return Math::IsZero(R + G + B + A); - } + bool IsTransparent() const; // Returns true if color has opacity channel in use (different from 1). - bool HasOpacity() const - { - return !Math::IsOne(A); - } + bool HasOpacity() const; public: - static bool NearEqual(const Color& a, const Color& b) - { - return Math::NearEqual(a.R, b.R) && Math::NearEqual(a.G, b.G) & Math::NearEqual(a.B, b.B) && Math::NearEqual(a.A, b.A); - } - - static bool NearEqual(const Color& a, const Color& b, float epsilon) - { - return Math::NearEqual(a.R, b.R, epsilon) && Math::NearEqual(a.G, b.G, epsilon) & Math::NearEqual(a.B, b.B, epsilon) && Math::NearEqual(a.A, b.A, epsilon); - } + static bool NearEqual(const Color& a, const Color& b); + static bool NearEqual(const Color& a, const Color& b, float epsilon); public: @@ -296,7 +283,7 @@ public: Vector4 ToVector4() const; /// - /// Gets Hue[0-1], Saturation[0-1] and Value[0-360] from RGB color. + /// Gets Hue[0-360], Saturation[0-1] and Value[0-1] from RGB color. /// /// HSV color Vector3 ToHSV() const; @@ -308,13 +295,7 @@ public: /// The end color. /// The value between 0 and 1 indicating the weight of interpolation. /// When the method completes, contains the linear interpolation of the two colors. - static void Lerp(const Color& start, const Color& end, float amount, Color& result) - { - result.R = Math::Lerp(start.R, end.R, amount); - result.G = Math::Lerp(start.G, end.G, amount); - result.B = Math::Lerp(start.B, end.B, amount); - result.A = Math::Lerp(start.A, end.A, amount); - } + static void Lerp(const Color& start, const Color& end, float amount, Color& result); /// /// Performs a linear interpolation between two colors. @@ -323,12 +304,7 @@ public: /// The end color. /// The value between 0 and 1 indicating the weight of interpolation. /// The linear interpolation of the two colors. - static Color Lerp(const Color& start, const Color& end, float amount) - { - Color result; - Lerp(start, end, amount, result); - return result; - } + static Color Lerp(const Color& start, const Color& end, float amount); // Converts a [0.0, 1.0] linear value into a [0.0, 1.0] sRGB value. static Color LinearToSrgb(const Color& linear); @@ -530,6 +506,14 @@ inline Color operator*(float a, const Color& b) return b * a; } +namespace Math +{ + FORCE_INLINE static bool NearEqual(const Color& a, const Color& b) + { + return Color::NearEqual(a, b); + } +} + template<> struct TIsPODType { diff --git a/Source/Engine/Core/Math/Color32.h b/Source/Engine/Core/Math/Color32.h index 89f64cc35..f848f6f73 100644 --- a/Source/Engine/Core/Math/Color32.h +++ b/Source/Engine/Core/Math/Color32.h @@ -231,6 +231,14 @@ inline Color32 operator*(float a, const Color32& b) return b * a; } +namespace Math +{ + FORCE_INLINE static bool NearEqual(const Color32& a, const Color32& b) + { + return a == b; + } +} + template<> struct TIsPODType { diff --git a/Source/Engine/Core/Math/Math.cpp b/Source/Engine/Core/Math/Math.cpp index 4075bcbda..d0642d7ca 100644 --- a/Source/Engine/Core/Math/Math.cpp +++ b/Source/Engine/Core/Math/Math.cpp @@ -5,8 +5,8 @@ void Math::SinCos(float angle, float& sine, float& cosine) { - sine = sin(angle); - cosine = cos(angle); + sine = Math::Sin(angle); + cosine = Math::Cos(angle); } uint32 Math::FloorLog2(uint32 value) diff --git a/Source/Engine/Core/Math/Matrix3x4.h b/Source/Engine/Core/Math/Matrix3x4.h new file mode 100644 index 000000000..49f6cc1ff --- /dev/null +++ b/Source/Engine/Core/Math/Matrix3x4.h @@ -0,0 +1,55 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Matrix.h" + +/// +/// Helper matrix for optimized float3x4 package of transformation matrices. +/// +struct FLAXENGINE_API Matrix3x4 +{ + float M[3][4]; + + void SetMatrix(const Matrix& m) + { + const float* src = m.Raw; + float* dst = &M[0][0]; + dst[0] = src[0]; + dst[1] = src[1]; + dst[2] = src[2]; + dst[3] = src[3]; + dst[4] = src[4]; + dst[5] = src[5]; + dst[6] = src[6]; + dst[7] = src[7]; + dst[8] = src[8]; + dst[9] = src[9]; + dst[10] = src[10]; + dst[11] = src[11]; + } + + void SetMatrixTranspose(const Matrix& m) + { + const float* src = m.Raw; + float* dst = &M[0][0]; + dst[0] = src[0]; + dst[1] = src[4]; + dst[2] = src[8]; + dst[3] = src[12]; + dst[4] = src[1]; + dst[5] = src[5]; + dst[6] = src[9]; + dst[7] = src[13]; + dst[8] = src[2]; + dst[9] = src[6]; + dst[10] = src[10]; + dst[11] = src[14]; + } +}; + +template<> +struct TIsPODType +{ + enum { Value = true }; +}; diff --git a/Source/Engine/Core/Math/Quaternion.cs b/Source/Engine/Core/Math/Quaternion.cs index bc86fa2c9..938bf48b1 100644 --- a/Source/Engine/Core/Math/Quaternion.cs +++ b/Source/Engine/Core/Math/Quaternion.cs @@ -324,6 +324,14 @@ namespace FlaxEngine Z = -Z; } + /// + /// Gets the conjugated quaternion. + /// + public Quaternion Conjugated() + { + return new Quaternion(-X, -Y, -Z, W); + } + /// /// Conjugates and renormalizes the quaternion. /// @@ -627,7 +635,7 @@ namespace FlaxEngine public static float AngleBetween(Quaternion a, Quaternion b) { float num = Dot(a, b); - return num > 0.99999999f ? 0 : Mathf.Acos(Mathf.Min(Mathf.Abs(num), 1f)) * 2f * 57.29578f; + return num > 0.9999999f ? 0 : Mathf.Acos(Mathf.Min(Mathf.Abs(num), 1f)) * 2f * 57.29578f; } /// @@ -1470,7 +1478,7 @@ namespace FlaxEngine [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(Quaternion left, Quaternion right) { - return Dot(ref left, ref right) > 0.99999999f; + return Dot(ref left, ref right) > 0.9999999f; } /// @@ -1485,7 +1493,7 @@ namespace FlaxEngine [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(Quaternion left, Quaternion right) { - return Dot(ref left, ref right) <= 0.99999999f; + return Dot(ref left, ref right) <= 0.9999999f; } /// @@ -1597,7 +1605,7 @@ namespace FlaxEngine [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(ref Quaternion other) { - //return Dot(ref this, ref other) > 0.99999999f; + //return Dot(ref this, ref other) > 0.9999999f; return Mathf.NearEqual(other.X, X) && Mathf.NearEqual(other.Y, Y) && Mathf.NearEqual(other.Z, Z) && Mathf.NearEqual(other.W, W); } diff --git a/Source/Engine/Core/Math/Quaternion.h b/Source/Engine/Core/Math/Quaternion.h index 54ea49540..d3eeb4f32 100644 --- a/Source/Engine/Core/Math/Quaternion.h +++ b/Source/Engine/Core/Math/Quaternion.h @@ -391,7 +391,7 @@ public: /// true if the specified is equal to this instance; otherwise, false. FORCE_INLINE bool operator==(const Quaternion& other) const { - return Dot(*this, other) > 0.99999999f; + return Dot(*this, other) > 0.9999999f; } /// @@ -414,7 +414,7 @@ public: /// true if the specified structures are equal; otherwise, false. static bool NearEqual(const Quaternion& a, const Quaternion& b) { - return Dot(a, b) > 0.99999999f; + return Dot(a, b) > 0.9999999f; } /// @@ -474,7 +474,7 @@ public: static float AngleBetween(const Quaternion& a, const Quaternion& b) { const float dot = Dot(a, b); - return dot > 0.99999999f ? 0 : Math::Acos(Math::Min(Math::Abs(dot), 1.0f)) * 2.0f * 57.29578f; + return dot > 0.9999999f ? 0 : Math::Acos(Math::Min(Math::Abs(dot), 1.0f)) * 2.0f * 57.29578f; } // Adds two quaternions diff --git a/Source/Engine/Core/Math/Transform.cpp b/Source/Engine/Core/Math/Transform.cpp index 5ac71779b..35f616236 100644 --- a/Source/Engine/Core/Math/Transform.cpp +++ b/Source/Engine/Core/Math/Transform.cpp @@ -43,37 +43,42 @@ void Transform::GetWorld(Matrix& result) const Transform Transform::Add(const Vector3& translation) const { Transform result; - result.Orientation = Orientation; result.Scale = Scale; Vector3::Add(Translation, translation, result.Translation); - return result; } Transform Transform::Add(const Transform& other) const { Transform result; - Quaternion::Multiply(Orientation, other.Orientation, result.Orientation); result.Orientation.Normalize(); Vector3::Multiply(Scale, other.Scale, result.Scale); Vector3::Add(Translation, other.Translation, result.Translation); + return result; +} +Transform Transform::Subtract(const Transform& other) const +{ + Transform result; + Vector3::Subtract(Translation, other.Translation, result.Translation); + const Quaternion invRotation = other.Orientation.Conjugated(); + Quaternion::Multiply(Orientation, invRotation, result.Orientation); + result.Orientation.Normalize(); + Vector3::Divide(Scale, other.Scale, result.Scale); return result; } Transform Transform::LocalToWorld(const Transform& other) const { Transform result; - Quaternion::Multiply(Orientation, other.Orientation, result.Orientation); result.Orientation.Normalize(); Vector3::Multiply(Scale, other.Scale, result.Scale); Vector3 tmp = other.Translation * Scale; Vector3::Transform(tmp, Orientation, tmp); Vector3::Add(tmp, Translation, result.Translation); - return result; } @@ -94,6 +99,13 @@ Vector3 Transform::LocalToWorld(const Vector3& point) const return result + Translation; } +Vector3 Transform::LocalToWorldVector(const Vector3& vector) const +{ + Vector3 result = vector * Scale; + Vector3::Transform(result, Orientation, result); + return result; +} + void Transform::LocalToWorld(const Vector3& point, Vector3& result) const { Vector3 tmp = point * Scale; @@ -171,6 +183,24 @@ Vector3 Transform::WorldToLocal(const Vector3& point) const return result * invScale; } +Vector3 Transform::WorldToLocalVector(const Vector3& vector) const +{ + Vector3 invScale = Scale; + if (invScale.X != 0.0f) + invScale.X = 1.0f / invScale.X; + if (invScale.Y != 0.0f) + invScale.Y = 1.0f / invScale.Y; + if (invScale.Z != 0.0f) + invScale.Z = 1.0f / invScale.Z; + + const Quaternion invRotation = Orientation.Conjugated(); + + Vector3 result; + Vector3::Transform(vector, invRotation, result); + + return result * invScale; +} + void Transform::WorldToLocal(const Vector3* points, int32 pointsCount, Vector3* result) const { Vector3 invScale = Scale; diff --git a/Source/Engine/Core/Math/Transform.cs b/Source/Engine/Core/Math/Transform.cs index 21ed705ff..129ddf92a 100644 --- a/Source/Engine/Core/Math/Transform.cs +++ b/Source/Engine/Core/Math/Transform.cs @@ -169,6 +169,37 @@ namespace FlaxEngine Matrix.Transformation(ref Scale, ref Orientation, ref Translation, out result); } + /// + /// Adds two transforms. + /// + /// The first transform to add. + /// The second transform to add. + /// The sum of the two transforms. + public static Transform Add(Transform left, Transform right) + { + Transform result; + Quaternion.Multiply(ref left.Orientation, ref right.Orientation, out result.Orientation); + Vector3.Multiply(ref left.Scale, ref right.Scale, out result.Scale); + Vector3.Add(ref left.Translation, ref right.Translation, out result.Translation); + return result; + } + + /// + /// Subtracts two transforms. + /// + /// The first transform to subtract from. + /// The second transform to subtract. + /// The difference of the two transforms. + public static Transform Subtract(Transform left, Transform right) + { + Transform result; + Vector3.Subtract(ref left.Translation, ref right.Translation, out result.Translation); + Quaternion invRotation = right.Orientation.Conjugated(); + Quaternion.Multiply(ref left.Orientation, ref invRotation, out result.Orientation); + Vector3.Divide(ref left.Scale, ref right.Scale, out result.Scale); + return result; + } + /// /// Perform transformation of the given transform in local space /// @@ -176,12 +207,10 @@ namespace FlaxEngine /// World space transform public Transform LocalToWorld(Transform other) { - Transform result = new Transform(Vector3.Zero); - + Transform result; Quaternion.Multiply(ref Orientation, ref other.Orientation, out result.Orientation); Vector3.Multiply(ref Scale, ref other.Scale, out result.Scale); result.Translation = LocalToWorld(other.Translation); - return result; } @@ -197,6 +226,18 @@ namespace FlaxEngine return point + Translation; } + /// + /// Performs transformation of the given vector in local space to the world space of this transform. + /// + /// The local space vector. + /// The world space vector. + public Vector3 LocalToWorldVector(Vector3 vector) + { + vector *= Scale; + Vector3.Transform(ref vector, ref Orientation, out vector); + return vector; + } + /// /// Perform transformation of the given points in local space /// @@ -217,7 +258,6 @@ namespace FlaxEngine /// Local space transform public Transform WorldToLocal(Transform other) { - Transform result = new Transform(Vector3.Zero); Vector3 invScale = Scale; if (invScale.X != 0.0f) invScale.X = 1.0f / invScale.X; @@ -226,6 +266,7 @@ namespace FlaxEngine if (invScale.Z != 0.0f) invScale.Z = 1.0f / invScale.Z; + Transform result; result.Orientation = Orientation; result.Orientation.Invert(); Quaternion.Multiply(ref result.Orientation, ref other.Orientation, out result.Orientation); @@ -259,6 +300,29 @@ namespace FlaxEngine return result * invScale; } + /// + /// Perform transformation of the given vector in world space + /// + /// World space vector + /// Local space vector + public Vector3 WorldToLocalVector(Vector3 vector) + { + Vector3 invScale = Scale; + if (invScale.X != 0.0f) + invScale.X = 1.0f / invScale.X; + if (invScale.Y != 0.0f) + invScale.Y = 1.0f / invScale.Y; + if (invScale.Z != 0.0f) + invScale.Z = 1.0f / invScale.Z; + + Quaternion invRotation = Orientation; + invRotation.Invert(); + + Vector3.Transform(ref vector, ref invRotation, out var result); + + return result * invScale; + } + /// /// Perform transformation of the given points in world space /// @@ -369,6 +433,28 @@ namespace FlaxEngine return !left.Equals(ref right); } + /// + /// Adds two transformations. + /// + /// The first transform to add. + /// The second transform to add. + /// The sum of the two transformations. + public static Transform operator +(Transform left, Transform right) + { + return Add(left, right); + } + + /// + /// Subtracts two transformations. + /// + /// The first transform to subtract from. + /// The second transform to subtract. + /// The difference of the two transformations. + public static Transform operator -(Transform left, Transform right) + { + return Subtract(left, right); + } + /// /// Returns a that represents this instance. /// diff --git a/Source/Engine/Core/Math/Transform.h b/Source/Engine/Core/Math/Transform.h index ed19efeae..069a49992 100644 --- a/Source/Engine/Core/Math/Transform.h +++ b/Source/Engine/Core/Math/Transform.h @@ -163,6 +163,13 @@ public: /// The sum of two transformations. Transform Add(const Transform& other) const; + /// + /// Subtracts transformation from this transform. + /// + /// The other transformation. + /// The different of two transformations. + Transform Subtract(const Transform& other) const; + /// /// Performs transformation of the given transform in local space to the world space of this transform. /// @@ -184,6 +191,13 @@ public: /// The world space point. Vector3 LocalToWorld(const Vector3& point) const; + /// + /// Performs transformation of the given vector in local space to the world space of this transform. + /// + /// The local space vector. + /// The world space vector. + Vector3 LocalToWorldVector(const Vector3& vector) const; + /// /// Performs transformation of the given point in local space to the world space of this transform. /// @@ -220,6 +234,13 @@ public: /// The local space point. Vector3 WorldToLocal(const Vector3& point) const; + /// + /// Performs transformation of the given vector in world space to the local space of this transform. + /// + /// The world space vector. + /// The local space vector. + Vector3 WorldToLocalVector(const Vector3& vector) const; + /// /// Performs transformation of the given points in world space to the local space of this transform. /// @@ -240,6 +261,11 @@ public: return Add(other); } + FORCE_INLINE Transform operator-(const Transform& other) const + { + return Subtract(other); + } + FORCE_INLINE Transform operator+(const Vector3& other) const { return Add(other); @@ -293,6 +319,14 @@ public: static void Lerp(const Transform& t1, const Transform& t2, float amount, Transform& result); }; +namespace Math +{ + FORCE_INLINE static bool NearEqual(const Transform& a, const Transform& b) + { + return Transform::NearEqual(a, b); + } +} + template<> struct TIsPODType { diff --git a/Source/Engine/Core/Math/Vector4.h b/Source/Engine/Core/Math/Vector4.h index adca955fc..c7771aa67 100644 --- a/Source/Engine/Core/Math/Vector4.h +++ b/Source/Engine/Core/Math/Vector4.h @@ -379,7 +379,7 @@ public: static bool NearEqual(const Vector4& a, const Vector4& b) { - return Math::NearEqual(a.X, b.X) && Math::NearEqual(a.Y, b.Y) & Math::NearEqual(a.Z, b.Z) && Math::NearEqual(a.W, b.W); + return Math::NearEqual(a.X, b.X) && Math::NearEqual(a.Y, b.Y) && Math::NearEqual(a.Z, b.Z) && Math::NearEqual(a.W, b.W); } static bool NearEqual(const Vector4& a, const Vector4& b, float epsilon) diff --git a/Source/Engine/Core/Types/BaseTypes.h b/Source/Engine/Core/Types/BaseTypes.h index c8cf21f99..b57f139ff 100644 --- a/Source/Engine/Core/Types/BaseTypes.h +++ b/Source/Engine/Core/Types/BaseTypes.h @@ -108,7 +108,5 @@ class Dictionary; inline T& operator&= (T& a, T b) { return (T&)((int&)a &= (int)b); } \ inline T& operator^= (T& a, T b) { return (T&)((int&)a ^= (int)b); } -#define DECLARE_SCRIPTING_TYPE_MINIMAL(type) \ - public: \ - friend class type##Internal; \ - static struct ScriptingTypeInitializer TypeInitializer; +// Returns byte offset from the object pointer in vtable to the begin of the given inherited type implementation +#define VTABLE_OFFSET(type, baseType) (((intptr)static_cast((type*)1))-1) diff --git a/Source/Engine/Core/Types/DataContainer.h b/Source/Engine/Core/Types/DataContainer.h index 954ff24e2..8b504e58f 100644 --- a/Source/Engine/Core/Types/DataContainer.h +++ b/Source/Engine/Core/Types/DataContainer.h @@ -324,9 +324,7 @@ public: void Append(T* data, int32 length) { if (length <= 0) - { return; - } if (Base::Length() == 0) { Copy(data, length); @@ -337,16 +335,13 @@ public: const auto prevLength = Base::_length; Base::_length = prevLength + length; - Base::_data = Allocator::Allocate(Base::_length * sizeof(T)); + Base::_data = (T*)Allocator::Allocate(Base::_length * sizeof(T)); Platform::MemoryCopy(Base::_data, prev, prevLength * sizeof(T)); Platform::MemoryCopy(Base::_data + prevLength * sizeof(T), data, length * sizeof(T)); if (_isAllocated && prev) - { Allocator::Free(prev); - } - _isAllocated = true; } diff --git a/Source/Engine/Core/Types/String.h b/Source/Engine/Core/Types/String.h index 4d1ef4e7f..cf21a1236 100644 --- a/Source/Engine/Core/Types/String.h +++ b/Source/Engine/Core/Types/String.h @@ -220,7 +220,7 @@ public: /// The index of the found substring or -1 if not found. int32 Find(const T* subStr, StringSearchCase searchCase = StringSearchCase::CaseSensitive, int32 startPosition = -1) const { - if (subStr == nullptr) + if (subStr == nullptr || !_data) return -1; const T* start = _data; if (startPosition != -1) @@ -241,7 +241,7 @@ public: int32 FindLast(const T* subStr, StringSearchCase searchCase = StringSearchCase::CaseSensitive, int32 startPosition = -1) const { const int32 subStrLen = StringUtils::Length(subStr); - if (subStrLen == 0) + if (subStrLen == 0 || !_data) return -1; if (startPosition == -1) startPosition = Length(); diff --git a/Source/Engine/Core/Utilities.h b/Source/Engine/Core/Utilities.h index 8d77c508d..531509d61 100644 --- a/Source/Engine/Core/Utilities.h +++ b/Source/Engine/Core/Utilities.h @@ -11,21 +11,21 @@ namespace Utilities template FORCE_INLINE T RoundTo1DecimalPlace(T value) { - return round(value * 10) / 10; + return (T)round((double)value * 10) / (T)10; } // Round floating point value up to 2 decimal places template FORCE_INLINE T RoundTo2DecimalPlaces(T value) { - return round(value * 100) / 100; + return (T)round((double)value * 100.0) / (T)100; } // Round floating point value up to 3 decimal places template FORCE_INLINE T RoundTo3DecimalPlaces(T value) { - return round(value * 1000) / 1000; + return (T)round((double)value * 1000.0) / (T)1000; } // Converts size of the file (in bytes) to the best fitting string diff --git a/Source/Engine/Debug/DebugDraw.cpp b/Source/Engine/Debug/DebugDraw.cpp index f954cd7cb..bae424c4e 100644 --- a/Source/Engine/Debug/DebugDraw.cpp +++ b/Source/Engine/Debug/DebugDraw.cpp @@ -131,6 +131,8 @@ struct DebugDrawData Array OneFrameLines; Array DefaultTriangles; Array OneFrameTriangles; + Array DefaultWireTriangles; + Array OneFrameWireTriangles; inline int32 Count() const { @@ -144,7 +146,7 @@ struct DebugDrawData inline int32 TrianglesCount() const { - return DefaultTriangles.Count() + OneFrameTriangles.Count(); + return DefaultTriangles.Count() + OneFrameTriangles.Count() + DefaultWireTriangles.Count() + OneFrameWireTriangles.Count(); } inline void Add(const DebugLine& l) @@ -163,13 +165,23 @@ struct DebugDrawData OneFrameTriangles.Add(t); } + inline void AddWire(const DebugTriangle& t) + { + if (t.TimeLeft > 0) + DefaultWireTriangles.Add(t); + else + OneFrameWireTriangles.Add(t); + } + inline void Update(float deltaTime) { UpdateList(deltaTime, DefaultLines); UpdateList(deltaTime, DefaultTriangles); + UpdateList(deltaTime, DefaultWireTriangles); OneFrameLines.Clear(); OneFrameTriangles.Clear(); + OneFrameWireTriangles.Clear(); } inline void Clear() @@ -178,6 +190,8 @@ struct DebugDrawData OneFrameLines.Clear(); DefaultTriangles.Clear(); OneFrameTriangles.Clear(); + DefaultWireTriangles.Clear(); + OneFrameWireTriangles.Clear(); } inline void Release() @@ -186,16 +200,20 @@ struct DebugDrawData OneFrameLines.Resize(0); DefaultTriangles.Resize(0); OneFrameTriangles.Resize(0); + DefaultWireTriangles.Resize(0); + OneFrameWireTriangles.Resize(0); } }; DebugDrawData DebugDrawDefault; DebugDrawData DebugDrawDepthTest; AssetReference DebugDrawShader; -PsData DebugDrawPsWireDefault; -PsData DebugDrawPsWireDepthTest; -PsData DebugDrawPsDefault; -PsData DebugDrawPsDepthTest; +PsData DebugDrawPsLinesDefault; +PsData DebugDrawPsLinesDepthTest; +PsData DebugDrawPsWireTrianglesDefault; +PsData DebugDrawPsWireTrianglesDepthTest; +PsData DebugDrawPsTrianglesDefault; +PsData DebugDrawPsTrianglesDepthTest; DynamicVertexBuffer* DebugDrawVB = nullptr; Vector3 SphereCache[DEBUG_DRAW_SPHERE_VERTICES]; Vector3 CircleCache[DEBUG_DRAW_CIRCLE_VERTICES]; @@ -295,10 +313,10 @@ bool DebugDrawService::Init() for (float a = 0.0f; a < TWO_PI; a += step) { // Calculate sines and cosines - float sinA = sin(a); - float cosA = cos(a); - float sinB = sin(a + step); - float cosB = cos(a + step); + float sinA = Math::Sin(a); + float cosA = Math::Cos(a); + float sinB = Math::Sin(a + step); + float cosB = Math::Cos(a + step); // XY loop SphereCache[index++] = Vector3(cosA, sinA, 0.0f); @@ -319,10 +337,10 @@ bool DebugDrawService::Init() for (float a = 0.0f; a < TWO_PI; a += step) { // Calculate sines and cosines - float sinA = sin(a); - float cosA = cos(a); - float sinB = sin(a + step); - float cosB = cos(a + step); + float sinA = Math::Sin(a); + float cosA = Math::Cos(a); + float sinB = Math::Sin(a + step); + float cosB = Math::Cos(a + step); CircleCache[index++] = Vector3(cosA, sinA, 0.0f); CircleCache[index++] = Vector3(cosB, sinB, 0.0f); @@ -454,16 +472,21 @@ void DebugDrawService::Update() // Default desc.PS = shader->GetPS("PS"); desc.PrimitiveTopologyType = PrimitiveTopologyType::Line; - failed |= DebugDrawPsWireDefault.Create(desc); + failed |= DebugDrawPsLinesDefault.Create(desc); desc.PrimitiveTopologyType = PrimitiveTopologyType::Triangle; - failed |= DebugDrawPsDefault.Create(desc); + failed |= DebugDrawPsTrianglesDefault.Create(desc); + desc.Wireframe = true; + failed |= DebugDrawPsWireTrianglesDefault.Create(desc); // Depth Test + desc.Wireframe = false; desc.PS = shader->GetPS("PS_DepthTest"); desc.PrimitiveTopologyType = PrimitiveTopologyType::Line; - failed |= DebugDrawPsWireDepthTest.Create(desc); + failed |= DebugDrawPsLinesDepthTest.Create(desc); desc.PrimitiveTopologyType = PrimitiveTopologyType::Triangle; - failed |= DebugDrawPsDepthTest.Create(desc); + failed |= DebugDrawPsTrianglesDepthTest.Create(desc); + desc.Wireframe = true; + failed |= DebugDrawPsWireTrianglesDepthTest.Create(desc); if (failed) { @@ -483,12 +506,14 @@ void DebugDrawService::Dispose() // Release resources SphereTriangleCache.Resize(0); - DebugDrawPsWireDefault.Release(); - DebugDrawPsWireDepthTest.Release(); - DebugDrawPsDepthTest.Release(); - DebugDrawPsDepthTest.Release(); + DebugDrawPsLinesDefault.Release(); + DebugDrawPsLinesDepthTest.Release(); + DebugDrawPsWireTrianglesDefault.Release(); + DebugDrawPsWireTrianglesDepthTest.Release(); + DebugDrawPsTrianglesDefault.Release(); + DebugDrawPsTrianglesDepthTest.Release(); SAFE_DELETE(DebugDrawVB); - DebugDrawShader.Unlink(); + DebugDrawShader = nullptr; } void DebugDraw::Draw(RenderContext& renderContext, GPUTextureView* target, GPUTextureView* depthBuffer, bool enableDepthTest) @@ -496,9 +521,9 @@ void DebugDraw::Draw(RenderContext& renderContext, GPUTextureView* target, GPUTe PROFILE_GPU_CPU("Debug Draw"); // Ensure to have shader loaded and any lines to render - const int32 DebugDrawDepthTestCount = DebugDrawDepthTest.Count(); - const int32 DebugDrawDefaultCount = DebugDrawDefault.Count(); - if (DebugDrawShader == nullptr || !DebugDrawShader->IsLoaded() || DebugDrawDepthTestCount + DebugDrawDefaultCount == 0) + const int32 debugDrawDepthTestCount = DebugDrawDepthTest.Count(); + const int32 debugDrawDefaultCount = DebugDrawDefault.Count(); + if (DebugDrawShader == nullptr || !DebugDrawShader->IsLoaded() || debugDrawDepthTestCount + debugDrawDefaultCount == 0) return; if (renderContext.Buffers == nullptr || !DebugDrawVB) return; @@ -518,6 +543,8 @@ void DebugDraw::Draw(RenderContext& renderContext, GPUTextureView* target, GPUTe const DebugDrawCall defaultLines = WriteLists(vertexCounter, DebugDrawDefault.DefaultLines, DebugDrawDefault.OneFrameLines); const DebugDrawCall depthTestTriangles = WriteLists(vertexCounter, DebugDrawDepthTest.DefaultTriangles, DebugDrawDepthTest.OneFrameTriangles); const DebugDrawCall defaultTriangles = WriteLists(vertexCounter, DebugDrawDefault.DefaultTriangles, DebugDrawDefault.OneFrameTriangles); + const DebugDrawCall depthTestWireTriangles = WriteLists(vertexCounter, DebugDrawDepthTest.DefaultWireTriangles, DebugDrawDepthTest.OneFrameWireTriangles); + const DebugDrawCall defaultWireTriangles = WriteLists(vertexCounter, DebugDrawDefault.DefaultWireTriangles, DebugDrawDefault.OneFrameWireTriangles); DebugDrawVB->Flush(context); #if COMPILE_WITH_PROFILER ProfilerCPU::EndEvent(updateBufferProfileKey); @@ -537,7 +564,7 @@ void DebugDraw::Draw(RenderContext& renderContext, GPUTextureView* target, GPUTe #define DRAW(drawCall) if (drawCall.VertexCount) // Draw with depth test - if (depthTestLines.VertexCount + depthTestTriangles.VertexCount > 0) + if (depthTestLines.VertexCount + depthTestTriangles.VertexCount + depthTestWireTriangles.VertexCount > 0) { if (data.EnableDepthTest) context->BindSR(0, renderContext.Buffers->DepthBuffer); @@ -548,16 +575,25 @@ void DebugDraw::Draw(RenderContext& renderContext, GPUTextureView* target, GPUTe // Lines if (depthTestLines.VertexCount) { - auto state = data.EnableDepthTest ? &DebugDrawPsWireDepthTest : &DebugDrawPsWireDefault; + auto state = data.EnableDepthTest ? &DebugDrawPsLinesDepthTest : &DebugDrawPsLinesDefault; context->SetState(state->Get(enableDepthWrite, true)); context->BindVB(ToSpan(&vb, 1)); context->Draw(depthTestLines.StartVertex, depthTestLines.VertexCount); } + + // Wire Triangles + if (depthTestWireTriangles.VertexCount) + { + auto state = data.EnableDepthTest ? &DebugDrawPsWireTrianglesDepthTest : &DebugDrawPsWireTrianglesDefault; + context->SetState(state->Get(enableDepthWrite, true)); + context->BindVB(ToSpan(&vb, 1)); + context->Draw(depthTestWireTriangles.StartVertex, depthTestWireTriangles.VertexCount); + } // Triangles if (depthTestTriangles.VertexCount) { - auto state = data.EnableDepthTest ? &DebugDrawPsDepthTest : &DebugDrawPsDefault; + auto state = data.EnableDepthTest ? &DebugDrawPsTrianglesDepthTest : &DebugDrawPsTrianglesDefault; context->SetState(state->Get(enableDepthWrite, true)); context->BindVB(ToSpan(&vb, 1)); context->Draw(depthTestTriangles.StartVertex, depthTestTriangles.VertexCount); @@ -568,22 +604,30 @@ void DebugDraw::Draw(RenderContext& renderContext, GPUTextureView* target, GPUTe } // Draw without depth - if (defaultLines.VertexCount + defaultTriangles.VertexCount > 0) + if (defaultLines.VertexCount + defaultTriangles.VertexCount + defaultWireTriangles.VertexCount > 0) { context->SetRenderTarget(target); // Lines if (defaultLines.VertexCount) { - context->SetState(DebugDrawPsWireDefault.Get(false, false)); + context->SetState(DebugDrawPsLinesDefault.Get(false, false)); context->BindVB(ToSpan(&vb, 1)); context->Draw(defaultLines.StartVertex, defaultLines.VertexCount); } + // Wire Triangles + if (defaultWireTriangles.VertexCount) + { + context->SetState(DebugDrawPsWireTrianglesDefault.Get(false, false)); + context->BindVB(ToSpan(&vb, 1)); + context->Draw(defaultWireTriangles.StartVertex, defaultWireTriangles.VertexCount); + } + // Triangles if (defaultTriangles.VertexCount) { - context->SetState(DebugDrawPsDefault.Get(false, false)); + context->SetState(DebugDrawPsTrianglesDefault.Get(false, false)); context->BindVB(ToSpan(&vb, 1)); context->Draw(defaultTriangles.StartVertex, defaultTriangles.VertexCount); } @@ -594,6 +638,7 @@ void DebugDraw::Draw(RenderContext& renderContext, GPUTextureView* target, GPUTe void DebugDraw::DrawActors(Actor** selectedActors, int32 selectedActorsCount) { + PROFILE_CPU(); if (selectedActors) { for (int32 i = 0; i < selectedActorsCount; i++) @@ -657,16 +702,6 @@ void DebugDraw::DrawLines(const Span& lines, const Matrix& transform, c } } -static Vector3 GetPoint(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t) -{ - const float oneMinusT = 1.0f - t; - return - oneMinusT * oneMinusT * oneMinusT * p0 + - 3.0f * oneMinusT * oneMinusT * t * p1 + - 3.0f * oneMinusT * t * t * p2 + - t * t * t * p3; -} - void DebugDraw::DrawBezier(const Vector3& p1, const Vector3& p2, const Vector3& p3, const Vector3& p4, const Color& color, float duration, bool depthTest) { // Create draw call entry @@ -683,13 +718,13 @@ void DebugDraw::DrawBezier(const Vector3& p1, const Vector3& p2, const Vector3& const Vector3 d3 = p4 - p3; const float len = d1.Length() + d2.Length() + d3.Length(); const int32 segmentCount = Math::Clamp(Math::CeilToInt(len * 0.05f), 1, 100); - const float segmentCountInv = 1.0f / segmentCount; + const float segmentCountInv = 1.0f / (float)segmentCount; list->EnsureCapacity(list->Count() + segmentCount + 2); // Draw segmented curve for (int32 i = 0; i <= segmentCount; i++) { - const float t = i * segmentCountInv; + const float t = (float)i * segmentCountInv; AnimationUtils::Bezier(p1, p2, p3, p4, t, l.End); list->Add(l); l.Start = l.End; @@ -885,18 +920,22 @@ void DebugDraw::DrawTriangles(const Span& vertices, const Color& color, list = duration > 0 ? &DebugDrawDepthTest.DefaultTriangles : &DebugDrawDepthTest.OneFrameTriangles; else list = duration > 0 ? &DebugDrawDefault.DefaultTriangles : &DebugDrawDefault.OneFrameTriangles; - list->EnsureCapacity(list->Count() + vertices.Length()); + list->EnsureCapacity(list->Count() + vertices.Length() / 3); - for (int32 i = 0; i < vertices.Length() * 3;) + for (int32 i = 0; i < vertices.Length();) { t.V0 = vertices[i++]; t.V1 = vertices[i++]; t.V2 = vertices[i++]; - list->Add(t); } } +void DebugDraw::DrawTriangles(const Array& vertices, const Color& color, float duration, bool depthTest) +{ + DrawTriangles(Span(vertices.Get(), vertices.Count()), color, duration, depthTest); +} + void DebugDraw::DrawTriangles(const Span& vertices, const Span& indices, const Color& color, float duration, bool depthTest) { ASSERT(indices.Length() % 3 == 0); @@ -910,18 +949,80 @@ void DebugDraw::DrawTriangles(const Span& vertices, const Span& list = duration > 0 ? &DebugDrawDepthTest.DefaultTriangles : &DebugDrawDepthTest.OneFrameTriangles; else list = duration > 0 ? &DebugDrawDefault.DefaultTriangles : &DebugDrawDefault.OneFrameTriangles; - list->EnsureCapacity(list->Count() + indices.Length()); + list->EnsureCapacity(list->Count() + indices.Length() / 3); - for (int32 i = 0; i < indices.Length() * 3;) + for (int32 i = 0; i < indices.Length();) { t.V0 = vertices[indices[i++]]; t.V1 = vertices[indices[i++]]; t.V2 = vertices[indices[i++]]; - list->Add(t); } } +void DebugDraw::DrawTriangles(const Array& vertices, const Array& indices, const Color& color, float duration, bool depthTest) +{ + DrawTriangles(Span(vertices.Get(), vertices.Count()), Span(indices.Get(), indices.Count()), color, duration, depthTest); +} + +void DebugDraw::DrawWireTriangles(const Span& vertices, const Color& color, float duration, bool depthTest) +{ + ASSERT(vertices.Length() % 3 == 0); + + DebugTriangle t; + t.Color = Color32(color); + t.TimeLeft = duration; + + Array* list; + if (depthTest) + list = duration > 0 ? &DebugDrawDepthTest.DefaultWireTriangles : &DebugDrawDepthTest.OneFrameWireTriangles; + else + list = duration > 0 ? &DebugDrawDefault.DefaultWireTriangles : &DebugDrawDefault.OneFrameWireTriangles; + list->EnsureCapacity(list->Count() + vertices.Length() / 3); + + for (int32 i = 0; i < vertices.Length();) + { + t.V0 = vertices[i++]; + t.V1 = vertices[i++]; + t.V2 = vertices[i++]; + list->Add(t); + } +} + +void DebugDraw::DrawWireTriangles(const Array& vertices, const Color& color, float duration, bool depthTest) +{ + DrawWireTriangles(Span(vertices.Get(), vertices.Count()), color, duration, depthTest); +} + +void DebugDraw::DrawWireTriangles(const Span& vertices, const Span& indices, const Color& color, float duration, bool depthTest) +{ + ASSERT(indices.Length() % 3 == 0); + + DebugTriangle t; + t.Color = Color32(color); + t.TimeLeft = duration; + + Array* list; + if (depthTest) + list = duration > 0 ? &DebugDrawDepthTest.DefaultWireTriangles : &DebugDrawDepthTest.OneFrameWireTriangles; + else + list = duration > 0 ? &DebugDrawDefault.DefaultWireTriangles : &DebugDrawDefault.OneFrameWireTriangles; + list->EnsureCapacity(list->Count() + indices.Length() / 3); + + for (int32 i = 0; i < indices.Length();) + { + t.V0 = vertices[indices[i++]]; + t.V1 = vertices[indices[i++]]; + t.V2 = vertices[indices[i++]]; + list->Add(t); + } +} + +void DebugDraw::DrawWireTriangles(const Array& vertices, const Array& indices, const Color& color, float duration, bool depthTest) +{ + DrawWireTriangles(Span(vertices.Get(), vertices.Count()), Span(indices.Get(), indices.Count()), color, duration, depthTest); +} + void DebugDraw::DrawWireTube(const Vector3& position, const Quaternion& orientation, float radius, float length, const Color& color, float duration, bool depthTest) { // Check if has no length (just sphere) @@ -948,10 +1049,10 @@ void DebugDraw::DrawWireTube(const Vector3& position, const Quaternion& orientat { // Calculate sines and cosines // TODO: optimize this stuff - float sinA = sin(a) * radius; - float cosA = cos(a) * radius; - float sinB = sin(a + step) * radius; - float cosB = cos(a + step) * radius; + float sinA = Math::Sin(a) * radius; + float cosA = Math::Cos(a) * radius; + float sinB = Math::Sin(a + step) * radius; + float cosB = Math::Cos(a + step) * radius; // First XY loop DRAW_WIRE_BOX_LINE(cosA, sinA, -halfLength, cosB, sinB, -halfLength); @@ -997,8 +1098,8 @@ void DebugDraw::DrawWireCylinder(const Vector3& position, const Quaternion& orie { // Cache data float theta = i * angleBetweenFacets; - float x = cos(theta) * radius; - float z = sin(theta) * radius; + float x = Math::Cos(theta) * radius; + float z = Math::Sin(theta) * radius; // Top cap CylinderCache[index++] = Vector3(x, verticalOffset, z); diff --git a/Source/Engine/Debug/DebugDraw.h b/Source/Engine/Debug/DebugDraw.h index 6027c6697..69c3d4fed 100644 --- a/Source/Engine/Debug/DebugDraw.h +++ b/Source/Engine/Debug/DebugDraw.h @@ -113,6 +113,15 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(DebugDraw); /// If set to true depth test will be performed, otherwise depth will be ignored. API_FUNCTION() static void DrawTriangles(const Span& vertices, const Color& color, float duration = 0.0f, bool depthTest = true); + /// + /// Draws the triangles. + /// + /// The triangle vertices list (must have multiple of 3 elements). + /// The color. + /// The duration (in seconds). Use 0 to draw it only once. + /// If set to true depth test will be performed, otherwise depth will be ignored. + static void DrawTriangles(const Array& vertices, const Color& color, float duration = 0.0f, bool depthTest = true); + /// /// Draws the triangles using the given index buffer. /// @@ -123,6 +132,54 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(DebugDraw); /// If set to true depth test will be performed, otherwise depth will be ignored. API_FUNCTION() static void DrawTriangles(const Span& vertices, const Span& indices, const Color& color, float duration = 0.0f, bool depthTest = true); + /// + /// Draws the triangles using the given index buffer. + /// + /// The triangle vertices list. + /// The triangle indices list (must have multiple of 3 elements). + /// The color. + /// The duration (in seconds). Use 0 to draw it only once. + /// If set to true depth test will be performed, otherwise depth will be ignored. + static void DrawTriangles(const Array& vertices, const Array& indices, const Color& color, float duration = 0.0f, bool depthTest = true); + + /// + /// Draws the wireframe triangles. + /// + /// The triangle vertices list (must have multiple of 3 elements). + /// The color. + /// The duration (in seconds). Use 0 to draw it only once. + /// If set to true depth test will be performed, otherwise depth will be ignored. + API_FUNCTION() static void DrawWireTriangles(const Span& vertices, const Color& color, float duration = 0.0f, bool depthTest = true); + + /// + /// Draws the wireframe triangles. + /// + /// The triangle vertices list (must have multiple of 3 elements). + /// The color. + /// The duration (in seconds). Use 0 to draw it only once. + /// If set to true depth test will be performed, otherwise depth will be ignored. + static void DrawWireTriangles(const Array& vertices, const Color& color, float duration = 0.0f, bool depthTest = true); + + /// + /// Draws the wireframe triangles using the given index buffer. + /// + /// The triangle vertices list. + /// The triangle indices list (must have multiple of 3 elements). + /// The color. + /// The duration (in seconds). Use 0 to draw it only once. + /// If set to true depth test will be performed, otherwise depth will be ignored. + API_FUNCTION() static void DrawWireTriangles(const Span& vertices, const Span& indices, const Color& color, float duration = 0.0f, bool depthTest = true); + + /// + /// Draws the wireframe triangles using the given index buffer. + /// + /// The triangle vertices list. + /// The triangle indices list (must have multiple of 3 elements). + /// The color. + /// The duration (in seconds). Use 0 to draw it only once. + /// If set to true depth test will be performed, otherwise depth will be ignored. + static void DrawWireTriangles(const Array& vertices, const Array& indices, const Color& color, float duration = 0.0f, bool depthTest = true); + /// /// Draws the wireframe box. /// @@ -232,6 +289,8 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(DebugDraw); #define DEBUG_DRAW_SPHERE(sphere, color, duration, depthTest) DebugDraw::DrawSphere(sphere, color, duration, depthTest) #define DEBUG_DRAW_BOX(box, color, duration, depthTest) DebugDraw::DrawBox(box, color, duration, depthTest) #define DEBUG_DRAW_WIRE_TRIANGLE(v0, v1, v2, color, duration, depthTest) DebugDraw::DrawWireTriangle(v0, v1, v2, color, duration, depthTest) +#define DEBUG_DRAW_WIRE_TRIANGLES(vertices, color, duration, depthTest) DebugDraw::DrawWireTriangles(vertices, color, duration, depthTest) +#define DEBUG_DRAW_WIRE_TRIANGLES_EX(vertices, indices, color, duration, depthTest) DebugDraw::DrawWireTriangles(vertices, indices, color, duration, depthTest) #define DEBUG_DRAW_WIRE_BOX(box, color, duration, depthTest) DebugDraw::DrawWireBox(box, color, duration, depthTest) #define DEBUG_DRAW_WIRE_FRUSTUM(frustum, color, duration, depthTest) DebugDraw::DrawWireFrustum(frustum, color, duration, depthTest) #define DEBUG_DRAW_WIRE_SPHERE(sphere, color, duration, depthTest) DebugDraw::DrawWireSphere(sphere, color, duration, depthTest) @@ -251,6 +310,8 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(DebugDraw); #define DEBUG_DRAW_SPHERE(sphere, color, duration, depthTest) #define DEBUG_DRAW_BOX(box, color, duration, depthTest) #define DEBUG_DRAW_WIRE_TRIANGLE(v0, v1, v2, color, duration, depthTest) +#define DEBUG_DRAW_WIRE_TRIANGLES(vertices, color, duration, depthTest) +#define DEBUG_DRAW_WIRE_TRIANGLES_EX(vertices, indices, color, duration, depthTest) #define DEBUG_DRAW_WIRE_BOX(box, color, duration, depthTest) #define DEBUG_DRAW_WIRE_FRUSTUM(frustum, color, duration, depthTest) #define DEBUG_DRAW_WIRE_SPHERE(sphere, color, duration, depthTest) diff --git a/Source/Engine/Engine/Base/GameBase.cpp b/Source/Engine/Engine/Base/GameBase.cpp index 6c02ce1d1..62e187e7d 100644 --- a/Source/Engine/Engine/Base/GameBase.cpp +++ b/Source/Engine/Engine/Base/GameBase.cpp @@ -119,7 +119,10 @@ bool GameBase::Init() } // Preload first scene asset data - GameBaseImpl::FirstScene = GameSettings::FirstScene; + const auto gameSettings = GameSettings::Get(); + if (!gameSettings) + return true; + GameBaseImpl::FirstScene = gameSettings->FirstScene; return false; } @@ -256,13 +259,13 @@ void GameBaseImpl::OnSplashScreenEnd() { // Hide splash screen SplashScreenTime = 0; - SplashScreen.Unlink(); + SplashScreen = nullptr; MainRenderTask::Instance->PostRender.Unbind(&OnPostRender); // Load the first scene LOG(Info, "Loading the first scene"); - FirstScene.Unlink(); - const auto sceneId = GameSettings::FirstScene; + const auto sceneId = FirstScene ? FirstScene.GetID() : Guid::Empty; + FirstScene = nullptr; if (Level::LoadSceneAsync(sceneId)) { LOG(Fatal, "Cannot load the first scene."); diff --git a/Source/Engine/Engine/CommandLine.cpp b/Source/Engine/Engine/CommandLine.cpp index 707f99de1..edfc9c268 100644 --- a/Source/Engine/Engine/CommandLine.cpp +++ b/Source/Engine/Engine/CommandLine.cpp @@ -133,6 +133,7 @@ bool CommandLine::Parse(const Char* cmdLine) PARSE_BOOL_SWITCH("-genprojectfiles ", GenProjectFiles); PARSE_ARG_SWITCH("-build ", Build); PARSE_BOOL_SWITCH("-skipcompile ", SkipCompile); + PARSE_BOOL_SWITCH("-shaderdebug ", ShaderDebug); #endif diff --git a/Source/Engine/Engine/CommandLine.h b/Source/Engine/Engine/CommandLine.h index 065135b48..278620bf5 100644 --- a/Source/Engine/Engine/CommandLine.h +++ b/Source/Engine/Engine/CommandLine.h @@ -150,6 +150,11 @@ public: /// Nullable SkipCompile; + /// + /// -shaderdebug (enables debugging data generation for shaders and disables shader compiler optimizations) + /// + Nullable ShaderDebug; + #endif }; diff --git a/Source/Engine/Engine/Engine.cpp b/Source/Engine/Engine/Engine.cpp index 140f1b4a0..2518d5916 100644 --- a/Source/Engine/Engine/Engine.cpp +++ b/Source/Engine/Engine/Engine.cpp @@ -51,6 +51,9 @@ namespace EngineImpl { bool IsReady = false; +#if !USE_EDITOR + bool RunInBackground = false; +#endif String CommandLine = nullptr; int32 Fps = 0, FpsAccumulatedFrames = 0; double FpsAccumulated = 0.0; @@ -133,6 +136,9 @@ int32 Engine::Main(const Char* cmdLine) Platform::BeforeRun(); EngineImpl::InitMainWindow(); Application::BeforeRun(); +#if !USE_EDITOR && (PLATFORM_WINDOWS || PLATFORM_LINUX) + EngineImpl::RunInBackground = PlatformSettings::Get()->RunInBackground; +#endif Log::Logger::WriteFloor(); LOG_FLUSH(); Time::OnBeforeRun(); @@ -279,11 +285,7 @@ void Engine::OnUpdate() bool isGameRunning = true; if (mainWindow && !mainWindow->IsFocused()) { -#if PLATFORM_WINDOWS || PLATFORM_LINUX - isGameRunning = PlatformSettings::Instance()->RunInBackground; -#else - isGameRunning = false; -#endif + isGameRunning = EngineImpl::RunInBackground; } Time::SetGamePaused(!isGameRunning); #endif @@ -393,8 +395,11 @@ const String& Engine::GetCommandLine() JsonAsset* Engine::GetCustomSettings(const StringView& key) { + const auto settings = GameSettings::Get(); + if (!settings) + return nullptr; Guid assetId = Guid::Empty; - GameSettings::CustomSettings.TryGet(key, assetId); + settings->CustomSettings.TryGet(key, assetId); return Content::LoadAsync(assetId); } diff --git a/Source/Engine/Engine/ISceneObject.cs b/Source/Engine/Engine/ISceneObject.cs deleted file mode 100644 index 2b5ae7706..000000000 --- a/Source/Engine/Engine/ISceneObject.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. - -using System; - -namespace FlaxEngine -{ - /// - /// Interface for scene objects that unifies various properties used across actors and scripts. - /// - public interface ISceneObject - { - /// - /// Gets the scene object which contains this object. - /// - Scene Scene { get; } - - /// - /// Gets a value indicating whether this object has a valid linkage to the prefab asset. - /// - bool HasPrefabLink { get; } - - /// - /// Gets the prefab asset ID. Empty if no prefab link exists. - /// - Guid PrefabID { get; } - - /// - /// Gets the ID of the object within a object that is used for synchronization with this object. Empty if no prefab link exists. - /// - Guid PrefabObjectID { get; } - - /// - /// Breaks the prefab linkage for this object (including all children). - /// - void BreakPrefabLink(); - } -} diff --git a/Source/Engine/Engine/ITransformable.cs b/Source/Engine/Engine/ITransformable.cs deleted file mode 100644 index a8bbd910c..000000000 --- a/Source/Engine/Engine/ITransformable.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. - -namespace FlaxEngine -{ - /// - /// Interface for objects that can be transformed. - /// - public interface ITransformable - { - /// - /// Gets or sets the transform. - /// - Transform Transform { get; set; } - } -} diff --git a/Source/Engine/Engine/Linux/LinuxGame.cpp b/Source/Engine/Engine/Linux/LinuxGame.cpp index 43bfe4a6e..1f59a66a9 100644 --- a/Source/Engine/Engine/Linux/LinuxGame.cpp +++ b/Source/Engine/Engine/Linux/LinuxGame.cpp @@ -16,7 +16,7 @@ void LinuxGame::InitMainWindowSettings(CreateWindowSettings& settings) { // TODO: restore window size and fullscreen mode from the cached local settings saved after previous session - const auto platformSettings = LinuxPlatformSettings::Instance(); + const auto platformSettings = LinuxPlatformSettings::Get(); auto windowMode = platformSettings->WindowMode; // Use command line switches @@ -54,7 +54,7 @@ void LinuxGame::InitMainWindowSettings(CreateWindowSettings& settings) bool LinuxGame::Init() { - const auto platformSettings = LinuxPlatformSettings::Instance(); + const auto platformSettings = LinuxPlatformSettings::Get(); // Create mutex if need to if (platformSettings->ForceSingleInstance) diff --git a/Source/Engine/Engine/PhysicalMaterial.cs b/Source/Engine/Engine/PhysicalMaterial.cs deleted file mode 100644 index 3136c8d8a..000000000 --- a/Source/Engine/Engine/PhysicalMaterial.cs +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. - -namespace FlaxEngine -{ - /// - /// Physical materials are used to define the response of a physical object when interacting dynamically with the world. - /// - public sealed class PhysicalMaterial - { - /// - /// The friction value of surface, controls how easily things can slide on this surface. - /// - [EditorOrder(0), Limit(0), EditorDisplay("Physical Material"), Tooltip("The friction value of surface, controls how easily things can slide on this surface.")] - public float Friction = 0.7f; - - /// - /// The friction combine mode, controls how friction is computed for multiple materials. - /// - [EditorOrder(1), EditorDisplay("Physical Material"), Tooltip("The friction combine mode, controls how friction is computed for multiple materials.")] - public PhysicsCombineMode FrictionCombineMode = PhysicsCombineMode.Average; - - /// - /// If set we will use the FrictionCombineMode of this material, instead of the FrictionCombineMode found in the Physics settings. - /// - [HideInEditor] - public bool OverrideFrictionCombineMode = false; - - /// - /// The restitution or 'bounciness' of this surface, between 0 (no bounce) and 1 (outgoing velocity is same as incoming). - /// - [EditorOrder(3), Range(0, 1), EditorDisplay("Physical Material"), Tooltip("The restitution or \'bounciness\' of this surface, between 0 (no bounce) and 1 (outgoing velocity is same as incoming).")] - public float Restitution = 0.3f; - - /// - /// The restitution combine mode, controls how restitution is computed for multiple materials. - /// - [EditorOrder(4), EditorDisplay("Physical Material"), Tooltip("The restitution combine mode, controls how restitution is computed for multiple materials.")] - public PhysicsCombineMode RestitutionCombineMode = PhysicsCombineMode.Average; - - /// - /// If set we will use the RestitutionCombineMode of this material, instead of the RestitutionCombineMode found in the Physics settings. - /// - [HideInEditor] - public bool OverrideRestitutionCombineMode = false; - } -} diff --git a/Source/Engine/Engine/Time.cpp b/Source/Engine/Engine/Time.cpp index 1afe7e05a..f26f4f759 100644 --- a/Source/Engine/Engine/Time.cpp +++ b/Source/Engine/Engine/Time.cpp @@ -3,17 +3,19 @@ #include "Time.h" #include "EngineService.h" #include "Engine/Core/Math/Math.h" -#include "Engine/Core/Config/TimeSettings.h" #include "Engine/Platform/Platform.h" -#include "Engine/Physics/PhysicsSettings.h" +#include "Engine/Core/Config/TimeSettings.h" +#include "Engine/Serialization/Serialization.h" namespace { bool FixedDeltaTimeEnable; float FixedDeltaTimeValue; + float MaxUpdateDeltaTime = 0.1f; } bool Time::_gamePaused = false; +float Time::_physicsMaxDeltaTime = 0.1f; DateTime Time::StartupTime; float Time::UpdateFPS = 30.0f; float Time::PhysicsFPS = 60.0f; @@ -43,6 +45,24 @@ public: TimeService TimeServiceInstance; +void TimeSettings::Apply() +{ + Time::UpdateFPS = UpdateFPS; + Time::PhysicsFPS = PhysicsFPS; + Time::DrawFPS = DrawFPS; + Time::TimeScale = TimeScale; + ::MaxUpdateDeltaTime = MaxUpdateDeltaTime; +} + +void TimeSettings::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) +{ + DESERIALIZE(UpdateFPS); + DESERIALIZE(PhysicsFPS); + DESERIALIZE(DrawFPS); + DESERIALIZE(TimeScale); + DESERIALIZE(MaxUpdateDeltaTime); +} + void Time::TickData::OnBeforeRun(float targetFps, double currentTime) { Time = UnscaledTime = TimeSpan::Zero(); @@ -219,7 +239,7 @@ void Time::OnBeforeRun() bool Time::OnBeginUpdate() { - if (Update.OnTickBegin(UpdateFPS, TimeSettings::Instance()->MaxUpdateDeltaTime)) + if (Update.OnTickBegin(UpdateFPS, MaxUpdateDeltaTime)) { Current = &Update; return true; @@ -229,7 +249,7 @@ bool Time::OnBeginUpdate() bool Time::OnBeginPhysics() { - if (Physics.OnTickBegin(PhysicsFPS, PhysicsSettings::Instance()->MaxDeltaTime)) + if (Physics.OnTickBegin(PhysicsFPS, _physicsMaxDeltaTime)) { Current = &Physics; return true; diff --git a/Source/Engine/Engine/Time.h b/Source/Engine/Engine/Time.h index 0e8c25806..0d44afa22 100644 --- a/Source/Engine/Engine/Time.h +++ b/Source/Engine/Engine/Time.h @@ -15,6 +15,7 @@ API_CLASS(Static) class FLAXENGINE_API Time DECLARE_SCRIPTING_TYPE_NO_SPAWN(Time); friend class Engine; friend class TimeService; + friend class PhysicsSettings; public: /// @@ -100,6 +101,7 @@ public: private: static bool _gamePaused; + static float _physicsMaxDeltaTime; public: diff --git a/Source/Engine/Engine/Windows/WindowsGame.cpp b/Source/Engine/Engine/Windows/WindowsGame.cpp index da82cc7fc..4b7496481 100644 --- a/Source/Engine/Engine/Windows/WindowsGame.cpp +++ b/Source/Engine/Engine/Windows/WindowsGame.cpp @@ -11,7 +11,7 @@ void WindowsGame::InitMainWindowSettings(CreateWindowSettings& settings) { // TODO: restore window size and fullscreen mode from the cached local settings saved after previous session - const auto platformSettings = WindowsPlatformSettings::Instance(); + const auto platformSettings = WindowsPlatformSettings::Get(); auto windowMode = platformSettings->WindowMode; // Use command line switches @@ -45,7 +45,7 @@ void WindowsGame::InitMainWindowSettings(CreateWindowSettings& settings) bool WindowsGame::Init() { - const auto platformSettings = WindowsPlatformSettings::Instance(); + const auto platformSettings = WindowsPlatformSettings::Get(); // Create mutex if need to if (platformSettings->ForceSingleInstance) diff --git a/Source/Engine/Foliage/Foliage.cpp b/Source/Engine/Foliage/Foliage.cpp index dcfd50212..c38191adf 100644 --- a/Source/Engine/Foliage/Foliage.cpp +++ b/Source/Engine/Foliage/Foliage.cpp @@ -201,7 +201,7 @@ void Foliage::RemoveFoliageType(int32 index) FoliageTypes[i].Index--; } auto& item = FoliageTypes[index]; - item.Model.Unlink(); + item.Model = nullptr; item.Entries.Release(); FoliageTypes.RemoveAtKeepOrder(index); diff --git a/Source/Engine/Graphics/GPUDevice.cpp b/Source/Engine/Graphics/GPUDevice.cpp index 47ec6d3a6..51d92cd3f 100644 --- a/Source/Engine/Graphics/GPUDevice.cpp +++ b/Source/Engine/Graphics/GPUDevice.cpp @@ -13,11 +13,12 @@ #include "Engine/Platform/Windows/WindowsWindow.h" #include "Engine/Render2D/Render2D.h" #include "Engine/Engine/CommandLine.h" +#include "Engine/Engine/Engine.h" #include "Engine/Engine/EngineService.h" #include "Engine/Profiler/Profiler.h" #include "Engine/Renderer/RenderList.h" -#include "Engine/Engine/Engine.h" #include "Engine/Core/Utilities.h" +#include "Engine/Scripting/SoftObjectReference.h" GPUPipelineState* GPUPipelineState::Spawn(const SpawnParams& params) { @@ -127,6 +128,7 @@ struct GPUDevice::PrivateData GPUPipelineState* PS_Clear = nullptr; GPUBuffer* FullscreenTriangleVB = nullptr; AssetReference DefaultMaterial; + SoftObjectReference DefaultDeformableMaterial; AssetReference DefaultNormalMap; AssetReference DefaultWhiteTexture; AssetReference DefaultBlackTexture; @@ -206,6 +208,7 @@ bool GPUDevice::LoadContent() _res->DefaultMaterial = Content::LoadAsyncInternal(TEXT("Engine/DefaultMaterial")); if (_res->DefaultMaterial == nullptr) return true; + _res->DefaultDeformableMaterial = Guid(0x639e12c0, 0x42d34bae, 0x89dd8b81, 0x7e1efc2d); // Load default normal map _res->DefaultNormalMap = Content::LoadAsyncInternal(TEXT("Engine/Textures/NormalTexture")); @@ -230,10 +233,11 @@ void GPUDevice::preDispose() RenderTargetPool::Flush(); // Release resources - _res->DefaultMaterial.Unlink(); - _res->DefaultNormalMap.Unlink(); - _res->DefaultWhiteTexture.Unlink(); - _res->DefaultBlackTexture.Unlink(); + _res->DefaultMaterial = nullptr; + _res->DefaultDeformableMaterial = nullptr; + _res->DefaultNormalMap = nullptr; + _res->DefaultWhiteTexture = nullptr; + _res->DefaultBlackTexture = nullptr; SAFE_DELETE_GPU_RESOURCE(_res->PS_CopyLinear); SAFE_DELETE_GPU_RESOURCE(_res->PS_Clear); SAFE_DELETE_GPU_RESOURCE(_res->FullscreenTriangleVB); @@ -380,6 +384,11 @@ MaterialBase* GPUDevice::GetDefaultMaterial() const return _res->DefaultMaterial; } +MaterialBase* GPUDevice::GetDefaultDeformableMaterial() const +{ + return _res->DefaultDeformableMaterial.Get(); +} + GPUTexture* GPUDevice::GetDefaultNormalMap() const { return _res->DefaultNormalMap ? _res->DefaultNormalMap->GetTexture() : nullptr; diff --git a/Source/Engine/Graphics/GPUDevice.h b/Source/Engine/Graphics/GPUDevice.h index 0b5fb46b9..e1deb16cc 100644 --- a/Source/Engine/Graphics/GPUDevice.h +++ b/Source/Engine/Graphics/GPUDevice.h @@ -234,6 +234,11 @@ public: /// MaterialBase* GetDefaultMaterial() const; + /// + /// Gets the default material (Deformable domain). + /// + MaterialBase* GetDefaultDeformableMaterial() const; + /// /// Gets the default normal map texture. /// diff --git a/Source/Engine/Graphics/GPUResourceProperty.h b/Source/Engine/Graphics/GPUResourceProperty.h index 5ad31439d..49e3c7b9b 100644 --- a/Source/Engine/Graphics/GPUResourceProperty.h +++ b/Source/Engine/Graphics/GPUResourceProperty.h @@ -75,12 +75,8 @@ public: GPUResourceProperty& operator=(const GPUResourceProperty& other) { - // Protect against invalid self-assignment if (this != &other) - { Set(other.Get()); - } - return *this; } @@ -158,7 +154,6 @@ public: /// Value to assign void Set(T* value) { - // Check if value will change if (_resource != value) { // Remove reference from the old one @@ -179,7 +174,6 @@ public: /// void Unlink() { - // Check if value will change if (_resource) { // Remove reference from the old one @@ -194,10 +188,7 @@ private: { if (_resource) { - // Unlink _resource = nullptr; - - // Fire event OnUnload(this); } } diff --git a/Source/Engine/Graphics/Materials/DecalMaterialShader.cpp b/Source/Engine/Graphics/Materials/DecalMaterialShader.cpp index c5166a088..d8d68e912 100644 --- a/Source/Engine/Graphics/Materials/DecalMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/DecalMaterialShader.cpp @@ -37,14 +37,16 @@ void DecalMaterialShader::Bind(BindParameters& params) auto context = params.GPUContext; auto& view = params.RenderContext.View; auto& drawCall = *params.FirstDrawCall; - const auto cb0 = _shader->GetCB(0); - const bool hasCb0 = cb0->GetSize() != 0; + byte* cb = _cbData.Get(); + auto materialData = reinterpret_cast(cb); + cb += sizeof(DecalMaterialShaderData); + int32 srv = 0; const bool isCameraInside = OrientedBoundingBox(Vector3::Half, params.FirstDrawCall->World).Contains(view.Position) == ContainmentType::Contains; // Setup parameters MaterialParameter::BindMeta bindMeta; bindMeta.Context = context; - bindMeta.Buffer0 = hasCb0 ? _cb0Data.Get() + sizeof(DecalMaterialShaderData) : nullptr; + bindMeta.Constants = cb; bindMeta.Input = nullptr; bindMeta.Buffers = nullptr; bindMeta.CanSampleDepth = true; @@ -54,11 +56,8 @@ void DecalMaterialShader::Bind(BindParameters& params) // Decals use depth buffer to draw on top of the objects context->BindSR(0, GET_TEXTURE_VIEW_SAFE(params.RenderContext.Buffers->DepthBuffer)); - // Setup material constants data - if (hasCb0) + // Setup material constants { - const auto materialData = reinterpret_cast(_cb0Data.Get()); - Matrix::Transpose(view.Frustum.GetMatrix(), materialData->ViewProjectionMatrix); Matrix::Transpose(drawCall.World, materialData->WorldMatrix); Matrix::Transpose(view.View, materialData->ViewMatrix); @@ -85,10 +84,10 @@ void DecalMaterialShader::Bind(BindParameters& params) } // Bind constants - if (hasCb0) + if (_cb) { - context->UpdateCB(cb0, _cb0Data.Get()); - context->BindCB(0, cb0); + context->UpdateCB(_cb, _cbData.Get()); + context->BindCB(0, _cb); } // Bind pipeline diff --git a/Source/Engine/Graphics/Materials/DeferredMaterialShader.cpp b/Source/Engine/Graphics/Materials/DeferredMaterialShader.cpp index 049484459..babfaf830 100644 --- a/Source/Engine/Graphics/Materials/DeferredMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/DeferredMaterialShader.cpp @@ -1,10 +1,12 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. #include "DeferredMaterialShader.h" +#include "MaterialShaderFeatures.h" #include "MaterialParams.h" #include "Engine/Graphics/RenderBuffers.h" #include "Engine/Graphics/RenderView.h" #include "Engine/Renderer/DrawCall.h" +#include "Engine/Renderer/RenderList.h" #include "Engine/Level/Scene/Lightmap.h" #include "Engine/Graphics/GPUContext.h" #include "Engine/Graphics/Shaders/GPUConstantBuffer.h" @@ -27,7 +29,6 @@ PACK_STRUCT(struct DeferredMaterialShaderData { float TimeParam; Vector4 ViewInfo; Vector4 ScreenSize; - Rectangle LightmapArea; Vector3 WorldInvScale; float WorldDeterminantSign; Vector2 Dummy0; @@ -48,8 +49,9 @@ bool DeferredMaterialShader::CanUseLightmap() const return true; } -bool DeferredMaterialShader::CanUseInstancing() const +bool DeferredMaterialShader::CanUseInstancing(InstancingHandler& handler) const { + handler = { SurfaceDrawCallHandler::GetHash, SurfaceDrawCallHandler::CanBatch, SurfaceDrawCallHandler::WriteDrawCall, }; return true; } @@ -59,90 +61,71 @@ void DeferredMaterialShader::Bind(BindParameters& params) auto context = params.GPUContext; auto& view = params.RenderContext.View; auto& drawCall = *params.FirstDrawCall; - const auto cb0 = _shader->GetCB(0); - const bool hasCb0 = cb0 && cb0->GetSize() != 0; + byte* cb = _cbData.Get(); + auto materialData = reinterpret_cast(cb); + cb += sizeof(DeferredMaterialShaderData); + int32 srv = 2; + + // Setup features + const bool useLightmap = _info.BlendMode == MaterialBlendMode::Opaque && LightmapFeature::Bind(params, cb, srv); // Setup parameters MaterialParameter::BindMeta bindMeta; bindMeta.Context = context; - bindMeta.Buffer0 = hasCb0 ? _cb0Data.Get() + sizeof(DeferredMaterialShaderData) : nullptr; + bindMeta.Constants = cb; bindMeta.Input = nullptr; bindMeta.Buffers = nullptr; bindMeta.CanSampleDepth = false; bindMeta.CanSampleGBuffer = false; MaterialParams::Bind(params.ParamsLink, bindMeta); - // Setup material constants data - auto materialData = reinterpret_cast(_cb0Data.Get()); - if (hasCb0) + // Setup material constants { Matrix::Transpose(view.Frustum.GetMatrix(), materialData->ViewProjectionMatrix); Matrix::Transpose(drawCall.World, materialData->WorldMatrix); Matrix::Transpose(view.View, materialData->ViewMatrix); - Matrix::Transpose(drawCall.PrevWorld, materialData->PrevWorldMatrix); + Matrix::Transpose(drawCall.Surface.PrevWorld, materialData->PrevWorldMatrix); Matrix::Transpose(view.PrevViewProjection, materialData->PrevViewProjectionMatrix); - materialData->ViewPos = view.Position; materialData->ViewFar = view.Far; materialData->ViewDir = view.Direction; materialData->TimeParam = Time::Draw.UnscaledTime.GetTotalSeconds(); materialData->ViewInfo = view.ViewInfo; materialData->ScreenSize = view.ScreenSize; - - // Extract per axis scales from LocalToWorld transform const float scaleX = Vector3(drawCall.World.M11, drawCall.World.M12, drawCall.World.M13).Length(); const float scaleY = Vector3(drawCall.World.M21, drawCall.World.M22, drawCall.World.M23).Length(); const float scaleZ = Vector3(drawCall.World.M31, drawCall.World.M32, drawCall.World.M33).Length(); - const Vector3 worldInvScale = Vector3( + materialData->WorldInvScale = Vector3( scaleX > 0.00001f ? 1.0f / scaleX : 0.0f, scaleY > 0.00001f ? 1.0f / scaleY : 0.0f, scaleZ > 0.00001f ? 1.0f / scaleZ : 0.0f); - - materialData->WorldInvScale = worldInvScale; materialData->WorldDeterminantSign = drawCall.WorldDeterminantSign; - materialData->LODDitherFactor = drawCall.LODDitherFactor; + materialData->LODDitherFactor = drawCall.Surface.LODDitherFactor; materialData->PerInstanceRandom = drawCall.PerInstanceRandom; materialData->TemporalAAJitter = view.TemporalAAJitter; - materialData->GeometrySize = drawCall.GeometrySize; - } - const bool useLightmap = view.Flags & ViewFlags::GI -#if USE_EDITOR - && EnableLightmapsUsage -#endif - && drawCall.Lightmap != nullptr; - if (useLightmap) - { - // Bind lightmap textures - GPUTexture *lightmap0, *lightmap1, *lightmap2; - drawCall.Lightmap->GetTextures(&lightmap0, &lightmap1, &lightmap2); - context->BindSR(0, lightmap0); - context->BindSR(1, lightmap1); - context->BindSR(2, lightmap2); - - // Set lightmap data - materialData->LightmapArea = drawCall.LightmapUVsArea; + materialData->GeometrySize = drawCall.Surface.GeometrySize; } // Check if is using mesh skinning - const bool useSkinning = drawCall.Skinning != nullptr; + const bool useSkinning = drawCall.Surface.Skinning != nullptr; bool perBoneMotionBlur = false; if (useSkinning) { // Bind skinning buffer - ASSERT(drawCall.Skinning->IsReady()); - context->BindSR(0, drawCall.Skinning->BoneMatrices->View()); - if (drawCall.Skinning->PrevBoneMatrices && drawCall.Skinning->PrevBoneMatrices->IsAllocated()) + ASSERT(drawCall.Surface.Skinning->IsReady()); + context->BindSR(0, drawCall.Surface.Skinning->BoneMatrices->View()); + if (drawCall.Surface.Skinning->PrevBoneMatrices && drawCall.Surface.Skinning->PrevBoneMatrices->IsAllocated()) { - context->BindSR(1, drawCall.Skinning->PrevBoneMatrices->View()); + context->BindSR(1, drawCall.Surface.Skinning->PrevBoneMatrices->View()); perBoneMotionBlur = true; } } // Bind constants - if (hasCb0) + if (_cb) { - context->UpdateCB(cb0, _cb0Data.Get()); - context->BindCB(0, cb0); + context->UpdateCB(_cb, _cbData.Get()); + context->BindCB(0, _cb); } // Select pipeline state based on current pass and render mode @@ -152,7 +135,7 @@ void DeferredMaterialShader::Bind(BindParameters& params) if (IsRunningRadiancePass) cullMode = CullMode::TwoSided; #endif - if (cullMode != CullMode::TwoSided && drawCall.IsNegativeScale()) + if (cullMode != CullMode::TwoSided && drawCall.WorldDeterminantSign < 0) { // Invert culling when scale is negative if (cullMode == CullMode::Normal) @@ -200,7 +183,7 @@ bool DeferredMaterialShader::Load() psDesc.VS = _shader->GetVS("VS", 1); _cacheInstanced.Default.Init(psDesc); - // GBuffer Pass with lightmap (use pixel shader permutation for USE_LIGHTMAP=1) + // GBuffer Pass with lightmap (pixel shader permutation for USE_LIGHTMAP=1) psDesc.VS = _shader->GetVS("VS"); psDesc.PS = _shader->GetPS("PS_GBuffer", 1); _cache.DefaultLightmap.Init(psDesc); @@ -216,21 +199,16 @@ bool DeferredMaterialShader::Load() psDesc.DepthWriteEnable = false; psDesc.DepthTestEnable = true; psDesc.DepthFunc = ComparisonFunc::LessEqual; - if (useTess) - { - psDesc.HS = _shader->GetHS("HS", 1); - psDesc.DS = _shader->GetDS("DS", 1); - } - psDesc.VS = _shader->GetVS("VS", 2); + psDesc.VS = _shader->GetVS("VS"); psDesc.PS = _shader->GetPS("PS_MotionVectors"); _cache.MotionVectors.Init(psDesc); // Motion Vectors pass with skinning - psDesc.VS = _shader->GetVS("VS_Skinned", 1); + psDesc.VS = _shader->GetVS("VS_Skinned"); _cache.MotionVectorsSkinned.Init(psDesc); // Motion Vectors pass with skinning (with per-bone motion blur) - psDesc.VS = _shader->GetVS("VS_Skinned", 2); + psDesc.VS = _shader->GetVS("VS_Skinned", 1); _cache.MotionVectorsSkinnedPerBone.Init(psDesc); // Depth Pass diff --git a/Source/Engine/Graphics/Materials/DeferredMaterialShader.h b/Source/Engine/Graphics/Materials/DeferredMaterialShader.h index f8df968c9..537547c6a 100644 --- a/Source/Engine/Graphics/Materials/DeferredMaterialShader.h +++ b/Source/Engine/Graphics/Materials/DeferredMaterialShader.h @@ -66,7 +66,7 @@ public: // [MaterialShader] DrawPass GetDrawModes() const override; bool CanUseLightmap() const override; - bool CanUseInstancing() const override; + bool CanUseInstancing(InstancingHandler& handler) const override; void Bind(BindParameters& params) override; void Unload() override; diff --git a/Source/Engine/Graphics/Materials/DeformableMaterialShader.cpp b/Source/Engine/Graphics/Materials/DeformableMaterialShader.cpp new file mode 100644 index 000000000..dafe13d46 --- /dev/null +++ b/Source/Engine/Graphics/Materials/DeformableMaterialShader.cpp @@ -0,0 +1,188 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#include "DeformableMaterialShader.h" +#include "MaterialShaderFeatures.h" +#include "MaterialParams.h" +#include "Engine/Graphics/RenderBuffers.h" +#include "Engine/Graphics/RenderView.h" +#include "Engine/Renderer/DrawCall.h" +#include "Engine/Renderer/RenderList.h" +#include "Engine/Graphics/GPUContext.h" +#include "Engine/Graphics/Shaders/GPUConstantBuffer.h" +#include "Engine/Graphics/GPUDevice.h" +#include "Engine/Graphics/Shaders/GPUShader.h" +#include "Engine/Graphics/GPULimits.h" +#include "Engine/Engine/Time.h" +#include "Engine/Graphics/RenderTask.h" + +PACK_STRUCT(struct DeformableMaterialShaderData { + Matrix ViewProjectionMatrix; + Matrix WorldMatrix; + Matrix LocalMatrix; + Matrix ViewMatrix; + Vector3 ViewPos; + float ViewFar; + Vector3 ViewDir; + float TimeParam; + Vector4 ViewInfo; + Vector4 ScreenSize; + Vector3 Dummy0; + float WorldDeterminantSign; + float MeshMinZ; + float Segment; + float ChunksPerSegment; + float PerInstanceRandom; + Vector4 TemporalAAJitter; + Vector3 GeometrySize; + float MeshMaxZ; + }); + +DrawPass DeformableMaterialShader::GetDrawModes() const +{ + return _drawModes; +} + +void DeformableMaterialShader::Bind(BindParameters& params) +{ + // Prepare + auto context = params.GPUContext; + auto& view = params.RenderContext.View; + auto& drawCall = *params.FirstDrawCall; + byte* cb = _cbData.Get(); + auto materialData = reinterpret_cast(cb); + cb += sizeof(DeformableMaterialShaderData); + int32 srv = 1; + + // Setup features + if (_info.BlendMode != MaterialBlendMode::Opaque) + ForwardShadingFeature::Bind(params, cb, srv); + + // Setup parameters + MaterialParameter::BindMeta bindMeta; + bindMeta.Context = context; + bindMeta.Constants = cb; + bindMeta.Input = nullptr; + bindMeta.Buffers = nullptr; + bindMeta.CanSampleDepth = false; + bindMeta.CanSampleGBuffer = false; + MaterialParams::Bind(params.ParamsLink, bindMeta); + + // Setup material constants + { + Matrix::Transpose(view.Frustum.GetMatrix(), materialData->ViewProjectionMatrix); + Matrix::Transpose(drawCall.World, materialData->WorldMatrix); + Matrix::Transpose(drawCall.Deformable.LocalMatrix, materialData->LocalMatrix); + Matrix::Transpose(view.View, materialData->ViewMatrix); + materialData->ViewPos = view.Position; + materialData->ViewFar = view.Far; + materialData->ViewDir = view.Direction; + materialData->TimeParam = Time::Draw.UnscaledTime.GetTotalSeconds(); + materialData->ViewInfo = view.ViewInfo; + materialData->ScreenSize = view.ScreenSize; + materialData->WorldDeterminantSign = drawCall.WorldDeterminantSign; + materialData->Segment = drawCall.Deformable.Segment; + materialData->ChunksPerSegment = drawCall.Deformable.ChunksPerSegment; + materialData->MeshMinZ = drawCall.Deformable.MeshMinZ; + materialData->MeshMaxZ = drawCall.Deformable.MeshMaxZ; + materialData->PerInstanceRandom = drawCall.PerInstanceRandom; + materialData->TemporalAAJitter = view.TemporalAAJitter; + materialData->GeometrySize = drawCall.Deformable.GeometrySize; + } + + // Bind spline deformation buffer + context->BindSR(0, drawCall.Deformable.SplineDeformation->View()); + + // Bind constants + if (_cb) + { + context->UpdateCB(_cb, _cbData.Get()); + context->BindCB(0, _cb); + } + + // Select pipeline state based on current pass and render mode + const bool wireframe = (_info.FeaturesFlags & MaterialFeaturesFlags::Wireframe) != 0 || view.Mode == ViewMode::Wireframe; + CullMode cullMode = view.Pass == DrawPass::Depth ? CullMode::TwoSided : _info.CullMode; + if (cullMode != CullMode::TwoSided && drawCall.WorldDeterminantSign < 0) + { + // Invert culling when scale is negative + if (cullMode == CullMode::Normal) + cullMode = CullMode::Inverted; + else + cullMode = CullMode::Normal; + } + PipelineStateCache* psCache = _cache.GetPS(view.Pass); + ASSERT(psCache); + GPUPipelineState* state = psCache->GetPS(cullMode, wireframe); + + // Bind pipeline + context->SetState(state); +} + +void DeformableMaterialShader::Unload() +{ + // Base + MaterialShader::Unload(); + + _cache.Release(); +} + +bool DeformableMaterialShader::Load() +{ + _drawModes = DrawPass::Depth; + auto psDesc = GPUPipelineState::Description::Default; + psDesc.DepthTestEnable = (_info.FeaturesFlags & MaterialFeaturesFlags::DisableDepthTest) == 0; + psDesc.DepthWriteEnable = (_info.FeaturesFlags & MaterialFeaturesFlags::DisableDepthWrite) == 0; + + // Check if use tessellation (both material and runtime supports it) + const bool useTess = _info.TessellationMode != TessellationMethod::None && GPUDevice::Instance->Limits.HasTessellation; + if (useTess) + { + psDesc.HS = _shader->GetHS("HS"); + psDesc.DS = _shader->GetDS("DS"); + } + + if (_info.BlendMode == MaterialBlendMode::Opaque) + { + _drawModes = DrawPass::GBuffer; + + // GBuffer Pass + psDesc.VS = _shader->GetVS("VS_SplineModel"); + psDesc.PS = _shader->GetPS("PS_GBuffer"); + _cache.Default.Init(psDesc); + } + else + { + _drawModes = DrawPass::Forward; + + // Forward Pass + psDesc.VS = _shader->GetVS("VS_SplineModel"); + psDesc.PS = _shader->GetPS("PS_Forward"); + psDesc.DepthWriteEnable = false; + psDesc.BlendMode = BlendingMode::AlphaBlend; + switch (_info.BlendMode) + { + case MaterialBlendMode::Transparent: + psDesc.BlendMode = BlendingMode::AlphaBlend; + break; + case MaterialBlendMode::Additive: + psDesc.BlendMode = BlendingMode::Additive; + break; + case MaterialBlendMode::Multiply: + psDesc.BlendMode = BlendingMode::Multiply; + break; + } + _cache.Default.Init(psDesc); + } + + // Depth Pass + psDesc.CullMode = CullMode::TwoSided; + psDesc.DepthClipEnable = false; + psDesc.DepthWriteEnable = true; + psDesc.DepthTestEnable = true; + psDesc.DepthFunc = ComparisonFunc::Less; + psDesc.HS = nullptr; + psDesc.DS = nullptr; + _cache.Depth.Init(psDesc); + + return false; +} diff --git a/Source/Engine/Graphics/Materials/DeformableMaterialShader.h b/Source/Engine/Graphics/Materials/DeformableMaterialShader.h new file mode 100644 index 000000000..f2c796a23 --- /dev/null +++ b/Source/Engine/Graphics/Materials/DeformableMaterialShader.h @@ -0,0 +1,63 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#include "MaterialShader.h" + +/// +/// Represents material that can be used to render objects that can be deformed. +/// +class DeformableMaterialShader : public MaterialShader +{ +private: + + struct Cache + { + PipelineStateCache Default; + PipelineStateCache Depth; + + FORCE_INLINE PipelineStateCache* GetPS(const DrawPass pass) + { + switch (pass) + { + case DrawPass::Depth: + return &Depth; + case DrawPass::GBuffer: + case DrawPass::Forward: + return &Default; + default: + return nullptr; + } + } + + FORCE_INLINE void Release() + { + Default.Release(); + Depth.Release(); + } + }; + +private: + + Cache _cache; + DrawPass _drawModes = DrawPass::None; + +public: + + DeformableMaterialShader(const String& name) + : MaterialShader(name) + { + } + +public: + + // [MaterialShader] + DrawPass GetDrawModes() const override; + void Bind(BindParameters& params) override; + void Unload() override; + +protected: + + // [MaterialShader] + bool Load() override; +}; diff --git a/Source/Engine/Graphics/Materials/ForwardMaterialShader.cpp b/Source/Engine/Graphics/Materials/ForwardMaterialShader.cpp index 863af34e1..2ea17c9cd 100644 --- a/Source/Engine/Graphics/Materials/ForwardMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/ForwardMaterialShader.cpp @@ -1,15 +1,18 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. #include "ForwardMaterialShader.h" +#include "MaterialShaderFeatures.h" #include "MaterialParams.h" #include "Engine/Engine/Time.h" +#include "Engine/Graphics/GPUDevice.h" #include "Engine/Graphics/GPULimits.h" -#include "Engine/Graphics/Models/SkinnedMeshDrawData.h" #include "Engine/Graphics/RenderView.h" -#include "Engine/Level/Actors/EnvironmentProbe.h" -#include "Engine/Renderer/DepthOfFieldPass.h" +#include "Engine/Graphics/RenderTask.h" +#include "Engine/Graphics/Models/SkinnedMeshDrawData.h" +#include "Engine/Graphics/Shaders/GPUConstantBuffer.h" +#include "Engine/Graphics/Shaders/GPUShader.h" #include "Engine/Renderer/DrawCall.h" -#include "Engine/Renderer/ShadowsPass.h" +#include "Engine/Renderer/RenderList.h" #if USE_EDITOR #include "Engine/Renderer/Lightmaps.h" #endif @@ -28,34 +31,24 @@ PACK_STRUCT(struct ForwardMaterialShaderData { float TimeParam; Vector4 ViewInfo; Vector4 ScreenSize; - Rectangle LightmapArea; Vector3 WorldInvScale; float WorldDeterminantSign; Vector2 Dummy0; float LODDitherFactor; float PerInstanceRandom; + Vector4 TemporalAAJitter; Vector3 GeometrySize; float Dummy1; }); -PACK_STRUCT(struct ForwardMaterialShaderLightingData { - LightData DirectionalLight; - LightShadowData DirectionalLightShadow; - LightData SkyLight; - ProbeData EnvironmentProbe; - ExponentialHeightFogData ExponentialHeightFog; - Vector3 Dummy2; - uint32 LocalLightsCount; - LightData LocalLights[MAX_LOCAL_LIGHTS]; - }); - DrawPass ForwardMaterialShader::GetDrawModes() const { return _drawModes; } -bool ForwardMaterialShader::CanUseInstancing() const +bool ForwardMaterialShader::CanUseInstancing(InstancingHandler& handler) const { + handler = { SurfaceDrawCallHandler::GetHash, SurfaceDrawCallHandler::CanBatch, SurfaceDrawCallHandler::WriteDrawCall, }; return true; } @@ -64,17 +57,19 @@ void ForwardMaterialShader::Bind(BindParameters& params) // Prepare auto context = params.GPUContext; auto& view = params.RenderContext.View; - auto cache = params.RenderContext.List; auto& drawCall = *params.FirstDrawCall; - const auto cb0 = _shader->GetCB(0); - const bool hasCb0 = cb0 && cb0->GetSize() != 0; - const auto cb1 = _shader->GetCB(1); - const bool hasCb1 = cb1 && cb1->GetSize() != 0; + byte* cb = _cbData.Get(); + auto materialData = reinterpret_cast(cb); + cb += sizeof(ForwardMaterialShaderData); + int32 srv = 2; + + // Setup features + ForwardShadingFeature::Bind(params, cb, srv); // Setup parameters MaterialParameter::BindMeta bindMeta; bindMeta.Context = context; - bindMeta.Buffer0 = hasCb0 ? _cb0Data.Get() + sizeof(ForwardMaterialShaderData) : nullptr; + bindMeta.Constants = cb; bindMeta.Input = nullptr; // forward pass materials cannot sample scene color for now bindMeta.Buffers = params.RenderContext.Buffers; bindMeta.CanSampleDepth = GPUDevice::Instance->Limits.HasReadOnlyDepth; @@ -82,164 +77,45 @@ void ForwardMaterialShader::Bind(BindParameters& params) MaterialParams::Bind(params.ParamsLink, bindMeta); // Check if is using mesh skinning - const bool useSkinning = drawCall.Skinning != nullptr; + const bool useSkinning = drawCall.Surface.Skinning != nullptr; if (useSkinning) { // Bind skinning buffer - ASSERT(drawCall.Skinning->IsReady()); - context->BindSR(0, drawCall.Skinning->BoneMatrices->View()); + ASSERT(drawCall.Surface.Skinning->IsReady()); + context->BindSR(0, drawCall.Surface.Skinning->BoneMatrices->View()); } - // Setup material constants data - const auto materialData = reinterpret_cast(_cb0Data.Get()); - if (hasCb0) + // Setup material constants { Matrix::Transpose(view.Frustum.GetMatrix(), materialData->ViewProjectionMatrix); Matrix::Transpose(drawCall.World, materialData->WorldMatrix); Matrix::Transpose(view.View, materialData->ViewMatrix); - Matrix::Transpose(drawCall.PrevWorld, materialData->PrevWorldMatrix); + Matrix::Transpose(drawCall.Surface.PrevWorld, materialData->PrevWorldMatrix); Matrix::Transpose(view.PrevViewProjection, materialData->PrevViewProjectionMatrix); - materialData->ViewPos = view.Position; materialData->ViewFar = view.Far; materialData->ViewDir = view.Direction; materialData->TimeParam = Time::Draw.UnscaledTime.GetTotalSeconds(); materialData->ViewInfo = view.ViewInfo; materialData->ScreenSize = view.ScreenSize; - - // Extract per axis scales from LocalToWorld transform const float scaleX = Vector3(drawCall.World.M11, drawCall.World.M12, drawCall.World.M13).Length(); const float scaleY = Vector3(drawCall.World.M21, drawCall.World.M22, drawCall.World.M23).Length(); const float scaleZ = Vector3(drawCall.World.M31, drawCall.World.M32, drawCall.World.M33).Length(); - const Vector3 worldInvScale = Vector3( + materialData->WorldInvScale = Vector3( scaleX > 0.00001f ? 1.0f / scaleX : 0.0f, scaleY > 0.00001f ? 1.0f / scaleY : 0.0f, scaleZ > 0.00001f ? 1.0f / scaleZ : 0.0f); - - materialData->WorldInvScale = worldInvScale; materialData->WorldDeterminantSign = drawCall.WorldDeterminantSign; - materialData->LODDitherFactor = drawCall.LODDitherFactor; + materialData->LODDitherFactor = drawCall.Surface.LODDitherFactor; materialData->PerInstanceRandom = drawCall.PerInstanceRandom; - materialData->GeometrySize = drawCall.GeometrySize; - } - - // Setup lighting constants data - if (hasCb1) - { - auto& lightingData = *reinterpret_cast(_cb1Data.Get()); - const int32 envProbeShaderRegisterIndex = 0; - const int32 skyLightShaderRegisterIndex = 1; - const int32 dirLightShaderRegisterIndex = 2; - - // Set fog input - if (cache->Fog) - { - cache->Fog->GetExponentialHeightFogData(view, lightingData.ExponentialHeightFog); - } - else - { - lightingData.ExponentialHeightFog.FogMinOpacity = 1.0f; - lightingData.ExponentialHeightFog.ApplyDirectionalInscattering = 0.0f; - } - - // Set directional light input - if (cache->DirectionalLights.HasItems()) - { - const auto& dirLight = cache->DirectionalLights.First(); - const auto shadowPass = ShadowsPass::Instance(); - const bool useShadow = shadowPass->LastDirLightIndex == 0; - if (useShadow) - { - lightingData.DirectionalLightShadow = shadowPass->LastDirLight; - context->BindSR(dirLightShaderRegisterIndex, shadowPass->LastDirLightShadowMap); - } - else - { - context->UnBindSR(dirLightShaderRegisterIndex); - } - dirLight.SetupLightData(&lightingData.DirectionalLight, view, useShadow); - } - else - { - lightingData.DirectionalLight.Color = Vector3::Zero; - lightingData.DirectionalLight.CastShadows = 0.0f; - context->UnBindSR(dirLightShaderRegisterIndex); - } - - // Set sky light - if (cache->SkyLights.HasItems()) - { - auto& skyLight = cache->SkyLights.First(); - skyLight.SetupLightData(&lightingData.SkyLight, view, false); - const auto texture = skyLight.Image ? skyLight.Image->GetTexture() : nullptr; - context->BindSR(skyLightShaderRegisterIndex, GET_TEXTURE_VIEW_SAFE(texture)); - } - else - { - Platform::MemoryClear(&lightingData.SkyLight, sizeof(lightingData.SkyLight)); - context->UnBindSR(skyLightShaderRegisterIndex); - } - - // Set reflection probe data - EnvironmentProbe* probe = nullptr; - // TODO: optimize env probe searching for a transparent material - use spatial cache for renderer to find it - for (int32 i = 0; i < cache->EnvironmentProbes.Count(); i++) - { - const auto p = cache->EnvironmentProbes[i]; - if (p->GetSphere().Contains(drawCall.World.GetTranslation()) != ContainmentType::Disjoint) - { - probe = p; - break; - } - } - if (probe && probe->GetProbe()) - { - probe->SetupProbeData(&lightingData.EnvironmentProbe); - const auto texture = probe->GetProbe()->GetTexture(); - context->BindSR(envProbeShaderRegisterIndex, GET_TEXTURE_VIEW_SAFE(texture)); - } - else - { - lightingData.EnvironmentProbe.Data1 = Vector4::Zero; - context->UnBindSR(envProbeShaderRegisterIndex); - } - - // Set local lights - lightingData.LocalLightsCount = 0; - for (int32 i = 0; i < cache->PointLights.Count(); i++) - { - const auto& light = cache->PointLights[i]; - if (BoundingSphere(light.Position, light.Radius).Contains(drawCall.World.GetTranslation()) != ContainmentType::Disjoint) - { - light.SetupLightData(&lightingData.LocalLights[lightingData.LocalLightsCount], view, false); - lightingData.LocalLightsCount++; - if (lightingData.LocalLightsCount == MAX_LOCAL_LIGHTS) - break; - } - } - for (int32 i = 0; i < cache->SpotLights.Count(); i++) - { - const auto& light = cache->SpotLights[i]; - if (BoundingSphere(light.Position, light.Radius).Contains(drawCall.World.GetTranslation()) != ContainmentType::Disjoint) - { - light.SetupLightData(&lightingData.LocalLights[lightingData.LocalLightsCount], view, false); - lightingData.LocalLightsCount++; - if (lightingData.LocalLightsCount == MAX_LOCAL_LIGHTS) - break; - } - } + materialData->GeometrySize = drawCall.Surface.GeometrySize; } // Bind constants - if (hasCb0) + if (_cb) { - context->UpdateCB(cb0, _cb0Data.Get()); - context->BindCB(0, cb0); - } - if (hasCb1) - { - context->UpdateCB(cb1, _cb1Data.Get()); - context->BindCB(1, cb1); + context->UpdateCB(_cb, _cbData.Get()); + context->BindCB(0, _cb); } // Select pipeline state based on current pass and render mode @@ -249,7 +125,7 @@ void ForwardMaterialShader::Bind(BindParameters& params) if (IsRunningRadiancePass) cullMode = CullMode::TwoSided; #endif - if (cullMode != CullMode::TwoSided && drawCall.IsNegativeScale()) + if (cullMode != CullMode::TwoSided && drawCall.WorldDeterminantSign < 0) { // Invert culling when scale is negative if (cullMode == CullMode::Normal) diff --git a/Source/Engine/Graphics/Materials/ForwardMaterialShader.h b/Source/Engine/Graphics/Materials/ForwardMaterialShader.h index d39fcea54..bcba48259 100644 --- a/Source/Engine/Graphics/Materials/ForwardMaterialShader.h +++ b/Source/Engine/Graphics/Materials/ForwardMaterialShader.h @@ -67,7 +67,7 @@ public: // [MaterialShader] DrawPass GetDrawModes() const override; - bool CanUseInstancing() const override; + bool CanUseInstancing(InstancingHandler& handler) const override; void Bind(BindParameters& params) override; void Unload() override; diff --git a/Source/Engine/Graphics/Materials/GUIMaterialShader.cpp b/Source/Engine/Graphics/Materials/GUIMaterialShader.cpp index 4b079dd54..769d36557 100644 --- a/Source/Engine/Graphics/Materials/GUIMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/GUIMaterialShader.cpp @@ -28,26 +28,24 @@ void GUIMaterialShader::Bind(BindParameters& params) { // Prepare auto context = params.GPUContext; - auto& view = params.RenderContext.View; - const auto cb0 = _shader->GetCB(0); - const bool hasCb0 = cb0->GetSize() != 0; + byte* cb = _cbData.Get(); + auto materialData = reinterpret_cast(cb); + cb += sizeof(GUIMaterialShaderData); + int32 srv = 0; const auto ps = context->IsDepthBufferBinded() ? _cache.Depth : _cache.NoDepth; // Setup parameters MaterialParameter::BindMeta bindMeta; bindMeta.Context = context; - bindMeta.Buffer0 = hasCb0 ? _cb0Data.Get() + sizeof(GUIMaterialShaderData) : nullptr; + bindMeta.Constants = cb; bindMeta.Input = nullptr; bindMeta.Buffers = nullptr; bindMeta.CanSampleDepth = false; bindMeta.CanSampleGBuffer = false; MaterialParams::Bind(params.ParamsLink, bindMeta); - // Setup material constants data - if (hasCb0) + // Setup material constants { - auto materialData = reinterpret_cast(_cb0Data.Get()); - const auto viewProjectionMatrix = (Matrix*)params.CustomData; Matrix::Transpose(*viewProjectionMatrix, materialData->ViewProjectionMatrix); Matrix::Transpose(Matrix::Identity, materialData->WorldMatrix); @@ -62,10 +60,10 @@ void GUIMaterialShader::Bind(BindParameters& params) } // Bind constants - if (hasCb0) + if (_cb) { - context->UpdateCB(cb0, _cb0Data.Get()); - context->BindCB(0, cb0); + context->UpdateCB(_cb, _cbData.Get()); + context->BindCB(0, _cb); } // Bind pipeline diff --git a/Source/Engine/Graphics/Materials/IMaterial.h b/Source/Engine/Graphics/Materials/IMaterial.h index 09796c04b..4d61656e8 100644 --- a/Source/Engine/Graphics/Materials/IMaterial.h +++ b/Source/Engine/Graphics/Materials/IMaterial.h @@ -2,7 +2,6 @@ #pragma once -#include "Engine/Threading/Task.h" #include "MaterialInfo.h" struct MaterialParamsLink; @@ -30,7 +29,6 @@ public: /// /// Determines whether material is a surface shader. /// - /// true if material is surface shader; otherwise, false. FORCE_INLINE bool IsSurface() const { return GetInfo().Domain == MaterialDomain::Surface; @@ -39,7 +37,6 @@ public: /// /// Determines whether material is a post fx. /// - /// true if material is post fx; otherwise, false. FORCE_INLINE bool IsPostFx() const { return GetInfo().Domain == MaterialDomain::PostProcess; @@ -48,7 +45,6 @@ public: /// /// Determines whether material is a decal. /// - /// true if material is decal; otherwise, false. FORCE_INLINE bool IsDecal() const { return GetInfo().Domain == MaterialDomain::Decal; @@ -57,7 +53,6 @@ public: /// /// Determines whether material is a GUI shader. /// - /// true if material is GUI shader; otherwise, false. FORCE_INLINE bool IsGUI() const { return GetInfo().Domain == MaterialDomain::GUI; @@ -66,7 +61,6 @@ public: /// /// Determines whether material is a terrain shader. /// - /// true if material is terrain shader; otherwise, false. FORCE_INLINE bool IsTerrain() const { return GetInfo().Domain == MaterialDomain::Terrain; @@ -75,28 +69,17 @@ public: /// /// Determines whether material is a particle shader. /// - /// true if material is particle shader; otherwise, false. FORCE_INLINE bool IsParticle() const { return GetInfo().Domain == MaterialDomain::Particle; } /// - /// Checks if material needs vertex color vertex buffer data for rendering. + /// Determines whether material is a deformable shader. /// - /// True if mesh renderer have to provide vertex color buffer to render that material - FORCE_INLINE bool RequireVertexColor() const + FORCE_INLINE bool IsDeformable() const { - return (GetInfo().UsageFlags & MaterialUsageFlags::UseVertexColor) != 0; - } - - /// - /// Checks if material supports dithered LOD transitions. - /// - /// True if material supports dithered LOD transitions, otherwise false. - FORCE_INLINE bool IsDitheredLODTransition() const - { - return (GetInfo().FeaturesFlags & MaterialFeaturesFlags::DitheredLODTransition) != 0; + return GetInfo().Domain == MaterialDomain::Deformable; } /// @@ -123,11 +106,22 @@ public: return false; } + /// + /// The instancing handling used to hash, batch and write draw calls. + /// + struct InstancingHandler + { + void (*GetHash)(const DrawCall& drawCall, int32& batchKey); + bool (*CanBatch)(const DrawCall& a, const DrawCall& b); + void (*WriteDrawCall)(struct InstanceData* instanceData, const DrawCall& drawCall); + }; + /// /// Returns true if material can use draw calls instancing. /// + /// The output data for the instancing handling used to hash, batch and write draw calls. Valid only when function returns true. /// True if can use instancing, otherwise false. - virtual bool CanUseInstancing() const + virtual bool CanUseInstancing(InstancingHandler& handler) const { return false; } diff --git a/Source/Engine/Graphics/Materials/MaterialInfo.h b/Source/Engine/Graphics/Materials/MaterialInfo.h index 50b4c1e35..862f8f221 100644 --- a/Source/Engine/Graphics/Materials/MaterialInfo.h +++ b/Source/Engine/Graphics/Materials/MaterialInfo.h @@ -39,6 +39,11 @@ API_ENUM() enum class MaterialDomain : byte /// The particle shader. Can be used only with particles geometry (sprites, trails and ribbons). Supports reading particle data on a GPU. /// Particle = 5, + + /// + /// The deformable shader. Can be used only with objects that can be deformed (spline models). + /// + Deformable = 6, }; /// diff --git a/Source/Engine/Graphics/Materials/MaterialParams.cpp b/Source/Engine/Graphics/Materials/MaterialParams.cpp index 1f1d1bac6..ea70d4587 100644 --- a/Source/Engine/Graphics/Materials/MaterialParams.cpp +++ b/Source/Engine/Graphics/Materials/MaterialParams.cpp @@ -230,36 +230,28 @@ void MaterialParameter::Bind(BindMeta& meta) const switch (_type) { case MaterialParameterType::Bool: - if (meta.Buffer0) - *((int32*)(meta.Buffer0 + _offset)) = _asBool; + *((int32*)(meta.Constants + _offset)) = _asBool; break; case MaterialParameterType::Integer: - if (meta.Buffer0) - *((int32*)(meta.Buffer0 + _offset)) = _asInteger; + *((int32*)(meta.Constants + _offset)) = _asInteger; break; case MaterialParameterType::Float: - if (meta.Buffer0) - *((float*)(meta.Buffer0 + _offset)) = _asFloat; + *((float*)(meta.Constants + _offset)) = _asFloat; break; case MaterialParameterType::Vector2: - if (meta.Buffer0) - *((Vector2*)(meta.Buffer0 + _offset)) = _asVector2; + *((Vector2*)(meta.Constants + _offset)) = _asVector2; break; case MaterialParameterType::Vector3: - if (meta.Buffer0) - *((Vector3*)(meta.Buffer0 + _offset)) = _asVector3; + *((Vector3*)(meta.Constants + _offset)) = _asVector3; break; case MaterialParameterType::Vector4: - if (meta.Buffer0) - *((Vector4*)(meta.Buffer0 + _offset)) = _asVector4; + *((Vector4*)(meta.Constants + _offset)) = _asVector4; break; case MaterialParameterType::Color: - if (meta.Buffer0) - *((Color*)(meta.Buffer0 + _offset)) = _asColor; + *((Color*)(meta.Constants + _offset)) = _asColor; break; case MaterialParameterType::Matrix: - if (meta.Buffer0) - Matrix::Transpose(_asMatrix, *(Matrix*)(meta.Buffer0 + _offset)); + Matrix::Transpose(_asMatrix, *(Matrix*)(meta.Constants + _offset)); break; case MaterialParameterType::NormalMap: { @@ -336,11 +328,10 @@ void MaterialParameter::Bind(BindMeta& meta) const break; } case MaterialParameterType::ChannelMask: - if (meta.Buffer0) - *((Vector4*)(meta.Buffer0 + _offset)) = Vector4(_asInteger == 0, _asInteger == 1, _asInteger == 2, _asInteger == 3); + *((Vector4*)(meta.Constants + _offset)) = Vector4(_asInteger == 0, _asInteger == 1, _asInteger == 2, _asInteger == 3); break; case MaterialParameterType::GameplayGlobal: - if (meta.Buffer0 && _asAsset) + if (_asAsset) { const auto e = _asAsset.As()->Variables.TryGet(_name); if (e) @@ -348,26 +339,26 @@ void MaterialParameter::Bind(BindMeta& meta) const switch (e->Value.Type.Type) { case VariantType::Bool: - *((bool*)(meta.Buffer0 + _offset)) = e->Value.AsBool; + *((bool*)(meta.Constants + _offset)) = e->Value.AsBool; break; case VariantType::Int: - *((int32*)(meta.Buffer0 + _offset)) = e->Value.AsInt; + *((int32*)(meta.Constants + _offset)) = e->Value.AsInt; break; case VariantType::Uint: - *((uint32*)(meta.Buffer0 + _offset)) = e->Value.AsUint; + *((uint32*)(meta.Constants + _offset)) = e->Value.AsUint; break; case VariantType::Float: - *((float*)(meta.Buffer0 + _offset)) = e->Value.AsFloat; + *((float*)(meta.Constants + _offset)) = e->Value.AsFloat; break; case VariantType::Vector2: - *((Vector2*)(meta.Buffer0 + _offset)) = e->Value.AsVector2(); + *((Vector2*)(meta.Constants + _offset)) = e->Value.AsVector2(); break; case VariantType::Vector3: - *((Vector3*)(meta.Buffer0 + _offset)) = e->Value.AsVector3(); + *((Vector3*)(meta.Constants + _offset)) = e->Value.AsVector3(); break; case VariantType::Vector4: case VariantType::Color: - *((Vector4*)(meta.Buffer0 + _offset)) = e->Value.AsVector4(); + *((Vector4*)(meta.Constants + _offset)) = e->Value.AsVector4(); break; default: ; } diff --git a/Source/Engine/Graphics/Materials/MaterialParams.h b/Source/Engine/Graphics/Materials/MaterialParams.h index 997b18d34..ca48dc143 100644 --- a/Source/Engine/Graphics/Materials/MaterialParams.h +++ b/Source/Engine/Graphics/Materials/MaterialParams.h @@ -309,9 +309,9 @@ public: GPUContext* Context; /// - /// The pointer to the first constants buffer in memory. + /// The pointer to the constants buffer in the memory. /// - byte* Buffer0; + byte* Constants; /// /// The input scene color. It's optional and used in forward/postFx rendering. diff --git a/Source/Engine/Graphics/Materials/MaterialShader.cpp b/Source/Engine/Graphics/Materials/MaterialShader.cpp index 478992959..caac3b5f2 100644 --- a/Source/Engine/Graphics/Materials/MaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/MaterialShader.cpp @@ -14,18 +14,15 @@ #include "GUIMaterialShader.h" #include "TerrainMaterialShader.h" #include "ParticleMaterialShader.h" +#include "DeformableMaterialShader.h" -GPUPipelineState* MaterialShader::PipelineStateCache::GetPS(CullMode mode, bool wireframe) +GPUPipelineState* MaterialShader::PipelineStateCache::InitPS(CullMode mode, bool wireframe) { - const int32 index = static_cast(mode) + (wireframe ? 3 : 0); - if (PS[index]) - return PS[index]; - Desc.CullMode = mode; Desc.Wireframe = wireframe; - PS[index] = GPUDevice::Instance->CreatePipelineState(); - PS[index]->Init(Desc); - return PS[index]; + auto ps = GPUDevice::Instance->CreatePipelineState(); + ps->Init(Desc); + return ps; } MaterialShader::MaterialShader(const String& name) @@ -65,6 +62,9 @@ MaterialShader* MaterialShader::Create(const String& name, MemoryReadStream& sha case MaterialDomain::Particle: material = New(name); break; + case MaterialDomain::Deformable: + material = New(name); + break; default: LOG(Fatal, "Unknown material type."); return nullptr; @@ -138,15 +138,17 @@ bool MaterialShader::Load(MemoryReadStream& shaderCacheStream, const MaterialInf } // Init memory for a constant buffer - const auto cb0 = _shader->GetCB(0); - if (cb0) + _cb = _shader->GetCB(0); + if (_cb) { - _cb0Data.Resize(cb0->GetSize(), false); - } - const auto cb1 = _shader->GetCB(1); - if (cb1) - { - _cb1Data.Resize(cb1->GetSize(), false); + int32 cbSize = _cb->GetSize(); + if (cbSize == 0) + { + // Handle unused constant buffer (eg. postFx returning solid color) + cbSize = 1024; + _cb = nullptr; + } + _cbData.Resize(cbSize, false); } // Initialize the material based on type (create pipeline states and setup) @@ -162,7 +164,7 @@ bool MaterialShader::Load(MemoryReadStream& shaderCacheStream, const MaterialInf void MaterialShader::Unload() { _isLoaded = false; - _cb0Data.Resize(0, false); - _cb1Data.Resize(0, false); + _cb = nullptr; + _cbData.Resize(0, false); _shader->ReleaseGPU(); } diff --git a/Source/Engine/Graphics/Materials/MaterialShader.h b/Source/Engine/Graphics/Materials/MaterialShader.h index 58ba297fa..8c49389a7 100644 --- a/Source/Engine/Graphics/Materials/MaterialShader.h +++ b/Source/Engine/Graphics/Materials/MaterialShader.h @@ -9,10 +9,11 @@ /// /// Current materials shader version. /// -#define MATERIAL_GRAPH_VERSION 146 +#define MATERIAL_GRAPH_VERSION 148 class Material; class GPUShader; +class GPUConstantBuffer; class MemoryReadStream; /// @@ -37,7 +38,16 @@ protected: Desc = desc; } - GPUPipelineState* GetPS(CullMode mode, bool wireframe); + GPUPipelineState* GetPS(CullMode mode, bool wireframe) + { + const int32 index = static_cast(mode) + (wireframe ? 3 : 0); + auto ps = PS[index]; + if (!ps) + PS[index] = ps = InitPS(mode, wireframe); + return ps; + } + + GPUPipelineState* InitPS(CullMode mode, bool wireframe); void Release() { @@ -52,8 +62,8 @@ protected: bool _isLoaded; GPUShader* _shader; - Array _cb0Data; - Array _cb1Data; + GPUConstantBuffer* _cb; + Array _cbData; MaterialInfo _info; protected: @@ -90,10 +100,8 @@ public: /// The created and loaded material or null if failed. static MaterialShader* CreateDummy(MemoryReadStream& shaderCacheStream, const MaterialInfo& info); -public: - /// - /// Clear loaded data + /// Clears the loaded data. /// virtual void Unload(); diff --git a/Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp b/Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp new file mode 100644 index 000000000..abc28a0de --- /dev/null +++ b/Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp @@ -0,0 +1,188 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#include "MaterialShaderFeatures.h" +#include "Engine/Graphics/RenderTask.h" +#include "Engine/Renderer/RenderList.h" +#include "Engine/Renderer/ShadowsPass.h" +#if USE_EDITOR +#include "Engine/Renderer/Lightmaps.h" +#endif +#include "Engine/Level/Scene/Lightmap.h" +#include "Engine/Level/Actors/EnvironmentProbe.h" + +void ForwardShadingFeature::Bind(MaterialShader::BindParameters& params, byte*& cb, int32& srv) +{ + auto context = params.GPUContext; + auto cache = params.RenderContext.List; + auto& view = params.RenderContext.View; + auto& drawCall = *params.FirstDrawCall; + auto& data = *(Data*)cb; + const int32 envProbeShaderRegisterIndex = srv + 0; + const int32 skyLightShaderRegisterIndex = srv + 1; + const int32 dirLightShaderRegisterIndex = srv + 2; + + // Set fog input + if (cache->Fog) + { + cache->Fog->GetExponentialHeightFogData(view, data.ExponentialHeightFog); + } + else + { + data.ExponentialHeightFog.FogMinOpacity = 1.0f; + data.ExponentialHeightFog.ApplyDirectionalInscattering = 0.0f; + } + + // Set directional light input + if (cache->DirectionalLights.HasItems()) + { + const auto& dirLight = cache->DirectionalLights.First(); + const auto shadowPass = ShadowsPass::Instance(); + const bool useShadow = shadowPass->LastDirLightIndex == 0; + if (useShadow) + { + data.DirectionalLightShadow = shadowPass->LastDirLight; + context->BindSR(dirLightShaderRegisterIndex, shadowPass->LastDirLightShadowMap); + } + else + { + context->UnBindSR(dirLightShaderRegisterIndex); + } + dirLight.SetupLightData(&data.DirectionalLight, view, useShadow); + } + else + { + data.DirectionalLight.Color = Vector3::Zero; + data.DirectionalLight.CastShadows = 0.0f; + context->UnBindSR(dirLightShaderRegisterIndex); + } + + // Set sky light + if (cache->SkyLights.HasItems()) + { + auto& skyLight = cache->SkyLights.First(); + skyLight.SetupLightData(&data.SkyLight, view, false); + const auto texture = skyLight.Image ? skyLight.Image->GetTexture() : nullptr; + context->BindSR(skyLightShaderRegisterIndex, GET_TEXTURE_VIEW_SAFE(texture)); + } + else + { + Platform::MemoryClear(&data.SkyLight, sizeof(data.SkyLight)); + context->UnBindSR(skyLightShaderRegisterIndex); + } + + // Set reflection probe data + EnvironmentProbe* probe = nullptr; + // TODO: optimize env probe searching for a transparent material - use spatial cache for renderer to find it + for (int32 i = 0; i < cache->EnvironmentProbes.Count(); i++) + { + const auto p = cache->EnvironmentProbes[i]; + if (p->GetSphere().Contains(drawCall.World.GetTranslation()) != ContainmentType::Disjoint) + { + probe = p; + break; + } + } + if (probe && probe->GetProbe()) + { + probe->SetupProbeData(&data.EnvironmentProbe); + const auto texture = probe->GetProbe()->GetTexture(); + context->BindSR(envProbeShaderRegisterIndex, GET_TEXTURE_VIEW_SAFE(texture)); + } + else + { + data.EnvironmentProbe.Data1 = Vector4::Zero; + context->UnBindSR(envProbeShaderRegisterIndex); + } + + // Set local lights + data.LocalLightsCount = 0; + for (int32 i = 0; i < cache->PointLights.Count(); i++) + { + const auto& light = cache->PointLights[i]; + if (BoundingSphere(light.Position, light.Radius).Contains(drawCall.World.GetTranslation()) != ContainmentType::Disjoint) + { + light.SetupLightData(&data.LocalLights[data.LocalLightsCount], view, false); + data.LocalLightsCount++; + if (data.LocalLightsCount == MaxLocalLights) + break; + } + } + for (int32 i = 0; i < cache->SpotLights.Count(); i++) + { + const auto& light = cache->SpotLights[i]; + if (BoundingSphere(light.Position, light.Radius).Contains(drawCall.World.GetTranslation()) != ContainmentType::Disjoint) + { + light.SetupLightData(&data.LocalLights[data.LocalLightsCount], view, false); + data.LocalLightsCount++; + if (data.LocalLightsCount == MaxLocalLights) + break; + } + } + + cb += sizeof(Data); + srv += SRVs; +} + +bool LightmapFeature::Bind(MaterialShader::BindParameters& params, byte*& cb, int32& srv) +{ + auto context = params.GPUContext; + auto& view = params.RenderContext.View; + auto& drawCall = *params.FirstDrawCall; + auto& data = *(Data*)cb; + + const bool useLightmap = view.Flags & ViewFlags::GI +#if USE_EDITOR + && EnableLightmapsUsage +#endif + && drawCall.Surface.Lightmap != nullptr; + if (useLightmap) + { + // Bind lightmap textures + GPUTexture *lightmap0, *lightmap1, *lightmap2; + drawCall.Features.Lightmap->GetTextures(&lightmap0, &lightmap1, &lightmap2); + context->BindSR(srv + 0, lightmap0); + context->BindSR(srv + 1, lightmap1); + context->BindSR(srv + 2, lightmap2); + + // Set lightmap data + data.LightmapArea = drawCall.Features.LightmapUVsArea; + } + + srv += SRVs; + cb += sizeof(Data); + return useLightmap; +} + +#if USE_EDITOR + +void ForwardShadingFeature::Generate(GeneratorData& data) +{ + data.Template = TEXT("Features/ForwardShading.hlsl"); +} + +void DeferredShadingFeature::Generate(GeneratorData& data) +{ + data.Template = TEXT("Features/DeferredShading.hlsl"); +} + +void TessellationFeature::Generate(GeneratorData& data) +{ + data.Template = TEXT("Features/Tessellation.hlsl"); +} + +void LightmapFeature::Generate(GeneratorData& data) +{ + data.Template = TEXT("Features/Lightmap.hlsl"); +} + +void DistortionFeature::Generate(GeneratorData& data) +{ + data.Template = TEXT("Features/Distortion.hlsl"); +} + +void MotionVectorsFeature::Generate(GeneratorData& data) +{ + data.Template = TEXT("Features/MotionVectors.hlsl"); +} + +#endif diff --git a/Source/Engine/Graphics/Materials/MaterialShaderFeatures.h b/Source/Engine/Graphics/Materials/MaterialShaderFeatures.h new file mode 100644 index 000000000..a70d9ada8 --- /dev/null +++ b/Source/Engine/Graphics/Materials/MaterialShaderFeatures.h @@ -0,0 +1,90 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#include "MaterialShader.h" +#include "Engine/Core/Math/Rectangle.h" + +// Material shader features are plugin-based functionalities that are reusable between different material domains. +struct MaterialShaderFeature +{ +#if USE_EDITOR + struct GeneratorData + { + const Char* Template; + }; +#endif; +}; + +// Material shader feature that add support for Forward shading inside the material shader. +struct ForwardShadingFeature : MaterialShaderFeature +{ + enum { MaxLocalLights = 4 }; + + enum { SRVs = 3 }; + + PACK_STRUCT(struct Data + { + LightData DirectionalLight; + LightShadowData DirectionalLightShadow; + LightData SkyLight; + ProbeData EnvironmentProbe; + ExponentialHeightFogData ExponentialHeightFog; + Vector3 Dummy2; + uint32 LocalLightsCount; + LightData LocalLights[MaxLocalLights]; + }); + + static void Bind(MaterialShader::BindParameters& params, byte*& cb, int32& srv); +#if USE_EDITOR + static void Generate(GeneratorData& data); +#endif +}; + +// Material shader feature that add support for Deferred shading inside the material shader. +struct DeferredShadingFeature : MaterialShaderFeature +{ +#if USE_EDITOR + static void Generate(GeneratorData& data); +#endif +}; + +// Material shader feature that adds geometry hardware tessellation (using Hull and Domain shaders). +struct TessellationFeature : MaterialShaderFeature +{ +#if USE_EDITOR + static void Generate(GeneratorData& data); +#endif +}; + +// Material shader feature that adds lightmap sampling feature. +struct LightmapFeature : MaterialShaderFeature +{ + enum { SRVs = 3 }; + + PACK_STRUCT(struct Data + { + Rectangle LightmapArea; + }); + + static bool Bind(MaterialShader::BindParameters& params, byte*& cb, int32& srv); +#if USE_EDITOR + static void Generate(GeneratorData& data); +#endif +}; + +// Material shader feature that adds distortion vectors rendering pass. +struct DistortionFeature : MaterialShaderFeature +{ +#if USE_EDITOR + static void Generate(GeneratorData& data); +#endif +}; + +// Material shader feature that adds motion vectors rendering pass. +struct MotionVectorsFeature : MaterialShaderFeature +{ +#if USE_EDITOR + static void Generate(GeneratorData& data); +#endif +}; diff --git a/Source/Engine/Graphics/Materials/ParticleMaterialShader.cpp b/Source/Engine/Graphics/Materials/ParticleMaterialShader.cpp index 8c1c065ab..7a718ceb7 100644 --- a/Source/Engine/Graphics/Materials/ParticleMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/ParticleMaterialShader.cpp @@ -1,20 +1,19 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. #include "ParticleMaterialShader.h" +#include "MaterialShaderFeatures.h" #include "MaterialParams.h" -#include "Engine/Renderer/DrawCall.h" -#include "Engine/Renderer/ShadowsPass.h" -#include "Engine/Graphics/RenderView.h" -#include "Engine/Renderer/RenderList.h" -#include "Engine/Graphics/GPUContext.h" -#include "Engine/Graphics/Shaders/GPUConstantBuffer.h" #include "Engine/Engine/Time.h" +#include "Engine/Renderer/DrawCall.h" +#include "Engine/Renderer/RenderList.h" +#include "Engine/Graphics/RenderView.h" +#include "Engine/Graphics/GPUContext.h" #include "Engine/Graphics/GPUDevice.h" -#include "Engine/Graphics/Shaders/GPUShader.h" #include "Engine/Graphics/GPULimits.h" +#include "Engine/Graphics/RenderTask.h" +#include "Engine/Graphics/Shaders/GPUShader.h" +#include "Engine/Graphics/Shaders/GPUConstantBuffer.h" #include "Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.h" -#include "Engine/Content/Assets/CubeTexture.h" -#include "Engine/Level/Actors/EnvironmentProbe.h" #define MAX_LOCAL_LIGHTS 4 @@ -49,17 +48,6 @@ PACK_STRUCT(struct ParticleMaterialShaderData { Matrix WorldMatrixInverseTransposed; }); -PACK_STRUCT(struct ParticleMaterialShaderLightingData { - LightData DirectionalLight; - LightShadowData DirectionalLightShadow; - LightData SkyLight; - ProbeData EnvironmentProbe; - ExponentialHeightFogData ExponentialHeightFog; - Vector3 Dummy1; - uint32 LocalLightsCount; - LightData LocalLights[MAX_LOCAL_LIGHTS]; - }); - DrawPass ParticleMaterialShader::GetDrawModes() const { return _drawModes; @@ -70,43 +58,42 @@ void ParticleMaterialShader::Bind(BindParameters& params) // Prepare auto context = params.GPUContext; auto& view = params.RenderContext.View; - auto cache = params.RenderContext.List; auto& drawCall = *params.FirstDrawCall; - const auto cb0 = _shader->GetCB(0); - const bool hasCb0 = cb0->GetSize() != 0; - const auto cb1 = _shader->GetCB(1); - const bool hasCb1 = cb1->GetSize() != 0; - const uint32 sortedIndicesOffset = drawCall.Module->SortedIndicesOffset; + const uint32 sortedIndicesOffset = drawCall.Particle.Module->SortedIndicesOffset; + byte* cb = _cbData.Get(); + auto materialData = reinterpret_cast(cb); + cb += sizeof(ParticleMaterialShaderData); + int32 srv = 2; + + // Setup features + ForwardShadingFeature::Bind(params, cb, srv); // Setup parameters MaterialParameter::BindMeta bindMeta; bindMeta.Context = context; - bindMeta.Buffer0 = hasCb0 ? _cb0Data.Get() + sizeof(ParticleMaterialShaderData) : nullptr; + bindMeta.Constants = cb; bindMeta.Input = nullptr; bindMeta.Buffers = params.RenderContext.Buffers; bindMeta.CanSampleDepth = GPUDevice::Instance->Limits.HasReadOnlyDepth; bindMeta.CanSampleGBuffer = true; MaterialParams::Bind(params.ParamsLink, bindMeta); - // Setup particles data and attributes binding info + // Setup particles data + context->BindSR(0, drawCall.Particle.Particles->GPU.Buffer->View()); + context->BindSR(1, drawCall.Particle.Particles->GPU.SortedIndices ? drawCall.Particle.Particles->GPU.SortedIndices->View() : nullptr); + + // Setup particles attributes binding info { - context->BindSR(0, drawCall.Particles->GPU.Buffer->View()); - if (drawCall.Particles->GPU.SortedIndices) - context->BindSR(1, drawCall.Particles->GPU.SortedIndices->View()); - - if (hasCb0) + const auto& p = *params.ParamsLink->This; + for (int32 i = 0; i < p.Count(); i++) { - const auto& p = *params.ParamsLink->This; - for (int32 i = 0; i < p.Count(); i++) + const auto& param = p.At(i); + if (param.GetParameterType() == MaterialParameterType::Integer && param.GetName().StartsWith(TEXT("Particle."))) { - const auto& param = p.At(i); - if (param.GetParameterType() == MaterialParameterType::Integer && param.GetName().StartsWith(TEXT("Particle."))) - { - auto name = StringView(param.GetName().Get() + 9); + auto name = StringView(param.GetName().Get() + 9); - const int32 offset = drawCall.Particles->Layout->FindAttributeOffset(name); - *((int32*)(bindMeta.Buffer0 + param.GetBindOffset())) = offset; - } + const int32 offset = drawCall.Particle.Particles->Layout->FindAttributeOffset(name); + *((int32*)(bindMeta.Constants + param.GetBindOffset())) = offset; } } } @@ -115,7 +102,7 @@ void ParticleMaterialShader::Bind(BindParameters& params) const bool wireframe = (_info.FeaturesFlags & MaterialFeaturesFlags::Wireframe) != 0 || view.Mode == ViewMode::Wireframe; CullMode cullMode = view.Pass == DrawPass::Depth ? CullMode::TwoSided : _info.CullMode; PipelineStateCache* psCache = nullptr; - switch (drawCall.Module->TypeID) + switch (drawCall.Particle.Module->TypeID) { // Sprite Rendering case 400: @@ -138,24 +125,19 @@ void ParticleMaterialShader::Bind(BindParameters& params) static StringView ParticleRibbonTwist(TEXT("RibbonTwist")); static StringView ParticleRibbonFacingVector(TEXT("RibbonFacingVector")); - if (hasCb0) - { - const auto materialData = reinterpret_cast(_cb0Data.Get()); + materialData->RibbonWidthOffset = drawCall.Particle.Particles->Layout->FindAttributeOffset(ParticleRibbonWidth, ParticleAttribute::ValueTypes::Float, -1); + materialData->RibbonTwistOffset = drawCall.Particle.Particles->Layout->FindAttributeOffset(ParticleRibbonTwist, ParticleAttribute::ValueTypes::Float, -1); + materialData->RibbonFacingVectorOffset = drawCall.Particle.Particles->Layout->FindAttributeOffset(ParticleRibbonFacingVector, ParticleAttribute::ValueTypes::Vector3, -1); - materialData->RibbonWidthOffset = drawCall.Particles->Layout->FindAttributeOffset(ParticleRibbonWidth, ParticleAttribute::ValueTypes::Float, -1); - materialData->RibbonTwistOffset = drawCall.Particles->Layout->FindAttributeOffset(ParticleRibbonTwist, ParticleAttribute::ValueTypes::Float, -1); - materialData->RibbonFacingVectorOffset = drawCall.Particles->Layout->FindAttributeOffset(ParticleRibbonFacingVector, ParticleAttribute::ValueTypes::Vector3, -1); + materialData->RibbonUVTilingDistance = drawCall.Particle.Ribbon.UVTilingDistance; + materialData->RibbonUVScale.X = drawCall.Particle.Ribbon.UVScaleX; + materialData->RibbonUVScale.Y = drawCall.Particle.Ribbon.UVScaleY; + materialData->RibbonUVOffset.X = drawCall.Particle.Ribbon.UVOffsetX; + materialData->RibbonUVOffset.Y = drawCall.Particle.Ribbon.UVOffsetY; + materialData->RibbonSegmentCount = drawCall.Particle.Ribbon.SegmentCount; - materialData->RibbonUVTilingDistance = drawCall.Ribbon.UVTilingDistance; - materialData->RibbonUVScale.X = drawCall.Ribbon.UVScaleX; - materialData->RibbonUVScale.Y = drawCall.Ribbon.UVScaleY; - materialData->RibbonUVOffset.X = drawCall.Ribbon.UVOffsetX; - materialData->RibbonUVOffset.Y = drawCall.Ribbon.UVOffsetY; - materialData->RibbonSegmentCount = drawCall.Ribbon.SegmentCount; - } - - if (drawCall.Ribbon.SegmentDistances) - context->BindSR(1, drawCall.Ribbon.SegmentDistances->View()); + if (drawCall.Particle.Ribbon.SegmentDistances) + context->BindSR(1, drawCall.Particle.Ribbon.SegmentDistances->View()); break; } @@ -163,11 +145,8 @@ void ParticleMaterialShader::Bind(BindParameters& params) ASSERT(psCache); GPUPipelineState* state = psCache->GetPS(cullMode, wireframe); - // Setup material constants data - if (hasCb0) + // Setup material constants { - const auto materialData = reinterpret_cast(_cb0Data.Get()); - static StringView ParticlePosition(TEXT("Position")); static StringView ParticleSpriteSize(TEXT("SpriteSize")); static StringView ParticleSpriteFacingMode(TEXT("SpriteFacingMode")); @@ -186,137 +165,25 @@ void ParticleMaterialShader::Bind(BindParameters& params) materialData->TimeParam = Time::Draw.UnscaledTime.GetTotalSeconds(); materialData->ViewInfo = view.ViewInfo; materialData->ScreenSize = view.ScreenSize; - materialData->SortedIndicesOffset = drawCall.Particles->GPU.SortedIndices && params.RenderContext.View.Pass != DrawPass::Depth ? sortedIndicesOffset : 0xFFFFFFFF; + materialData->SortedIndicesOffset = drawCall.Particle.Particles->GPU.SortedIndices && params.RenderContext.View.Pass != DrawPass::Depth ? sortedIndicesOffset : 0xFFFFFFFF; materialData->PerInstanceRandom = drawCall.PerInstanceRandom; - materialData->ParticleStride = drawCall.Particles->Stride; - materialData->PositionOffset = drawCall.Particles->Layout->FindAttributeOffset(ParticlePosition, ParticleAttribute::ValueTypes::Vector3); - materialData->SpriteSizeOffset = drawCall.Particles->Layout->FindAttributeOffset(ParticleSpriteSize, ParticleAttribute::ValueTypes::Vector2); - materialData->SpriteFacingModeOffset = drawCall.Particles->Layout->FindAttributeOffset(ParticleSpriteFacingMode, ParticleAttribute::ValueTypes::Int, -1); - materialData->SpriteFacingVectorOffset = drawCall.Particles->Layout->FindAttributeOffset(ParticleSpriteFacingVector, ParticleAttribute::ValueTypes::Vector3); - materialData->VelocityOffset = drawCall.Particles->Layout->FindAttributeOffset(ParticleVelocityOffset, ParticleAttribute::ValueTypes::Vector3); - materialData->RotationOffset = drawCall.Particles->Layout->FindAttributeOffset(ParticleRotationOffset, ParticleAttribute::ValueTypes::Vector3, -1); - materialData->ScaleOffset = drawCall.Particles->Layout->FindAttributeOffset(ParticleScaleOffset, ParticleAttribute::ValueTypes::Vector3, -1); - materialData->ModelFacingModeOffset = drawCall.Particles->Layout->FindAttributeOffset(ParticleModelFacingModeOffset, ParticleAttribute::ValueTypes::Int, -1); + materialData->ParticleStride = drawCall.Particle.Particles->Stride; + materialData->PositionOffset = drawCall.Particle.Particles->Layout->FindAttributeOffset(ParticlePosition, ParticleAttribute::ValueTypes::Vector3); + materialData->SpriteSizeOffset = drawCall.Particle.Particles->Layout->FindAttributeOffset(ParticleSpriteSize, ParticleAttribute::ValueTypes::Vector2); + materialData->SpriteFacingModeOffset = drawCall.Particle.Particles->Layout->FindAttributeOffset(ParticleSpriteFacingMode, ParticleAttribute::ValueTypes::Int, -1); + materialData->SpriteFacingVectorOffset = drawCall.Particle.Particles->Layout->FindAttributeOffset(ParticleSpriteFacingVector, ParticleAttribute::ValueTypes::Vector3); + materialData->VelocityOffset = drawCall.Particle.Particles->Layout->FindAttributeOffset(ParticleVelocityOffset, ParticleAttribute::ValueTypes::Vector3); + materialData->RotationOffset = drawCall.Particle.Particles->Layout->FindAttributeOffset(ParticleRotationOffset, ParticleAttribute::ValueTypes::Vector3, -1); + materialData->ScaleOffset = drawCall.Particle.Particles->Layout->FindAttributeOffset(ParticleScaleOffset, ParticleAttribute::ValueTypes::Vector3, -1); + materialData->ModelFacingModeOffset = drawCall.Particle.Particles->Layout->FindAttributeOffset(ParticleModelFacingModeOffset, ParticleAttribute::ValueTypes::Int, -1); Matrix::Invert(drawCall.World, materialData->WorldMatrixInverseTransposed); } - // Setup lighting constants data - if (hasCb1) - { - auto& lightingData = *reinterpret_cast(_cb1Data.Get()); - const int32 envProbeShaderRegisterIndex = 2; - const int32 skyLightShaderRegisterIndex = 3; - const int32 dirLightShaderRegisterIndex = 4; - - // Set fog input - if (cache->Fog) - { - cache->Fog->GetExponentialHeightFogData(view, lightingData.ExponentialHeightFog); - } - else - { - lightingData.ExponentialHeightFog.FogMinOpacity = 1.0f; - lightingData.ExponentialHeightFog.ApplyDirectionalInscattering = 0.0f; - } - - // Set directional light input - if (cache->DirectionalLights.HasItems()) - { - const auto& dirLight = cache->DirectionalLights.First(); - const auto shadowPass = ShadowsPass::Instance(); - const bool useShadow = shadowPass->LastDirLightIndex == 0; - if (useShadow) - { - lightingData.DirectionalLightShadow = shadowPass->LastDirLight; - context->BindSR(dirLightShaderRegisterIndex, shadowPass->LastDirLightShadowMap); - } - else - { - context->UnBindSR(dirLightShaderRegisterIndex); - } - dirLight.SetupLightData(&lightingData.DirectionalLight, view, useShadow); - } - else - { - lightingData.DirectionalLight.Color = Vector3::Zero; - lightingData.DirectionalLight.CastShadows = 0.0f; - context->UnBindSR(dirLightShaderRegisterIndex); - } - - // Set sky light - if (cache->SkyLights.HasItems()) - { - auto& skyLight = cache->SkyLights.Last(); - skyLight.SetupLightData(&lightingData.SkyLight, view, false); - const auto texture = skyLight.Image ? skyLight.Image->GetTexture() : nullptr; - context->BindSR(skyLightShaderRegisterIndex, texture); - } - else - { - Platform::MemoryClear(&lightingData.SkyLight, sizeof(lightingData.SkyLight)); - context->UnBindSR(skyLightShaderRegisterIndex); - } - - // Set reflection probe data - EnvironmentProbe* probe = nullptr; - // TODO: optimize env probe searching for a transparent material - use spatial cache for renderer to find it - for (int32 i = 0; i < cache->EnvironmentProbes.Count(); i++) - { - const auto p = cache->EnvironmentProbes[i]; - if (p->GetSphere().Contains(drawCall.World.GetTranslation()) != ContainmentType::Disjoint) - { - probe = p; - break; - } - } - if (probe && probe->GetProbe()) - { - probe->SetupProbeData(&lightingData.EnvironmentProbe); - const auto texture = probe->GetProbe()->GetTexture(); - context->BindSR(envProbeShaderRegisterIndex, texture); - } - else - { - lightingData.EnvironmentProbe.Data1 = Vector4::Zero; - context->UnBindSR(envProbeShaderRegisterIndex); - } - - // Set local lights - lightingData.LocalLightsCount = 0; - for (int32 i = 0; i < cache->PointLights.Count(); i++) - { - const auto& light = cache->PointLights[i]; - if (BoundingSphere(light.Position, light.Radius).Contains(drawCall.World.GetTranslation()) != ContainmentType::Disjoint) - { - light.SetupLightData(&lightingData.LocalLights[lightingData.LocalLightsCount], view, false); - lightingData.LocalLightsCount++; - if (lightingData.LocalLightsCount == MAX_LOCAL_LIGHTS) - break; - } - } - for (int32 i = 0; i < cache->SpotLights.Count(); i++) - { - const auto& light = cache->SpotLights[i]; - if (BoundingSphere(light.Position, light.Radius).Contains(drawCall.World.GetTranslation()) != ContainmentType::Disjoint) - { - light.SetupLightData(&lightingData.LocalLights[lightingData.LocalLightsCount], view, false); - lightingData.LocalLightsCount++; - if (lightingData.LocalLightsCount == MAX_LOCAL_LIGHTS) - break; - } - } - } - // Bind constants - if (hasCb0) + if (_cb) { - context->UpdateCB(cb0, _cb0Data.Get()); - context->BindCB(0, cb0); - } - if (hasCb1) - { - context->UpdateCB(cb1, _cb1Data.Get()); - context->BindCB(1, cb1); + context->UpdateCB(_cb, _cbData.Get()); + context->BindCB(0, _cb); } // Bind pipeline diff --git a/Source/Engine/Graphics/Materials/PostFxMaterialShader.cpp b/Source/Engine/Graphics/Materials/PostFxMaterialShader.cpp index 5c2fb1df0..a55455173 100644 --- a/Source/Engine/Graphics/Materials/PostFxMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/PostFxMaterialShader.cpp @@ -26,25 +26,23 @@ void PostFxMaterialShader::Bind(BindParameters& params) // Prepare auto context = params.GPUContext; auto& view = params.RenderContext.View; - auto& drawCall = *params.FirstDrawCall; - const auto cb0 = _shader->GetCB(0); - const bool hasCb0 = cb0->GetSize() != 0; + byte* cb = _cbData.Get(); + auto materialData = reinterpret_cast(cb); + cb += sizeof(PostFxMaterialShaderData); + int32 srv = 0; // Setup parameters MaterialParameter::BindMeta bindMeta; bindMeta.Context = context; - bindMeta.Buffer0 = hasCb0 ? _cb0Data.Get() + sizeof(PostFxMaterialShaderData) : nullptr; + bindMeta.Constants = cb; bindMeta.Input = params.Input; bindMeta.Buffers = params.RenderContext.Buffers; bindMeta.CanSampleDepth = true; bindMeta.CanSampleGBuffer = true; MaterialParams::Bind(params.ParamsLink, bindMeta); - // Setup material constants data - if (hasCb0) + // Setup material constants { - const auto materialData = reinterpret_cast(_cb0Data.Get()); - Matrix::Transpose(view.View, materialData->ViewMatrix); materialData->ViewPos = view.Position; materialData->ViewFar = view.Far; @@ -56,10 +54,10 @@ void PostFxMaterialShader::Bind(BindParameters& params) } // Bind constants - if (hasCb0) + if (_cb) { - context->UpdateCB(cb0, _cb0Data.Get()); - context->BindCB(0, cb0); + context->UpdateCB(_cb, _cbData.Get()); + context->BindCB(0, _cb); } // Bind pipeline diff --git a/Source/Engine/Graphics/Materials/TerrainMaterialShader.cpp b/Source/Engine/Graphics/Materials/TerrainMaterialShader.cpp index 212e27d87..ec3c038a5 100644 --- a/Source/Engine/Graphics/Materials/TerrainMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/TerrainMaterialShader.cpp @@ -1,6 +1,7 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. #include "TerrainMaterialShader.h" +#include "MaterialShaderFeatures.h" #include "MaterialParams.h" #include "Engine/Engine/Time.h" #include "Engine/Graphics/GPUContext.h" @@ -25,7 +26,6 @@ PACK_STRUCT(struct TerrainMaterialShaderData { float TimeParam; Vector4 ViewInfo; Vector4 ScreenSize; - Rectangle LightmapArea; Vector3 WorldInvScale; float WorldDeterminantSign; float PerInstanceRandom; @@ -54,84 +54,65 @@ void TerrainMaterialShader::Bind(BindParameters& params) auto context = params.GPUContext; auto& view = params.RenderContext.View; auto& drawCall = *params.FirstDrawCall; - const auto cb0 = _shader->GetCB(0); - const bool hasCb0 = cb0->GetSize() != 0; + byte* cb = _cbData.Get(); + auto materialData = reinterpret_cast(cb); + cb += sizeof(TerrainMaterialShaderData); + int32 srv = 3; + + // Setup features + const bool useLightmap = LightmapFeature::Bind(params, cb, srv); // Setup parameters MaterialParameter::BindMeta bindMeta; bindMeta.Context = context; - bindMeta.Buffer0 = hasCb0 ? _cb0Data.Get() + sizeof(TerrainMaterialShaderData) : nullptr; + bindMeta.Constants = cb; bindMeta.Input = nullptr; bindMeta.Buffers = nullptr; bindMeta.CanSampleDepth = false; bindMeta.CanSampleGBuffer = false; MaterialParams::Bind(params.ParamsLink, bindMeta); - // Setup material constants data - auto data = reinterpret_cast(_cb0Data.Get()); - if (hasCb0) + // Setup material constants { - Matrix::Transpose(view.Frustum.GetMatrix(), data->ViewProjectionMatrix); - Matrix::Transpose(drawCall.World, data->WorldMatrix); - Matrix::Transpose(view.View, data->ViewMatrix); - - data->ViewPos = view.Position; - data->ViewFar = view.Far; - data->ViewDir = view.Direction; - data->TimeParam = Time::Draw.UnscaledTime.GetTotalSeconds(); - data->ViewInfo = view.ViewInfo; - data->ScreenSize = view.ScreenSize; - - // Extract per axis scales from LocalToWorld transform + Matrix::Transpose(view.Frustum.GetMatrix(), materialData->ViewProjectionMatrix); + Matrix::Transpose(drawCall.World, materialData->WorldMatrix); + Matrix::Transpose(view.View, materialData->ViewMatrix); + materialData->ViewPos = view.Position; + materialData->ViewFar = view.Far; + materialData->ViewDir = view.Direction; + materialData->TimeParam = Time::Draw.UnscaledTime.GetTotalSeconds(); + materialData->ViewInfo = view.ViewInfo; + materialData->ScreenSize = view.ScreenSize; const float scaleX = Vector3(drawCall.World.M11, drawCall.World.M12, drawCall.World.M13).Length(); const float scaleY = Vector3(drawCall.World.M21, drawCall.World.M22, drawCall.World.M23).Length(); const float scaleZ = Vector3(drawCall.World.M31, drawCall.World.M32, drawCall.World.M33).Length(); - data->WorldInvScale = Vector3( + materialData->WorldInvScale = Vector3( scaleX > 0.00001f ? 1.0f / scaleX : 0.0f, scaleY > 0.00001f ? 1.0f / scaleY : 0.0f, scaleZ > 0.00001f ? 1.0f / scaleZ : 0.0f); - - data->WorldDeterminantSign = drawCall.WorldDeterminantSign; - data->PerInstanceRandom = drawCall.PerInstanceRandom; - data->CurrentLOD = drawCall.TerrainData.CurrentLOD; - data->ChunkSizeNextLOD = drawCall.TerrainData.ChunkSizeNextLOD; - data->TerrainChunkSizeLOD0 = drawCall.TerrainData.TerrainChunkSizeLOD0; - data->HeightmapUVScaleBias = drawCall.TerrainData.HeightmapUVScaleBias; - data->NeighborLOD = drawCall.TerrainData.NeighborLOD; - data->OffsetUV = drawCall.TerrainData.OffsetUV; - } - const bool useLightmap = view.Flags & ViewFlags::GI -#if USE_EDITOR - && EnableLightmapsUsage -#endif - && view.Pass == DrawPass::GBuffer - && drawCall.Lightmap != nullptr; - if (useLightmap) - { - // Bind lightmap textures - GPUTexture *lightmap0, *lightmap1, *lightmap2; - drawCall.Lightmap->GetTextures(&lightmap0, &lightmap1, &lightmap2); - context->BindSR(0, lightmap0); - context->BindSR(1, lightmap1); - context->BindSR(2, lightmap2); - - // Set lightmap data - data->LightmapArea = drawCall.LightmapUVsArea; + materialData->WorldDeterminantSign = drawCall.WorldDeterminantSign; + materialData->PerInstanceRandom = drawCall.PerInstanceRandom; + materialData->CurrentLOD = drawCall.Terrain.CurrentLOD; + materialData->ChunkSizeNextLOD = drawCall.Terrain.ChunkSizeNextLOD; + materialData->TerrainChunkSizeLOD0 = drawCall.Terrain.TerrainChunkSizeLOD0; + materialData->HeightmapUVScaleBias = drawCall.Terrain.HeightmapUVScaleBias; + materialData->NeighborLOD = drawCall.Terrain.NeighborLOD; + materialData->OffsetUV = drawCall.Terrain.OffsetUV; } // Bind terrain textures - const auto heightmap = drawCall.TerrainData.Patch->Heightmap.Get()->GetTexture(); - const auto splatmap0 = drawCall.TerrainData.Patch->Splatmap[0] ? drawCall.TerrainData.Patch->Splatmap[0]->GetTexture() : nullptr; - const auto splatmap1 = drawCall.TerrainData.Patch->Splatmap[1] ? drawCall.TerrainData.Patch->Splatmap[1]->GetTexture() : nullptr; - context->BindSR(3, heightmap); - context->BindSR(4, splatmap0); - context->BindSR(5, splatmap1); + const auto heightmap = drawCall.Terrain.Patch->Heightmap->GetTexture(); + const auto splatmap0 = drawCall.Terrain.Patch->Splatmap[0] ? drawCall.Terrain.Patch->Splatmap[0]->GetTexture() : nullptr; + const auto splatmap1 = drawCall.Terrain.Patch->Splatmap[1] ? drawCall.Terrain.Patch->Splatmap[1]->GetTexture() : nullptr; + context->BindSR(0, heightmap); + context->BindSR(1, splatmap0); + context->BindSR(2, splatmap1); // Bind constants - if (hasCb0) + if (_cb) { - context->UpdateCB(cb0, _cb0Data.Get()); - context->BindCB(0, cb0); + context->UpdateCB(_cb, _cbData.Get()); + context->BindCB(0, _cb); } // Select pipeline state based on current pass and render mode @@ -141,7 +122,7 @@ void TerrainMaterialShader::Bind(BindParameters& params) if (IsRunningRadiancePass) cullMode = CullMode::TwoSided; #endif - if (cullMode != CullMode::TwoSided && drawCall.IsNegativeScale()) + if (cullMode != CullMode::TwoSided && drawCall.WorldDeterminantSign < 0) { // Invert culling when scale is negative if (cullMode == CullMode::Normal) diff --git a/Source/Engine/Graphics/Models/Mesh.cpp b/Source/Engine/Graphics/Models/Mesh.cpp index 444d60540..8285883ae 100644 --- a/Source/Engine/Graphics/Models/Mesh.cpp +++ b/Source/Engine/Graphics/Models/Mesh.cpp @@ -350,7 +350,7 @@ bool Mesh::Intersects(const Ray& ray, const Matrix& world, float& distance, Vect #endif } -void Mesh::GetDrawCallGeometry(DrawCall& drawCall) +void Mesh::GetDrawCallGeometry(DrawCall& drawCall) const { drawCall.Geometry.IndexBuffer = _indexBuffer; drawCall.Geometry.VertexBuffers[0] = _vertexBuffers[0]; @@ -359,8 +359,8 @@ void Mesh::GetDrawCallGeometry(DrawCall& drawCall) drawCall.Geometry.VertexBuffersOffsets[0] = 0; drawCall.Geometry.VertexBuffersOffsets[1] = 0; drawCall.Geometry.VertexBuffersOffsets[2] = 0; - drawCall.Geometry.StartIndex = 0; - drawCall.Geometry.IndicesCount = _triangles * 3; + drawCall.Draw.StartIndex = 0; + drawCall.Draw.IndicesCount = _triangles * 3; } void Mesh::Render(GPUContext* context) const @@ -386,22 +386,20 @@ void Mesh::Draw(const RenderContext& renderContext, MaterialBase* material, cons drawCall.Geometry.VertexBuffersOffsets[0] = 0; drawCall.Geometry.VertexBuffersOffsets[1] = 0; drawCall.Geometry.VertexBuffersOffsets[2] = 0; - drawCall.Geometry.StartIndex = 0; - drawCall.Geometry.IndicesCount = _triangles * 3; + drawCall.Draw.StartIndex = 0; + drawCall.Draw.IndicesCount = _triangles * 3; drawCall.InstanceCount = 1; - drawCall.IndirectArgsBuffer = nullptr; - drawCall.IndirectArgsOffset = 0; drawCall.Material = material; drawCall.World = world; - drawCall.PrevWorld = world; drawCall.ObjectPosition = drawCall.World.GetTranslation(); - drawCall.GeometrySize = _box.GetSize(); - drawCall.Lightmap = nullptr; - drawCall.LightmapUVsArea = Rectangle::Empty; - drawCall.Skinning = nullptr; + drawCall.Surface.GeometrySize = _box.GetSize(); + drawCall.Surface.PrevWorld = world; + drawCall.Surface.Lightmap = nullptr; + drawCall.Surface.LightmapUVsArea = Rectangle::Empty; + drawCall.Surface.Skinning = nullptr; + drawCall.Surface.LODDitherFactor = 0.0f; drawCall.WorldDeterminantSign = Math::FloatSelect(world.RotDeterminant(), 1, -1); drawCall.PerInstanceRandom = 0.0f; - drawCall.LODDitherFactor = 0.0f; renderContext.List->AddDrawCall(DrawPass::Default, flags, drawCall, receiveDecals); } @@ -449,22 +447,20 @@ void Mesh::Draw(const RenderContext& renderContext, const DrawInfo& info, float drawCall.Geometry.VertexBuffers[2] = info.VertexColors[_lodIndex]; drawCall.Geometry.VertexBuffersOffsets[2] = vertexOffset * sizeof(VB2ElementType); } - drawCall.Geometry.StartIndex = 0; - drawCall.Geometry.IndicesCount = _triangles * 3; + drawCall.Draw.StartIndex = 0; + drawCall.Draw.IndicesCount = _triangles * 3; drawCall.InstanceCount = 1; - drawCall.IndirectArgsBuffer = nullptr; - drawCall.IndirectArgsOffset = 0; drawCall.Material = material; drawCall.World = *info.World; - drawCall.PrevWorld = info.DrawState->PrevWorld; drawCall.ObjectPosition = drawCall.World.GetTranslation(); - drawCall.GeometrySize = _box.GetSize(); - drawCall.Lightmap = info.Flags & StaticFlags::Lightmap ? info.Lightmap : nullptr; - drawCall.LightmapUVsArea = info.LightmapUVs ? *info.LightmapUVs : Rectangle::Empty; - drawCall.Skinning = nullptr; + drawCall.Surface.GeometrySize = _box.GetSize(); + drawCall.Surface.PrevWorld = info.DrawState->PrevWorld; + drawCall.Surface.Lightmap = info.Flags & StaticFlags::Lightmap ? info.Lightmap : nullptr; + drawCall.Surface.LightmapUVsArea = info.LightmapUVs ? *info.LightmapUVs : Rectangle::Empty; + drawCall.Surface.Skinning = nullptr; + drawCall.Surface.LODDitherFactor = lodDitherFactor; drawCall.WorldDeterminantSign = Math::FloatSelect(drawCall.World.RotDeterminant(), 1, -1); drawCall.PerInstanceRandom = info.PerInstanceRandom; - drawCall.LODDitherFactor = lodDitherFactor; renderContext.List->AddDrawCall(drawModes, info.Flags, drawCall, entry.ReceiveDecals); } diff --git a/Source/Engine/Graphics/Models/Mesh.h b/Source/Engine/Graphics/Models/Mesh.h index 80d566fa3..94d4979a3 100644 --- a/Source/Engine/Graphics/Models/Mesh.h +++ b/Source/Engine/Graphics/Models/Mesh.h @@ -377,7 +377,7 @@ public: /// Gets the draw call geometry for this mesh. Sets the index and vertex buffers. /// /// The draw call. - void GetDrawCallGeometry(DrawCall& drawCall); + void GetDrawCallGeometry(DrawCall& drawCall) const; /// /// Model instance drawing packed data. diff --git a/Source/Engine/Graphics/Models/ModelData.cpp b/Source/Engine/Graphics/Models/ModelData.cpp index 56981e65d..71b511b4f 100644 --- a/Source/Engine/Graphics/Models/ModelData.cpp +++ b/Source/Engine/Graphics/Models/ModelData.cpp @@ -2,6 +2,7 @@ #include "ModelData.h" #include "Engine/Core/Log.h" +#include "Engine/Animations/CurveSerialization.h" #include "Engine/Serialization/WriteStream.h" #include "Engine/Debug/Exceptions/ArgumentNullException.h" #include "Engine/Debug/Exceptions/ArgumentOutOfRangeException.h" @@ -753,7 +754,7 @@ bool ModelData::Pack2SkinnedModelHeader(WriteStream* stream) const stream->Write(&sphere); // TODO: calculate Sphere and Box at once - make it faster using SSE - + // Blend Shapes const int32 blendShapes = mesh.BlendShapes.Count(); stream->WriteUint16(blendShapes); @@ -830,9 +831,9 @@ bool ModelData::Pack2AnimationHeader(WriteStream* stream) const auto& anim = Animation.Channels[i]; stream->WriteString(anim.NodeName, 172); - anim.Position.Serialize(*stream); - anim.Rotation.Serialize(*stream); - anim.Scale.Serialize(*stream); + Serialization::Serialize(*stream, anim.Position); + Serialization::Serialize(*stream, anim.Rotation); + Serialization::Serialize(*stream, anim.Scale); } return false; diff --git a/Source/Engine/Graphics/Models/SkinnedMesh.cpp b/Source/Engine/Graphics/Models/SkinnedMesh.cpp index 90aa03d86..9e26867ab 100644 --- a/Source/Engine/Graphics/Models/SkinnedMesh.cpp +++ b/Source/Engine/Graphics/Models/SkinnedMesh.cpp @@ -186,22 +186,20 @@ void SkinnedMesh::Draw(const RenderContext& renderContext, const DrawInfo& info, drawCall.Geometry.VertexBuffersOffsets[0] = 0; drawCall.Geometry.VertexBuffersOffsets[1] = 0; drawCall.Geometry.VertexBuffersOffsets[2] = 0; - drawCall.Geometry.StartIndex = 0; - drawCall.Geometry.IndicesCount = _triangles * 3; + drawCall.Draw.StartIndex = 0; + drawCall.Draw.IndicesCount = _triangles * 3; drawCall.InstanceCount = 1; - drawCall.IndirectArgsBuffer = nullptr; - drawCall.IndirectArgsOffset = 0; drawCall.Material = material; drawCall.World = *info.World; - drawCall.PrevWorld = info.DrawState->PrevWorld; drawCall.ObjectPosition = drawCall.World.GetTranslation(); - drawCall.GeometrySize = _box.GetSize(); - drawCall.Lightmap = nullptr; - drawCall.LightmapUVsArea = Rectangle::Empty; - drawCall.Skinning = info.Skinning; + drawCall.Surface.GeometrySize = _box.GetSize(); + drawCall.Surface.PrevWorld = info.DrawState->PrevWorld; + drawCall.Surface.Lightmap = nullptr; + drawCall.Surface.LightmapUVsArea = Rectangle::Empty; + drawCall.Surface.Skinning = info.Skinning; + drawCall.Surface.LODDitherFactor = lodDitherFactor; drawCall.WorldDeterminantSign = Math::FloatSelect(drawCall.World.RotDeterminant(), 1, -1); drawCall.PerInstanceRandom = info.PerInstanceRandom; - drawCall.LODDitherFactor = lodDitherFactor; renderContext.List->AddDrawCall(drawModes, StaticFlags::None, drawCall, entry.ReceiveDecals); } diff --git a/Source/Engine/Graphics/Models/SkinnedMeshDrawData.cpp b/Source/Engine/Graphics/Models/SkinnedMeshDrawData.cpp index 2094be3b2..25c29b11a 100644 --- a/Source/Engine/Graphics/Models/SkinnedMeshDrawData.cpp +++ b/Source/Engine/Graphics/Models/SkinnedMeshDrawData.cpp @@ -4,53 +4,7 @@ #include "Engine/Graphics/GPUDevice.h" #include "Engine/Animations/Config.h" #include "Engine/Core/Math/Matrix.h" - -struct SkinMatrix3x4 -{ - float M[3][4]; - - FORCE_INLINE void SetMatrix(const Matrix& mat) - { - const float* src = mat.Raw; - float* dest = &(M[0][0]); - - dest[0] = src[0]; // [0][0] - dest[1] = src[1]; // [0][1] - dest[2] = src[2]; // [0][2] - dest[3] = src[3]; // [0][3] - - dest[4] = src[4]; // [1][0] - dest[5] = src[5]; // [1][1] - dest[6] = src[6]; // [1][2] - dest[7] = src[7]; // [1][3] - - dest[8] = src[8]; // [2][0] - dest[9] = src[9]; // [2][1] - dest[10] = src[10]; // [2][2] - dest[11] = src[11]; // [2][3] - } - - FORCE_INLINE void SetMatrixTranspose(const Matrix& mat) - { - const float* src = mat.Raw; - float* dest = &(M[0][0]); - - dest[0] = src[0]; // [0][0] - dest[1] = src[4]; // [1][0] - dest[2] = src[8]; // [2][0] - dest[3] = src[12]; // [3][0] - - dest[4] = src[1]; // [0][1] - dest[5] = src[5]; // [1][1] - dest[6] = src[9]; // [2][1] - dest[7] = src[13]; // [3][1] - - dest[8] = src[2]; // [0][2] - dest[9] = src[6]; // [1][2] - dest[10] = src[10]; // [2][2] - dest[11] = src[14]; // [3][2] - } -}; +#include "Engine/Core/Math/Matrix3x4.h" SkinnedMeshDrawData::~SkinnedMeshDrawData() { @@ -99,7 +53,6 @@ void SkinnedMeshDrawData::SetData(const Matrix* bones, bool dropHistory) LOG(Fatal, "Failed to initialize the skinned mesh bones buffer"); } } - Swap(PrevBoneMatrices, BoneMatrices); } else @@ -109,15 +62,15 @@ void SkinnedMeshDrawData::SetData(const Matrix* bones, bool dropHistory) // Copy bones to the buffer const int32 count = BonesCount; - const int32 PreFetchStride = 2; + const int32 preFetchStride = 2; const Matrix* input = bones; - const auto output = (SkinMatrix3x4*)Data.Get(); - ASSERT(Data.Count() == count * sizeof(SkinMatrix3x4)); + const auto output = (Matrix3x4*)Data.Get(); + ASSERT(Data.Count() == count * sizeof(Matrix3x4)); for (int32 i = 0; i < count; i++) { - SkinMatrix3x4* bone = output + i; - Platform::Prefetch(bone + PreFetchStride); - Platform::Prefetch((byte*)(bone + PreFetchStride) + PLATFORM_CACHE_LINE_SIZE); + Matrix3x4* bone = output + i; + Platform::Prefetch(bone + preFetchStride); + Platform::Prefetch((byte*)(bone + preFetchStride) + PLATFORM_CACHE_LINE_SIZE); bone->SetMatrixTranspose(input[i]); } diff --git a/Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.cpp b/Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.cpp index 2446c5ea6..c97b5e5bf 100644 --- a/Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.cpp +++ b/Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.cpp @@ -3,6 +3,7 @@ #include "ShaderAssetBase.h" #include "ShaderStorage.h" #include "ShaderCacheManager.h" +#include "Engine/Engine/CommandLine.h" #include "Engine/Serialization/MemoryReadStream.h" #include "Engine/ShadowsOfMordor/AtlasChartsPacker.h" @@ -225,8 +226,11 @@ bool ShaderAssetBase::LoadShaderCache(ShaderCacheResult& result) options.SourceLength = sourceLength; options.Profile = shaderProfile; options.Output = &cacheStream; - //options.GenerateDebugData = true; - //options.NoOptimize = true; + if (CommandLine::Options.ShaderDebug) + { + options.GenerateDebugData = true; + options.NoOptimize = true; + } auto& platformDefine = options.Macros.AddOne(); #if PLATFORM_WINDOWS platformDefine.Name = "PLATFORM_WINDOWS"; diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUContextDX11.cpp b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUContextDX11.cpp index 45076f39f..9c9af3145 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUContextDX11.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUContextDX11.cpp @@ -1,16 +1,16 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. -#include "GPUShaderDX11.h" #if GRAPHICS_API_DIRECTX11 #include "GPUContextDX11.h" -#include "Engine/Core/Math/Viewport.h" -#include "Engine/Core/Math/Rectangle.h" +#include "GPUShaderDX11.h" #include "GPUShaderProgramDX11.h" #include "GPUPipelineStateDX11.h" #include "GPUTextureDX11.h" #include "GPUBufferDX11.h" #include "Engine/GraphicsDevice/DirectX/RenderToolsDX.h" +#include "Engine/Core/Math/Viewport.h" +#include "Engine/Core/Math/Rectangle.h" #include "Engine/Profiler/RenderStats.h" #define DX11_CLEAR_SR_ON_STAGE_DISABLE 0 diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp index 64151749b..e9da74a99 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp @@ -91,7 +91,7 @@ GPUDevice* GPUDeviceDX11::Create() else if (CommandLine::Options.D3D11) maxAllowedFeatureLevel = D3D_FEATURE_LEVEL_11_0; #if !USE_EDITOR && PLATFORM_WINDOWS - auto winSettings = WindowsPlatformSettings::Instance(); + auto winSettings = WindowsPlatformSettings::Get(); if (!winSettings->SupportDX11 && !winSettings->SupportDX10) { // Skip if there is no support diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp index 096a0ec11..944833ca3 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp @@ -45,7 +45,7 @@ GPUDevice* GPUDeviceDX12::Create() selectedAdapter.Description.VendorId = GPU_VENDOR_ID_AMD; #else #if !USE_EDITOR && PLATFORM_WINDOWS - auto winSettings = WindowsPlatformSettings::Instance(); + auto winSettings = WindowsPlatformSettings::Get(); if (!winSettings->SupportDX12) { // Skip if there is no support diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp index ea9e6a990..1205cde98 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp @@ -1076,7 +1076,7 @@ GPUDeviceVulkan::GPUDeviceVulkan(ShaderProfile shaderProfile, GPUAdapterVulkan* GPUDevice* GPUDeviceVulkan::Create() { #if !USE_EDITOR && (PLATFORM_WINDOWS || PLATFORM_LINUX) - auto settings = PlatformSettings::Instance(); + auto settings = PlatformSettings::Get(); if (!settings->SupportVulkan) { // Skip if there is no support @@ -1825,7 +1825,9 @@ bool GPUDeviceVulkan::Init() #endif #undef INIT_FUNC VmaAllocatorCreateInfo allocatorInfo = {}; + allocatorInfo.vulkanApiVersion = VK_API_VERSION_1_0; allocatorInfo.physicalDevice = gpu; + allocatorInfo.instance = Instance; allocatorInfo.device = Device; allocatorInfo.pVulkanFunctions = &vulkanFunctions; VALIDATE_VULKAN_RESULT(vmaCreateAllocator(&allocatorInfo, &Allocator)); diff --git a/Source/Engine/Input/Input.cpp b/Source/Engine/Input/Input.cpp index 825e7f6b2..73d5316eb 100644 --- a/Source/Engine/Input/Input.cpp +++ b/Source/Engine/Input/Input.cpp @@ -98,6 +98,12 @@ Delegate Input::ActionTriggered; Array Input::ActionMappings; Array Input::AxisMappings; +void InputSettings::Apply() +{ + Input::ActionMappings = ActionMappings; + Input::AxisMappings = AxisMappings; +} + void Mouse::OnMouseMoved(const Vector2& newPosition) { _prevState.MousePosition = newPosition; diff --git a/Source/Engine/Input/Input.h b/Source/Engine/Input/Input.h index 33852f9ec..4ef7bfffb 100644 --- a/Source/Engine/Input/Input.h +++ b/Source/Engine/Input/Input.h @@ -21,7 +21,6 @@ class InputDevice; API_CLASS(Static) class FLAXENGINE_API Input { DECLARE_SCRIPTING_TYPE_NO_SPAWN(Input); -public: /// /// Gets the mouse (null if platform does not support mouse or it is not connected). diff --git a/Source/Engine/Input/InputSettings.h b/Source/Engine/Input/InputSettings.h index 18718bfab..7771028e8 100644 --- a/Source/Engine/Input/InputSettings.h +++ b/Source/Engine/Input/InputSettings.h @@ -5,14 +5,13 @@ #include "Engine/Core/Config/Settings.h" #include "Engine/Serialization/JsonTools.h" #include "VirtualInput.h" -#include "Input.h" /// /// Input settings container. /// -/// -class InputSettings : public Settings +API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API InputSettings : public SettingsBase { +DECLARE_SCRIPTING_TYPE_MINIMAL(InputSettings); public: /// @@ -27,19 +26,14 @@ public: public: - // [Settings] - void Apply() override - { - Input::ActionMappings = ActionMappings; - Input::AxisMappings = AxisMappings; - } + /// + /// Gets the instance of the settings asset (default value if missing). Object returned by this method is always loaded with valid data to use. + /// + static InputSettings* Get(); + + // [SettingsBase] + void Apply() override; - void RestoreDefault() override - { - ActionMappings.Resize(0); - AxisMappings.Resize(0); - } - void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override { const auto actionMappings = stream.FindMember("ActionMappings"); diff --git a/Source/Engine/Level/Actor.cpp b/Source/Engine/Level/Actor.cpp index f558f25a0..324e6ada1 100644 --- a/Source/Engine/Level/Actor.cpp +++ b/Source/Engine/Level/Actor.cpp @@ -8,7 +8,6 @@ #include "Prefabs/Prefab.h" #include "Prefabs/PrefabManager.h" #include "Engine/Core/Log.h" -#include "Engine/Core/Config/LayersTagsSettings.h" #include "Engine/Scripting/Script.h" #include "Engine/Scripting/ManagedCLR/MClass.h" #include "Engine/Threading/Threading.h" @@ -388,21 +387,19 @@ Actor* Actor::GetChildByPrefabObjectId(const Guid& prefabObjectId) const bool Actor::HasTag(const StringView& tag) const { - return HasTag() && tag == LayersAndTagsSettings::Instance()->Tags[_tag]; + return HasTag() && tag == Level::Tags[_tag]; } const String& Actor::GetLayerName() const { - const auto settings = LayersAndTagsSettings::Instance(); - return settings->Layers[_layer]; + return Level::Layers[_layer]; } const String& Actor::GetTag() const { if (HasTag()) { - const auto settings = LayersAndTagsSettings::Instance(); - return settings->Tags[_tag]; + return Level::Tags[_tag]; } return String::Empty; } @@ -422,13 +419,13 @@ void Actor::SetTagIndex(int32 tagIndex) if (tagIndex == ACTOR_TAG_INVALID) { } - else if (LayersAndTagsSettings::Instance()->Tags.IsEmpty()) + else if (Level::Tags.IsEmpty()) { tagIndex = ACTOR_TAG_INVALID; } else { - tagIndex = tagIndex < 0 ? ACTOR_TAG_INVALID : Math::Min(tagIndex, LayersAndTagsSettings::Instance()->Tags.Count() - 1); + tagIndex = tagIndex < 0 ? ACTOR_TAG_INVALID : Math::Min(tagIndex, Level::Tags.Count() - 1); } if (tagIndex == _tag) return; @@ -446,7 +443,7 @@ void Actor::SetTag(const StringView& tagName) } else { - tagIndex = LayersAndTagsSettings::Instance()->Tags.Find(tagName); + tagIndex = Level::Tags.Find(tagName); if (tagIndex == -1) { LOG(Error, "Cannot change actor tag. Given value is invalid."); @@ -602,6 +599,11 @@ void Actor::SetDirection(const Vector3& value) SetOrientation(orientation); } +void Actor::ResetLocalTransform() +{ + SetLocalTransform(Transform::Identity); +} + void Actor::SetLocalTransform(const Transform& value) { CHECK(!value.IsNanOrInfinity()); @@ -948,11 +950,19 @@ void Actor::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) const auto parent = Scripting::FindObject(parentId); if (_parent != parent) { - if (_parent) - _parent->Children.RemoveKeepOrder(this); - _parent = parent; - if (_parent) - _parent->Children.Add(this); + if (IsDuringPlay()) + { + SetParent(parent, false, false); + } + else + { + if (_parent) + _parent->Children.RemoveKeepOrder(this); + _parent = parent; + if (_parent) + _parent->Children.Add(this); + OnParentChanged(); + } } else if (!parent && parentId.IsValid()) { @@ -973,7 +983,7 @@ void Actor::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) if (tag->value.IsString() && tag->value.GetStringLength()) { const String tagName = tag->value.GetText(); - _tag = LayersAndTagsSettings::Instance()->GetOrAddTag(tagName); + _tag = Level::GetOrAddTag(tagName); } } @@ -1044,6 +1054,10 @@ void Actor::OnDisable() } } +void Actor::OnParentChanged() +{ +} + void Actor::OnTransformChanged() { ASSERT_LOW_LAYER(!_localTransform.IsNanOrInfinity()); @@ -1263,6 +1277,11 @@ Script* Actor::GetScriptByPrefabObjectId(const Guid& prefabObjectId) const return result; } +bool Actor::IsPrefabRoot() const +{ + return _isPrefabRoot != 0; +} + Actor* Actor::FindActor(const StringView& name) const { Actor* result = nullptr; diff --git a/Source/Engine/Level/Actor.cs b/Source/Engine/Level/Actor.cs index 674842a65..2d9c27b9a 100644 --- a/Source/Engine/Level/Actor.cs +++ b/Source/Engine/Level/Actor.cs @@ -4,8 +4,68 @@ using System; namespace FlaxEngine { - partial class Actor : ITransformable, ISceneObject + partial class Actor { + /// + /// Returns true if object is fully static on the scene, otherwise false. + /// + [Unmanaged] + [Tooltip("Returns true if object is fully static on the scene, otherwise false.")] + public bool IsStatic => StaticFlags == FlaxEngine.StaticFlags.FullyStatic; + + /// + /// Returns true if object has static transform. + /// + [Unmanaged] + [Tooltip("Returns true if object has static transform.")] + public bool IsTransformStatic => (StaticFlags & StaticFlags.Transform) == StaticFlags.Transform; + + /// + /// Adds the actor static flags. + /// + /// The flags to add. + [Unmanaged] + [Tooltip("Adds the actor static flags.")] + public void AddStaticFlags(StaticFlags flags) + { + StaticFlags |= flags; + } + + /// + /// Removes the actor static flags. + /// + /// The flags to remove. + [Unmanaged] + [Tooltip("Removes the actor static flags.")] + public void RemoveStaticFlags(StaticFlags flags) + { + StaticFlags &= ~flags; + } + + /// + /// Sets a single static flag to the desire value. + /// + /// The flag to change. + /// The target value of the flag. + [Unmanaged] + [Tooltip("Sets a single static flag to the desire value.")] + public void SetStaticFlag(StaticFlags flag, bool value) + { + StaticFlags = StaticFlags & ~flag | (value ? flag : StaticFlags.None); + } + + /// + /// Returns true if object has given flag(s) set. + /// + /// The flag(s) to check. + /// True if has flag(s) set, otherwise false. + [Unmanaged] + [Tooltip("Returns true if object has given flag(s) set..")] + public bool HasStaticFlag(StaticFlags flag) + { + return (StaticFlags & flag) == flag; + } + /// /// The rotation as Euler angles in degrees. /// @@ -143,7 +203,7 @@ namespace FlaxEngine } /// - /// Finds the script of the given type. + /// Finds the script of the given type from this actor. /// /// Type of the script to search for. Includes any scripts derived from the type. /// The script or null if failed to find. @@ -153,7 +213,7 @@ namespace FlaxEngine } /// - /// Tries to find the script of the given type. + /// Tries to find the script of the given type from this actor. /// /// Type of the script to search for. Includes any scripts derived from the type. /// The returned script, valid only if method returns true. @@ -180,7 +240,7 @@ namespace FlaxEngine } /// - /// Searches for all scripts of a specific type. + /// Searches for all scripts of a specific type from this actor. /// /// Type of the scripts to search for. Includes any scripts derived from the type. /// All scripts matching the specified type. diff --git a/Source/Engine/Level/Actor.h b/Source/Engine/Level/Actor.h index 7621e4a2a..04377fd4f 100644 --- a/Source/Engine/Level/Actor.h +++ b/Source/Engine/Level/Actor.h @@ -79,7 +79,7 @@ public: /// /// Gets the object layer (index). Can be used for selective rendering or ignoring raycasts. /// - API_PROPERTY(Attributes="NoAnimate, EditorDisplay(\"General\"), EditorOrder(-69)") + API_PROPERTY(Attributes="NoAnimate, EditorDisplay(\"General\"), EditorOrder(-69), CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ActorLayerEditor\")") FORCE_INLINE int32 GetLayer() const { return _layer; @@ -123,7 +123,7 @@ public: /// /// Gets the name of the tag. /// - API_PROPERTY(Attributes="NoAnimate, EditorDisplay(\"General\"), EditorOrder(-68)") + API_PROPERTY(Attributes="NoAnimate, EditorDisplay(\"General\"), EditorOrder(-68), CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ActorTagEditor\")") const String& GetTag() const; /// @@ -272,14 +272,14 @@ public: } /// - /// Gets the script of the given type. + /// Gets the script of the given type from this actor. /// /// Type of the script to search for. Includes any scripts derived from the type. /// The script or null. API_FUNCTION() Script* GetScript(const MClass* type) const; /// - /// Gets the script of the given type. + /// Gets the script of the given type from this actor. /// /// The script or null. template @@ -289,14 +289,14 @@ public: } /// - /// Gets the scripts of the given type. + /// Gets the scripts of the given type from this actor. /// /// Type of the script to search for. Includes any scripts derived from the type. /// The scripts. API_FUNCTION() Array GetScripts(const MClass* type) const; /// - /// Gets the scripts of the given type. + /// Gets the scripts of the given type from this actor. /// /// The scripts. template @@ -339,7 +339,7 @@ public: /// /// Returns true if object is fully static on the scene, otherwise false. /// - API_PROPERTY() FORCE_INLINE bool IsStatic() const + FORCE_INLINE bool IsStatic() const { return _staticFlags == StaticFlags::FullyStatic; } @@ -347,7 +347,7 @@ public: /// /// Returns true if object has static transform. /// - API_PROPERTY() FORCE_INLINE bool IsTransformStatic() const + FORCE_INLINE bool IsTransformStatic() const { return (_staticFlags & StaticFlags::Transform) != 0; } @@ -355,7 +355,7 @@ public: /// /// Gets the actor static fags. /// - API_PROPERTY(Attributes="NoAnimate, EditorDisplay(\"General\"), EditorOrder(-80)") + API_PROPERTY(Attributes="NoAnimate, EditorDisplay(\"General\"), EditorOrder(-80), CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ActorStaticFlagsEditor\")") FORCE_INLINE StaticFlags GetStaticFlags() const { return _staticFlags; @@ -367,11 +367,19 @@ public: /// The value to set. API_PROPERTY() virtual void SetStaticFlags(StaticFlags value); + /// + /// Returns true if object has given flag(s) set. + /// + FORCE_INLINE bool HasStaticFlag(StaticFlags flag) const + { + return (_staticFlags & flag) == (int)flag; + } + /// /// Adds the actor static flags. /// /// The flags to add. - API_FUNCTION() FORCE_INLINE void AddStaticFlags(StaticFlags flags) + FORCE_INLINE void AddStaticFlags(StaticFlags flags) { SetStaticFlags(_staticFlags | flags); } @@ -380,7 +388,7 @@ public: /// Removes the actor static flags. /// /// The flags to remove. - API_FUNCTION() FORCE_INLINE void RemoveStaticFlags(StaticFlags flags) + FORCE_INLINE void RemoveStaticFlags(StaticFlags flags) { SetStaticFlags(static_cast(_staticFlags & ~flags)); } @@ -390,7 +398,7 @@ public: /// /// The flag to change. /// The target value of the flag. - API_FUNCTION() FORCE_INLINE void SetStaticFlag(StaticFlags flag, bool value) + FORCE_INLINE void SetStaticFlag(StaticFlags flag, bool value) { SetStaticFlags(static_cast(_staticFlags & ~flag) | (value ? flag : StaticFlags::None)); } @@ -500,10 +508,7 @@ public: /// /// Resets the actor local transform. /// - FORCE_INLINE void ResetLocalTransform() - { - SetLocalTransform(Transform::Identity); - } + void ResetLocalTransform(); /// /// Gets local transform of the actor in parent actor space. @@ -523,7 +528,7 @@ public: /// /// Gets local position of the actor in parent actor space. /// - API_PROPERTY(Attributes="EditorDisplay(\"Transform\", \"Position\"), DefaultValue(typeof(Vector3), \"0,0,0\"), EditorOrder(-30), NoSerialize") + API_PROPERTY(Attributes="EditorDisplay(\"Transform\", \"Position\"), DefaultValue(typeof(Vector3), \"0,0,0\"), EditorOrder(-30), NoSerialize, CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ActorTransformEditor+PositionScaleEditor\")") FORCE_INLINE Vector3 GetLocalPosition() const { return _localTransform.Translation; @@ -538,7 +543,7 @@ public: /// /// Gets local rotation of the actor in parent actor space. /// - API_PROPERTY(Attributes="EditorDisplay(\"Transform\", \"Rotation\"), DefaultValue(typeof(Quaternion), \"0,0,0,1\"), EditorOrder(-20), NoSerialize") + API_PROPERTY(Attributes="EditorDisplay(\"Transform\", \"Rotation\"), DefaultValue(typeof(Quaternion), \"0,0,0,1\"), EditorOrder(-20), NoSerialize, CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ActorTransformEditor+OrientationEditor\")") FORCE_INLINE Quaternion GetLocalOrientation() const { return _localTransform.Orientation; @@ -553,7 +558,7 @@ public: /// /// Gets local scale vector of the actor in parent actor space. /// - API_PROPERTY(Attributes="EditorDisplay(\"Transform\", \"Scale\"), DefaultValue(typeof(Vector3), \"1,1,1\"), Limit(float.MinValue, float.MaxValue, 0.01f), EditorOrder(-10), NoSerialize") + API_PROPERTY(Attributes="EditorDisplay(\"Transform\", \"Scale\"), DefaultValue(typeof(Vector3), \"1,1,1\"), Limit(float.MinValue, float.MaxValue, 0.01f), EditorOrder(-10), NoSerialize, CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ActorTransformEditor+PositionScaleEditor\")") FORCE_INLINE Vector3 GetLocalScale() const { return _localTransform.Scale; @@ -708,15 +713,10 @@ public: /// The script or null. Script* GetScriptByPrefabObjectId(const Guid& prefabObjectId) const; -public: - /// /// Gets a value indicating whether this actor is a prefab instance root object. /// - API_PROPERTY() FORCE_INLINE bool IsPrefabRoot() const - { - return _isPrefabRoot != 0; - } + API_PROPERTY() bool IsPrefabRoot() const; public: @@ -918,9 +918,7 @@ public: /// /// Called when actor parent gets changed. /// - virtual void OnParentChanged() - { - } + virtual void OnParentChanged(); /// /// Called when actor transform gets changed. diff --git a/Source/Engine/Level/Actors/AnimatedModel.cpp b/Source/Engine/Level/Actors/AnimatedModel.cpp index 3d6450716..85ee6d333 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.cpp +++ b/Source/Engine/Level/Actors/AnimatedModel.cpp @@ -327,14 +327,6 @@ void AnimatedModel::SyncParameters() } } -void AnimatedModel::UpdateBounds() -{ - UpdateLocalBounds(); - - BoundingBox::Transform(_boxLocal, _world, _box); - BoundingSphere::FromBox(_box, _sphere); -} - void AnimatedModel::BeginPlay(SceneBeginData* data) { if (SkinnedModel && SkinnedModel->IsLoaded()) @@ -401,6 +393,14 @@ void AnimatedModel::UpdateLocalBounds() _boxLocal = box; } +void AnimatedModel::UpdateBounds() +{ + UpdateLocalBounds(); + + BoundingBox::Transform(_boxLocal, _world, _box); + BoundingSphere::FromBox(_box, _sphere); +} + void AnimatedModel::OnAnimUpdate() { UpdateBounds(); diff --git a/Source/Engine/Level/Actors/AnimatedModel.h b/Source/Engine/Level/Actors/AnimatedModel.h index 641247273..d5d9b4424 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.h +++ b/Source/Engine/Level/Actors/AnimatedModel.h @@ -290,6 +290,8 @@ private: /// void UpdateLocalBounds(); + void UpdateBounds(); + /// /// Called after animation graph update. /// @@ -317,7 +319,6 @@ public: void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; bool IntersectsEntry(int32 entryIndex, const Ray& ray, float& distance, Vector3& normal) override; bool IntersectsEntry(const Ray& ray, float& distance, Vector3& normal, int32& entryIndex) override; - void UpdateBounds() final override; protected: diff --git a/Source/Engine/Level/Actors/BoneSocket.cpp b/Source/Engine/Level/Actors/BoneSocket.cpp index 7673781ff..028b1c507 100644 --- a/Source/Engine/Level/Actors/BoneSocket.cpp +++ b/Source/Engine/Level/Actors/BoneSocket.cpp @@ -104,6 +104,9 @@ void BoneSocket::OnParentChanged() { // Base Actor::OnParentChanged(); + + if (!IsDuringPlay()) + return; _index = -1; UpdateTransformation(); diff --git a/Source/Engine/Level/Actors/BoxBrush.cpp b/Source/Engine/Level/Actors/BoxBrush.cpp index ca38c103d..f02dd5bd3 100644 --- a/Source/Engine/Level/Actors/BoxBrush.cpp +++ b/Source/Engine/Level/Actors/BoxBrush.cpp @@ -272,6 +272,9 @@ void BoxBrush::OnParentChanged() { // Base Actor::OnParentChanged(); + + if (!IsDuringPlay()) + return; // Fire event OnBrushModified(); diff --git a/Source/Engine/Level/Actors/BoxVolume.cpp b/Source/Engine/Level/Actors/BoxVolume.cpp index 6235244bc..91912fa69 100644 --- a/Source/Engine/Level/Actors/BoxVolume.cpp +++ b/Source/Engine/Level/Actors/BoxVolume.cpp @@ -132,8 +132,10 @@ void BoxVolume::OnTransformChanged() // Base Actor::OnTransformChanged(); + const auto prevBounds = _box; OrientedBoundingBox::CreateCentered(Vector3::Zero, _size, _bounds); _bounds.Transform(_transform.GetWorld()); _bounds.GetBoundingBox(_box); BoundingSphere::FromBox(_box, _sphere); + OnBoundsChanged(prevBounds); } diff --git a/Source/Engine/Level/Actors/Decal.cpp b/Source/Engine/Level/Actors/Decal.cpp index 92652ea7d..c7e8475b3 100644 --- a/Source/Engine/Level/Actors/Decal.cpp +++ b/Source/Engine/Level/Actors/Decal.cpp @@ -7,6 +7,7 @@ #include "Engine/Level/Scene/SceneRendering.h" #include "Engine/Graphics/RenderView.h" #include "Engine/Graphics/RenderTask.h" +#include "Engine/Graphics/RenderTools.h" Decal::Decal(const SpawnParams& params) : Actor(params) @@ -66,6 +67,13 @@ void Decal::Draw(RenderContext& renderContext) Material->IsLoaded() && Material->IsDecal()) { + const auto lodView = (renderContext.LodProxyView ? renderContext.LodProxyView : &renderContext.View); + const float screenRadiusSquared = RenderTools::ComputeBoundsScreenRadiusSquared(_sphere.Center, _sphere.Radius, *lodView) * renderContext.View.ModelLODDistanceFactorSqrt; + + // Check if decal is being culled + if (Math::Square(DrawMinScreenSize * 0.5f) > screenRadiusSquared) + return; + renderContext.List->Decals.Add(this); } } @@ -80,6 +88,7 @@ void Decal::Serialize(SerializeStream& stream, const void* otherObj) SERIALIZE(Material); SERIALIZE_MEMBER(Size, _size); SERIALIZE(SortOrder); + SERIALIZE(DrawMinScreenSize); } void Decal::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) @@ -90,6 +99,7 @@ void Decal::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) DESERIALIZE(Material); DESERIALIZE_MEMBER(Size, _size); DESERIALIZE(SortOrder); + DESERIALIZE(DrawMinScreenSize); } bool Decal::IntersectsItself(const Ray& ray, float& distance, Vector3& normal) diff --git a/Source/Engine/Level/Actors/Decal.h b/Source/Engine/Level/Actors/Decal.h index 41a5addea..78a92bfd6 100644 --- a/Source/Engine/Level/Actors/Decal.h +++ b/Source/Engine/Level/Actors/Decal.h @@ -30,9 +30,15 @@ public: /// /// The decal rendering order. The higher values are render later (on top). /// - API_FIELD(Attributes="EditorOrder(20), DefaultValue(0), EditorDisplay(\"Decal\")") + API_FIELD(Attributes="EditorOrder(20), EditorDisplay(\"Decal\")") int32 SortOrder = 0; + /// + /// The minimum screen size for the decal drawing. If the decal size on the screen is smaller than this value then decal will be culled. Set it to higher value to make culling more aggressive. + /// + API_FIELD(Attributes="EditorOrder(30), EditorDisplay(\"Decal\")") + float DrawMinScreenSize = 0.02f; + /// /// Gets the decal bounds size (in local space). /// diff --git a/Source/Engine/Level/Actors/ModelInstanceActor.h b/Source/Engine/Level/Actors/ModelInstanceActor.h index 63c6c4807..7b2a08fd9 100644 --- a/Source/Engine/Level/Actors/ModelInstanceActor.h +++ b/Source/Engine/Level/Actors/ModelInstanceActor.h @@ -19,11 +19,6 @@ public: /// ModelInstanceEntries Entries; - /// - /// Updates the bounds of the actor. - /// - virtual void UpdateBounds() = 0; - /// /// Gets the model entries collection. Each entry contains data how to render meshes using this entry (transformation, material, shadows casting, etc.). /// @@ -42,7 +37,7 @@ public: /// Sets the material to the entry slot. Can be used to override the material of the meshes using this slot. /// /// The material slot entry index. - /// The material to set.. + /// The material to set. API_FUNCTION() void SetMaterial(int32 entryIndex, MaterialBase* material); /// @@ -63,7 +58,10 @@ public: /// When the method completes and returns true, contains the distance of the intersection (if any valid). /// When the method completes, contains the intersection surface normal vector (if any valid). /// True if the actor is intersected by the ray, otherwise false. - API_FUNCTION() virtual bool IntersectsEntry(int32 entryIndex, API_PARAM(Ref) const Ray& ray, API_PARAM(Out) float& distance, API_PARAM(Out) Vector3& normal) = 0; + API_FUNCTION() virtual bool IntersectsEntry(int32 entryIndex, API_PARAM(Ref) const Ray& ray, API_PARAM(Out) float& distance, API_PARAM(Out) Vector3& normal) + { + return false; + } /// /// Determines if there is an intersection between the model actor mesh entry and a ray. @@ -76,7 +74,10 @@ public: /// When the method completes, contains the intersection surface normal vector (if any valid). /// When the method completes, contains the intersection entry index (if any valid). /// True if the actor is intersected by the ray, otherwise false. - API_FUNCTION() virtual bool IntersectsEntry(API_PARAM(Ref) const Ray& ray, API_PARAM(Out) float& distance, API_PARAM(Out) Vector3& normal, API_PARAM(Out) int32& entryIndex) = 0; + API_FUNCTION() virtual bool IntersectsEntry(API_PARAM(Ref) const Ray& ray, API_PARAM(Out) float& distance, API_PARAM(Out) Vector3& normal, API_PARAM(Out) int32& entryIndex) + { + return false; + } protected: diff --git a/Source/Engine/Level/Actors/Skybox.cpp b/Source/Engine/Level/Actors/Skybox.cpp index 48c19191f..e8ea08c89 100644 --- a/Source/Engine/Level/Actors/Skybox.cpp +++ b/Source/Engine/Level/Actors/Skybox.cpp @@ -93,7 +93,7 @@ void Skybox::ApplySky(GPUContext* context, RenderContext& renderContext, const M Platform::MemoryClear(&drawCall, sizeof(DrawCall)); drawCall.World = world; drawCall.ObjectPosition = drawCall.World.GetTranslation(); - drawCall.GeometrySize = _box.GetSize(); + drawCall.Surface.GeometrySize = _box.GetSize(); drawCall.WorldDeterminantSign = Math::FloatSelect(world.RotDeterminant(), 1, -1); drawCall.PerInstanceRandom = GetPerInstanceRandom(); MaterialBase::BindParameters bindParams(context, renderContext, drawCall); diff --git a/Source/Engine/Level/Actors/Spline.cpp b/Source/Engine/Level/Actors/Spline.cpp new file mode 100644 index 000000000..76ecb9653 --- /dev/null +++ b/Source/Engine/Level/Actors/Spline.cpp @@ -0,0 +1,512 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#include "Spline.h" +#include "Engine/Serialization/Serialization.h" +#include "Engine/Animations/CurveSerialization.h" +#include + +Spline::Spline(const SpawnParams& params) + : Actor(params) +{ +} + +bool Spline::GetIsLoop() const +{ + return _loop; +} + +void Spline::SetIsLoop(bool value) +{ + if (_loop != value) + { + _loop = value; + UpdateSpline(); + } +} + +Vector3 Spline::GetSplinePoint(float time) const +{ + Transform t; + Curve.Evaluate(t, time, _loop); + return _transform.LocalToWorld(t.Translation); +} + +Vector3 Spline::GetSplineLocalPoint(float time) const +{ + Transform t; + Curve.Evaluate(t, time, _loop); + return t.Translation; +} + +Quaternion Spline::GetSplineOrientation(float time) const +{ + Transform t; + Curve.Evaluate(t, time, _loop); + Quaternion::Multiply(_transform.Orientation, t.Orientation, t.Orientation); + t.Orientation.Normalize(); + return t.Orientation; +} + +Quaternion Spline::GetSplineLocalOrientation(float time) const +{ + Transform t; + Curve.Evaluate(t, time, _loop); + return t.Orientation; +} + +Vector3 Spline::GetSplineScale(float time) const +{ + Transform t; + Curve.Evaluate(t, time, _loop); + return _transform.Scale * t.Scale; +} + +Vector3 Spline::GetSplineLocalScale(float time) const +{ + Transform t; + Curve.Evaluate(t, time, _loop); + return t.Scale; +} + +Transform Spline::GetSplineTransform(float time) const +{ + Transform t; + Curve.Evaluate(t, time, _loop); + return _transform.LocalToWorld(t); +} + +Transform Spline::GetSplineLocalTransform(float time) const +{ + Transform t; + Curve.Evaluate(t, time, _loop); + return t; +} + +Vector3 Spline::GetSplineDirection(float time) const +{ + return _transform.LocalToWorldVector(GetSplineLocalDirection(time)); +} + +Vector3 Spline::GetSplineLocalDirection(float time) const +{ + if (Curve.GetKeyframes().Count() == 0) + return Vector3::Forward; + Transform t; + Curve.EvaluateFirstDerivative(t, time, _loop); + t.Translation.Normalize(); + return t.Translation; +} + +Vector3 Spline::GetSplinePoint(int32 index) const +{ + CHECK_RETURN(index >= 0 && index < GetSplinePointsCount(), Vector3::Zero) + return _transform.LocalToWorld(Curve[index].Value.Translation); +} + +Vector3 Spline::GetSplineLocalPoint(int32 index) const +{ + CHECK_RETURN(index >= 0 && index < GetSplinePointsCount(), Vector3::Zero) + return Curve[index].Value.Translation; +} + +Transform Spline::GetSplineTransform(int32 index) const +{ + CHECK_RETURN(index >= 0 && index < GetSplinePointsCount(), Transform::Identity) + return _transform.LocalToWorld(Curve[index].Value); +} + +Transform Spline::GetSplineLocalTransform(int32 index) const +{ + CHECK_RETURN(index >= 0 && index < GetSplinePointsCount(), Transform::Identity) + return Curve[index].Value; +} + +Transform Spline::GetSplineTangent(int32 index, bool isIn) +{ + return _transform.LocalToWorld(GetSplineLocalTangent(index, isIn)); +} + +Transform Spline::GetSplineLocalTangent(int32 index, bool isIn) +{ + CHECK_RETURN(index >= 0 && index < GetSplinePointsCount(), Transform::Identity) + const auto& k = Curve[index]; + const auto& tangent = isIn ? k.TangentIn : k.TangentOut; + return tangent + k.Value; +} + +int32 Spline::GetSplinePointsCount() const +{ + return Curve.GetKeyframes().Count(); +} + +float Spline::GetSplineDuration() const +{ + return Curve.GetLength(); +} + +float Spline::GetSplineLength() const +{ + float sum = 0.0f; + const int32 slices = 20; + const float step = 1.0f / (float)slices; + Vector3 prevPoint = Vector3::Zero; + for (int32 i = 1; i < Curve.GetKeyframes().Count(); i++) + { + const auto& a = Curve[i = 1]; + const auto& b = Curve[i]; + + const float length = Math::Abs(b.Time - a.Time); + Vector3 leftTangent, rightTangent; + AnimationUtils::GetTangent(a.Value.Translation, a.TangentOut.Translation, length, leftTangent); + AnimationUtils::GetTangent(b.Value.Translation, b.TangentIn.Translation, length, rightTangent); + + // TODO: implement sth more analytical than brute-force solution + for (int32 slice = 0; slice < slices; slice++) + { + const float t = (float)slice * step; + Vector3 pos; + AnimationUtils::Bezier(a.Value.Translation, leftTangent, rightTangent, b.Value.Translation, t, pos); + pos *= _transform.Scale; + sum += Vector3::DistanceSquared(pos, prevPoint); + prevPoint = pos; + } + } + return Math::Sqrt(sum); +} + +float Spline::GetSplineTime(int32 index) const +{ + CHECK_RETURN(index >= 0 && index < GetSplinePointsCount(), 0.0f) + return Curve[index].Time; +} + +namespace +{ + void FindTimeClosestToPoint(const Vector3& point, const Spline::Keyframe& start, const Spline::Keyframe& end, float& bestDistanceSquared, float& bestTime) + { + // TODO: implement sth more analytical than brute-force solution + const int32 slices = 100; + const float step = 1.0f / (float)slices; + const float length = Math::Abs(end.Time - start.Time); + for (int32 i = 0; i <= slices; i++) + { + const float t = (float)i * step; + Transform result; + Spline::Keyframe::Interpolate(start, end, t, length, result); + const float distanceSquared = Vector3::DistanceSquared(point, result.Translation); + if (distanceSquared < bestDistanceSquared) + { + bestDistanceSquared = distanceSquared; + bestTime = start.Time + t * length; + } + } + } +} + +float Spline::GetSplineTimeClosestToPoint(const Vector3& point) const +{ + const int32 pointsCount = Curve.GetKeyframes().Count(); + if (pointsCount == 0) + return 0.0f; + if (pointsCount == 1) + return Curve[0].Time; + const Vector3 localPoint = _transform.WorldToLocal(point); + float bestDistanceSquared = MAX_float; + float bestTime = 0.0f; + for (int32 i = 1; i < pointsCount; i++) + FindTimeClosestToPoint(localPoint, Curve[i - 1], Curve[i], bestDistanceSquared, bestTime); + return bestTime; +} + +Vector3 Spline::GetSplinePointClosestToPoint(const Vector3& point) const +{ + return GetSplinePoint(GetSplineTimeClosestToPoint(point)); +} + +void Spline::GetSplinePoints(Array& points) const +{ + for (auto& e : Curve.GetKeyframes()) + points.Add(_transform.LocalToWorld(e.Value.Translation)); +} + +void Spline::GetSplineLocalPoints(Array& points) const +{ + for (auto& e : Curve.GetKeyframes()) + points.Add(e.Value.Translation); +} + +void Spline::GetSplinePoints(Array& points) const +{ + for (auto& e : Curve.GetKeyframes()) + points.Add(_transform.LocalToWorld(e.Value)); +} + +void Spline::GetSplineLocalPoints(Array& points) const +{ + for (auto& e : Curve.GetKeyframes()) + points.Add(e.Value); +} + +void Spline::ClearSpline() +{ + if (Curve.IsEmpty()) + return; + Curve.Clear(); + UpdateSpline(); +} + +void Spline::RemoveSplinePoint(int32 index, bool updateSpline) +{ + CHECK(index >= 0 && index < GetSplinePointsCount()); + Curve.GetKeyframes().RemoveAtKeepOrder(index); + if (updateSpline) + UpdateSpline(); +} + +void Spline::SetSplinePoint(int32 index, const Vector3& point, bool updateSpline) +{ + CHECK(index >= 0 && index < GetSplinePointsCount()); + Curve[index].Value.Translation = _transform.WorldToLocal(point); + if (updateSpline) + UpdateSpline(); +} + +void Spline::SetSplineLocalPoint(int32 index, const Vector3& point, bool updateSpline) +{ + CHECK(index >= 0 && index < GetSplinePointsCount()); + Curve[index].Value.Translation = point; + if (updateSpline) + UpdateSpline(); +} + +void Spline::SetSplineTransform(int32 index, const Transform& point, bool updateSpline) +{ + CHECK(index >= 0 && index < GetSplinePointsCount()); + Curve[index].Value = _transform.WorldToLocal(point); + if (updateSpline) + UpdateSpline(); +} + +void Spline::SetSplineLocalTransform(int32 index, const Transform& point, bool updateSpline) +{ + CHECK(index >= 0 && index < GetSplinePointsCount()); + Curve[index].Value = point; + if (updateSpline) + UpdateSpline(); +} + +void Spline::SetSplineTangent(int32 index, const Transform& point, bool isIn, bool updateSpline) +{ + SetSplineLocalTangent(index, _transform.WorldToLocal(point), isIn, updateSpline); +} + +void Spline::SetSplineLocalTangent(int32 index, const Transform& point, bool isIn, bool updateSpline) +{ + CHECK(index >= 0 && index < GetSplinePointsCount()); + auto& k = Curve[index]; + auto& tangent = isIn ? k.TangentIn : k.TangentOut; + tangent = point - k.Value; + if (updateSpline) + UpdateSpline(); +} + +void Spline::SetSplinePointTime(int32 index, float time, bool updateSpline) +{ + CHECK(index >= 0 && index < GetSplinePointsCount()); + Curve[index].Time = time; + if (updateSpline) + UpdateSpline(); +} + +void Spline::AddSplinePoint(const Vector3& point, bool updateSpline) +{ + const Keyframe k(Curve.IsEmpty() ? 0.0f : Curve.GetKeyframes().Last().Time + 1.0f, Transform(_transform.WorldToLocal(point))); + Curve.GetKeyframes().Add(k); + if (updateSpline) + UpdateSpline(); +} + +void Spline::AddSplineLocalPoint(const Vector3& point, bool updateSpline) +{ + const Keyframe k(Curve.IsEmpty() ? 0.0f : Curve.GetKeyframes().Last().Time + 1.0f, Transform(point)); + Curve.GetKeyframes().Add(k); + if (updateSpline) + UpdateSpline(); +} + +void Spline::AddSplinePoint(const Transform& point, bool updateSpline) +{ + const Keyframe k(Curve.IsEmpty() ? 0.0f : Curve.GetKeyframes().Last().Time + 1.0f, _transform.WorldToLocal(point)); + Curve.GetKeyframes().Add(k); + if (updateSpline) + UpdateSpline(); +} + +void Spline::AddSplineLocalPoint(const Transform& point, bool updateSpline) +{ + const Keyframe k(Curve.IsEmpty() ? 0.0f : Curve.GetKeyframes().Last().Time + 1.0f, point); + Curve.GetKeyframes().Add(k); + if (updateSpline) + UpdateSpline(); +} + +void Spline::InsertSplinePoint(int32 index, float time, const Transform& point, bool updateSpline) +{ + const Keyframe k(time, _transform.WorldToLocal(point)); + Curve.GetKeyframes().Insert(index, k); + if (updateSpline) + UpdateSpline(); +} + +void Spline::InsertSplineLocalPoint(int32 index, float time, const Transform& point, bool updateSpline) +{ + const Keyframe k(time, point); + Curve.GetKeyframes().Insert(index, k); + if (updateSpline) + UpdateSpline(); +} + +void Spline::SetTangentsLinear() +{ + const int32 count = Curve.GetKeyframes().Count(); + if (count < 2) + return; + + if (_loop) + Curve[count - 1].Value = Curve[0].Value; + for (int32 i = 0; i < count; i++) + { + auto& k = Curve[i]; + k.TangentIn = k.TangentOut = Transform::Identity; + } + + UpdateSpline(); +} + +void Spline::SetTangentsSmooth() +{ + const int32 count = Curve.GetKeyframes().Count(); + if (count < 2) + return; + + auto& keys = Curve.GetKeyframes(); + const int32 last = count - 2; + if (_loop) + Curve[count - 1].Value = Curve[0].Value; + for (int32 i = 0; i <= last; i++) + { + auto& key = keys[i]; + const auto& prevKey = keys[i == 0 ? (_loop ? last : 0) : i - 1]; + const auto& nextKey = keys[i == last ? (_loop ? 0 : last) : i + 1]; + const float prevTime = _loop && i == 0 ? key.Time : prevKey.Time; + const float nextTime = _loop && i == last ? key.Time : nextKey.Time; + const Vector3 slope = key.Value.Translation - prevKey.Value.Translation + nextKey.Value.Translation - key.Value.Translation; + const Vector3 tangent = slope / Math::Max(nextTime - prevTime, ZeroTolerance); + key.TangentIn.Translation = -tangent; + key.TangentOut.Translation = tangent; + } + + UpdateSpline(); +} + +void Spline::UpdateSpline() +{ + // Always keep last point in the loop + const int32 count = Curve.GetKeyframes().Count(); + if (_loop && count > 1) + { + auto& first = Curve[0]; + auto& last = Curve[count - 1]; + last.Value = first.Value; + last.TangentIn = first.TangentIn; + last.TangentOut = first.TangentOut; + } + + SplineUpdated(); +} + +void Spline::GetKeyframes(MonoArray* data) +{ + Platform::MemoryCopy(mono_array_addr_with_size(data, sizeof(Keyframe), 0), Curve.GetKeyframes().Get(), sizeof(Keyframe) * Curve.GetKeyframes().Count()); +} + +void Spline::SetKeyframes(MonoArray* data) +{ + const auto count = (int32)mono_array_length(data); + Curve.GetKeyframes().Resize(count, false); + Platform::MemoryCopy(Curve.GetKeyframes().Get(), mono_array_addr_with_size(data, sizeof(Keyframe), 0), sizeof(Keyframe) * count); + UpdateSpline(); +} + +#if USE_EDITOR + +#include "Engine/Debug/DebugDraw.h" + +namespace +{ + void DrawSpline(Spline* spline, const Color& color, const Transform& transform, bool depthTest) + { + const int32 count = spline->Curve.GetKeyframes().Count(); + if (count == 0) + return; + Spline::Keyframe* prev = spline->Curve.GetKeyframes().Get(); + Vector3 prevPos = transform.LocalToWorld(prev->Value.Translation); + DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(prevPos, 5.0f), color, 0.0f, depthTest); + for (int32 i = 1; i < count; i++) + { + Spline::Keyframe* next = prev + 1; + Vector3 nextPos = transform.LocalToWorld(next->Value.Translation); + DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(nextPos, 5.0f), 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); + prev = next; + prevPos = nextPos; + } + } +} + +void Spline::OnDebugDraw() +{ + const Color color = GetSplineColor(); + DrawSpline(this, color.AlphaMultiplied(0.7f), _transform, true); + + // Base + Actor::OnDebugDraw(); +} + +void Spline::OnDebugDrawSelected() +{ + const Color color = GetSplineColor(); + DrawSpline(this, color.AlphaMultiplied(0.3f), _transform, false); + + // Base + Actor::OnDebugDrawSelected(); +} + +#endif + +void Spline::Serialize(SerializeStream& stream, const void* otherObj) +{ + // Base + Actor::Serialize(stream, otherObj); + + SERIALIZE_GET_OTHER_OBJ(Spline); + + SERIALIZE_MEMBER(IsLoop, _loop); + SERIALIZE(Curve); +} + +void Spline::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) +{ + // Base + Actor::Deserialize(stream, modifier); + + DESERIALIZE_MEMBER(IsLoop, _loop); + DESERIALIZE(Curve); + + // Initialize spline when loading data during gameplay + if (IsDuringPlay()) + { + UpdateSpline(); + } +} diff --git a/Source/Engine/Level/Actors/Spline.h b/Source/Engine/Level/Actors/Spline.h new file mode 100644 index 000000000..f19f9a443 --- /dev/null +++ b/Source/Engine/Level/Actors/Spline.h @@ -0,0 +1,382 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#include "../Actor.h" +#include "Engine/Animations/Curve.h" + +/// +/// Spline shape actor that defines spatial curve with utility functions for general purpose usage. +/// +API_CLASS() class FLAXENGINE_API Spline : public Actor +{ +DECLARE_SCENE_OBJECT(Spline); + typedef BezierCurveKeyframe Keyframe; +private: + + bool _loop = false; + +public: + + /// + /// The spline bezier curve points represented as series of transformations in 3D space (with tangents). Points are stored in local-space of the actor. + /// + /// Ensure to call UpdateSpline() after editing curve to reflect the changes. + BezierCurve Curve; + + /// + /// Whether to use spline as closed loop. In that case, ensure to place start and end at the same location. + /// + API_PROPERTY(Attributes="EditorOrder(0), EditorDisplay(\"Spline\")") + bool GetIsLoop() const; + + /// + /// Whether to use spline as closed loop. In that case, ensure to place start and end at the same location. + /// + API_PROPERTY() void SetIsLoop(bool value); + +public: + + /// + /// Evaluates the spline curve at the given time and calculates the point location in 3D (world-space). + /// + /// The time value. Can be negative or larger than curve length (curve will loop or clamp). + /// The calculated curve point location (world-space). + API_FUNCTION() Vector3 GetSplinePoint(float time) const; + + /// + /// Evaluates the spline curve at the given time and calculates the point location in 3D (local-space). + /// + /// The time value. Can be negative or larger than curve length (curve will loop or clamp). + /// The calculated curve point location (local-space). + API_FUNCTION() Vector3 GetSplineLocalPoint(float time) const; + + /// + /// Evaluates the spline curve at the given time and calculates the point rotation in 3D (world-space). + /// + /// The time value. Can be negative or larger than curve length (curve will loop or clamp). + /// The calculated curve point rotation (world-space). + API_FUNCTION() Quaternion GetSplineOrientation(float time) const; + + /// + /// Evaluates the spline curve at the given time and calculates the point rotation in 3D (local-space). + /// + /// The time value. Can be negative or larger than curve length (curve will loop or clamp). + /// The calculated curve point rotation (local-space). + API_FUNCTION() Quaternion GetSplineLocalOrientation(float time) const; + + /// + /// Evaluates the spline curve at the given time and calculates the point scale in 3D (world-space). + /// + /// The time value. Can be negative or larger than curve length (curve will loop or clamp). + /// The calculated curve point scale (world-space). + API_FUNCTION() Vector3 GetSplineScale(float time) const; + + /// + /// Evaluates the spline curve at the given time and calculates the point scale in 3D (local-space). + /// + /// The time value. Can be negative or larger than curve length (curve will loop or clamp). + /// The calculated curve point scale (local-space). + API_FUNCTION() Vector3 GetSplineLocalScale(float time) const; + + /// + /// Evaluates the spline curve at the given time and calculates the transformation in 3D (world-space). + /// + /// The time value. Can be negative or larger than curve length (curve will loop or clamp). + /// The calculated curve point transformation (world-space). + API_FUNCTION() Transform GetSplineTransform(float time) const; + + /// + /// Evaluates the spline curve at the given time and calculates the transformation in 3D (local-space). + /// + /// The time value. Can be negative or larger than curve length (curve will loop or clamp). + /// The calculated curve point transformation (local-space). + API_FUNCTION() Transform GetSplineLocalTransform(float time) const; + + /// + /// Evaluates the spline curve direction (forward vector, aka position 1st derivative) at the given time in 3D (world-space). + /// + /// The time value. Can be negative or larger than curve length (curve will loop or clamp). + /// The calculated curve direction (world-space). + API_FUNCTION() Vector3 GetSplineDirection(float time) const; + + /// + /// Evaluates the spline curve direction (forward vector, aka position 1st derivative) at the given time in 3D (local-space). + /// + /// The time value. Can be negative or larger than curve length (curve will loop or clamp). + /// The calculated curve direction (local-space). + API_FUNCTION() Vector3 GetSplineLocalDirection(float time) const; + + /// + /// Evaluates the spline curve at the given index (world-space). + /// + /// The curve keyframe index. Zero-based, smaller than GetSplinePointsCount(). + /// The curve point location (world-space). + API_FUNCTION() Vector3 GetSplinePoint(int32 index) const; + + /// + /// Evaluates the spline curve at the given index (local-space). + /// + /// The curve keyframe index. Zero-based, smaller than GetSplinePointsCount(). + /// The curve point location (local-space). + API_FUNCTION() Vector3 GetSplineLocalPoint(int32 index) const; + + /// + /// Evaluates the spline curve at the given index (world-space). + /// + /// The curve keyframe index. Zero-based, smaller than GetSplinePointsCount(). + /// The curve point transformation (world-space). + API_FUNCTION() Transform GetSplineTransform(int32 index) const; + + /// + /// Evaluates the spline curve at the given index (local-space). + /// + /// The curve keyframe index. Zero-based, smaller than GetSplinePointsCount(). + /// The curve point transformation (local-space). + API_FUNCTION() Transform GetSplineLocalTransform(int32 index) const; + + /// + /// Gets the spline curve point tangent at the given index (world-space). + /// + /// Tangents are stored relative to the curve point but this methods converts them to be in world-space. + /// The curve keyframe index. Zero-based, smaller than GetSplinePointsCount(). + /// True if get arrive tangent, otherwise gets leave tangent (in or out). + /// The transformation of the tangent to set (world-space). + API_FUNCTION() Transform GetSplineTangent(int32 index, bool isIn); + + /// + /// Gets the spline curve point tangent at the given index (local-space). + /// + /// Tangents are stored relative to the curve point but this methods converts them to be in local-space of the actor. + /// The curve keyframe index. Zero-based, smaller than GetSplinePointsCount(). + /// True if get arrive tangent, otherwise gets leave tangent (in or out). + /// The transformation of the tangent to set (world-space). + API_FUNCTION() Transform GetSplineLocalTangent(int32 index, bool isIn); + + /// + /// Gets the amount of points in the spline. + /// + API_PROPERTY() int32 GetSplinePointsCount() const; + + /// + /// Gets the total duration of the spline curve (time of the last point). + /// + API_PROPERTY() float GetSplineDuration() const; + + /// + /// Gets the total length of the spline curve (distance between all the points). + /// + API_PROPERTY() float GetSplineLength() const; + + /// + /// Gets the time of the spline keyframe. + /// + /// The curve keyframe index. Zero-based, smaller than GetSplinePointsCount(). + /// The spline time. + API_FUNCTION() float GetSplineTime(int32 index) const; + + /// + /// Calculates the closest point to the given location and returns the spline time at that point. + /// + /// The point in world-space to find the spline point that is closest to it. + /// The spline time. + API_FUNCTION() float GetSplineTimeClosestToPoint(const Vector3& point) const; + + /// + /// Calculates the closest point to the given location. + /// + /// The point in world-space to find the spline point that is closest to it. + /// The spline position. + API_FUNCTION() Vector3 GetSplinePointClosestToPoint(const Vector3& point) const; + + /// + /// Gets the spline curve points list (world-space). + /// + /// The result points collection. + API_FUNCTION() void GetSplinePoints(API_PARAM(Out) Array& points) const; + + /// + /// Gets the spline curve points list (local-space). + /// + /// The result points collection. + API_FUNCTION() void GetSplineLocalPoints(API_PARAM(Out) Array& points) const; + + /// + /// Gets the spline curve points list (world-space). + /// + /// The result points collection. + API_FUNCTION() void GetSplinePoints(API_PARAM(Out) Array& points) const; + + /// + /// Gets the spline curve points list (local-space). + /// + /// The result points collection. + API_FUNCTION() void GetSplineLocalPoints(API_PARAM(Out) Array& points) const; + +public: + + /// + /// Clears the spline to be empty. + /// + API_FUNCTION() void ClearSpline(); + + /// + /// Removes the spline curve point at the given index. + /// + /// The curve keyframe index. Zero-based, smaller than GetSplinePointsCount(). + /// True if update spline after removing the point, otherwise false. + API_FUNCTION() void RemoveSplinePoint(int32 index, bool updateSpline = true); + + /// + /// Sets the spline curve at the given index (world-space). + /// + /// The curve keyframe index. Zero-based, smaller than GetSplinePointsCount(). + /// The location of the point to set (world-space). + /// True if update spline after editing the point, otherwise false. + API_FUNCTION() void SetSplinePoint(int32 index, const Vector3& point, bool updateSpline = true); + + /// + /// Sets the spline curve at the given index (local-space). + /// + /// The curve keyframe index. Zero-based, smaller than GetSplinePointsCount(). + /// The location of the point to set (local-space). + /// True if update spline after editing the point, otherwise false. + API_FUNCTION() void SetSplineLocalPoint(int32 index, const Vector3& point, bool updateSpline = true); + + /// + /// Sets the spline curve at the given index (world-space). + /// + /// The curve keyframe index. Zero-based, smaller than GetSplinePointsCount(). + /// The transformation of the point to set (world-space). + /// True if update spline after editing the point, otherwise false. + API_FUNCTION() void SetSplineTransform(int32 index, const Transform& point, bool updateSpline = true); + + /// + /// Sets the spline curve at the given index (local-space). + /// + /// The curve keyframe index. Zero-based, smaller than GetSplinePointsCount(). + /// The transformation of the point to set (local-space). + /// True if update spline after editing the point, otherwise false. + API_FUNCTION() void SetSplineLocalTransform(int32 index, const Transform& point, bool updateSpline = true); + + /// + /// Sets the spline curve point tangent at the given index (world-space). + /// + /// Tangents are stored relative to the curve point but this methods converts them to be in world-space. + /// The curve keyframe index. Zero-based, smaller than GetSplinePointsCount(). + /// The transformation of the tangent to set (world-space). + /// True if set arrive tangent, otherwise sets leave tangent (in or out). + /// True if update spline after editing the point, otherwise false. + API_FUNCTION() void SetSplineTangent(int32 index, const Transform& point, bool isIn, bool updateSpline = true); + + /// + /// Sets the spline curve point tangent at the given index (local-space). + /// + /// Tangents are stored relative to the curve point but this methods converts them to be in local-space of the actor. + /// The curve keyframe index. Zero-based, smaller than GetSplinePointsCount(). + /// The transformation of the tangent to set (local-space). + /// True if set arrive tangent, otherwise sets leave tangent (in or out). + /// True if update spline after editing the point, otherwise false. + API_FUNCTION() void SetSplineLocalTangent(int32 index, const Transform& point, bool isIn, bool updateSpline = true); + + /// + /// Sets the spline curve point time at the given index (world-space). + /// + /// The curve keyframe index. Zero-based, smaller than GetSplinePointsCount(). + /// The time to set. + /// True if update spline after editing the point, otherwise false. + API_FUNCTION() void SetSplinePointTime(int32 index, float time, bool updateSpline = true); + + /// + /// Adds the point to the spline curve (at the end). + /// + /// The location of the point to add to the curve (world-space). + /// True if update spline after editing the point, otherwise false. + API_FUNCTION() void AddSplinePoint(const Vector3& point, bool updateSpline = true); + + /// + /// Adds the point to the spline curve (at the end). + /// + /// The location of the point to add to the curve (local-space). + /// True if update spline after adding the point, otherwise false. + API_FUNCTION() void AddSplineLocalPoint(const Vector3& point, bool updateSpline = true); + + /// + /// Adds the point to the spline curve (at the end). + /// + /// The transformation of the point to add to the curve (world-space). + /// True if update spline after adding the point, otherwise false. + API_FUNCTION() void AddSplinePoint(const Transform& point, bool updateSpline = true); + + /// + /// Adds the point to the spline curve (at the end). + /// + /// The transformation of the point to add to the curve (local-space). + /// True if update spline after adding the point, otherwise false. + API_FUNCTION() void AddSplineLocalPoint(const Transform& point, bool updateSpline = true); + + /// + /// Inserts the spline curve point at the given index (world-space). + /// + /// The curve keyframe index. Zero-based, smaller than GetSplinePointsCount(). + /// The time value. + /// The location of the point to add to the curve (world-space). + /// True if update spline after removing the point, otherwise false. + API_FUNCTION() void InsertSplinePoint(int32 index, float time, const Transform& point, bool updateSpline = true); + + /// + /// Inserts the spline curve point at the given index (local-space). + /// + /// The curve keyframe index. Zero-based, smaller than GetSplinePointsCount(). + /// The time value. + /// The location of the point to add to the curve (local-space). + /// True if update spline after removing the point, otherwise false. + API_FUNCTION() void InsertSplineLocalPoint(int32 index, float time, const Transform& point, bool updateSpline = true); + + /// + /// Updates the curve tangent points to make curve linear. + /// + API_FUNCTION() void SetTangentsLinear(); + + /// + /// Updates the curve tangent points to make curve smooth. + /// + API_FUNCTION() void SetTangentsSmooth(); + +public: + + /// + /// Called when spline gets updated (eg. after curve modification). + /// + API_EVENT() Action SplineUpdated; + + /// + /// Updates the spline after it was modified. Recreates the collision and/or any cached state that depends on the spline type. + /// + API_FUNCTION() virtual void UpdateSpline(); + +protected: + +#if USE_EDITOR + virtual Color GetSplineColor() + { + return Color::White; + } +#endif + +private: + + // Internal bindings + API_FUNCTION(NoProxy) void GetKeyframes(MonoArray* data); + API_FUNCTION(NoProxy) void SetKeyframes(MonoArray* data); + +public: + + // [Actor] +#if USE_EDITOR + void OnDebugDraw() override; + void OnDebugDrawSelected() override; +#endif + void Serialize(SerializeStream& stream, const void* otherObj) override; + void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; +}; diff --git a/Source/Engine/Level/Actors/SplineModel.cpp b/Source/Engine/Level/Actors/SplineModel.cpp new file mode 100644 index 000000000..84306716b --- /dev/null +++ b/Source/Engine/Level/Actors/SplineModel.cpp @@ -0,0 +1,478 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#include "SplineModel.h" +#include "Spline.h" +#include "Engine/Engine/Engine.h" +#include "Engine/Core/Math/Matrix3x4.h" +#include "Engine/Serialization/Serialization.h" +#include "Engine/Graphics/GPUDevice.h" +#include "Engine/Graphics/RenderTools.h" +#include "Engine/Profiler/ProfilerCPU.h" +#if USE_EDITOR +#include "Editor/Editor.h" +#endif + +#define SPLINE_RESOLUTION 32.0f + +SplineModel::SplineModel(const SpawnParams& params) + : ModelInstanceActor(params) +{ + Model.Changed.Bind(this); + Model.Loaded.Bind(this); +} + +SplineModel::~SplineModel() +{ + SAFE_DELETE_GPU_RESOURCE(_deformationBuffer); + if (_deformationBufferData) + Allocator::Free(_deformationBufferData); +} + +Transform SplineModel::GetPreTransform() const +{ + return _preTransform; +} + +void SplineModel::SetPreTransform(const Transform& value) +{ + if (_preTransform == value) + return; + _preTransform = value; + OnSplineUpdated(); +} + +float SplineModel::GetQuality() const +{ + return _quality; +} + +void SplineModel::SetQuality(float value) +{ + value = Math::Clamp(value, 0.0f, 100.0f); + if (Math::NearEqual(value, _quality)) + return; + _quality = value; + OnSplineUpdated(); +} + +float SplineModel::GetBoundsScale() const +{ + return _boundsScale; +} + +void SplineModel::SetBoundsScale(float value) +{ + if (Math::NearEqual(_boundsScale, value)) + return; + _boundsScale = value; + OnSplineUpdated(); +} + +int32 SplineModel::GetLODBias() const +{ + return static_cast(_lodBias); +} + +void SplineModel::SetLODBias(int32 value) +{ + _lodBias = static_cast(Math::Clamp(value, -100, 100)); +} + +int32 SplineModel::GetForcedLOD() const +{ + return static_cast(_forcedLod); +} + +void SplineModel::SetForcedLOD(int32 value) +{ + _forcedLod = static_cast(Math::Clamp(value, -1, 100)); +} + +void SplineModel::OnModelChanged() +{ + Entries.Release(); + + if (Model && !Model->IsLoaded()) + { + OnSplineUpdated(); + } +} + +void SplineModel::OnModelLoaded() +{ + Entries.SetupIfInvalid(Model); + + OnSplineUpdated(); +} + +void SplineModel::OnSplineUpdated() +{ + // Skip updates when actor is disabled or something is missing + if (!_spline || !Model || !Model->IsLoaded() || !IsActiveInHierarchy() || _spline->GetSplinePointsCount() < 2) + { + _box = BoundingBox(_transform.Translation, _transform.Translation); + BoundingSphere::FromBox(_box, _sphere); + return; + } + PROFILE_CPU(); + + // Setup model instances over the spline segments + const auto& keyframes = _spline->Curve.GetKeyframes(); + const int32 segments = keyframes.Count() - 1; + const int32 chunksPerSegment = Math::Clamp(Math::CeilToInt(SPLINE_RESOLUTION * _quality), 2, 1024); + const float chunksPerSegmentInv = 1.0f / (float)chunksPerSegment; + const Transform splineTransform = GetTransform(); + _instances.Resize(segments, false); + BoundingBox localModelBounds(Vector3::Maximum, Vector3::Minimum); + { + auto& meshes = Model->LODs[0].Meshes; + Vector3 corners[8]; + for (int32 j = 0; j < meshes.Count(); j++) + { + const auto& mesh = meshes[j]; + mesh.GetCorners(corners); + + for (int32 i = 0; i < 8; i++) + { + // Transform mesh corner using pre-transform but use double-precision to prevent issues when rotating model + Vector3 tmp = corners[i] * _preTransform.Scale; + double rotation[4] = { (double)_preTransform.Orientation.X, (double)_preTransform.Orientation.Y, (double)_preTransform.Orientation.Z, (double)_preTransform.Orientation.W }; + const double length = sqrt(rotation[0] * rotation[0] + rotation[1] * rotation[1] + rotation[2] * rotation[2] + rotation[3] * rotation[3]); + const double inv = 1.0 / length; + rotation[0] *= inv; + rotation[1] *= inv; + rotation[2] *= inv; + rotation[3] *= inv; + double pos[3] = { (double)tmp.X, (double)tmp.Y, (double)tmp.Z }; + const double x = rotation[0] + rotation[0]; + const double y = rotation[1] + rotation[1]; + const double z = rotation[2] + rotation[2]; + const double wx = rotation[3] * x; + const double wy = rotation[3] * y; + const double wz = rotation[3] * z; + const double xx = rotation[0] * x; + const double xy = rotation[0] * y; + const double xz = rotation[0] * z; + const double yy = rotation[1] * y; + const double yz = rotation[1] * z; + const double zz = rotation[2] * z; + tmp = Vector3( + (float)(pos[0] * (1.0 - yy - zz) + pos[1] * (xy - wz) + pos[2] * (xz + wy)) + _preTransform.Translation.X, + (float)(pos[0] * (xy + wz) + pos[1] * (1.0 - xx - zz) + pos[2] * (yz - wx)) + _preTransform.Translation.Y, + (float)(pos[0] * (xz - wy) + pos[1] * (yz + wx) + pos[2] * (1.0 - xx - yy)) + _preTransform.Translation.Z); + + localModelBounds.Minimum = Vector3::Min(localModelBounds.Minimum, tmp); + localModelBounds.Maximum = Vector3::Max(localModelBounds.Maximum, tmp); + } + } + } + _meshMinZ = localModelBounds.Minimum.Z; + _meshMaxZ = localModelBounds.Maximum.Z; + Transform chunkLocal, chunkWorld, leftTangent, rightTangent; + Array segmentPoints; + segmentPoints.Resize(chunksPerSegment); + for (int32 segment = 0; segment < segments; segment++) + { + auto& instance = _instances[segment]; + const auto& start = keyframes[segment]; + const auto& end = keyframes[segment + 1]; + const float length = end.Time - start.Time; + AnimationUtils::GetTangent(start.Value, start.TangentOut, length, leftTangent); + AnimationUtils::GetTangent(end.Value, end.TangentIn, length, rightTangent); + + // Find maximum scale over the segment spline and collect the segment positions for bounds + segmentPoints.Clear(); + segmentPoints.Add(end.Value.Translation); + float maxScale = end.Value.Scale.GetAbsolute().MaxValue(); + for (int32 chunk = 0; chunk < chunksPerSegment; chunk++) + { + const float alpha = (float)chunk * chunksPerSegmentInv; + AnimationUtils::Bezier(start.Value, leftTangent, rightTangent, end.Value, alpha, chunkLocal); + splineTransform.LocalToWorld(chunkLocal, chunkWorld); + segmentPoints.Add(chunkWorld.Translation); + maxScale = Math::Max(maxScale, chunkWorld.Scale.GetAbsolute().MaxValue()); + } + BoundingSphere::FromPoints(segmentPoints.Get(), segmentPoints.Count(), instance.Sphere); + instance.Sphere.Radius *= maxScale * _boundsScale; + } + + // Update deformation buffer during next drawing + _deformationDirty = true; + + // Update bounds + _sphere = _instances.First().Sphere; + for (int32 i = 1; i < _instances.Count(); i++) + BoundingSphere::Merge(_sphere, _instances[i].Sphere, _sphere); + BoundingBox::FromSphere(_sphere, _box); +} + +void SplineModel::UpdateDeformationBuffer() +{ + PROFILE_CPU(); + + // Deformation buffer contains precomputed matrices for each chunk of the spline segment (packed with transposed float3x4 matrix) + _deformationDirty = false; + if (!_deformationBuffer) + _deformationBuffer = GPUDevice::Instance->CreateBuffer(GetName()); + const auto& keyframes = _spline->Curve.GetKeyframes(); + const int32 segments = keyframes.Count() - 1; + const int32 chunksPerSegment = Math::Clamp(Math::CeilToInt(SPLINE_RESOLUTION * _quality), 2, 1024); + const int32 count = (chunksPerSegment * segments + 1) * 3; + const uint32 size = count * sizeof(Vector4); + if (_deformationBuffer->GetSize() != size) + { + if (_deformationBufferData) + { + Allocator::Free(_deformationBufferData); + _deformationBufferData = nullptr; + } + if (_deformationBuffer->Init(GPUBufferDescription::Typed(count, PixelFormat::R32G32B32A32_Float, false, IsTransformStatic() ? GPUResourceUsage::Default : GPUResourceUsage::Dynamic))) + { + LOG(Error, "Failed to initialize the spline model {0} deformation buffer.", ToString()); + return; + } + } + if (!_deformationBufferData) + _deformationBufferData = Allocator::Allocate(size); + _chunksPerSegment = (float)chunksPerSegment; + + // Update pre-calculated matrices for spline chunks + auto ptr = (Matrix3x4*)_deformationBufferData; + const float chunksPerSegmentInv = 1.0f / (float)chunksPerSegment; + Matrix m; + Transform transform, leftTangent, rightTangent; + for (int32 segment = 0; segment < segments; segment++) + { + auto& instance = _instances[segment]; + const auto& start = keyframes[segment]; + const auto& end = keyframes[segment + 1]; + const float length = end.Time - start.Time; + AnimationUtils::GetTangent(start.Value, start.TangentOut, length, leftTangent); + AnimationUtils::GetTangent(end.Value, end.TangentIn, length, rightTangent); + for (int32 chunk = 0; chunk < chunksPerSegment; chunk++) + { + const float alpha = (float)chunk * chunksPerSegmentInv; + + // Evaluate transformation at the curve + AnimationUtils::Bezier(start.Value, leftTangent, rightTangent, end.Value, alpha, transform); + + // Apply spline direction (from position 1st derivative) + Vector3 direction; + AnimationUtils::BezierFirstDerivative(start.Value.Translation, leftTangent.Translation, rightTangent.Translation, end.Value.Translation, alpha, direction); + direction.Normalize(); + Quaternion orientation; + if (direction.IsZero()) + orientation = Quaternion::Identity; + else if (Vector3::Dot(direction, Vector3::Up) >= 0.999f) + Quaternion::RotationAxis(Vector3::Left, PI_HALF, orientation); + else + Quaternion::LookRotation(direction, Vector3::Cross(Vector3::Cross(direction, Vector3::Up), direction), orientation); + transform.Orientation = orientation * transform.Orientation; + + // Write transform into deformation buffer + transform.GetWorld(m); + ptr->SetMatrixTranspose(m); + ptr++; + } + instance.RotDeterminant = m.RotDeterminant(); + } + + // Add last transformation to prevent issues when sampling spline deformation buffer with alpha=1 + { + const auto& start = keyframes[segments - 1]; + const auto& end = keyframes[segments]; + const float length = end.Time - start.Time; + const float alpha = 1.0f - ZeroTolerance; // Offset to prevent zero derivative at the end of the curve + AnimationUtils::GetTangent(start.Value, start.TangentOut, length, leftTangent); + AnimationUtils::GetTangent(end.Value, end.TangentIn, length, rightTangent); + AnimationUtils::Bezier(start.Value, leftTangent, rightTangent, end.Value, alpha, transform); + Vector3 direction; + AnimationUtils::BezierFirstDerivative(start.Value.Translation, leftTangent.Translation, rightTangent.Translation, end.Value.Translation, alpha, direction); + direction.Normalize(); + Quaternion orientation; + if (direction.IsZero()) + orientation = Quaternion::Identity; + else if (Vector3::Dot(direction, Vector3::Up) >= 0.999f) + Quaternion::RotationAxis(Vector3::Left, PI_HALF, orientation); + else + Quaternion::LookRotation(direction, Vector3::Cross(Vector3::Cross(direction, Vector3::Up), direction), orientation); + transform.Orientation = orientation * transform.Orientation; + transform.GetWorld(m); + ptr->SetMatrixTranspose(m); + } + + // Flush data with GPU + auto context = GPUDevice::Instance->GetMainContext(); + context->UpdateBuffer(_deformationBuffer, _deformationBufferData, size); + + // Static splines are rarely updated so release scratch memory + if (IsTransformStatic()) + { + Allocator::Free(_deformationBufferData); + _deformationBufferData = nullptr; + } +} + +void SplineModel::OnParentChanged() +{ + if (_spline) + { + _spline->SplineUpdated.Unbind(this); + } + + // Base + Actor::OnParentChanged(); + + _spline = Cast(_parent); + if (_spline) + { + _spline->SplineUpdated.Bind(this); + } + + OnSplineUpdated(); +} + +bool SplineModel::HasContentLoaded() const +{ + return (Model == nullptr || Model->IsLoaded()) && Entries.HasContentLoaded(); +} + +void SplineModel::Draw(RenderContext& renderContext) +{ + const DrawPass actorDrawModes = (DrawPass)(DrawModes & renderContext.View.Pass); + if (!_spline || !Model || !Model->IsLoaded() || !Model->CanBeRendered() || actorDrawModes == DrawPass::None) + return; + auto model = Model.Get(); + if (!Entries.IsValidFor(model)) + Entries.Setup(model); + + // Build mesh deformation buffer for the whole spline + if (_deformationDirty) + UpdateDeformationBuffer(); + + // Draw all segments + DrawCall drawCall; + drawCall.InstanceCount = 1; + drawCall.Deformable.SplineDeformation = _deformationBuffer; + drawCall.Deformable.ChunksPerSegment = _chunksPerSegment; + drawCall.Deformable.MeshMinZ = _meshMinZ; + drawCall.Deformable.MeshMaxZ = _meshMaxZ; + drawCall.Deformable.GeometrySize = _box.GetSize(); + drawCall.PerInstanceRandom = GetPerInstanceRandom(); + _preTransform.GetWorld(drawCall.Deformable.LocalMatrix); + const Transform splineTransform = GetTransform(); + splineTransform.GetWorld(drawCall.World); + drawCall.ObjectPosition = drawCall.World.GetTranslation() + drawCall.Deformable.LocalMatrix.GetTranslation(); + const float worldDeterminantSign = drawCall.World.RotDeterminant() * drawCall.Deformable.LocalMatrix.RotDeterminant(); + for (int32 segment = 0; segment < _instances.Count(); segment++) + { + auto& instance = _instances[segment]; + if (!renderContext.View.CullingFrustum.Intersects(instance.Sphere)) + continue; + drawCall.Deformable.Segment = (float)segment; + + // Select a proper LOD index (model may be culled) + int32 lodIndex; + if (_forcedLod != -1) + { + lodIndex = _forcedLod; + } + else + { + lodIndex = RenderTools::ComputeModelLOD(model, instance.Sphere.Center, instance.Sphere.Radius, renderContext); + if (lodIndex == -1) + continue; + } + lodIndex += _lodBias + renderContext.View.ModelLODBias; + lodIndex = model->ClampLODIndex(lodIndex); + + // Draw + const auto& lod = model->LODs[lodIndex]; + for (int32 i = 0; i < lod.Meshes.Count(); i++) + { + const auto mesh = &lod.Meshes[i]; + + // Cache data + const auto& entry = Entries[mesh->GetMaterialSlotIndex()]; + if (!entry.Visible || !mesh->IsInitialized()) + continue; + const MaterialSlot& slot = model->MaterialSlots[mesh->GetMaterialSlotIndex()]; + + // Check if skip rendering + const auto shadowsMode = static_cast(entry.ShadowsMode & slot.ShadowsMode); + const auto drawModes = static_cast(actorDrawModes & renderContext.View.GetShadowsDrawPassMask(shadowsMode)); + if (drawModes == DrawPass::None) + continue; + + // Select material + MaterialBase* material = nullptr; + if (entry.Material && entry.Material->IsLoaded()) + material = entry.Material; + else if (slot.Material && slot.Material->IsLoaded()) + material = slot.Material; + if (!material || !material->IsDeformable()) + material = GPUDevice::Instance->GetDefaultDeformableMaterial(); + if (!material || !material->IsDeformable()) + continue; + + // Submit draw call + mesh->GetDrawCallGeometry(drawCall); + drawCall.Material = material; + drawCall.WorldDeterminantSign = Math::FloatSelect(worldDeterminantSign * instance.RotDeterminant, 1, -1); + renderContext.List->AddDrawCall(drawModes, _staticFlags, drawCall, entry.ReceiveDecals); + } + } +} + +void SplineModel::DrawGeneric(RenderContext& renderContext) +{ + Draw(renderContext); +} + +bool SplineModel::IntersectsItself(const Ray& ray, float& distance, Vector3& normal) +{ + return false; +} + +void SplineModel::Serialize(SerializeStream& stream, const void* otherObj) +{ + // Base + ModelInstanceActor::Serialize(stream, otherObj); + + SERIALIZE_GET_OTHER_OBJ(SplineModel); + + SERIALIZE_MEMBER(Quality, _quality); + SERIALIZE_MEMBER(BoundsScale, _boundsScale); + SERIALIZE_MEMBER(LODBias, _lodBias); + SERIALIZE_MEMBER(ForcedLOD, _forcedLod); + SERIALIZE_MEMBER(PreTransform, _preTransform) + SERIALIZE(Model); + SERIALIZE(DrawModes); + + stream.JKEY("Buffer"); + stream.Object(&Entries, other ? &other->Entries : nullptr); +} + +void SplineModel::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) +{ + // Base + ModelInstanceActor::Deserialize(stream, modifier); + + DESERIALIZE_MEMBER(Quality, _quality); + DESERIALIZE_MEMBER(BoundsScale, _boundsScale); + DESERIALIZE_MEMBER(LODBias, _lodBias); + DESERIALIZE_MEMBER(ForcedLOD, _forcedLod); + DESERIALIZE_MEMBER(PreTransform, _preTransform); + DESERIALIZE(Model); + DESERIALIZE(DrawModes); + + Entries.DeserializeIfExists(stream, "Buffer", modifier); +} + +void SplineModel::OnTransformChanged() +{ + // Base + ModelInstanceActor::OnTransformChanged(); + + OnSplineUpdated(); +} diff --git a/Source/Engine/Level/Actors/SplineModel.h b/Source/Engine/Level/Actors/SplineModel.h new file mode 100644 index 000000000..6a0393cdc --- /dev/null +++ b/Source/Engine/Level/Actors/SplineModel.h @@ -0,0 +1,128 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#include "ModelInstanceActor.h" +#include "Engine/Content/Assets/Model.h" + +class Spline; + +/// +/// Renders model over the spline segments. +/// +API_CLASS() class FLAXENGINE_API SplineModel : public ModelInstanceActor +{ +DECLARE_SCENE_OBJECT(SplineModel); +private: + + struct Instance + { + BoundingSphere Sphere; + float RotDeterminant; + }; + + float _boundsScale = 1.0f, _quality = 1.0f; + char _lodBias = 0; + char _forcedLod = -1; + bool _deformationDirty = false; + Array _instances; + Transform _preTransform = Transform::Identity; + Spline* _spline = nullptr; + GPUBuffer* _deformationBuffer = nullptr; + void* _deformationBufferData = nullptr; + float _chunksPerSegment, _meshMinZ, _meshMaxZ; + +public: + + ~SplineModel(); + + /// + /// The model asset to draw. + /// + API_FIELD(Attributes="EditorOrder(20), DefaultValue(null), EditorDisplay(\"Model\")") + AssetReference Model; + + /// + /// Gets the transformation applied to the model geometry before placing it over the spline. Can be used to change the way model goes over the spline. + /// + API_PROPERTY(Attributes="EditorOrder(21), EditorDisplay(\"Model\")") + Transform GetPreTransform() const; + + /// + /// Sets the transformation applied to the model geometry before placing it over the spline. Can be used to change the way model goes over the spline. + /// + API_PROPERTY() void SetPreTransform(const Transform& value); + + /// + /// The draw passes to use for rendering this object. + /// + API_FIELD(Attributes="EditorOrder(15), DefaultValue(DrawPass.Default), EditorDisplay(\"Model\")") + DrawPass DrawModes = DrawPass::Default; + + /// + /// Gets the spline model quality scale. Higher values improve the spline representation (better tessellation) but reduce performance. + /// + API_PROPERTY(Attributes="EditorOrder(11), DefaultValue(1.0f), EditorDisplay(\"Model\"), Limit(0.1f, 100.0f, 0.1f)") + float GetQuality() const; + + /// + /// Sets the spline model quality scale. Higher values improve the spline representation (better tessellation) but reduce performance. + /// + API_PROPERTY() void SetQuality(float value); + + /// + /// Gets the model bounds scale. It is useful when using Position Offset to animate the vertices of the object outside of its bounds. + /// + API_PROPERTY(Attributes="EditorOrder(12), DefaultValue(1.0f), EditorDisplay(\"Model\"), Limit(0, 10.0f, 0.1f)") + float GetBoundsScale() const; + + /// + /// Sets the model bounds scale. It is useful when using Position Offset to animate the vertices of the object outside of its bounds. + /// + API_PROPERTY() void SetBoundsScale(float value); + + /// + /// Gets the model Level Of Detail bias value. Allows to increase or decrease rendered model quality. + /// + API_PROPERTY(Attributes="EditorOrder(40), DefaultValue(0), Limit(-100, 100, 0.1f), EditorDisplay(\"Model\", \"LOD Bias\")") + int32 GetLODBias() const; + + /// + /// Sets the model Level Of Detail bias value. Allows to increase or decrease rendered model quality. + /// + API_PROPERTY() void SetLODBias(int32 value); + + /// + /// Gets the model forced Level Of Detail index. Allows to bind the given model LOD to show. Value -1 disables this feature. + /// + API_PROPERTY(Attributes="EditorOrder(50), DefaultValue(-1), Limit(-1, 100, 0.1f), EditorDisplay(\"Model\", \"Forced LOD\")") + int32 GetForcedLOD() const; + + /// + /// Sets the model forced Level Of Detail index. Allows to bind the given model LOD to show. Value -1 disables this feature. + /// + API_PROPERTY() void SetForcedLOD(int32 value); + +private: + + void OnModelChanged(); + void OnModelLoaded(); + void OnSplineUpdated(); + void UpdateDeformationBuffer(); + +public: + + // [ModelInstanceActor] + bool HasContentLoaded() const override; + void Draw(RenderContext& renderContext) override; + void DrawGeneric(RenderContext& renderContext) override; + bool IntersectsItself(const Ray& ray, float& distance, Vector3& normal) override; + void Serialize(SerializeStream& stream, const void* otherObj) override; + void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; + void OnParentChanged() override; + +protected: + + // [ModelInstanceActor] + void OnTransformChanged() override; +}; diff --git a/Source/Engine/Level/Actors/SpotLight.cpp b/Source/Engine/Level/Actors/SpotLight.cpp index 57d4d97f3..c601506c4 100644 --- a/Source/Engine/Level/Actors/SpotLight.cpp +++ b/Source/Engine/Level/Actors/SpotLight.cpp @@ -198,8 +198,8 @@ void SpotLight::OnDebugDrawSelected() Vector3 up = _transform.GetUp(); Vector3 forward = GetDirection(); float radius = GetScaledRadius(); - float discRadius = radius * tan(_outerConeAngle * DegreesToRadians); - float falloffDiscRadius = radius * tan(_innerConeAngle * DegreesToRadians); + float discRadius = radius * Math::Tan(_outerConeAngle * DegreesToRadians); + float falloffDiscRadius = radius * Math::Tan(_innerConeAngle * DegreesToRadians); Vector3 position = GetPosition(); DEBUG_DRAW_LINE(position, position + forward * radius + up * discRadius, color, 0, true); diff --git a/Source/Engine/Level/Actors/StaticModel.h b/Source/Engine/Level/Actors/StaticModel.h index 5ca6e05e4..a255ba4ab 100644 --- a/Source/Engine/Level/Actors/StaticModel.h +++ b/Source/Engine/Level/Actors/StaticModel.h @@ -181,6 +181,7 @@ private: void OnModelChanged(); void OnModelLoaded(); + void UpdateBounds(); public: @@ -193,7 +194,6 @@ public: void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; bool IntersectsEntry(int32 entryIndex, const Ray& ray, float& distance, Vector3& normal) override; bool IntersectsEntry(const Ray& ray, float& distance, Vector3& normal, int32& entryIndex) override; - void UpdateBounds() override; protected: diff --git a/Source/Engine/Level/Level.Build.cs b/Source/Engine/Level/Level.Build.cs index 204ea9e55..8409226dd 100644 --- a/Source/Engine/Level/Level.Build.cs +++ b/Source/Engine/Level/Level.Build.cs @@ -21,7 +21,6 @@ public class Level : EngineModule options.PublicDependencies.Add("Scripting"); options.PublicDependencies.Add("Serialization"); - options.PublicDependencies.Add("Navigation"); if (options.Target.IsEditor) { diff --git a/Source/Engine/Level/Level.cpp b/Source/Engine/Level/Level.cpp index 62d066803..ba4ce4014 100644 --- a/Source/Engine/Level/Level.cpp +++ b/Source/Engine/Level/Level.cpp @@ -9,6 +9,7 @@ #include "Engine/Core/Cache.h" #include "Engine/Core/Collections/CollectionPoolCache.h" #include "Engine/Core/ObjectsRemovalService.h" +#include "Engine/Core/Config/LayersTagsSettings.h" #include "Engine/Debug/Exceptions/ArgumentException.h" #include "Engine/Debug/Exceptions/ArgumentNullException.h" #include "Engine/Debug/Exceptions/InvalidOperationException.h" @@ -97,6 +98,7 @@ public: { } + bool Init() override; void Update() override; void LateUpdate() override; void FixedUpdate() override; @@ -124,6 +126,8 @@ Delegate Level::SceneUnloaded; Action Level::ScriptsReloadStart; Action Level::ScriptsReload; Action Level::ScriptsReloadEnd; +Array Level::Tags; +String Level::Layers[32]; bool LevelImpl::spawnActor(Actor* actor, Actor* parent) { @@ -158,6 +162,15 @@ bool LevelImpl::deleteActor(Actor* actor) return false; } +bool LevelService::Init() +{ + auto& settings = *LayersAndTagsSettings::Get(); + Level::Tags = settings.Tags; + for (int32 i = 0; i < ARRAY_COUNT(Level::Layers); i++) + Level::Layers[i] = settings.Layers[i]; + return false; +} + void LevelService::Update() { PROFILE_CPU(); @@ -642,6 +655,25 @@ void LevelImpl::CallSceneEvent(SceneEventType eventType, Scene* scene, Guid scen } } +int32 Level::GetOrAddTag(const StringView& tag) +{ + int32 index = Tags.Find(tag); + if (index == INVALID_INDEX) + { + index = Tags.Count(); + Tags.AddOne() = tag; + } + return index; +} + +int32 Level::GetNonEmptyLayerNamesCount() +{ + int32 result = 31; + while (result >= 0 && Layers[result].IsEmpty()) + result--; + return result + 1; +} + void Level::callActorEvent(ActorEventType eventType, Actor* a, Actor* b) { PROFILE_CPU(); diff --git a/Source/Engine/Level/Level.h b/Source/Engine/Level/Level.h index 5e274978a..64bef8d27 100644 --- a/Source/Engine/Level/Level.h +++ b/Source/Engine/Level/Level.h @@ -434,6 +434,31 @@ public: /// Output array with only parents static void ConstructParentActorsTreeList(const Array& input, Array& output); +public: + + /// + /// The tags names. + /// + static Array Tags; + + /// + /// The layers names. + /// + static String Layers[32]; + + /// + /// Gets or adds the tag (returns the tag index). + /// + /// The tag. + /// The tag index. + static int32 GetOrAddTag(const StringView& tag); + + /// + /// Gets the amount of non empty layer names (from the beginning, trims the last ones). + /// + /// The layers count. + static int32 GetNonEmptyLayerNamesCount(); + private: // Actor API diff --git a/Source/Engine/Level/Scene/Lightmap.cpp b/Source/Engine/Level/Scene/Lightmap.cpp index b55b603f5..4a2eec796 100644 --- a/Source/Engine/Level/Scene/Lightmap.cpp +++ b/Source/Engine/Level/Scene/Lightmap.cpp @@ -79,7 +79,7 @@ void Lightmap::EnsureSize(int32 size) { // Unlink texture that cannot be loaded LOG(Warning, "Lightmap::EnsureSize failed to load texture"); - texture.Unlink(); + texture = nullptr; } else { @@ -88,7 +88,7 @@ void Lightmap::EnsureSize(int32 size) { // Unlink texture and import new with valid size LOG(Info, "Changing lightmap {0}:{1} size from {2} to {3}", _index, textureIndex, texture->GetTexture()->Size(), size); - texture.Unlink(); + texture = nullptr; } } } diff --git a/Source/Engine/Level/Scene/Scene.cpp b/Source/Engine/Level/Scene/Scene.cpp index 2d48b9f14..d38bd4225 100644 --- a/Source/Engine/Level/Scene/Scene.cpp +++ b/Source/Engine/Level/Scene/Scene.cpp @@ -8,7 +8,10 @@ #include "Engine/Physics/Colliders/MeshCollider.h" #include "Engine/Level/Actors/StaticModel.h" #include "Engine/Level/ActorsCache.h" -#include "Engine/Navigation/NavigationScene.h" +#include "Engine/Navigation/NavigationSettings.h" +#include "Engine/Navigation/NavMeshBoundsVolume.h" +#include "Engine/Navigation/NavMesh.h" +#include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Serialization/Serialization.h" REGISTER_JSON_ASSET(SceneAsset, "FlaxEngine.SceneAsset"); @@ -27,7 +30,6 @@ Scene::Scene(const SpawnParams& params) , Ticking(this) , LightmapsData(this) , CSGData(this) - , Navigation(::New(this)) { // Default name _name = TEXT("Scene"); @@ -42,7 +44,6 @@ Scene::Scene(const SpawnParams& params) Scene::~Scene() { - Delete(Navigation); } LightmapSettings Scene::GetLightmapSettings() const @@ -55,6 +56,31 @@ void Scene::SetLightmapSettings(const LightmapSettings& value) Info.LightmapSettings = value; } +BoundingBox Scene::GetNavigationBounds() +{ + if (NavigationVolumes.IsEmpty()) + return BoundingBox::Empty; + PROFILE_CPU_NAMED("GetNavigationBounds"); + auto box = NavigationVolumes[0]->GetBox(); + for (int32 i = 1; i < NavigationVolumes.Count(); i++) + BoundingBox::Merge(box, NavigationVolumes[i]->GetBox(), box); + return box; +} + +NavMeshBoundsVolume* Scene::FindNavigationBoundsOverlap(const BoundingBox& bounds) +{ + NavMeshBoundsVolume* result = nullptr; + for (int32 i = 0; i < NavigationVolumes.Count(); i++) + { + if (NavigationVolumes[i]->GetBox().Intersects(bounds)) + { + result = NavigationVolumes[i]; + break; + } + } + return result; +} + void Scene::ClearLightmaps() { LightmapsData.ClearLightmaps(); @@ -216,12 +242,6 @@ void Scene::Serialize(SerializeStream& stream, const void* otherObj) // Update scene info object SaveTime = DateTime::NowUTC(); -#if USE_EDITOR - // Save navmesh tiles to asset (if modified) - if (Navigation->IsDataDirty) - Navigation->SaveNavMesh(); -#endif - LightmapsData.SaveLightmaps(Info.Lightmaps); Info.Serialize(stream, other ? &other->Info : nullptr); @@ -230,8 +250,6 @@ void Scene::Serialize(SerializeStream& stream, const void* otherObj) stream.JKEY("CSG"); stream.Object(&CSGData, other ? &other->CSGData : nullptr); } - - SERIALIZE_MEMBER(NavMesh, Navigation->DataAsset); } void Scene::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) @@ -243,15 +261,45 @@ void Scene::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) LightmapsData.LoadLightmaps(Info.Lightmaps); CSGData.DeserializeIfExists(stream, "CSG", modifier); - DESERIALIZE_MEMBER(NavMesh, Navigation->DataAsset); + // [Deprecated on 13.01.2021, expires on 13.01.2023] + if (modifier->EngineBuild <= 6215 && NavigationMeshes.IsEmpty()) + { + const auto e = SERIALIZE_FIND_MEMBER(stream, "NavMesh"); + if (e != stream.MemberEnd()) + { + // Upgrade from old single hidden navmesh data into NavMesh actors on a scene + AssetReference dataAsset; + Serialization::Deserialize(e->value, dataAsset, modifier); + const auto settings = NavigationSettings::Get(); + if (dataAsset && settings->NavMeshes.HasItems()) + { + auto navMesh = New(); + navMesh->SetStaticFlags(StaticFlags::FullyStatic); + navMesh->SetName(TEXT("NavMesh.") + settings->NavMeshes[0].Name); + navMesh->DataAsset = dataAsset; + navMesh->Properties = settings->NavMeshes[0]; + if (IsDuringPlay()) + { + navMesh->SetParent(this, false); + } + else + { + navMesh->_parent = this; + navMesh->_scene = this; + Children.Add(navMesh); + navMesh->CreateManaged(); + } + } + } + } } void Scene::OnDeleteObject() { // Cleanup LightmapsData.UnloadLightmaps(); - CSGData.Model.Unlink(); - CSGData.CollisionData.Unlink(); + CSGData.Model = nullptr; + CSGData.CollisionData = nullptr; // Base Actor::OnDeleteObject(); @@ -309,22 +357,6 @@ void Scene::EndPlay() Actor::EndPlay(); } -void Scene::OnEnable() -{ - // Base - Actor::OnEnable(); - - Navigation->OnEnable(); -} - -void Scene::OnDisable() -{ - Navigation->OnDisable(); - - // Base - Actor::OnDisable(); -} - void Scene::OnTransformChanged() { // Base diff --git a/Source/Engine/Level/Scene/Scene.h b/Source/Engine/Level/Scene/Scene.h index 628726e4b..d3c1502c6 100644 --- a/Source/Engine/Level/Scene/Scene.h +++ b/Source/Engine/Level/Scene/Scene.h @@ -12,8 +12,9 @@ class MeshCollider; class Level; -class NavigationScene; class ReloadScriptsAction; +class NavMeshBoundsVolume; +class NavMesh; /// /// The scene root object that contains a hierarchy of actors. @@ -69,11 +70,6 @@ public: /// CSG::SceneCSGData CSGData; - /// - /// The navigation scene (always valid). - /// - NavigationScene* Navigation; - /// /// Gets the lightmap settings (per scene). /// @@ -85,6 +81,31 @@ public: /// API_PROPERTY() void SetLightmapSettings(const LightmapSettings& value); +public: + + /// + /// The list of registered navigation bounds volumes (in the scene). + /// + Array NavigationVolumes; + + /// + /// The list of registered navigation meshes (in the scene). + /// + Array NavigationMeshes; + + /// + /// Gets the total navigation volumes bounds. + /// + /// The navmesh bounds. + BoundingBox GetNavigationBounds(); + + /// + /// Finds the navigation volume bounds that have intersection with the given world-space bounding box. + /// + /// The bounds. + /// The intersecting volume or null if none found. + NavMeshBoundsVolume* FindNavigationBoundsOverlap(const BoundingBox& bounds); + public: /// @@ -148,12 +169,10 @@ public: protected: - // [Scene] + // [Actor] void PostLoad() override; void PostSpawn() override; void BeginPlay(SceneBeginData* data) override; - void OnEnable() override; - void OnDisable() override; void OnTransformChanged() override; }; diff --git a/Source/Engine/Level/SceneObjectsFactory.cpp b/Source/Engine/Level/SceneObjectsFactory.cpp index 30cd21abc..a5cb27712 100644 --- a/Source/Engine/Level/SceneObjectsFactory.cpp +++ b/Source/Engine/Level/SceneObjectsFactory.cpp @@ -79,7 +79,7 @@ SceneObject* SceneObjectsFactory::Spawn(ISerializable::DeserializeStream& stream if (type) { const ScriptingObjectSpawnParams params(id, type); - obj = (SceneObject*)type.GetType().Class.Spawn(params); + obj = (SceneObject*)type.GetType().Script.Spawn(params); if (obj == nullptr) { LOG(Warning, "Failed to spawn object of type {0}.", type.ToString(true)); @@ -109,7 +109,7 @@ SceneObject* SceneObjectsFactory::Spawn(ISerializable::DeserializeStream& stream if (type) { const ScriptingObjectSpawnParams params(id, type); - obj = (SceneObject*)type.GetType().Class.Spawn(params); + obj = (SceneObject*)type.GetType().Script.Spawn(params); if (obj == nullptr) { LOG(Warning, "Failed to spawn object of type {0}.", type.ToString(true)); @@ -384,7 +384,7 @@ Actor* SceneObjectsFactory::CreateActor(int32 typeId, const Guid& id) if (type) { const ScriptingObjectSpawnParams params(id, type); - const auto result = dynamic_cast(type.GetType().Class.Spawn(params)); + const auto result = dynamic_cast(type.GetType().Script.Spawn(params)); if (result == nullptr) { LOG(Warning, "Failed to spawn object of type {0}.", type.ToString(true)); diff --git a/Source/Engine/Level/SceneQuery.h b/Source/Engine/Level/SceneQuery.h index 4dedf0b8d..245c4c8a7 100644 --- a/Source/Engine/Level/SceneQuery.h +++ b/Source/Engine/Level/SceneQuery.h @@ -3,10 +3,10 @@ #pragma once // Enables locking scenes during scene query execution can provide some safety when using scene queries from other threads but may provide stalls on a main thread -#define SCENE_QUERIES_WITH_LOCK 0 +#define SCENE_QUERIES_WITH_LOCK 1 -#include "Scene/Scene.h" #include "Level.h" +#include "Scene/Scene.h" #if SCENE_QUERIES_WITH_LOCK #include "Engine/Threading/Threading.h" #endif diff --git a/Source/Engine/Level/Spline.cs b/Source/Engine/Level/Spline.cs new file mode 100644 index 000000000..29c65763a --- /dev/null +++ b/Source/Engine/Level/Spline.cs @@ -0,0 +1,63 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +using System; +using System.Runtime.InteropServices; + +namespace FlaxEngine +{ + partial class Spline + { + private BezierCurve.Keyframe[] _keyframes; + + /// + /// Gets or sets the spline keyframes collection. + /// + [Unmanaged] + [Tooltip("Spline keyframes collection.")] + [EditorOrder(10), EditorDisplay("Spline"), Collection(CanReorderItems = false)] + public BezierCurve.Keyframe[] SplineKeyframes + { + get + { + var count = SplinePointsCount; + if (_keyframes == null || _keyframes.Length != count) + _keyframes = new BezierCurve.Keyframe[count]; +#if !BUILD_RELEASE + if (Marshal.SizeOf(typeof(BezierCurve.Keyframe)) != Transform.SizeInBytes * 3 + sizeof(float)) + throw new Exception("Invalid size of BezierCurve keyframe " + Marshal.SizeOf(typeof(BezierCurve.Keyframe)) + " bytes."); +#endif + Internal_GetKeyframes(__unmanagedPtr, _keyframes); + return _keyframes; + } + set + { + if (value == null) + value = Utils.GetEmptyArray.Keyframe>(); + _keyframes = null; + Internal_SetKeyframes(__unmanagedPtr, value); + } + } + + /// + /// Gets the spline keyframe. + /// + /// The spline point index. + /// The keyframe. + public BezierCurve.Keyframe GetSplineKeyframe(int index) + { + return SplineKeyframes[index]; + } + + /// + /// Sets the spline keyframe. + /// + /// The spline point index. + /// The keyframe. + public void SetSplineKeyframe(int index, BezierCurve.Keyframe keyframe) + { + SetSplineLocalTransform(index, keyframe.Value, false); + SetSplineLocalTangent(index, keyframe.Value + keyframe.TangentIn, true, false); + SetSplineLocalTangent(index, keyframe.Value + keyframe.TangentOut, false); + } + } +} diff --git a/Source/Engine/Navigation/NavLink.cpp b/Source/Engine/Navigation/NavLink.cpp index 121bd499f..e0ba84821 100644 --- a/Source/Engine/Navigation/NavLink.cpp +++ b/Source/Engine/Navigation/NavLink.cpp @@ -14,7 +14,6 @@ NavLink::NavLink(const SpawnParams& params) void NavLink::UpdateBounds() { - // Cache bounds const auto start = _transform.LocalToWorld(Start); const auto end = _transform.LocalToWorld(End); BoundingBox::FromPoints(start, end, _box); diff --git a/Source/Engine/Navigation/NavMesh.cpp b/Source/Engine/Navigation/NavMesh.cpp index d0bdba81f..fe7756049 100644 --- a/Source/Engine/Navigation/NavMesh.cpp +++ b/Source/Engine/Navigation/NavMesh.cpp @@ -1,340 +1,169 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. #include "NavMesh.h" -#include "Engine/Core/Log.h" -#include "NavigationScene.h" -#include "Engine/Profiler/ProfilerCPU.h" -#include "Engine/Threading/Threading.h" -#include -#include - -#define MAX_NODES 2048 -#define USE_DATA_LINK 0 -#define USE_NAV_MESH_ALLOC 1 - -NavMesh::NavMesh() -{ - _navMesh = nullptr; - _navMeshQuery = dtAllocNavMeshQuery(); - _tileSize = 0; -} - -NavMesh::~NavMesh() -{ - dtFreeNavMesh(_navMesh); - dtFreeNavMeshQuery(_navMeshQuery); -} - -int32 NavMesh::GetTilesCapacity() const -{ - return _navMesh ? _navMesh->getMaxTiles() : 0; -} - -void NavMesh::SetTileSize(float tileSize) -{ - ScopeLock lock(Locker); - - // Skip if the same or invalid - if (Math::NearEqual(_tileSize, tileSize) || tileSize < 1) - return; - - // Dispose the existing mesh (its invalid) - if (_navMesh) - { - dtFreeNavMesh(_navMesh); - _navMesh = nullptr; - _tiles.Clear(); - } - - _tileSize = tileSize; -} - -void NavMesh::EnsureCapacity(int32 tilesToAddCount) -{ - ScopeLock lock(Locker); - - const int32 newTilesCount = _tiles.Count() + tilesToAddCount; - const int32 capacity = GetTilesCapacity(); - if (newTilesCount <= capacity) - return; - - PROFILE_CPU_NAMED("NavMesh.EnsureCapacity"); - - // Navmesh tiles capacity growing rule - int32 newCapacity = 0; - if (capacity) - { - while (newCapacity < newTilesCount) - { - newCapacity = Math::RoundUpToPowerOf2(newCapacity); - } - } - else - { - newCapacity = 32; - } - - LOG(Info, "Resizing navmesh from {0} to {1} tiles capacity", capacity, newCapacity); - - // Ensure to have size assigned - ASSERT(_tileSize != 0); - - // Free previous data (if any) - if (_navMesh) - { - dtFreeNavMesh(_navMesh); - } - - // Allocate new navmesh - _navMesh = dtAllocNavMesh(); - if (dtStatusFailed(_navMeshQuery->init(_navMesh, MAX_NODES))) - { - LOG(Fatal, "Failed to initialize nav mesh."); - } - - // Prepare parameters - dtNavMeshParams params; - params.orig[0] = 0.0f; - params.orig[1] = 0.0f; - params.orig[2] = 0.0f; - params.tileWidth = _tileSize; - params.tileHeight = _tileSize; - params.maxTiles = newCapacity; - const int32 tilesBits = (int32)Math::Log2((float)Math::RoundUpToPowerOf2(params.maxTiles)); - params.maxPolys = 1 << (22 - tilesBits); - - // Initialize nav mesh - if (dtStatusFailed(_navMesh->init(¶ms))) - { - LOG(Fatal, "Navmesh init failed."); - return; - } - - // Prepare tiles container - _tiles.EnsureCapacity(newCapacity); - - // Restore previous tiles - for (auto& tile : _tiles) - { - const int32 dataSize = tile.Data.Length(); -#if USE_NAV_MESH_ALLOC - const auto flags = DT_TILE_FREE_DATA; - const auto data = (byte*)dtAlloc(dataSize, DT_ALLOC_PERM); - Platform::MemoryCopy(data, tile.Data.Get(), dataSize); -#else - const auto flags = 0; - const auto data = tile.Data.Get(); +#include "NavMeshRuntime.h" +#include "Engine/Level/Scene/Scene.h" +#include "Engine/Serialization/Serialization.h" +#if COMPILE_WITH_ASSETS_IMPORTER +#include "Engine/ContentImporters/AssetsImportingManager.h" +#include "Engine/Serialization/MemoryWriteStream.h" +#if USE_EDITOR +#include "Editor/Editor.h" #endif - if (dtStatusFailed(_navMesh->addTile(data, dataSize, flags, 0, nullptr))) - { - LOG(Warning, "Could not add tile to navmesh."); - } - } -} - -void NavMesh::AddTiles(NavigationScene* scene) -{ - // Skip if no data - ASSERT(scene); - if (scene->Data.Tiles.IsEmpty()) - return; - auto& data = scene->Data; - - PROFILE_CPU_NAMED("NavMesh.AddTiles"); - - ScopeLock lock(Locker); - - // Validate data (must match navmesh) or init navmesh to match the tiles options - if (_navMesh) - { - if (Math::NotNearEqual(data.TileSize, _tileSize)) - { - LOG(Warning, "Cannot add navigation scene tiles to the navmesh. Navmesh tile size: {0}, input tiles size: {1}", _tileSize, data.TileSize); - return; - } - } - else - { - _tileSize = data.TileSize; - } - - // Ensure to have space for new tiles - EnsureCapacity(data.Tiles.Count()); - - // Add new tiles - for (auto& tileData : data.Tiles) - { - AddTileInternal(scene, tileData); - } -} - -void NavMesh::AddTile(NavigationScene* scene, NavMeshTileData& tileData) -{ - ASSERT(scene); - auto& data = scene->Data; - - PROFILE_CPU_NAMED("NavMesh.AddTile"); - - ScopeLock lock(Locker); - - // Validate data (must match navmesh) or init navmesh to match the tiles options - if (_navMesh) - { - if (Math::NotNearEqual(data.TileSize, _tileSize)) - { - LOG(Warning, "Cannot add navigation scene tile to the navmesh. Navmesh tile size: {0}, input tile size: {1}", _tileSize, data.TileSize); - return; - } - } - else - { - _tileSize = data.TileSize; - } - - // Ensure to have space for new tile - EnsureCapacity(1); - - // Add new tile - AddTileInternal(scene, tileData); -} - -bool IsTileFromScene(const NavMesh* navMesh, const NavMeshTile& tile, void* customData) -{ - return tile.Scene == (NavigationScene*)customData; -} - -void NavMesh::RemoveTiles(NavigationScene* scene) -{ - RemoveTiles(IsTileFromScene, scene); -} - -void NavMesh::RemoveTile(int32 x, int32 y, int32 layer) -{ - ScopeLock lock(Locker); - - // Skip if no data - if (!_navMesh) - return; - - PROFILE_CPU_NAMED("NavMesh.RemoveTile"); - - const auto tileRef = _navMesh->getTileRefAt(x, y, layer); - if (tileRef == 0) - { - return; - } - - if (dtStatusFailed(_navMesh->removeTile(tileRef, nullptr, nullptr))) - { - LOG(Warning, "Failed to remove tile from navmesh."); - } - - for (int32 i = 0; i < _tiles.Count(); i++) - { - auto& tile = _tiles[i]; - if (tile.X == x && tile.Y == y && tile.Layer == layer) - { - _tiles.RemoveAt(i); - break; - } - } -} - -void NavMesh::RemoveTiles(bool (* prediction)(const NavMesh* navMesh, const NavMeshTile& tile, void* customData), void* userData) -{ - ScopeLock lock(Locker); - - // Skip if no data - ASSERT(prediction); - if (!_navMesh) - return; - - PROFILE_CPU_NAMED("NavMesh.RemoveTiles"); - - for (int32 i = 0; i < _tiles.Count(); i++) - { - auto& tile = _tiles[i]; - if (prediction(this, tile, userData)) - { - const auto tileRef = _navMesh->getTileRefAt(tile.X, tile.Y, tile.Layer); - if (tileRef == 0) - { - LOG(Warning, "Missing navmesh tile at {0}x{1}, layer: {2}", tile.X, tile.Y, tile.Layer); - } - else - { - if (dtStatusFailed(_navMesh->removeTile(tileRef, nullptr, nullptr))) - { - LOG(Warning, "Failed to remove tile from navmesh."); - } - } - - _tiles.RemoveAt(i--); - } - } -} - -void NavMesh::Dispose() -{ - if (_navMesh) - { - dtFreeNavMesh(_navMesh); - _navMesh = nullptr; - } - _tiles.Resize(0); -} - -void NavMesh::AddTileInternal(NavigationScene* scene, NavMeshTileData& tileData) -{ - // Check if that tile has been added to navmesh - NavMeshTile* tile = nullptr; - const auto tileRef = _navMesh->getTileRefAt(tileData.PosX, tileData.PosY, tileData.Layer); - if (tileRef) - { - // Remove any existing tile at that location - _navMesh->removeTile(tileRef, nullptr, nullptr); - - // Reuse tile data container - for (int32 i = 0; i < _tiles.Count(); i++) - { - auto& e = _tiles[i]; - if (e.X == tileData.PosX && e.Y == tileData.PosY && e.Layer == tileData.Layer) - { - tile = &e; - break; - } - } - } - else - { - // Add tile - tile = &_tiles.AddOne(); - } - ASSERT(tile); - - // Copy tile properties - tile->Scene = scene; - tile->X = tileData.PosX; - tile->Y = tileData.PosY; - tile->Layer = tileData.Layer; -#if USE_DATA_LINK - tile->Data.Link(tileData.Data); -#else - tile->Data.Copy(tileData.Data); #endif - // Add tile to navmesh - const int32 dataSize = tile->Data.Length(); -#if USE_NAV_MESH_ALLOC - const auto flags = DT_TILE_FREE_DATA; - const auto data = (byte*)dtAlloc(dataSize, DT_ALLOC_PERM); - Platform::MemoryCopy(data, tile->Data.Get(), dataSize); -#else - const auto flags = 0; - const auto data = tile->Data.Get(); +NavMesh::NavMesh(const SpawnParams& params) + : Actor(params) + , IsDataDirty(false) +{ + DataAsset.Loaded.Bind(this); +} + +void NavMesh::SaveNavMesh() +{ +#if COMPILE_WITH_ASSETS_IMPORTER + + // Skip if scene is missing + const auto scene = GetScene(); + if (!scene) + return; + +#if USE_EDITOR + // Skip if game is running in editor (eg. game scripts update dynamic navmesh) + if (Editor::IsPlayMode) + return; #endif - if (dtStatusFailed(_navMesh->addTile(data, dataSize, flags, 0, nullptr))) + + // Clear flag + IsDataDirty = false; + + // Check if has no navmesh data generated (someone could just remove navmesh volumes or generate for empty scene) + if (Data.Tiles.IsEmpty()) { - LOG(Warning, "Could not add tile to navmesh."); + DataAsset = nullptr; + return; + } + + // Prepare + Guid assetId = DataAsset.GetID(); + if (!assetId.IsValid()) + assetId = Guid::New(); + const String assetPath = scene->GetDataFolderPath() / TEXT("NavMesh") + Properties.Name + ASSET_FILES_EXTENSION_WITH_DOT; + + // Generate navmesh tiles data + const int32 streamInitialCapacity = Math::RoundUpToPowerOf2((Data.Tiles.Count() + 1) * 1024); + MemoryWriteStream stream(streamInitialCapacity); + Data.Save(stream); + BytesContainer bytesContainer; + bytesContainer.Link(stream.GetHandle(), stream.GetPosition()); + + // Save asset to file + if (AssetsImportingManager::Create(AssetsImportingManager::CreateRawDataTag, assetPath, assetId, (void*)&bytesContainer)) + { + LOG(Warning, "Failed to save navmesh tiles data to file."); + return; + } + + // Link the created asset + DataAsset = assetId; + +#endif +} + +void NavMesh::ClearData() +{ + if (Data.Tiles.HasItems()) + { + IsDataDirty = true; + Data.TileSize = 0.0f; + Data.Tiles.Resize(0); } } + +NavMeshRuntime* NavMesh::GetRuntime(bool createIfMissing) const +{ + return NavMeshRuntime::Get(Properties, createIfMissing); +} + +void NavMesh::AddTiles() +{ + auto navMesh = NavMeshRuntime::Get(Properties, true); + navMesh->AddTiles(this); +} + +void NavMesh::RemoveTiles() +{ + auto navMesh = NavMeshRuntime::Get(Properties, false); + if (navMesh) + navMesh->RemoveTiles(this); +} + +void NavMesh::OnDataAssetLoaded() +{ + // Skip if already has data (prevent reloading navmesh on saving) + if (Data.Tiles.HasItems()) + return; + + const bool isEnabled = IsDuringPlay() && IsActiveInHierarchy(); + + // Remove added tiles + if (isEnabled) + { + RemoveTiles(); + } + + // Load navmesh tiles + BytesContainer data; + data.Link(DataAsset->Data); + Data.Load(data, false); + IsDataDirty = false; + + // Add loaded tiles + if (isEnabled) + { + AddTiles(); + } +} + +void NavMesh::Serialize(SerializeStream& stream, const void* otherObj) +{ + // Base + Actor::Serialize(stream, otherObj); + +#if USE_EDITOR + // Save navmesh tiles to asset (if modified) + if (IsDataDirty) + SaveNavMesh(); +#endif + + SERIALIZE_GET_OTHER_OBJ(NavMesh); + SERIALIZE(DataAsset); + SERIALIZE(Properties); +} + +void NavMesh::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) +{ + // Base + Actor::Deserialize(stream, modifier); + + DESERIALIZE(DataAsset); + DESERIALIZE(Properties); +} + +void NavMesh::OnEnable() +{ + // Base + Actor::OnEnable(); + + GetScene()->NavigationMeshes.Add(this); + AddTiles(); +} + +void NavMesh::OnDisable() +{ + RemoveTiles(); + GetScene()->NavigationMeshes.Remove(this); + + // Base + Actor::OnDisable(); +} diff --git a/Source/Engine/Navigation/NavMesh.h b/Source/Engine/Navigation/NavMesh.h index cdce2cf6b..c9096d323 100644 --- a/Source/Engine/Navigation/NavMesh.h +++ b/Source/Engine/Navigation/NavMesh.h @@ -2,122 +2,84 @@ #pragma once -#include "Engine/Core/Types/BaseTypes.h" -#include "Engine/Platform/CriticalSection.h" #include "NavMeshData.h" +#include "NavigationTypes.h" +#include "Engine/Content/AssetReference.h" +#include "Engine/Content/Assets/RawDataAsset.h" +#include "Engine/Level/Actor.h" -class NavigationScene; -class dtNavMesh; -class dtNavMeshQuery; +class NavMeshBoundsVolume; +class NavMeshRuntime; -class NavMeshTile +/// +/// The navigation mesh actor that holds a navigation data for a scene. +/// +API_CLASS() class FLAXENGINE_API NavMesh : public Actor { +DECLARE_SCENE_OBJECT(NavMesh); public: - int32 X; - int32 Y; - int32 Layer; - NavigationScene* Scene; - BytesContainer Data; -}; + /// + /// The flag used to mark that navigation data has been modified since load. Used to save runtime data to the file on scene serialization. + /// + bool IsDataDirty; -class FLAXENGINE_API NavMesh -{ -private: + /// + /// The navmesh tiles data. + /// + NavMeshData Data; - dtNavMesh* _navMesh; - dtNavMeshQuery* _navMeshQuery; - float _tileSize; - Array _tiles; + /// + /// The cached navmesh data asset. + /// + AssetReference DataAsset; -public: +#if USE_EDITOR - NavMesh(); + /// + /// If checked, the navmesh will be drawn in debug view when showing navigation data. + /// + API_FIELD(Attributes="EditorOrder(1), EditorDisplay(\"Nav Mesh\")") bool ShowDebugDraw = true; - ~NavMesh(); +#endif + + /// + /// The navigation mesh properties. + /// + API_FIELD(Attributes="EditorOrder(10), EditorDisplay(\"Nav Mesh\")") NavMeshProperties Properties; public: /// - /// The NavMesh object locker. + /// Saves the nav mesh tiles data to the asset. Supported only in builds with assets saving enabled (eg. editor) and not during gameplay (eg. design time). /// - CriticalSection Locker; + void SaveNavMesh(); /// - /// Gets the size of the tile (in world-units). Returns zero if not initialized yet. + /// Clears the data. /// - /// The tile size. - FORCE_INLINE float GetTileSize() const - { - return _tileSize; - } - - dtNavMesh* GetNavMesh() const - { - return _navMesh; - } - - dtNavMeshQuery* GetNavMeshQuery() const - { - return _navMeshQuery; - } - - int32 GetTilesCapacity() const; - -public: + void ClearData(); /// - /// Sets the size of the tile (if not assigned). Disposes the mesh if added tiles have different size. + /// Gets the navmesh runtime object that matches with properties. /// - /// The size of the tile. - void SetTileSize(float tileSize); - - /// - /// Ensures the navmesh capacity for adding new tiles. Performs resizing if needed. - /// - /// The new tiles amount. - void EnsureCapacity(int32 tilesToAddCount); - - /// - /// Adds the tiles from the given scene to the runtime navmesh. - /// - /// The navigation scene. - void AddTiles(NavigationScene* scene); - - /// - /// Adds the tile from the given scene to the runtime navmesh. - /// - /// The navigation scene. - /// The tile data. - void AddTile(NavigationScene* scene, NavMeshTileData& tileData); - - /// - /// Removes all the tiles from the navmesh that has been added from the given navigation scene. - /// - /// The scene. - void RemoveTiles(NavigationScene* scene); - - /// - /// Removes the tile from the navmesh. - /// - /// The tile X coordinate. - /// The tile Y coordinate. - /// The tile layer. - void RemoveTile(int32 x, int32 y, int32 layer); - - /// - /// Removes all the tiles that custom prediction callback marks. - /// - /// The prediction callback, returns true for tiles to remove and false for tiles to preserve. - /// The user data passed to the callback method. - void RemoveTiles(bool (*prediction)(const NavMesh* navMesh, const NavMeshTile& tile, void* customData), void* userData); - - /// - /// Releases the navmesh. - /// - void Dispose(); + NavMeshRuntime* GetRuntime(bool createIfMissing = true) const; private: - void AddTileInternal(NavigationScene* scene, NavMeshTileData& tileData); + void AddTiles(); + void RemoveTiles(); + void OnDataAssetLoaded(); + +public: + + // [Actor] + void Serialize(SerializeStream& stream, const void* otherObj) override; + void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; + +protected: + + // [Actor] + void OnEnable() override; + void OnDisable() override; }; diff --git a/Source/Engine/Navigation/NavMeshBoundsVolume.cpp b/Source/Engine/Navigation/NavMeshBoundsVolume.cpp index 34a881ede..700442b2f 100644 --- a/Source/Engine/Navigation/NavMeshBoundsVolume.cpp +++ b/Source/Engine/Navigation/NavMeshBoundsVolume.cpp @@ -2,7 +2,7 @@ #include "NavMeshBoundsVolume.h" #include "Engine/Level/Scene/Scene.h" -#include "NavigationScene.h" +#include "Engine/Serialization/Serialization.h" #if USE_EDITOR #include "Editor/Editor.h" #include "Editor/Managed/ManagedEditor.h" @@ -14,17 +14,35 @@ NavMeshBoundsVolume::NavMeshBoundsVolume(const SpawnParams& params) { } +void NavMeshBoundsVolume::Serialize(SerializeStream& stream, const void* otherObj) +{ + // Base + BoxVolume::Serialize(stream, otherObj); + + SERIALIZE_GET_OTHER_OBJ(NavMeshBoundsVolume); + + SERIALIZE_MEMBER(AgentsMask, AgentsMask.Mask); +} + +void NavMeshBoundsVolume::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) +{ + // Base + BoxVolume::Deserialize(stream, modifier); + + DESERIALIZE_MEMBER(AgentsMask, AgentsMask.Mask); +} + void NavMeshBoundsVolume::OnEnable() { - GetScene()->Navigation->Volumes.Add(this); - // Base Actor::OnEnable(); + + GetScene()->NavigationVolumes.Add(this); } void NavMeshBoundsVolume::OnDisable() { - GetScene()->Navigation->Volumes.Remove(this); + GetScene()->NavigationVolumes.Remove(this); // Base Actor::OnDisable(); diff --git a/Source/Engine/Navigation/NavMeshBoundsVolume.h b/Source/Engine/Navigation/NavMeshBoundsVolume.h index 2846f9e4f..674ba4877 100644 --- a/Source/Engine/Navigation/NavMeshBoundsVolume.h +++ b/Source/Engine/Navigation/NavMeshBoundsVolume.h @@ -3,13 +3,28 @@ #pragma once #include "Engine/Level/Actors/BoxVolume.h" +#include "NavigationTypes.h" /// -/// A special type of volume that defines the areas of the scene in which navigation meshes are generated. +/// A special type of volume that defines the area of the scene in which navigation meshes are generated. /// API_CLASS() class FLAXENGINE_API NavMeshBoundsVolume : public BoxVolume { DECLARE_SCENE_OBJECT(NavMeshBoundsVolume); +public: + + /// + /// The agent types used by this navmesh bounds volume (from navigation settings). Can be used to generate navmesh for a certain set of agents. + /// + API_FIELD(Attributes="EditorDisplay(\"Box Volume\"), EditorOrder(10)") + NavAgentMask AgentsMask; + +public: + + // [BoxVolume] + void Serialize(SerializeStream& stream, const void* otherObj) override; + void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; + protected: // [BoxVolume] diff --git a/Source/Engine/Navigation/NavMeshBuilder.cpp b/Source/Engine/Navigation/NavMeshBuilder.cpp index 2220d2cb6..55319bb9e 100644 --- a/Source/Engine/Navigation/NavMeshBuilder.cpp +++ b/Source/Engine/Navigation/NavMeshBuilder.cpp @@ -3,9 +3,20 @@ #if COMPILE_WITH_NAV_MESH_BUILDER #include "NavMeshBuilder.h" +#include "NavMesh.h" +#include "NavigationSettings.h" +#include "NavMeshBoundsVolume.h" +#include "NavLink.h" +#include "NavModifierVolume.h" +#include "NavMeshRuntime.h" +#include "Engine/Core/Log.h" #include "Engine/Core/Math/BoundingBox.h" +#include "Engine/Core/Math/Int3.h" #include "Engine/Physics/Colliders/BoxCollider.h" +#include "Engine/Physics/Colliders/SphereCollider.h" +#include "Engine/Physics/Colliders/CapsuleCollider.h" #include "Engine/Physics/Colliders/MeshCollider.h" +#include "Engine/Physics/Colliders/SplineCollider.h" #include "Engine/Threading/ThreadPoolTask.h" #include "Engine/Terrain/TerrainPatch.h" #include "Engine/Terrain/Terrain.h" @@ -13,14 +24,6 @@ #include "Engine/Level/Scene/Scene.h" #include "Engine/Level/Level.h" #include "Engine/Level/SceneQuery.h" -#include "Engine/Core/Log.h" -#include "Engine/Core/Math/Int3.h" -#include "NavigationScene.h" -#include "NavigationSettings.h" -#include "NavMeshBoundsVolume.h" -#include "NavMesh.h" -#include "NavLink.h" -#include "Navigation.h" #include #include #include @@ -54,9 +57,17 @@ struct OffMeshLink int32 Id; }; +struct Modifier +{ + BoundingBox Bounds; + NavAreaProperties* NavArea; +}; + struct NavigationSceneRasterization { - BoundingBox TileBounds; + NavMesh* NavMesh; + BoundingBox TileBoundsNavMesh; + Matrix WorldToNavMesh; rcContext* Context; rcConfig* Config; rcHeightfield* Heightfield; @@ -64,15 +75,21 @@ struct NavigationSceneRasterization Array VertexBuffer; Array IndexBuffer; Array* OffMeshLinks; + Array* Modifiers; + const bool IsWorldToNavMeshIdentity; - NavigationSceneRasterization(const BoundingBox& tileBounds, rcContext* context, rcConfig* config, rcHeightfield* heightfield, Array* offMeshLinks) - : TileBounds(tileBounds) + NavigationSceneRasterization(::NavMesh* navMesh, const BoundingBox& tileBoundsNavMesh, const Matrix& worldToNavMesh, rcContext* context, rcConfig* config, rcHeightfield* heightfield, Array* offMeshLinks, Array* modifiers) + : TileBoundsNavMesh(tileBoundsNavMesh) + , WorldToNavMesh(worldToNavMesh) + , IsWorldToNavMeshIdentity(worldToNavMesh.IsIdentity()) { + NavMesh = navMesh; Context = context; Config = config; Heightfield = heightfield; WalkableThreshold = Math::Cos(config->walkableSlopeAngle * DegreesToRadians); OffMeshLinks = offMeshLinks; + Modifiers = modifiers; } void RasterizeTriangles() @@ -83,23 +100,118 @@ struct NavigationSceneRasterization return; // Rasterize triangles - for (int32 i0 = 0; i0 < ib.Count();) + if (IsWorldToNavMeshIdentity) { - auto v0 = vb[ib[i0++]]; - auto v1 = vb[ib[i0++]]; - auto v2 = vb[ib[i0++]]; + // Faster path + for (int32 i0 = 0; i0 < ib.Count();) + { + auto v0 = vb[ib[i0++]]; + auto v1 = vb[ib[i0++]]; + auto v2 = vb[ib[i0++]]; - auto n = Vector3::Cross(v0 - v1, v0 - v2); - n.Normalize(); - const char area = n.Y > WalkableThreshold ? RC_WALKABLE_AREA : 0; - rcRasterizeTriangle(Context, &v0.X, &v1.X, &v2.X, area, *Heightfield); + auto n = Vector3::Cross(v0 - v1, v0 - v2); + n.Normalize(); + const char area = n.Y > WalkableThreshold ? RC_WALKABLE_AREA : 0; + rcRasterizeTriangle(Context, &v0.X, &v1.X, &v2.X, area, *Heightfield); + } + } + else + { + // Transform vertices from world space into the navmesh space + const Matrix worldToNavMesh = WorldToNavMesh; + for (int32 i0 = 0; i0 < ib.Count();) + { + auto v0 = Vector3::Transform(vb[ib[i0++]], worldToNavMesh); + auto v1 = Vector3::Transform(vb[ib[i0++]], worldToNavMesh); + auto v2 = Vector3::Transform(vb[ib[i0++]], worldToNavMesh); + + auto n = Vector3::Cross(v0 - v1, v0 - v2); + n.Normalize(); + const char area = n.Y > WalkableThreshold ? RC_WALKABLE_AREA : RC_NULL_AREA; + rcRasterizeTriangle(Context, &v0.X, &v1.X, &v2.X, area, *Heightfield); + } + } + } + + static void TriangulateBox(Array& vb, Array& ib, const OrientedBoundingBox& box) + { + vb.Resize(8); + box.GetCorners(vb.Get()); + ib.Add(BoxTrianglesIndicesCache, 36); + } + + static void TriangulateBox(Array& vb, Array& ib, const BoundingBox& box) + { + vb.Resize(8); + box.GetCorners(vb.Get()); + ib.Add(BoxTrianglesIndicesCache, 36); + } + + static void TriangulateSphere(Array& vb, Array& ib, const BoundingSphere& sphere) + { + const int32 sphereResolution = 12; + const int32 verticalSegments = sphereResolution; + const int32 horizontalSegments = sphereResolution * 2; + + // Generate vertices for unit sphere + Vector3 vertices[(verticalSegments + 1) * (horizontalSegments + 1)]; + int32 vertexCount = 0; + for (int32 j = 0; j <= horizontalSegments; j++) + vertices[vertexCount++] = Vector3(0, -1, 0); + for (int32 i = 1; i < verticalSegments; i++) + { + const float latitude = (float)i * PI / verticalSegments - PI / 2.0f; + const float dy = Math::Sin(latitude); + const float dxz = Math::Cos(latitude); + auto& firstHorizontalVertex = vertices[vertexCount++]; + firstHorizontalVertex = Vector3(0, dy, dxz); + for (int32 j = 1; j < horizontalSegments; j++) + { + const float longitude = (float)j * 2.0f * PI / horizontalSegments; + const float dx = Math::Sin(longitude) * dxz; + const float dz = Math::Cos(longitude) * dxz; + vertices[vertexCount++] = Vector3(dx, dy, dz); + } + vertices[vertexCount++] = firstHorizontalVertex; + } + for (int32 j = 0; j <= horizontalSegments; j++) + vertices[vertexCount++] = Vector3(0, 1, 0); + + // Transform vertices into world space vertex buffer + vb.Resize(vertexCount); + for (int32 i = 0; i < vertexCount; i++) + vb[i] = sphere.Center + vertices[i] * sphere.Radius; + + // Generate index buffer + const int32 stride = horizontalSegments + 1; + int32 indexCount = 0; + ib.Resize(verticalSegments * (horizontalSegments + 1) * 6); + for (int32 i = 0; i < verticalSegments; i++) + { + const int32 nextI = i + 1; + for (int32 j = 0; j <= horizontalSegments; j++) + { + const int32 nextJ = (j + 1) % stride; + + ib[indexCount++] = i * stride + j; + ib[indexCount++] = nextI * stride + j; + ib[indexCount++] = i * stride + nextJ; + + ib[indexCount++] = i * stride + nextJ; + ib[indexCount++] = nextI * stride + j; + ib[indexCount++] = nextI * stride + nextJ; + } } } static bool Walk(Actor* actor, NavigationSceneRasterization& e) { // Early out if object is not intersecting with the tile bounds or is not using navigation - if (!actor->GetIsActive() || !(actor->GetStaticFlags() & StaticFlags::Navigation) || !actor->GetBox().Intersects(e.TileBounds)) + if (!actor->GetIsActive() || !(actor->GetStaticFlags() & StaticFlags::Navigation)) + return true; + BoundingBox actorBoxNavMesh; + BoundingBox::Transform(actor->GetBox(), e.WorldToNavMesh, actorBoxNavMesh); + if (!actorBoxNavMesh.Intersects(e.TileBoundsNavMesh)) return true; // Prepare buffers (for triangles) @@ -113,12 +225,26 @@ struct NavigationSceneRasterization { PROFILE_CPU_NAMED("BoxCollider"); - OrientedBoundingBox box = boxCollider->GetOrientedBox(); + const OrientedBoundingBox box = boxCollider->GetOrientedBox(); + TriangulateBox(vb, ib, box); - vb.Resize(8); - box.GetCorners(vb.Get()); + e.RasterizeTriangles(); + } + else if (const auto* sphereCollider = dynamic_cast(actor)) + { + PROFILE_CPU_NAMED("SphereCollider"); - ib.Add(BoxTrianglesIndicesCache, 36); + const BoundingSphere sphere = sphereCollider->GetSphere(); + TriangulateSphere(vb, ib, sphere); + + e.RasterizeTriangles(); + } + else if (const auto* capsuleCollider = dynamic_cast(actor)) + { + PROFILE_CPU_NAMED("CapsuleCollider"); + + const BoundingBox box = capsuleCollider->GetBox(); + TriangulateBox(vb, ib, box); e.RasterizeTriangles(); } @@ -127,13 +253,25 @@ struct NavigationSceneRasterization PROFILE_CPU_NAMED("MeshCollider"); auto collisionData = meshCollider->CollisionData.Get(); - if (!collisionData || collisionData->WaitForLoaded(1000.0f)) + if (!collisionData || collisionData->WaitForLoaded()) return true; collisionData->ExtractGeometry(vb, ib); e.RasterizeTriangles(); } + else if (const auto* splineCollider = dynamic_cast(actor)) + { + PROFILE_CPU_NAMED("SplineCollider"); + + auto collisionData = splineCollider->CollisionData.Get(); + if (!collisionData || collisionData->WaitForLoaded()) + return true; + + splineCollider->ExtractGeometry(vb, ib); + + e.RasterizeTriangles(); + } else if (const auto* terrain = dynamic_cast(actor)) { PROFILE_CPU_NAMED("Terrain"); @@ -141,7 +279,9 @@ struct NavigationSceneRasterization for (int32 patchIndex = 0; patchIndex < terrain->GetPatchesCount(); patchIndex++) { const auto patch = terrain->GetPatch(patchIndex); - if (!patch->GetBounds().Intersects(e.TileBounds)) + BoundingBox patchBoundsNavMesh; + BoundingBox::Transform(patch->GetBounds(), e.WorldToNavMesh, patchBoundsNavMesh); + if (!patchBoundsNavMesh.Intersects(e.TileBoundsNavMesh)) continue; patch->ExtractCollisionGeometry(vb, ib); @@ -155,60 +295,78 @@ struct NavigationSceneRasterization OffMeshLink link; link.Start = navLink->GetTransform().LocalToWorld(navLink->Start); + Vector3::Transform(link.Start, e.WorldToNavMesh, link.Start); link.End = navLink->GetTransform().LocalToWorld(navLink->End); + Vector3::Transform(link.End, e.WorldToNavMesh, link.End); link.Radius = navLink->Radius; link.BiDir = navLink->BiDirectional; link.Id = GetHash(navLink->GetID()); e.OffMeshLinks->Add(link); } + else if (const auto* navModifierVolume = dynamic_cast(actor)) + { + if (navModifierVolume->AgentsMask.IsNavMeshSupported(e.NavMesh->Properties)) + { + PROFILE_CPU_NAMED("NavModifierVolume"); - // TODO: nav mesh for capsule collider - // TODO: nav mesh for sphere collider + Modifier modifier; + OrientedBoundingBox bounds = navModifierVolume->GetOrientedBox(); + bounds.Transform(e.WorldToNavMesh); + bounds.GetBoundingBox(modifier.Bounds); + modifier.NavArea = navModifierVolume->GetNavArea(); + + e.Modifiers->Add(modifier); + } + } return true; } }; -void RasterizeGeometry(const BoundingBox& tileBounds, rcContext* context, rcConfig* config, rcHeightfield* heightfield, Array* offMeshLinks) +void RasterizeGeometry(NavMesh* navMesh, const BoundingBox& tileBoundsNavMesh, const Matrix& worldToNavMesh, rcContext* context, rcConfig* config, rcHeightfield* heightfield, Array* offMeshLinks, Array* modifiers) { PROFILE_CPU_NAMED("RasterizeGeometry"); - NavigationSceneRasterization rasterization(tileBounds, context, config, heightfield, offMeshLinks); + NavigationSceneRasterization rasterization(navMesh, tileBoundsNavMesh, worldToNavMesh, context, config, heightfield, offMeshLinks, modifiers); Function treeWalkFunction(NavigationSceneRasterization::Walk); SceneQuery::TreeExecute(treeWalkFunction, rasterization); } // Builds navmesh tile bounds and check if there are any valid navmesh volumes at that tile location // Returns true if tile is intersecting with any navmesh bounds volume actor - which means tile is in use -bool GetNavMeshTileBounds(NavigationScene* scene, int32 x, int32 y, float tileSize, BoundingBox& tileBounds) +bool GetNavMeshTileBounds(Scene* scene, NavMesh* navMesh, int32 x, int32 y, float tileSize, BoundingBox& tileBoundsNavMesh, const Matrix& worldToNavMesh) { // Build initial tile bounds (with infinite extent) - tileBounds.Minimum.X = x * tileSize; - tileBounds.Minimum.Y = -NAV_MESH_TILE_MAX_EXTENT; - tileBounds.Minimum.Z = y * tileSize; - tileBounds.Maximum.X = tileBounds.Minimum.X + tileSize; - tileBounds.Maximum.Y = NAV_MESH_TILE_MAX_EXTENT; - tileBounds.Maximum.Z = tileBounds.Minimum.Z + tileSize; + tileBoundsNavMesh.Minimum.X = (float)x * tileSize; + tileBoundsNavMesh.Minimum.Y = -NAV_MESH_TILE_MAX_EXTENT; + tileBoundsNavMesh.Minimum.Z = (float)y * tileSize; + tileBoundsNavMesh.Maximum.X = tileBoundsNavMesh.Minimum.X + tileSize; + tileBoundsNavMesh.Maximum.Y = NAV_MESH_TILE_MAX_EXTENT; + tileBoundsNavMesh.Maximum.Z = tileBoundsNavMesh.Minimum.Z + tileSize; // Check if any navmesh volume intersects with the tile bool foundAnyVolume = false; Vector2 rangeY; - for (int32 i = 0; i < scene->Volumes.Count(); i++) + for (int32 i = 0; i < scene->NavigationVolumes.Count(); i++) { - const auto volume = scene->Volumes[i]; + const auto volume = scene->NavigationVolumes[i]; + if (!volume->AgentsMask.IsNavMeshSupported(navMesh->Properties)) + continue; const auto& volumeBounds = volume->GetBox(); - if (volumeBounds.Intersects(tileBounds)) + BoundingBox volumeBoundsNavMesh; + BoundingBox::Transform(volumeBounds, worldToNavMesh, volumeBoundsNavMesh); + if (volumeBoundsNavMesh.Intersects(tileBoundsNavMesh)) { if (foundAnyVolume) { - rangeY.X = Math::Min(rangeY.X, volumeBounds.Minimum.Y); - rangeY.Y = Math::Max(rangeY.Y, volumeBounds.Maximum.Y); + rangeY.X = Math::Min(rangeY.X, volumeBoundsNavMesh.Minimum.Y); + rangeY.Y = Math::Max(rangeY.Y, volumeBoundsNavMesh.Maximum.Y); } else { - rangeY.X = volumeBounds.Minimum.Y; - rangeY.Y = volumeBounds.Maximum.Y; + rangeY.X = volumeBoundsNavMesh.Minimum.Y; + rangeY.Y = volumeBoundsNavMesh.Maximum.Y; } foundAnyVolume = true; } @@ -217,45 +375,45 @@ bool GetNavMeshTileBounds(NavigationScene* scene, int32 x, int32 y, float tileSi if (foundAnyVolume) { // Build proper tile bounds - tileBounds.Minimum.Y = rangeY.X; - tileBounds.Maximum.Y = rangeY.Y; + tileBoundsNavMesh.Minimum.Y = rangeY.X; + tileBoundsNavMesh.Maximum.Y = rangeY.Y; } return foundAnyVolume; } -void RemoveTile(NavMesh* navMesh, NavigationScene* scene, int32 x, int32 y, int32 layer) +void RemoveTile(NavMesh* navMesh, NavMeshRuntime* runtime, int32 x, int32 y, int32 layer) { - ScopeLock lock(navMesh->Locker); + ScopeLock lock(runtime->Locker); // Find tile data and remove it - for (int32 i = 0; i < scene->Data.Tiles.Count(); i++) + for (int32 i = 0; i < navMesh->Data.Tiles.Count(); i++) { - auto& tile = scene->Data.Tiles[i]; + auto& tile = navMesh->Data.Tiles[i]; if (tile.PosX == x && tile.PosY == y && tile.Layer == layer) { - scene->Data.Tiles.RemoveAt(i); - scene->IsDataDirty = true; + navMesh->Data.Tiles.RemoveAt(i); + navMesh->IsDataDirty = true; break; } } // Remove tile from navmesh - navMesh->RemoveTile(x, y, layer); + runtime->RemoveTile(x, y, layer); } -bool GenerateTile(NavigationScene* scene, int32 x, int32 y, BoundingBox& tileBounds, float tileSize, rcConfig& config) +bool GenerateTile(NavMesh* navMesh, NavMeshRuntime* runtime, int32 x, int32 y, BoundingBox& tileBoundsNavMesh, const Matrix& worldToNavMesh, float tileSize, rcConfig& config) { rcContext context; int32 layer = 0; // Expand tile bounds by a certain margin - const float tileBorderSize = (1 + config.borderSize) * config.cs; - tileBounds.Minimum -= tileBorderSize; - tileBounds.Maximum += tileBorderSize; + const float tileBorderSize = (1.0f + (float)config.borderSize) * config.cs; + tileBoundsNavMesh.Minimum -= tileBorderSize; + tileBoundsNavMesh.Maximum += tileBorderSize; - rcVcopy(config.bmin, &tileBounds.Minimum.X); - rcVcopy(config.bmax, &tileBounds.Maximum.X); + rcVcopy(config.bmin, &tileBoundsNavMesh.Minimum.X); + rcVcopy(config.bmax, &tileBoundsNavMesh.Maximum.X); rcHeightfield* heightfield = rcAllocHeightfield(); if (!heightfield) @@ -270,7 +428,8 @@ bool GenerateTile(NavigationScene* scene, int32 x, int32 y, BoundingBox& tileBou } Array offMeshLinks; - RasterizeGeometry(tileBounds, &context, &config, heightfield, &offMeshLinks); + Array modifiers; + RasterizeGeometry(navMesh, tileBoundsNavMesh, worldToNavMesh, &context, &config, heightfield, &offMeshLinks, &modifiers); rcFilterLowHangingWalkableObstacles(&context, config.walkableClimb, *heightfield); rcFilterLedgeSpans(&context, config.walkableHeight, config.walkableClimb, *heightfield); @@ -296,6 +455,13 @@ bool GenerateTile(NavigationScene* scene, int32 x, int32 y, BoundingBox& tileBou return true; } + // Mark areas + for (auto& modifier : modifiers) + { + const unsigned char areaId = modifier.NavArea ? modifier.NavArea->Id : RC_NULL_AREA; + rcMarkBoxArea(&context, &modifier.Bounds.Minimum.X, &modifier.Bounds.Maximum.X, areaId, *compactHeightfield); + } + if (!rcBuildDistanceField(&context, *compactHeightfield)) { LOG(Warning, "Could not generate navmesh: Could not build distance field."); @@ -347,15 +513,15 @@ bool GenerateTile(NavigationScene* scene, int32 x, int32 y, BoundingBox& tileBou rcFreeCompactHeightfield(compactHeightfield); rcFreeContourSet(contourSet); - for (int i = 0; i < polyMesh->npolys; ++i) + for (int i = 0; i < polyMesh->npolys; i++) { - polyMesh->flags[i] = polyMesh->areas[i] == RC_WALKABLE_AREA ? 1 : 0; + polyMesh->flags[i] = polyMesh->areas[i] != RC_NULL_AREA ? 1 : 0; } if (polyMesh->nverts == 0) { // Empty tile - RemoveTile(Navigation::GetNavMesh(), scene, x, y, layer); + RemoveTile(navMesh, runtime, x, y, layer); return false; } @@ -373,9 +539,9 @@ bool GenerateTile(NavigationScene* scene, int32 x, int32 y, BoundingBox& tileBou params.detailVertsCount = detailMesh->nverts; params.detailTris = detailMesh->tris; params.detailTriCount = detailMesh->ntris; - params.walkableHeight = config.walkableHeight * config.ch; - params.walkableRadius = config.walkableRadius * config.cs; - params.walkableClimb = config.walkableClimb * config.ch; + params.walkableHeight = (float)config.walkableHeight * config.ch; + params.walkableRadius = (float)config.walkableRadius * config.cs; + params.walkableClimb = (float)config.walkableClimb * config.ch; params.tileX = x; params.tileY = y; params.tileLayer = layer; @@ -414,7 +580,7 @@ bool GenerateTile(NavigationScene* scene, int32 x, int32 y, BoundingBox& tileBou offMeshArea[i] = RC_WALKABLE_AREA; offMeshFlags[i] = 1; - // TODO: support navigation areas, navigation area type for off mesh links + // TODO: support navigation area type for off mesh links } params.offMeshConCount = linksCount; @@ -438,20 +604,33 @@ bool GenerateTile(NavigationScene* scene, int32 x, int32 y, BoundingBox& tileBou { PROFILE_CPU_NAMED("Navigation.CreateTile"); - ScopeLock lock(Navigation::GetNavMesh()->Locker); + ScopeLock lock(runtime->Locker); - // Add tile data - scene->IsDataDirty = true; - auto& tile = scene->Data.Tiles.AddOne(); - tile.PosX = x; - tile.PosY = y; - tile.Layer = layer; + navMesh->IsDataDirty = true; + NavMeshTileData* tile = nullptr; + for (int32 i = 0; i < navMesh->Data.Tiles.Count(); i++) + { + auto& e = navMesh->Data.Tiles[i]; + if (e.PosX == x && e.PosY == y && e.Layer == layer) + { + tile = &e; + break; + } + } + if (!tile) + { + // Add new tile + tile = &navMesh->Data.Tiles.AddOne(); + tile->PosX = x; + tile->PosY = y; + tile->Layer = layer; + } - // Copy data - tile.Data.Copy(navData, navDataSize); + // Copy data to the tile + tile->Data.Copy(navData, navDataSize); // Add tile to navmesh - Navigation::GetNavMesh()->AddTile(scene, tile); + runtime->AddTile(navMesh, *tile); } dtFree(navData); @@ -461,20 +640,21 @@ bool GenerateTile(NavigationScene* scene, int32 x, int32 y, BoundingBox& tileBou float GetTileSize() { - auto& settings = *NavigationSettings::Instance(); + auto& settings = *NavigationSettings::Get(); return settings.CellSize * settings.TileSize; } -void InitConfig(rcConfig& config) +void InitConfig(rcConfig& config, NavMesh* navMesh) { - auto& settings = *NavigationSettings::Instance(); + auto& settings = *NavigationSettings::Get(); + auto& navMeshProperties = navMesh->Properties; config.cs = settings.CellSize; config.ch = settings.CellHeight; - config.walkableSlopeAngle = settings.WalkableMaxSlopeAngle; - config.walkableHeight = (int)(settings.WalkableHeight / config.ch + 0.99f); - config.walkableClimb = (int)(settings.WalkableMaxClimb / config.ch); - config.walkableRadius = (int)(settings.WalkableRadius / config.cs + 0.99f); + config.walkableSlopeAngle = navMeshProperties.Agent.MaxSlopeAngle; + config.walkableHeight = (int)(navMeshProperties.Agent.Height / config.ch + 0.99f); + config.walkableClimb = (int)(navMeshProperties.Agent.StepHeight / config.ch); + config.walkableRadius = (int)(navMeshProperties.Agent.Radius / config.cs + 0.99f); config.maxEdgeLen = (int)(settings.MaxEdgeLen / config.cs); config.maxSimplificationError = settings.MaxEdgeError; config.minRegionArea = rcSqr(settings.MinRegionArea); @@ -506,8 +686,11 @@ class NavMeshTileBuildTask : public ThreadPoolTask { public: - NavigationScene* Scene; - BoundingBox TileBounds; + Scene* Scene; + ScriptingObjectReference NavMesh; + NavMeshRuntime* Runtime; + BoundingBox TileBoundsNavMesh; + Matrix WorldToNavMesh; int32 X; int32 Y; float TileSize; @@ -520,7 +703,12 @@ public: { PROFILE_CPU_NAMED("BuildNavMeshTile"); - if (GenerateTile(Scene, X, Y, TileBounds, TileSize, Config)) + const auto navMesh = NavMesh.Get(); + if (!navMesh) + { + return false; + } + if (GenerateTile(NavMesh, Runtime, X, Y, TileBoundsNavMesh, WorldToNavMesh, TileSize, Config)) { LOG(Warning, "Failed to generate navmesh tile at {0}x{1}.", X, Y); } @@ -557,7 +745,7 @@ void OnSceneUnloading(Scene* scene, const Guid& sceneId) for (int32 i = 0; i < NavBuildTasks.Count(); i++) { auto task = NavBuildTasks[i]; - if (task->Scene == scene->Navigation) + if (task->Scene == scene) { NavBuildTasksLocker.Unlock(); @@ -600,15 +788,16 @@ float NavMeshBuilder::GetNavMeshBuildingProgress() return result; } -void BuildTileAsync(NavigationScene* scene, int32 x, int32 y, rcConfig& config, const BoundingBox& tileBounds, float tileSize) +void BuildTileAsync(NavMesh* navMesh, int32 x, int32 y, rcConfig& config, const BoundingBox& tileBoundsNavMesh, const Matrix& worldToNavMesh, float tileSize) { + NavMeshRuntime* runtime = navMesh->GetRuntime(); NavBuildTasksLocker.Lock(); // Skip if this tile is already during cooking for (int32 i = 0; i < NavBuildTasks.Count(); i++) { const auto task = NavBuildTasks[i]; - if (task->X == x && task->Y == y) + if (task->X == x && task->Y == y && task->Runtime == runtime) { NavBuildTasksLocker.Unlock(); return; @@ -617,10 +806,13 @@ void BuildTileAsync(NavigationScene* scene, int32 x, int32 y, rcConfig& config, // Create task auto task = New(); - task->Scene = scene; + task->Scene = navMesh->GetScene(); + task->NavMesh = navMesh; + task->Runtime = runtime; task->X = x; task->Y = y; - task->TileBounds = tileBounds; + task->TileBoundsNavMesh = tileBoundsNavMesh; + task->WorldToNavMesh = worldToNavMesh; task->TileSize = tileSize; task->Config = config; NavBuildTasks.Add(task); @@ -632,79 +824,23 @@ void BuildTileAsync(NavigationScene* scene, int32 x, int32 y, rcConfig& config, task->Start(); } -void BuildWholeScene(NavigationScene* scene) +void BuildDirtyBounds(Scene* scene, NavMesh* navMesh, const BoundingBox& dirtyBounds, bool rebuild) { const float tileSize = GetTileSize(); - const auto navMesh = Navigation::GetNavMesh(); - - // Compute total navigation area bounds - const BoundingBox worldBounds = scene->GetNavigationBounds(); - - // Align total bounds to tile size - BoundingBox worldBoundsAligned; - worldBoundsAligned.Minimum = Vector3::Floor(worldBounds.Minimum / tileSize) * tileSize; - worldBoundsAligned.Maximum = Vector3::Ceil(worldBounds.Maximum / tileSize) * tileSize; - - // Calculate tiles range for the given navigation world bounds (aligned to tiles size) - const Int3 tilesMin = Int3(worldBoundsAligned.Minimum / tileSize); - const Int3 tilesMax = Int3(worldBoundsAligned.Maximum / tileSize); - const int32 tilesX = tilesMax.X - tilesMin.X; - const int32 tilesY = tilesMax.Z - tilesMin.Z; - - { - PROFILE_CPU_NAMED("Prepare"); - - // Prepare navmesh - navMesh->RemoveTiles(scene); - navMesh->SetTileSize(tileSize); - navMesh->EnsureCapacity(tilesX * tilesY); - - // Prepare scene data - scene->Data.TileSize = tileSize; - scene->Data.Tiles.Clear(); - scene->Data.Tiles.EnsureCapacity(tilesX * tilesX); - scene->IsDataDirty = true; - } - - // Initialize nav mesh configuration - rcConfig config; - InitConfig(config); - - // Generate all tiles that intersect with the navigation volume bounds - { - PROFILE_CPU_NAMED("StartBuildingTiles"); - - for (int32 y = tilesMin.Z; y < tilesMax.Z; y ++) - { - for (int32 x = tilesMin.X; x < tilesMax.X; x ++) - { - BoundingBox tileBounds; - if (GetNavMeshTileBounds(scene, x, y, tileSize, tileBounds)) - { - BuildTileAsync(scene, x, y, config, tileBounds, tileSize); - } - else - { - RemoveTile(navMesh, scene, x, y, 0); - } - } - } - } -} - -void BuildDirtyBounds(NavigationScene* scene, const BoundingBox& dirtyBounds) -{ - const float tileSize = GetTileSize(); - const auto navMesh = Navigation::GetNavMesh(); + NavMeshRuntime* runtime = navMesh->GetRuntime(); + Matrix worldToNavMesh; + Matrix::RotationQuaternion(runtime->Properties.Rotation, worldToNavMesh); // Align dirty bounds to tile size + BoundingBox dirtyBoundsNavMesh; + BoundingBox::Transform(dirtyBounds, worldToNavMesh, dirtyBoundsNavMesh); BoundingBox dirtyBoundsAligned; - dirtyBoundsAligned.Minimum = Vector3::Floor(dirtyBounds.Minimum / tileSize) * tileSize; - dirtyBoundsAligned.Maximum = Vector3::Ceil(dirtyBounds.Maximum / tileSize) * tileSize; + dirtyBoundsAligned.Minimum = Vector3::Floor(dirtyBoundsNavMesh.Minimum / tileSize) * tileSize; + dirtyBoundsAligned.Maximum = Vector3::Ceil(dirtyBoundsNavMesh.Maximum / tileSize) * tileSize; // Calculate tiles range for the given navigation dirty bounds (aligned to tiles size) - const Int3 tilesMin = Int3(dirtyBoundsAligned.Minimum / tileSize); - const Int3 tilesMax = Int3(dirtyBoundsAligned.Maximum / tileSize); + const Int3 tilesMin(dirtyBoundsAligned.Minimum / tileSize); + const Int3 tilesMax(dirtyBoundsAligned.Maximum / tileSize); const int32 tilesX = tilesMax.X - tilesMin.X; const int32 tilesY = tilesMax.Z - tilesMin.Z; @@ -712,51 +848,161 @@ void BuildDirtyBounds(NavigationScene* scene, const BoundingBox& dirtyBounds) PROFILE_CPU_NAMED("Prepare"); // Prepare scene data and navmesh - if (Math::NotNearEqual(scene->Data.TileSize, tileSize)) + rebuild |= Math::NotNearEqual(navMesh->Data.TileSize, tileSize); + if (rebuild) { - navMesh->RemoveTiles(scene); - navMesh->SetTileSize(tileSize); - navMesh->EnsureCapacity(tilesX * tilesY); + // Remove all tiles from navmesh runtime + runtime->RemoveTiles(navMesh); + runtime->SetTileSize(tileSize); + runtime->EnsureCapacity(tilesX * tilesY); - scene->Data.TileSize = tileSize; - scene->Data.Tiles.Clear(); - scene->Data.Tiles.EnsureCapacity(tilesX * tilesX); - scene->IsDataDirty = true; + // Remove all tiles from navmesh data + navMesh->Data.TileSize = tileSize; + navMesh->Data.Tiles.Clear(); + navMesh->Data.Tiles.EnsureCapacity(tilesX * tilesX); + navMesh->IsDataDirty = true; } else { - // Prepare navmesh - navMesh->SetTileSize(tileSize); - navMesh->EnsureCapacity(tilesX * tilesY); + // Ensure to have enough memory for tiles + runtime->SetTileSize(tileSize); + runtime->EnsureCapacity(tilesX * tilesY); } } // Initialize nav mesh configuration rcConfig config; - InitConfig(config); + InitConfig(config, navMesh); // Generate all tiles that intersect with the navigation volume bounds { PROFILE_CPU_NAMED("StartBuildingTiles"); - for (int32 y = tilesMin.Z; y < tilesMax.Z; y ++) + for (int32 y = tilesMin.Z; y < tilesMax.Z; y++) { - for (int32 x = tilesMin.X; x < tilesMax.X; x ++) + for (int32 x = tilesMin.X; x < tilesMax.X; x++) { - BoundingBox tileBounds; - if (GetNavMeshTileBounds(scene, x, y, tileSize, tileBounds)) + BoundingBox tileBoundsNavMesh; + if (GetNavMeshTileBounds(scene, navMesh, x, y, tileSize, tileBoundsNavMesh, worldToNavMesh)) { - BuildTileAsync(scene, x, y, config, tileBounds, tileSize); + BuildTileAsync(navMesh, x, y, config, tileBoundsNavMesh, worldToNavMesh, tileSize); } else { - RemoveTile(navMesh, scene, x, y, 0); + RemoveTile(navMesh, runtime, x, y, 0); } } } } } +void BuildDirtyBounds(Scene* scene, const BoundingBox& dirtyBounds, bool rebuild) +{ + auto settings = NavigationSettings::Get(); + + // Validate nav areas ids to be unique and in valid range + for (int32 i = 0; i < settings->NavAreas.Count(); i++) + { + auto& a = settings->NavAreas[i]; + if (a.Id > RC_WALKABLE_AREA) + { + LOG(Error, "Nav Area {0} uses invalid Id. Valid values are in range 0-63 only.", a.Name); + return; + } + + for (int32 j = i + 1; j < settings->NavAreas.Count(); j++) + { + auto& b = settings->NavAreas[j]; + if (a.Id == b.Id) + { + LOG(Error, "Nav Area {0} uses the same Id={1} as Nav Area {2}. Each area hast to have unique Id.", a.Name, a.Id, b.Name); + return; + } + } + } + + // Sync navmeshes + for (auto& navMeshProperties : settings->NavMeshes) + { + NavMesh* navMesh = nullptr; + for (auto e : scene->NavigationMeshes) + { + if (e->Properties.Name == navMeshProperties.Name) + { + navMesh = e; + break; + } + } + if (navMesh) + { + // Sync settings + auto runtime = navMesh->GetRuntime(false); + navMesh->Properties = navMeshProperties; + if (runtime) + runtime->Properties = navMeshProperties; + } + else if (settings->AutoAddMissingNavMeshes) + { + // Spawn missing navmesh + navMesh = New(); + navMesh->SetStaticFlags(StaticFlags::FullyStatic); + navMesh->SetName(TEXT("NavMesh.") + navMeshProperties.Name); + navMesh->Properties = navMeshProperties; + navMesh->SetParent(scene, false); + } + } + + // Build all navmeshes on the scene + for (NavMesh* navMesh : scene->NavigationMeshes) + { + BuildDirtyBounds(scene, navMesh, dirtyBounds, rebuild); + } + + // Remove unused navmeshes + if (settings->AutoRemoveMissingNavMeshes) + { + for (NavMesh* navMesh : scene->NavigationMeshes) + { + // Skip used navmeshes + if (navMesh->Data.Tiles.HasItems()) + continue; + + // Skip navmeshes during async building + int32 usageCount = 0; + NavBuildTasksLocker.Lock(); + for (int32 i = 0; i < NavBuildTasks.Count(); i++) + { + if (NavBuildTasks[i]->NavMesh == navMesh) + usageCount++; + } + NavBuildTasksLocker.Unlock(); + if (usageCount != 0) + continue; + + navMesh->DeleteObject(); + } + } +} + +void BuildWholeScene(Scene* scene) +{ + // Compute total navigation area bounds + const BoundingBox worldBounds = scene->GetNavigationBounds(); + + BuildDirtyBounds(scene, worldBounds, true); +} + +void ClearNavigation(Scene* scene) +{ + const bool autoRemoveMissingNavMeshes = NavigationSettings::Get()->AutoRemoveMissingNavMeshes; + for (NavMesh* navMesh : scene->NavigationMeshes) + { + navMesh->ClearData(); + if (autoRemoveMissingNavMeshes) + navMesh->DeleteObject(); + } +} + void NavMeshBuilder::Update() { ScopeLock lock(NavBuildQueueLocker); @@ -769,15 +1015,12 @@ void NavMeshBuilder::Update() if (now - req.Time >= 0) { NavBuildQueue.RemoveAt(i--); - auto scene = req.Scene->Navigation; + const auto scene = req.Scene.Get(); // Early out if scene has no bounds volumes to define nav mesh area - if (scene->Volumes.IsEmpty()) + if (scene->NavigationVolumes.IsEmpty()) { - // Cleanup if no navigation to use - scene->Data.TileSize = 0; - scene->Data.Tiles.Resize(0); - scene->IsDataDirty = true; + ClearNavigation(scene); continue; } @@ -788,7 +1031,7 @@ void NavMeshBuilder::Update() } else { - BuildDirtyBounds(scene, req.DirtyBounds); + BuildDirtyBounds(scene, req.DirtyBounds, false); } } } @@ -797,9 +1040,9 @@ void NavMeshBuilder::Update() void NavMeshBuilder::Build(Scene* scene, float timeoutMs) { // Early out if scene is not using navigation - if (scene->Navigation->Volumes.IsEmpty()) + if (scene->NavigationVolumes.IsEmpty()) { - scene->Navigation->ClearData(); + ClearNavigation(scene); return; } @@ -828,9 +1071,9 @@ void NavMeshBuilder::Build(Scene* scene, float timeoutMs) void NavMeshBuilder::Build(Scene* scene, const BoundingBox& dirtyBounds, float timeoutMs) { // Early out if scene is not using navigation - if (scene->Navigation->Volumes.IsEmpty()) + if (scene->NavigationVolumes.IsEmpty()) { - scene->Navigation->ClearData(); + ClearNavigation(scene); return; } diff --git a/Source/Engine/Navigation/NavMeshBuilder.h b/Source/Engine/Navigation/NavMeshBuilder.h index 4e59027ce..27dab4273 100644 --- a/Source/Engine/Navigation/NavMeshBuilder.h +++ b/Source/Engine/Navigation/NavMeshBuilder.h @@ -4,9 +4,14 @@ #if COMPILE_WITH_NAV_MESH_BUILDER +#include "Engine/Core/Compiler.h" + class Scene; -class NavMeshBuilder +/// +/// The navigation mesh building utility. +/// +class FLAXENGINE_API NavMeshBuilder { public: diff --git a/Source/Engine/Navigation/NavMeshRuntime.cpp b/Source/Engine/Navigation/NavMeshRuntime.cpp new file mode 100644 index 000000000..3d112a39e --- /dev/null +++ b/Source/Engine/Navigation/NavMeshRuntime.cpp @@ -0,0 +1,694 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#include "NavMeshRuntime.h" +#include "NavigationSettings.h" +#include "NavMesh.h" +#include "Engine/Core/Log.h" +#include "Engine/Core/Math/Matrix.h" +#include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Threading/Threading.h" +#include +#include +#include + +#define MAX_NODES 2048 +#define USE_DATA_LINK 0 +#define USE_NAV_MESH_ALLOC 1 +// TODO: try not using USE_NAV_MESH_ALLOC + +#define DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL 50.0f +#define DEFAULT_NAV_QUERY_EXTENT_VERTICAL 250.0f + +namespace +{ + FORCE_INLINE void InitFilter(dtQueryFilter& filter) + { + Platform::MemoryCopy(filter.m_areaCost, NavMeshRuntime::NavAreasCosts, sizeof(NavMeshRuntime::NavAreasCosts)); + } +} + +NavMeshRuntime::NavMeshRuntime(const NavMeshProperties& properties) + : Properties(properties) +{ + _navMesh = nullptr; + _navMeshQuery = dtAllocNavMeshQuery(); + _tileSize = 0; +} + +NavMeshRuntime::~NavMeshRuntime() +{ + dtFreeNavMesh(_navMesh); + dtFreeNavMeshQuery(_navMeshQuery); +} + +int32 NavMeshRuntime::GetTilesCapacity() const +{ + return _navMesh ? _navMesh->getMaxTiles() : 0; +} + +bool NavMeshRuntime::FindDistanceToWall(const Vector3& startPosition, NavMeshHit& hitInfo, float maxDistance) const +{ + ScopeLock lock(Locker); + + const auto query = GetNavMeshQuery(); + if (!query) + { + return false; + } + + dtQueryFilter filter; + InitFilter(filter); + Vector3 extent(DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL, DEFAULT_NAV_QUERY_EXTENT_VERTICAL, DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL); + + Vector3 startPositionNavMesh; + Vector3::Transform(startPosition, Properties.Rotation, startPositionNavMesh); + + dtPolyRef startPoly = 0; + query->findNearestPoly(&startPositionNavMesh.X, &extent.X, &filter, &startPoly, nullptr); + if (!startPoly) + { + return false; + } + + if (!dtStatusSucceed(query->findDistanceToWall(startPoly, &startPosition.X, maxDistance, &filter, &hitInfo.Distance, &hitInfo.Position.X, &hitInfo.Normal.X))) + { + return false; + } + + Quaternion invRotation; + Quaternion::Invert(Properties.Rotation, invRotation); + Vector3::Transform(hitInfo.Position, invRotation, hitInfo.Position); + Vector3::Transform(hitInfo.Normal, invRotation, hitInfo.Normal); + + return true; +} + +bool NavMeshRuntime::FindPath(const Vector3& startPosition, const Vector3& endPosition, Array& resultPath) const +{ + resultPath.Clear(); + ScopeLock lock(Locker); + + const auto query = GetNavMeshQuery(); + if (!query) + { + return false; + } + + dtQueryFilter filter; + InitFilter(filter); + Vector3 extent(DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL, DEFAULT_NAV_QUERY_EXTENT_VERTICAL, DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL); + + Vector3 startPositionNavMesh, endPositionNavMesh; + Vector3::Transform(startPosition, Properties.Rotation, startPositionNavMesh); + Vector3::Transform(endPosition, Properties.Rotation, endPositionNavMesh); + + dtPolyRef startPoly = 0; + query->findNearestPoly(&startPositionNavMesh.X, &extent.X, &filter, &startPoly, nullptr); + if (!startPoly) + { + return false; + } + dtPolyRef endPoly = 0; + query->findNearestPoly(&endPositionNavMesh.X, &extent.X, &filter, &endPoly, nullptr); + if (!endPoly) + { + return false; + } + + dtPolyRef path[NAV_MESH_PATH_MAX_SIZE]; + int32 pathSize; + const auto findPathStatus = query->findPath(startPoly, endPoly, &startPositionNavMesh.X, &endPositionNavMesh.X, &filter, path, &pathSize, NAV_MESH_PATH_MAX_SIZE); + if (dtStatusFailed(findPathStatus)) + { + return false; + } + + Quaternion invRotation; + Quaternion::Invert(Properties.Rotation, invRotation); + + // Check for special case, where path has not been found, and starting polygon was the one closest to the target + if (pathSize == 1 && dtStatusDetail(findPathStatus, DT_PARTIAL_RESULT)) + { + // In this case we find a point on starting polygon, that's closest to destination and store it as path end + resultPath.Resize(2); + resultPath[0] = startPosition; + resultPath[1] = startPositionNavMesh; + query->closestPointOnPolyBoundary(startPoly, &endPositionNavMesh.X, &resultPath[1].X); + Vector3::Transform(resultPath[1], invRotation, resultPath[1]); + } + else + { + int straightPathCount = 0; + resultPath.EnsureCapacity(NAV_MESH_PATH_MAX_SIZE); + const auto findStraightPathStatus = query->findStraightPath(&startPositionNavMesh.X, &endPositionNavMesh.X, path, pathSize, (float*)resultPath.Get(), nullptr, nullptr, &straightPathCount, resultPath.Capacity(), DT_STRAIGHTPATH_AREA_CROSSINGS); + if (dtStatusFailed(findStraightPathStatus)) + { + return false; + } + resultPath.Resize(straightPathCount); + for (auto& pos : resultPath) + Vector3::Transform(pos, invRotation, pos); + } + + return true; +} + +bool NavMeshRuntime::TestPath(const Vector3& startPosition, const Vector3& endPosition) const +{ + ScopeLock lock(Locker); + + const auto query = GetNavMeshQuery(); + if (!query) + { + return false; + } + + dtQueryFilter filter; + InitFilter(filter); + Vector3 extent(DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL, DEFAULT_NAV_QUERY_EXTENT_VERTICAL, DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL); + + Vector3 startPositionNavMesh, endPositionNavMesh; + Vector3::Transform(startPosition, Properties.Rotation, startPositionNavMesh); + Vector3::Transform(endPosition, Properties.Rotation, endPositionNavMesh); + + dtPolyRef startPoly = 0; + query->findNearestPoly(&startPositionNavMesh.X, &extent.X, &filter, &startPoly, nullptr); + if (!startPoly) + { + return false; + } + dtPolyRef endPoly = 0; + query->findNearestPoly(&endPositionNavMesh.X, &extent.X, &filter, &endPoly, nullptr); + if (!endPoly) + { + return false; + } + + dtPolyRef path[NAV_MESH_PATH_MAX_SIZE]; + int32 pathSize; + const auto findPathStatus = query->findPath(startPoly, endPoly, &startPositionNavMesh.X, &endPositionNavMesh.X, &filter, path, &pathSize, NAV_MESH_PATH_MAX_SIZE); + if (dtStatusFailed(findPathStatus)) + { + return false; + } + + if (dtStatusDetail(findPathStatus, DT_PARTIAL_RESULT)) + { + return false; + } + + return true; +} + +bool NavMeshRuntime::ProjectPoint(const Vector3& point, Vector3& result) const +{ + ScopeLock lock(Locker); + + const auto query = GetNavMeshQuery(); + if (!query) + { + return false; + } + + dtQueryFilter filter; + InitFilter(filter); + Vector3 extent(DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL, DEFAULT_NAV_QUERY_EXTENT_VERTICAL, DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL); + + Vector3 pointNavMesh; + Vector3::Transform(point, Properties.Rotation, pointNavMesh); + + dtPolyRef startPoly = 0; + query->findNearestPoly(&pointNavMesh.X, &extent.X, &filter, &startPoly, &result.X); + if (!startPoly) + { + return false; + } + + Quaternion invRotation; + Quaternion::Invert(Properties.Rotation, invRotation); + Vector3::Transform(result, invRotation, result); + + return true; +} + +bool NavMeshRuntime::RayCast(const Vector3& startPosition, const Vector3& endPosition, NavMeshHit& hitInfo) const +{ + ScopeLock lock(Locker); + + const auto query = GetNavMeshQuery(); + if (!query) + { + return false; + } + + dtQueryFilter filter; + InitFilter(filter); + Vector3 extent(DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL, DEFAULT_NAV_QUERY_EXTENT_VERTICAL, DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL); + + Vector3 startPositionNavMesh, endPositionNavMesh; + Vector3::Transform(startPosition, Properties.Rotation, startPositionNavMesh); + Vector3::Transform(endPosition, Properties.Rotation, endPositionNavMesh); + + dtPolyRef startPoly = 0; + query->findNearestPoly(&startPositionNavMesh.X, &extent.X, &filter, &startPoly, nullptr); + if (!startPoly) + { + return false; + } + + dtRaycastHit hit; + hit.path = nullptr; + hit.maxPath = 0; + const bool result = dtStatusSucceed(query->raycast(startPoly, &startPositionNavMesh.X, &endPositionNavMesh.X, &filter, 0, &hit)); + if (hit.t >= MAX_float) + { + hitInfo.Position = endPosition; + hitInfo.Distance = 0; + } + else + { + hitInfo.Position = startPosition + (endPosition - startPosition) * hit.t; + hitInfo.Distance = hit.t; + } + hitInfo.Normal = *(Vector3*)&hit.hitNormal; + + return result; +} + +void NavMeshRuntime::SetTileSize(float tileSize) +{ + ScopeLock lock(Locker); + + // Skip if the same or invalid + if (Math::NearEqual(_tileSize, tileSize) || tileSize < 1) + return; + + // Dispose the existing mesh (its invalid) + if (_navMesh) + { + dtFreeNavMesh(_navMesh); + _navMesh = nullptr; + _tiles.Clear(); + } + + _tileSize = tileSize; +} + +void NavMeshRuntime::EnsureCapacity(int32 tilesToAddCount) +{ + ScopeLock lock(Locker); + + const int32 newTilesCount = _tiles.Count() + tilesToAddCount; + const int32 capacity = GetTilesCapacity(); + if (newTilesCount <= capacity) + return; + + PROFILE_CPU_NAMED("NavMeshRuntime.EnsureCapacity"); + + // Navmesh tiles capacity growing rule + int32 newCapacity = 0; + if (capacity) + { + while (newCapacity < newTilesCount) + { + newCapacity = Math::RoundUpToPowerOf2(newCapacity); + } + } + else + { + newCapacity = 32; + } + + LOG(Info, "Resizing navmesh {2} from {0} to {1} tiles capacity", capacity, newCapacity, Properties.Name); + + // Ensure to have size assigned + ASSERT(_tileSize != 0); + + // Fre previous data (if any) + if (_navMesh) + { + dtFreeNavMesh(_navMesh); + } + + // Allocate new navmesh + _navMesh = dtAllocNavMesh(); + if (dtStatusFailed(_navMeshQuery->init(_navMesh, MAX_NODES))) + { + LOG(Error, "Failed to initialize navmesh {0}.", Properties.Name); + } + + // Prepare parameters + dtNavMeshParams params; + params.orig[0] = 0.0f; + params.orig[1] = 0.0f; + params.orig[2] = 0.0f; + params.tileWidth = _tileSize; + params.tileHeight = _tileSize; + params.maxTiles = newCapacity; + const int32 tilesBits = (int32)Math::Log2((float)Math::RoundUpToPowerOf2(params.maxTiles)); + params.maxPolys = 1 << (22 - tilesBits); + + // Initialize nav mesh + if (dtStatusFailed(_navMesh->init(¶ms))) + { + LOG(Error, "Navmesh {0} init failed.", Properties.Name); + return; + } + + // Prepare tiles container + _tiles.EnsureCapacity(newCapacity); + + // Restore previous tiles + for (auto& tile : _tiles) + { + const int32 dataSize = tile.Data.Length(); +#if USE_NAV_MESH_ALLOC + const auto flags = DT_TILE_FREE_DATA; + const auto data = (byte*)dtAlloc(dataSize, DT_ALLOC_PERM); + Platform::MemoryCopy(data, tile.Data.Get(), dataSize); +#else + const auto flags = 0; + const auto data = tile.Data.Get(); +#endif + if (dtStatusFailed(_navMesh->addTile(data, dataSize, flags, 0, nullptr))) + { + LOG(Warning, "Could not add tile to navmesh {0}.", Properties.Name); + } + } +} + +void NavMeshRuntime::AddTiles(NavMesh* navMesh) +{ + // Skip if no data + ASSERT(navMesh); + if (navMesh->Data.Tiles.IsEmpty()) + return; + auto& data = navMesh->Data; + + PROFILE_CPU_NAMED("NavMeshRuntime.AddTiles"); + + ScopeLock lock(Locker); + + // Validate data (must match navmesh) or init navmesh to match the tiles options + if (_navMesh) + { + if (Math::NotNearEqual(data.TileSize, _tileSize)) + { + LOG(Warning, "Cannot add navigation scene tiles to the navmesh {2}. Navmesh tile size: {0}, input tiles size: {1}", _tileSize, data.TileSize, Properties.Name); + return; + } + } + else + { + _tileSize = data.TileSize; + } + + // Ensure to have space for new tiles + EnsureCapacity(data.Tiles.Count()); + + // Add new tiles + for (auto& tileData : data.Tiles) + { + AddTileInternal(navMesh, tileData); + } +} + +void NavMeshRuntime::AddTile(NavMesh* navMesh, NavMeshTileData& tileData) +{ + ASSERT(navMesh); + auto& data = navMesh->Data; + + PROFILE_CPU_NAMED("NavMeshRuntime.AddTile"); + + ScopeLock lock(Locker); + + // Validate data (must match navmesh) or init navmesh to match the tiles options + if (_navMesh) + { + if (Math::NotNearEqual(data.TileSize, _tileSize)) + { + LOG(Warning, "Cannot add navigation scene tile to the navmesh {2}. Navmesh tile size: {0}, input tile size: {1}", _tileSize, data.TileSize, Properties.Name); + return; + } + } + else + { + _tileSize = data.TileSize; + } + + // Ensure to have space for new tile + EnsureCapacity(1); + + // Add new tile + AddTileInternal(navMesh, tileData); +} + +bool IsTileFromScene(const NavMeshRuntime* navMesh, const NavMeshTile& tile, void* customData) +{ + return tile.NavMesh == (NavMesh*)customData; +} + +void NavMeshRuntime::RemoveTiles(NavMesh* navMesh) +{ + RemoveTiles(IsTileFromScene, navMesh); +} + +void NavMeshRuntime::RemoveTile(int32 x, int32 y, int32 layer) +{ + ScopeLock lock(Locker); + + // Skip if no data + if (!_navMesh) + return; + + PROFILE_CPU_NAMED("NavMeshRuntime.RemoveTile"); + + const auto tileRef = _navMesh->getTileRefAt(x, y, layer); + if (tileRef == 0) + { + return; + } + + if (dtStatusFailed(_navMesh->removeTile(tileRef, nullptr, nullptr))) + { + LOG(Warning, "Failed to remove tile from navmesh {0}.", Properties.Name); + } + + for (int32 i = 0; i < _tiles.Count(); i++) + { + auto& tile = _tiles[i]; + if (tile.X == x && tile.Y == y && tile.Layer == layer) + { + _tiles.RemoveAt(i); + break; + } + } +} + +void NavMeshRuntime::RemoveTiles(bool (* prediction)(const NavMeshRuntime* navMesh, const NavMeshTile& tile, void* customData), void* userData) +{ + ScopeLock lock(Locker); + + // Skip if no data + ASSERT(prediction); + if (!_navMesh) + return; + + PROFILE_CPU_NAMED("NavMeshRuntime.RemoveTiles"); + + for (int32 i = 0; i < _tiles.Count(); i++) + { + auto& tile = _tiles[i]; + if (prediction(this, tile, userData)) + { + const auto tileRef = _navMesh->getTileRefAt(tile.X, tile.Y, tile.Layer); + if (tileRef == 0) + { + LOG(Warning, "Missing navmesh {3} tile at {0}x{1}, layer: {2}", tile.X, tile.Y, tile.Layer, Properties.Name); + } + else + { + if (dtStatusFailed(_navMesh->removeTile(tileRef, nullptr, nullptr))) + { + LOG(Warning, "Failed to remove tile from navmesh {0}.", Properties.Name); + } + } + + _tiles.RemoveAt(i--); + } + } +} + +#if COMPILE_WITH_DEBUG_DRAW + +#include "Engine/Debug/DebugDraw.h" + +void DrawPoly(NavMeshRuntime* navMesh, const Matrix& navMeshToWorld, const dtMeshTile& tile, const dtPoly& poly) +{ + const unsigned int ip = (unsigned int)(&poly - tile.polys); + const dtPolyDetail& pd = tile.detailMeshes[ip]; + const Color& areaColor = NavMeshRuntime::NavAreasColors[poly.getArea()]; + const Color color = Color::Lerp(navMesh->Properties.Color, areaColor, areaColor.A); + const float drawOffsetY = 10.0f + ((float)GetHash(color) / (float)MAX_uint32) * 10.0f; // Apply some offset to prevent Z-fighting for different navmeshes + const Color fillColor = color * 0.5f; + const Color edgesColor = Color::FromHSV(color.ToHSV() + Vector3(20.0f, 0, -0.1f), color.A); + + for (int i = 0; i < pd.triCount; i++) + { + Vector3 v[3]; + const unsigned char* t = &tile.detailTris[(pd.triBase + i) * 4]; + + for (int k = 0; k < 3; k++) + { + if (t[k] < poly.vertCount) + { + v[k] = *(Vector3*)&tile.verts[poly.verts[t[k]] * 3]; + } + else + { + v[k] = *(Vector3*)&tile.detailVerts[(pd.vertBase + t[k] - poly.vertCount) * 3]; + } + } + + v[0].Y += drawOffsetY; + v[1].Y += drawOffsetY; + v[2].Y += drawOffsetY; + + Vector3::Transform(v[0], navMeshToWorld, v[0]); + Vector3::Transform(v[1], navMeshToWorld, v[1]); + Vector3::Transform(v[2], navMeshToWorld, v[2]); + + DEBUG_DRAW_TRIANGLE(v[0], v[1], v[2], fillColor, 0, true); + } + + for (int k = 0; k < pd.triCount; k++) + { + const unsigned char* t = &tile.detailTris[(pd.triBase + k) * 4]; + Vector3 v[3]; + + for (int m = 0; m < 3; m++) + { + if (t[m] < poly.vertCount) + v[m] = *(Vector3*)&tile.verts[poly.verts[t[m]] * 3]; + else + v[m] = *(Vector3*)&tile.detailVerts[(pd.vertBase + (t[m] - poly.vertCount)) * 3]; + } + + v[0].Y += drawOffsetY; + v[1].Y += drawOffsetY; + v[2].Y += drawOffsetY; + + Vector3::Transform(v[0], navMeshToWorld, v[0]); + Vector3::Transform(v[1], navMeshToWorld, v[1]); + Vector3::Transform(v[2], navMeshToWorld, v[2]); + + for (int m = 0, n = 2; m < 3; n = m++) + { + // Skip inner detail edges + if (((t[3] >> (n * 2)) & 0x3) == 0) + continue; + + DEBUG_DRAW_LINE(v[n], v[m], edgesColor, 0, true); + } + } +} + +void NavMeshRuntime::DebugDraw() +{ + ScopeLock lock(Locker); + + const dtNavMesh* dtNavMesh = GetNavMesh(); + const int tilesCount = dtNavMesh ? dtNavMesh->getMaxTiles() : 0; + if (tilesCount == 0) + return; + Matrix worldToNavMesh, navMeshToWorld; + Matrix::RotationQuaternion(Properties.Rotation, worldToNavMesh); + Matrix::Invert(worldToNavMesh, navMeshToWorld); + + for (int tileIndex = 0; tileIndex < tilesCount; tileIndex++) + { + const dtMeshTile* tile = dtNavMesh->getTile(tileIndex); + if (!tile->header) + continue; + + //DebugDraw::DrawWireBox(*(BoundingBox*)&tile->header->bmin[0], Color::CadetBlue); + + for (int i = 0; i < tile->header->polyCount; i++) + { + const dtPoly* poly = &tile->polys[i]; + if (poly->getType() != DT_POLYTYPE_GROUND) + continue; + + DrawPoly(this, navMeshToWorld, *tile, *poly); + } + } +} + +#endif + +void NavMeshRuntime::Dispose() +{ + if (_navMesh) + { + dtFreeNavMesh(_navMesh); + _navMesh = nullptr; + } + _tiles.Resize(0); +} + +void NavMeshRuntime::AddTileInternal(NavMesh* navMesh, NavMeshTileData& tileData) +{ + // Check if that tile has been added to navmesh + NavMeshTile* tile = nullptr; + const auto tileRef = _navMesh->getTileRefAt(tileData.PosX, tileData.PosY, tileData.Layer); + if (tileRef) + { + // Remove any existing tile at that location + if (dtStatusFailed(_navMesh->removeTile(tileRef, nullptr, nullptr))) + { + LOG(Warning, "Failed to remove tile from navmesh {0}.", Properties.Name); + } + + // Reuse tile data container + for (int32 i = 0; i < _tiles.Count(); i++) + { + auto& e = _tiles[i]; + if (e.X == tileData.PosX && e.Y == tileData.PosY && e.Layer == tileData.Layer) + { + tile = &e; + break; + } + } + } + if (!tile) + { + // Add tile + tile = &_tiles.AddOne(); + } + + // Copy tile properties + tile->NavMesh = navMesh; + tile->X = tileData.PosX; + tile->Y = tileData.PosY; + tile->Layer = tileData.Layer; +#if USE_DATA_LINK + tile->Data.Link(tileData.Data); +#else + tile->Data.Copy(tileData.Data); +#endif + + // Add tile to navmesh + const int32 dataSize = tile->Data.Length(); +#if USE_NAV_MESH_ALLOC + const auto flags = DT_TILE_FREE_DATA; + const auto data = (byte*)dtAlloc(dataSize, DT_ALLOC_PERM); + Platform::MemoryCopy(data, tile->Data.Get(), dataSize); +#else + const auto flags = 0; + const auto data = tile->Data.Get(); +#endif + if (dtStatusFailed(_navMesh->addTile(data, dataSize, flags, 0, nullptr))) + { + LOG(Warning, "Could not add tile to navmesh {0}.", Properties.Name); + } +} diff --git a/Source/Engine/Navigation/NavMeshRuntime.h b/Source/Engine/Navigation/NavMeshRuntime.h new file mode 100644 index 000000000..fd8dcf794 --- /dev/null +++ b/Source/Engine/Navigation/NavMeshRuntime.h @@ -0,0 +1,199 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Engine/Core/Types/BaseTypes.h" +#include "Engine/Platform/CriticalSection.h" +#include "NavMeshData.h" +#include "NavigationTypes.h" + +class dtNavMesh; +class dtNavMeshQuery; +class NavMesh; + +/// +/// The navigation mesh tile data. +/// +class FLAXENGINE_API NavMeshTile +{ +public: + + int32 X; + int32 Y; + int32 Layer; + NavMesh* NavMesh; + BytesContainer Data; +}; + +/// +/// The navigation mesh runtime object that builds the navmesh from all loaded scenes. +/// +class FLAXENGINE_API NavMeshRuntime +{ +public: + + // Gets the navigation mesh runtime for a given navmesh name. Return null if missing. + static NavMeshRuntime* Get(const StringView& navMeshName); + + // Gets the navigation mesh runtime for a given agent properties trying to pick the best matching navmesh. + static NavMeshRuntime* Get(const NavAgentProperties& agentProperties); + + // Gets the navigation mesh runtime for a given navmesh properties. + static NavMeshRuntime* Get(const NavMeshProperties& navMeshProperties, bool createIfMissing = false); + + // The lookup table that maps areaId of the navmesh to the current properties (applied by the NavigationSettings). Cached to improve runtime performance. + static float NavAreasCosts[64]; +#if COMPILE_WITH_DEBUG_DRAW + static Color NavAreasColors[64]; +#endif + +private: + + dtNavMesh* _navMesh; + dtNavMeshQuery* _navMeshQuery; + float _tileSize; + Array _tiles; + +public: + + NavMeshRuntime(const NavMeshProperties& properties); + ~NavMeshRuntime(); + +public: + + /// + /// The object locker. + /// + CriticalSection Locker; + + /// + /// The navigation mesh properties. + /// + NavMeshProperties Properties; + + /// + /// Gets the size of the tile (in world-units). Returns zero if not initialized yet. + /// + FORCE_INLINE float GetTileSize() const + { + return _tileSize; + } + + dtNavMesh* GetNavMesh() const + { + return _navMesh; + } + + dtNavMeshQuery* GetNavMeshQuery() const + { + return _navMeshQuery; + } + + int32 GetTilesCapacity() const; + +public: + + /// + /// Finds the distance from the specified start position to the nearest polygon wall. + /// + /// The start position. + /// The result hit information. Valid only when query succeed. + /// The maximum distance to search for wall (search radius). + /// True if ray hits an matching object, otherwise false. + bool FindDistanceToWall(const Vector3& startPosition, NavMeshHit& hitInfo, float maxDistance = MAX_float) const; + + /// + /// Finds the path between the two positions presented as a list of waypoints stored in the corners array. + /// + /// The start position. + /// The end position. + /// The result path. + /// True if found valid path between given two points (it may be partial), otherwise false if failed. + bool FindPath(const Vector3& startPosition, const Vector3& endPosition, Array& resultPath) const; + + /// + /// Tests the path between the two positions (non-partial). + /// + /// The start position. + /// The end position. + /// True if found valid path between given two points, otherwise false if failed. + bool TestPath(const Vector3& startPosition, const Vector3& endPosition) const; + + /// + /// Projects the point to nav mesh surface (finds the nearest polygon). + /// + /// The source point. + /// The result position on the navmesh (valid only if method returns true). + /// True if found valid location on the navmesh, otherwise false. + bool ProjectPoint(const Vector3& point, Vector3& result) const; + + /// + /// Casts a 'walkability' ray along the surface of the navigation mesh from the start position toward the end position. + /// + /// The start position. + /// The end position. + /// The result hit information. Valid only when query succeed. + /// True if ray hits an matching object, otherwise false. + bool RayCast(const Vector3& startPosition, const Vector3& endPosition, NavMeshHit& hitInfo) const; + +public: + + /// + /// Sets the size of the tile (if not assigned). Disposes the mesh if added tiles have different size. + /// + /// The size of the tile. + void SetTileSize(float tileSize); + + /// + /// Ensures the navmesh capacity for adding new tiles. Performs resizing if needed. + /// + /// The new tiles amount. + void EnsureCapacity(int32 tilesToAddCount); + + /// + /// Adds the tiles from the given scene to the runtime navmesh. + /// + /// The navigation mesh. + void AddTiles(NavMesh* navMesh); + + /// + /// Adds the tile from the given scene to the runtime navmesh. + /// + /// The navigation mesh. + /// The tile data. + void AddTile(NavMesh* navMesh, NavMeshTileData& tileData); + + /// + /// Removes all the tiles from the navmesh that has been added from the given navigation scene. + /// + /// The navigation mesh. + void RemoveTiles(NavMesh* navMesh); + + /// + /// Removes the tile from the navmesh. + /// + /// The tile X coordinate. + /// The tile Y coordinate. + /// The tile layer. + void RemoveTile(int32 x, int32 y, int32 layer); + + /// + /// Removes all the tiles that custom prediction callback marks. + /// + /// The prediction callback, returns true for tiles to remove and false for tiles to preserve. + /// The user data passed to the callback method. + void RemoveTiles(bool (*prediction)(const NavMeshRuntime* navMesh, const NavMeshTile& tile, void* customData), void* userData); + +#if COMPILE_WITH_DEBUG_DRAW + void DebugDraw(); +#endif + + /// + /// Releases the navmesh. + /// + void Dispose(); + +private: + + void AddTileInternal(NavMesh* navMesh, NavMeshTileData& tileData); +}; diff --git a/Source/Engine/Navigation/NavModifierVolume.cpp b/Source/Engine/Navigation/NavModifierVolume.cpp new file mode 100644 index 000000000..e72393a03 --- /dev/null +++ b/Source/Engine/Navigation/NavModifierVolume.cpp @@ -0,0 +1,84 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#include "NavModifierVolume.h" +#include "NavigationSettings.h" +#include "NavMeshBuilder.h" +#include "Engine/Level/Scene/Scene.h" +#include "Engine/Serialization/Serialization.h" +#if USE_EDITOR +#include "Editor/Editor.h" +#include "Editor/Managed/ManagedEditor.h" +#endif + +NavModifierVolume::NavModifierVolume(const SpawnParams& params) + : BoxVolume(params) +{ + _size = 100.0f; +} + +NavAreaProperties* NavModifierVolume::GetNavArea() const +{ + auto settings = NavigationSettings::Get(); + for (auto& navArea : settings->NavAreas) + { + if (navArea.Name == AreaName) + return &navArea; + } + return nullptr; +} + +void NavModifierVolume::Serialize(SerializeStream& stream, const void* otherObj) +{ + // Base + BoxVolume::Serialize(stream, otherObj); + + SERIALIZE_GET_OTHER_OBJ(NavModifierVolume); + + SERIALIZE_MEMBER(AgentsMask, AgentsMask.Mask); + SERIALIZE(AreaName); +} + +void NavModifierVolume::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) +{ + // Base + BoxVolume::Deserialize(stream, modifier); + + DESERIALIZE_MEMBER(AgentsMask, AgentsMask.Mask); + DESERIALIZE(AreaName); +} + +void NavModifierVolume::OnBoundsChanged(const BoundingBox& prevBounds) +{ +#if COMPILE_WITH_NAV_MESH_BUILDER + // Auto-rebuild modified navmesh area + if ( + IsDuringPlay() && IsActiveInHierarchy() && HasStaticFlag(StaticFlags::Navigation) && + ( + // Build at runtime for dynamic modifiers + !HasStaticFlag(StaticFlags::Transform) +#if USE_EDITOR + // Build in editor when using auto-rebuild option + || (!Editor::IsPlayMode && Editor::Managed->CanAutoBuildNavMesh()) +#endif + )) + { + BoundingBox dirtyBounds; + BoundingBox::Merge(prevBounds, _box, dirtyBounds); +#if USE_EDITOR + const float timeoutMs = ManagedEditor::ManagedEditorOptions.AutoRebuildNavMeshTimeoutMs; +#else + const float timeoutMs = 0.0f; +#endif + NavMeshBuilder::Build(GetScene(), dirtyBounds, timeoutMs); + } +#endif +} + +#if USE_EDITOR + +Color NavModifierVolume::GetWiresColor() +{ + return Color::Red; +} + +#endif diff --git a/Source/Engine/Navigation/NavModifierVolume.h b/Source/Engine/Navigation/NavModifierVolume.h new file mode 100644 index 000000000..9b6a3e764 --- /dev/null +++ b/Source/Engine/Navigation/NavModifierVolume.h @@ -0,0 +1,48 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Engine/Level/Actors/BoxVolume.h" +#include "NavigationTypes.h" + +/// +/// A special type of volume that defines the area of the scene in which navigation is restricted (eg. higher traversal cost or dynamic obstacle block). +/// +API_CLASS() class FLAXENGINE_API NavModifierVolume : public BoxVolume +{ +DECLARE_SCENE_OBJECT(NavModifierVolume); +public: + + /// + /// The agent types used by this navmesh modifier volume (from navigation settings). Can be used to adjust navmesh for a certain set of agents. + /// + API_FIELD(Attributes="EditorDisplay(\"Navigation\"), EditorOrder(10)") + NavAgentMask AgentsMask; + + /// + /// The name of the nav area to apply within the modifiers volume. Nav area properties are picked from the Navigation Settings asset. + /// + API_FIELD(Attributes="EditorDisplay(\"Navigation\"), EditorOrder(20)") + String AreaName; + +public: + + /// + /// Gets the properties of the nav area used by this volume. Null if missing or invalid area name. + /// + NavAreaProperties* GetNavArea() const; + +public: + + // [BoxVolume] + void Serialize(SerializeStream& stream, const void* otherObj) override; + void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; + +protected: + + // [BoxVolume] + void OnBoundsChanged(const BoundingBox& prevBounds) override; +#if USE_EDITOR + Color GetWiresColor() override; +#endif +}; diff --git a/Source/Engine/Navigation/Navigation.Build.cs b/Source/Engine/Navigation/Navigation.Build.cs index 85662915f..647995cdf 100644 --- a/Source/Engine/Navigation/Navigation.Build.cs +++ b/Source/Engine/Navigation/Navigation.Build.cs @@ -15,6 +15,7 @@ public class Navigation : EngineModule options.PublicDefinitions.Add("COMPILE_WITH_NAV_MESH_BUILDER"); + options.PrivateDependencies.Add("Level"); options.PrivateDependencies.Add("recastnavigation"); if (options.Target.IsEditor) diff --git a/Source/Engine/Navigation/Navigation.cpp b/Source/Engine/Navigation/Navigation.cpp index ac23bedea..9ff3498d7 100644 --- a/Source/Engine/Navigation/Navigation.cpp +++ b/Source/Engine/Navigation/Navigation.cpp @@ -1,19 +1,153 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. #include "Navigation.h" -#include "Engine/Threading/Threading.h" -#include "Engine/Level/Scene/Scene.h" -#include "Engine/Engine/EngineService.h" +#include "NavigationSettings.h" +#include "NavMeshRuntime.h" #include "NavMeshBuilder.h" +#include "Engine/Core/Config/GameSettings.h" +#include "Engine/Content/Content.h" +#include "Engine/Content/JsonAsset.h" +#include "Engine/Threading/Threading.h" +#if USE_EDITOR +#include "Engine/Level/Level.h" +#include "Engine/Level/Scene/Scene.h" +#endif #include "NavMesh.h" -#include + +#include "Engine/Engine/EngineService.h" +#include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Serialization/Serialization.h" #include -#include +#include -#define DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL 50.0f -#define DEFAULT_NAV_QUERY_EXTENT_VERTICAL 250.0f +namespace +{ + Array> NavMeshes; +} -NavMesh* _navMesh = nullptr; +NavMeshRuntime* NavMeshRuntime::Get(const StringView& navMeshName) +{ + NavMeshRuntime* result = nullptr; + for (auto navMesh : NavMeshes) + { + if (navMesh->Properties.Name == navMeshName) + { + result = navMesh; + break; + } + } + return result; +} + +NavMeshRuntime* NavMeshRuntime::Get(const NavAgentProperties& agentProperties) +{ + NavMeshRuntime* result = nullptr; + // TODO: maybe build lookup table for agentProperties -> navMesh to improve perf on frequent calls? + float bestAgentRadiusDiff = -MAX_float; + float bestAgentHeightDiff = -MAX_float; + bool bestIsValid = false; + for (auto navMesh : NavMeshes) + { + const auto& navMeshProperties = navMesh->Properties; + const float agentRadiusDiff = navMeshProperties.Agent.Radius - agentProperties.Radius; + const float agentHeightDiff = navMeshProperties.Agent.Height - agentProperties.Height; + const bool isValid = agentRadiusDiff >= 0.0f && agentHeightDiff >= 0.0f; + + // NavMesh must be valid for an agent and be first valid or have better properties than the best matching result so far + if (isValid + && + ( + !bestIsValid + || + (agentRadiusDiff + agentHeightDiff < bestAgentRadiusDiff + bestAgentHeightDiff) + ) + ) + { + result = navMesh; + bestIsValid = true; + bestAgentRadiusDiff = agentRadiusDiff; + bestAgentHeightDiff = agentHeightDiff; + } + } + return result; +} + +NavMeshRuntime* NavMeshRuntime::Get(const NavMeshProperties& navMeshProperties, bool createIfMissing) +{ + NavMeshRuntime* result = nullptr; + for (auto navMesh : NavMeshes) + { + if (navMesh->Properties == navMeshProperties) + { + result = navMesh; + break; + } + } + if (!result && createIfMissing) + { + // Create a new navmesh + result = New(navMeshProperties); + NavMeshes.Add(result); + } + return result; +} + +static_assert(ARRAY_COUNT(NavMeshRuntime::NavAreasCosts) == DT_MAX_AREAS, "Invalid nav areas amount limit."); +float NavMeshRuntime::NavAreasCosts[64]; +#if COMPILE_WITH_DEBUG_DRAW +Color NavMeshRuntime::NavAreasColors[64]; +#endif + +bool NavAgentProperties::operator==(const NavAgentProperties& other) const +{ + return Math::NearEqual(Radius, other.Radius) && Math::NearEqual(Height, other.Height) && Math::NearEqual(StepHeight, other.StepHeight) && Math::NearEqual(MaxSlopeAngle, other.MaxSlopeAngle); +} + +bool NavAgentMask::IsAgentSupported(int32 agentIndex) const +{ + return (Mask & (1 << agentIndex)) != 0; +} + +bool NavAgentMask::IsAgentSupported(const NavAgentProperties& agentProperties) const +{ + auto settings = NavigationSettings::Get(); + for (int32 agentIndex = 0; agentIndex < settings->NavMeshes.Count(); agentIndex++) + { + if (settings->NavMeshes[agentIndex].Agent == agentProperties) + { + return (Mask & (1 << agentIndex)) != 0; + } + } + return false; +} + +bool NavAgentMask::IsNavMeshSupported(const NavMeshProperties& navMeshProperties) const +{ + auto settings = NavigationSettings::Get(); + for (int32 agentIndex = 0; agentIndex < settings->NavMeshes.Count(); agentIndex++) + { + if (settings->NavMeshes[agentIndex] == navMeshProperties) + { + return (Mask & (1 << agentIndex)) != 0; + } + } + return false; +} + +bool NavAgentMask::operator==(const NavAgentMask& other) const +{ + return Mask == other.Mask; +} + +bool NavAreaProperties::operator==(const NavAreaProperties& other) const +{ + return Name == other.Name && Id == other.Id && Math::NearEqual(Cost, other.Cost); +} + +bool NavMeshProperties::operator==(const NavMeshProperties& other) const +{ + return Name == other.Name && Quaternion::NearEqual(Rotation, other.Rotation, 0.001f) && Agent == other.Agent; +} class NavigationService : public EngineService { @@ -36,11 +170,6 @@ public: NavigationService NavigationServiceInstance; -NavMesh* Navigation::GetNavMesh() -{ - return _navMesh; -} - void* dtAllocDefault(size_t size, dtAllocHint) { return Allocator::Allocate(size); @@ -51,15 +180,89 @@ void* rcAllocDefault(size_t size, rcAllocHint) return Allocator::Allocate(size); } +NavigationSettings::NavigationSettings() +{ + // Init navmeshes + NavMeshes.Resize(1); + auto& navMesh = NavMeshes[0]; + navMesh.Name = TEXT("Default"); + + // Init nav areas + NavAreas.Resize(2); + auto& areaNull = NavAreas[0]; + areaNull.Name = TEXT("Null"); + areaNull.Color = Color::Transparent; + areaNull.Id = 0; + areaNull.Cost = MAX_float; + auto& areaWalkable = NavAreas[1]; + areaWalkable.Name = TEXT("Walkable"); + areaWalkable.Color = Color::Transparent; + areaWalkable.Id = 63; + areaWalkable.Cost = 1; +} + +IMPLEMENT_SETTINGS_GETTER(NavigationSettings, Navigation); + +void NavigationSettings::Apply() +{ + // Cache areas properties + for (auto& area : NavAreas) + { + if (area.Id < DT_MAX_AREAS) + { + NavMeshRuntime::NavAreasCosts[area.Id] = area.Cost; +#if USE_EDITOR + NavMeshRuntime::NavAreasColors[area.Id] = area.Color; +#endif + } + } +} + +void NavigationSettings::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) +{ + DESERIALIZE(AutoAddMissingNavMeshes); + DESERIALIZE(AutoRemoveMissingNavMeshes); + DESERIALIZE(CellHeight); + DESERIALIZE(CellSize); + DESERIALIZE(TileSize); + DESERIALIZE(MinRegionArea); + DESERIALIZE(MergeRegionArea); + DESERIALIZE(MaxEdgeLen); + DESERIALIZE(MaxEdgeError); + DESERIALIZE(DetailSamplingDist); + DESERIALIZE(MaxDetailSamplingError); + if (modifier->EngineBuild >= 6215) + { + DESERIALIZE(NavMeshes); + } + else + { + // [Deprecated on 12.01.2021, expires on 12.01.2022] + float WalkableRadius = 34.0f; + float WalkableHeight = 144.0f; + float WalkableMaxClimb = 35.0f; + float WalkableMaxSlopeAngle = 60.0f; + DESERIALIZE(WalkableRadius); + DESERIALIZE(WalkableHeight); + DESERIALIZE(WalkableMaxClimb); + DESERIALIZE(WalkableMaxSlopeAngle); + NavMeshes.Resize(1); + auto& navMesh = NavMeshes[0]; + navMesh.Name = TEXT("Default"); + navMesh.Agent.Radius = WalkableRadius; + navMesh.Agent.Height = WalkableHeight; + navMesh.Agent.StepHeight = WalkableMaxClimb; + navMesh.Agent.MaxSlopeAngle = WalkableMaxSlopeAngle; + } + DESERIALIZE(NavAreas); +} + bool NavigationService::Init() { // Link memory allocation calls to use engine default allocator dtAllocSetCustom(dtAllocDefault, Allocator::Free); rcAllocSetCustom(rcAllocDefault, Allocator::Free); - // Create global nav mesh - _navMesh = New(); - return false; } @@ -74,156 +277,49 @@ void NavigationService::Update() void NavigationService::Dispose() { - if (_navMesh) + // Release nav meshes + for (auto navMesh : NavMeshes) { - _navMesh->Dispose(); - Delete(_navMesh); - _navMesh = nullptr; + navMesh->Dispose(); + Delete(navMesh); } + NavMeshes.Clear(); + NavMeshes.ClearDelete(); } bool Navigation::FindDistanceToWall(const Vector3& startPosition, NavMeshHit& hitInfo, float maxDistance) { - ScopeLock lock(_navMesh->Locker); - - const auto query = _navMesh->GetNavMeshQuery(); - if (!query) - { + if (NavMeshes.IsEmpty()) return false; - } - - dtQueryFilter filter; - Vector3 extent(DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL, DEFAULT_NAV_QUERY_EXTENT_VERTICAL, DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL); - - dtPolyRef startPoly = 0; - query->findNearestPoly(&startPosition.X, &extent.X, &filter, &startPoly, nullptr); - if (!startPoly) - { - return false; - } - - return dtStatusSucceed(_navMesh->GetNavMeshQuery()->findDistanceToWall(startPoly, &startPosition.X, maxDistance, &filter, &hitInfo.Distance, &hitInfo.Position.X, &hitInfo.Normal.X)); + return NavMeshes.First()->FindDistanceToWall(startPosition, hitInfo, maxDistance); } bool Navigation::FindPath(const Vector3& startPosition, const Vector3& endPosition, Array& resultPath) { - resultPath.Clear(); - ScopeLock lock(_navMesh->Locker); - - const auto query = _navMesh->GetNavMeshQuery(); - if (!query) - { + if (NavMeshes.IsEmpty()) return false; - } + return NavMeshes.First()->FindPath(startPosition, endPosition, resultPath); +} - dtQueryFilter filter; - Vector3 extent(DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL, DEFAULT_NAV_QUERY_EXTENT_VERTICAL, DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL); - - dtPolyRef startPoly = 0; - query->findNearestPoly(&startPosition.X, &extent.X, &filter, &startPoly, nullptr); - if (!startPoly) - { +bool Navigation::TestPath(const Vector3& startPosition, const Vector3& endPosition) +{ + if (NavMeshes.IsEmpty()) return false; - } - dtPolyRef endPoly = 0; - query->findNearestPoly(&endPosition.X, &extent.X, &filter, &endPoly, nullptr); - if (!endPoly) - { - return false; - } - - dtPolyRef path[NAV_MESH_PATH_MAX_SIZE]; - int32 pathSize; - const auto findPathStatus = _navMesh->GetNavMeshQuery()->findPath(startPoly, endPoly, &startPosition.X, &endPosition.X, &filter, path, &pathSize, NAV_MESH_PATH_MAX_SIZE); - if (dtStatusFailed(findPathStatus)) - { - return false; - } - - // Check for special case, where path has not been found, and starting polygon was the one closest to the target - if (pathSize == 1 && dtStatusDetail(findPathStatus, DT_PARTIAL_RESULT)) - { - // In this case we find a point on starting polygon, that's closest to destination and store it as path end - resultPath.Resize(2); - resultPath[0] = startPosition; - resultPath[1] = startPosition; - _navMesh->GetNavMeshQuery()->closestPointOnPolyBoundary(startPoly, &endPosition.X, &resultPath[1].X); - } - else - { - int straightPathCount = 0; - resultPath.EnsureCapacity(NAV_MESH_PATH_MAX_SIZE); - const auto findStraightPathStatus = _navMesh->GetNavMeshQuery()->findStraightPath(&startPosition.X, &endPosition.X, path, pathSize, (float*)resultPath.Get(), nullptr, nullptr, &straightPathCount, resultPath.Capacity(), DT_STRAIGHTPATH_AREA_CROSSINGS); - if (dtStatusFailed(findStraightPathStatus)) - { - return false; - } - resultPath.Resize(straightPathCount); - } - - return true; + return NavMeshes.First()->TestPath(startPosition, endPosition); } bool Navigation::ProjectPoint(const Vector3& point, Vector3& result) { - ScopeLock lock(_navMesh->Locker); - - const auto query = _navMesh->GetNavMeshQuery(); - if (!query) - { + if (NavMeshes.IsEmpty()) return false; - } - - dtQueryFilter filter; - Vector3 extent(DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL, DEFAULT_NAV_QUERY_EXTENT_VERTICAL, DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL); - - dtPolyRef startPoly = 0; - query->findNearestPoly(&point.X, &extent.X, &filter, &startPoly, &result.X); - if (!startPoly) - { - return false; - } - - return true; + return NavMeshes.First()->ProjectPoint(point, result); } bool Navigation::RayCast(const Vector3& startPosition, const Vector3& endPosition, NavMeshHit& hitInfo) { - ScopeLock lock(_navMesh->Locker); - - const auto query = _navMesh->GetNavMeshQuery(); - if (!query) - { + if (NavMeshes.IsEmpty()) return false; - } - - dtQueryFilter filter; - Vector3 extent(DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL, DEFAULT_NAV_QUERY_EXTENT_VERTICAL, DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL); - - dtPolyRef startPoly = 0; - query->findNearestPoly(&startPosition.X, &extent.X, &filter, &startPoly, nullptr); - if (!startPoly) - { - return false; - } - - dtRaycastHit hit; - hit.path = nullptr; - hit.maxPath = 0; - const bool result = dtStatusSucceed(_navMesh->GetNavMeshQuery()->raycast(startPoly, &startPosition.X, &endPosition.X, &filter, 0, &hit)); - if (hit.t >= MAX_float) - { - hitInfo.Position = endPosition; - hitInfo.Distance = 0; - } - else - { - hitInfo.Position = startPosition + (endPosition - startPosition) * hit.t; - hitInfo.Distance = hit.t; - } - hitInfo.Normal = *(Vector3*)&hit.hitNormal; - - return result; + return NavMeshes.First()->RayCast(startPosition, endPosition, hitInfo); } #if COMPILE_WITH_NAV_MESH_BUILDER @@ -250,96 +346,34 @@ void Navigation::BuildNavMesh(Scene* scene, const BoundingBox& dirtyBounds, floa #endif -#if USE_EDITOR - -#include "Engine/Debug/DebugDraw.h" - -void DrawPoly(const dtMeshTile& tile, const dtPoly& poly) -{ - const unsigned int ip = (unsigned int)(&poly - tile.polys); - const dtPolyDetail& pd = tile.detailMeshes[ip]; - const float DrawOffsetY = 20.0f; - - for (int i = 0; i < pd.triCount; i++) - { - Vector3 v[3]; - const unsigned char* t = &tile.detailTris[(pd.triBase + i) * 4]; - - for (int k = 0; k < 3; k++) - { - if (t[k] < poly.vertCount) - { - v[k] = *(Vector3*)&tile.verts[poly.verts[t[k]] * 3]; - } - else - { - v[k] = *(Vector3*)&tile.detailVerts[(pd.vertBase + t[k] - poly.vertCount) * 3]; - } - } - - v[0].Y += DrawOffsetY; - v[1].Y += DrawOffsetY; - v[2].Y += DrawOffsetY; - - DEBUG_DRAW_TRIANGLE(v[0], v[1], v[2], Color::Green * 0.5f, 0, true); - } - - for (int k = 0; k < pd.triCount; k++) - { - const unsigned char* t = &tile.detailTris[(pd.triBase + k) * 4]; - Vector3 v[3]; - - for (int m = 0; m < 3; m++) - { - if (t[m] < poly.vertCount) - v[m] = *(Vector3*)&tile.verts[poly.verts[t[m]] * 3]; - else - v[m] = *(Vector3*)&tile.detailVerts[(pd.vertBase + (t[m] - poly.vertCount)) * 3]; - } - - v[0].Y += DrawOffsetY; - v[1].Y += DrawOffsetY; - v[2].Y += DrawOffsetY; - - for (int m = 0, n = 2; m < 3; n = m++) - { - // Skip inner detail edges - if (((t[3] >> (n * 2)) & 0x3) == 0) - continue; - - DEBUG_DRAW_LINE(v[n], v[m], Color::YellowGreen, 0, true); - } - } -} +#if COMPILE_WITH_DEBUG_DRAW void Navigation::DrawNavMesh() { - if (!_navMesh) - return; - - ScopeLock lock(_navMesh->Locker); - - const dtNavMesh* dtNavMesh = _navMesh->GetNavMesh(); - const int tilesCount = dtNavMesh ? dtNavMesh->getMaxTiles() : 0; - if (tilesCount == 0) - return; - - for (int tileIndex = 0; tileIndex < tilesCount; tileIndex++) + for (auto navMesh : NavMeshes) { - const dtMeshTile* tile = dtNavMesh->getTile(tileIndex); - if (!tile->header) - continue; - - //DebugDraw::DrawWireBox(*(BoundingBox*)&tile->header->bmin[0], Color::CadetBlue); - - for (int i = 0; i < tile->header->polyCount; i++) +#if USE_EDITOR + // Skip drawing if any of the navmeshes on scene has disabled ShowDebugDraw option + bool skip = false; + for (auto scene : Level::Scenes) { - const dtPoly* poly = &tile->polys[i]; - if (poly->getType() != DT_POLYTYPE_GROUND) - continue; - - DrawPoly(*tile, *poly); + for (auto e : scene->NavigationMeshes) + { + if (e->Properties == navMesh->Properties) + { + if (!e->ShowDebugDraw) + { + skip = true; + } + break; + } + } } + if (skip) + continue; +#endif + + navMesh->DebugDraw(); } } diff --git a/Source/Engine/Navigation/Navigation.h b/Source/Engine/Navigation/Navigation.h index 8c896099b..dcd6b9fbb 100644 --- a/Source/Engine/Navigation/Navigation.h +++ b/Source/Engine/Navigation/Navigation.h @@ -2,36 +2,9 @@ #pragma once -#include "Engine/Scripting/ScriptingType.h" -#include "Engine/Core/Math/Vector3.h" +#include "NavigationTypes.h" class Scene; -class NavMesh; - -#define NAV_MESH_PATH_MAX_SIZE 200 - -/// -/// The result information for navigation mesh queries. -/// -API_STRUCT() struct NavMeshHit -{ -DECLARE_SCRIPTING_TYPE_MINIMAL(NavMeshHit); - - /// - /// The hit point position. - /// - API_FIELD() Vector3 Position; - - /// - /// The distance to hit point (from the query origin). - /// - API_FIELD() float Distance; - - /// - /// The hit point normal vector. - /// - API_FIELD() Vector3 Normal; -}; /// /// The navigation service used for path finding and agents navigation system. @@ -39,14 +12,6 @@ DECLARE_SCRIPTING_TYPE_MINIMAL(NavMeshHit); API_CLASS(Static) class FLAXENGINE_API Navigation { DECLARE_SCRIPTING_TYPE_NO_SPAWN(Navigation); -public: - - /// - /// Gets the navigation mesh (read-only). Use the other API to request data to use safe access to the navigation system. - /// - /// The navigation mesh. - static NavMesh* GetNavMesh(); - public: /// @@ -67,6 +32,14 @@ public: /// True if found valid path between given two points (it may be partial), otherwise false if failed. API_FUNCTION() static bool FindPath(const Vector3& startPosition, const Vector3& endPosition, API_PARAM(Out) Array& resultPath); + /// + /// Tests the path between the two positions (non-partial). + /// + /// The start position. + /// The end position. + /// True if found valid path between given two points, otherwise false if failed. + API_FUNCTION() static bool TestPath(const Vector3& startPosition, const Vector3& endPosition); + /// /// Projects the point to nav mesh surface (finds the nearest polygon). /// @@ -121,7 +94,7 @@ public: #endif -#if USE_EDITOR +#if COMPILE_WITH_DEBUG_DRAW /// /// Draws the navigation for all the scenes (uses DebugDraw interface). diff --git a/Source/Engine/Navigation/NavigationScene.cpp b/Source/Engine/Navigation/NavigationScene.cpp deleted file mode 100644 index c92c14ac1..000000000 --- a/Source/Engine/Navigation/NavigationScene.cpp +++ /dev/null @@ -1,141 +0,0 @@ -// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. - -#include "NavigationScene.h" -#include "NavMesh.h" -#include "Navigation.h" -#include "NavMeshBoundsVolume.h" -#include "Engine/Profiler/ProfilerCPU.h" -#include "Engine/Level/Scene/Scene.h" -#include "Engine/Content/Assets/RawDataAsset.h" -#include "Engine/Core/Log.h" -#if USE_EDITOR -#include "Editor/Editor.h" -#endif -#if COMPILE_WITH_ASSETS_IMPORTER -#include "Engine/ContentImporters/AssetsImportingManager.h" -#include "Engine/Serialization/MemoryWriteStream.h" -#endif - -NavigationScene::NavigationScene(::Scene* scene) - : Scene(scene) - , IsDataDirty(false) -{ - DataAsset.Loaded.Bind(this); -} - -BoundingBox NavigationScene::GetNavigationBounds() -{ - if (Volumes.IsEmpty()) - return BoundingBox::Empty; - - PROFILE_CPU_NAMED("GetNavigationBounds"); - - auto box = Volumes[0]->GetBox(); - for (int32 i = 1; i < Volumes.Count(); i++) - BoundingBox::Merge(box, Volumes[i]->GetBox(), box); - return box; -} - -NavMeshBoundsVolume* NavigationScene::FindNavigationBoundsOverlap(const BoundingBox& bounds) -{ - NavMeshBoundsVolume* result = nullptr; - - for (int32 i = 0; i < Volumes.Count(); i++) - { - if (Volumes[i]->GetBox().Intersects(bounds)) - { - result = Volumes[i]; - break; - } - } - - return result; -} - -void NavigationScene::SaveNavMesh() -{ -#if COMPILE_WITH_ASSETS_IMPORTER - -#if USE_EDITOR - // Skip if game is running in editor (eg. game scripts update dynamic navmesh) - if (Editor::IsPlayMode) - return; -#endif - - // Clear flag - IsDataDirty = false; - - // Check if has no navmesh data generated (someone could just remove navmesh volumes or generate for empty scene) - if (Data.Tiles.IsEmpty()) - { - // Keep asset reference valid - DataAsset.Unlink(); - return; - } - - // Prepare - Guid assetId = DataAsset.GetID(); - if (!assetId.IsValid()) - assetId = Guid::New(); - const String assetPath = Scene->GetDataFolderPath() / TEXT("NavMesh") + ASSET_FILES_EXTENSION_WITH_DOT; - - // Generate navmesh tiles data - const int32 streamInitialCapacity = Math::RoundUpToPowerOf2((Data.Tiles.Count() + 1) * 1024); - MemoryWriteStream stream(streamInitialCapacity); - Data.Save(stream); - BytesContainer bytesContainer; - bytesContainer.Link(stream.GetHandle(), stream.GetPosition()); - - // Save asset to file - if (AssetsImportingManager::Create(AssetsImportingManager::CreateRawDataTag, assetPath, assetId, (void*)&bytesContainer)) - { - LOG(Warning, "Failed to save navmesh tiles data to file."); - return; - } - - // Link the created asset - DataAsset = assetId; - -#endif -} - -void NavigationScene::OnEnable() -{ - auto navMesh = Navigation::GetNavMesh(); - if (navMesh) - navMesh->AddTiles(this); -} - -void NavigationScene::OnDisable() -{ - auto navMesh = Navigation::GetNavMesh(); - if (navMesh) - navMesh->RemoveTiles(this); -} - -void NavigationScene::OnDataAssetLoaded() -{ - // Skip if already has data (prevent reloading navmesh on saving) - if (Data.Tiles.HasItems()) - return; - - const bool isEnabled = Scene->IsDuringPlay() && Scene->IsActiveInHierarchy(); - - // Remove added tiles - if (isEnabled) - { - OnDisable(); - } - - // Load navmesh tiles - BytesContainer data; - data.Link(DataAsset->Data); - Data.Load(data, false); - IsDataDirty = false; - - // Add loaded tiles - if (isEnabled) - { - OnEnable(); - } -} diff --git a/Source/Engine/Navigation/NavigationScene.h b/Source/Engine/Navigation/NavigationScene.h deleted file mode 100644 index ecdca0744..000000000 --- a/Source/Engine/Navigation/NavigationScene.h +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. - -#pragma once - -#include "NavMeshData.h" -#include "Engine/Content/AssetReference.h" -#include "Engine/Content/Assets/RawDataAsset.h" - -class Scene; -class NavMeshBoundsVolume; - -/// -/// Scene object navigation data. -/// -class NavigationScene -{ - friend Scene; - -public: - - /// - /// Initializes a new instance of the class. - /// - /// The scene. - NavigationScene(Scene* scene); - -public: - - /// - /// The parent scene. - /// - Scene* Scene; - - /// - /// The flag used to mark that navigation data has been modified since load. Used to save runtime data to the file on scene serialization. - /// - bool IsDataDirty; - - /// - /// The navmesh tiles data. - /// - NavMeshData Data; - - /// - /// The cached navmesh data asset. - /// - AssetReference DataAsset; - -public: - - /// - /// The list of registered navigation bounds volumes (in the scene). - /// - Array Volumes; - - /// - /// Gets the total navigation volumes bounds. - /// - /// The navmesh bounds. - BoundingBox GetNavigationBounds(); - - /// - /// Finds the navigation volume bounds that have intersection with the given world-space bounding box. - /// - /// The bounds. - /// The intersecting volume or null if none found. - NavMeshBoundsVolume* FindNavigationBoundsOverlap(const BoundingBox& bounds); - -public: - - /// - /// Saves the nav mesh tiles data to the asset. Supported only in builds with assets saving enabled (eg. editor) and not during gameplay (eg. design time). - /// - void SaveNavMesh(); - - /// - /// Clears the data. - /// - void ClearData() - { - if (Data.Tiles.HasItems()) - { - IsDataDirty = true; - Data.TileSize = 0.0f; - Data.Tiles.Resize(0); - } - } - -protected: - - void OnEnable(); - void OnDisable(); - void OnDataAssetLoaded(); -}; diff --git a/Source/Engine/Navigation/NavigationSettings.cs b/Source/Engine/Navigation/NavigationSettings.cs new file mode 100644 index 000000000..8a97e4adc --- /dev/null +++ b/Source/Engine/Navigation/NavigationSettings.cs @@ -0,0 +1,116 @@ +// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved. + +using System; +using FlaxEngine; + +namespace FlaxEditor.Content.Settings +{ + partial class NavigationSettings + { + /// + /// Initializes a new instance of the class. + /// + public NavigationSettings() + { + // Init navmeshes + NavMeshes = new NavMeshProperties[1]; + ref var navMesh = ref NavMeshes[0]; + navMesh.Name = "Default"; + navMesh.Color = Color.Green; + navMesh.Rotation = Quaternion.Identity; + navMesh.Agent.Radius = 34.0f; + navMesh.Agent.Height = 144.0f; + navMesh.Agent.StepHeight = 35.0f; + navMesh.Agent.MaxSlopeAngle = 60.0f; + + // Init nav areas + NavAreas = new NavAreaProperties[2]; + ref var areaNull = ref NavAreas[0]; + areaNull.Name = "Null"; + areaNull.Color = Color.Transparent; + areaNull.Id = 0; + areaNull.Cost = float.MaxValue; + ref var areaWalkable = ref NavAreas[1]; + areaWalkable.Name = "Walkable"; + areaWalkable.Color = Color.Transparent; + areaWalkable.Id = 63; + areaWalkable.Cost = 1; + } + + // [Deprecated on 12.01.2021, expires on 12.01.2022] + private void UpgradeToNavMeshes() + { + if (NavMeshes != null && NavMeshes.Length == 1) + return; + NavMeshes = new NavMeshProperties[1]; + ref var navMesh = ref NavMeshes[0]; + navMesh.Name = "Default"; + navMesh.Color = Color.Green; + navMesh.Rotation = Quaternion.Identity; + navMesh.Agent.Radius = 34.0f; + navMesh.Agent.Height = 144.0f; + navMesh.Agent.StepHeight = 35.0f; + navMesh.Agent.MaxSlopeAngle = 60.0f; + } + + // [Deprecated on 12.01.2021, expires on 12.01.2022] + [Serialize, Obsolete] + private float WalkableRadius + { + get => throw new Exception(); + set + { + UpgradeToNavMeshes(); + NavMeshes[0].Agent.Radius = value; + } + } + + // [Deprecated on 12.01.2021, expires on 12.01.2022] + [Serialize, Obsolete] + private float WalkableHeight + { + get => throw new Exception(); + set + { + UpgradeToNavMeshes(); + NavMeshes[0].Agent.Height = value; + } + } + + // [Deprecated on 12.01.2021, expires on 12.01.2022] + [Serialize, Obsolete] + private float WalkableMaxClimb + { + get => throw new Exception(); + set + { + UpgradeToNavMeshes(); + NavMeshes[0].Agent.StepHeight = value; + } + } + + // [Deprecated on 12.01.2021, expires on 12.01.2022] + [Serialize, Obsolete] + private float WalkableMaxSlopeAngle + { + get => throw new Exception(); + set + { + UpgradeToNavMeshes(); + NavMeshes[0].Agent.MaxSlopeAngle = value; + } + } + } +} + +namespace FlaxEngine +{ + partial struct NavAgentProperties + { + /// + public override string ToString() + { + return $"Radius: {Radius}, Height: {Height}, StepHeight: {StepHeight}, MaxSlopeAngle: {MaxSlopeAngle}"; + } + } +} diff --git a/Source/Engine/Navigation/NavigationSettings.h b/Source/Engine/Navigation/NavigationSettings.h index 1e053d819..0b35d5153 100644 --- a/Source/Engine/Navigation/NavigationSettings.h +++ b/Source/Engine/Navigation/NavigationSettings.h @@ -2,118 +2,110 @@ #pragma once +#include "NavigationTypes.h" #include "Engine/Core/Config/Settings.h" -#include "Engine/Serialization/Serialization.h" +#include "Engine/Core/Collections/Array.h" /// /// The navigation system settings container. /// -/// -class NavigationSettings : public Settings +API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings", NoConstructor) class FLAXENGINE_API NavigationSettings : public SettingsBase { +DECLARE_SCRIPTING_TYPE_MINIMAL(NavigationSettings); public: /// - /// The height of a grid cell in the navigation mesh building steps using heightfields. - /// A lower number means higher precision on the vertical axis but longer build times. + /// If checked, enables automatic navmesh actors spawning on a scenes that are using it during navigation building. /// + API_FIELD(Attributes="EditorOrder(100), EditorDisplay(\"Navigation\")") + bool AutoAddMissingNavMeshes = true; + + /// + /// If checked, enables automatic navmesh actors removing from a scenes that are not using it during navigation building. + /// + API_FIELD(Attributes="EditorOrder(110), EditorDisplay(\"Navigation\")") + bool AutoRemoveMissingNavMeshes = true; + +public: + + /// + /// The height of a grid cell in the navigation mesh building steps using heightfields. A lower number means higher precision on the vertical axis but longer build times. + /// + API_FIELD(Attributes="Limit(1, 400), EditorOrder(210), EditorDisplay(\"Nav Mesh Options\")") float CellHeight = 10.0f; /// - /// The width/height of a grid cell in the navigation mesh building steps using heightfields. - /// A lower number means higher precision on the horizontal axes but longer build times. + /// The width/height of a grid cell in the navigation mesh building steps using heightfields. A lower number means higher precision on the horizontal axes but longer build times. /// + API_FIELD(Attributes="Limit(1, 400), EditorOrder(220), EditorDisplay(\"Nav Mesh Options\")") float CellSize = 30.0f; /// /// Tile size used for Navigation mesh tiles, the final size of a tile is CellSize*TileSize. /// + API_FIELD(Attributes="Limit(8, 4096), EditorOrder(230), EditorDisplay(\"Nav Mesh Options\")") int32 TileSize = 64; /// /// The minimum number of cells allowed to form isolated island areas. /// + API_FIELD(Attributes="Limit(0, 100), EditorOrder(240), EditorDisplay(\"Nav Mesh Options\")") int32 MinRegionArea = 0; /// /// Any regions with a span count smaller than this value will, if possible, be merged with larger regions. /// + API_FIELD(Attributes="Limit(0, 100), EditorOrder(250), EditorDisplay(\"Nav Mesh Options\")") int32 MergeRegionArea = 20; /// /// The maximum allowed length for contour edges along the border of the mesh. /// + API_FIELD(Attributes="Limit(100), EditorOrder(260), EditorDisplay(\"Nav Mesh Options\", \"Max Edge Length\")") float MaxEdgeLen = 1200.0f; /// /// The maximum distance a simplified contour's border edges should deviate from the original raw contour. /// + API_FIELD(Attributes="Limit(0.1f, 4), EditorOrder(270), EditorDisplay(\"Nav Mesh Options\")") float MaxEdgeError = 1.3f; /// /// The sampling distance to use when generating the detail mesh. /// + API_FIELD(Attributes="Limit(1), EditorOrder(280), EditorDisplay(\"Nav Mesh Options\", \"Detail Sampling Distance\")") float DetailSamplingDist = 600.0f; /// /// The maximum distance the detail mesh surface should deviate from heightfield data. /// + API_FIELD(Attributes="Limit(0, 3), EditorOrder(290), EditorDisplay(\"Nav Mesh Options\")") float MaxDetailSamplingError = 1.0f; - /// - /// The radius of the smallest objects to traverse this nav mesh. Objects can't pass through gaps of less than twice the radius. - /// - float WalkableRadius = 34.0f; - - /// - /// The height of the smallest objects to traverse this nav mesh. Objects can't enter areas with ceilings lower than this value. - /// - float WalkableHeight = 144.0f; - - /// - /// The maximum ledge height that is considered to still be traversable. - /// - float WalkableMaxClimb = 35.0f; - - /// - /// The maximum slope that is considered walkable (in degrees). Objects can't go up or down slopes higher than this value. - /// - float WalkableMaxSlopeAngle = 60.0f; - public: - // [Settings] - void RestoreDefault() final override - { - CellHeight = 10.0f; - CellSize = 30.0f; - TileSize = 64; - MinRegionArea = 0; - MergeRegionArea = 20; - MaxEdgeLen = 1200.0f; - MaxEdgeError = 1.3f; - DetailSamplingDist = 600.0f; - MaxDetailSamplingError = 1.0f; - WalkableRadius = 34.0f; - WalkableHeight = 144.0f; - WalkableMaxClimb = 35.0f; - WalkableMaxSlopeAngle = 60.0f; - } + /// + /// The configuration for navmeshes. + /// + API_FIELD(Attributes="EditorOrder(1000), EditorDisplay(\"Agents\", EditorDisplayAttribute.InlineStyle)") + Array NavMeshes; - void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override - { - DESERIALIZE(CellHeight); - DESERIALIZE(CellSize); - DESERIALIZE(TileSize); - DESERIALIZE(MinRegionArea); - DESERIALIZE(MergeRegionArea); - DESERIALIZE(MaxEdgeLen); - DESERIALIZE(MaxEdgeError); - DESERIALIZE(DetailSamplingDist); - DESERIALIZE(MaxDetailSamplingError); - DESERIALIZE(WalkableRadius); - DESERIALIZE(WalkableHeight); - DESERIALIZE(WalkableMaxClimb); - DESERIALIZE(WalkableMaxSlopeAngle); - } + /// + /// The configuration for nav areas. + /// + API_FIELD(Attributes="EditorOrder(2000), EditorDisplay(\"Areas\", EditorDisplayAttribute.InlineStyle)") + Array NavAreas; + +public: + + NavigationSettings(); + + /// + /// Gets the instance of the settings asset (default value if missing). Object returned by this method is always loaded with valid data to use. + /// + static NavigationSettings* Get(); + + // [SettingsBase] + void Apply() override; + void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override; }; diff --git a/Source/Engine/Navigation/NavigationTypes.h b/Source/Engine/Navigation/NavigationTypes.h new file mode 100644 index 000000000..11ebaeb1a --- /dev/null +++ b/Source/Engine/Navigation/NavigationTypes.h @@ -0,0 +1,179 @@ +// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Engine/Scripting/ScriptingType.h" +#include "Engine/Core/Types/String.h" +#include "Engine/Core/Math/Color.h" +#include "Engine/Core/Math/Vector3.h" +#include "Engine/Core/Math/Quaternion.h" +#include "Engine/Core/ISerializable.h" + +#define NAV_MESH_PATH_MAX_SIZE 200 + +/// +/// The navigation system agent properties container for navmesh building and querying. +/// +API_STRUCT() struct FLAXENGINE_API NavAgentProperties : ISerializable +{ +API_AUTO_SERIALIZATION(); +DECLARE_SCRIPTING_TYPE_MINIMAL(NavAgentProperties); + + /// + /// The radius of the agent used for navigation. Agents can't pass through gaps of less than twice the radius. + /// + API_FIELD(Attributes="EditorOrder(0)") + float Radius = 34.0f; + + /// + /// The height of the agent used for navigation. Agents can't enter areas with ceilings lower than this value. + /// + API_FIELD(Attributes="EditorOrder(10)") + float Height = 144.0f; + + /// + /// The step height used for navigation. Defines the maximum ledge height that is considered to still be traversable by the agent. + /// + API_FIELD(Attributes="EditorOrder(20)") + float StepHeight = 35.0f; + + /// + /// The maximum slope (in degrees) that is considered walkable for navigation. Agents can't go up or down slopes higher than this value. + /// + API_FIELD(Attributes="EditorOrder(30)") + float MaxSlopeAngle = 60.0f; + + bool operator==(const NavAgentProperties& other) const; + + bool operator!=(const NavAgentProperties& other) const + { + return !operator==(other); + } +}; + +/// +/// The navigation mesh properties container for navmesh building. +/// +API_STRUCT() struct FLAXENGINE_API NavMeshProperties : ISerializable +{ +API_AUTO_SERIALIZATION(); +DECLARE_SCRIPTING_TYPE_MINIMAL(NavMeshProperties); + + /// + /// The navmesh type name. Identifies different types of the navmeshes, used to sync navmesh properties with settings asset. + /// + API_FIELD(Attributes="EditorOrder(0)") + String Name; + + /// + /// The navmesh type color (for debugging). + /// + API_FIELD(Attributes="EditorOrder(10)") + Color Color = Color::Green; + + /// + /// The navmesh rotation applied to navigation surface. Used during building to the rotate scene geometry and to revert back result during path finding queries. Can be used to generate navmesh on walls. + /// + API_FIELD(Attributes="EditorOrder(20)") + Quaternion Rotation = Quaternion::Identity; + + /// + /// The properties of the agent used to generate walkable navigation surface. + /// + API_FIELD(Attributes="EditorOrder(30)") + NavAgentProperties Agent; + + bool operator==(const NavMeshProperties& other) const; + + bool operator!=(const NavMeshProperties& other) const + { + return !operator==(other); + } +}; + +/// +/// The navigation system agents selection mask (from navigation system settings). Uses 1 bit per agent type (up to 32 agents). +/// +API_STRUCT() struct FLAXENGINE_API NavAgentMask +{ +DECLARE_SCRIPTING_TYPE_MINIMAL(NavAgentMask); + + /// + /// The agents selection mask. + /// + API_FIELD() uint32 Mask = MAX_uint32; + + bool IsAgentSupported(int32 agentIndex) const; + bool IsAgentSupported(const NavAgentProperties& agentProperties) const; + bool IsNavMeshSupported(const NavMeshProperties& navMeshProperties) const; + + bool operator==(const NavAgentMask& other) const; + + bool operator!=(const NavAgentMask& other) const + { + return !operator==(other); + } +}; + +/// +/// The result information for navigation mesh queries. +/// +API_STRUCT() struct FLAXENGINE_API NavMeshHit +{ +DECLARE_SCRIPTING_TYPE_MINIMAL(NavMeshHit); + + /// + /// The hit point position. + /// + API_FIELD() Vector3 Position; + + /// + /// The distance to hit point (from the query origin). + /// + API_FIELD() float Distance; + + /// + /// The hit point normal vector. + /// + API_FIELD() Vector3 Normal; +}; + +/// +/// The navigation area properties container for navmesh building and navigation runtime. +/// +API_STRUCT() struct FLAXENGINE_API NavAreaProperties : ISerializable +{ +API_AUTO_SERIALIZATION(); +DECLARE_SCRIPTING_TYPE_MINIMAL(NavAreaProperties); + + /// + /// The area type name. Identifies different types of the areas. + /// + API_FIELD(Attributes="EditorOrder(0)") + String Name; + + /// + /// The area type color (for debugging). Alpha channel is used to blend with navmesh color (alpha 0 to use navmesh color only). + /// + API_FIELD(Attributes="EditorOrder(10)") + Color Color = Color::Transparent; + + /// + /// The area id. It must be unique for the project. Valid range 0-63. Value 0 is reserved for Null areas (empty, non-navigable areas). + /// + API_FIELD(Attributes="EditorOrder(20), Limit(0, 63)") + byte Id; + + /// + /// The cost scale for the area traversal for agents. The higher the cost, the less likely agent wil choose the path that goes over it. For instance, areas that are harder to move like sand should have higher cost for proper path finding. + /// + API_FIELD(Attributes="EditorOrder(30), Limit(0, float.MaxValue, 0.1f)") + float Cost = 1; + + bool operator==(const NavAreaProperties& other) const; + + bool operator!=(const NavAreaProperties& other) const + { + return !operator==(other); + } +}; diff --git a/Source/Engine/Particles/Graph/GPU/GPUParticles.cpp b/Source/Engine/Particles/Graph/GPU/GPUParticles.cpp index befd5b25d..cd16a9c52 100644 --- a/Source/Engine/Particles/Graph/GPU/GPUParticles.cpp +++ b/Source/Engine/Particles/Graph/GPU/GPUParticles.cpp @@ -155,7 +155,7 @@ void GPUParticles::Execute(GPUContext* context, ParticleEmitter* emitter, Partic // Setup parameters MaterialParameter::BindMeta bindMeta; bindMeta.Context = context; - bindMeta.Buffer0 = hasCB ? _cbData.Get() + sizeof(GPUParticlesData) : nullptr; + bindMeta.Constants = hasCB ? _cbData.Get() + sizeof(GPUParticlesData) : nullptr; bindMeta.Input = nullptr; if (viewTask) { diff --git a/Source/Engine/Particles/ParticleManager.cpp b/Source/Engine/Particles/ParticleManager.cpp index 40cebd892..f2e70fee3 100644 --- a/Source/Engine/Particles/ParticleManager.cpp +++ b/Source/Engine/Particles/ParticleManager.cpp @@ -83,8 +83,8 @@ public: drawCall.Geometry.VertexBuffersOffsets[0] = 0; drawCall.Geometry.VertexBuffersOffsets[1] = 0; drawCall.Geometry.VertexBuffersOffsets[2] = 0; - drawCall.Geometry.StartIndex = 0; - drawCall.Geometry.IndicesCount = IndexCount; + drawCall.Draw.StartIndex = 0; + drawCall.Draw.IndicesCount = IndexCount; } }; @@ -173,10 +173,6 @@ void DrawEmitterCPU(RenderContext& renderContext, ParticleBuffer* buffer, DrawCa const auto context = GPUDevice::Instance->GetMainContext(); auto emitter = buffer->Emitter; - drawCall.InstanceCount = 1; - drawCall.IndirectArgsBuffer = nullptr; - drawCall.IndirectArgsOffset = 0; - // Check if need to perform any particles sorting if (emitter->Graph.SortModules.HasItems() && renderContext.View.Pass != DrawPass::Depth) { @@ -423,7 +419,7 @@ void DrawEmitterCPU(RenderContext& renderContext, ParticleBuffer* buffer, DrawCa { const int32 moduleIndex = renderModulesIndices[index]; auto module = emitter->Graph.RenderModules[moduleIndex]; - drawCall.Module = module; + drawCall.Particle.Module = module; switch (module->TypeID) { @@ -486,7 +482,7 @@ void DrawEmitterCPU(RenderContext& renderContext, ParticleBuffer* buffer, DrawCa int32 count = buffer->CPU.Count; // Setup ribbon data - auto& ribbon = drawCall.Ribbon; + auto& ribbon = drawCall.Particle.Ribbon; ribbon.UVTilingDistance = uvTilingDistance; ribbon.SegmentCount = ribbonModulesSegmentCount[ribbonModuleIndex]; ribbon.UVScaleX = uvScale.X; @@ -516,8 +512,8 @@ void DrawEmitterCPU(RenderContext& renderContext, ParticleBuffer* buffer, DrawCa drawCall.Geometry.VertexBuffersOffsets[0] = 0; drawCall.Geometry.VertexBuffersOffsets[1] = 0; drawCall.Geometry.VertexBuffersOffsets[2] = 0; - drawCall.Geometry.StartIndex = ribbonModulesDrawIndicesStart[ribbonModuleIndex]; - drawCall.Geometry.IndicesCount = ribbonModulesDrawIndicesCount[ribbonModuleIndex]; + drawCall.Draw.StartIndex = ribbonModulesDrawIndicesStart[ribbonModuleIndex]; + drawCall.Draw.IndicesCount = ribbonModulesDrawIndicesCount[ribbonModuleIndex]; drawCall.InstanceCount = 1; renderContext.List->AddDrawCall((DrawPass)(drawModes & moduleDrawModes), staticFlags, drawCall, false); @@ -557,7 +553,7 @@ void OnShaderReloading(Asset* obj) void CleanupGPUParticlesSorting() { - GPUParticlesSorting.Unlink(); + GPUParticlesSorting = nullptr; } void DrawEmitterGPU(RenderContext& renderContext, ParticleBuffer* buffer, DrawCall& drawCall, DrawPass drawModes, StaticFlags staticFlags, ParticleEmitterInstance& emitterData, const RenderModulesIndices& renderModulesIndices) @@ -788,7 +784,7 @@ void DrawEmitterGPU(RenderContext& renderContext, ParticleBuffer* buffer, DrawCa { int32 moduleIndex = renderModulesIndices[index]; auto module = emitter->Graph.RenderModules[moduleIndex]; - drawCall.Module = module; + drawCall.Particle.Module = module; switch (module->TypeID) { @@ -802,8 +798,8 @@ void DrawEmitterGPU(RenderContext& renderContext, ParticleBuffer* buffer, DrawCa // Submit draw call SpriteRenderer.SetupDrawCall(drawCall); drawCall.InstanceCount = 0; - drawCall.IndirectArgsBuffer = buffer->GPU.IndirectDrawArgsBuffer; - drawCall.IndirectArgsOffset = indirectDrawCallIndex * sizeof(GPUDrawIndexedIndirectArgs); + drawCall.Draw.IndirectArgsBuffer = buffer->GPU.IndirectDrawArgsBuffer; + drawCall.Draw.IndirectArgsOffset = indirectDrawCallIndex * sizeof(GPUDrawIndexedIndirectArgs); renderContext.List->AddDrawCall((DrawPass)(drawModes & moduleDrawModes), staticFlags, drawCall, false); indirectDrawCallIndex++; @@ -830,8 +826,8 @@ void DrawEmitterGPU(RenderContext& renderContext, ParticleBuffer* buffer, DrawCa // Execute draw call mesh.GetDrawCallGeometry(drawCall); drawCall.InstanceCount = 0; - drawCall.IndirectArgsBuffer = buffer->GPU.IndirectDrawArgsBuffer; - drawCall.IndirectArgsOffset = indirectDrawCallIndex * sizeof(GPUDrawIndexedIndirectArgs); + drawCall.Draw.IndirectArgsBuffer = buffer->GPU.IndirectDrawArgsBuffer; + drawCall.Draw.IndirectArgsOffset = indirectDrawCallIndex * sizeof(GPUDrawIndexedIndirectArgs); renderContext.List->AddDrawCall((DrawPass)(drawModes & moduleDrawModes), staticFlags, drawCall, false); indirectDrawCallIndex++; } @@ -875,9 +871,7 @@ void ParticleManager::DrawParticles(RenderContext& renderContext, ParticleEffect // Setup a draw call common data DrawCall drawCall; - drawCall.LightmapUVsArea = Rectangle::Empty; drawCall.PerInstanceRandom = effect->GetPerInstanceRandom(); - drawCall.LODDitherFactor = 1.0f; drawCall.ObjectPosition = world.GetTranslation(); // Draw all emitters @@ -890,9 +884,8 @@ void ParticleManager::DrawParticles(RenderContext& renderContext, ParticleEffect auto emitter = buffer->Emitter; drawCall.World = emitter->SimulationSpace == ParticlesSimulationSpace::World ? Matrix::Identity : world; - drawCall.PrevWorld = drawCall.World; drawCall.WorldDeterminantSign = Math::FloatSelect(drawCall.World.RotDeterminant(), 1, -1); - drawCall.Particles = buffer; + drawCall.Particle.Particles = buffer; // Check if need to render any module RenderModulesIndices renderModulesIndices; diff --git a/Source/Engine/Physics/Actors/SplineRopeBody.cpp b/Source/Engine/Physics/Actors/SplineRopeBody.cpp new file mode 100644 index 000000000..a610e76ce --- /dev/null +++ b/Source/Engine/Physics/Actors/SplineRopeBody.cpp @@ -0,0 +1,183 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#include "SplineRopeBody.h" +#include "Engine/Level/Actors/Spline.h" +#include "Engine/Level/Scene/Scene.h" +#include "Engine/Physics/Physics.h" +#include "Engine/Engine/Time.h" +#include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Serialization/Serialization.h" + +SplineRopeBody::SplineRopeBody(const SpawnParams& params) + : Actor(params) +{ +} + +void SplineRopeBody::Tick() +{ + if (!_spline || _spline->GetSplinePointsCount() < 2) + return; + PROFILE_CPU(); + + // Cache data + const Vector3 gravity = Physics::GetGravity() * GravityScale; + auto& keyframes = _spline->Curve.GetKeyframes(); + const Transform splineTransform = _spline->GetTransform(); + const int32 keyframesCount = keyframes.Count(); + const float substepTime = SubstepTime; + const float substepTimeSqr = substepTime * substepTime; + bool splineDirty = false; + + // Synchronize spline keyframes with simulated masses + if (_masses.Count() > keyframesCount) + _masses.Resize(keyframesCount); + else + { + _masses.EnsureCapacity(keyframesCount); + while (_masses.Count() < keyframesCount) + { + const int32 i = _masses.Count(); + auto& mass = _masses.AddOne(); + mass.PrevPosition = splineTransform.LocalToWorld(keyframes[i].Value.Translation); + if (i != 0) + mass.SegmentLength = Vector3::Distance(mass.PrevPosition, _masses[i - 1].PrevPosition); + else + mass.SegmentLength = 0.0f; + } + } + { + // Rope start + auto& mass = _masses.First(); + mass.Position = mass.PrevPosition = GetPosition(); + mass.Unconstrained = false; + if (splineTransform.LocalToWorld(keyframes.First().Value.Translation) != mass.Position) + splineDirty = true; + } + for (int32 i = 1; i < keyframesCount; i++) + { + auto& mass = _masses[i]; + mass.Unconstrained = true; + mass.Position = splineTransform.LocalToWorld(keyframes[i].Value.Translation); + } + if (AttachEnd) + { + // Rope end + auto& mass = _masses.Last(); + mass.Position = mass.PrevPosition = AttachEnd->GetPosition(); + mass.Unconstrained = false; + if (splineTransform.LocalToWorld(keyframes.Last().Value.Translation) != mass.Position) + splineDirty = true; + } + + // Perform simulation in substeps to have better stability + _time += Time::Update.DeltaTime.GetTotalSeconds(); + while (_time > substepTime) + { + // Verlet integration + // [Reference: https://en.wikipedia.org/wiki/Verlet_integration] + const Vector3 force = gravity + AdditionalForce; + for (int32 i = 0; i < keyframesCount; i++) + { + auto& mass = _masses[i]; + if (mass.Unconstrained) + { + const Vector3 velocity = mass.Position - mass.PrevPosition; + mass.PrevPosition = mass.Position; + mass.Position = mass.Position + velocity + (substepTimeSqr * force); + keyframes[i].Value.Translation = splineTransform.WorldToLocal(mass.Position); + } + } + + // Distance constraint + for (int32 i = 1; i < keyframesCount; i++) + { + auto& massA = _masses[i - 1]; + auto& massB = _masses[i]; + Vector3 offset = massB.Position - massA.Position; + const float distance = offset.Length(); + const float scale = (distance - massB.SegmentLength) / Math::Max(distance, ZeroTolerance); + if (massA.Unconstrained && massB.Unconstrained) + { + offset *= scale * 0.5f; + massA.Position += offset; + massB.Position -= offset; + } + else if (massA.Unconstrained) + { + massA.Position += scale * offset; + } + else if (massB.Unconstrained) + { + massB.Position -= scale * offset; + } + } + + // Stiffness constraint + if (EnableStiffness) + { + for (int32 i = 2; i < keyframesCount; i++) + { + auto& massA = _masses[i - 2]; + auto& massB = _masses[i]; + Vector3 offset = massB.Position - massA.Position; + const float distance = offset.Length(); + const float scale = (distance - massB.SegmentLength * 2.0f) / Math::Max(distance, ZeroTolerance); + if (massA.Unconstrained && massB.Unconstrained) + { + offset *= scale * 0.5f; + massA.Position += offset; + massB.Position -= offset; + } + else if (massA.Unconstrained) + { + massA.Position += scale * offset; + } + else if (massB.Unconstrained) + { + massB.Position -= scale * offset; + } + } + } + + _time -= substepTime; + splineDirty = true; + } + + // Update spline and relevant components (eg. spline model) + if (splineDirty) + { + for (int32 i = 0; i < keyframesCount; i++) + keyframes[i].Value.Translation = splineTransform.WorldToLocal(_masses[i].Position); + + _spline->UpdateSpline(); + } +} + +void SplineRopeBody::OnEnable() +{ + GetScene()->Ticking.FixedUpdate.AddTick(this); + + Actor::OnEnable(); +} + +void SplineRopeBody::OnDisable() +{ + Actor::OnDisable(); + + GetScene()->Ticking.FixedUpdate.RemoveTick(this); +} + +void SplineRopeBody::OnParentChanged() +{ + Actor::OnParentChanged(); + + _spline = Cast(_parent); +} + +void SplineRopeBody::OnTransformChanged() +{ + Actor::OnTransformChanged(); + + _box = BoundingBox(_transform.Translation, _transform.Translation); + _sphere = BoundingSphere(_transform.Translation, 0.0f); +} diff --git a/Source/Engine/Physics/Actors/SplineRopeBody.h b/Source/Engine/Physics/Actors/SplineRopeBody.h new file mode 100644 index 000000000..32e175ba7 --- /dev/null +++ b/Source/Engine/Physics/Actors/SplineRopeBody.h @@ -0,0 +1,75 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Engine/Level/Actor.h" +#include "Engine/Scripting/ScriptingObjectReference.h" + +class Spline; + +/// +/// Physical simulation actor for ropes, chains and cables represented by a spline. +/// +/// +API_CLASS() class FLAXENGINE_API SplineRopeBody : public Actor +{ +API_AUTO_SERIALIZATION(); +DECLARE_SCENE_OBJECT(SplineRopeBody); +private: + + struct Mass + { + Vector3 Position; + float SegmentLength; + Vector3 PrevPosition; + bool Unconstrained; + }; + + Spline* _spline = nullptr; + float _time = 0.0f; + Array _masses; + +public: + + /// + /// The target actor too attach the rope end to. If unset the rope end will run freely. + /// + API_FIELD(Attributes="EditorOrder(0), DefaultValue(null), EditorDisplay(\"Rope\")") + ScriptingObjectReference AttachEnd; + + /// + /// The world gravity scale applied to the rope. Can be used to adjust gravity force or disable it. + /// + API_FIELD(Attributes="EditorOrder(10), EditorDisplay(\"Rope\")") + float GravityScale = 1.0f; + + /// + /// The additional, external force applied to rope (world-space). This can be eg. wind force. + /// + API_FIELD(Attributes="EditorOrder(20), EditorDisplay(\"Rope\")") + Vector3 AdditionalForce = Vector3::Zero; + + /// + /// If checked, the physics solver will use stiffness constraint for rope. It will be less likely to bend over and will preserve more it's shape. + /// + API_FIELD(Attributes="EditorOrder(30), EditorDisplay(\"Rope\")") + bool EnableStiffness = false; + + /// + /// The rope simulation update substep (in seconds). Defines the frequency of physics update. + /// + API_FIELD(Attributes="EditorOrder(40), Limit(0, 0.1f, 0.0001f), EditorDisplay(\"Rope\")") + float SubstepTime = 0.02f; + +private: + + void Tick(); + +public: + + // [Actor] + void OnEnable() override; + void OnDisable() override; + void OnTransformChanged() override; + void OnParentChanged() override; +}; diff --git a/Source/Engine/Physics/Colliders/BoxCollider.cpp b/Source/Engine/Physics/Colliders/BoxCollider.cpp index da78e6a2f..487059148 100644 --- a/Source/Engine/Physics/Colliders/BoxCollider.cpp +++ b/Source/Engine/Physics/Colliders/BoxCollider.cpp @@ -4,9 +4,6 @@ #include "Engine/Serialization/Serialization.h" #include "Engine/Physics/Utilities.h" #include -#if USE_EDITOR -#include "Engine/Level/Scene/SceneRendering.h" -#endif BoxCollider::BoxCollider(const SpawnParams& params) : Collider(params) @@ -123,26 +120,6 @@ void BoxCollider::Deserialize(DeserializeStream& stream, ISerializeModifier* mod DESERIALIZE_MEMBER(Size, _size); } -#if USE_EDITOR - -void BoxCollider::OnEnable() -{ - GetSceneRendering()->AddPhysicsDebug(this); - - // Base - Collider::OnEnable(); -} - -void BoxCollider::OnDisable() -{ - GetSceneRendering()->RemovePhysicsDebug(this); - - // Base - Collider::OnDisable(); -} - -#endif - void BoxCollider::UpdateBounds() { // Cache bounds @@ -152,32 +129,11 @@ void BoxCollider::UpdateBounds() BoundingSphere::FromBox(_box, _sphere); } -void BoxCollider::CreateShape() +void BoxCollider::GetGeometry(PxGeometryHolder& geometry) { - // Setup shape geometry - _cachedScale = GetScale(); Vector3 size = _size * _cachedScale; size.Absolute(); const float minSize = 0.001f; - const PxBoxGeometry geometry(Math::Max(size.X * 0.5f, minSize), Math::Max(size.Y * 0.5f, minSize), Math::Max(size.Z * 0.5f, minSize)); - - // Setup shape - CreateShapeBase(geometry); -} - -void BoxCollider::UpdateGeometry() -{ - // Check if has no shape created - if (_shape == nullptr) - return; - - // Setup shape geometry - _cachedScale = GetScale(); - Vector3 size = _size * _cachedScale; - size.Absolute(); - const float minSize = 0.001f; - const PxBoxGeometry geometry(Math::Max(size.X * 0.5f, minSize), Math::Max(size.Y * 0.5f, minSize), Math::Max(size.Z * 0.5f, minSize)); - - // Setup shape - _shape->setGeometry(geometry); + const PxBoxGeometry box(Math::Max(size.X * 0.5f, minSize), Math::Max(size.Y * 0.5f, minSize), Math::Max(size.Z * 0.5f, minSize)); + geometry.storeAny(box); } diff --git a/Source/Engine/Physics/Colliders/BoxCollider.h b/Source/Engine/Physics/Colliders/BoxCollider.h index ac381e878..89850b101 100644 --- a/Source/Engine/Physics/Colliders/BoxCollider.h +++ b/Source/Engine/Physics/Colliders/BoxCollider.h @@ -47,12 +47,6 @@ public: return _bounds; } -private: - -#if USE_EDITOR - void DrawPhysicsDebug(RenderView& view); -#endif - public: // [Collider] @@ -67,11 +61,9 @@ public: protected: // [Collider] -#if USE_EDITOR - void OnEnable() override; - void OnDisable() override; -#endif void UpdateBounds() override; - void CreateShape() override; - void UpdateGeometry() override; + void GetGeometry(PxGeometryHolder& geometry) override; +#if USE_EDITOR + void DrawPhysicsDebug(RenderView& view) override; +#endif }; diff --git a/Source/Engine/Physics/Colliders/CapsuleCollider.cpp b/Source/Engine/Physics/Colliders/CapsuleCollider.cpp index e3e717839..d3bc4dc30 100644 --- a/Source/Engine/Physics/Colliders/CapsuleCollider.cpp +++ b/Source/Engine/Physics/Colliders/CapsuleCollider.cpp @@ -4,9 +4,6 @@ #include "Engine/Serialization/Serialization.h" #include "Engine/Physics/Utilities.h" #include -#if USE_EDITOR -#include "Engine/Level/Scene/SceneRendering.h" -#endif CapsuleCollider::CapsuleCollider(const SpawnParams& params) : Collider(params) @@ -93,26 +90,6 @@ void CapsuleCollider::Deserialize(DeserializeStream& stream, ISerializeModifier* DESERIALIZE_MEMBER(Height, _height); } -#if USE_EDITOR - -void CapsuleCollider::OnEnable() -{ - GetSceneRendering()->AddPhysicsDebug(this); - - // Base - Collider::OnEnable(); -} - -void CapsuleCollider::OnDisable() -{ - GetSceneRendering()->RemovePhysicsDebug(this); - - // Base - Collider::OnDisable(); -} - -#endif - void CapsuleCollider::UpdateBounds() { // Cache bounds @@ -123,34 +100,12 @@ void CapsuleCollider::UpdateBounds() BoundingSphere::FromBox(_box, _sphere); } -void CapsuleCollider::CreateShape() +void CapsuleCollider::GetGeometry(PxGeometryHolder& geometry) { - // Setup shape geometry - _cachedScale = GetScale(); const float scaling = _cachedScale.GetAbsolute().MaxValue(); const float minSize = 0.001f; const float radius = Math::Max(Math::Abs(_radius) * scaling, minSize); const float height = Math::Max(Math::Abs(_height) * scaling, minSize); - const PxCapsuleGeometry geometry(radius, height * 0.5f); - - // Setup shape - CreateShapeBase(geometry); -} - -void CapsuleCollider::UpdateGeometry() -{ - // Check if has no shape created - if (_shape == nullptr) - return; - - // Setup shape geometry - _cachedScale = GetScale(); - const float scaling = _cachedScale.GetAbsolute().MaxValue(); - const float minSize = 0.001f; - const float radius = Math::Max(Math::Abs(_radius) * scaling, minSize); - const float height = Math::Max(Math::Abs(_height) * scaling, minSize); - const PxCapsuleGeometry geometry(radius, height * 0.5f); - - // Setup shape - _shape->setGeometry(geometry); + const PxCapsuleGeometry capsule(radius, height * 0.5f); + geometry.storeAny(capsule); } diff --git a/Source/Engine/Physics/Colliders/CapsuleCollider.h b/Source/Engine/Physics/Colliders/CapsuleCollider.h index 5efe18c13..4a66ca877 100644 --- a/Source/Engine/Physics/Colliders/CapsuleCollider.h +++ b/Source/Engine/Physics/Colliders/CapsuleCollider.h @@ -63,12 +63,6 @@ public: /// API_PROPERTY() void SetHeight(float value); -private: - -#if USE_EDITOR - void DrawPhysicsDebug(RenderView& view); -#endif - public: // [Collider] @@ -82,11 +76,9 @@ public: protected: // [Collider] -#if USE_EDITOR - void OnEnable() override; - void OnDisable() override; -#endif void UpdateBounds() override; - void CreateShape() override; - void UpdateGeometry() override; + void GetGeometry(PxGeometryHolder& geometry) override; +#if USE_EDITOR + void DrawPhysicsDebug(RenderView& view) override; +#endif }; diff --git a/Source/Engine/Physics/Colliders/CharacterController.cpp b/Source/Engine/Physics/Colliders/CharacterController.cpp index 79410bdf4..7657c7594 100644 --- a/Source/Engine/Physics/Colliders/CharacterController.cpp +++ b/Source/Engine/Physics/Colliders/CharacterController.cpp @@ -7,9 +7,6 @@ #include "Engine/Serialization/Serialization.h" #include "Engine/Engine/Time.h" #include "Engine/Physics/PhysicalMaterial.h" -#if USE_EDITOR -#include "Engine/Level/Scene/SceneRendering.h" -#endif #include #include #include @@ -276,6 +273,11 @@ void CharacterController::UpdateGeometry() UpdateSize(); } +void CharacterController::GetGeometry(PxGeometryHolder& geometry) +{ + // Unused +} + void CharacterController::UpdateLayerBits() { // Base @@ -309,26 +311,6 @@ void CharacterController::EndPlay() _shape = nullptr; } -#if USE_EDITOR - -void CharacterController::OnEnable() -{ - GetSceneRendering()->AddPhysicsDebug(this); - - // Base - Collider::OnEnable(); -} - -void CharacterController::OnDisable() -{ - GetSceneRendering()->RemovePhysicsDebug(this); - - // Base - Collider::OnDisable(); -} - -#endif - void CharacterController::OnActiveInTreeChanged() { // Skip collider base diff --git a/Source/Engine/Physics/Colliders/CharacterController.h b/Source/Engine/Physics/Colliders/CharacterController.h index 3959e8ad8..f850b9124 100644 --- a/Source/Engine/Physics/Colliders/CharacterController.h +++ b/Source/Engine/Physics/Colliders/CharacterController.h @@ -142,7 +142,7 @@ public: API_PROPERTY() void SetStepOffset(float value); /// - /// Gets the minimum move distance of the character controller. The minimum travelled distance to consider. If travelled distance is smaller, the character doesn't move. This is used to stop the recursive motion algorithm when remaining distance to travel is small. + /// Gets the minimum move distance of the character controller. The minimum traveled distance to consider. If traveled distance is smaller, the character doesn't move. This is used to stop the recursive motion algorithm when remaining distance to travel is small. /// API_PROPERTY(Attributes="EditorOrder(230), DefaultValue(0.0f), Limit(0, 1000), EditorDisplay(\"Character Controller\")") FORCE_INLINE float GetMinMoveDistance() const @@ -151,7 +151,7 @@ public: } /// - /// Sets the minimum move distance of the character controller.The minimum travelled distance to consider. If travelled distance is smaller, the character doesn't move. This is used to stop the recursive motion algorithm when remaining distance to travel is small. + /// Sets the minimum move distance of the character controller.The minimum traveled distance to consider. If traveled distance is smaller, the character doesn't move. This is used to stop the recursive motion algorithm when remaining distance to travel is small. /// API_PROPERTY() void SetMinMoveDistance(float value); @@ -212,12 +212,6 @@ protected: /// void UpdateSize() const; -private: - -#if USE_EDITOR - void DrawPhysicsDebug(RenderView& view); -#endif - public: // [Collider] @@ -240,12 +234,12 @@ protected: // [PhysicsActor] void UpdateGeometry() override; + void GetGeometry(PxGeometryHolder& geometry) override; void UpdateLayerBits() override; void BeginPlay(SceneBeginData* data) override; void EndPlay() override; #if USE_EDITOR - void OnEnable() override; - void OnDisable() override; + void DrawPhysicsDebug(RenderView& view) override; #endif void OnActiveInTreeChanged() override; void OnParentChanged() override; diff --git a/Source/Engine/Physics/Colliders/Collider.cpp b/Source/Engine/Physics/Colliders/Collider.cpp index e73963c7c..a1a84a523 100644 --- a/Source/Engine/Physics/Colliders/Collider.cpp +++ b/Source/Engine/Physics/Colliders/Collider.cpp @@ -2,6 +2,9 @@ #include "Collider.h" #include "Engine/Core/Log.h" +#if USE_EDITOR +#include "Engine/Level/Scene/SceneRendering.h" +#endif #include "Engine/Serialization/Serialization.h" #include "Engine/Physics/Utilities.h" #include "Engine/Physics/PhysicsSettings.h" @@ -182,6 +185,16 @@ bool Collider::IsAttached() const return _shape && _shape->getActor() != nullptr; } +bool Collider::CanAttach(RigidBody* rigidBody) const +{ + return true; +} + +bool Collider::CanBeTrigger() const +{ + return true; +} + RigidBody* Collider::GetAttachedRigidBody() const { if (_shape && _staticActor == nullptr) @@ -193,6 +206,26 @@ RigidBody* Collider::GetAttachedRigidBody() const return nullptr; } +#if USE_EDITOR + +void Collider::OnEnable() +{ + GetSceneRendering()->AddPhysicsDebug(this); + + // Base + Actor::OnEnable(); +} + +void Collider::OnDisable() +{ + // Base + Actor::OnDisable(); + + GetSceneRendering()->RemovePhysicsDebug(this); +} + +#endif + void Collider::Attach(RigidBody* rigidBody) { ASSERT(CanAttach(rigidBody)); @@ -232,17 +265,20 @@ void Collider::UpdateLayerBits() filterData.word0 = GetLayerMask(); // Own layer mask - filterData.word1 = PhysicsSettings::Instance()->LayerMasks[GetLayer()]; + filterData.word1 = Physics::LayerMasks[GetLayer()]; _shape->setSimulationFilterData(filterData); _shape->setQueryFilterData(filterData); } -void Collider::CreateShapeBase(const PxGeometry& geometry) +void Collider::CreateShape() { - ASSERT(_shape == nullptr); + // Setup shape geometry + _cachedScale = GetScale(); + PxGeometryHolder geometry; + GetGeometry(geometry); - // Prepare + // Create shape const bool isTrigger = _isTrigger && CanBeTrigger(); const PxShapeFlags shapeFlags = GetShapeFlags(isTrigger, IsActiveInHierarchy()); PxMaterial* material = Physics::GetDefaultMaterial(); @@ -250,17 +286,62 @@ void Collider::CreateShapeBase(const PxGeometry& geometry) { material = ((PhysicalMaterial*)Material->Instance)->GetPhysXMaterial(); } - - // Create shape - _shape = CPhysX->createShape(geometry, *material, true, shapeFlags); + ASSERT(_shape == nullptr); + _shape = CPhysX->createShape(geometry.any(), *material, true, shapeFlags); ASSERT(_shape); _shape->userData = this; - - // Setup properties _shape->setContactOffset(Math::Max(_shape->getRestOffset() + ZeroTolerance, _contactOffset)); UpdateLayerBits(); } +void Collider::UpdateGeometry() +{ + if (_shape == nullptr) + return; + + // Setup shape geometry + _cachedScale = GetScale(); + PxGeometryHolder geometry; + GetGeometry(geometry); + + // Recreate shape if geometry has different type + if (_shape->getGeometryType() != geometry.getType()) + { + // Detach from the actor + auto actor = _shape->getActor(); + if (actor) + actor->detachShape(*_shape); + + // Release shape + Physics::RemoveCollider(this); + _shape->release(); + _shape = nullptr; + + // Recreate shape + CreateShape(); + + // Reattach again (only if can, see CanAttach function) + if (actor) + { + const auto rigidBody = dynamic_cast(GetParent()); + if (_staticActor != nullptr || (rigidBody && CanAttach(rigidBody))) + { + actor->attachShape(*_shape); + } + else + { + // Be static triangle mesh + CreateStaticActor(); + } + } + + return; + } + + // Update shape + _shape->setGeometry(geometry.any()); +} + void Collider::CreateStaticActor() { ASSERT(_staticActor == nullptr); @@ -285,6 +366,14 @@ void Collider::RemoveStaticActor() _staticActor = nullptr; } +#if USE_EDITOR + +void Collider::DrawPhysicsDebug(RenderView& view) +{ +} + +#endif + void Collider::OnMaterialChanged() { // Update the shape material diff --git a/Source/Engine/Physics/Colliders/Collider.h b/Source/Engine/Physics/Colliders/Collider.h index c836a7886..3e7b5ecfd 100644 --- a/Source/Engine/Physics/Colliders/Collider.h +++ b/Source/Engine/Physics/Colliders/Collider.h @@ -160,19 +160,13 @@ public: /// /// The rigid body. /// true if this collider can be attached the specified rigid body; otherwise, false. - virtual bool CanAttach(RigidBody* rigidBody) const - { - return true; - } + virtual bool CanAttach(RigidBody* rigidBody) const; /// /// Determines whether this collider can be a trigger shape. /// /// true if this collider can be trigger; otherwise, false. - virtual bool CanBeTrigger() const - { - return true; - } + virtual bool CanBeTrigger() const; /// /// Attaches collider to the specified rigid body. @@ -198,20 +192,20 @@ protected: virtual void UpdateBounds() = 0; /// - /// Creates the collider shape. + /// Gets the collider shape geometry. /// - virtual void CreateShape() = 0; + /// The output geometry. + virtual void GetGeometry(PxGeometryHolder& geometry) = 0; /// - /// Creates the collider shape from the given geometry. + /// Creates the collider shape. /// - /// The geometry. - void CreateShapeBase(const PxGeometry& geometry); + virtual void CreateShape(); /// /// Updates the shape geometry. /// - virtual void UpdateGeometry() = 0; + virtual void UpdateGeometry(); /// /// Creates the static actor. @@ -223,6 +217,10 @@ protected: /// void RemoveStaticActor(); +#if USE_EDITOR + virtual void DrawPhysicsDebug(RenderView& view); +#endif + private: void OnMaterialChanged(); @@ -237,6 +235,10 @@ public: protected: // [PhysicsColliderActor] +#if USE_EDITOR + void OnEnable() override; + void OnDisable() override; +#endif void BeginPlay(SceneBeginData* data) override; void EndPlay() override; void OnActiveInTreeChanged() override; diff --git a/Source/Engine/Physics/Colliders/MeshCollider.cpp b/Source/Engine/Physics/Colliders/MeshCollider.cpp index 1b72d83eb..7ba76f963 100644 --- a/Source/Engine/Physics/Colliders/MeshCollider.cpp +++ b/Source/Engine/Physics/Colliders/MeshCollider.cpp @@ -5,11 +5,6 @@ #include "Engine/Serialization/Serialization.h" #include "Engine/Physics/Utilities.h" #include "Engine/Physics/Physics.h" -#include -#include -#if USE_EDITOR -#include "Engine/Level/Scene/SceneRendering.h" -#endif MeshCollider::MeshCollider(const SpawnParams& params) : Collider(params) @@ -117,26 +112,6 @@ void MeshCollider::Deserialize(DeserializeStream& stream, ISerializeModifier* mo DESERIALIZE(CollisionData); } -#if USE_EDITOR - -void MeshCollider::OnEnable() -{ - GetSceneRendering()->AddPhysicsDebug(this); - - // Base - Collider::OnEnable(); -} - -void MeshCollider::OnDisable() -{ - GetSceneRendering()->RemovePhysicsDebug(this); - - // Base - Collider::OnDisable(); -} - -#endif - void MeshCollider::UpdateBounds() { // Cache bounds @@ -149,11 +124,10 @@ void MeshCollider::UpdateBounds() BoundingSphere::FromBox(_box, _sphere); } -void MeshCollider::CreateShape() +void MeshCollider::GetGeometry(PxGeometryHolder& geometry) { // Prepare scale - Vector3 scale = GetScale(); - _cachedScale = scale; + Vector3 scale = _cachedScale; scale.Absolute(); const float minSize = 0.001f; scale = Vector3::Max(scale, minSize); @@ -165,100 +139,23 @@ void MeshCollider::CreateShape() if (type == CollisionDataType::ConvexMesh) { // Convex mesh - PxConvexMeshGeometry geometry; - geometry.scale.scale = C2P(scale); - geometry.convexMesh = CollisionData->GetConvex(); - CreateShapeBase(geometry); + PxConvexMeshGeometry convexMesh; + convexMesh.scale.scale = C2P(scale); + convexMesh.convexMesh = CollisionData->GetConvex(); + geometry.storeAny(convexMesh); } else if (type == CollisionDataType::TriangleMesh) { // Triangle mesh - PxTriangleMeshGeometry geometry; - geometry.scale.scale = C2P(scale); - geometry.triangleMesh = CollisionData->GetTriangle(); - CreateShapeBase(geometry); + PxTriangleMeshGeometry triangleMesh; + triangleMesh.scale.scale = C2P(scale); + triangleMesh.triangleMesh = CollisionData->GetTriangle(); + geometry.storeAny(triangleMesh); } else { // Dummy geometry - const PxSphereGeometry geometry(0.01f); - CreateShapeBase(geometry); - } -} - -void MeshCollider::UpdateGeometry() -{ - // Check if has no shape created - if (_shape == nullptr) - return; - - // Recreate shape if geometry has different type - CollisionDataType type = CollisionDataType::None; - if (CollisionData && CollisionData->IsLoaded()) - type = CollisionData->GetOptions().Type; - if ((type == CollisionDataType::ConvexMesh && _shape->getGeometryType() != PxGeometryType::eCONVEXMESH) - || (type == CollisionDataType::TriangleMesh && _shape->getGeometryType() != PxGeometryType::eTRIANGLEMESH) - || (type == CollisionDataType::None && _shape->getGeometryType() != PxGeometryType::eSPHERE) - ) - { - // Detach from the actor - auto actor = _shape->getActor(); - if (actor) - actor->detachShape(*_shape); - - // Release shape - Physics::RemoveCollider(this); - _shape->release(); - _shape = nullptr; - - // Recreate shape - CreateShape(); - - // Reattach again (only if can, see CanAttach function) - if (actor) - { - if (_staticActor != nullptr || type != CollisionDataType::TriangleMesh) - { - actor->attachShape(*_shape); - } - else - { - // Be static triangle mesh - CreateStaticActor(); - } - } - - return; - } - - // Prepare scale - Vector3 scale = GetScale(); - _cachedScale = scale; - scale.Absolute(); - const float minSize = 0.001f; - scale = Vector3::Max(scale, minSize); - - // Setup shape (based on type) - if (type == CollisionDataType::ConvexMesh) - { - // Convex mesh - PxConvexMeshGeometry geometry; - geometry.scale.scale = C2P(scale); - geometry.convexMesh = CollisionData->GetConvex(); - _shape->setGeometry(geometry); - } - else if (type == CollisionDataType::TriangleMesh) - { - // Triangle mesh - PxTriangleMeshGeometry geometry; - geometry.scale.scale = C2P(scale); - geometry.triangleMesh = CollisionData->GetTriangle(); - _shape->setGeometry(geometry); - } - else - { - // Dummy geometry - const PxSphereGeometry geometry(0.01f); - _shape->setGeometry(geometry); + const PxSphereGeometry sphere(minSize); + geometry.storeAny(sphere); } } diff --git a/Source/Engine/Physics/Colliders/MeshCollider.h b/Source/Engine/Physics/Colliders/MeshCollider.h index 22dbd755f..efcf60cee 100644 --- a/Source/Engine/Physics/Colliders/MeshCollider.h +++ b/Source/Engine/Physics/Colliders/MeshCollider.h @@ -25,9 +25,6 @@ private: void OnCollisionDataChanged(); void OnCollisionDataLoaded(); -#if USE_EDITOR - void DrawPhysicsDebug(RenderView& view); -#endif public: @@ -45,10 +42,8 @@ protected: // [Collider] #if USE_EDITOR - void OnEnable() override; - void OnDisable() override; + void DrawPhysicsDebug(RenderView& view) override; #endif void UpdateBounds() override; - void CreateShape() override; - void UpdateGeometry() override; + void GetGeometry(PxGeometryHolder& geometry) override; }; diff --git a/Source/Engine/Physics/Colliders/SphereCollider.cpp b/Source/Engine/Physics/Colliders/SphereCollider.cpp index ef308cf8a..b602b8f84 100644 --- a/Source/Engine/Physics/Colliders/SphereCollider.cpp +++ b/Source/Engine/Physics/Colliders/SphereCollider.cpp @@ -4,9 +4,6 @@ #include "Engine/Serialization/Serialization.h" #include "Engine/Physics/Utilities.h" #include -#if USE_EDITOR -#include "Engine/Level/Scene/SceneRendering.h" -#endif SphereCollider::SphereCollider(const SpawnParams& params) : Collider(params) @@ -67,26 +64,6 @@ void SphereCollider::Deserialize(DeserializeStream& stream, ISerializeModifier* DESERIALIZE_MEMBER(Radius, _radius); } -#if USE_EDITOR - -void SphereCollider::OnEnable() -{ - GetSceneRendering()->AddPhysicsDebug(this); - - // Base - Collider::OnEnable(); -} - -void SphereCollider::OnDisable() -{ - GetSceneRendering()->RemovePhysicsDebug(this); - - // Base - Collider::OnDisable(); -} - -#endif - void SphereCollider::UpdateBounds() { // Cache bounds @@ -95,32 +72,11 @@ void SphereCollider::UpdateBounds() _sphere.GetBoundingBox(_box); } -void SphereCollider::CreateShape() +void SphereCollider::GetGeometry(PxGeometryHolder& geometry) { - // Setup shape geometry - _cachedScale = GetScale(); const float scaling = _cachedScale.GetAbsolute().MaxValue(); const float radius = Math::Abs(_radius) * scaling; const float minSize = 0.001f; - const PxSphereGeometry geometry(Math::Max(radius, minSize)); - - // Setup shape - CreateShapeBase(geometry); -} - -void SphereCollider::UpdateGeometry() -{ - // Check if has no shape created - if (_shape == nullptr) - return; - - // Setup shape geometry - _cachedScale = GetScale(); - const float scaling = _cachedScale.GetAbsolute().MaxValue(); - const float radius = Math::Abs(_radius) * scaling; - const float minSize = 0.001f; - const PxSphereGeometry geometry(Math::Max(radius, minSize)); - - // Setup shape - _shape->setGeometry(geometry); + const PxSphereGeometry sphere(Math::Max(radius, minSize)); + geometry.storeAny(sphere); } diff --git a/Source/Engine/Physics/Colliders/SphereCollider.h b/Source/Engine/Physics/Colliders/SphereCollider.h index 0835152f0..ed2d4cd09 100644 --- a/Source/Engine/Physics/Colliders/SphereCollider.h +++ b/Source/Engine/Physics/Colliders/SphereCollider.h @@ -37,12 +37,6 @@ public: /// API_PROPERTY() void SetRadius(float value); -private: - -#if USE_EDITOR - void DrawPhysicsDebug(RenderView& view); -#endif - public: // [Collider] @@ -57,10 +51,8 @@ protected: // [Collider] #if USE_EDITOR - void OnEnable() override; - void OnDisable() override; + void DrawPhysicsDebug(RenderView& view) override; #endif void UpdateBounds() override; - void CreateShape() override; - void UpdateGeometry() override; + void GetGeometry(PxGeometryHolder& geometry) override; }; diff --git a/Source/Engine/Physics/Colliders/SplineCollider.cpp b/Source/Engine/Physics/Colliders/SplineCollider.cpp new file mode 100644 index 000000000..e44d986cc --- /dev/null +++ b/Source/Engine/Physics/Colliders/SplineCollider.cpp @@ -0,0 +1,340 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#include "SplineCollider.h" +#include "Engine/Core/Log.h" +#include "Engine/Core/Math/Matrix.h" +#include "Engine/Level/Actors/Spline.h" +#include "Engine/Serialization/Serialization.h" +#include "Engine/Physics/Utilities.h" +#include "Engine/Physics/Physics.h" +#include "Engine/Profiler/ProfilerCPU.h" +#if COMPILE_WITH_PHYSICS_COOKING +#include "Engine/Physics/CollisionCooking.h" +#include +#include +#endif +#include + +SplineCollider::SplineCollider(const SpawnParams& params) + : Collider(params) +{ + CollisionData.Changed.Bind(this); + CollisionData.Loaded.Bind(this); +} + +Transform SplineCollider::GetPreTransform() const +{ + return _preTransform; +} + +void SplineCollider::SetPreTransform(const Transform& value) +{ + if (_preTransform == value) + return; + _preTransform = value; + UpdateGeometry(); +} + +void SplineCollider::ExtractGeometry(Array& vertexBuffer, Array& indexBuffer) const +{ + vertexBuffer.Add(_vertexBuffer); + indexBuffer.Add(_indexBuffer); +} + +void SplineCollider::OnCollisionDataChanged() +{ + // This should not be called during physics simulation, if it happened use write lock on physx scene + ASSERT(!Physics::IsDuringSimulation()); + + if (CollisionData) + { + // Ensure that collision asset is loaded (otherwise objects might fall though collider that is not yet loaded on play begin) + CollisionData->WaitForLoaded(); + } + + UpdateGeometry(); +} + +void SplineCollider::OnCollisionDataLoaded() +{ + UpdateGeometry(); +} + +void SplineCollider::OnSplineUpdated() +{ + if (!_spline || !IsActiveInHierarchy() || _spline->GetSplinePointsCount() < 2 || !CollisionData || !CollisionData->IsLoaded()) + { + _box = BoundingBox(_transform.Translation, _transform.Translation); + BoundingSphere::FromBox(_box, _sphere); + return; + } + + UpdateGeometry(); +} + +bool SplineCollider::CanAttach(RigidBody* rigidBody) const +{ + return false; +} + +bool SplineCollider::CanBeTrigger() const +{ + return false; +} + +#if USE_EDITOR + +#include "Engine/Debug/DebugDraw.h" + +void SplineCollider::DrawPhysicsDebug(RenderView& view) +{ + DEBUG_DRAW_WIRE_TRIANGLES_EX(_vertexBuffer, _indexBuffer, Color::GreenYellow * 0.8f, 0, true); +} + +void SplineCollider::OnDebugDrawSelected() +{ + DEBUG_DRAW_WIRE_TRIANGLES_EX(_vertexBuffer, _indexBuffer, Color::GreenYellow, 0, false); + + // Base + Collider::OnDebugDrawSelected(); +} + +#endif + +bool SplineCollider::IntersectsItself(const Ray& ray, float& distance, Vector3& normal) +{ + // Use detailed hit + if (_shape) + { + RayCastHit hitInfo; + if (!RayCast(ray.Position, ray.Direction, hitInfo)) + return false; + distance = hitInfo.Distance; + normal = hitInfo.Normal; + return true; + } + + // Fallback to AABB + return _box.Intersects(ray, distance, normal); +} + +void SplineCollider::Serialize(SerializeStream& stream, const void* otherObj) +{ + // Base + Collider::Serialize(stream, otherObj); + + SERIALIZE_GET_OTHER_OBJ(SplineCollider); + + SERIALIZE(CollisionData); + SERIALIZE_MEMBER(PreTransform, _preTransform) +} + +void SplineCollider::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) +{ + // Base + Collider::Deserialize(stream, modifier); + + DESERIALIZE(CollisionData); + DESERIALIZE_MEMBER(PreTransform, _preTransform); +} + +void SplineCollider::OnParentChanged() +{ + if (_spline) + { + _spline->SplineUpdated.Unbind(this); + } + + // Base + Collider::OnParentChanged(); + + _spline = Cast(_parent); + if (_spline) + { + _spline->SplineUpdated.Bind(this); + } + + OnSplineUpdated(); +} + +void SplineCollider::EndPlay() +{ + // Base + Collider::EndPlay(); + + // Cleanup + if (_triangleMesh) + { + Physics::RemoveObject(_triangleMesh); + _triangleMesh = nullptr; + } +} + +void SplineCollider::UpdateBounds() +{ + // Unused as bounds are updated during collision building +} + +void SplineCollider::GetGeometry(PxGeometryHolder& geometry) +{ + // Reset bounds + _box = BoundingBox(_transform.Translation, _transform.Translation); + BoundingSphere::FromBox(_box, _sphere); + + // Skip if sth is missing + if (!_spline || !IsActiveInHierarchy() || _spline->GetSplinePointsCount() < 2 || !CollisionData || !CollisionData->IsLoaded()) + { + geometry.storeAny(PxSphereGeometry(0.001f)); + return; + } + PROFILE_CPU(); + + // Extract collision geometry + // TODO: cache memory allocation for dynamic colliders + Array collisionVertices; + Array collisionIndices; + CollisionData->ExtractGeometry(collisionVertices, collisionIndices); + if (collisionIndices.IsEmpty()) + { + geometry.storeAny(PxSphereGeometry(0.001f)); + return; + } + + // Apply local mesh transformation + if (!_preTransform.IsIdentity()) + { + for (int32 i = 0; i < collisionVertices.Count(); i++) + collisionVertices[i] = _preTransform.LocalToWorld(collisionVertices[i]); + } + + // Find collision geometry local bounds + BoundingBox localModelBounds; + localModelBounds.Minimum = localModelBounds.Maximum = collisionVertices[0]; + for (int32 i = 1; i < collisionVertices.Count(); i++) + { + Vector3 v = collisionVertices[i]; + localModelBounds.Minimum = Vector3::Min(localModelBounds.Minimum, v); + localModelBounds.Maximum = Vector3::Max(localModelBounds.Maximum, v); + } + auto localModelBoundsSize = localModelBounds.GetSize(); + + // Deform geometry over the spline + const auto& keyframes = _spline->Curve.GetKeyframes(); + const int32 segments = keyframes.Count() - 1; + _vertexBuffer.Resize(collisionVertices.Count() * segments); + _indexBuffer.Resize(collisionIndices.Count() * segments); + const Transform splineTransform = _spline->GetTransform(); + const Transform colliderTransform = GetTransform(); + Transform curveTransform, leftTangent, rightTangent; + for (int32 segment = 0; segment < segments; segment++) + { + // Setup for the spline segment + auto offsetVertices = segment * collisionVertices.Count(); + auto offsetIndices = segment * collisionIndices.Count(); + const auto& start = keyframes[segment]; + const auto& end = keyframes[segment + 1]; + const float length = end.Time - start.Time; + AnimationUtils::GetTangent(start.Value, start.TangentOut, length, leftTangent); + AnimationUtils::GetTangent(end.Value, end.TangentIn, length, rightTangent); + + // Vertex buffer is deformed along the spline + auto srcVertices = collisionVertices.Get(); + auto dstVertices = _vertexBuffer.Get() + offsetVertices; + for (int32 i = 0; i < collisionVertices.Count(); i++) + { + Vector3 v = srcVertices[i]; + const float alpha = Math::Saturate((v.Z - localModelBounds.Minimum.Z) / localModelBoundsSize.Z); + v.Z = alpha; + + // Evaluate transformation at the curve + AnimationUtils::Bezier(start.Value, leftTangent, rightTangent, end.Value, alpha, curveTransform); + + // Apply spline direction (from position 1st derivative) + Vector3 direction; + AnimationUtils::BezierFirstDerivative(start.Value.Translation, leftTangent.Translation, rightTangent.Translation, end.Value.Translation, alpha, direction); + direction.Normalize(); + Quaternion orientation; + if (direction.IsZero()) + orientation = Quaternion::Identity; + else if (Vector3::Dot(direction, Vector3::Up) >= 0.999f) + Quaternion::RotationAxis(Vector3::Left, PI_HALF, orientation); + else + Quaternion::LookRotation(direction, Vector3::Cross(Vector3::Cross(direction, Vector3::Up), direction), orientation); + curveTransform.Orientation = orientation * curveTransform.Orientation; + + // Transform vertex + v = curveTransform.LocalToWorld(v); + v = splineTransform.LocalToWorld(v); + v = colliderTransform.WorldToLocal(v); + + dstVertices[i] = v; + } + + // Index buffer is the same for every segment except it's shifted + auto srcIndices = collisionIndices.Get(); + auto dstIndices = _indexBuffer.Get() + offsetIndices; + for (int32 i = 0; i < collisionIndices.Count(); i++) + dstIndices[i] = srcIndices[i] + offsetVertices; + } + + // Prepare scale + Vector3 scale = _cachedScale; + scale.Absolute(); + const float minSize = 0.001f; + scale = Vector3::Max(scale, minSize); + + // TODO: add support for cooking collision for static splines in editor and reusing it in game + +#if COMPILE_WITH_PHYSICS_COOKING + // Cook triangle mesh collision + CollisionCooking::CookingInput cookingInput; + cookingInput.VertexCount = _vertexBuffer.Count(); + cookingInput.VertexData = _vertexBuffer.Get(); + cookingInput.IndexCount = _indexBuffer.Count(); + cookingInput.IndexData = _indexBuffer.Get(); + cookingInput.Is16bitIndexData = false; + BytesContainer collisionData; + if (!CollisionCooking::CookTriangleMesh(cookingInput, collisionData)) + { + // Create triangle mesh + if (_triangleMesh) + { + Physics::RemoveObject(_triangleMesh); + _triangleMesh = nullptr; + } + PxDefaultMemoryInputData input(collisionData.Get(), collisionData.Length()); + // TODO: try using getVerticesForModification for dynamic triangle mesh vertices updating when changing curve in the editor + _triangleMesh = Physics::GetPhysics()->createTriangleMesh(input); + if (!_triangleMesh) + { + LOG(Error, "Failed to create triangle mesh from collision data of {0}.", ToString()); + geometry.storeAny(PxSphereGeometry(0.001f)); + return; + } + + // Transform vertices back to world space for debug shapes drawing and navmesh building + for (int32 i = 0; i < _vertexBuffer.Count(); i++) + _vertexBuffer[i] = colliderTransform.LocalToWorld(_vertexBuffer[i]); + + // Update bounds + _box = P2C(_triangleMesh->getLocalBounds()); + Matrix splineWorld; + colliderTransform.GetWorld(splineWorld); + BoundingBox::Transform(_box, splineWorld, _box); + BoundingSphere::FromBox(_box, _sphere); + + // Setup geometry + PxTriangleMeshGeometry triangleMesh; + triangleMesh.scale.scale = C2P(scale); + triangleMesh.triangleMesh = _triangleMesh; + geometry.storeAny(triangleMesh); + + // TODO: find a way of releasing _vertexBuffer and _indexBuffer for static colliders (note: ExtractGeometry usage for navmesh generation at runtime) + + return; + } +#endif + + LOG(Error, "Cannot build collision data for {0} due to runtime collision cooking diabled.", ToString()); + geometry.storeAny(PxSphereGeometry(0.001f)); +} diff --git a/Source/Engine/Physics/Colliders/SplineCollider.h b/Source/Engine/Physics/Colliders/SplineCollider.h new file mode 100644 index 000000000..c1cdab7b7 --- /dev/null +++ b/Source/Engine/Physics/Colliders/SplineCollider.h @@ -0,0 +1,80 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Collider.h" +#include "Engine/Content/AssetReference.h" +#include "Engine/Physics/CollisionData.h" + +class Spline; + +/// +/// A collider represented by an arbitrary mesh that goes over the spline. +/// +/// +/// +API_CLASS() class FLAXENGINE_API SplineCollider : public Collider +{ +DECLARE_SCENE_OBJECT(SplineCollider); +private: + Spline* _spline = nullptr; + PxTriangleMesh* _triangleMesh = nullptr; + Array _vertexBuffer; + Array _indexBuffer; + Transform _preTransform = Transform::Identity; + +public: + + /// + /// Linked collision data asset that contains convex mesh or triangle mesh used to represent a spline collider shape. + /// + API_FIELD(Attributes="EditorOrder(100), DefaultValue(null), EditorDisplay(\"Collider\")") + AssetReference CollisionData; + + /// + /// Gets the transformation applied to the collision data model geometry before placing it over the spline. Can be used to change the way model goes over the spline. + /// + API_PROPERTY(Attributes="EditorOrder(101), EditorDisplay(\"Collider\")") + Transform GetPreTransform() const; + + /// + /// Sets the transformation applied to the collision data model geometry before placing it over the spline. Can be used to change the way model goes over the spline. + /// + API_PROPERTY() void SetPreTransform(const Transform& value); + + /// + /// Extracts the collision data geometry into list of triangles. + /// + /// The output vertex buffer. + /// The output index buffer. + void ExtractGeometry(Array& vertexBuffer, Array& indexBuffer) const; + +private: + + void OnCollisionDataChanged(); + void OnCollisionDataLoaded(); + void OnSplineUpdated(); + +public: + + // [Collider] + bool CanAttach(RigidBody* rigidBody) const override; + bool CanBeTrigger() const override; +#if USE_EDITOR + void OnDebugDrawSelected() override; +#endif + bool IntersectsItself(const Ray& ray, float& distance, Vector3& normal) override; + void Serialize(SerializeStream& stream, const void* otherObj) override; + void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; + void OnParentChanged() override; + void EndPlay() override; + +protected: + + // [Collider] +#if USE_EDITOR + void DrawPhysicsDebug(RenderView& view) override; +#endif + void UpdateBounds() override; + void GetGeometry(PxGeometryHolder& geometry) override; +}; diff --git a/Source/Engine/Physics/CollisionCooking.cpp b/Source/Engine/Physics/CollisionCooking.cpp index 070f49936..9076d979e 100644 --- a/Source/Engine/Physics/CollisionCooking.cpp +++ b/Source/Engine/Physics/CollisionCooking.cpp @@ -10,10 +10,12 @@ #include #include +#define CONVEX_VERTEX_MIN 8 +#define CONVEX_VERTEX_MAX 255 #define ENSURE_CAN_COOK \ if (Physics::GetCooking() == nullptr) \ { \ - LOG(Warning, "Physics collisions cooking is disabled at runtime. Enable Physics Settings option SupportCookingAtRuntime to use terrain generation at runtime."); \ + LOG(Warning, "Physics collisions cooking is disabled at runtime. Enable Physics Settings option SupportCookingAtRuntime to use collision generation at runtime."); \ return true; \ } @@ -27,7 +29,10 @@ bool CollisionCooking::CookConvexMesh(CookingInput& input, BytesContainer& outpu desc.points.stride = sizeof(Vector3); desc.points.data = input.VertexData; desc.flags = PxConvexFlag::eCOMPUTE_CONVEX; - desc.vertexLimit = input.ConvexVertexLimit; + if (input.ConvexVertexLimit == 0) + desc.vertexLimit = CONVEX_VERTEX_MAX; + else + desc.vertexLimit = (PxU16)Math::Clamp(input.ConvexVertexLimit, CONVEX_VERTEX_MIN, CONVEX_VERTEX_MAX); if (input.ConvexFlags & ConvexMeshGenerationFlags::SkipValidation) desc.flags |= PxConvexFlag::Enum::eDISABLE_MESH_VALIDATION; if (input.ConvexFlags & ConvexMeshGenerationFlags::UsePlaneShifting) @@ -83,9 +88,9 @@ bool CollisionCooking::CookTriangleMesh(CookingInput& input, BytesContainer& out bool CollisionCooking::CookCollision(const Argument& arg, CollisionData::SerializedOptions& outputOptions, BytesContainer& outputData) { - int32 convexVertexLimit = Math::Clamp(arg.ConvexVertexLimit, 8, 255); + int32 convexVertexLimit = Math::Clamp(arg.ConvexVertexLimit, CONVEX_VERTEX_MIN, CONVEX_VERTEX_MAX); if (arg.ConvexVertexLimit == 0) - convexVertexLimit = 255; + convexVertexLimit = CONVEX_VERTEX_MAX; DataContainer finalVertexData; DataContainer finalIndexData; @@ -106,19 +111,19 @@ bool CollisionCooking::CookCollision(const Argument& arg, CollisionData::Seriali auto lod = &arg.OverrideModelData->LODs[lodIndex]; const int32 meshesCount = lod->Meshes.Count(); - // Count vertex/index buffer sizes - int32 vCount = 0; - int32 iCount = 0; - for (int32 i = 0; i < meshesCount; i++) - { - const auto mesh = lod->Meshes[i]; - vCount += mesh->Positions.Count(); + // Count vertex/index buffer sizes + int32 vCount = 0; + int32 iCount = 0; + for (int32 i = 0; i < meshesCount; i++) + { + const auto mesh = lod->Meshes[i]; + vCount += mesh->Positions.Count(); - if (needIndexBuffer) - { - iCount += mesh->Indices.Count() * 3; - } + if (needIndexBuffer) + { + iCount += mesh->Indices.Count() * 3; } + } if (meshesCount == 1) { diff --git a/Source/Engine/Physics/CollisionCooking.h b/Source/Engine/Physics/CollisionCooking.h index b368af547..658f88428 100644 --- a/Source/Engine/Physics/CollisionCooking.h +++ b/Source/Engine/Physics/CollisionCooking.h @@ -24,13 +24,13 @@ public: struct CookingInput { - int32 VertexCount; - Vector3* VertexData; - int32 IndexCount; - void* IndexData; - bool Is16bitIndexData; - ConvexMeshGenerationFlags ConvexFlags; - int32 ConvexVertexLimit; + int32 VertexCount = 0; + Vector3* VertexData = nullptr; + int32 IndexCount = 0; + void* IndexData = nullptr; + bool Is16bitIndexData = false; + ConvexMeshGenerationFlags ConvexFlags = ConvexMeshGenerationFlags::None; + int32 ConvexVertexLimit = 255; }; /// @@ -38,21 +38,12 @@ public: /// struct Argument { - CollisionDataType Type; - ModelData* OverrideModelData; + CollisionDataType Type = CollisionDataType::None; + ModelData* OverrideModelData = nullptr; AssetReference Model; - int32 ModelLodIndex; - ConvexMeshGenerationFlags ConvexFlags; - int32 ConvexVertexLimit; - - Argument() - { - Type = CollisionDataType::None; - OverrideModelData = nullptr; - ModelLodIndex = 0; - ConvexFlags = ConvexMeshGenerationFlags::None; - ConvexVertexLimit = 255; - } + int32 ModelLodIndex = 0; + ConvexMeshGenerationFlags ConvexFlags = ConvexMeshGenerationFlags::None; + int32 ConvexVertexLimit = 255; }; /// diff --git a/Source/Engine/Physics/Joints/Joint.cpp b/Source/Engine/Physics/Joints/Joint.cpp index cda249d71..56c5c1b32 100644 --- a/Source/Engine/Physics/Joints/Joint.cpp +++ b/Source/Engine/Physics/Joints/Joint.cpp @@ -295,6 +295,9 @@ void Joint::OnParentChanged() // Base Actor::OnParentChanged(); + if (!IsDuringPlay()) + return; + // Check reparenting Joint case const auto parent = dynamic_cast(GetParent()); if (parent == nullptr) diff --git a/Source/Engine/Physics/PhysicalMaterial.cpp b/Source/Engine/Physics/PhysicalMaterial.cpp deleted file mode 100644 index bd1ecd7e0..000000000 --- a/Source/Engine/Physics/PhysicalMaterial.cpp +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. - -#include "PhysicalMaterial.h" -#include "PhysicsSettings.h" -#include "Engine/Serialization/JsonTools.h" -#include "Physics.h" -#include -#include - -PhysicalMaterial::PhysicalMaterial() - : _material(nullptr) -{ -} - -PhysicalMaterial::~PhysicalMaterial() -{ - if (_material) - { - Physics::RemoveMaterial(_material); - } -} - -PxMaterial* PhysicalMaterial::GetPhysXMaterial() -{ - if (_material == nullptr && CPhysX) - { - _material = CPhysX->createMaterial(Friction, Friction, Restitution); - _material->userData = this; - - const PhysicsCombineMode useFrictionCombineMode = (OverrideFrictionCombineMode ? FrictionCombineMode : PhysicsSettings::Instance()->FrictionCombineMode); - _material->setFrictionCombineMode(static_cast(useFrictionCombineMode)); - - const PhysicsCombineMode useRestitutionCombineMode = (OverrideRestitutionCombineMode ? RestitutionCombineMode : PhysicsSettings::Instance()->RestitutionCombineMode); - _material->setRestitutionCombineMode(static_cast(useRestitutionCombineMode)); - } - - return _material; -} - -void PhysicalMaterial::UpdatePhysXMaterial() -{ - if (_material != nullptr) - { - _material->setStaticFriction(Friction); - _material->setDynamicFriction(Friction); - const PhysicsCombineMode useFrictionCombineMode = (OverrideFrictionCombineMode ? FrictionCombineMode : PhysicsSettings::Instance()->FrictionCombineMode); - _material->setFrictionCombineMode(static_cast(useFrictionCombineMode)); - - _material->setRestitution(Restitution); - const PhysicsCombineMode useRestitutionCombineMode = (OverrideRestitutionCombineMode ? RestitutionCombineMode : PhysicsSettings::Instance()->RestitutionCombineMode); - _material->setRestitutionCombineMode(static_cast(useRestitutionCombineMode)); - } -} - -void PhysicalMaterial::Serialize(SerializeStream& stream, const void* otherObj) -{ - MISSING_CODE("PhysicalMaterial::Serialize is not implemented"); -} - -void PhysicalMaterial::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) -{ - Friction = JsonTools::GetFloat(stream, "Friction", PhysicalMaterial_Friction); - FrictionCombineMode = JsonTools::GetEnum(stream, "FrictionCombineMode", PhysicalMaterial_FrictionCombineMode); - OverrideFrictionCombineMode = JsonTools::GetBool(stream, "OverrideFrictionCombineMode", PhysicalMaterial_OverrideFrictionCombineMode); - Restitution = JsonTools::GetFloat(stream, "Restitution", PhysicalMaterial_Restitution); - RestitutionCombineMode = JsonTools::GetEnum(stream, "RestitutionCombineMode", PhysicalMaterial_RestitutionCombineMode); - OverrideRestitutionCombineMode = JsonTools::GetBool(stream, "OverrideRestitutionCombineMode", PhysicalMaterial_OverrideRestitutionCombineMode); -} diff --git a/Source/Engine/Physics/PhysicalMaterial.h b/Source/Engine/Physics/PhysicalMaterial.h index 3b06a2a43..66a321b86 100644 --- a/Source/Engine/Physics/PhysicalMaterial.h +++ b/Source/Engine/Physics/PhysicalMaterial.h @@ -5,20 +5,13 @@ #include "Types.h" #include "Engine/Serialization/ISerializable.h" -// Default values for the physical material - -#define PhysicalMaterial_Friction 0.7f -#define PhysicalMaterial_FrictionCombineMode PhysicsCombineMode::Average -#define PhysicalMaterial_OverrideFrictionCombineMode false -#define PhysicalMaterial_Restitution 0.3f -#define PhysicalMaterial_RestitutionCombineMode PhysicsCombineMode::Average -#define PhysicalMaterial_OverrideRestitutionCombineMode false - /// /// Physical materials are used to define the response of a physical object when interacting dynamically with the world. /// -class FLAXENGINE_API PhysicalMaterial : public ISerializable +API_CLASS() class FLAXENGINE_API PhysicalMaterial final : public ISerializable { +API_AUTO_SERIALIZATION(); +DECLARE_SCRIPTING_TYPE_MINIMAL(PhysicalMaterial); private: PxMaterial* _material; @@ -40,32 +33,38 @@ public: /// /// The friction value of surface, controls how easily things can slide on this surface. /// - float Friction = PhysicalMaterial_Friction; + API_FIELD(Attributes="EditorOrder(0), Limit(0), EditorDisplay(\"Physical Material\")") + float Friction = 0.7f; /// /// The friction combine mode, controls how friction is computed for multiple materials. /// - PhysicsCombineMode FrictionCombineMode = PhysicalMaterial_FrictionCombineMode; + API_FIELD(Attributes="EditorOrder(1), EditorDisplay(\"Physical Material\")") + PhysicsCombineMode FrictionCombineMode = PhysicsCombineMode::Average; /// /// If set we will use the FrictionCombineMode of this material, instead of the FrictionCombineMode found in the Physics settings. /// - bool OverrideFrictionCombineMode = PhysicalMaterial_OverrideFrictionCombineMode; + API_FIELD(Attributes="HideInEditor") + bool OverrideFrictionCombineMode = false; /// /// The restitution or 'bounciness' of this surface, between 0 (no bounce) and 1 (outgoing velocity is same as incoming). /// - float Restitution = PhysicalMaterial_Restitution; + API_FIELD(Attributes="EditorOrder(3), Range(0, 1), EditorDisplay(\"Physical Material\")") + float Restitution = 0.3f; /// /// The restitution combine mode, controls how restitution is computed for multiple materials. /// - PhysicsCombineMode RestitutionCombineMode = PhysicalMaterial_RestitutionCombineMode; + API_FIELD(Attributes="EditorOrder(4), EditorDisplay(\"Physical Material\")") + PhysicsCombineMode RestitutionCombineMode = PhysicsCombineMode::Average; /// /// If set we will use the RestitutionCombineMode of this material, instead of the RestitutionCombineMode found in the Physics settings. /// - bool OverrideRestitutionCombineMode = PhysicalMaterial_OverrideRestitutionCombineMode; + API_FIELD(Attributes="HideInEditor") + bool OverrideRestitutionCombineMode = false; public: @@ -79,10 +78,4 @@ public: /// Updates the PhysX material (after any property change). /// void UpdatePhysXMaterial(); - -public: - - // [ISerializable] - void Serialize(SerializeStream& stream, const void* otherObj) override; - void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; }; diff --git a/Source/Engine/Physics/Physics.cpp b/Source/Engine/Physics/Physics.cpp index 2daa47010..98237948f 100644 --- a/Source/Engine/Physics/Physics.cpp +++ b/Source/Engine/Physics/Physics.cpp @@ -1,6 +1,7 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. #include "Physics.h" +#include "PhysicalMaterial.h" #include "Engine/Core/Log.h" #include "Engine/Threading/Threading.h" #include "Engine/Platform/CPUInfo.h" @@ -13,6 +14,8 @@ #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Core/Memory/Memory.h" #include "Engine/Engine/EngineService.h" +#include "Engine/Serialization/Serialization.h" +#include "Engine/Engine/Time.h" #include #include #if WITH_PVD @@ -110,34 +113,43 @@ struct ActionData }; PxPhysics* CPhysX = nullptr; -PhysXAllocator PhysXAllocatorCallback; -PhysXError PhysXErrorCallback; -PxSimulationFilterShader PhysXDefaultFilterShader = PxDefaultSimulationFilterShader; #if WITH_PVD PxPvd* CPVD = nullptr; #endif -PxTolerancesScale ToleranceScale; -SimulationEventCallback EventsCallback; -void* ScratchMemory = nullptr; -FixedStepper* Stepper = nullptr; -CriticalSection FlushLocker; -Array NewActors; -Array Actions; -Array DeadActors; -Array DeadMaterials; -Array _deadObjects; -Array DeadColliders; -Array DeadJoints; -bool _isDuringSimulation = false; -PxFoundation* _foundation = nullptr; + +namespace +{ + PhysXAllocator PhysXAllocatorCallback; + PhysXError PhysXErrorCallback; + PxSimulationFilterShader PhysXDefaultFilterShader = PxDefaultSimulationFilterShader; + PxTolerancesScale ToleranceScale; + SimulationEventCallback EventsCallback; + void* ScratchMemory = nullptr; + FixedStepper* Stepper = nullptr; + CriticalSection FlushLocker; + Array NewActors; + Array Actions; + Array DeadActors; + Array DeadMaterials; + Array _deadObjects; + Array DeadColliders; + Array DeadJoints; + bool _queriesHitTriggers = true; + bool _isDuringSimulation = false; + PhysicsCombineMode _frictionCombineMode = PhysicsCombineMode::Average; + PhysicsCombineMode _restitutionCombineMode = PhysicsCombineMode::Average; + PxFoundation* _foundation = nullptr; #if COMPILE_WITH_PHYSICS_COOKING -PxCooking* Cooking = nullptr; + PxCooking* Cooking = nullptr; #endif -PxScene* PhysicsScene = nullptr; -PxMaterial* DefaultMaterial = nullptr; -PxControllerManager* ControllerManager = nullptr; -PxCpuDispatcher* CpuDispatcher = nullptr; + PxScene* PhysicsScene = nullptr; + PxMaterial* DefaultMaterial = nullptr; + PxControllerManager* ControllerManager = nullptr; + PxCpuDispatcher* CpuDispatcher = nullptr; +} + bool Physics::AutoSimulation = true; +uint32 Physics::LayerMasks[32]; class PhysicsService : public EngineService { @@ -146,6 +158,8 @@ public: PhysicsService() : EngineService(TEXT("Physics"), 0) { + for (int32 i = 0; i < 32; i++) + Physics::LayerMasks[i] = MAX_uint32; } bool Init() override; @@ -155,12 +169,146 @@ public: PhysicsService PhysicsServiceInstance; +PxShapeFlags GetShapeFlags(bool isTrigger, bool isEnabled) +{ +#if WITH_PVD + PxShapeFlags flags = PxShapeFlag::eVISUALIZATION; +#else + PxShapeFlags flags = static_cast(0); +#endif + + if (isEnabled) + { + if (isTrigger) + { + flags |= PxShapeFlag::eTRIGGER_SHAPE; + if (_queriesHitTriggers) + flags |= PxShapeFlag::eSCENE_QUERY_SHAPE; + } + else + { + flags = PxShapeFlag::eSIMULATION_SHAPE | PxShapeFlag::eSCENE_QUERY_SHAPE; + } + } + + return flags; +} + +void PhysicsSettings::Apply() +{ + Time::_physicsMaxDeltaTime = MaxDeltaTime; + _queriesHitTriggers = QueriesHitTriggers; + _frictionCombineMode = FrictionCombineMode; + _restitutionCombineMode = RestitutionCombineMode; + Platform::MemoryCopy(Physics::LayerMasks, LayerMasks, sizeof(LayerMasks)); + Physics::SetGravity(DefaultGravity); + Physics::SetBounceThresholdVelocity(BounceThresholdVelocity); + Physics::SetEnableCCD(!DisableCCD); + + // TODO: setting eADAPTIVE_FORCE requires PxScene setup (physx docs: This flag is not mutable, and must be set in PxSceneDesc at scene creation.) + // TODO: update all shapes filter data + // TODO: update all shapes flags + + /* + { + get all actors and then: + + const PxU32 numShapes = actor->getNbShapes(); + PxShape** shapes = (PxShape**)SAMPLE_ALLOC(sizeof(PxShape*)*numShapes); + actor->getShapes(shapes, numShapes); + for (PxU32 i = 0; i < numShapes; i++) + { + .. + } + SAMPLE_FREE(shapes); + }*/ +} + +PhysicsSettings::PhysicsSettings() +{ + for (int32 i = 0; i < 32; i++) + LayerMasks[i] = MAX_uint32; +} + +void PhysicsSettings::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) +{ + DESERIALIZE(DefaultGravity); + DESERIALIZE(TriangleMeshTriangleMinAreaThreshold); + DESERIALIZE(BounceThresholdVelocity); + DESERIALIZE(FrictionCombineMode); + DESERIALIZE(RestitutionCombineMode); + DESERIALIZE(DisableCCD); + DESERIALIZE(EnableAdaptiveForce); + DESERIALIZE(MaxDeltaTime); + DESERIALIZE(EnableSubstepping); + DESERIALIZE(SubstepDeltaTime); + DESERIALIZE(MaxSubsteps); + DESERIALIZE(QueriesHitTriggers); + DESERIALIZE(SupportCookingAtRuntime); + + const auto layers = stream.FindMember("LayerMasks"); + if (layers != stream.MemberEnd()) + { + auto& layersArray = layers->value; + ASSERT(layersArray.IsArray()); + for (uint32 i = 0; i < layersArray.Size() && i < 32; i++) + { + LayerMasks[i] = layersArray[i].GetUint(); + } + } +} + +PhysicalMaterial::PhysicalMaterial() + : _material(nullptr) +{ +} + +PhysicalMaterial::~PhysicalMaterial() +{ + if (_material) + { + Physics::RemoveMaterial(_material); + } +} + +PxMaterial* PhysicalMaterial::GetPhysXMaterial() +{ + if (_material == nullptr && CPhysX) + { + _material = CPhysX->createMaterial(Friction, Friction, Restitution); + _material->userData = this; + + const PhysicsCombineMode useFrictionCombineMode = OverrideFrictionCombineMode ? FrictionCombineMode : _frictionCombineMode; + _material->setFrictionCombineMode(static_cast(useFrictionCombineMode)); + + const PhysicsCombineMode useRestitutionCombineMode = OverrideRestitutionCombineMode ? RestitutionCombineMode : _restitutionCombineMode; + _material->setRestitutionCombineMode(static_cast(useRestitutionCombineMode)); + } + + return _material; +} + +void PhysicalMaterial::UpdatePhysXMaterial() +{ + if (_material != nullptr) + { + _material->setStaticFriction(Friction); + _material->setDynamicFriction(Friction); + const PhysicsCombineMode useFrictionCombineMode = OverrideFrictionCombineMode ? FrictionCombineMode : _frictionCombineMode; + _material->setFrictionCombineMode(static_cast(useFrictionCombineMode)); + + _material->setRestitution(Restitution); + const PhysicsCombineMode useRestitutionCombineMode = OverrideRestitutionCombineMode ? RestitutionCombineMode : _restitutionCombineMode; + _material->setRestitutionCombineMode(static_cast(useRestitutionCombineMode)); + } +} + bool PhysicsService::Init() { #define CHECK_INIT(value, msg) if(!value) { LOG(Error, msg); return true; } auto cpuInfo = Platform::GetCPUInfo(); - auto settings = PhysicsSettings::Instance(); + auto& settings = *PhysicsSettings::Get(); // Send info LOG(Info, "Setup NVIDIA PhysX {0}.{1}.{2}", PX_PHYSICS_VERSION_MAJOR, PX_PHYSICS_VERSION_MINOR, PX_PHYSICS_VERSION_BUGFIX); @@ -220,7 +368,7 @@ bool PhysicsService::Init() #if COMPILE_WITH_PHYSICS_COOKING #if !USE_EDITOR - if (settings->SupportCookingAtRuntime) + if (settings.SupportCookingAtRuntime) #endif { // Init cooking @@ -235,16 +383,16 @@ bool PhysicsService::Init() // Create scene description PxSceneDesc sceneDesc(CPhysX->getTolerancesScale()); - sceneDesc.gravity = C2P(settings->DefaultGravity); + sceneDesc.gravity = C2P(settings.DefaultGravity); sceneDesc.flags |= PxSceneFlag::eENABLE_ACTIVE_ACTORS; //sceneDesc.flags |= PxSceneFlag::eEXCLUDE_KINEMATICS_FROM_ACTIVE_ACTORS; // TODO: set it? - if (!settings->DisableCCD) + if (!settings.DisableCCD) sceneDesc.flags |= PxSceneFlag::eENABLE_CCD; - if (settings->EnableAdaptiveForce) + if (settings.EnableAdaptiveForce) sceneDesc.flags |= PxSceneFlag::eADAPTIVE_FORCE; sceneDesc.simulationEventCallback = &EventsCallback; sceneDesc.filterShader = PhysiXFilterShader; - sceneDesc.bounceThresholdVelocity = settings->BounceThresholdVelocity; + sceneDesc.bounceThresholdVelocity = settings.BounceThresholdVelocity; if (sceneDesc.cpuDispatcher == nullptr) { CpuDispatcher = PxDefaultCpuDispatcherCreate(Math::Clamp(cpuInfo.ProcessorCoreCount - 1, 1, 4)); @@ -366,7 +514,7 @@ void Physics::SetGravity(const Vector3& value) bool Physics::GetEnableCCD() { - return PhysicsScene ? (PhysicsScene->getFlags() & PxSceneFlag::eENABLE_CCD) == PxSceneFlag::eENABLE_CCD : !PhysicsSettings_DisableCCD; + return PhysicsScene ? (PhysicsScene->getFlags() & PxSceneFlag::eENABLE_CCD) == PxSceneFlag::eENABLE_CCD : !PhysicsSettings::Get()->DisableCCD; } void Physics::SetEnableCCD(const bool value) @@ -377,7 +525,7 @@ void Physics::SetEnableCCD(const bool value) float Physics::GetBounceThresholdVelocity() { - return PhysicsScene ? PhysicsScene->getBounceThresholdVelocity() : PhysicsSettings_BounceThresholdVelocity; + return PhysicsScene ? PhysicsScene->getBounceThresholdVelocity() : PhysicsSettings::Get()->BounceThresholdVelocity; } void Physics::SetBounceThresholdVelocity(const float value) @@ -390,13 +538,13 @@ void Physics::Simulate(float dt) { ASSERT(IsInMainThread() && !_isDuringSimulation); ASSERT(CPhysX); - const auto settings = PhysicsSettings::Instance(); + const auto& settings = *PhysicsSettings::Get(); // Flush the old/new objects and the other requests before the simulation FlushRequests(); // Clamp delta - dt = Math::Clamp(dt, 0.0f, settings->MaxDeltaTime); + dt = Math::Clamp(dt, 0.0f, settings.MaxDeltaTime); // Prepare util objects if (ScratchMemory == nullptr) @@ -407,10 +555,10 @@ void Physics::Simulate(float dt) { Stepper = New(); } - if (settings->EnableSubstepping) + if (settings.EnableSubstepping) { // Use substeps - Stepper->Setup(settings->SubstepDeltaTime, settings->MaxSubsteps); + Stepper->Setup(settings.SubstepDeltaTime, settings.MaxSubsteps); } else { diff --git a/Source/Engine/Physics/Physics.h b/Source/Engine/Physics/Physics.h index bdc1326d3..05b64cb0f 100644 --- a/Source/Engine/Physics/Physics.h +++ b/Source/Engine/Physics/Physics.h @@ -155,6 +155,11 @@ public: /// API_PROPERTY() static void SetBounceThresholdVelocity(float value); + /// + /// The collision layers masks. Used to define layer-based collision detection. + /// + static uint32 LayerMasks[32]; + public: /// diff --git a/Source/Engine/Physics/PhysicsSettings.cpp b/Source/Engine/Physics/PhysicsSettings.cpp deleted file mode 100644 index be8bff331..000000000 --- a/Source/Engine/Physics/PhysicsSettings.cpp +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. - -#include "PhysicsSettings.h" -#include "Engine/Serialization/Serialization.h" -#include "Physics.h" - -void PhysicsSettings::Apply() -{ - // Set simulation parameters - Physics::SetGravity(DefaultGravity); - Physics::SetBounceThresholdVelocity(BounceThresholdVelocity); - Physics::SetEnableCCD(!DisableCCD); - - // TODO: setting eADAPTIVE_FORCE requires PxScene setup (physx docs: This flag is not mutable, and must be set in PxSceneDesc at scene creation.) - - // Update all shapes - - // TODO: update all shapes filter data - // TODO: update all shapes flags - - /* - { - get all actors and then: - - const PxU32 numShapes = actor->getNbShapes(); - PxShape** shapes = (PxShape**)SAMPLE_ALLOC(sizeof(PxShape*)*numShapes); - actor->getShapes(shapes, numShapes); - for (PxU32 i = 0; i < numShapes; i++) - { - .. - } - SAMPLE_FREE(shapes); - }*/ -} - -void PhysicsSettings::RestoreDefault() -{ - DefaultGravity = PhysicsSettings_DefaultGravity; - TriangleMeshTriangleMinAreaThreshold = PhysicsSettings_TriangleMeshTriangleMinAreaThreshold; - BounceThresholdVelocity = PhysicsSettings_BounceThresholdVelocity; - FrictionCombineMode = PhysicsSettings_FrictionCombineMode; - RestitutionCombineMode = PhysicsSettings_RestitutionCombineMode; - DisableCCD = PhysicsSettings_DisableCCD; - EnableAdaptiveForce = PhysicsSettings_EnableAdaptiveForce; - MaxDeltaTime = PhysicsSettings_MaxDeltaTime; - EnableSubstepping = PhysicsSettings_EnableSubstepping; - SubstepDeltaTime = PhysicsSettings_SubstepDeltaTime; - MaxSubsteps = PhysicsSettings_MaxSubsteps; - QueriesHitTriggers = PhysicsSettings_QueriesHitTriggers; - SupportCookingAtRuntime = PhysicsSettings_SupportCookingAtRuntime; - - for (int32 i = 0; i < 32; i++) - LayerMasks[i] = MAX_uint32; -} - -void PhysicsSettings::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) -{ - DESERIALIZE(DefaultGravity); - DESERIALIZE(TriangleMeshTriangleMinAreaThreshold); - DESERIALIZE(BounceThresholdVelocity); - DESERIALIZE(FrictionCombineMode); - DESERIALIZE(RestitutionCombineMode); - DESERIALIZE(DisableCCD); - DESERIALIZE(EnableAdaptiveForce); - DESERIALIZE(MaxDeltaTime); - DESERIALIZE(EnableSubstepping); - DESERIALIZE(SubstepDeltaTime); - DESERIALIZE(MaxSubsteps); - DESERIALIZE(QueriesHitTriggers); - DESERIALIZE(SupportCookingAtRuntime); - - const auto layers = stream.FindMember("LayerMasks"); - if (layers != stream.MemberEnd()) - { - auto& layersArray = layers->value; - ASSERT(layersArray.IsArray()); - for (uint32 i = 0; i < layersArray.Size() && i < 32; i++) - { - LayerMasks[i] = layersArray[i].GetUint(); - } - } -} diff --git a/Source/Engine/Physics/PhysicsSettings.h b/Source/Engine/Physics/PhysicsSettings.h index 84c00deb9..214c86558 100644 --- a/Source/Engine/Physics/PhysicsSettings.h +++ b/Source/Engine/Physics/PhysicsSettings.h @@ -6,105 +6,91 @@ #include "Engine/Core/Math/Vector3.h" #include "Types.h" -// Default values for the physics settings - -#define PhysicsSettings_DefaultGravity Vector3(0, -981.0f, 0) -#define PhysicsSettings_TriangleMeshTriangleMinAreaThreshold (5.0f) -#define PhysicsSettings_BounceThresholdVelocity (200.0f) -#define PhysicsSettings_FrictionCombineMode PhysicsCombineMode::Average -#define PhysicsSettings_RestitutionCombineMode PhysicsCombineMode::Average -#define PhysicsSettings_DisableCCD false -#define PhysicsSettings_EnableAdaptiveForce false -#define PhysicsSettings_MaxDeltaTime (1.0f / 10.0f) -#define PhysicsSettings_EnableSubstepping false -#define PhysicsSettings_SubstepDeltaTime (1.0f / 120.0f) -#define PhysicsSettings_MaxSubsteps 5 -#define PhysicsSettings_QueriesHitTriggers true -#define PhysicsSettings_SupportCookingAtRuntime false - /// /// Physics simulation settings container. /// -/// -class PhysicsSettings : public Settings +API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings", NoConstructor) class FLAXENGINE_API PhysicsSettings : public SettingsBase { -public: - - /// - /// Initializes a new instance of the class. - /// - PhysicsSettings() - { - for (int32 i = 0; i < 32; i++) - LayerMasks[i] = MAX_uint32; - } - +DECLARE_SCRIPTING_TYPE_MINIMAL(PhysicsSettings); public: /// /// The default gravity force value (in cm^2/s). /// - Vector3 DefaultGravity = PhysicsSettings_DefaultGravity; - - /// - /// Triangles from triangle meshes (CSG) with an area less than or equal to this value will be removed from physics collision data. Set to less than or equal 0 to disable. - /// - float TriangleMeshTriangleMinAreaThreshold = PhysicsSettings_TriangleMeshTriangleMinAreaThreshold; - - /// - /// Minimum relative velocity required for an object to bounce. A typical value for simulation stability is about 0.2 * gravity - /// - float BounceThresholdVelocity = PhysicsSettings_BounceThresholdVelocity; - - /// - /// Default friction combine mode, controls how friction is computed for multiple materials. - /// - PhysicsCombineMode FrictionCombineMode = PhysicsSettings_FrictionCombineMode; - - /// - /// Default restitution combine mode, controls how restitution is computed for multiple materials. - /// - PhysicsCombineMode RestitutionCombineMode = PhysicsSettings_RestitutionCombineMode; - - /// - /// If true CCD will be ignored. This is an optimization when CCD is never used which removes the need for physx to check it internally. - /// - bool DisableCCD = PhysicsSettings_DisableCCD; - - /// - /// Enables adaptive forces to accelerate convergence of the solver. Can improve physics simulation performance but lead to artifacts. - /// - bool EnableAdaptiveForce = PhysicsSettings_EnableAdaptiveForce; - - /// - /// The maximum allowed delta time (in seconds) for the physics simulation step. - /// - float MaxDeltaTime = PhysicsSettings_MaxDeltaTime; - - /// - /// Whether to substep the physics simulation. - /// - bool EnableSubstepping = PhysicsSettings_EnableSubstepping; - - /// - /// Delta time (in seconds) for an individual simulation substep. - /// - float SubstepDeltaTime = PhysicsSettings_SubstepDeltaTime; - - /// - /// The maximum number of substeps for physics simulation. - /// - int32 MaxSubsteps = PhysicsSettings_MaxSubsteps; + API_FIELD(Attributes="EditorOrder(0), DefaultValue(typeof(Vector3), \"0,-981.0,0\"), EditorDisplay(\"Simulation\")") + Vector3 DefaultGravity = Vector3(0, -981.0f, 0); /// /// If enabled, any Raycast or other scene query that intersects with a Collider marked as a Trigger will returns with a hit. Individual raycasts can override this behavior. /// - bool QueriesHitTriggers = PhysicsSettings_QueriesHitTriggers; + API_FIELD(Attributes="EditorOrder(10), DefaultValue(true), EditorDisplay(\"Framerate\")") + bool QueriesHitTriggers = true; + + /// + /// Triangles from triangle meshes (CSG) with an area less than or equal to this value will be removed from physics collision data. Set to less than or equal 0 to disable. + /// + API_FIELD(Attributes="EditorOrder(20), DefaultValue(5.0f), EditorDisplay(\"Simulation\")") + float TriangleMeshTriangleMinAreaThreshold = 5.0f; + + /// + /// Minimum relative velocity required for an object to bounce. A typical value for simulation stability is about 0.2 * gravity + /// + API_FIELD(Attributes="EditorOrder(30), DefaultValue(200.0f), EditorDisplay(\"Simulation\")") + float BounceThresholdVelocity = 200.0f; + + /// + /// Default friction combine mode, controls how friction is computed for multiple materials. + /// + API_FIELD(Attributes="EditorOrder(40), DefaultValue(PhysicsCombineMode.Average), EditorDisplay(\"Simulation\")") + PhysicsCombineMode FrictionCombineMode = PhysicsCombineMode::Average; + + /// + /// Default restitution combine mode, controls how restitution is computed for multiple materials. + /// + API_FIELD(Attributes="EditorOrder(40), DefaultValue(PhysicsCombineMode.Average), EditorDisplay(\"Simulation\")") + PhysicsCombineMode RestitutionCombineMode = PhysicsCombineMode::Average; + + /// + /// If true CCD will be ignored. This is an optimization when CCD is never used which removes the need for physx to check it internally. + /// + API_FIELD(Attributes="EditorOrder(70), DefaultValue(false), EditorDisplay(\"Simulation\")") + bool DisableCCD = false; + + /// + /// Enables adaptive forces to accelerate convergence of the solver. Can improve physics simulation performance but lead to artifacts. + /// + API_FIELD(Attributes="EditorOrder(80), DefaultValue(false), EditorDisplay(\"Simulation\")") + bool EnableAdaptiveForce = false; + + /// + /// The maximum allowed delta time (in seconds) for the physics simulation step. + /// + API_FIELD(Attributes="EditorOrder(1000), DefaultValue(1.0f / 10.0f), EditorDisplay(\"Framerate\")") + float MaxDeltaTime = 1.0f / 10.0f; + + /// + /// Whether to substep the physics simulation. + /// + API_FIELD(Attributes="EditorOrder(1005), DefaultValue(false), EditorDisplay(\"Framerate\")") + bool EnableSubstepping = false; + + /// + /// Delta time (in seconds) for an individual simulation substep. + /// + API_FIELD(Attributes="EditorOrder(1010), DefaultValue(1.0f / 120.0f), EditorDisplay(\"Framerate\")") + float SubstepDeltaTime = 1.0f / 120.0f; + + /// + /// The maximum number of substeps for physics simulation. + /// + API_FIELD(Attributes="EditorOrder(1020), DefaultValue(5), EditorDisplay(\"Framerate\")") + int32 MaxSubsteps = 5; /// /// Enables support for cooking physical collision shapes geometry at runtime. Use it to enable generating runtime terrain collision or convex mesh colliders. /// - bool SupportCookingAtRuntime = PhysicsSettings_SupportCookingAtRuntime; + API_FIELD(Attributes="EditorOrder(1100), DefaultValue(false), EditorDisplay(\"Other\")") + bool SupportCookingAtRuntime = false; public: @@ -115,8 +101,17 @@ public: public: - // [Settings] + /// + /// Initializes a new instance of the class. + /// + PhysicsSettings(); + + /// + /// Gets the instance of the settings asset (default value if missing). Object returned by this method is always loaded with valid data to use. + /// + static PhysicsSettings* Get(); + + // [SettingsBase] void Apply() override; - void RestoreDefault() override; void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override; }; diff --git a/Source/Engine/Physics/Types.h b/Source/Engine/Physics/Types.h index 80a4e30b9..6465dd900 100644 --- a/Source/Engine/Physics/Types.h +++ b/Source/Engine/Physics/Types.h @@ -28,6 +28,7 @@ namespace physx class PxFoundation; class PxShape; class PxGeometry; + class PxGeometryHolder; class PxProfileZoneManager; class PxMaterial; class PxPvd; diff --git a/Source/Engine/Physics/Utilities.h b/Source/Engine/Physics/Utilities.h index 11914277f..34ee7f66d 100644 --- a/Source/Engine/Physics/Utilities.h +++ b/Source/Engine/Physics/Utilities.h @@ -14,9 +14,6 @@ #include #include #include -#include "PhysicsSettings.h" - -//////////////////////////// Conversion between PhysX and Flax types inline PxVec2& C2P(const Vector2& v) { @@ -43,8 +40,6 @@ inline PxBounds3& C2P(const BoundingBox& v) return *(PxBounds3*)&v; } -//////////////////////////// - inline Vector2& P2C(const PxVec2& v) { return *(Vector2*)&v; @@ -79,29 +74,4 @@ inline Vector3 P2C(const PxExtendedVec3& v) #endif } -//////////////////////////// - -inline PxShapeFlags GetShapeFlags(bool isTrigger, bool isEnabled) -{ -#if WITH_PVD - PxShapeFlags flags = PxShapeFlag::eVISUALIZATION; -#else - PxShapeFlags flags = static_cast(0); -#endif - - if (isEnabled) - { - if (isTrigger) - { - flags |= PxShapeFlag::eTRIGGER_SHAPE; - if (PhysicsSettings::Instance()->QueriesHitTriggers) - flags |= PxShapeFlag::eSCENE_QUERY_SHAPE; - } - else - { - flags = PxShapeFlag::eSIMULATION_SHAPE | PxShapeFlag::eSCENE_QUERY_SHAPE; - } - } - - return flags; -} +extern PxShapeFlags GetShapeFlags(bool isTrigger, bool isEnabled); diff --git a/Source/Engine/Platform/Android/AndroidPlatformSettings.h b/Source/Engine/Platform/Android/AndroidPlatformSettings.h index eabe6f6b1..0526849a9 100644 --- a/Source/Engine/Platform/Android/AndroidPlatformSettings.h +++ b/Source/Engine/Platform/Android/AndroidPlatformSettings.h @@ -9,41 +9,37 @@ /// /// Android platform settings. /// -/// -class AndroidPlatformSettings : public Settings +API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API AndroidPlatformSettings : public SettingsBase { +DECLARE_SCRIPTING_TYPE_MINIMAL(AndroidPlatformSettings); public: /// /// The application package name (eg. com.company.product). Custom tokens: ${PROJECT_NAME}, ${COMPANY_NAME}. /// - String PackageName; + API_FIELD(Attributes="EditorOrder(0), EditorDisplay(\"General\")") + String PackageName = TEXT("com.${COMPANY_NAME}.${PROJECT_NAME}"); /// /// The application permissions list (eg. android.media.action.IMAGE_CAPTURE). Added to the generated manifest file. /// + API_FIELD(Attributes="EditorOrder(100), EditorDisplay(\"General\")") Array Permissions; /// /// Custom icon texture (asset id) to use for the application (overrides the default one). /// + API_FIELD(Attributes="EditorOrder(1030), AssetReference(typeof(Texture)), EditorDisplay(\"Other\")") Guid OverrideIcon; public: - AndroidPlatformSettings() - { - RestoreDefault(); - } - - // [Settings] - void RestoreDefault() final override - { - PackageName = TEXT("com.${COMPANY_NAME}.${PROJECT_NAME}"); - Permissions.Clear(); - OverrideIcon = Guid::Empty; - } + /// + /// Gets the instance of the settings asset (default value if missing). Object returned by this method is always loaded with valid data to use. + /// + static AndroidPlatformSettings* Get(); + // [SettingsBase] void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override { DESERIALIZE(PackageName); diff --git a/Source/Engine/Platform/Base/NetworkBase.cpp b/Source/Engine/Platform/Base/NetworkBase.cpp new file mode 100644 index 000000000..6ba5208f8 --- /dev/null +++ b/Source/Engine/Platform/Base/NetworkBase.cpp @@ -0,0 +1,103 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#include "NetworkBase.h" + +bool NetworkBase::CreateSocket(NetworkSocket& socket, NetworkProtocol proto, NetworkIPVersion ipv) +{ + return true; +} + +bool NetworkBase::DestroySocket(NetworkSocket& socket) +{ + return true; +} + +bool NetworkBase::SetSocketOption(NetworkSocket& socket, NetworkSocketOption option, bool value) +{ + return true; +} + +bool NetworkBase::SetSocketOption(NetworkSocket& socket, NetworkSocketOption option, int32 value) +{ + return true; +} + +bool NetworkBase::GetSocketOption(NetworkSocket& socket, NetworkSocketOption option, bool* value) +{ + return true; +} + +bool NetworkBase::GetSocketOption(NetworkSocket& socket, NetworkSocketOption option, int32* value) +{ + return true; +} + +bool NetworkBase::ConnectSocket(NetworkSocket& socket, NetworkEndPoint& endPoint) +{ + return true; +} + +bool NetworkBase::BindSocket(NetworkSocket& socket, NetworkEndPoint& endPoint) +{ + return true; +} + +bool NetworkBase::Listen(NetworkSocket& socket, uint16 queueSize) +{ + return true; +} + +bool NetworkBase::Accept(NetworkSocket& serverSock, NetworkSocket& newSock, NetworkEndPoint& newEndPoint) +{ + return true; +} + +bool NetworkBase::IsReadable(NetworkSocket& socket) +{ + return true; +} + +bool NetworkBase::IsWriteable(NetworkSocket& socket) +{ + return true; +} + +int32 NetworkBase::Poll(NetworkSocketGroup& group) +{ + return -1; +} + +bool NetworkBase::GetSocketState(NetworkSocketGroup& group, uint32 index, NetworkSocketState& state) +{ + return true; +} + +int32 NetworkBase::AddSocketToGroup(NetworkSocketGroup& group, NetworkSocket& socket) +{ + return -1; +} + +void NetworkBase::ClearGroup(NetworkSocketGroup& group) +{ + group.Count = 0; +} + +int32 NetworkBase::WriteSocket(NetworkSocket socket, byte* data, uint32 length, NetworkEndPoint* endPoint) +{ + return -1; +} + +int32 NetworkBase::ReadSocket(NetworkSocket socket, byte* buffer, uint32 bufferSize, NetworkEndPoint* endPoint) +{ + return -1; +} + +bool NetworkBase::CreateEndPoint(String* address, String* port, NetworkIPVersion ipv, NetworkEndPoint& endPoint, bool bindable) +{ + return true; +} + +NetworkEndPoint NetworkBase::RemapEndPointToIPv6(NetworkEndPoint& endPoint) +{ + return NetworkEndPoint(); +} diff --git a/Source/Engine/Platform/Base/NetworkBase.h b/Source/Engine/Platform/Base/NetworkBase.h new file mode 100644 index 000000000..e5ad03f98 --- /dev/null +++ b/Source/Engine/Platform/Base/NetworkBase.h @@ -0,0 +1,267 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Engine/Core/Types/BaseTypes.h" +#include "Engine/Core/Types/String.h" +API_INJECT_CPP_CODE("#include \"Engine/Platform/Network.h\""); + +#define SOCKGROUP_MAXCOUNT 64 +#define SOCKGROUP_ITEMSIZE 16 + +enum class FLAXENGINE_API NetworkProtocol +{ + /// Not specified. + Undefined, + /// User Datagram Protocol. + Udp, + /// Transmission Control Protocol. + Tcp +}; + +enum class FLAXENGINE_API NetworkIPVersion +{ + /// Not specified. + Undefined, + /// Internet Protocol version 4. + IPv4, + /// Internet Protocol version 6. + IPv6 +}; + +struct FLAXENGINE_API NetworkSocket +{ + NetworkProtocol Protocol = NetworkProtocol::Undefined; + NetworkIPVersion IPVersion = NetworkIPVersion::Undefined; + byte Data[8] = {}; +}; + +struct FLAXENGINE_API NetworkEndPoint +{ + NetworkIPVersion IPVersion = NetworkIPVersion::Undefined; + String Address; + String Port; + byte Data[28] = {}; +}; + +enum class FLAXENGINE_API NetworkSocketOption +{ + /// Enables debugging info recording. + Debug, + /// Allows local address reusing. + ReuseAddr, + /// Keeps connections alive. + KeepAlive, + /// Indicates that outgoing data should be sent on whatever interface the socket is bound to and not a routed on some other interface. + DontRoute, + /// Allows for sending broadcast data. + Broadcast, + /// Uses the local loopback address when sending data from this socket. + UseLoopback, + /// Lingers on close if data present. + Linger, + /// Allows out-of-bound data to be returned in-line with regular data. + OOBInline, + /// Socket send data buffer size. + SendBuffer, + /// Socket receive data buffer size. + RecvBuffer, + /// The timeout in milliseconds for blocking send calls. + SendTimeout, + /// The timeout in milliseconds for blocking receive calls. + RecvTimeout, + /// The last socket error code. + Error, + /// Enables the Nagle algorithm for TCP sockets. + NoDelay, + /// Enables IPv6/Ipv4 dual-stacking, UDP/TCP. + IPv6Only, + /// Retrieve the current path MTU, the socket must be connected UDP/TCP. + Mtu +}; + +struct FLAXENGINE_API NetworkSocketState +{ + bool Error = false; + bool Invalid = false; + bool Disconnected = false; + bool Readable = false; + bool Writeable = false; +}; + +struct FLAXENGINE_API NetworkSocketGroup +{ + uint32 Count = 0; + byte Data[SOCKGROUP_MAXCOUNT * SOCKGROUP_ITEMSIZE] = {}; +}; + +class FLAXENGINE_API NetworkBase +{ +public: + /// + /// Creates a new native socket. + /// + /// The socket struct to fill in. + /// The protocol. + /// The ip version. + /// Returns true on error, otherwise false. + static bool CreateSocket(NetworkSocket& socket, NetworkProtocol proto, NetworkIPVersion ipv); + + /// + /// Closes native socket. + /// + /// The socket. + /// Returns true on error, otherwise false. + static bool DestroySocket(NetworkSocket& socket); + + /// + /// Sets the specified socket option. + /// + /// The socket. + /// The option. + /// The value. + /// Returns true on error, otherwise false. + static bool SetSocketOption(NetworkSocket& socket, NetworkSocketOption option, bool value); + + /// + /// Sets the specified socket option. + /// + /// The socket. + /// The option. + /// The value. + /// Returns true on error, otherwise false. + static bool SetSocketOption(NetworkSocket& socket, NetworkSocketOption option, int32 value); + + /// + /// Gets the specified socket option. + /// + /// The socket. + /// The option. + /// The returned value. + /// Returns true on error, otherwise false. + static bool GetSocketOption(NetworkSocket& socket, NetworkSocketOption option, bool* value); + + /// + /// Gets the specified socket option. + /// + /// The socket. + /// The option. + /// The returned value. + /// Returns true on error, otherwise false. + static bool GetSocketOption(NetworkSocket& socket, NetworkSocketOption option, int32* value); + + /// + /// Connects a socket to the specified end point. + /// + /// The socket. + /// The end point. + /// Returns true on error, otherwise false. + static bool ConnectSocket(NetworkSocket& socket, NetworkEndPoint& endPoint); + + /// + /// Binds a socket to the specified end point. + /// + /// The socket. + /// The end point. + /// Returns true on error, otherwise false. + static bool BindSocket(NetworkSocket& socket, NetworkEndPoint& endPoint); + + /// + /// Listens for incoming connection. + /// + /// The socket. + /// Pending connection queue size. + /// Returns true on error, otherwise false. + static bool Listen(NetworkSocket& socket, uint16 queueSize); + + /// + /// Accepts a pending connection. + /// + /// The socket. + /// The newly connected socket. + /// The end point of the new socket. + /// Returns true on error, otherwise false. + static bool Accept(NetworkSocket& serverSock, NetworkSocket& newSock, NetworkEndPoint& newEndPoint); + + /// + /// Checks for socket readability. + /// + /// The socket. + /// Returns true when data is available. Otherwise false. + static bool IsReadable(NetworkSocket& socket); + + /// + /// Checks for socket writeability. + /// + /// The socket. + /// Returns true when data can be written. Otherwise false. + static bool IsWriteable(NetworkSocket& socket); + + /// + /// Updates sockets states. + /// + /// The sockets group. + /// Returns -1 on error, The number of elements where states are nonzero, otherwise 0. + static int32 Poll(NetworkSocketGroup& group); + + /// + /// Retrieves socket state. + /// + /// The group. + /// The socket index in group. + /// The returned state. + /// Returns true on error, otherwise false. + static bool GetSocketState(NetworkSocketGroup& group, uint32 index, NetworkSocketState& state); + + /// + /// Adds a socket to a group. + /// + /// The group. + /// The socket. + /// Returns the socket index in group or -1 on error. + static int32 AddSocketToGroup(NetworkSocketGroup& group, NetworkSocket& socket); + + /// + /// Clears the socket group. + /// + /// The group. + static void ClearGroup(NetworkSocketGroup& group); + + /// + /// Writes data to the socket. + /// + /// The socket. + /// The data to write. + /// The length of data. + /// If protocol is UDP , the destination end point. Otherwise nullptr. + /// Returns -1 on error, otherwise bytes written. + static int32 WriteSocket(NetworkSocket socket, byte* data, uint32 length, NetworkEndPoint* endPoint = nullptr); + + /// + /// Reads data on the socket. + /// + /// The socket. + /// The buffer. + /// Size of the buffer. + /// If UDP, the end point from where data is coming. Otherwise nullptr. + /// Returns -1 on error, otherwise bytes read. + static int32 ReadSocket(NetworkSocket socket, byte* buffer, uint32 bufferSize, NetworkEndPoint* endPoint = nullptr); + + /// + /// Creates an end point. + /// + /// The address (hostname, IPv4 or IPv6). + /// The port. + /// The ip version. + /// The created end point. + /// True if the end point will be connected or binded. + /// Returns true on error, otherwise false. + static bool CreateEndPoint(String* address, String* port, NetworkIPVersion ipv, NetworkEndPoint& endPoint, bool bindable = false); + + /// + /// Remaps an ipv4 end point to an ipv6 one. + /// + /// The ipv4 end point. + /// The ipv6 end point. + static NetworkEndPoint RemapEndPointToIPv6(NetworkEndPoint& endPoint); +}; diff --git a/Source/Engine/Platform/Linux/LinuxPlatformSettings.h b/Source/Engine/Platform/Linux/LinuxPlatformSettings.h index bb5092354..7da0ec406 100644 --- a/Source/Engine/Platform/Linux/LinuxPlatformSettings.h +++ b/Source/Engine/Platform/Linux/LinuxPlatformSettings.h @@ -9,66 +9,67 @@ /// /// Linux platform settings. /// -/// -class LinuxPlatformSettings : public Settings +API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API LinuxPlatformSettings : public SettingsBase { +DECLARE_SCRIPTING_TYPE_MINIMAL(LinuxPlatformSettings); public: /// /// The default game window mode. /// + API_FIELD(Attributes="EditorOrder(10), DefaultValue(GameWindowMode.Windowed), EditorDisplay(\"Window\")") GameWindowMode WindowMode = GameWindowMode::Windowed; /// /// The default game window width (in pixels). /// + API_FIELD(Attributes="EditorOrder(20), DefaultValue(1280), EditorDisplay(\"Window\")") int32 ScreenWidth = 1280; /// /// The default game window height (in pixels). /// + API_FIELD(Attributes="EditorOrder(30), DefaultValue(720), EditorDisplay(\"Window\")") int32 ScreenHeight = 720; - /// - /// Enables game running when application window loses focus. - /// - bool RunInBackground = false; - /// /// Enables resizing the game window by the user. /// + API_FIELD(Attributes="EditorOrder(40), DefaultValue(false), EditorDisplay(\"Window\")") bool ResizableWindow = false; + /// + /// Enables game running when application window loses focus. + /// + API_FIELD(Attributes="EditorOrder(1010), DefaultValue(false), EditorDisplay(\"Other\", \"Run In Background\")") + bool RunInBackground = false; + /// /// Limits maximum amount of concurrent game instances running to one, otherwise user may launch application more than once. /// + API_FIELD(Attributes="EditorOrder(1020), DefaultValue(false), EditorDisplay(\"Other\")") bool ForceSingleInstance = false; /// /// Custom icon texture (asset id) to use for the application (overrides the default one). /// - Guid OverrideIcon = Guid::Empty; + API_FIELD(Attributes="EditorOrder(1030), CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.AssetRefEditor\"), AssetReference(typeof(Texture)), EditorDisplay(\"Other\")") + Guid OverrideIcon; /// /// Enables support for Vulkan. Disabling it reduces compiled shaders count. /// + API_FIELD(Attributes="EditorOrder(2000), DefaultValue(true), EditorDisplay(\"Graphics\")") bool SupportVulkan = true; public: - // [Settings] - void RestoreDefault() final override - { - WindowMode = GameWindowMode::Windowed; - ScreenWidth = 1280; - ScreenHeight = 720; - RunInBackground = false; - ResizableWindow = false; - ForceSingleInstance = false; - OverrideIcon = Guid::Empty; - SupportVulkan = true; - } + /// + /// Gets the instance of the settings asset (default value if missing). Object returned by this method is always loaded with valid data to use. + /// + static LinuxPlatformSettings* Get(); + // [SettingsBase] void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override { DESERIALIZE(WindowMode); diff --git a/Source/Engine/Platform/Network.h b/Source/Engine/Platform/Network.h new file mode 100644 index 000000000..b8c198770 --- /dev/null +++ b/Source/Engine/Platform/Network.h @@ -0,0 +1,21 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#if PLATFORM_WINDOWS +#include "Win32/Win32Network.h" +#elif PLATFORM_UWP +#include "Win32/Win32Network.h" +#elif PLATFORM_LINUX +#include "Base/NetworkBase.h" +#elif PLATFORM_PS4 +#include "Base/NetworkBase.h" +#elif PLATFORM_XBOX_SCARLETT +#include "Win32/Win32Network.h" +#elif PLATFORM_ANDROID +#include "Base/NetworkBase.h" +#else +#error Missing Network implementation! +#endif + +#include "Types.h" diff --git a/Source/Engine/Platform/Platform.Build.cs b/Source/Engine/Platform/Platform.Build.cs index b7a2baa97..72a706685 100644 --- a/Source/Engine/Platform/Platform.Build.cs +++ b/Source/Engine/Platform/Platform.Build.cs @@ -62,5 +62,15 @@ public class Platform : EngineModule break; default: throw new InvalidPlatformException(options.Platform.Target); } + if (options.Target.IsEditor) + { + // Include platform settings headers + options.SourceFiles.Add(Path.Combine(FolderPath, "Windows", "WindowsPlatformSettings.h")); + options.SourceFiles.Add(Path.Combine(FolderPath, "UWP", "UWPPlatformSettings.h")); + options.SourceFiles.Add(Path.Combine(FolderPath, "Linux", "LinuxPlatformSettings.h")); + options.SourceFiles.Add(Path.Combine(FolderPath, "Android", "AndroidPlatformSettings.h")); + AddSourceFileIfExists(options, Path.Combine(Globals.EngineRoot, "Source", "Platforms", "XboxScarlett", "Engine", "Platform", "XboxScarlettPlatformSettings.h")); + AddSourceFileIfExists(options, Path.Combine(Globals.EngineRoot, "Source", "Platforms", "PS4", "Engine", "Platform", "PS4PlatformSettings.h")); + } } } diff --git a/Source/Engine/Platform/Types.h b/Source/Engine/Platform/Types.h index c8e955c1b..ad67e0b01 100644 --- a/Source/Engine/Platform/Types.h +++ b/Source/Engine/Platform/Types.h @@ -22,6 +22,8 @@ class Win32Thread; typedef Win32Thread Thread; class WindowsWindow; typedef WindowsWindow Window; +class Win32Network; +typedef Win32Network Network; #elif PLATFORM_UWP @@ -43,6 +45,8 @@ class Win32Thread; typedef Win32Thread Thread; class UWPWindow; typedef UWPWindow Window; +class NetworkBase; +typedef NetworkBase Network; #elif PLATFORM_LINUX @@ -64,6 +68,8 @@ class LinuxThread; typedef LinuxThread Thread; class LinuxWindow; typedef LinuxWindow Window; +class NetworkBase; +typedef NetworkBase Network; #elif PLATFORM_PS4 @@ -85,6 +91,8 @@ class PS4Thread; typedef PS4Thread Thread; class PS4Window; typedef PS4Window Window; +class NetworkBase; +typedef NetworkBase Network; #elif PLATFORM_XBOX_SCARLETT @@ -106,6 +114,8 @@ class Win32Thread; typedef Win32Thread Thread; class XboxScarlettWindow; typedef XboxScarlettWindow Window; +class NetworkBase; +typedef NetworkBase Network; #elif PLATFORM_ANDROID @@ -127,6 +137,8 @@ class AndroidThread; typedef AndroidThread Thread; class AndroidWindow; typedef AndroidWindow Window; +class NetworkBase; +typedef NetworkBase Network; #else diff --git a/Source/Engine/Platform/UWP/UWPPlatform.cpp b/Source/Engine/Platform/UWP/UWPPlatform.cpp index 3bd654f10..ed01948aa 100644 --- a/Source/Engine/Platform/UWP/UWPPlatform.cpp +++ b/Source/Engine/Platform/UWP/UWPPlatform.cpp @@ -111,6 +111,7 @@ void UWPPlatform::BeforeExit() void UWPPlatform::Exit() { + Win32Platform::Exit(); } int32 UWPPlatform::GetDpi() diff --git a/Source/Engine/Platform/UWP/UWPPlatformSettings.h b/Source/Engine/Platform/UWP/UWPPlatformSettings.h index c668bfb72..e600cead7 100644 --- a/Source/Engine/Platform/UWP/UWPPlatformSettings.h +++ b/Source/Engine/Platform/UWP/UWPPlatformSettings.h @@ -9,15 +9,15 @@ /// /// Universal Windows Platform settings. /// -/// -class UWPPlatformSettings : public Settings +API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API UWPPlatformSettings : public SettingsBase { +DECLARE_SCRIPTING_TYPE_MINIMAL(UWPPlatformSettings); public: /// /// The preferred launch windowing mode. /// - enum class WindowMode + API_ENUM() enum class WindowMode { /// /// The full screen mode @@ -33,7 +33,7 @@ public: /// /// The display orientation modes. Can be combined as flags. /// - enum class DisplayOrientations + API_ENUM(Attributes="Flags") enum class DisplayOrientations { /// /// The none. @@ -71,40 +71,41 @@ public: /// /// The preferred launch windowing mode. Always fullscreen on Xbox. /// + API_FIELD(Attributes="EditorOrder(10), DefaultValue(WindowMode.FullScreen), EditorDisplay(\"Window\")") WindowMode PreferredLaunchWindowingMode = WindowMode::FullScreen; /// /// The display orientation modes. Can be combined as flags. /// + API_FIELD(Attributes="EditorOrder(20), DefaultValue(DisplayOrientations.All), EditorDisplay(\"Window\")") DisplayOrientations AutoRotationPreferences = DisplayOrientations::All; /// /// The location of the package certificate (relative to the project). /// + API_FIELD(Attributes="EditorOrder(1010), DefaultValue(\"\"), EditorDisplay(\"Other\")") String CertificateLocation; /// /// Enables support for DirectX 11. Disabling it reduces compiled shaders count. /// + API_FIELD(Attributes="EditorOrder(2000), DefaultValue(true), EditorDisplay(\"Graphics\", \"Support DirectX 11\")") bool SupportDX11 = true; /// /// Enables support for DirectX 10 and DirectX 10.1. Disabling it reduces compiled shaders count. /// + API_FIELD(Attributes="EditorOrder(2010), DefaultValue(false), EditorDisplay(\"Graphics\", \"Support DirectX 10\")") bool SupportDX10 = false; public: - // [Settings] - void RestoreDefault() final override - { - PreferredLaunchWindowingMode = WindowMode::FullScreen; - AutoRotationPreferences = DisplayOrientations::All; - CertificateLocation.Clear(); - SupportDX11 = true; - SupportDX10 = false; - } + /// + /// Gets the instance of the settings asset (default value if missing). Object returned by this method is always loaded with valid data to use. + /// + static UWPPlatformSettings* Get(); + // [SettingsBase] void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override { DESERIALIZE(PreferredLaunchWindowingMode); diff --git a/Source/Engine/Platform/Win32/Win32Network.cpp b/Source/Engine/Platform/Win32/Win32Network.cpp new file mode 100644 index 000000000..38b99a25e --- /dev/null +++ b/Source/Engine/Platform/Win32/Win32Network.cpp @@ -0,0 +1,489 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#include "Win32Network.h" +#include "Engine/Core/Log.h" +#include "Engine/Core/Collections/Array.h" +#include +#include +#include + +#define SOCKOPT(OPTENUM, OPTLEVEL, OPTNAME) case OPTENUM: *level = OPTLEVEL; *name = OPTNAME; break; + +static_assert(sizeof NetworkSocket::Data >= sizeof SOCKET, "NetworkSocket::Data is not big enough to contains SOCKET !"); +static_assert(sizeof NetworkEndPoint::Data >= sizeof sockaddr_in6, "NetworkEndPoint::Data is not big enough to contains sockaddr_in6 !"); + +// @formatter:off +static const IN6_ADDR v4MappedPrefix = { { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00 } }; +// @formatter:on + +/* + * Known issues : + * Even if dualstacking is enabled it's not possible to bind an Ipv4mappedIPv6 endpoint. windows limitation + */ + +static String GetErrorMessage(int error) +{ + wchar_t* s = nullptr; + FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, error, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + reinterpret_cast(&s), 0, nullptr); + String str(s); + LocalFree(s); + return str; +} + +static String GetLastErrorMessage() +{ + return GetErrorMessage(WSAGetLastError()); +} + +static int GetAddrSize(const sockaddr& addr) +{ + return addr.sa_family == AF_INET6 ? sizeof sockaddr_in6 : sizeof sockaddr_in; +} + +static int GetAddrSizeFromEP(NetworkEndPoint& endPoint) +{ + return endPoint.IPVersion == NetworkIPVersion::IPv6 ? sizeof sockaddr_in6 : sizeof sockaddr_in; +} + +static NetworkIPVersion GetIPVersionFromAddr(const sockaddr& addr) +{ + return addr.sa_family == AF_INET6 ? NetworkIPVersion::IPv6 : NetworkIPVersion::IPv4;; +} + +static bool CreateEndPointFromAddr(sockaddr* addr, NetworkEndPoint& endPoint) +{ + uint32 size = GetAddrSize(*addr); + uint16 port; + void* paddr; + if (addr->sa_family == AF_INET6) + { + paddr = &((sockaddr_in6*)addr)->sin6_addr; + port = ntohs(((sockaddr_in6*)addr)->sin6_port); + } + else if (addr->sa_family == AF_INET) + { + paddr = &((sockaddr_in*)addr)->sin_addr; + port = ntohs(((sockaddr_in*)addr)->sin_port); + } + else + { + LOG(Error, "Unable to create endpoint, sockaddr must be INET or INET6! Family : {0}", addr->sa_family); + return true; + } + + char ip[INET6_ADDRSTRLEN]; + if (inet_ntop(addr->sa_family, paddr, ip, INET6_ADDRSTRLEN) == nullptr) + { + LOG(Error, "Unable to extract address from sockaddr! Error : {0}", GetLastErrorMessage()); + return true; + } + endPoint.Address = String(ip); + char strPort[6]; + _itoa(port, strPort, 10); + endPoint.Port = String(strPort); + endPoint.IPVersion = GetIPVersionFromAddr(*addr); + memcpy(endPoint.Data, addr, size); + return false; +} + +static void PrintAddrFromInfo(addrinfoW& info) +{ + addrinfoW* curr; + for (curr = &info; curr != nullptr; curr = curr->ai_next) + { + void* addr; + if (curr->ai_family == AF_INET) + { + sockaddr_in* ipv4 = (struct sockaddr_in*)curr->ai_addr; + addr = &(ipv4->sin_addr); + } + else + { + sockaddr_in6* ipv6 = (struct sockaddr_in6*)curr->ai_addr; + addr = &(ipv6->sin6_addr); + } + + char str[INET6_ADDRSTRLEN]; + inet_ntop(curr->ai_family, addr, str, INET6_ADDRSTRLEN); + LOG(Info, "ADDR INFO family : {0} socktype : {1}, proto : {2} address : {3}", curr->ai_family, curr->ai_socktype, curr->ai_protocol, StringAnsi(str).ToString()); + } +} + +static void TranslateSockOptToNative(NetworkSocketOption option, int32* level, int32* name) +{ + switch (option) + { + SOCKOPT(NetworkSocketOption::Debug, SOL_SOCKET, SO_DEBUG) + SOCKOPT(NetworkSocketOption::ReuseAddr, SOL_SOCKET, SO_REUSEADDR) + SOCKOPT(NetworkSocketOption::KeepAlive, SOL_SOCKET, SO_KEEPALIVE) + SOCKOPT(NetworkSocketOption::DontRoute, SOL_SOCKET, SO_DONTROUTE) + SOCKOPT(NetworkSocketOption::Broadcast, SOL_SOCKET, SO_BROADCAST) + SOCKOPT(NetworkSocketOption::UseLoopback, SOL_SOCKET, SO_USELOOPBACK) + SOCKOPT(NetworkSocketOption::Linger, SOL_SOCKET, SO_LINGER) + SOCKOPT(NetworkSocketOption::OOBInline, SOL_SOCKET, SO_OOBINLINE) + SOCKOPT(NetworkSocketOption::SendBuffer, SOL_SOCKET, SO_SNDBUF) + SOCKOPT(NetworkSocketOption::RecvBuffer, SOL_SOCKET, SO_RCVBUF) + SOCKOPT(NetworkSocketOption::SendTimeout, SOL_SOCKET, SO_SNDTIMEO) + SOCKOPT(NetworkSocketOption::RecvTimeout, SOL_SOCKET, SO_RCVTIMEO) + SOCKOPT(NetworkSocketOption::Error, SOL_SOCKET, SO_ERROR) + SOCKOPT(NetworkSocketOption::NoDelay, IPPROTO_TCP, TCP_NODELAY) + SOCKOPT(NetworkSocketOption::IPv6Only, IPPROTO_IPV6, IPV6_V6ONLY) + SOCKOPT(NetworkSocketOption::Mtu, IPPROTO_IP , IP_MTU) + } +} + +bool Win32Network::CreateSocket(NetworkSocket& socket, NetworkProtocol proto, NetworkIPVersion ipv) +{ + socket.Protocol = proto; + socket.IPVersion = ipv; + const uint8 family = socket.IPVersion == NetworkIPVersion::IPv6 ? AF_INET6 : AF_INET; + const uint8 stype = socket.Protocol == NetworkProtocol::Tcp ? SOCK_STREAM : SOCK_DGRAM; + const uint8 prot = socket.Protocol == NetworkProtocol::Tcp ? IPPROTO_TCP : IPPROTO_UDP; + SOCKET sock; + + if ((sock = ::socket(family, stype, prot)) == INVALID_SOCKET) + { + LOG(Error, "Can't create native socket! Error : {0}", GetLastErrorMessage()); + return true; + } + memcpy(socket.Data, &sock, sizeof sock); + unsigned long value = 1; + if (ioctlsocket(sock, FIONBIO, &value) == SOCKET_ERROR) + { + LOG(Error, "Can't set socket to NON-BLOCKING type! Error : {0}", GetLastErrorMessage()); + return true; // Support using blocking socket , need to test it + } + return false; +} + +bool Win32Network::DestroySocket(NetworkSocket& socket) +{ + const SOCKET sock = *(SOCKET*)socket.Data; + if (sock != INVALID_SOCKET) + { + closesocket(sock); + return false; + } + LOG(Warning, "Unable to delete socket INVALID_SOCKET! Socket : {0}", sock); + return true; +} + +bool Win32Network::SetSocketOption(NetworkSocket& socket, NetworkSocketOption option, bool value) +{ + const int32 v = value; + return SetSocketOption(socket, option, v); +} + +bool Win32Network::SetSocketOption(NetworkSocket& socket, NetworkSocketOption option, int32 value) +{ + int32 optlvl = 0; + int32 optnme = 0; + + TranslateSockOptToNative(option, &optlvl, &optnme); + + if (setsockopt(*(SOCKET*)socket.Data, optlvl, optnme, (char*)&value, sizeof value) == SOCKET_ERROR) + { + LOG(Warning, "Unable to set socket option ! Socket : {0} Error : {1}", *(SOCKET*)socket.Data, GetLastErrorMessage()); + return true; + } + return false; +} + +bool Win32Network::GetSocketOption(NetworkSocket& socket, NetworkSocketOption option, bool* value) +{ + int32 v; + const bool status = GetSocketOption(socket, option, &v); + *value = v == 1 ? true : false; + return status; +} + +bool Win32Network::GetSocketOption(NetworkSocket& socket, NetworkSocketOption option, int32* value) +{ + int32 optlvl = 0; + int32 optnme = 0; + + TranslateSockOptToNative(option, &optlvl, &optnme); + + int32 size; + if (getsockopt(*(SOCKET*)socket.Data, optlvl, optnme, (char*)value, &size) == SOCKET_ERROR) + { + LOG(Warning, "Unable to get socket option ! Socket : {0} Error : {1}", *(SOCKET*)socket.Data, GetLastErrorMessage()); + return true; + } + return false; +} + +bool Win32Network::ConnectSocket(NetworkSocket& socket, NetworkEndPoint& endPoint) +{ + const uint16 size = GetAddrSizeFromEP(endPoint); + if (connect(*(SOCKET*)socket.Data, (const sockaddr*)endPoint.Data, size) == SOCKET_ERROR) + { + int error = WSAGetLastError(); + if (error == WSAEWOULDBLOCK) + return false; + LOG(Error, "Unable to connect socket to address! Socket : {0} Address : {1} Port : {2} Error : {3}", *(SOCKET*)socket.Data, endPoint.Address, endPoint.Port, GetErrorMessage(error)); + return true; + } + return false; +} + +bool Win32Network::BindSocket(NetworkSocket& socket, NetworkEndPoint& endPoint) +{ + if (socket.IPVersion != endPoint.IPVersion) + { + LOG(Error, "Can't bind socket to end point, Socket.IPVersion != EndPoint.IPVersion! Socket : {0}", *(SOCKET*)socket.Data); + return true; + } + + const uint16 size = endPoint.IPVersion == NetworkIPVersion::IPv6 ? sizeof sockaddr_in6 : sizeof sockaddr_in; + if (bind(*(SOCKET*)socket.Data, (const sockaddr*)endPoint.Data, size) == SOCKET_ERROR) + { + LOG(Error, "Unable to bind socket! Socket : {0} Address : {1} Port : {2} Error : {3}", *(SOCKET*)socket.Data, endPoint.Address, endPoint.Port, GetLastErrorMessage()); + return true; + } + return false; +} + +bool Win32Network::Listen(NetworkSocket& socket, uint16 queueSize) +{ + if (listen(*(SOCKET*)socket.Data, (int32)queueSize) == SOCKET_ERROR) + { + LOG(Error, "Unable to listen ! Socket : {0} Error : {1}", GetLastErrorMessage()); + return true; + } + return false; +} + +bool Win32Network::Accept(NetworkSocket& serverSock, NetworkSocket& newSock, NetworkEndPoint& newEndPoint) +{ + if (serverSock.Protocol != NetworkProtocol::Tcp) + { + LOG(Warning, "Can't accept connection on UDP socket! Socket : {0}", *(SOCKET*)serverSock.Data); + return true; + } + SOCKET sock; + sockaddr_in6 addr; + int32 size = sizeof sockaddr_in6; + if ((sock = accept(*(SOCKET*)serverSock.Data, (sockaddr*)&addr, &size)) == INVALID_SOCKET) + { + int32 error = WSAGetLastError(); + if (error == WSAEWOULDBLOCK) + return false; + LOG(Warning, "Unable to accept incoming connection! Socket : {0} Error : {1}", *(SOCKET*)serverSock.Data, GetErrorMessage(error)); + return true; + } + memcpy(newSock.Data, &sock, sizeof sock); + memcpy(newEndPoint.Data, &addr, size); + newSock.Protocol = serverSock.Protocol; + newSock.IPVersion = serverSock.IPVersion; + if (CreateEndPointFromAddr((sockaddr*)&addr, newEndPoint)) + return true; + return false; +} + +bool Win32Network::IsReadable(NetworkSocket& socket) +{ + pollfd entry; + entry.fd = *(SOCKET*)socket.Data; + entry.events = POLLRDNORM; + if (WSAPoll(&entry, 1, 0) == SOCKET_ERROR) + { + int32 error = WSAGetLastError(); + if (error == WSAEWOULDBLOCK) + return false; + LOG(Error, "Unable to poll socket! Socket : {0} Error : {1}", *(SOCKET*)socket.Data, GetErrorMessage(error)); + return false; + } + if (entry.revents & POLLRDNORM) + return true; + return false; +} + +bool Win32Network::IsWriteable(NetworkSocket& socket) +{ + pollfd entry; + entry.fd = *(SOCKET*)socket.Data; + entry.events = POLLWRNORM; + if (WSAPoll(&entry, 1, 0) == SOCKET_ERROR) + { + int32 error = WSAGetLastError(); + if (error == WSAEWOULDBLOCK) + return false; + LOG(Error, "Unable to poll socket! Socket : {0} Error : {1}", *(SOCKET*)socket.Data, GetErrorMessage(error)); + return false; + } + if (entry.revents & POLLWRNORM) + return true; + return false; +} + +int32 Win32Network::Poll(NetworkSocketGroup& group) +{ + int32 pollret = WSAPoll((pollfd*)group.Data, group.Count, 0); + if (pollret == SOCKET_ERROR) + LOG(Error, "Unable to poll socket group! Error : {0}", GetLastErrorMessage()); + return pollret; +} + +bool Win32Network::GetSocketState(NetworkSocketGroup& group, uint32 index, NetworkSocketState& state) +{ + if (index >= SOCKGROUP_MAXCOUNT) + return true; + pollfd* pollptr = (pollfd*)&group.Data[index * SOCKGROUP_ITEMSIZE]; + memset(&state, 0, sizeof state); + if (pollptr->revents & POLLERR) + state.Error = true; + if (pollptr->revents & POLLHUP) + state.Disconnected = true; + if (pollptr->revents & POLLNVAL) + state.Invalid = true; + if (pollptr->revents & POLLRDNORM) + state.Readable = true; + if (pollptr->revents & POLLWRNORM) + state.Writeable = true; + return false; +} + +int32 Win32Network::AddSocketToGroup(NetworkSocketGroup& group, NetworkSocket& socket) +{ + if (group.Count >= SOCKGROUP_MAXCOUNT) + return -1; + pollfd pollinfo; + pollinfo.fd = *(SOCKET*)socket.Data; + pollinfo.events = POLLRDNORM | POLLWRNORM; + *(pollfd*)&group.Data[group.Count * SOCKGROUP_ITEMSIZE] = pollinfo; + group.Count++; + return group.Count - 1; +} + +void Win32Network::ClearGroup(NetworkSocketGroup& group) +{ + group.Count = 0; +} + +int32 Win32Network::WriteSocket(NetworkSocket socket, byte* data, uint32 length, NetworkEndPoint* endPoint) +{ + if (endPoint != nullptr && socket.IPVersion != endPoint->IPVersion) + { + LOG(Error, "Unable to send data, Socket.IPVersion != EndPoint.IPVersion! Socket : {0}", *(SOCKET*)socket.Data); + return -1; + } + uint32 size; + if (endPoint == nullptr && socket.Protocol == NetworkProtocol::Tcp) + { + if ((size = send(*(SOCKET*)socket.Data, (const char*)data, length, 0)) == SOCKET_ERROR) + { + LOG(Error, "Unable to send data! Socket : {0} Data Length : {1} Error : {2}", *(SOCKET*)socket.Data, length, GetLastErrorMessage()); + return -1; + } + } + else if (endPoint != nullptr && socket.Protocol == NetworkProtocol::Udp) + { + if ((size = sendto(*(SOCKET*)socket.Data, (const char*)data, length, 0, (const sockaddr*)endPoint->Data, GetAddrSizeFromEP(*endPoint))) == SOCKET_ERROR) + { + LOG(Error, "Unable to send data! Socket : {0} Address : {1} Port : {2} Data Length : {3} Error : {4}", *(SOCKET*)socket.Data, endPoint->Address, endPoint->Port, length, GetLastErrorMessage()); + return -1; + } + } + else + { + // TODO: better explanation + LOG(Error, "Unable to send data! Socket : {0} Data Length : {1}", *(SOCKET*)socket.Data, length); + return -1; + } + return size; +} + +int32 Win32Network::ReadSocket(NetworkSocket socket, byte* buffer, uint32 bufferSize, NetworkEndPoint* endPoint) +{ + uint32 size; + if (endPoint == nullptr) // TCP + { + if ((size = recv(*(SOCKET*)socket.Data, (char*)buffer, bufferSize, 0)) == SOCKET_ERROR) + { + const int32 error = WSAGetLastError(); + if (error == WSAEWOULDBLOCK) + return 0; + LOG(Error, "Unable to read data! Socket : {0} Buffer Size : {1} Error : {2}", *(SOCKET*)socket.Data, bufferSize, GetErrorMessage(error)); + return -1; + } + } + else // UDP + { + int32 addrsize = sizeof sockaddr_in6; + sockaddr_in6 addr; + if ((size = recvfrom(*(SOCKET*)socket.Data, (char*)buffer, bufferSize, 0, (sockaddr*)&addr, &addrsize)) == SOCKET_ERROR) + { + LOG(Error, "Unable to read data! Socket : {0} Buffer Size : {1} Error : {2}", *(SOCKET*)socket.Data, bufferSize, GetLastErrorMessage()); + return -1; + } + if (CreateEndPointFromAddr((sockaddr*)&addr, *endPoint)) + return true; + } + return size; +} + +bool Win32Network::CreateEndPoint(String* address, String* port, NetworkIPVersion ipv, NetworkEndPoint& endPoint, bool bindable) +{ + int status; + addrinfoW hints; + addrinfoW* info; + memset(&hints, 0, sizeof hints); + hints.ai_family = ipv == NetworkIPVersion::IPv6 ? AF_INET6 : ipv == NetworkIPVersion::IPv4 ? AF_INET : AF_UNSPEC; + hints.ai_flags |= AI_ADDRCONFIG; + hints.ai_flags |= AI_V4MAPPED; + if (bindable) + hints.ai_flags = AI_PASSIVE; + + // consider using NUMERICHOST/NUMERICSERV if address is a valid Ipv4 or IPv6 so we can skip some look up ( potentially slow when resolving host names ) + if ((status = GetAddrInfoW(address == nullptr ? nullptr : address->Get(), port->Get(), &hints, &info)) != 0) + { + LOG(Error, "Unable to query info for address : {0} Error : {1}", address ? address->Get() : String("ANY"), gai_strerror(status)); + return true; + } + + if (info == nullptr) + { + LOG(Error, "Unable to resolve address! Address : {0}", address ? address->Get() : String("ANY")); + return true; + } + + if (CreateEndPointFromAddr(info->ai_addr, endPoint)) + { + FreeAddrInfoW(info); + return true; + } + FreeAddrInfoW(info); + + return false; +} + +NetworkEndPoint Win32Network::RemapEndPointToIPv6(NetworkEndPoint endPoint) +{ + if (endPoint.IPVersion == NetworkIPVersion::IPv6) + { + LOG(Warning, "Unable to remap EndPoint, already in IPv6 format!"); + return endPoint; + } + + NetworkEndPoint pv6; + sockaddr_in* addr4 = (sockaddr_in*)endPoint.Data; + sockaddr_in6* addr6 = (sockaddr_in6*)pv6.Data; + const SCOPE_ID scope = SCOPEID_UNSPECIFIED_INIT; + + // Can be replaced by windows built-in macro IN6ADDR_SETV4MAPPED() + memset(addr6, 0, sizeof sockaddr_in6); + addr6->sin6_family = AF_INET6; + addr6->sin6_scope_struct = scope; + addr6->sin6_addr = v4MappedPrefix; + addr6->sin6_port = addr4->sin_port; + memcpy(&addr6->sin6_addr.u.Byte[12], &addr4->sin_addr, 4); // :::::FFFF:XXXX:XXXX X=IPv4 + pv6.IPVersion = NetworkIPVersion::IPv6; + pv6.Port = endPoint.Port; + + return pv6; +} diff --git a/Source/Engine/Platform/Win32/Win32Network.h b/Source/Engine/Platform/Win32/Win32Network.h new file mode 100644 index 000000000..7a1aaabbf --- /dev/null +++ b/Source/Engine/Platform/Win32/Win32Network.h @@ -0,0 +1,38 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#if PLATFORM_WIN32 + +#include "Engine/Platform/Base/NetworkBase.h" + +class FLAXENGINE_API Win32Network : public NetworkBase +{ + friend NetworkEndPoint; + friend NetworkSocket; + +public: + // [NetworkBase] + static bool CreateSocket(NetworkSocket& socket, NetworkProtocol proto, NetworkIPVersion ipv); + static bool DestroySocket(NetworkSocket& socket); + static bool SetSocketOption(NetworkSocket& socket, NetworkSocketOption option, bool value); + static bool SetSocketOption(NetworkSocket& socket, NetworkSocketOption option, int32 value); + static bool GetSocketOption(NetworkSocket& socket, NetworkSocketOption option, bool* value); + static bool GetSocketOption(NetworkSocket& socket, NetworkSocketOption option, int32* value); + static bool ConnectSocket(NetworkSocket& socket, NetworkEndPoint& endPoint); + static bool BindSocket(NetworkSocket& socket, NetworkEndPoint& endPoint); + static bool Listen(NetworkSocket& socket, uint16 queueSize); + static bool Accept(NetworkSocket& serverSock, NetworkSocket& newSock, NetworkEndPoint& newEndPoint); + static bool IsReadable(NetworkSocket& socket); + static bool IsWriteable(NetworkSocket& socket); + static int32 Poll(NetworkSocketGroup& group); + static bool GetSocketState(NetworkSocketGroup& group, uint32 index, NetworkSocketState& state); + static int32 AddSocketToGroup(NetworkSocketGroup& group, NetworkSocket& socket); + static void ClearGroup(NetworkSocketGroup& group); + static int32 WriteSocket(NetworkSocket socket, byte* data, uint32 length, NetworkEndPoint* endPoint = nullptr); + static int32 ReadSocket(NetworkSocket socket, byte* buffer, uint32 bufferSize, NetworkEndPoint* endPoint = nullptr); + static bool CreateEndPoint(String* address, String* port, NetworkIPVersion ipv, NetworkEndPoint& endPoint, bool bindable = false); + static NetworkEndPoint RemapEndPointToIPv6(NetworkEndPoint endPoint); +}; + +#endif diff --git a/Source/Engine/Platform/Win32/Win32Platform.cpp b/Source/Engine/Platform/Win32/Win32Platform.cpp index 129ff4249..b01b96feb 100644 --- a/Source/Engine/Platform/Win32/Win32Platform.cpp +++ b/Source/Engine/Platform/Win32/Win32Platform.cpp @@ -9,8 +9,9 @@ #include "Engine/Core/Types/Guid.h" #include "Engine/Core/Types/String.h" #include "Engine/Core/Math/Math.h" -#include "IncludeWindowsHeaders.h" #include "Engine/Core/Collections/HashFunctions.h" +#include "Engine/Core/Log.h" +#include "IncludeWindowsHeaders.h" #include #include @@ -25,6 +26,7 @@ namespace CPUInfo CpuInfo; uint64 ClockFrequency; double CyclesToSeconds; + WSAData WsaData; } // Helper function to count set bits in the processor mask @@ -44,6 +46,18 @@ DWORD CountSetBits(ULONG_PTR bitMask) return bitSetCount; } +static String GetLastErrorMessage() +{ + wchar_t* s = nullptr; + FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, WSAGetLastError(), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + reinterpret_cast(&s), 0, nullptr); + String str(s); + LocalFree(s); + return str; +} + bool Win32Platform::Init() { if (PlatformBase::Init()) @@ -56,6 +70,7 @@ bool Win32Platform::Init() ClockFrequency = frequency.QuadPart; CyclesToSeconds = 1.0 / static_cast(frequency.QuadPart); + // Count CPUs BOOL done = FALSE; PSYSTEM_LOGICAL_PROCESSOR_INFORMATION buffer = nullptr; PSYSTEM_LOGICAL_PROCESSOR_INFORMATION ptr; @@ -68,12 +83,10 @@ bool Win32Platform::Init() DWORD processorPackageCount = 0; DWORD byteOffset = 0; PCACHE_DESCRIPTOR cache; - while (!done) { DWORD rc = GetLogicalProcessorInformation(buffer, &returnLength); - - if (FALSE == rc) + if (rc == FALSE) { if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) { @@ -81,9 +94,7 @@ bool Win32Platform::Init() { free(buffer); } - buffer = static_cast(malloc(returnLength)); - if (buffer == nullptr) { return true; @@ -99,23 +110,16 @@ bool Win32Platform::Init() done = TRUE; } } - ptr = buffer; - while (byteOffset + sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION) <= returnLength) { switch (ptr->Relationship) { case RelationProcessorCore: - processorCoreCount++; - - // A hyper threaded core supplies more than one logical processor logicalProcessorCount += CountSetBits(ptr->ProcessorMask); break; - case RelationCache: - // Cache data is in ptr->Cache, one CACHE_DESCRIPTOR structure for each cache. cache = &ptr->Cache; if (cache->Level == 1) { @@ -130,9 +134,7 @@ bool Win32Platform::Init() processorL3CacheSize += cache->Size; } break; - case RelationProcessorPackage: - // Logical processors share a physical package processorPackageCount++; break; } @@ -212,24 +214,22 @@ bool Win32Platform::Init() DeviceId.D = (uint32)cpuInfo.ClockSpeed * cpuInfo.LogicalProcessorCount * cpuInfo.ProcessorCoreCount * cpuInfo.CacheLineSize; } + // Init networking + if (WSAStartup(MAKEWORD(2, 0), &WsaData) != 0) + LOG(Error, "Unable to initializes native network! Error : {0}", GetLastErrorMessage()); + return false; } +void Win32Platform::Exit() +{ + WSACleanup(); +} + void Win32Platform::MemoryBarrier() { - // NOTE: _ReadWriteBarrier and friends only prevent the - // compiler from reordering loads and stores. To prevent - // the CPU from doing the same, we have to use the - // MemoryBarrier macro which expands to e.g. a serializing - // XCHG instruction on x86. Also note that the MemoryBarrier - // macro does *not* imply _ReadWriteBarrier, so that call - // cannot be eliminated. - _ReadWriteBarrier(); - - // MemoryBarrier macro (we use undef to hide some symbols from Windows headers) #if PLATFORM_64BITS - #ifdef _AMD64_ __faststorefence(); #elif defined(_IA64_) @@ -237,7 +237,6 @@ void Win32Platform::MemoryBarrier() #else #error "Invalid platform." #endif - #else LONG barrier; __asm { diff --git a/Source/Engine/Platform/Win32/Win32Platform.h b/Source/Engine/Platform/Win32/Win32Platform.h index b6a4cdd62..cde506278 100644 --- a/Source/Engine/Platform/Win32/Win32Platform.h +++ b/Source/Engine/Platform/Win32/Win32Platform.h @@ -16,6 +16,7 @@ public: // [PlatformBase] static bool Init(); + static void Exit(); static void MemoryBarrier(); static int64 InterlockedExchange(int64 volatile* dst, int64 exchange); static int32 InterlockedCompareExchange(int32 volatile* dst, int32 exchange, int32 comperand); diff --git a/Source/Engine/Platform/Windows/WindowsPlatform.cpp b/Source/Engine/Platform/Windows/WindowsPlatform.cpp index cb9999a08..e01ef7a44 100644 --- a/Source/Engine/Platform/Windows/WindowsPlatform.cpp +++ b/Source/Engine/Platform/Windows/WindowsPlatform.cpp @@ -624,6 +624,8 @@ void WindowsPlatform::Exit() // Unregister app class UnregisterClassW(ApplicationWindowClass, nullptr); + + Win32Platform::Exit(); } #if !BUILD_RELEASE diff --git a/Source/Engine/Platform/Windows/WindowsPlatformSettings.h b/Source/Engine/Platform/Windows/WindowsPlatformSettings.h index 454c7d708..48a39444a 100644 --- a/Source/Engine/Platform/Windows/WindowsPlatformSettings.h +++ b/Source/Engine/Platform/Windows/WindowsPlatformSettings.h @@ -9,84 +9,85 @@ /// /// Windows platform settings. /// -/// -class WindowsPlatformSettings : public Settings +API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API WindowsPlatformSettings : public SettingsBase { +DECLARE_SCRIPTING_TYPE_MINIMAL(WindowsPlatformSettings); public: /// /// The default game window mode. /// + API_FIELD(Attributes="EditorOrder(10), DefaultValue(GameWindowMode.Windowed), EditorDisplay(\"Window\")") GameWindowMode WindowMode = GameWindowMode::Windowed; /// /// The default game window width (in pixels). /// + API_FIELD(Attributes="EditorOrder(20), DefaultValue(1280), EditorDisplay(\"Window\")") int32 ScreenWidth = 1280; /// /// The default game window height (in pixels). /// + API_FIELD(Attributes="EditorOrder(30), DefaultValue(720), EditorDisplay(\"Window\")") int32 ScreenHeight = 720; - /// - /// Enables game running when application window loses focus. - /// - bool RunInBackground = false; - /// /// Enables resizing the game window by the user. /// + API_FIELD(Attributes="EditorOrder(40), DefaultValue(false), EditorDisplay(\"Window\")") bool ResizableWindow = false; + /// + /// Enables game running when application window loses focus. + /// + API_FIELD(Attributes="EditorOrder(1010), DefaultValue(false), EditorDisplay(\"Other\", \"Run In Background\")") + bool RunInBackground = false; + /// /// Limits maximum amount of concurrent game instances running to one, otherwise user may launch application more than once. /// + API_FIELD(Attributes="EditorOrder(1020), DefaultValue(false), EditorDisplay(\"Other\")") bool ForceSingleInstance = false; /// /// Custom icon texture (asset id) to use for the application (overrides the default one). /// + API_FIELD(Attributes="EditorOrder(1030), CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.AssetRefEditor\"), AssetReference(typeof(Texture)), EditorDisplay(\"Other\")") Guid OverrideIcon; /// /// Enables support for DirectX 12. Disabling it reduces compiled shaders count. /// + API_FIELD(Attributes="EditorOrder(2000), DefaultValue(false), EditorDisplay(\"Graphics\", \"Support DirectX 12\")") bool SupportDX12 = false; /// /// Enables support for DirectX 11. Disabling it reduces compiled shaders count. /// + API_FIELD(Attributes="EditorOrder(2010), DefaultValue(true), EditorDisplay(\"Graphics\", \"Support DirectX 11\")") bool SupportDX11 = true; /// /// Enables support for DirectX 10 and DirectX 10.1. Disabling it reduces compiled shaders count. /// + API_FIELD(Attributes="EditorOrder(2020), DefaultValue(false), EditorDisplay(\"Graphics\", \"Support DirectX 10\")") bool SupportDX10 = false; /// /// Enables support for Vulkan. Disabling it reduces compiled shaders count. /// + API_FIELD(Attributes="EditorOrder(2030), DefaultValue(false), EditorDisplay(\"Graphics\")") bool SupportVulkan = false; public: - // [Settings] - void RestoreDefault() final override - { - WindowMode = GameWindowMode::Windowed; - ScreenWidth = 1280; - ScreenHeight = 720; - RunInBackground = false; - ResizableWindow = false; - ForceSingleInstance = false; - OverrideIcon = Guid::Empty; - SupportDX12 = false; - SupportDX11 = true; - SupportDX10 = false; - SupportVulkan = false; - } + /// + /// Gets the instance of the settings asset (default value if missing). Object returned by this method is always loaded with valid data to use. + /// + static WindowsPlatformSettings* Get(); + // [SettingsBase] void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override { DESERIALIZE(WindowMode); diff --git a/Source/Engine/Platform/Windows/WindowsWindow.cpp b/Source/Engine/Platform/Windows/WindowsWindow.cpp index 61c8737b8..2d60cc40a 100644 --- a/Source/Engine/Platform/Windows/WindowsWindow.cpp +++ b/Source/Engine/Platform/Windows/WindowsWindow.cpp @@ -426,8 +426,8 @@ void WindowsWindow::GetScreenInfo(int32& x, int32& y, int32& width, int32& heigh // Calculate result x = monitorInfo.rcMonitor.left; y = monitorInfo.rcMonitor.top; - width = monitorInfo.rcMonitor.right - x; - height = monitorInfo.rcMonitor.bottom - y; + width = monitorInfo.rcMonitor.right - monitorInfo.rcMonitor.left; + height = monitorInfo.rcMonitor.bottom - monitorInfo.rcMonitor.top; } float WindowsWindow::GetOpacity() const @@ -481,7 +481,7 @@ void WindowsWindow::StartTrackingMouse(bool useMouseScreenOffset) int32 x = 0 , y = 0, width = 0, height = 0; GetScreenInfo(x, y, width, height); - _mouseOffsetScreenSize = Rectangle(x, y, width, height); + _mouseOffsetScreenSize = Rectangle((float)x, (float)y, (float)width, (float)height); SetCapture(_handle); } diff --git a/Source/Engine/Render2D/Render2D.cpp b/Source/Engine/Render2D/Render2D.cpp index c956566ca..6e6936cc5 100644 --- a/Source/Engine/Render2D/Render2D.cpp +++ b/Source/Engine/Render2D/Render2D.cpp @@ -525,7 +525,7 @@ void Render2DService::Dispose() Lines.Resize(0); Lines2.Resize(0); - GUIShader.Unlink(); + GUIShader = nullptr; PsoDepth.Dispose(); PsoNoDepth.Dispose(); diff --git a/Source/Engine/Renderer/AmbientOcclusionPass.cpp b/Source/Engine/Renderer/AmbientOcclusionPass.cpp index dfae20792..e72de509b 100644 --- a/Source/Engine/Renderer/AmbientOcclusionPass.cpp +++ b/Source/Engine/Renderer/AmbientOcclusionPass.cpp @@ -196,7 +196,7 @@ void AmbientOcclusionPass::Dispose() SAFE_DELETE_GPU_RESOURCE(_psApplyHalf); // Release asset - _shader.Unlink(); + _shader = nullptr; } void AmbientOcclusionPass::Render(RenderContext& renderContext) diff --git a/Source/Engine/Renderer/AntiAliasing/FXAA.cpp b/Source/Engine/Renderer/AntiAliasing/FXAA.cpp index 159e481a6..3dcb6ae71 100644 --- a/Source/Engine/Renderer/AntiAliasing/FXAA.cpp +++ b/Source/Engine/Renderer/AntiAliasing/FXAA.cpp @@ -55,11 +55,9 @@ void FXAA::Dispose() // Base RendererPass::Dispose(); - // Delete pipeline state + // Cleanup _psFXAA.Delete(); - - // Release asset - _shader.Unlink(); + _shader = nullptr; } void FXAA::Render(RenderContext& renderContext, GPUTexture* input, GPUTextureView* output) diff --git a/Source/Engine/Renderer/AntiAliasing/SMAA.cpp b/Source/Engine/Renderer/AntiAliasing/SMAA.cpp index cf7e53f40..7c1ee0c16 100644 --- a/Source/Engine/Renderer/AntiAliasing/SMAA.cpp +++ b/Source/Engine/Renderer/AntiAliasing/SMAA.cpp @@ -82,15 +82,13 @@ void SMAA::Dispose() // Base RendererPass::Dispose(); - // Delete pipeline states + // Cleanup _psEdge.Delete(); _psBlend.Delete(); SAFE_DELETE_GPU_RESOURCE(_psNeighbor); - - // Release assets - _shader.Unlink(); - _areaTex.Unlink(); - _searchTex.Unlink(); + _shader = nullptr; + _areaTex = nullptr; + _searchTex = nullptr; } void SMAA::Render(RenderContext& renderContext, GPUTexture* input, GPUTextureView* output) diff --git a/Source/Engine/Renderer/AntiAliasing/TAA.cpp b/Source/Engine/Renderer/AntiAliasing/TAA.cpp index 7b8dfe17f..a1fee93ad 100644 --- a/Source/Engine/Renderer/AntiAliasing/TAA.cpp +++ b/Source/Engine/Renderer/AntiAliasing/TAA.cpp @@ -42,8 +42,9 @@ void TAA::Dispose() // Base RendererPass::Dispose(); + // Cleanup _psTAA = nullptr; - _shader.Unlink(); + _shader = nullptr; } bool TAA::NeedMotionVectors(RenderContext& renderContext) diff --git a/Source/Engine/Renderer/AtmospherePreCompute.cpp b/Source/Engine/Renderer/AtmospherePreCompute.cpp index 66576e55f..cef9964ca 100644 --- a/Source/Engine/Renderer/AtmospherePreCompute.cpp +++ b/Source/Engine/Renderer/AtmospherePreCompute.cpp @@ -305,7 +305,7 @@ void release() SAFE_DELETE_GPU_RESOURCE(_psInscatterS); SAFE_DELETE_GPU_RESOURCE(_psInscatterN); SAFE_DELETE_GPU_RESOURCE(_psCopyInscatterNAdd); - _shader.Unlink(); + _shader = nullptr; SAFE_DELETE(_task); SAFE_DELETE_GPU_RESOURCE(AtmosphereTransmittance); SAFE_DELETE_GPU_RESOURCE(AtmosphereIrradiance); diff --git a/Source/Engine/Renderer/ColorGradingPass.cpp b/Source/Engine/Renderer/ColorGradingPass.cpp index 94caf2b46..c40248caa 100644 --- a/Source/Engine/Renderer/ColorGradingPass.cpp +++ b/Source/Engine/Renderer/ColorGradingPass.cpp @@ -121,11 +121,9 @@ void ColorGradingPass::Dispose() // Base RendererPass::Dispose(); - // Delete pipeline state + // Cleanup _psLut.Delete(); - - // Release assets - _shader.Unlink(); + _shader = nullptr; } GPUTexture* ColorGradingPass::RenderLUT(RenderContext& renderContext) diff --git a/Source/Engine/Renderer/DepthOfFieldPass.cpp b/Source/Engine/Renderer/DepthOfFieldPass.cpp index b5382ec72..4e4ee7ed1 100644 --- a/Source/Engine/Renderer/DepthOfFieldPass.cpp +++ b/Source/Engine/Renderer/DepthOfFieldPass.cpp @@ -66,21 +66,17 @@ void DepthOfFieldPass::Dispose() // Base RendererPass::Dispose(); - // Delete pipeline states + // Cleanup SAFE_DELETE_GPU_RESOURCE(_psDofDepthBlurGeneration); SAFE_DELETE_GPU_RESOURCE(_psBokehGeneration); SAFE_DELETE_GPU_RESOURCE(_psDoNotGenerateBokeh); SAFE_DELETE_GPU_RESOURCE(_psBokeh); SAFE_DELETE_GPU_RESOURCE(_psBokehComposite); - - // Release assets - _shader.Unlink(); - _defaultBokehHexagon.Unlink(); - _defaultBokehOctagon.Unlink(); - _defaultBokehCircle.Unlink(); - _defaultBokehCross.Unlink(); - - // Release resources + _shader = nullptr; + _defaultBokehHexagon = nullptr; + _defaultBokehOctagon = nullptr; + _defaultBokehCircle = nullptr; + _defaultBokehCross = nullptr; SAFE_DELETE_GPU_RESOURCE(_bokehBuffer); SAFE_DELETE_GPU_RESOURCE(_bokehIndirectArgsBuffer); } diff --git a/Source/Engine/Renderer/DrawCall.h b/Source/Engine/Renderer/DrawCall.h index 7c4f4fe6d..036ba26bd 100644 --- a/Source/Engine/Renderer/DrawCall.h +++ b/Source/Engine/Renderer/DrawCall.h @@ -113,6 +113,11 @@ public: /// struct DrawCall { + /// + /// The material to use for rendering. + /// + IMaterial* Material; + struct { /// @@ -129,16 +134,6 @@ struct DrawCall /// The geometry vertex buffers byte offsets. /// uint32 VertexBuffersOffsets[3]; - - /// - /// The location of the first index read by the GPU from the index buffer. - /// - int32 StartIndex; - - /// - /// The indices count. - /// - int32 IndicesCount; } Geometry; /// @@ -146,66 +141,58 @@ struct DrawCall /// int32 InstanceCount; - /// - /// The indirect draw arguments offset. - /// - uint32 IndirectArgsOffset; - - /// - /// The indirect draw arguments buffer. - /// - GPUBuffer* IndirectArgsBuffer; - - /// - /// The target material to use. - /// - IMaterial* Material; - - // Particles don't use skinning nor lightmaps so pack those stuff together union { struct { /// - /// Pointer to lightmap for static object with prebaked lighting. + /// The location of the first index read by the GPU from the index buffer. /// + int32 StartIndex; + + /// + /// The indices count. + /// + int32 IndicesCount; + }; + + struct + { + /// + /// The indirect draw arguments offset. + /// + uint32 IndirectArgsOffset; + + /// + /// The indirect draw arguments buffer. + /// + GPUBuffer* IndirectArgsBuffer; + }; + } Draw; + + // Per-material shader data packed into union + union + { + struct + { const Lightmap* Lightmap; + Rectangle LightmapUVsArea; + } Features; - /// - /// The skinning data. If set then material should use GPU skinning during rendering. - /// + struct + { + const Lightmap* Lightmap; + Rectangle LightmapUVsArea; SkinnedMeshDrawData* Skinning; - }; - - struct - { - /// - /// The particles data. Used only by the particles shaders. - /// - class ParticleBuffer* Particles; - - /// - /// The particle module to draw. - /// - class ParticleEmitterGraphCPUNode* Module; - }; - }; - - /// - /// Object world transformation matrix. - /// - Matrix World; - - // Terrain and particles don't use previous world matrix so pack those stuff together - union - { - /// - /// Object world transformation matrix using during previous frame. - /// - Matrix PrevWorld; + Vector3 GeometrySize; // Object geometry size in the world (unscaled). + float LODDitherFactor; // The model LOD transition dither progress. + Matrix PrevWorld; + } Surface; struct { + const Lightmap* Lightmap; + Rectangle LightmapUVsArea; Vector4 HeightmapUVScaleBias; Vector4 NeighborLOD; Vector2 OffsetUV; @@ -213,25 +200,47 @@ struct DrawCall float ChunkSizeNextLOD; float TerrainChunkSizeLOD0; const class TerrainPatch* Patch; - } TerrainData; + } Terrain; struct { - int32 RibbonOrderOffset; - float UVTilingDistance; - float UVScaleX; - float UVScaleY; - float UVOffsetX; - float UVOffsetY; - uint32 SegmentCount; - GPUBuffer* SegmentDistances; - } Ribbon; + class ParticleBuffer* Particles; + class ParticleEmitterGraphCPUNode* Module; + + struct + { + int32 OrderOffset; + float UVTilingDistance; + float UVScaleX; + float UVScaleY; + float UVOffsetX; + float UVOffsetY; + uint32 SegmentCount; + GPUBuffer* SegmentDistances; + } Ribbon; + } Particle; + + struct + { + GPUBuffer* SplineDeformation; + Matrix LocalMatrix; // Geometry transformation applied before deformation. + Vector3 GeometrySize; // Object geometry size in the world (unscaled). + float Segment; + float ChunksPerSegment; + float MeshMinZ; + float MeshMaxZ; + } Deformable; + + struct + { + byte Raw[96]; + } Custom; }; /// - /// Lightmap UVs area that entry occupies. + /// Object world transformation matrix. /// - Rectangle LightmapUVsArea; + Matrix World; /// /// Object location in the world used for draw calls sorting. @@ -243,36 +252,17 @@ struct DrawCall /// float WorldDeterminantSign; - /// - /// Object geometry size in the world (unscaled). - /// - Vector3 GeometrySize; - /// /// The random per-instance value (normalized to range 0-1). /// float PerInstanceRandom; - /// - /// The model LOD transition dither factor. - /// - float LODDitherFactor; - /// /// Does nothing. /// DrawCall() { } - - /// - /// Determines whether world transform matrix is performing negative scale (then model culling should be inverted). - /// - /// true if world matrix contains negative scale; otherwise, false. - FORCE_INLINE bool IsNegativeScale() const - { - return WorldDeterminantSign < 0; - } }; template<> @@ -333,6 +323,6 @@ struct TIsPODType #define GEOMETRY_DRAW_STATE_EVENT_END(drawState, worldMatrix) \ if (drawState.PrevFrame != frame) \ { \ - drawState.PrevWorld = _world; \ + drawState.PrevWorld = worldMatrix; \ drawState.PrevFrame = frame; \ } diff --git a/Source/Engine/Renderer/Editor/LightmapUVsDensity.cpp b/Source/Engine/Renderer/Editor/LightmapUVsDensity.cpp index 152b43d50..9ba9f1a0d 100644 --- a/Source/Engine/Renderer/Editor/LightmapUVsDensity.cpp +++ b/Source/Engine/Renderer/Editor/LightmapUVsDensity.cpp @@ -178,7 +178,7 @@ void LightmapUVsDensityMaterialShader::Bind(BindParameters& params) scaleZ > 0.00001f ? 1.0f / scaleZ : 0.0f); data.LightmapTexelsPerWorldUnit = ShadowsOfMordor::LightmapTexelsPerWorldUnit; data.LightmapSize = 1024.0f; - data.LightmapArea = drawCall.LightmapUVsArea; + data.LightmapArea = drawCall.Surface.LightmapUVsArea; if (drawCallModel) { // Calculate current lightmap slot size for the object (matches the ShadowsOfMordor calculations when baking the lighting) diff --git a/Source/Engine/Renderer/EyeAdaptationPass.cpp b/Source/Engine/Renderer/EyeAdaptationPass.cpp index a85e0a72b..3bfbb9daa 100644 --- a/Source/Engine/Renderer/EyeAdaptationPass.cpp +++ b/Source/Engine/Renderer/EyeAdaptationPass.cpp @@ -244,15 +244,13 @@ void EyeAdaptationPass::Dispose() // Base RendererPass::Dispose(); - // Delete pipeline states + // Cleanup SAFE_DELETE_GPU_RESOURCE(_psManual); SAFE_DELETE_GPU_RESOURCE(_psLuminanceMap); SAFE_DELETE_GPU_RESOURCE(_psBlendLuminance); SAFE_DELETE_GPU_RESOURCE(_psApplyLuminance); SAFE_DELETE_GPU_RESOURCE(_psHistogram); - - // Release asset - _shader.Unlink(); + _shader = nullptr; } bool EyeAdaptationPass::setupResources() diff --git a/Source/Engine/Renderer/ForwardPass.cpp b/Source/Engine/Renderer/ForwardPass.cpp index ad178fe67..93ccf0750 100644 --- a/Source/Engine/Renderer/ForwardPass.cpp +++ b/Source/Engine/Renderer/ForwardPass.cpp @@ -62,11 +62,9 @@ void ForwardPass::Dispose() // Base RendererPass::Dispose(); - // Delete pipeline states + // Cleanup SAFE_DELETE_GPU_RESOURCE(_psApplyDistortion); - - // Release assets - _shader.Unlink(); + _shader = nullptr; } void ForwardPass::Render(RenderContext& renderContext, GPUTexture* input, GPUTexture* output) diff --git a/Source/Engine/Renderer/GBufferPass.cpp b/Source/Engine/Renderer/GBufferPass.cpp index 5c6f76905..2165e25dc 100644 --- a/Source/Engine/Renderer/GBufferPass.cpp +++ b/Source/Engine/Renderer/GBufferPass.cpp @@ -77,14 +77,11 @@ void GBufferPass::Dispose() // Base RendererPass::Dispose(); - // Delete pipeline state + // Cleanup SAFE_DELETE_GPU_RESOURCE(_psDebug); - - // Release assets - _gBufferShader.Unlink(); - _skyModel.Unlink(); - _boxModel.Unlink(); - + _gBufferShader = nullptr; + _skyModel = nullptr; + _boxModel = nullptr; #if USE_EDITOR SAFE_DELETE(_lightmapUVsDensityMaterialShader); SAFE_DELETE(_vertexColorsMaterialShader); @@ -145,7 +142,8 @@ void GBufferPass::Fill(RenderContext& renderContext, GPUTextureView* lightBuffer drawCall.Material = _lightmapUVsDensityMaterialShader; } } - if (!_lightmapUVsDensityMaterialShader->CanUseInstancing()) + IMaterial::InstancingHandler handler; + if (!_lightmapUVsDensityMaterialShader->CanUseInstancing(handler)) { drawCallsList.CanUseInstancing = false; } @@ -166,7 +164,8 @@ void GBufferPass::Fill(RenderContext& renderContext, GPUTextureView* lightBuffer drawCall.Material = _vertexColorsMaterialShader; } } - if (!_vertexColorsMaterialShader->CanUseInstancing()) + IMaterial::InstancingHandler handler; + if (!_vertexColorsMaterialShader->CanUseInstancing(handler)) { drawCallsList.CanUseInstancing = false; } @@ -311,8 +310,6 @@ void GBufferPass::DrawDecals(RenderContext& renderContext, GPUTextureView* light DrawCall drawCall; MaterialBase::BindParameters bindParams(gpuContext, renderContext, drawCall); drawCall.Material = nullptr; - drawCall.Lightmap = nullptr; - drawCall.Skinning = nullptr; drawCall.WorldDeterminantSign = 1.0f; // Draw all decals diff --git a/Source/Engine/Renderer/HistogramPass.cpp b/Source/Engine/Renderer/HistogramPass.cpp index fe16dbd19..e8bfc9fad 100644 --- a/Source/Engine/Renderer/HistogramPass.cpp +++ b/Source/Engine/Renderer/HistogramPass.cpp @@ -102,8 +102,9 @@ void HistogramPass::Dispose() // Base RendererPass::Dispose(); + // Cleanup SAFE_DELETE_GPU_RESOURCE(_histogramBuffer); - _shader.Unlink(); + _shader = nullptr; } bool HistogramPass::setupResources() diff --git a/Source/Engine/Renderer/LightPass.cpp b/Source/Engine/Renderer/LightPass.cpp index d021ede01..c79033c37 100644 --- a/Source/Engine/Renderer/LightPass.cpp +++ b/Source/Engine/Renderer/LightPass.cpp @@ -164,7 +164,7 @@ void LightPass::Dispose() // Base RendererPass::Dispose(); - // Delete pipeline states + // Cleanup _psLightDir.Delete(); _psLightPointNormal.Delete(); _psLightPointInverted.Delete(); @@ -172,9 +172,7 @@ void LightPass::Dispose() _psLightSpotInverted.Delete(); SAFE_DELETE_GPU_RESOURCE(_psLightSkyNormal); SAFE_DELETE_GPU_RESOURCE(_psLightSkyInverted); - - // Release assets - _sphereModel.Unlink(); + _sphereModel = nullptr; } void LightPass::RenderLight(RenderContext& renderContext, GPUTextureView* lightBuffer) diff --git a/Source/Engine/Renderer/MotionBlurPass.cpp b/Source/Engine/Renderer/MotionBlurPass.cpp index f10ef442b..689916708 100644 --- a/Source/Engine/Renderer/MotionBlurPass.cpp +++ b/Source/Engine/Renderer/MotionBlurPass.cpp @@ -134,16 +134,14 @@ void MotionBlurPass::Dispose() // Base RendererPass::Dispose(); - // Delete pipeline state + // Cleanup SAFE_DELETE_GPU_RESOURCE(_psCameraMotionVectors); SAFE_DELETE_GPU_RESOURCE(_psMotionVectorsDebug); SAFE_DELETE_GPU_RESOURCE(_psTileMax); SAFE_DELETE_GPU_RESOURCE(_psTileMaxVariable); SAFE_DELETE_GPU_RESOURCE(_psNeighborMax); SAFE_DELETE_GPU_RESOURCE(_psMotionBlur); - - // Release asset - _shader.Unlink(); + _shader = nullptr; } void MotionBlurPass::RenderMotionVectors(RenderContext& renderContext) diff --git a/Source/Engine/Renderer/PostProcessingPass.cpp b/Source/Engine/Renderer/PostProcessingPass.cpp index 02469aa81..4e5b4d4fb 100644 --- a/Source/Engine/Renderer/PostProcessingPass.cpp +++ b/Source/Engine/Renderer/PostProcessingPass.cpp @@ -167,19 +167,17 @@ void PostProcessingPass::Dispose() // Base RendererPass::Dispose(); - // Delete pipeline states + // Cleanup SAFE_DELETE_GPU_RESOURCE(_psThreshold); SAFE_DELETE_GPU_RESOURCE(_psScale); SAFE_DELETE_GPU_RESOURCE(_psBlurH); SAFE_DELETE_GPU_RESOURCE(_psBlurV); SAFE_DELETE_GPU_RESOURCE(_psGenGhosts); _psComposite.Delete(); - - // Release assets - _shader.Unlink(); - _defaultLensColor.Unlink(); - _defaultLensDirt.Unlink(); - _defaultLensStar.Unlink(); + _shader = nullptr; + _defaultLensColor = nullptr; + _defaultLensDirt = nullptr; + _defaultLensStar = nullptr; } void PostProcessingPass::Render(RenderContext& renderContext, GPUTexture* input, GPUTexture* output, GPUTexture* colorGradingLUT) diff --git a/Source/Engine/Renderer/ProbesRenderer.cpp b/Source/Engine/Renderer/ProbesRenderer.cpp index 6688c4716..3f48f8c6e 100644 --- a/Source/Engine/Renderer/ProbesRenderer.cpp +++ b/Source/Engine/Renderer/ProbesRenderer.cpp @@ -333,7 +333,7 @@ void ProbesRenderer::Release() SAFE_DELETE_GPU_RESOURCE(_psAccDiffuseIrradiance); SAFE_DELETE_GPU_RESOURCE(_psAccumulateCubeFaces); SAFE_DELETE_GPU_RESOURCE(_psCopyFrameLHB); - _shader.Unlink(); + _shader = nullptr; SAFE_DELETE_GPU_RESOURCE(_output); SAFE_DELETE(_task); SAFE_DELETE_GPU_RESOURCE(_probe); diff --git a/Source/Engine/Renderer/ReflectionsPass.cpp b/Source/Engine/Renderer/ReflectionsPass.cpp index 40f3aeb30..c3108cf0d 100644 --- a/Source/Engine/Renderer/ReflectionsPass.cpp +++ b/Source/Engine/Renderer/ReflectionsPass.cpp @@ -324,15 +324,13 @@ void ReflectionsPass::Dispose() // Base RendererPass::Dispose(); - // Delete pipeline states + // Cleanup SAFE_DELETE_GPU_RESOURCE(_psProbeNormal); SAFE_DELETE_GPU_RESOURCE(_psProbeInverted); SAFE_DELETE_GPU_RESOURCE(_psCombinePass); - - // Release assets - _shader.Unlink(); - _sphereModel.Unlink(); - _preIntegratedGF.Unlink(); + _shader = nullptr; + _sphereModel = nullptr; + _preIntegratedGF = nullptr; } bool sortProbes(EnvironmentProbe* const& p1, EnvironmentProbe* const& p2) diff --git a/Source/Engine/Renderer/RenderList.cpp b/Source/Engine/Renderer/RenderList.cpp index c78931416..7056e8737 100644 --- a/Source/Engine/Renderer/RenderList.cpp +++ b/Source/Engine/Renderer/RenderList.cpp @@ -19,6 +19,11 @@ #define BATCH_KEY_BITS 32 #define BATCH_KEY_MASK ((1 << BATCH_KEY_BITS) - 1) +static_assert(sizeof(DrawCall) <= 280, "Too big draw call data size."); +static_assert(sizeof(DrawCall::Surface) >= sizeof(DrawCall::Terrain), "Wrong draw call data size."); +static_assert(sizeof(DrawCall::Surface) >= sizeof(DrawCall::Particle), "Wrong draw call data size."); +static_assert(sizeof(DrawCall::Surface) >= sizeof(DrawCall::Custom), "Wrong draw call data size."); + namespace { // Cached data for the draw calls sorting @@ -495,24 +500,24 @@ end: } } -/// -/// Checks if this draw call be batched together with the other one. -/// -/// The first draw call. -/// The second draw call. -/// True if can merge them, otherwise false. -FORCE_INLINE bool CanBatchWith(const DrawCall& a, const DrawCall& b) +namespace { - return Platform::MemoryCompare(&a.Geometry, &b.Geometry, sizeof(a.Geometry)) == 0 && - a.Material == b.Material && - a.Lightmap == b.Lightmap && - // TODO: add batch.CanBatch flag computed in AddDrawCall to remove those checks here for Skinning and IndirectDrawArgs - a.Skinning == nullptr && - b.Skinning == nullptr && - a.IndirectArgsBuffer == nullptr && - b.IndirectArgsBuffer == nullptr && - a.WorldDeterminantSign == b.WorldDeterminantSign && - a.Material->CanUseInstancing(); + /// + /// Checks if this draw call be batched together with the other one. + /// + /// The first draw call. + /// The second draw call. + /// True if can merge them, otherwise false. + FORCE_INLINE bool CanBatchWith(const DrawCall& a, const DrawCall& b) + { + IMaterial::InstancingHandler handler; + return a.Material == b.Material && + a.Material->CanUseInstancing(handler) && + Platform::MemoryCompare(&a.Geometry, &b.Geometry, sizeof(a.Geometry)) == 0 && + a.InstanceCount != 0 && + b.InstanceCount != 0 && + a.WorldDeterminantSign == b.WorldDeterminantSign; + } } void RenderList::SortDrawCalls(const RenderContext& renderContext, bool reverseDistance, DrawCallsList& list) @@ -540,7 +545,9 @@ void RenderList::SortDrawCalls(const RenderContext& renderContext, bool reverseD batchKey = (batchKey * 397) ^ GetHash(drawCall.Geometry.VertexBuffers[1]); batchKey = (batchKey * 397) ^ GetHash(drawCall.Geometry.VertexBuffers[2]); batchKey = (batchKey * 397) ^ GetHash(drawCall.Material); - batchKey = (batchKey * 397) ^ GetHash(drawCall.Lightmap); + IMaterial::InstancingHandler handler; + if (drawCall.Material->CanUseInstancing(handler)) + handler.GetHash(drawCall, batchKey); batchKey += (int32)(471 * drawCall.WorldDeterminantSign); #if USE_BATCH_KEY_MASK const uint32 batchHashKey = (uint32)batchKey & BATCH_KEY_MASK; @@ -635,14 +642,12 @@ void RenderList::ExecuteDrawCalls(const RenderContext& renderContext, DrawCallsL auto& batch = list.Batches[i]; if (batch.BatchSize > 1) { + IMaterial::InstancingHandler handler; + DrawCalls[list.Indices[batch.StartIndex]].Material->CanUseInstancing(handler); for (int32 j = 0; j < batch.BatchSize; j++) { auto& drawCall = DrawCalls[list.Indices[batch.StartIndex + j]]; - instanceData->InstanceOrigin = Vector4(drawCall.World.M41, drawCall.World.M42, drawCall.World.M43, drawCall.PerInstanceRandom); - instanceData->InstanceTransform1 = Vector4(drawCall.World.M11, drawCall.World.M12, drawCall.World.M13, drawCall.LODDitherFactor); - instanceData->InstanceTransform2 = Vector3(drawCall.World.M21, drawCall.World.M22, drawCall.World.M23); - instanceData->InstanceTransform3 = Vector3(drawCall.World.M31, drawCall.World.M32, drawCall.World.M33); - instanceData->InstanceLightmapArea = Half4(drawCall.LightmapUVsArea); + handler.WriteDrawCall(instanceData, drawCall); instanceData++; } } @@ -685,20 +690,20 @@ DRAW: context->BindIB(drawCall.Geometry.IndexBuffer); - if (drawCall.IndirectArgsBuffer) + if (drawCall.InstanceCount == 0) { // No support for batching indirect draw calls ASSERT(batch.BatchSize == 1); context->BindVB(ToSpan(vb, vbCount), vbOffsets); - context->DrawIndexedInstancedIndirect(drawCall.IndirectArgsBuffer, drawCall.IndirectArgsOffset); + context->DrawIndexedInstancedIndirect(drawCall.Draw.IndirectArgsBuffer, drawCall.Draw.IndirectArgsOffset); } else { if (batch.BatchSize == 1) { context->BindVB(ToSpan(vb, vbCount), vbOffsets); - context->DrawIndexedInstanced(drawCall.Geometry.IndicesCount, batch.InstanceCount, 0, 0, drawCall.Geometry.StartIndex); + context->DrawIndexedInstanced(drawCall.Draw.IndicesCount, batch.InstanceCount, 0, 0, drawCall.Draw.StartIndex); } else { @@ -707,7 +712,7 @@ DRAW: vbOffsets[vbCount] = 0; vbCount++; context->BindVB(ToSpan(vb, vbCount), vbOffsets); - context->DrawIndexedInstanced(drawCall.Geometry.IndicesCount, batch.InstanceCount, instanceBufferOffset, 0, drawCall.Geometry.StartIndex); + context->DrawIndexedInstanced(drawCall.Draw.IndicesCount, batch.InstanceCount, instanceBufferOffset, 0, drawCall.Draw.StartIndex); instanceBufferOffset += batch.BatchSize; } @@ -730,15 +735,38 @@ DRAW: context->BindIB(drawCall.Geometry.IndexBuffer); context->BindVB(ToSpan(drawCall.Geometry.VertexBuffers, 3), drawCall.Geometry.VertexBuffersOffsets); - if (drawCall.IndirectArgsBuffer) + if (drawCall.InstanceCount == 0) { - context->DrawIndexedInstancedIndirect(drawCall.IndirectArgsBuffer, drawCall.IndirectArgsOffset); + context->DrawIndexedInstancedIndirect(drawCall.Draw.IndirectArgsBuffer, drawCall.Draw.IndirectArgsOffset); } else { - context->DrawIndexedInstanced(drawCall.Geometry.IndicesCount, drawCall.InstanceCount, 0, 0, drawCall.Geometry.StartIndex); + context->DrawIndexedInstanced(drawCall.Draw.IndicesCount, drawCall.InstanceCount, 0, 0, drawCall.Draw.StartIndex); } } } } } + +void SurfaceDrawCallHandler::GetHash(const DrawCall& drawCall, int32& batchKey) +{ + batchKey = (batchKey * 397) ^ ::GetHash(drawCall.Surface.Lightmap); +} + +bool SurfaceDrawCallHandler::CanBatch(const DrawCall& a, const DrawCall& b) +{ + return a.Surface.Lightmap == b.Surface.Lightmap && + a.Surface.Skinning == nullptr && + b.Surface.Skinning == nullptr; +} + +void SurfaceDrawCallHandler::WriteDrawCall(InstanceData* instanceData, const DrawCall& drawCall) +{ + instanceData->InstanceOrigin = Vector3(drawCall.World.M41, drawCall.World.M42, drawCall.World.M43); + instanceData->PerInstanceRandom = drawCall.PerInstanceRandom; + instanceData->InstanceTransform1 = Vector3(drawCall.World.M11, drawCall.World.M12, drawCall.World.M13); + instanceData->LODDitherFactor = drawCall.Surface.LODDitherFactor; + instanceData->InstanceTransform2 = Vector3(drawCall.World.M21, drawCall.World.M22, drawCall.World.M23); + instanceData->InstanceTransform3 = Vector3(drawCall.World.M31, drawCall.World.M32, drawCall.World.M33); + instanceData->InstanceLightmapArea = Half4(drawCall.Surface.LightmapUVsArea); +} diff --git a/Source/Engine/Renderer/RenderList.h b/Source/Engine/Renderer/RenderList.h index dffbbd2d1..69ef0c33d 100644 --- a/Source/Engine/Renderer/RenderList.h +++ b/Source/Engine/Renderer/RenderList.h @@ -357,18 +357,6 @@ public: private: - /// - /// Represents data per instance element used for instanced rendering. - /// - struct InstanceData - { - Vector4 InstanceOrigin; // .w contains PerInstanceRandom - Vector4 InstanceTransform1; // .w contains LODDitherFactor - Vector3 InstanceTransform2; - Vector3 InstanceTransform3; - Half4 InstanceLightmapArea; - }; - DynamicVertexBuffer _instanceBuffer; public: @@ -475,3 +463,24 @@ public: /// The collected draw calls list. void ExecuteDrawCalls(const RenderContext& renderContext, DrawCallsList& list); }; + +/// +/// Represents data per instance element used for instanced rendering. +/// +struct FLAXENGINE_API InstanceData +{ + Vector3 InstanceOrigin; + float PerInstanceRandom; + Vector3 InstanceTransform1; + float LODDitherFactor; + Vector3 InstanceTransform2; + Vector3 InstanceTransform3; + Half4 InstanceLightmapArea; +}; + +struct SurfaceDrawCallHandler +{ + static void GetHash(const DrawCall& drawCall, int32& batchKey); + static bool CanBatch(const DrawCall& a, const DrawCall& b); + static void WriteDrawCall(InstanceData* instanceData, const DrawCall& drawCall); +}; diff --git a/Source/Engine/Renderer/Renderer.cpp b/Source/Engine/Renderer/Renderer.cpp index 4d3241375..89efef74c 100644 --- a/Source/Engine/Renderer/Renderer.cpp +++ b/Source/Engine/Renderer/Renderer.cpp @@ -31,9 +31,6 @@ #include "Editor/Editor.h" #endif -// It must use less or the same amount of memory -static_assert(sizeof(DrawCall::TerrainData) <= sizeof(DrawCall::PrevWorld), "Invalid size of the terrain data in the draw call."); - #if USE_EDITOR // Additional options used in editor for lightmaps baking bool IsRunningRadiancePass = false; diff --git a/Source/Engine/Renderer/ScreenSpaceReflectionsPass.cpp b/Source/Engine/Renderer/ScreenSpaceReflectionsPass.cpp index 491f53262..5e81db45c 100644 --- a/Source/Engine/Renderer/ScreenSpaceReflectionsPass.cpp +++ b/Source/Engine/Renderer/ScreenSpaceReflectionsPass.cpp @@ -152,16 +152,14 @@ void ScreenSpaceReflectionsPass::Dispose() // Base RendererPass::Dispose(); - // Delete pipeline states + // Cleanup SAFE_DELETE_GPU_RESOURCE(_psRayTracePass); SAFE_DELETE_GPU_RESOURCE(_psCombinePass); SAFE_DELETE_GPU_RESOURCE(_psTemporalPass); SAFE_DELETE_GPU_RESOURCE(_psMixPass); _psResolvePass.Delete(); - - // Release assets - _shader.Unlink(); - _preIntegratedGF.Unlink(); + _shader = nullptr; + _preIntegratedGF = nullptr; } void ScreenSpaceReflectionsPass::Render(RenderContext& renderContext, GPUTextureView* reflectionsRT, GPUTextureView* lightBuffer) diff --git a/Source/Engine/Renderer/ShadowsPass.cpp b/Source/Engine/Renderer/ShadowsPass.cpp index 11812a45c..4c3260045 100644 --- a/Source/Engine/Renderer/ShadowsPass.cpp +++ b/Source/Engine/Renderer/ShadowsPass.cpp @@ -193,11 +193,12 @@ void ShadowsPass::Dispose() // Base RendererPass::Dispose(); + // Cleanup _psShadowDir.Delete(); _psShadowPoint.Delete(); _psShadowSpot.Delete(); - _shader.Unlink(); - _sphereModel.Unlink(); + _shader = nullptr; + _sphereModel = nullptr; SAFE_DELETE_GPU_RESOURCE(_shadowMapCSM); SAFE_DELETE_GPU_RESOURCE(_shadowMapCube); } diff --git a/Source/Engine/Renderer/Utils/BitonicSort.cpp b/Source/Engine/Renderer/Utils/BitonicSort.cpp index 5dc4fffee..89313a95d 100644 --- a/Source/Engine/Renderer/Utils/BitonicSort.cpp +++ b/Source/Engine/Renderer/Utils/BitonicSort.cpp @@ -69,7 +69,7 @@ void BitonicSort::Dispose() // Base RendererPass::Dispose(); - // Delete pipeline states + // Cleanup SAFE_DELETE_GPU_RESOURCE(_dispatchArgsBuffer); _cb = nullptr; _indirectArgsCS = nullptr; @@ -77,9 +77,7 @@ void BitonicSort::Dispose() _innerSortCS = nullptr; _outerSortCS = nullptr; _copyIndicesCS = nullptr; - - // Release asset - _shader.Unlink(); + _shader = nullptr; } void BitonicSort::Sort(GPUContext* context, GPUBuffer* sortingKeysBuffer, GPUBuffer* countBuffer, uint32 counterOffset, bool sortAscending, GPUBuffer* sortedIndicesBuffer) diff --git a/Source/Engine/Renderer/Utils/MultiScaler.cpp b/Source/Engine/Renderer/Utils/MultiScaler.cpp index c415c1900..2c6e8ba14 100644 --- a/Source/Engine/Renderer/Utils/MultiScaler.cpp +++ b/Source/Engine/Renderer/Utils/MultiScaler.cpp @@ -81,14 +81,12 @@ void MultiScaler::Dispose() // Base RendererPass::Dispose(); - // Delete pipeline states + // Cleanup SAFE_DELETE_GPU_RESOURCE(_psHalfDepth); _psBlur5.Delete(); _psBlur9.Delete(); _psBlur13.Delete(); - - // Release asset - _shader.Unlink(); + _shader = nullptr; } void MultiScaler::Filter(const FilterMode mode, GPUContext* context, const int32 width, const int32 height, GPUTextureView* src, GPUTextureView* dst, GPUTextureView* tmp) @@ -121,7 +119,7 @@ void MultiScaler::Filter(const FilterMode mode, GPUContext* context, const int32 ps = &_psBlur13; break; default: - CRASH; + CRASH; return; } @@ -177,7 +175,7 @@ void MultiScaler::Filter(const FilterMode mode, GPUContext* context, const int32 ps = &_psBlur13; break; default: - CRASH; + CRASH; return; } diff --git a/Source/Engine/Renderer/VolumetricFogPass.cpp b/Source/Engine/Renderer/VolumetricFogPass.cpp index 0bf8d838e..ff8f469ae 100644 --- a/Source/Engine/Renderer/VolumetricFogPass.cpp +++ b/Source/Engine/Renderer/VolumetricFogPass.cpp @@ -89,26 +89,19 @@ void VolumetricFogPass::Dispose() // Base RendererPass::Dispose(); - // Delete pipeline states + // Cleanup _psInjectLight.Delete(); - _csInitialize = nullptr; _csLightScattering.Clear(); _csFinalIntegration = nullptr; - SAFE_DELETE_GPU_RESOURCE(_vbCircleRasterize); SAFE_DELETE_GPU_RESOURCE(_ibCircleRasterize); - - // Release assets - _shader.Unlink(); + _shader = nullptr; } -float ComputeZSliceFromDepth(float SceneDepth, const VolumetricFogOptions& options, int32 GridSizeZ) +float ComputeZSliceFromDepth(float sceneDepth, const VolumetricFogOptions& options, int32 gridSizeZ) { - // This must match frustum voxels depth distribution - // See ComputeNormalizedZSliceFromDepth() in VolumetricFog.shader - - return SceneDepth / options.Distance * GridSizeZ; + return sceneDepth / options.Distance * (float)gridSizeZ; } bool VolumetricFogPass::Init(RenderContext& renderContext, GPUContext* context, VolumetricFogOptions& options) diff --git a/Source/Engine/Scripting/BinaryModule.cpp b/Source/Engine/Scripting/BinaryModule.cpp index e40a7b716..d5df8947e 100644 --- a/Source/Engine/Scripting/BinaryModule.cpp +++ b/Source/Engine/Scripting/BinaryModule.cpp @@ -14,6 +14,10 @@ #include "MException.h" #include "Scripting.h" #include "StdTypesContainer.h" +#include "Events.h" + +Dictionary, void(*)(ScriptingObject*, void*, bool)> ScriptingEvents::EventsTable; +Delegate&, const ScriptingTypeHandle&, const StringView&> ScriptingEvents::Event; ManagedBinaryModule* GetBinaryModuleCorlib() { @@ -60,62 +64,80 @@ ScriptingType::ScriptingType() , Module(nullptr) , InitRuntime(nullptr) , Fullname(nullptr, 0) - , Type(ScriptingTypes::Class) + , Type(ScriptingTypes::Script) , BaseTypePtr(nullptr) + , Interfaces(nullptr) { - Class.Spawn = nullptr; - Class.VTable = nullptr; - Class.ScriptVTable = nullptr; - Class.ScriptVTableBase = nullptr; - Class.SetupScriptVTable = nullptr; - Class.SetupScriptObjectVTable = nullptr; - Class.DefaultInstance = nullptr; + Script.Spawn = nullptr; + Script.VTable = nullptr; + Script.ScriptVTable = nullptr; + Script.ScriptVTableBase = nullptr; + Script.SetupScriptVTable = nullptr; + Script.SetupScriptObjectVTable = nullptr; + Script.DefaultInstance = nullptr; } -ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime, SpawnHandler spawn, const ScriptingTypeHandle& baseType, SetupScriptVTableHandler setupScriptVTable, SetupScriptObjectVTableHandler setupScriptObjectVTable) +ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime, SpawnHandler spawn, const ScriptingTypeHandle& baseType, SetupScriptVTableHandler setupScriptVTable, SetupScriptObjectVTableHandler setupScriptObjectVTable, const InterfaceImplementation* interfaces) : ManagedClass(nullptr) , Module(module) , InitRuntime(initRuntime) , Fullname(fullname) - , Type(ScriptingTypes::Class) + , Type(ScriptingTypes::Script) , BaseTypeHandle(baseType) , BaseTypePtr(nullptr) + , Interfaces(interfaces) , Size(size) { - Class.Spawn = spawn; - Class.VTable = nullptr; - Class.ScriptVTable = nullptr; - Class.ScriptVTableBase = nullptr; - Class.SetupScriptVTable = setupScriptVTable; - Class.SetupScriptObjectVTable = setupScriptObjectVTable; - Class.DefaultInstance = nullptr; + Script.Spawn = spawn; + Script.VTable = nullptr; + Script.ScriptVTable = nullptr; + Script.ScriptVTableBase = nullptr; + Script.SetupScriptVTable = setupScriptVTable; + Script.SetupScriptObjectVTable = setupScriptObjectVTable; + Script.DefaultInstance = nullptr; } -ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime, SpawnHandler spawn, ScriptingTypeInitializer* baseType, SetupScriptVTableHandler setupScriptVTable, SetupScriptObjectVTableHandler setupScriptObjectVTable) +ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime, SpawnHandler spawn, ScriptingTypeInitializer* baseType, SetupScriptVTableHandler setupScriptVTable, SetupScriptObjectVTableHandler setupScriptObjectVTable, const InterfaceImplementation* interfaces) + : ManagedClass(nullptr) + , Module(module) + , InitRuntime(initRuntime) + , Fullname(fullname) + , Type(ScriptingTypes::Script) + , BaseTypePtr(baseType) + , Interfaces(interfaces) + , Size(size) +{ + Script.Spawn = spawn; + Script.VTable = nullptr; + Script.ScriptVTable = nullptr; + Script.ScriptVTableBase = nullptr; + Script.SetupScriptVTable = setupScriptVTable; + Script.SetupScriptObjectVTable = setupScriptObjectVTable; + Script.DefaultInstance = nullptr; +} + +ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime, Ctor ctor, Dtor dtor, ScriptingTypeInitializer* baseType, const InterfaceImplementation* interfaces) : ManagedClass(nullptr) , Module(module) , InitRuntime(initRuntime) , Fullname(fullname) , Type(ScriptingTypes::Class) , BaseTypePtr(baseType) + , Interfaces(interfaces) , Size(size) { - Class.Spawn = spawn; - Class.VTable = nullptr; - Class.ScriptVTable = nullptr; - Class.ScriptVTableBase = nullptr; - Class.SetupScriptVTable = setupScriptVTable; - Class.SetupScriptObjectVTable = setupScriptObjectVTable; - Class.DefaultInstance = nullptr; + Class.Ctor = ctor; + Class.Dtor = dtor; } -ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime, Ctor ctor, Dtor dtor, Copy copy, Box box, Unbox unbox, GetField getField, SetField setField, ScriptingTypeInitializer* baseType) +ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime, Ctor ctor, Dtor dtor, Copy copy, Box box, Unbox unbox, GetField getField, SetField setField, ScriptingTypeInitializer* baseType, const InterfaceImplementation* interfaces) : ManagedClass(nullptr) , Module(module) , InitRuntime(initRuntime) , Fullname(fullname) , Type(ScriptingTypes::Structure) , BaseTypePtr(baseType) + , Interfaces(interfaces) , Size(size) { Struct.Ctor = ctor; @@ -127,6 +149,18 @@ ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* modul Struct.SetField = setField; } +ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* module, InitRuntimeHandler initRuntime, ScriptingTypeInitializer* baseType, const InterfaceImplementation* interfaces) + : ManagedClass(nullptr) + , Module(module) + , InitRuntime(initRuntime) + , Fullname(fullname) + , Type(ScriptingTypes::Interface) + , BaseTypePtr(baseType) + , Interfaces(interfaces) + , Size(0) +{ +} + ScriptingType::ScriptingType(const ScriptingType& other) : ManagedClass(other.ManagedClass) , Module(other.Module) @@ -135,18 +169,19 @@ ScriptingType::ScriptingType(const ScriptingType& other) , Type(other.Type) , BaseTypeHandle(other.BaseTypeHandle) , BaseTypePtr(other.BaseTypePtr) + , Interfaces(other.Interfaces) , Size(other.Size) { switch (other.Type) { - case ScriptingTypes::Class: - Class.Spawn = other.Class.Spawn; - Class.VTable = nullptr; - Class.ScriptVTable = nullptr; - Class.ScriptVTableBase = nullptr; - Class.SetupScriptVTable = other.Class.SetupScriptVTable; - Class.SetupScriptObjectVTable = other.Class.SetupScriptObjectVTable; - Class.DefaultInstance = nullptr; + case ScriptingTypes::Script: + Script.Spawn = other.Script.Spawn; + Script.VTable = nullptr; + Script.ScriptVTable = nullptr; + Script.ScriptVTableBase = nullptr; + Script.SetupScriptVTable = other.Script.SetupScriptVTable; + Script.SetupScriptObjectVTable = other.Script.SetupScriptObjectVTable; + Script.DefaultInstance = nullptr; break; case ScriptingTypes::Structure: Struct.Ctor = other.Struct.Ctor; @@ -157,8 +192,14 @@ ScriptingType::ScriptingType(const ScriptingType& other) Struct.GetField = other.Struct.GetField; Struct.SetField = other.Struct.SetField; break; + case ScriptingTypes::Class: + Class.Ctor = other.Class.Ctor; + Class.Dtor = other.Class.Dtor; + break; case ScriptingTypes::Enum: break; + case ScriptingTypes::Interface: + break; default: ; } } @@ -171,22 +212,23 @@ ScriptingType::ScriptingType(ScriptingType&& other) , Type(other.Type) , BaseTypeHandle(other.BaseTypeHandle) , BaseTypePtr(other.BaseTypePtr) + , Interfaces(other.Interfaces) , Size(other.Size) { switch (other.Type) { - case ScriptingTypes::Class: - Class.Spawn = other.Class.Spawn; - Class.VTable = other.Class.VTable; - other.Class.VTable = nullptr; - Class.ScriptVTable = other.Class.ScriptVTable; - other.Class.ScriptVTable = nullptr; - Class.ScriptVTableBase = other.Class.ScriptVTableBase; - other.Class.ScriptVTableBase = nullptr; - Class.SetupScriptVTable = other.Class.SetupScriptVTable; - Class.SetupScriptObjectVTable = other.Class.SetupScriptObjectVTable; - Class.DefaultInstance = other.Class.DefaultInstance; - other.Class.DefaultInstance = nullptr; + case ScriptingTypes::Script: + Script.Spawn = other.Script.Spawn; + Script.VTable = other.Script.VTable; + other.Script.VTable = nullptr; + Script.ScriptVTable = other.Script.ScriptVTable; + other.Script.ScriptVTable = nullptr; + Script.ScriptVTableBase = other.Script.ScriptVTableBase; + other.Script.ScriptVTableBase = nullptr; + Script.SetupScriptVTable = other.Script.SetupScriptVTable; + Script.SetupScriptObjectVTable = other.Script.SetupScriptObjectVTable; + Script.DefaultInstance = other.Script.DefaultInstance; + other.Script.DefaultInstance = nullptr; break; case ScriptingTypes::Structure: Struct.Ctor = other.Struct.Ctor; @@ -197,8 +239,14 @@ ScriptingType::ScriptingType(ScriptingType&& other) Struct.GetField = other.Struct.GetField; Struct.SetField = other.Struct.SetField; break; + case ScriptingTypes::Class: + Class.Ctor = other.Class.Ctor; + Class.Dtor = other.Class.Dtor; + break; case ScriptingTypes::Enum: break; + case ScriptingTypes::Interface: + break; default: ; } } @@ -207,18 +255,20 @@ ScriptingType::~ScriptingType() { switch (Type) { - case ScriptingTypes::Class: - if (Class.DefaultInstance) - Delete(Class.DefaultInstance); - if (Class.VTable) - Platform::Free((byte*)Class.VTable - GetVTablePrefix()); - Platform::Free(Class.ScriptVTable); - Platform::Free(Class.ScriptVTableBase); + case ScriptingTypes::Script: + if (Script.DefaultInstance) + Delete(Script.DefaultInstance); + if (Script.VTable) + Platform::Free((byte*)Script.VTable - GetVTablePrefix()); + Platform::Free(Script.ScriptVTable); + Platform::Free(Script.ScriptVTableBase); break; case ScriptingTypes::Structure: break; case ScriptingTypes::Enum: break; + case ScriptingTypes::Interface: + break; default: ; } } @@ -235,17 +285,40 @@ ScriptingTypeHandle ScriptingType::GetHandle() const ScriptingObject* ScriptingType::GetDefaultInstance() const { - ASSERT(Type == ScriptingTypes::Class); - if (!Class.DefaultInstance) + ASSERT(Type == ScriptingTypes::Script); + if (!Script.DefaultInstance) { const ScriptingObjectSpawnParams params(Guid::New(), GetHandle()); - Class.DefaultInstance = Class.Spawn(params); - if (!Class.DefaultInstance) + Script.DefaultInstance = Script.Spawn(params); + if (!Script.DefaultInstance) { LOG(Error, "Failed to create default instance of type {0}", ToString()); } } - return Class.DefaultInstance; + return Script.DefaultInstance; +} + +const ScriptingType::InterfaceImplementation* ScriptingType::GetInterface(const ScriptingTypeInitializer* interfaceType) const +{ + const InterfaceImplementation* interfaces = Interfaces; + if (interfaces) + { + while (interfaces->InterfaceType) + { + if (interfaces->InterfaceType == interfaceType) + return interfaces; + interfaces++; + } + } + if (BaseTypeHandle) + { + return BaseTypeHandle.GetType().GetInterface(interfaceType); + } + if (BaseTypePtr) + { + return BaseTypePtr->GetType().GetInterface(interfaceType); + } + return nullptr; } String ScriptingType::ToString() const @@ -253,11 +326,12 @@ String ScriptingType::ToString() const return String(Fullname.Get(), Fullname.Length()); } -ScriptingTypeInitializer::ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::SpawnHandler spawn, ScriptingTypeInitializer* baseType, ScriptingType::SetupScriptVTableHandler setupScriptVTable, ScriptingType::SetupScriptObjectVTableHandler setupScriptObjectVTable) +ScriptingTypeInitializer::ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::SpawnHandler spawn, ScriptingTypeInitializer* baseType, ScriptingType::SetupScriptVTableHandler setupScriptVTable, ScriptingType::SetupScriptObjectVTableHandler setupScriptObjectVTable, const ScriptingType::InterfaceImplementation* interfaces) : ScriptingTypeHandle(module, module->Types.Count()) { + // Script module->Types.AddUninitialized(); - new(module->Types.Get() + TypeIndex)ScriptingType(fullname, module, size, initRuntime, spawn, baseType, setupScriptVTable, setupScriptObjectVTable); + new(module->Types.Get() + TypeIndex)ScriptingType(fullname, module, size, initRuntime, spawn, baseType, setupScriptVTable, setupScriptObjectVTable, interfaces); const MString typeName(fullname.Get(), fullname.Length()); #if BUILD_DEBUG if (module->TypeNameToTypeIndex.ContainsKey(typeName)) @@ -268,11 +342,44 @@ ScriptingTypeInitializer::ScriptingTypeInitializer(BinaryModule* module, const S module->TypeNameToTypeIndex[typeName] = TypeIndex; } -ScriptingTypeInitializer::ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::Ctor ctor, ScriptingType::Dtor dtor, ScriptingType::Copy copy, ScriptingType::Box box, ScriptingType::Unbox unbox, ScriptingType::GetField getField, ScriptingType::SetField setField, ScriptingTypeInitializer* baseType) +ScriptingTypeInitializer::ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::Ctor ctor, ScriptingType::Dtor dtor, ScriptingTypeInitializer* baseType, const ScriptingType::InterfaceImplementation* interfaces) : ScriptingTypeHandle(module, module->Types.Count()) { + // Class module->Types.AddUninitialized(); - new(module->Types.Get() + TypeIndex)ScriptingType(fullname, module, size, initRuntime, ctor, dtor, copy, box, unbox, getField, setField, baseType); + new(module->Types.Get() + TypeIndex)ScriptingType(fullname, module, size, initRuntime, ctor, dtor, baseType, interfaces); + const MString typeName(fullname.Get(), fullname.Length()); +#if BUILD_DEBUG + if (module->TypeNameToTypeIndex.ContainsKey(typeName)) + { + LOG(Error, "Duplicated native typename {0} from module {1}.", String(fullname), String(module->GetName())); + } +#endif + module->TypeNameToTypeIndex[typeName] = TypeIndex; +} + +ScriptingTypeInitializer::ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::Ctor ctor, ScriptingType::Dtor dtor, ScriptingType::Copy copy, ScriptingType::Box box, ScriptingType::Unbox unbox, ScriptingType::GetField getField, ScriptingType::SetField setField, ScriptingTypeInitializer* baseType, const ScriptingType::InterfaceImplementation* interfaces) + : ScriptingTypeHandle(module, module->Types.Count()) +{ + // Structure + module->Types.AddUninitialized(); + new(module->Types.Get() + TypeIndex)ScriptingType(fullname, module, size, initRuntime, ctor, dtor, copy, box, unbox, getField, setField, baseType, interfaces); + const MString typeName(fullname.Get(), fullname.Length()); +#if BUILD_DEBUG + if (module->TypeNameToTypeIndex.ContainsKey(typeName)) + { + LOG(Error, "Duplicated native typename {0} from module {1}.", String(fullname), String(module->GetName())); + } +#endif + module->TypeNameToTypeIndex[typeName] = TypeIndex; +} + +ScriptingTypeInitializer::ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, ScriptingType::InitRuntimeHandler initRuntime, ScriptingTypeInitializer* baseType, const ScriptingType::InterfaceImplementation* interfaces) + : ScriptingTypeHandle(module, module->Types.Count()) +{ + // Interface + module->Types.AddUninitialized(); + new(module->Types.Get() + TypeIndex)ScriptingType(fullname, module, initRuntime, baseType, interfaces); const MString typeName(fullname.Get(), fullname.Length()); #if BUILD_DEBUG if (module->TypeNameToTypeIndex.ContainsKey(typeName)) @@ -368,7 +475,7 @@ ScriptingObject* ManagedBinaryModule::ManagedObjectSpawn(const ScriptingObjectSp // Create native object ScriptingTypeHandle managedTypeHandle = params.Type; const ScriptingType* managedTypePtr = &managedTypeHandle.GetType(); - while (managedTypePtr->Class.Spawn != &ManagedObjectSpawn) + while (managedTypePtr->Script.Spawn != &ManagedObjectSpawn) { managedTypeHandle = managedTypePtr->GetBaseType(); managedTypePtr = &managedTypeHandle.GetType(); @@ -376,12 +483,12 @@ ScriptingObject* ManagedBinaryModule::ManagedObjectSpawn(const ScriptingObjectSp ScriptingType& managedType = (ScriptingType&)*managedTypePtr; ScriptingTypeHandle nativeTypeHandle = managedType.GetBaseType(); const ScriptingType* nativeTypePtr = &nativeTypeHandle.GetType(); - while (nativeTypePtr->Class.Spawn == &ManagedObjectSpawn) + while (nativeTypePtr->Script.Spawn == &ManagedObjectSpawn) { nativeTypeHandle = nativeTypePtr->GetBaseType(); nativeTypePtr = &nativeTypeHandle.GetType(); } - ScriptingObject* object = nativeTypePtr->Class.Spawn(params); + ScriptingObject* object = nativeTypePtr->Script.Spawn(params); if (!object) { return nullptr; @@ -392,9 +499,9 @@ ScriptingObject* ManagedBinaryModule::ManagedObjectSpawn(const ScriptingObjectSp // We create a custom vtable for the C# objects that use a native class object with virtual functions overrides. // To make it easy to use in C++ we inject custom wrapper methods into C++ object vtable to call C# code from them. // Because virtual member functions calls are C++ ABI and impl-defined this is quite hard. But works. - if (managedType.Class.ScriptVTable) + if (managedType.Script.ScriptVTable) { - if (!managedType.Class.VTable) + if (!managedType.Script.VTable) { // Duplicate vtable void** vtable = *(void***)object; @@ -403,23 +510,23 @@ ScriptingObject* ManagedBinaryModule::ManagedObjectSpawn(const ScriptingObjectSp while (vtable[entriesCount] && entriesCount < 200) entriesCount++; const int32 size = entriesCount * sizeof(void*); - managedType.Class.VTable = (void**)((byte*)Platform::Allocate(prefixSize + size, 16) + prefixSize); - Platform::MemoryCopy((byte*)managedType.Class.VTable - prefixSize, (byte*)vtable - prefixSize, prefixSize + size); + managedType.Script.VTable = (void**)((byte*)Platform::Allocate(prefixSize + size, 16) + prefixSize); + Platform::MemoryCopy((byte*)managedType.Script.VTable - prefixSize, (byte*)vtable - prefixSize, prefixSize + size); // Override vtable entries by the class for (ScriptingTypeHandle e = nativeTypeHandle; e;) { const ScriptingType& eType = e.GetType(); - if (eType.Class.SetupScriptObjectVTable) + if (eType.Script.SetupScriptObjectVTable) { - eType.Class.SetupScriptObjectVTable(managedType.Class.ScriptVTable, managedType.Class.ScriptVTableBase, managedType.Class.VTable, entriesCount, 0); + eType.Script.SetupScriptObjectVTable(managedType.Script.ScriptVTable, managedType.Script.ScriptVTableBase, managedType.Script.VTable, entriesCount, 0); } e = eType.GetBaseType(); } } // Override object vtable with hacked one that has C# functions calls - *(void**)object = managedType.Class.VTable; + *(void**)object = managedType.Script.VTable; } // Mark as managed type @@ -499,6 +606,8 @@ void ManagedBinaryModule::OnLoaded(MAssembly* assembly) for (int32 typeIndex = 0; typeIndex < Types.Count(); typeIndex++) { ScriptingType& type = Types[typeIndex]; + if (type.Type == ScriptingTypes::Interface) + continue; // TODO: generate C# class for interfaces in API ASSERT(type.ManagedClass == nullptr); // Cache class @@ -557,7 +666,7 @@ void ManagedBinaryModule::OnLoaded(MAssembly* assembly) if (baseClassModule->TypeNameToTypeIndex.TryGet(baseClass->GetFullName(), typeIndex)) { nativeType = ScriptingTypeHandle(baseClassModule, typeIndex); - if (nativeType.GetType().Class.Spawn != &ManagedObjectSpawn) + if (nativeType.GetType().Script.Spawn != &ManagedObjectSpawn) break; } baseClass = baseClass->GetBaseClass(); @@ -592,14 +701,14 @@ void ManagedBinaryModule::OnLoaded(MAssembly* assembly) for (ScriptingTypeHandle e = nativeType; e;) { const ScriptingType& eType = e.GetType(); - if (eType.Class.SetupScriptVTable) + if (eType.Script.SetupScriptVTable) { ASSERT(eType.ManagedClass); - eType.Class.SetupScriptVTable(eType.ManagedClass, type.Class.ScriptVTable, type.Class.ScriptVTableBase); + eType.Script.SetupScriptVTable(eType.ManagedClass, type.Script.ScriptVTable, type.Script.ScriptVTableBase); } e = eType.GetBaseType(); } - MMethod** scriptVTable = (MMethod**)type.Class.ScriptVTable; + MMethod** scriptVTable = (MMethod**)type.Script.ScriptVTable; while (scriptVTable && *scriptVTable) { const MMethod* referenceMethod = *scriptVTable; @@ -657,10 +766,10 @@ void ManagedBinaryModule::OnUnloading(MAssembly* assembly) for (ScriptingType& type : Types) { type.ManagedClass = nullptr; - if (type.Type == ScriptingTypes::Class && type.Class.ScriptVTable) + if (type.Type == ScriptingTypes::Script && type.Script.ScriptVTable) { - Platform::Free(type.Class.ScriptVTable); - type.Class.ScriptVTable = nullptr; + Platform::Free(type.Script.ScriptVTable); + type.Script.ScriptVTable = nullptr; } } ClassToTypeIndex.Clear(); diff --git a/Source/Engine/Scripting/Events.h b/Source/Engine/Scripting/Events.h new file mode 100644 index 000000000..11f1643ee --- /dev/null +++ b/Source/Engine/Scripting/Events.h @@ -0,0 +1,36 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#include "ScriptingType.h" +#include "Engine/Core/Delegate.h" +#include "Engine/Core/Types/Span.h" +#include "Engine/Core/Types/Pair.h" +#include "Engine/Core/Types/Variant.h" +#include "Engine/Core/Types/StringView.h" +#include "Engine/Core/Collections/Dictionary.h" + +/// +/// The helper utility for binding and invoking scripting events (eg. used by Visual Scripting). +/// +class FLAXENGINE_API ScriptingEvents +{ +public: + + /// + /// Global table for registered even binder methods (key is pair of type and event name, value is method that takes instance with event, object to bind and flag to bind or unbind). + /// + /// + /// Key: pair of event type name (full), event name. + /// Value: event binder function with parameters: event caller instance (null for static events), object to bind, true to bind/false to unbind. + /// + static Dictionary, void(*)(ScriptingObject*, void*, bool)> EventsTable; + + /// + /// The action called when any scripting event occurs. Can be used to invoke scripting code that binded to this particular event. + /// + /// + /// Delegate parameters: event caller instance (null for static events), event invocation parameters list, event type name (full), event name. + /// + static Delegate&, const ScriptingTypeHandle&, const StringView&> Event; +}; diff --git a/Source/Engine/Scripting/ManagedCLR/MAssembly.Mono.cpp b/Source/Engine/Scripting/ManagedCLR/MAssembly.Mono.cpp index e24b71eff..e40014e53 100644 --- a/Source/Engine/Scripting/ManagedCLR/MAssembly.Mono.cpp +++ b/Source/Engine/Scripting/ManagedCLR/MAssembly.Mono.cpp @@ -29,7 +29,6 @@ MAssembly::MAssembly(MDomain* domain, const StringAnsiView& name, const MAssembl , _isDependency(false) , _isFileLocked(false) , _hasCachedClasses(false) - , _classes(options.DictionaryInitialSize) , _reloadCount(0) , _name(name) , _options(options) @@ -342,6 +341,8 @@ void MAssembly::OnLoading() Loading(this); _isLoading = true; + if (_classes.Capacity() == 0) + _classes.EnsureCapacity(_options.DictionaryInitialSize); // Pick a domain if (_domain == nullptr) diff --git a/Source/Engine/Scripting/Object.cs b/Source/Engine/Scripting/Object.cs index 1c649ba9b..a4e2424e9 100644 --- a/Source/Engine/Scripting/Object.cs +++ b/Source/Engine/Scripting/Object.cs @@ -205,6 +205,11 @@ namespace FlaxEngine return obj?.__unmanagedPtr ?? IntPtr.Zero; } + internal static IntPtr GetUnmanagedPtr(SoftObjectReference reference) + { + return GetUnmanagedPtr(reference.Get()); + } + /// public override int GetHashCode() { diff --git a/Source/Engine/Scripting/Script.cpp b/Source/Engine/Scripting/Script.cpp index 51e827ea8..e1f15e44e 100644 --- a/Source/Engine/Scripting/Script.cpp +++ b/Source/Engine/Scripting/Script.cpp @@ -186,9 +186,9 @@ void Script::SetupType() while (typeHandle != Script::TypeInitializer) { auto& type = typeHandle.GetType(); - _tickUpdate |= type.Class.ScriptVTable[8] != nullptr; - _tickLateUpdate |= type.Class.ScriptVTable[9] != nullptr; - _tickFixedUpdate |= type.Class.ScriptVTable[10] != nullptr; + _tickUpdate |= type.Script.ScriptVTable[8] != nullptr; + _tickLateUpdate |= type.Script.ScriptVTable[9] != nullptr; + _tickFixedUpdate |= type.Script.ScriptVTable[10] != nullptr; typeHandle = type.GetBaseType(); } } diff --git a/Source/Engine/Scripting/Script.cs b/Source/Engine/Scripting/Script.cs index 7f8561c68..c1a0894f0 100644 --- a/Source/Engine/Scripting/Script.cs +++ b/Source/Engine/Scripting/Script.cs @@ -2,7 +2,7 @@ namespace FlaxEngine { - partial class Script : ISceneObject + partial class Script { /// /// Gets the scene object which contains this script. diff --git a/Source/Engine/Scripting/ScriptingObject.cpp b/Source/Engine/Scripting/ScriptingObject.cpp index 5cdea3ab4..dc9e27865 100644 --- a/Source/Engine/Scripting/ScriptingObject.cpp +++ b/Source/Engine/Scripting/ScriptingObject.cpp @@ -71,6 +71,12 @@ ScriptingObject* ScriptingObject::ToNative(MonoObject* obj) return ptr; } +bool ScriptingObject::Is(const ScriptingTypeHandle& type) const +{ + CHECK_RETURN(type, false); + return _type == type || CanCast(GetClass(), type.GetType().ManagedClass); +} + void ScriptingObject::ChangeID(const Guid& newId) { ASSERT(newId.IsValid() && newId != _id); @@ -205,6 +211,14 @@ void ScriptingObject::UnregisterObject() Scripting::UnregisterObject(this); } +bool ScriptingObject::CanCast(const ScriptingTypeHandle& from, const ScriptingTypeHandle& to) +{ + if (!from && !to) + return true; + CHECK_RETURN(from && to, false); + return CanCast(from.GetType().ManagedClass, to.GetType().ManagedClass); +} + bool ScriptingObject::CanCast(MClass* from, MClass* to) { if (!from && !to) @@ -356,7 +370,7 @@ public: // Create unmanaged object const ScriptingObjectSpawnParams params(Guid::New(), ScriptingTypeHandle(module, typeIndex)); - ScriptingObject* obj = scriptingType.Class.Spawn(params); + ScriptingObject* obj = scriptingType.Script.Spawn(params); if (obj == nullptr) { LOG(Error, "Failed to spawn object of type \'{0}.{1}\'.", String(mono_class_get_namespace(typeClass)), String(mono_class_get_name(typeClass))); @@ -399,7 +413,7 @@ public: // Create unmanaged object const ScriptingObjectSpawnParams params(Guid::New(), type); - ScriptingObject* obj = type.GetType().Class.Spawn(params); + ScriptingObject* obj = type.GetType().Script.Spawn(params); if (obj == nullptr) { LOG(Error, "Failed to spawn object of type \'{0}\'.", String(typeName)); @@ -453,7 +467,7 @@ public: // Create unmanaged object const ScriptingObjectSpawnParams params(Guid::New(), ScriptingTypeHandle(module, typeIndex)); - ScriptingObject* obj = scriptingType.Class.Spawn(params); + ScriptingObject* obj = scriptingType.Script.Spawn(params); if (obj == nullptr) { LOG(Error, "Failed to spawn object of type \'{0}.{1}\'.", String(mono_class_get_namespace(typeClass)), String(mono_class_get_name(typeClass))); diff --git a/Source/Engine/Scripting/ScriptingObject.h b/Source/Engine/Scripting/ScriptingObject.h index 9c5b74e18..9eb375fc5 100644 --- a/Source/Engine/Scripting/ScriptingObject.h +++ b/Source/Engine/Scripting/ScriptingObject.h @@ -131,12 +131,30 @@ public: return obj ? obj->GetOrCreateManagedInstance() : nullptr; } + /// + /// Checks if can cast one scripting object type into another type. + /// + /// The object type for the cast. + /// The destination type to the cast. + /// True if can, otherwise false. + static bool CanCast(const ScriptingTypeHandle& from, const ScriptingTypeHandle& to); + + /// + /// Checks if can cast one scripting object type into another type. + /// + /// The object class for the cast. + /// The destination class to the cast. + /// True if can, otherwise false. + static bool CanCast(MClass* from, MClass* to); + template static T* Cast(ScriptingObject* obj) { return obj && CanCast(obj->GetClass(), T::GetStaticClass()) ? (T*)obj : nullptr; } + bool Is(const ScriptingTypeHandle& type) const; + bool Is(MClass* type) const { return CanCast(GetClass(), type); @@ -185,10 +203,6 @@ public: /// void UnregisterObject(); -private: - - static bool CanCast(MClass* from, MClass* to); - protected: /// diff --git a/Source/Engine/Scripting/ScriptingType.h b/Source/Engine/Scripting/ScriptingType.h index 942edcd1a..b27d92f2f 100644 --- a/Source/Engine/Scripting/ScriptingType.h +++ b/Source/Engine/Scripting/ScriptingType.h @@ -93,9 +93,11 @@ inline uint32 GetHash(const ScriptingTypeHandle& key) /// enum class ScriptingTypes { - Class = 0, + Script = 0, Structure = 1, Enum = 2, + Class = 3, + Interface = 4, }; /// @@ -115,6 +117,15 @@ struct FLAXENGINE_API ScriptingType typedef void (*GetField)(void* ptr, const String& name, Variant& value); typedef void (*SetField)(void* ptr, const String& name, const Variant& value); + struct InterfaceImplementation + { + // Pointer to the type of the implemented interface. + const ScriptingTypeInitializer* InterfaceType; + + // The offset (in bytes) from the object pointer to the interface implementation. Used for casting object to the interface. + int16 VTableOffset; + }; + /// /// The managed class (cached, can be null if missing). /// @@ -150,6 +161,11 @@ struct FLAXENGINE_API ScriptingType /// const ScriptingTypeInitializer* BaseTypePtr; + /// + /// The list of interfaces implemented by this type (null if unused, list ends with null entry). + /// + const InterfaceImplementation* Interfaces; + /// /// The native size of the type value (in bytes). /// @@ -193,7 +209,7 @@ struct FLAXENGINE_API ScriptingType /// The default instance of the scripting type. Used by serialization system for comparison to save only modified properties of the object. /// mutable ScriptingObject* DefaultInstance; - } Class; + } Script; struct { @@ -218,12 +234,23 @@ struct FLAXENGINE_API ScriptingType // Structure field value setter SetField SetField; } Struct; + + struct + { + // Class constructor method pointer + Ctor Ctor; + + // Class destructor method pointer + Dtor Dtor; + } Class; }; ScriptingType(); - ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime, SpawnHandler spawn, const ScriptingTypeHandle& baseType, SetupScriptVTableHandler setupScriptVTable = nullptr, SetupScriptObjectVTableHandler setupScriptObjectVTable = nullptr); - ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime = DefaultInitRuntime, SpawnHandler spawn = DefaultSpawn, ScriptingTypeInitializer* baseType = nullptr, SetupScriptVTableHandler setupScriptVTable = nullptr, SetupScriptObjectVTableHandler setupScriptObjectVTable = nullptr); - ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime, Ctor ctor, Dtor dtor, Copy copy, Box box, Unbox unbox, GetField getField, SetField setField, ScriptingTypeInitializer* baseType); + ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime, SpawnHandler spawn, const ScriptingTypeHandle& baseType, SetupScriptVTableHandler setupScriptVTable = nullptr, SetupScriptObjectVTableHandler setupScriptObjectVTable = nullptr, const InterfaceImplementation* interfaces = nullptr); + ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime = DefaultInitRuntime, SpawnHandler spawn = DefaultSpawn, ScriptingTypeInitializer* baseType = nullptr, SetupScriptVTableHandler setupScriptVTable = nullptr, SetupScriptObjectVTableHandler setupScriptObjectVTable = nullptr, const InterfaceImplementation* interfaces = nullptr); + ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime, Ctor ctor, Dtor dtor, ScriptingTypeInitializer* baseType, const InterfaceImplementation* interfaces = nullptr); + ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime, Ctor ctor, Dtor dtor, Copy copy, Box box, Unbox unbox, GetField getField, SetField setField, ScriptingTypeInitializer* baseType, const InterfaceImplementation* interfaces = nullptr); + ScriptingType(const StringAnsiView& fullname, BinaryModule* module, InitRuntimeHandler initRuntime, ScriptingTypeInitializer* baseType, const InterfaceImplementation* interfaces = nullptr); ScriptingType(const ScriptingType& other); ScriptingType(ScriptingType&& other); ScriptingType& operator=(ScriptingType&& other) = delete; @@ -259,6 +286,11 @@ struct FLAXENGINE_API ScriptingType /// ScriptingObject* GetDefaultInstance() const; + /// + /// Gets the pointer to the implementation of the given interface type for this scripting type (including base types). Returns null if given interface is not implemented. + /// + const InterfaceImplementation* GetInterface(const ScriptingTypeInitializer* interfaceType) const; + String ToString() const; }; @@ -267,8 +299,10 @@ struct FLAXENGINE_API ScriptingType /// struct FLAXENGINE_API ScriptingTypeInitializer : ScriptingTypeHandle { - ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime = ScriptingType::DefaultInitRuntime, ScriptingType::SpawnHandler spawn = ScriptingType::DefaultSpawn, ScriptingTypeInitializer* baseType = nullptr, ScriptingType::SetupScriptVTableHandler setupScriptVTable = nullptr, ScriptingType::SetupScriptObjectVTableHandler setupScriptObjectVTable = nullptr); - ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::Ctor ctor, ScriptingType::Dtor dtor, ScriptingType::Copy copy, ScriptingType::Box box, ScriptingType::Unbox unbox, ScriptingType::GetField getField, ScriptingType::SetField setField, ScriptingTypeInitializer* baseType = nullptr); + ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime = ScriptingType::DefaultInitRuntime, ScriptingType::SpawnHandler spawn = ScriptingType::DefaultSpawn, ScriptingTypeInitializer* baseType = nullptr, ScriptingType::SetupScriptVTableHandler setupScriptVTable = nullptr, ScriptingType::SetupScriptObjectVTableHandler setupScriptObjectVTable = nullptr, const ScriptingType::InterfaceImplementation* interfaces = nullptr); + ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::Ctor ctor, ScriptingType::Dtor dtor, ScriptingTypeInitializer* baseType = nullptr, const ScriptingType::InterfaceImplementation* interfaces = nullptr); + ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::Ctor ctor, ScriptingType::Dtor dtor, ScriptingType::Copy copy, ScriptingType::Box box, ScriptingType::Unbox unbox, ScriptingType::GetField getField, ScriptingType::SetField setField, ScriptingTypeInitializer* baseType = nullptr, const ScriptingType::InterfaceImplementation* interfaces = nullptr); + ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, ScriptingType::InitRuntimeHandler initRuntime, ScriptingTypeInitializer* baseType = nullptr, const ScriptingType::InterfaceImplementation* interfaces = nullptr); }; /// diff --git a/Source/Engine/Scripting/SoftObjectReference.cs b/Source/Engine/Scripting/SoftObjectReference.cs new file mode 100644 index 000000000..df098d02a --- /dev/null +++ b/Source/Engine/Scripting/SoftObjectReference.cs @@ -0,0 +1,80 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +using System; + +namespace FlaxEngine +{ + /// + /// The scripting object soft reference. Objects gets referenced on use (ID reference is resolving it). + /// + public struct SoftObjectReference : IComparable, IComparable + { + private Guid _id; + private Object _object; + + /// + /// Gets or sets the object identifier. + /// + public Guid ID + { + get => _id; + set + { + if (_id == value) + return; + _id = value; + _object = null; + } + } + + /// + /// Gets the object reference. + /// + /// The object type. + /// The resolved object or null. + public T Get() where T : Object + { + if (!_object) + _object = Object.Find(ref _id, typeof(T)); + return _object as T; + } + + /// + /// Sets the object reference. + /// + /// The object. + public void Set(Object obj) + { + _object = obj; + _id = obj?.ID ?? Guid.Empty; + } + + /// + public override string ToString() + { + if (_object) + return _object.ToString(); + return _id.ToString(); + } + + /// + public override int GetHashCode() + { + return _id.GetHashCode(); + } + + /// + public int CompareTo(object obj) + { + if (obj is SoftObjectReference other) + return CompareTo(other); + return 0; + } + + /// + public int CompareTo(SoftObjectReference other) + { + return _id.CompareTo(other._id); + } + } +} diff --git a/Source/Engine/Scripting/SoftObjectReference.h b/Source/Engine/Scripting/SoftObjectReference.h new file mode 100644 index 000000000..b6f08db4a --- /dev/null +++ b/Source/Engine/Scripting/SoftObjectReference.h @@ -0,0 +1,328 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Engine/Scripting/ScriptingObject.h" + +// Don't include Scripting.h but just FindObject method +extern FLAXENGINE_API ScriptingObject* FindObject(const Guid& id, MClass* type); + +/// +/// The scripting object soft reference. Objects gets referenced on use (ID reference is resolving it). +/// +class FLAXENGINE_API SoftObjectReferenceBase +{ +public: + + typedef Delegate<> EventType; + +protected: + + ScriptingObject* _object = nullptr; + Guid _id = Guid::Empty; + +public: + + /// + /// Action fired when reference gets changed. + /// + EventType Changed; + +public: + + /// + /// Initializes a new instance of the class. + /// + SoftObjectReferenceBase() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The object to link. + SoftObjectReferenceBase(ScriptingObject* obj) + { + OnSet(obj); + } + + /// + /// Finalizes an instance of the class. + /// + ~SoftObjectReferenceBase() + { + if (_object) + _object->Deleted.Unbind(this); + } + +public: + + /// + /// Gets the object ID. + /// + /// The object ID or Guid::Empty if nothing assigned. + Guid GetID() const + { + return _object ? _object->GetID() : _id; + } + +protected: + + /// + /// Sets the object. + /// + /// The object. + void OnSet(ScriptingObject* object) + { + auto e = _object; + if (e != object) + { + if (e) + e->Deleted.Unbind(this); + _object = e = object; + _id = e ? e->GetID() : Guid::Empty; + if (e) + e->Deleted.Bind(this); + Changed(); + } + } + + void OnDeleted(ScriptingObject* obj) + { + ASSERT(_object == obj); + _object->Deleted.Unbind(this); + _object = nullptr; + Changed(); + } +}; + +/// +/// The scripting object soft reference. Objects gets referenced on use (ID reference is resolving it). +/// +template +API_CLASS(InBuild) class SoftObjectReference : public SoftObjectReferenceBase +{ +public: + + typedef SoftObjectReference Type; + +public: + + /// + /// Initializes a new instance of the class. + /// + SoftObjectReference() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The object to link. + SoftObjectReference(T* obj) + : SoftObjectReferenceBase(obj) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The other property. + SoftObjectReference(const SoftObjectReference& other) + : SoftObjectReferenceBase(other.Get()) + { + } + + /// + /// Finalizes an instance of the class. + /// + ~SoftObjectReference() + { + } + +public: + + /// + /// Compares the property value with the given object. + /// + /// The other. + /// True if property object equals the given value. + FORCE_INLINE bool operator==(T* other) + { + return Get() == other; + } + + /// + /// Compares the property value with the other property value. + /// + /// The other property. + /// True if properties are equal. + FORCE_INLINE bool operator==(const SoftObjectReference& other) + { + return _id == other._id; + } + + /// + /// Compares the property value with the given object. + /// + /// The other. + /// True if property object not equals the given value. + FORCE_INLINE bool operator!=(T* other) + { + return Get() != other; + } + + /// + /// Compares the property value with the other property value. + /// + /// The other property. + /// True if properties are not equal. + FORCE_INLINE bool operator!=(const SoftObjectReference& other) + { + return _id != other._id; + } + + /// + /// Sets the property to the given property value. + /// + /// The other property. + /// The reference to this property. + SoftObjectReference& operator=(const SoftObjectReference& other) + { + if (this != &other) + OnSet(other.Get()); + return *this; + } + + /// + /// Sets the property to the given value. + /// + /// The object. + /// The reference to this property. + FORCE_INLINE SoftObjectReference& operator=(const T& other) + { + OnSet(&other); + return *this; + } + + /// + /// Sets the property to the given value. + /// + /// The object. + /// The reference to this property. + FORCE_INLINE SoftObjectReference& operator=(T* other) + { + OnSet(other); + return *this; + } + + /// + /// Sets the property to the object of the given ID. + /// + /// The object ID. + /// The reference to this property. + FORCE_INLINE SoftObjectReference& operator=(const Guid& id) + { + Set(id); + return *this; + } + + /// + /// Implicit conversion to the object. + /// + /// The object reference. + FORCE_INLINE operator T*() const + { + return (T*)Get(); + } + + /// + /// Implicit conversion to boolean value. + /// + /// True if object has been assigned, otherwise false + FORCE_INLINE operator bool() const + { + return _object != nullptr || _id.IsValid(); + } + + /// + /// Object accessor. + /// + /// The object reference. + FORCE_INLINE T* operator->() const + { + return (T*)Get(); + } + + /// + /// Gets the object pointer. + /// + /// The object reference. + T* Get() const + { + if (!_object) + const_cast(this)->OnSet(FindObject(_id, T::GetStaticClass())); + return (T*)_object; + } + + /// + /// Gets the object as a given type (static cast). + /// + /// Asset + template + FORCE_INLINE U* As() const + { + return static_cast(Get()); + } + +public: + + /// + /// Gets managed instance object (or null if no object linked). + /// + /// The managed object instance. + MonoObject* GetManagedInstance() const + { + auto object = Get(); + return object ? object->GetOrCreateManagedInstance() : nullptr; + } + + /// + /// Determines whether object is assigned and managed instance of the object is alive. + /// + /// True if managed object has been created and exists, otherwise false. + bool HasManagedInstance() const + { + auto object = Get(); + return object && object->HasManagedInstance(); + } + + /// + /// Gets the managed instance object or creates it if missing or null if not assigned. + /// + /// The Mono managed object. + MonoObject* GetOrCreateManagedInstance() const + { + auto object = Get(); + return object ? object->GetOrCreateManagedInstance() : nullptr; + } + + /// + /// Sets the object. + /// + /// The object ID. Uses Scripting to find the registered object of the given ID. + void Set(const Guid& id) + { + _id = id; + _object = nullptr; + } + + /// + /// Sets the object. + /// + /// The object. + FORCE_INLINE void Set(T* object) + { + OnSet(object); + } +}; diff --git a/Source/Engine/Serialization/ISerializable.h b/Source/Engine/Serialization/ISerializable.h index d5f21ec44..26678c998 100644 --- a/Source/Engine/Serialization/ISerializable.h +++ b/Source/Engine/Serialization/ISerializable.h @@ -2,57 +2,5 @@ #pragma once -#include "JsonFwd.h" -#include "Engine/Core/Compiler.h" - -class JsonWriter; -class ISerializeModifier; - -/// -/// Interface for objects that can be serialized/deserialized to/from JSON format. -/// -class FLAXENGINE_API ISerializable -{ -public: - - typedef rapidjson_flax::Document SerializeDocument; - - /// - /// Serialization output stream - /// - typedef rapidjson_flax::Value DeserializeStream; - - /// - /// Serialization input stream - /// - typedef JsonWriter SerializeStream; - -public: - - /// - /// Finalizes an instance of the class. - /// - virtual ~ISerializable() = default; - - /// - /// Serialize object to the output stream compared to the values of the other object instance (eg. default class object). If other object is null then serialize all properties. - /// - /// The output stream. - /// The instance of the object to compare with and serialize only the modified properties. If null, then serialize all properties. - virtual void Serialize(SerializeStream& stream, const void* otherObj) = 0; - - /// - /// Deserialize object from the input stream - /// - /// The input stream. - /// The deserialization modifier object. Always valid. - virtual void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) = 0; - - /// - /// Deserialize object from the input stream child member. Won't deserialize it if member is missing. - /// - /// The input stream. - /// The input stream member to lookup. - /// The deserialization modifier object. Always valid. - void DeserializeIfExists(DeserializeStream& stream, const char* memberName, ISerializeModifier* modifier); -}; +// ISerializable moved to Core module +#include "Engine/Core/ISerializable.h" diff --git a/Source/Engine/Serialization/JsonCustomSerializers/ExtendedDefaultContractResolver.cs b/Source/Engine/Serialization/JsonCustomSerializers/ExtendedDefaultContractResolver.cs index f6a62dd54..cc07597e8 100644 --- a/Source/Engine/Serialization/JsonCustomSerializers/ExtendedDefaultContractResolver.cs +++ b/Source/Engine/Serialization/JsonCustomSerializers/ExtendedDefaultContractResolver.cs @@ -118,9 +118,11 @@ namespace FlaxEngine.Json.JsonCustomSerializers if (noSerialize) continue; + var isObsolete = attributes.Any(x => x is ObsoleteAttribute); + var jsonProperty = CreateProperty(p, memberSerialization); jsonProperty.Writable = true; - jsonProperty.Readable = true; + jsonProperty.Readable = !isObsolete; if (_flaxType.IsAssignableFrom(p.PropertyType)) { diff --git a/Source/Engine/Serialization/JsonSerializer.cs b/Source/Engine/Serialization/JsonSerializer.cs index 231831442..d3300dd23 100644 --- a/Source/Engine/Serialization/JsonSerializer.cs +++ b/Source/Engine/Serialization/JsonSerializer.cs @@ -87,6 +87,38 @@ namespace FlaxEngine.Json } } + /// + /// Serialize SoftObjectReference as Guid in internal format. + /// + /// + internal class SoftObjectReferenceConverter : JsonConverter + { + /// + public override unsafe void WriteJson(JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer) + { + var id = ((SoftObjectReference)value).ID; + writer.WriteValue(JsonSerializer.GetStringID(&id)); + } + + /// + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer) + { + var result = new SoftObjectReference(); + if (reader.TokenType == JsonToken.String) + { + JsonSerializer.ParseID((string)reader.Value, out var id); + result.ID = id; + } + return result; + } + + /// + public override bool CanConvert(Type objectType) + { + return objectType == typeof(SoftObjectReference); + } + } + /* /// /// Serialize Guid values using `N` format @@ -120,6 +152,7 @@ namespace FlaxEngine.Json } } */ + /// /// Objects serialization tool (json format). /// @@ -183,6 +216,7 @@ namespace FlaxEngine.Json ObjectConverter = new FlaxObjectConverter(); settings.Converters.Add(ObjectConverter); settings.Converters.Add(new SceneReferenceConverter()); + settings.Converters.Add(new SoftObjectReferenceConverter()); settings.Converters.Add(new VersionConverter()); //settings.Converters.Add(new GuidConverter()); return settings; diff --git a/Source/Engine/Serialization/Serialization.h b/Source/Engine/Serialization/Serialization.h index b760e9689..039d9df3e 100644 --- a/Source/Engine/Serialization/Serialization.h +++ b/Source/Engine/Serialization/Serialization.h @@ -9,6 +9,7 @@ #include "Engine/Core/Collections/Array.h" #include "Engine/Core/Collections/Dictionary.h" #include "Engine/Scripting/ScriptingObjectReference.h" +#include "Engine/Scripting/SoftObjectReference.h" #include "Engine/Content/AssetReference.h" #include "Engine/Content/WeakAssetReference.h" @@ -457,6 +458,27 @@ namespace Serialization v = id; } + // Soft Object Reference + + template + inline bool ShouldSerialize(const SoftObjectReference& v, const void* otherObj) + { + return !otherObj || v.Get() != ((SoftObjectReference*)otherObj)->Get(); + } + template + inline void Serialize(ISerializable::SerializeStream& stream, const SoftObjectReference& v, const void* otherObj) + { + stream.Guid(v.GetID()); + } + template + inline void Deserialize(ISerializable::DeserializeStream& stream, SoftObjectReference& v, ISerializeModifier* modifier) + { + Guid id; + Deserialize(stream, id, modifier); + modifier->IdsMapping.TryGet(id, id); + v = id; + } + // Asset Reference template diff --git a/Source/Engine/ShadowsOfMordor/Builder.Jobs.cpp b/Source/Engine/ShadowsOfMordor/Builder.Jobs.cpp index 7ec466eee..20c2fb325 100644 --- a/Source/Engine/ShadowsOfMordor/Builder.Jobs.cpp +++ b/Source/Engine/ShadowsOfMordor/Builder.Jobs.cpp @@ -173,7 +173,7 @@ void ShadowsOfMordor::Builder::onJobRender(GPUContext* context) context->SetState(_psRenderCacheTerrain); context->BindIB(drawCall.Geometry.IndexBuffer); context->BindVB(ToSpan(drawCall.Geometry.VertexBuffers, 1)); - context->DrawIndexed(drawCall.Geometry.IndicesCount, 0, drawCall.Geometry.StartIndex); + context->DrawIndexed(drawCall.Draw.IndicesCount, 0, drawCall.Draw.StartIndex); break; } diff --git a/Source/Engine/ShadowsOfMordor/Builder.cpp b/Source/Engine/ShadowsOfMordor/Builder.cpp index 715803839..b9cb57376 100644 --- a/Source/Engine/ShadowsOfMordor/Builder.cpp +++ b/Source/Engine/ShadowsOfMordor/Builder.cpp @@ -499,7 +499,7 @@ void ShadowsOfMordor::Builder::releaseResources() SAFE_DELETE_GPU_RESOURCE(_psRenderCacheModel); SAFE_DELETE_GPU_RESOURCE(_psRenderCacheTerrain); SAFE_DELETE_GPU_RESOURCE(_psBlurCache); - _shader.Unlink(); + _shader = nullptr; SAFE_DELETE_GPU_RESOURCE(_irradianceReduction); diff --git a/Source/Engine/Terrain/Terrain.cpp b/Source/Engine/Terrain/Terrain.cpp index 3b00818c2..e87de12a3 100644 --- a/Source/Engine/Terrain/Terrain.cpp +++ b/Source/Engine/Terrain/Terrain.cpp @@ -71,7 +71,7 @@ void Terrain::UpdateLayerBits() filterData.word0 = GetLayerMask(); // Own layer mask - filterData.word1 = PhysicsSettings::Instance()->LayerMasks[GetLayer()]; + filterData.word1 = Physics::LayerMasks[GetLayer()]; // Update the shapes layer bits for (int32 pathIndex = 0; pathIndex < _patches.Count(); pathIndex++) diff --git a/Source/Engine/Terrain/TerrainChunk.cpp b/Source/Engine/Terrain/TerrainChunk.cpp index fbb80c27d..08e5946ee 100644 --- a/Source/Engine/Terrain/TerrainChunk.cpp +++ b/Source/Engine/Terrain/TerrainChunk.cpp @@ -21,7 +21,7 @@ void TerrainChunk::Init(TerrainPatch* patch, uint16 x, uint16 z) _yHeight = 1; _heightmapUVScaleBias = Vector4(1.0f, 1.0f, _x, _z) * (1.0f / TerrainPatch::CHUNKS_COUNT_EDGE); _perInstanceRandom = (_patch->_terrain->_id.C ^ _x ^ _z) * (1.0f / MAX_uint32); - OverrideMaterial.Unlink(); + OverrideMaterial = nullptr; } bool TerrainChunk::PrepareDraw(const RenderContext& renderContext) @@ -83,38 +83,34 @@ void TerrainChunk::Draw(const RenderContext& renderContext) const if (TerrainManager::GetChunkGeometry(drawCall, chunkSize, lod)) return; drawCall.InstanceCount = 1; - drawCall.IndirectArgsBuffer = nullptr; - drawCall.IndirectArgsOffset = 0; drawCall.Material = _cachedDrawMaterial; drawCall.World = _world; drawCall.ObjectPosition = drawCall.World.GetTranslation(); - drawCall.TerrainData.Patch = _patch; - drawCall.TerrainData.HeightmapUVScaleBias = _heightmapUVScaleBias; - drawCall.TerrainData.OffsetUV = Vector2((float)(_patch->_x * TerrainPatch::CHUNKS_COUNT_EDGE + _x), (float)(_patch->_z * TerrainPatch::CHUNKS_COUNT_EDGE + _z)); - drawCall.TerrainData.CurrentLOD = (float)lod; - drawCall.TerrainData.ChunkSizeNextLOD = (float)(((chunkSize + 1) >> (lod + 1)) - 1); - drawCall.TerrainData.TerrainChunkSizeLOD0 = TERRAIN_UNITS_PER_VERTEX * chunkSize; + drawCall.Terrain.Patch = _patch; + drawCall.Terrain.HeightmapUVScaleBias = _heightmapUVScaleBias; + drawCall.Terrain.OffsetUV = Vector2((float)(_patch->_x * TerrainPatch::CHUNKS_COUNT_EDGE + _x), (float)(_patch->_z * TerrainPatch::CHUNKS_COUNT_EDGE + _z)); + drawCall.Terrain.CurrentLOD = (float)lod; + drawCall.Terrain.ChunkSizeNextLOD = (float)(((chunkSize + 1) >> (lod + 1)) - 1); + drawCall.Terrain.TerrainChunkSizeLOD0 = TERRAIN_UNITS_PER_VERTEX * chunkSize; // TODO: try using SIMD clamping for 4 chunks at once - drawCall.TerrainData.NeighborLOD.X = (float)Math::Clamp(_neighbors[0]->_cachedDrawLOD, lod, minLod); - drawCall.TerrainData.NeighborLOD.Y = (float)Math::Clamp(_neighbors[1]->_cachedDrawLOD, lod, minLod); - drawCall.TerrainData.NeighborLOD.Z = (float)Math::Clamp(_neighbors[2]->_cachedDrawLOD, lod, minLod); - drawCall.TerrainData.NeighborLOD.W = (float)Math::Clamp(_neighbors[3]->_cachedDrawLOD, lod, minLod); + drawCall.Terrain.NeighborLOD.X = (float)Math::Clamp(_neighbors[0]->_cachedDrawLOD, lod, minLod); + drawCall.Terrain.NeighborLOD.Y = (float)Math::Clamp(_neighbors[1]->_cachedDrawLOD, lod, minLod); + drawCall.Terrain.NeighborLOD.Z = (float)Math::Clamp(_neighbors[2]->_cachedDrawLOD, lod, minLod); + drawCall.Terrain.NeighborLOD.W = (float)Math::Clamp(_neighbors[3]->_cachedDrawLOD, lod, minLod); const auto scene = _patch->_terrain->GetScene(); const auto flags = _patch->_terrain->_staticFlags; if (flags & StaticFlags::Lightmap && scene) { - drawCall.Lightmap = scene->LightmapsData.GetReadyLightmap(Lightmap.TextureIndex); - drawCall.LightmapUVsArea = Lightmap.UVsArea; + drawCall.Terrain.Lightmap = scene->LightmapsData.GetReadyLightmap(Lightmap.TextureIndex); + drawCall.Terrain.LightmapUVsArea = Lightmap.UVsArea; } else { - drawCall.Lightmap = nullptr; - drawCall.LightmapUVsArea = Rectangle::Empty; + drawCall.Terrain.Lightmap = nullptr; + drawCall.Terrain.LightmapUVsArea = Rectangle::Empty; } - drawCall.Skinning = nullptr; drawCall.WorldDeterminantSign = Math::FloatSelect(drawCall.World.RotDeterminant(), 1, -1); drawCall.PerInstanceRandom = _perInstanceRandom; - drawCall.LODDitherFactor = 0.0f; // Add half-texel offset for heightmap sampling in vertex shader //const float lodHeightmapSize = Math::Max(1, drawCall.TerrainData.Heightmap->Width() >> lod); @@ -142,37 +138,33 @@ void TerrainChunk::Draw(const RenderContext& renderContext, MaterialBase* materi if (TerrainManager::GetChunkGeometry(drawCall, chunkSize, lod)) return; drawCall.InstanceCount = 1; - drawCall.IndirectArgsBuffer = nullptr; - drawCall.IndirectArgsOffset = 0; drawCall.Material = material; drawCall.World = _world; drawCall.ObjectPosition = drawCall.World.GetTranslation(); - drawCall.TerrainData.Patch = _patch; - drawCall.TerrainData.HeightmapUVScaleBias = _heightmapUVScaleBias; - drawCall.TerrainData.OffsetUV = Vector2((float)(_patch->_x * TerrainPatch::CHUNKS_COUNT_EDGE + _x), (float)(_patch->_z * TerrainPatch::CHUNKS_COUNT_EDGE + _z)); - drawCall.TerrainData.CurrentLOD = (float)lod; - drawCall.TerrainData.ChunkSizeNextLOD = (float)(((chunkSize + 1) >> (lod + 1)) - 1); - drawCall.TerrainData.TerrainChunkSizeLOD0 = TERRAIN_UNITS_PER_VERTEX * chunkSize; - drawCall.TerrainData.NeighborLOD.X = (float)lod; - drawCall.TerrainData.NeighborLOD.Y = (float)lod; - drawCall.TerrainData.NeighborLOD.Z = (float)lod; - drawCall.TerrainData.NeighborLOD.W = (float)lod; + drawCall.Terrain.Patch = _patch; + drawCall.Terrain.HeightmapUVScaleBias = _heightmapUVScaleBias; + drawCall.Terrain.OffsetUV = Vector2((float)(_patch->_x * TerrainPatch::CHUNKS_COUNT_EDGE + _x), (float)(_patch->_z * TerrainPatch::CHUNKS_COUNT_EDGE + _z)); + drawCall.Terrain.CurrentLOD = (float)lod; + drawCall.Terrain.ChunkSizeNextLOD = (float)(((chunkSize + 1) >> (lod + 1)) - 1); + drawCall.Terrain.TerrainChunkSizeLOD0 = TERRAIN_UNITS_PER_VERTEX * chunkSize; + drawCall.Terrain.NeighborLOD.X = (float)lod; + drawCall.Terrain.NeighborLOD.Y = (float)lod; + drawCall.Terrain.NeighborLOD.Z = (float)lod; + drawCall.Terrain.NeighborLOD.W = (float)lod; const auto scene = _patch->_terrain->GetScene(); const auto flags = _patch->_terrain->_staticFlags; if (flags & StaticFlags::Lightmap && scene) { - drawCall.Lightmap = scene->LightmapsData.GetReadyLightmap(Lightmap.TextureIndex); - drawCall.LightmapUVsArea = Lightmap.UVsArea; + drawCall.Terrain.Lightmap = scene->LightmapsData.GetReadyLightmap(Lightmap.TextureIndex); + drawCall.Terrain.LightmapUVsArea = Lightmap.UVsArea; } else { - drawCall.Lightmap = nullptr; - drawCall.LightmapUVsArea = Rectangle::Empty; + drawCall.Terrain.Lightmap = nullptr; + drawCall.Terrain.LightmapUVsArea = Rectangle::Empty; } - drawCall.Skinning = nullptr; drawCall.WorldDeterminantSign = Math::FloatSelect(drawCall.World.RotDeterminant(), 1, -1); drawCall.PerInstanceRandom = _perInstanceRandom; - drawCall.LODDitherFactor = 0.0f; // Add half-texel offset for heightmap sampling in vertex shader //const float lodHeightmapSize = Math::Max(1, drawCall.TerrainData.Heightmap->Width() >> lod); diff --git a/Source/Engine/Terrain/TerrainManager.cpp b/Source/Engine/Terrain/TerrainManager.cpp index 0638acd45..caf620ff0 100644 --- a/Source/Engine/Terrain/TerrainManager.cpp +++ b/Source/Engine/Terrain/TerrainManager.cpp @@ -37,8 +37,8 @@ public: drawCall.Geometry.VertexBuffersOffsets[0] = 0; drawCall.Geometry.VertexBuffersOffsets[1] = 0; drawCall.Geometry.VertexBuffersOffsets[2] = 0; - drawCall.Geometry.StartIndex = 0; - drawCall.Geometry.IndicesCount = IndicesCount; + drawCall.Draw.StartIndex = 0; + drawCall.Draw.IndicesCount = IndicesCount; } }; diff --git a/Source/Engine/Terrain/TerrainPatch.cpp b/Source/Engine/Terrain/TerrainPatch.cpp index 552238e9e..133b44c6a 100644 --- a/Source/Engine/Terrain/TerrainPatch.cpp +++ b/Source/Engine/Terrain/TerrainPatch.cpp @@ -64,12 +64,12 @@ void TerrainPatch::Init(Terrain* terrain, int16 x, int16 z) { Chunks[i].Init(this, i % CHUNKS_COUNT_EDGE, i / CHUNKS_COUNT_EDGE); } - Heightmap.Unlink(); + Heightmap = nullptr; for (int32 i = 0; i < TERRAIN_MAX_SPLATMAPS_COUNT; i++) { - Splatmap[i].Unlink(); + Splatmap[i] = nullptr; } - _heightfield.Unlink(); + _heightfield = nullptr; #if TERRAIN_UPDATING _cachedHeightMap.Resize(0); _cachedHolesMask.Resize(0); diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Texture.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Texture.cpp index 90103c165..32af18364 100644 --- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Texture.cpp +++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Texture.cpp @@ -163,27 +163,4 @@ void MaterialGenerator::linearizeSceneDepth(Node* caller, const Value& depth, Va value = writeLocal(VariantType::Float, String::Format(TEXT("ViewInfo.w / ({0}.x - ViewInfo.z)"), depth.Value), caller); } -byte MaterialGenerator::getStartSrvRegister(MaterialLayer* baseLayer) -{ - // Note: this must match material templates - switch (baseLayer->Domain) - { - case MaterialDomain::Surface: - return baseLayer->BlendMode == MaterialBlendMode::Transparent ? 3 : 3; - case MaterialDomain::PostProcess: - return 0; - case MaterialDomain::Decal: - return 1; - case MaterialDomain::GUI: - return 0; - case MaterialDomain::Terrain: - return 6; - case MaterialDomain::Particle: - return 5; - default: - CRASH; - return 0; - } -} - #endif diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp index 57f669f7f..23779e3e1 100644 --- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp +++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp @@ -4,7 +4,9 @@ #include "MaterialGenerator.h" #include "Engine/Visject/ShaderGraphUtilities.h" +#include "Engine/Platform/File.h" #include "Engine/Graphics/Materials/MaterialShader.h" +#include "Engine/Graphics/Materials/MaterialShaderFeatures.h" /// /// Material shader source code template has special marks for generated code. @@ -20,10 +22,86 @@ enum MaterialTemplateInputsMapping In_GetMaterialVS = 5, In_GetMaterialDS = 6, In_Includes = 7, + In_Utilities = 8, + In_Shaders = 9, In_MAX }; +/// +/// Material shader feature source code template has special marks for generated code. Each starts with '@' char and index of the mapped string. +/// +enum class FeatureTemplateInputsMapping +{ + Defines = 0, + Includes = 1, + Constants = 2, + Resources = 3, + Utilities = 4, + Shaders = 5, + MAX +}; + +struct FeatureData +{ + MaterialShaderFeature::GeneratorData Data; + String Inputs[(int32)FeatureTemplateInputsMapping::MAX]; + + bool Init(); +}; + +namespace +{ + // Loaded and parsed features data cache + Dictionary Features; +} + +bool FeatureData::Init() +{ + // Load template file + const String path = Globals::EngineContentFolder / TEXT("Editor/MaterialTemplates/") + Data.Template; + String contents; + if (File::ReadAllText(path, contents)) + { + LOG(Error, "Cannot open file {0}", path); + return true; + } + + int32 i = 0; + const int32 length = contents.Length(); + + // Skip until input start + for (; i < length; i++) + { + if (contents[i] == '@') + break; + } + + // Load all inputs + do + { + // Parse input type + i++; + const int32 inIndex = contents[i++] - '0'; + ASSERT_LOW_LAYER(Math::IsInRange(inIndex, 0, (int32)FeatureTemplateInputsMapping::MAX - 1)); + + // Read until next input start + const Char* start = &contents[i]; + for (; i < length; i++) + { + const auto c = contents[i]; + if (c == '@') + break; + } + const Char* end = &contents[i]; + + // Set input + Inputs[inIndex].Set(start, (int32)(end - start)); + } while (i < length); + + return false; +} + MaterialValue MaterialGenerator::getUVs(VariantType::Vector2, TEXT("input.TexCoord")); MaterialValue MaterialGenerator::getTime(VariantType::Float, TEXT("TimeParam")); MaterialValue MaterialGenerator::getNormal(VariantType::Vector3, TEXT("input.TBN[2]")); @@ -54,6 +132,7 @@ bool MaterialGenerator::Generate(WriteStream& source, MaterialInfo& materialInfo ASSERT_LOW_LAYER(_layers.Count() > 0); String inputs[In_MAX]; + Array> features; // Setup and prepare layers _writer.Clear(); @@ -88,6 +167,59 @@ bool MaterialGenerator::Generate(WriteStream& source, MaterialInfo& materialInfo const MaterialGraphBox* layerInputBox = baseLayer->Root->GetBox(0); const bool isLayered = layerInputBox->HasConnection(); + // Initialize features +#define ADD_FEATURE(type) \ + { \ + StringAnsiView typeName(#type, ARRAY_COUNT(#type) - 1); \ + features.Add(typeName); \ + if (!Features.ContainsKey(typeName)) \ + { \ + auto& feature = Features[typeName]; \ + type::Generate(feature.Data); \ + if (feature.Init()) \ + return true; \ + } \ + } + switch (baseLayer->Domain) + { + case MaterialDomain::Surface: + if (materialInfo.TessellationMode != TessellationMethod::None) + ADD_FEATURE(TessellationFeature); + if (materialInfo.BlendMode == MaterialBlendMode::Opaque) + ADD_FEATURE(MotionVectorsFeature); + if (materialInfo.BlendMode == MaterialBlendMode::Opaque) + ADD_FEATURE(LightmapFeature); + if (materialInfo.BlendMode == MaterialBlendMode::Opaque) + ADD_FEATURE(DeferredShadingFeature); + if (materialInfo.BlendMode != MaterialBlendMode::Opaque && (materialInfo.FeaturesFlags & MaterialFeaturesFlags::DisableDistortion) == 0) + ADD_FEATURE(DistortionFeature); + if (materialInfo.BlendMode != MaterialBlendMode::Opaque) + ADD_FEATURE(ForwardShadingFeature); + break; + case MaterialDomain::Terrain: + if (materialInfo.TessellationMode != TessellationMethod::None) + ADD_FEATURE(TessellationFeature); + ADD_FEATURE(LightmapFeature); + ADD_FEATURE(DeferredShadingFeature); + break; + case MaterialDomain::Particle: + if (materialInfo.BlendMode != MaterialBlendMode::Opaque && (materialInfo.FeaturesFlags & MaterialFeaturesFlags::DisableDistortion) == 0) + ADD_FEATURE(DistortionFeature); + ADD_FEATURE(ForwardShadingFeature); + break; + case MaterialDomain::Deformable: + if (materialInfo.TessellationMode != TessellationMethod::None) + ADD_FEATURE(TessellationFeature); + if (materialInfo.BlendMode == MaterialBlendMode::Opaque) + ADD_FEATURE(DeferredShadingFeature); + if (materialInfo.BlendMode != MaterialBlendMode::Opaque) + ADD_FEATURE(ForwardShadingFeature); + break; + default: + break; + } +#undef ADD_FEATURE + // Check if material is using special features and update the metadata flags if (!isLayered) { @@ -105,7 +237,7 @@ bool MaterialGenerator::Generate(WriteStream& source, MaterialInfo& materialInfo { materialVarPS = Value(VariantType::Void, baseLayer->GetVariableName(nullptr)); _writer.Write(TEXT("\tMaterial {0} = (Material)0;\n"), materialVarPS.Value); - if (baseLayer->Domain == MaterialDomain::Surface || baseLayer->Domain == MaterialDomain::Terrain || baseLayer->Domain == MaterialDomain::Particle) + if (baseLayer->Domain == MaterialDomain::Surface || baseLayer->Domain == MaterialDomain::Terrain || baseLayer->Domain == MaterialDomain::Particle || baseLayer->Domain == MaterialDomain::Deformable) { eatMaterialGraphBox(baseLayer, MaterialGraphBoxes::Emissive); eatMaterialGraphBox(baseLayer, MaterialGraphBoxes::Normal); @@ -241,11 +373,13 @@ bool MaterialGenerator::Generate(WriteStream& source, MaterialInfo& materialInfo // Update material usage based on material generator outputs materialInfo.UsageFlags = baseLayer->UsageFlags; +#define WRITE_FEATURES(input) for (auto f : features) _writer.Write(Features[f].Inputs[(int32)FeatureTemplateInputsMapping::input]); // Defines { _writer.Write(TEXT("#define MATERIAL_MASK_THRESHOLD ({0})\n"), baseLayer->MaskThreshold); _writer.Write(TEXT("#define CUSTOM_VERTEX_INTERPOLATORS_COUNT ({0})\n"), _vsToPsInterpolants.Count()); - _writer.Write(TEXT("#define MATERIAL_OPACITY_THRESHOLD ({0})"), baseLayer->OpacityThreshold); + _writer.Write(TEXT("#define MATERIAL_OPACITY_THRESHOLD ({0})\n"), baseLayer->OpacityThreshold); + WRITE_FEATURES(Defines); inputs[In_Defines] = _writer.ToString(); _writer.Clear(); } @@ -253,31 +387,89 @@ bool MaterialGenerator::Generate(WriteStream& source, MaterialInfo& materialInfo // Includes { for (auto& include : _includes) - { _writer.Write(TEXT("#include \"{0}\"\n"), include.Item); - } + WRITE_FEATURES(Includes); inputs[In_Includes] = _writer.ToString(); _writer.Clear(); } - // Check if material is using any parameters - if (_parameters.HasItems()) + // Constants { - ShaderGraphUtilities::GenerateShaderConstantBuffer(_writer, _parameters); + WRITE_FEATURES(Constants); + if (_parameters.HasItems()) + ShaderGraphUtilities::GenerateShaderConstantBuffer(_writer, _parameters); inputs[In_Constants] = _writer.ToString(); _writer.Clear(); + } - const int32 startRegister = getStartSrvRegister(baseLayer); - const auto error = ShaderGraphUtilities::GenerateShaderResources(_writer, _parameters, startRegister); - if (error) + // Resources + { + int32 srv = 0; + switch (baseLayer->Domain) { - OnError(nullptr, nullptr, error); - return true; + case MaterialDomain::Surface: + srv = 2; // Skinning Bones + Prev Bones + break; + case MaterialDomain::Decal: + srv = 1; // Depth buffer + break; + case MaterialDomain::Terrain: + srv = 3; // Heightmap + 2 splatmaps + break; + case MaterialDomain::Particle: + srv = 2; // Particles data + Sorted indices/Ribbon segments + break; + case MaterialDomain::Deformable: + srv = 1; // Mesh deformation buffer + break; + } + for (auto f : features) + { + const auto& text = Features[f].Inputs[(int32)FeatureTemplateInputsMapping::Resources]; + const Char* str = text.Get(); + int32 prevIdx = 0, idx = 0; + while (true) + { + idx = text.Find(TEXT("__SRV__"), StringSearchCase::CaseSensitive, prevIdx); + if (idx == -1) + break; + int32 len = idx - prevIdx; + _writer.Write(StringView(str, len)); + str += len; + _writer.Write(StringUtils::ToString(srv)); + srv++; + str += ARRAY_COUNT("__SRV__") - 1; + prevIdx = idx + ARRAY_COUNT("__SRV__") - 1; + } + _writer.Write(StringView(str)); + } + if (_parameters.HasItems()) + { + const auto error = ShaderGraphUtilities::GenerateShaderResources(_writer, _parameters, srv); + if (error) + { + OnError(nullptr, nullptr, error); + return true; + } } inputs[In_ShaderResources] = _writer.ToString(); _writer.Clear(); } + // Utilities + { + WRITE_FEATURES(Utilities); + inputs[In_Utilities] = _writer.ToString(); + _writer.Clear(); + } + + // Shaders + { + WRITE_FEATURES(Shaders); + inputs[In_Shaders] = _writer.ToString(); + _writer.Clear(); + } + // Save material parameters data if (_parameters.HasItems()) MaterialParams::Save(parametersData, &_parameters); @@ -292,10 +484,7 @@ bool MaterialGenerator::Generate(WriteStream& source, MaterialInfo& materialInfo switch (materialInfo.Domain) { case MaterialDomain::Surface: - if (materialInfo.BlendMode == MaterialBlendMode::Opaque) - path /= TEXT("SurfaceDeferred.shader"); - else - path /= TEXT("SurfaceForward.shader"); + path /= TEXT("Surface.shader"); break; case MaterialDomain::PostProcess: path /= TEXT("PostProcess.shader"); @@ -312,20 +501,22 @@ bool MaterialGenerator::Generate(WriteStream& source, MaterialInfo& materialInfo case MaterialDomain::Particle: path /= TEXT("Particle.shader"); break; + case MaterialDomain::Deformable: + path /= TEXT("Deformable.shader"); + break; default: LOG(Warning, "Unknown material domain."); return true; } - auto file = FileReadStream::Open(path); if (file == nullptr) { - LOG(Warning, "Cannot load material base source code."); + LOG(Error, "Cannot open file {0}", path); return true; } // Format template - uint32 length = file->GetLength(); + const uint32 length = file->GetLength(); Array tmp; for (uint32 i = 0; i < length; i++) { diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.h b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.h index 804955618..bee1efca4 100644 --- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.h +++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.h @@ -210,8 +210,6 @@ public: static MaterialGraphBoxesMapping MaterialGraphBoxesMappings[]; static const MaterialGraphBoxesMapping& GetMaterialRootNodeBox(MaterialGraphBoxes box); - - static byte getStartSrvRegister(MaterialLayer* baseLayer); }; #endif diff --git a/Source/Engine/UI/GUI/Panels/DropPanel.cs b/Source/Engine/UI/GUI/Panels/DropPanel.cs index fa5ff7f59..04493c7ef 100644 --- a/Source/Engine/UI/GUI/Panels/DropPanel.cs +++ b/Source/Engine/UI/GUI/Panels/DropPanel.cs @@ -31,9 +31,14 @@ namespace FlaxEngine.GUI protected bool _mouseOverHeader; /// - /// The 'mouse down' flag (over header). + /// The 'mouse down' flag (over header) for the left mouse button. /// - protected bool _mouseDown; + protected bool _mouseButtonLeftDown; + + /// + /// The 'mouse down' flag (over header) for the right mouse button. + /// + protected bool _mouseButtonRightDown; /// /// The animation progress (normalized). @@ -126,6 +131,11 @@ namespace FlaxEngine.GUI [EditorDisplay("Style"), EditorOrder(2000)] public bool EnableDropDownIcon { get; set; } + /// + /// Occurs when mouse right-clicks over the header. + /// + public event Action MouseButtonRightClicked; + /// /// Occurs when drop panel is opened or closed. /// @@ -430,10 +440,14 @@ namespace FlaxEngine.GUI return true; _mouseOverHeader = HeaderRectangle.Contains(location); - if (button == MouseButton.Left && _mouseOverHeader) { - _mouseDown = true; + _mouseButtonLeftDown = true; + return true; + } + if (button == MouseButton.Right && _mouseOverHeader) + { + _mouseButtonRightDown = true; return true; } @@ -455,16 +469,17 @@ namespace FlaxEngine.GUI return true; _mouseOverHeader = HeaderRectangle.Contains(location); - - if (button == MouseButton.Left && _mouseDown) + if (button == MouseButton.Left && _mouseButtonLeftDown) { - _mouseDown = false; - + _mouseButtonLeftDown = false; if (_mouseOverHeader) - { Toggle(); - } - + return true; + } + if (button == MouseButton.Right && _mouseButtonRightDown) + { + _mouseButtonRightDown = false; + MouseButtonRightClicked?.Invoke(this, location); return true; } @@ -474,7 +489,8 @@ namespace FlaxEngine.GUI /// public override void OnMouseLeave() { - _mouseDown = false; + _mouseButtonLeftDown = false; + _mouseButtonRightDown = false; _mouseOverHeader = false; base.OnMouseLeave(); diff --git a/Source/Engine/UI/TextRender.cpp b/Source/Engine/UI/TextRender.cpp index 958aced8b..8e00c2cc3 100644 --- a/Source/Engine/UI/TextRender.cpp +++ b/Source/Engine/UI/TextRender.cpp @@ -319,15 +319,15 @@ void TextRender::Draw(RenderContext& renderContext) // Setup draw call DrawCall drawCall; drawCall.World = _world; - drawCall.PrevWorld = _drawState.PrevWorld; drawCall.ObjectPosition = drawCall.World.GetTranslation(); - drawCall.GeometrySize = _localBox.GetSize(); - drawCall.Lightmap = nullptr; - drawCall.LightmapUVsArea = Rectangle::Empty; - drawCall.Skinning = nullptr; + drawCall.Surface.GeometrySize = _localBox.GetSize(); + drawCall.Surface.PrevWorld = _drawState.PrevWorld; + drawCall.Surface.Lightmap = nullptr; + drawCall.Surface.LightmapUVsArea = Rectangle::Empty; + drawCall.Surface.Skinning = nullptr; + drawCall.Surface.LODDitherFactor = 0.0f; drawCall.WorldDeterminantSign = Math::FloatSelect(_world.RotDeterminant(), 1, -1); drawCall.PerInstanceRandom = GetPerInstanceRandom(); - drawCall.LODDitherFactor = 0.0f; drawCall.Geometry.IndexBuffer = _ib.GetBuffer(); drawCall.Geometry.VertexBuffers[0] = _vb0.GetBuffer(); drawCall.Geometry.VertexBuffers[1] = _vb1.GetBuffer(); @@ -336,14 +336,12 @@ void TextRender::Draw(RenderContext& renderContext) drawCall.Geometry.VertexBuffersOffsets[1] = 0; drawCall.Geometry.VertexBuffersOffsets[2] = 0; drawCall.InstanceCount = 1; - drawCall.IndirectArgsBuffer = nullptr; - drawCall.IndirectArgsOffset = 0; // Submit draw calls for (const auto& e : _drawChunks) { - drawCall.Geometry.IndicesCount = e.IndicesCount; - drawCall.Geometry.StartIndex = e.StartIndex; + drawCall.Draw.IndicesCount = e.IndicesCount; + drawCall.Draw.StartIndex = e.StartIndex; drawCall.Material = e.Material; renderContext.List->AddDrawCall(drawModes, GetStaticFlags(), drawCall, true); } diff --git a/Source/Engine/UI/UIControl.cpp b/Source/Engine/UI/UIControl.cpp index 131c04298..33fe8a93b 100644 --- a/Source/Engine/UI/UIControl.cpp +++ b/Source/Engine/UI/UIControl.cpp @@ -156,6 +156,9 @@ void UIControl::OnParentChanged() // Base Actor::OnParentChanged(); + if (!IsDuringPlay()) + return; + UICONTROL_INVOKE(ParentChanged); } diff --git a/Source/Engine/Utilities/State.cs b/Source/Engine/Utilities/State.cs index f77308878..a0797ec0e 100644 --- a/Source/Engine/Utilities/State.cs +++ b/Source/Engine/Utilities/State.cs @@ -12,9 +12,6 @@ namespace FlaxEngine.Utilities /// /// Gets the state machine. /// - /// - /// The state machine. - /// public StateMachine StateMachine => owner; /// diff --git a/Source/Engine/Utilities/StateMachine.cs b/Source/Engine/Utilities/StateMachine.cs index 9966e3378..250d2fbe6 100644 --- a/Source/Engine/Utilities/StateMachine.cs +++ b/Source/Engine/Utilities/StateMachine.cs @@ -35,6 +35,11 @@ namespace FlaxEngine.Utilities /// public event Action StateChanged; + /// + /// Gets the states (read-only). + /// + public IReadOnlyList States => states; + /// /// Gets state of given type. /// diff --git a/Source/Engine/Utilities/TextWriter.h b/Source/Engine/Utilities/TextWriter.h index d2702bf3c..54f2e996a 100644 --- a/Source/Engine/Utilities/TextWriter.h +++ b/Source/Engine/Utilities/TextWriter.h @@ -4,6 +4,8 @@ #include "Engine/Core/NonCopyable.h" #include "Engine/Core/Formatting.h" +#include "Engine/Core/Types/String.h" +#include "Engine/Core/Types/StringView.h" #include "Engine/Serialization/MemoryWriteStream.h" /// @@ -97,6 +99,24 @@ public: WriteLine(); } + /// + /// Write text to the buffer + /// + /// Data + void Write(const StringViewBase& text) + { + _buffer.WriteBytes((void*)text.Get(), text.Length() * sizeof(CharType)); + } + + /// + /// Write text to the buffer + /// + /// Data + void Write(const StringBase& text) + { + _buffer.WriteBytes((void*)text.Get(), text.Length() * sizeof(CharType)); + } + /// /// Write text to the buffer /// diff --git a/Source/Engine/Visject/ShaderGraphUtilities.cpp b/Source/Engine/Visject/ShaderGraphUtilities.cpp index c84a500e4..079b6e602 100644 --- a/Source/Engine/Visject/ShaderGraphUtilities.cpp +++ b/Source/Engine/Visject/ShaderGraphUtilities.cpp @@ -177,10 +177,6 @@ const Char* ShaderGraphUtilities::GenerateShaderResources(TextWriterUnicode& wri } } } - - if (startRegister != registerIndex) - writer.WriteLine(); - return nullptr; } diff --git a/Source/Shaders/MaterialCommon.hlsl b/Source/Shaders/MaterialCommon.hlsl index 8f47124dc..8fc981cd7 100644 --- a/Source/Shaders/MaterialCommon.hlsl +++ b/Source/Shaders/MaterialCommon.hlsl @@ -61,9 +61,6 @@ #ifndef MAX_TESSELLATION_FACTOR #define MAX_TESSELLATION_FACTOR 15 #endif -#ifndef IS_MOTION_VECTORS_PASS - #define IS_MOTION_VECTORS_PASS 0 -#endif #ifndef PER_BONE_MOTION_BLUR #define PER_BONE_MOTION_BLUR 0 #endif @@ -164,6 +161,12 @@ float3x3 CalcTangentBasisFromWorldNormal(float3 normal) return float3x3(tangent, bitangent, normal); } +float3x3 CalcTangentBasis(float3 normal, float4 tangent) +{ + float3 bitangent = cross(normal, tangent.xyz) * tangent.w; + return float3x3(tangent.xyz, bitangent, normal); +} + // [Jimenez et al. 2016, "Practical Realtime Strategies for Accurate Indirect Occlusion"] float3 AOMultiBounce(float visibility, float3 albedo) { @@ -173,24 +176,4 @@ float3 AOMultiBounce(float visibility, float3 albedo) return max(visibility, ((visibility * a + b) * visibility + c) * visibility); } -#if CAN_USE_LIGHTMAP - -// Evaluates the H-Basis coefficients in the tangent space normal direction -float3 GetHBasisIrradiance(in float3 n, in float3 h0, in float3 h1, in float3 h2, in float3 h3) -{ - float3 color = 0.0f; - - // Band 0 - color += h0 * (1.0f / sqrt(2.0f * PI)); - - // Band 1 - color += h1 * -sqrt(1.5f / PI) * n.y; - color += h2 * sqrt(1.5f / PI) * (2 * n.z - 1.0f); - color += h3 * -sqrt(1.5f / PI) * n.x; - - return color; -} - -#endif - #endif diff --git a/Source/ThirdParty/VulkanMemoryAllocator/LICENSE.txt b/Source/ThirdParty/VulkanMemoryAllocator/LICENSE.txt index 67b0d01dc..71e824f80 100644 --- a/Source/ThirdParty/VulkanMemoryAllocator/LICENSE.txt +++ b/Source/ThirdParty/VulkanMemoryAllocator/LICENSE.txt @@ -1,4 +1,4 @@ -Copyright (c) 2017-2019 Advanced Micro Devices, Inc. All rights reserved. +Copyright (c) 2017-2021 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 diff --git a/Source/ThirdParty/VulkanMemoryAllocator/vk_mem_alloc.h b/Source/ThirdParty/VulkanMemoryAllocator/vk_mem_alloc.h index 12256dee6..a410c63e4 100644 --- a/Source/ThirdParty/VulkanMemoryAllocator/vk_mem_alloc.h +++ b/Source/ThirdParty/VulkanMemoryAllocator/vk_mem_alloc.h @@ -1,5 +1,5 @@ // -// Copyright (c) 2017-2019 Advanced Micro Devices, Inc. All rights reserved. +// Copyright (c) 2017-2021 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 @@ -23,15 +23,11 @@ #ifndef AMD_VULKAN_MEMORY_ALLOCATOR_H #define AMD_VULKAN_MEMORY_ALLOCATOR_H -#ifdef __cplusplus -extern "C" { -#endif - /** \mainpage Vulkan Memory Allocator -Version 2.2.1-development (2018-12-14) +Version 3.0.0-development (2021-02-16) -Copyright (c) 2017-2018 Advanced Micro Devices, Inc. All rights reserved. \n +Copyright (c) 2017-2021 Advanced Micro Devices, Inc. All rights reserved. \n License: MIT Documentation of all members: vk_mem_alloc.h @@ -52,8 +48,12 @@ Documentation of all members: vk_mem_alloc.h - \subpage memory_mapping - [Mapping functions](@ref memory_mapping_mapping_functions) - [Persistently mapped memory](@ref memory_mapping_persistently_mapped_memory) - - [Cache control](@ref memory_mapping_cache_control) + - [Cache flush and invalidate](@ref memory_mapping_cache_control) - [Finding out if memory is mappable](@ref memory_mapping_finding_if_memory_mappable) + - \subpage staying_within_budget + - [Querying for budget](@ref staying_within_budget_querying_for_budget) + - [Controlling memory usage](@ref staying_within_budget_controlling_memory_usage) + - \subpage resource_aliasing - \subpage custom_memory_pools - [Choosing memory type index](@ref custom_memory_pools_MemTypeIndex) - [Linear allocation algorithm](@ref linear_algorithm) @@ -63,10 +63,10 @@ Documentation of all members: vk_mem_alloc.h - [Ring buffer](@ref linear_algorithm_ring_buffer) - [Buddy allocation algorithm](@ref buddy_algorithm) - \subpage defragmentation - - [Defragmenting CPU memory](@ref defragmentation_cpu) - - [Defragmenting GPU memory](@ref defragmentation_gpu) - - [Additional notes](@ref defragmentation_additional_notes) - - [Writing custom allocation algorithm](@ref defragmentation_custom_algorithm) + - [Defragmenting CPU memory](@ref defragmentation_cpu) + - [Defragmenting GPU memory](@ref defragmentation_gpu) + - [Additional notes](@ref defragmentation_additional_notes) + - [Writing custom allocation algorithm](@ref defragmentation_custom_algorithm) - \subpage lost_allocations - \subpage statistics - [Numeric statistics](@ref statistics_numeric_statistics) @@ -80,6 +80,7 @@ Documentation of all members: vk_mem_alloc.h - [Corruption detection](@ref debugging_memory_usage_corruption_detection) - \subpage record_and_replay - \subpage usage_patterns + - [Common mistakes](@ref usage_patterns_common_mistakes) - [Simple patterns](@ref usage_patterns_simple) - [Advanced patterns](@ref usage_patterns_advanced) - \subpage configuration @@ -88,6 +89,8 @@ Documentation of all members: vk_mem_alloc.h - [Device memory allocation callbacks](@ref allocation_callbacks) - [Device heap memory limit](@ref heap_memory_limit) - \subpage vk_khr_dedicated_allocation + - \subpage enabling_buffer_device_address + - \subpage vk_amd_device_coherent_memory - \subpage general_considerations - [Thread safety](@ref general_considerations_thread_safety) - [Validation layer warnings](@ref general_considerations_validation_layer_warnings) @@ -139,24 +142,42 @@ before including these headers (like `WIN32_LEAN_AND_MEAN` or `WINVER` for Windows, `VK_USE_PLATFORM_WIN32_KHR` for Vulkan), you must define them before every `#include` of this library. +You may need to configure the way you import Vulkan functions. + +- By default, VMA assumes you you link statically with Vulkan API. If this is not the case, + `#define VMA_STATIC_VULKAN_FUNCTIONS 0` before `#include` of the VMA implementation and use another way. +- You can `#define VMA_DYNAMIC_VULKAN_FUNCTIONS 1` and make sure `vkGetInstanceProcAddr` and `vkGetDeviceProcAddr` globals are defined. + All the remaining Vulkan functions will be fetched automatically. +- Finally, you can provide your own pointers to all Vulkan functions needed by VMA using structure member + VmaAllocatorCreateInfo::pVulkanFunctions, if you fetched them in some custom way e.g. using some loader like [Volk](https://github.com/zeux/volk). + \section quick_start_initialization Initialization At program startup: --# Initialize Vulkan to have `VkPhysicalDevice` and `VkDevice` object. +-# Initialize Vulkan to have `VkPhysicalDevice`, `VkDevice` and `VkInstance` object. -# Fill VmaAllocatorCreateInfo structure and create #VmaAllocator object by calling vmaCreateAllocator(). \code VmaAllocatorCreateInfo allocatorInfo = {}; +allocatorInfo.vulkanApiVersion = VK_API_VERSION_1_2; allocatorInfo.physicalDevice = physicalDevice; allocatorInfo.device = device; +allocatorInfo.instance = instance; VmaAllocator allocator; vmaCreateAllocator(&allocatorInfo, &allocator); \endcode +Only members `physicalDevice`, `device`, `instance` are required. +However, you should inform the library which Vulkan version do you use by setting +VmaAllocatorCreateInfo::vulkanApiVersion and which extensions did you enable +by setting VmaAllocatorCreateInfo::flags (like #VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT for VK_KHR_buffer_device_address). +Otherwise, VMA would use only features of Vulkan 1.0 core with no extensions. + + \section quick_start_resource_allocation Resource allocation When you want to create a buffer or image: @@ -206,7 +227,8 @@ You can also combine multiple methods. -# If you already have a buffer or an image created, you want to allocate memory for it and then you will bind it yourself, you can use function vmaAllocateMemoryForBuffer(), vmaAllocateMemoryForImage(). - For binding you should use functions: vmaBindBufferMemory(), vmaBindImageMemory(). + For binding you should use functions: vmaBindBufferMemory(), vmaBindImageMemory() + or their extended versions: vmaBindBufferMemory2(), vmaBindImageMemory2(). -# If you want to create a buffer or an image, allocate memory for it and bind them together, all in one call, you can use function vmaCreateBuffer(), vmaCreateImage(). This is the easiest and recommended way to use this library. @@ -297,6 +319,7 @@ VmaAllocation allocation; vmaCreateBuffer(allocator, &bufferInfo, &allocInfo, &buffer, &allocation, nullptr); \endcode + \section choosing_memory_type_custom_memory_pools Custom memory pools If you allocate from custom memory pool, all the ways of specifying memory @@ -421,16 +444,18 @@ There are some exceptions though, when you should consider mapping memory only f which requires unmapping before GPU can see updated texture. - Keeping many large memory blocks mapped may impact performance or stability of some debugging tools. -\section memory_mapping_cache_control Cache control - +\section memory_mapping_cache_control Cache flush and invalidate + Memory in Vulkan doesn't need to be unmapped before using it on GPU, but unless a memory types has `VK_MEMORY_PROPERTY_HOST_COHERENT_BIT` flag set, -you need to manually invalidate cache before reading of mapped pointer -and flush cache after writing to mapped pointer. +you need to manually **invalidate** cache before reading of mapped pointer +and **flush** cache after writing to mapped pointer. +Map/unmap operations don't do that automatically. Vulkan provides following functions for this purpose `vkFlushMappedMemoryRanges()`, `vkInvalidateMappedMemoryRanges()`, but this library provides more convenient functions that refer to given allocation object: vmaFlushAllocation(), -vmaInvalidateAllocation(). +vmaInvalidateAllocation(), +or multiple objects at once: vmaFlushAllocations(), vmaInvalidateAllocations(). Regions of memory specified for flush/invalidate must be aligned to `VkPhysicalDeviceLimits::nonCoherentAtomSize`. This is automatically ensured by the library. @@ -440,7 +465,7 @@ within blocks are aligned to this value, so their offsets are always multiply of Please note that memory allocated with #VMA_MEMORY_USAGE_CPU_ONLY is guaranteed to be `HOST_COHERENT`. -Also, Windows drivers from all 3 PC GPU vendors (AMD, Intel, NVIDIA) +Also, Windows drivers from all 3 **PC** GPU vendors (AMD, Intel, NVIDIA) currently provide `HOST_COHERENT` flag on all memory types that are `HOST_VISIBLE`, so on this platform you may not need to bother. @@ -472,7 +497,7 @@ vmaCreateBuffer(allocator, &bufCreateInfo, &allocCreateInfo, &buf, &alloc, &allo VkMemoryPropertyFlags memFlags; vmaGetMemoryTypeProperties(allocator, allocInfo.memoryType, &memFlags); -if((memFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) == 0) +if((memFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) != 0) { // Allocation ended up in mappable memory. You can map it and access it directly. void* mappedData; @@ -507,7 +532,7 @@ VmaAllocation alloc; VmaAllocationInfo allocInfo; vmaCreateBuffer(allocator, &bufCreateInfo, &allocCreateInfo, &buf, &alloc, &allocInfo); -if(allocInfo.pUserData != nullptr) +if(allocInfo.pMappedData != nullptr) { // Allocation ended up in mappable memory. // It's persistently mapped. You can access it directly. @@ -521,6 +546,186 @@ else \endcode +\page staying_within_budget Staying within budget + +When developing a graphics-intensive game or program, it is important to avoid allocating +more GPU memory than it's physically available. When the memory is over-committed, +various bad things can happen, depending on the specific GPU, graphics driver, and +operating system: + +- It may just work without any problems. +- The application may slow down because some memory blocks are moved to system RAM + and the GPU has to access them through PCI Express bus. +- A new allocation may take very long time to complete, even few seconds, and possibly + freeze entire system. +- The new allocation may fail with `VK_ERROR_OUT_OF_DEVICE_MEMORY`. +- It may even result in GPU crash (TDR), observed as `VK_ERROR_DEVICE_LOST` + returned somewhere later. + +\section staying_within_budget_querying_for_budget Querying for budget + +To query for current memory usage and available budget, use function vmaGetBudget(). +Returned structure #VmaBudget contains quantities expressed in bytes, per Vulkan memory heap. + +Please note that this function returns different information and works faster than +vmaCalculateStats(). vmaGetBudget() can be called every frame or even before every +allocation, while vmaCalculateStats() is intended to be used rarely, +only to obtain statistical information, e.g. for debugging purposes. + +It is recommended to use VK_EXT_memory_budget device extension to obtain information +about the budget from Vulkan device. VMA is able to use this extension automatically. +When not enabled, the allocator behaves same way, but then it estimates current usage +and available budget based on its internal information and Vulkan memory heap sizes, +which may be less precise. In order to use this extension: + +1. Make sure extensions VK_EXT_memory_budget and VK_KHR_get_physical_device_properties2 + required by it are available and enable them. Please note that the first is a device + extension and the second is instance extension! +2. Use flag #VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT when creating #VmaAllocator object. +3. Make sure to call vmaSetCurrentFrameIndex() every frame. Budget is queried from + Vulkan inside of it to avoid overhead of querying it with every allocation. + +\section staying_within_budget_controlling_memory_usage Controlling memory usage + +There are many ways in which you can try to stay within the budget. + +First, when making new allocation requires allocating a new memory block, the library +tries not to exceed the budget automatically. If a block with default recommended size +(e.g. 256 MB) would go over budget, a smaller block is allocated, possibly even +dedicated memory for just this resource. + +If the size of the requested resource plus current memory usage is more than the +budget, by default the library still tries to create it, leaving it to the Vulkan +implementation whether the allocation succeeds or fails. You can change this behavior +by using #VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT flag. With it, the allocation is +not made if it would exceed the budget or if the budget is already exceeded. +Some other allocations become lost instead to make room for it, if the mechanism of +[lost allocations](@ref lost_allocations) is used. +If that is not possible, the allocation fails with `VK_ERROR_OUT_OF_DEVICE_MEMORY`. +Example usage pattern may be to pass the #VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT flag +when creating resources that are not essential for the application (e.g. the texture +of a specific object) and not to pass it when creating critically important resources +(e.g. render targets). + +Finally, you can also use #VMA_ALLOCATION_CREATE_NEVER_ALLOCATE_BIT flag to make sure +a new allocation is created only when it fits inside one of the existing memory blocks. +If it would require to allocate a new block, if fails instead with `VK_ERROR_OUT_OF_DEVICE_MEMORY`. +This also ensures that the function call is very fast because it never goes to Vulkan +to obtain a new block. + +Please note that creating \ref custom_memory_pools with VmaPoolCreateInfo::minBlockCount +set to more than 0 will try to allocate memory blocks without checking whether they +fit within budget. + + +\page resource_aliasing Resource aliasing (overlap) + +New explicit graphics APIs (Vulkan and Direct3D 12), thanks to manual memory +management, give an opportunity to alias (overlap) multiple resources in the +same region of memory - a feature not available in the old APIs (Direct3D 11, OpenGL). +It can be useful to save video memory, but it must be used with caution. + +For example, if you know the flow of your whole render frame in advance, you +are going to use some intermediate textures or buffers only during a small range of render passes, +and you know these ranges don't overlap in time, you can bind these resources to +the same place in memory, even if they have completely different parameters (width, height, format etc.). + +![Resource aliasing (overlap)](../gfx/Aliasing.png) + +Such scenario is possible using VMA, but you need to create your images manually. +Then you need to calculate parameters of an allocation to be made using formula: + +- allocation size = max(size of each image) +- allocation alignment = max(alignment of each image) +- allocation memoryTypeBits = bitwise AND(memoryTypeBits of each image) + +Following example shows two different images bound to the same place in memory, +allocated to fit largest of them. + +\code +// A 512x512 texture to be sampled. +VkImageCreateInfo img1CreateInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO }; +img1CreateInfo.imageType = VK_IMAGE_TYPE_2D; +img1CreateInfo.extent.width = 512; +img1CreateInfo.extent.height = 512; +img1CreateInfo.extent.depth = 1; +img1CreateInfo.mipLevels = 10; +img1CreateInfo.arrayLayers = 1; +img1CreateInfo.format = VK_FORMAT_R8G8B8A8_SRGB; +img1CreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL; +img1CreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; +img1CreateInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; +img1CreateInfo.samples = VK_SAMPLE_COUNT_1_BIT; + +// A full screen texture to be used as color attachment. +VkImageCreateInfo img2CreateInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO }; +img2CreateInfo.imageType = VK_IMAGE_TYPE_2D; +img2CreateInfo.extent.width = 1920; +img2CreateInfo.extent.height = 1080; +img2CreateInfo.extent.depth = 1; +img2CreateInfo.mipLevels = 1; +img2CreateInfo.arrayLayers = 1; +img2CreateInfo.format = VK_FORMAT_R8G8B8A8_UNORM; +img2CreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL; +img2CreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; +img2CreateInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; +img2CreateInfo.samples = VK_SAMPLE_COUNT_1_BIT; + +VkImage img1; +res = vkCreateImage(device, &img1CreateInfo, nullptr, &img1); +VkImage img2; +res = vkCreateImage(device, &img2CreateInfo, nullptr, &img2); + +VkMemoryRequirements img1MemReq; +vkGetImageMemoryRequirements(device, img1, &img1MemReq); +VkMemoryRequirements img2MemReq; +vkGetImageMemoryRequirements(device, img2, &img2MemReq); + +VkMemoryRequirements finalMemReq = {}; +finalMemReq.size = std::max(img1MemReq.size, img2MemReq.size); +finalMemReq.alignment = std::max(img1MemReq.alignment, img2MemReq.alignment); +finalMemReq.memoryTypeBits = img1MemReq.memoryTypeBits & img2MemReq.memoryTypeBits; +// Validate if(finalMemReq.memoryTypeBits != 0) + +VmaAllocationCreateInfo allocCreateInfo = {}; +allocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; + +VmaAllocation alloc; +res = vmaAllocateMemory(allocator, &finalMemReq, &allocCreateInfo, &alloc, nullptr); + +res = vmaBindImageMemory(allocator, alloc, img1); +res = vmaBindImageMemory(allocator, alloc, img2); + +// You can use img1, img2 here, but not at the same time! + +vmaFreeMemory(allocator, alloc); +vkDestroyImage(allocator, img2, nullptr); +vkDestroyImage(allocator, img1, nullptr); +\endcode + +Remember that using resouces that alias in memory requires proper synchronization. +You need to issue a memory barrier to make sure commands that use `img1` and `img2` +don't overlap on GPU timeline. +You also need to treat a resource after aliasing as uninitialized - containing garbage data. +For example, if you use `img1` and then want to use `img2`, you need to issue +an image memory barrier for `img2` with `oldLayout` = `VK_IMAGE_LAYOUT_UNDEFINED`. + +Additional considerations: + +- Vulkan also allows to interpret contents of memory between aliasing resources consistently in some cases. +See chapter 11.8. "Memory Aliasing" of Vulkan specification or `VK_IMAGE_CREATE_ALIAS_BIT` flag. +- You can create more complex layout where different images and buffers are bound +at different offsets inside one large allocation. For example, one can imagine +a big texture used in some render passes, aliasing with a set of many small buffers +used between in some further passes. To bind a resource at non-zero offset of an allocation, +use vmaBindBufferMemory2() / vmaBindImageMemory2(). +- Before allocating memory for the resources you want to alias, check `memoryTypeBits` +returned in memory requirements of each resource to make sure the bits overlap. +Some GPUs may expose multiple memory types suitable e.g. only for buffers or +images with `COLOR_ATTACHMENT` usage, so the sets of memory types supported by your +resources may be disjoint. Aliasing them is not possible in that case. + + \page custom_memory_pools Custom memory pools A memory pool contains a number of `VkDeviceMemory` blocks. @@ -744,7 +949,7 @@ allocations. To mitigate this problem, you can use defragmentation feature: structure #VmaDefragmentationInfo2, function vmaDefragmentationBegin(), vmaDefragmentationEnd(). -Given set of allocations, +Given set of allocations, this function can move them to compact used memory, ensure more continuous free space and possibly also free some `VkDeviceMemory` blocks. @@ -761,7 +966,8 @@ What it doesn't do, so you need to do it yourself: - Recreate buffers and images that were bound to allocations that were defragmented and bind them with their new places in memory. You must use `vkDestroyBuffer()`, `vkDestroyImage()`, - `vkCreateBuffer()`, `vkCreateImage()` for that purpose and NOT vmaDestroyBuffer(), + `vkCreateBuffer()`, `vkCreateImage()`, vmaBindBufferMemory(), vmaBindImageMemory() + for that purpose and NOT vmaDestroyBuffer(), vmaDestroyImage(), vmaCreateBuffer(), vmaCreateImage(), because you don't need to destroy or create allocation objects! - Recreate views and update descriptors that point to these buffers and images. @@ -809,13 +1015,13 @@ for(uint32_t i = 0; i < allocCount; ++i) // Create new buffer with same parameters. VkBufferCreateInfo bufferInfo = ...; vkCreateBuffer(device, &bufferInfo, nullptr, &buffers[i]); - + // You can make dummy call to vkGetBufferMemoryRequirements here to silence validation layer warning. - + // Bind new buffer to new memory region. Data contained in it is already moved. VmaAllocationInfo allocInfo; vmaGetAllocationInfo(allocator, allocations[i], &allocInfo); - vkBindBufferMemory(device, buffers[i], allocInfo.deviceMemory, allocInfo.offset); + vmaBindBufferMemory(allocator, allocations[i], buffers[i]); } } \endcode @@ -887,13 +1093,13 @@ for(uint32_t i = 0; i < allocCount; ++i) // Create new buffer with same parameters. VkBufferCreateInfo bufferInfo = ...; vkCreateBuffer(device, &bufferInfo, nullptr, &buffers[i]); - + // You can make dummy call to vkGetBufferMemoryRequirements here to silence validation layer warning. - + // Bind new buffer to new memory region. Data contained in it is already moved. VmaAllocationInfo allocInfo; vmaGetAllocationInfo(allocator, allocations[i], &allocInfo); - vkBindBufferMemory(device, buffers[i], allocInfo.deviceMemory, allocInfo.offset); + vmaBindBufferMemory(allocator, allocations[i], buffers[i]); } } \endcode @@ -907,19 +1113,22 @@ in function vmaDefragmentationBegin(). \section defragmentation_additional_notes Additional notes -While using defragmentation, you may experience validation layer warnings, which you just need to ignore. -See [Validation layer warnings](@ref general_considerations_validation_layer_warnings). +It is only legal to defragment allocations bound to: -If you defragment allocations bound to images, these images should be created with -`VK_IMAGE_CREATE_ALIAS_BIT` flag, to make sure that new image created with same -parameters and pointing to data copied to another memory region will interpret -its contents consistently. Otherwise you may experience corrupted data on some -implementations, e.g. due to different pixel swizzling used internally by the graphics driver. +- buffers +- images created with `VK_IMAGE_CREATE_ALIAS_BIT`, `VK_IMAGE_TILING_LINEAR`, and + being currently in `VK_IMAGE_LAYOUT_GENERAL` or `VK_IMAGE_LAYOUT_PREINITIALIZED`. + +Defragmentation of images created with `VK_IMAGE_TILING_OPTIMAL` or in any other +layout may give undefined results. If you defragment allocations bound to images, new images to be bound to new memory region after defragmentation should be created with `VK_IMAGE_LAYOUT_PREINITIALIZED` -and then transitioned to their original layout from before defragmentation using -an image memory barrier. +and then transitioned to their original layout from before defragmentation if +needed using an image memory barrier. + +While using defragmentation, you may experience validation layer warnings, which you just need to ignore. +See [Validation layer warnings](@ref general_considerations_validation_layer_warnings). Please don't expect memory to be fully compacted after defragmentation. Algorithms inside are based on some heuristics that try to maximize number of Vulkan @@ -1186,6 +1395,9 @@ printf("Image name: %s\n", imageName); That string is also printed in JSON report created by vmaBuildStatsString(). +\note Passing string name to VMA allocation doesn't automatically set it to the Vulkan buffer or image created with it. +You must do it manually using an extension like VK_EXT_debug_utils, which is independent of this library. + \page debugging_memory_usage Debugging incorrect memory usage @@ -1277,7 +1489,7 @@ which indicates a serious bug. You can also explicitly request checking margins of all allocations in all memory blocks that belong to specified memory types by using function vmaCheckCorruption(), -or in memory blocks that belong to specified custom pool, by using function +or in memory blocks that belong to specified custom pool, by using function vmaCheckPoolCorruption(). Margin validation (corruption detection) works only for memory types that are @@ -1301,6 +1513,13 @@ application. It can be useful to: \section record_and_replay_usage Usage +Recording functionality is disabled by default. +To enable it, define following macro before every include of this library: + +\code +#define VMA_RECORDING_ENABLED 1 +\endcode + To record sequence of calls to a file: Fill in VmaAllocatorCreateInfo::pRecordSettings member while creating #VmaAllocator object. File is opened and written during whole lifetime of the allocator. @@ -1327,7 +1546,6 @@ It's a human-readable, text file in CSV format (Comma Separated Values). coded and tested only on Windows. Inclusion of recording code is driven by `VMA_RECORDING_ENABLED` macro. Support for other platforms should be easy to add. Contributions are welcomed. -- Currently calls to vmaDefragment() function are not recorded. \page usage_patterns Recommended usage patterns @@ -1336,6 +1554,27 @@ See also slides from talk: [Sawicki, Adam. Advanced Graphics Techniques Tutorial: Memory management in Vulkan and DX12. Game Developers Conference, 2018](https://www.gdcvault.com/play/1025458/Advanced-Graphics-Techniques-Tutorial-New) +\section usage_patterns_common_mistakes Common mistakes + +Use of CPU_TO_GPU instead of CPU_ONLY memory + +#VMA_MEMORY_USAGE_CPU_TO_GPU is recommended only for resources that will be +mapped and written by the CPU, as well as read directly by the GPU - like some +buffers or textures updated every frame (dynamic). If you create a staging copy +of a resource to be written by CPU and then used as a source of transfer to +another resource placed in the GPU memory, that staging resource should be +created with #VMA_MEMORY_USAGE_CPU_ONLY. Please read the descriptions of these +enums carefully for details. + +Unnecessary use of custom pools + +\ref custom_memory_pools may be useful for special purposes - when you want to +keep certain type of resources separate e.g. to reserve minimum amount of memory +for them, limit maximum amount of memory they can occupy, or make some of them +push out the other through the mechanism of \ref lost_allocations. For most +resources this is not needed and so it is not recommended to create #VmaPool +objects and allocations out of them. Allocating from the default pool is sufficient. + \section usage_patterns_simple Simple patterns \subsection usage_patterns_simple_render_targets Render targets @@ -1391,6 +1630,7 @@ This is a more complex situation. Different solutions are possible, and the best one depends on specific GPU type, but you can use this simple approach for the start. Prefer to write to such resource sequentially (e.g. using `memcpy`). Don't perform random access or any reads from it on CPU, as it may be very slow. +Also note that textures written directly from the host through a mapped pointer need to be in LINEAR not OPTIMAL layout. \subsection usage_patterns_readback Readback @@ -1423,10 +1663,10 @@ directly instead of submitting explicit transfer (see below). For resources that you frequently write on CPU and read on GPU, many solutions are possible: -# Create one copy in video memory using #VMA_MEMORY_USAGE_GPU_ONLY, - second copy in system memory using #VMA_MEMORY_USAGE_CPU_ONLY and submit explicit tranfer each time. --# Create just single copy using #VMA_MEMORY_USAGE_CPU_TO_GPU, map it and fill it on CPU, + second copy in system memory using #VMA_MEMORY_USAGE_CPU_ONLY and submit explicit transfer each time. +-# Create just a single copy using #VMA_MEMORY_USAGE_CPU_TO_GPU, map it and fill it on CPU, read it directly on GPU. --# Create just single copy using #VMA_MEMORY_USAGE_CPU_ONLY, map it and fill it on CPU, +-# Create just a single copy using #VMA_MEMORY_USAGE_CPU_ONLY, map it and fill it on CPU, read it directly on GPU. Which solution is the most efficient depends on your resource and especially on the GPU. @@ -1454,6 +1694,10 @@ solutions are possible: You should take some measurements to decide which option is faster in case of your specific resource. +Note that textures accessed directly from the host through a mapped pointer need to be in LINEAR layout, +which may slow down their usage on the device. +Textures accessed only by the device and transfer operations can use OPTIMAL layout. + If you don't want to specialize your code for specific types of GPUs, you can still make an simple optimization for cases when your resource ends up in mappable memory to use it directly in this case instead of creating CPU-side staging copy. @@ -1469,14 +1713,38 @@ mutex, atomic etc. The library uses its own implementation of containers by default, but you can switch to using STL containers instead. +For example, define `VMA_ASSERT(expr)` before including the library to provide +custom implementation of the assertion, compatible with your project. +By default it is defined to standard C `assert(expr)` in `_DEBUG` configuration +and empty otherwise. + \section config_Vulkan_functions Pointers to Vulkan functions -The library uses Vulkan functions straight from the `vulkan.h` header by default. -If you want to provide your own pointers to these functions, e.g. fetched using -`vkGetInstanceProcAddr()` and `vkGetDeviceProcAddr()`: +There are multiple ways to import pointers to Vulkan functions in the library. +In the simplest case you don't need to do anything. +If the compilation or linking of your program or the initialization of the #VmaAllocator +doesn't work for you, you can try to reconfigure it. + +First, the allocator tries to fetch pointers to Vulkan functions linked statically, +like this: + +\code +m_VulkanFunctions.vkAllocateMemory = (PFN_vkAllocateMemory)vkAllocateMemory; +\endcode + +If you want to disable this feature, set configuration macro: `#define VMA_STATIC_VULKAN_FUNCTIONS 0`. + +Second, you can provide the pointers yourself by setting member VmaAllocatorCreateInfo::pVulkanFunctions. +You can fetch them e.g. using functions `vkGetInstanceProcAddr` and `vkGetDeviceProcAddr` or +by using a helper library like [volk](https://github.com/zeux/volk). + +Third, VMA tries to fetch remaining pointers that are still null by calling +`vkGetInstanceProcAddr` and `vkGetDeviceProcAddr` on its own. +If you want to disable this feature, set configuration macro: `#define VMA_DYNAMIC_VULKAN_FUNCTIONS 0`. + +Finally, all the function pointers required by the library (considering selected +Vulkan version and enabled extensions) are checked with `VMA_ASSERT` if they are not null. --# Define `VMA_STATIC_VULKAN_FUNCTIONS 0`. --# Provide valid pointers through VmaAllocatorCreateInfo::pVulkanFunctions. \section custom_memory_allocator Custom host memory allocator @@ -1498,11 +1766,11 @@ VmaAllocatorCreateInfo::pDeviceMemoryCallbacks. When device memory of certain heap runs out of free space, new allocations may fail (returning error code) or they may succeed, silently pushing some existing memory blocks from GPU VRAM to system RAM (which degrades performance). This -behavior is implementation-dependant - it depends on GPU vendor and graphics +behavior is implementation-dependent - it depends on GPU vendor and graphics driver. On AMD cards it can be controlled while creating Vulkan device object by using -VK_AMD_memory_allocation_behavior extension, if available. +VK_AMD_memory_overallocation_behavior extension, if available. Alternatively, if you want to test how your program behaves with limited amount of Vulkan device memory available without switching your graphics card to one that really has @@ -1556,11 +1824,115 @@ unaware of it. To learn more about this extension, see: -- [VK_KHR_dedicated_allocation in Vulkan specification](https://www.khronos.org/registry/vulkan/specs/1.0-extensions/html/vkspec.html#VK_KHR_dedicated_allocation) +- [VK_KHR_dedicated_allocation in Vulkan specification](https://www.khronos.org/registry/vulkan/specs/1.2-extensions/html/chap44.html#VK_KHR_dedicated_allocation) - [VK_KHR_dedicated_allocation unofficial manual](http://asawicki.info/articles/VK_KHR_dedicated_allocation.php5) +\page vk_amd_device_coherent_memory VK_AMD_device_coherent_memory + +VK_AMD_device_coherent_memory is a device extension that enables access to +additional memory types with `VK_MEMORY_PROPERTY_DEVICE_COHERENT_BIT_AMD` and +`VK_MEMORY_PROPERTY_DEVICE_UNCACHED_BIT_AMD` flag. It is useful mostly for +allocation of buffers intended for writing "breadcrumb markers" in between passes +or draw calls, which in turn are useful for debugging GPU crash/hang/TDR cases. + +When the extension is available but has not been enabled, Vulkan physical device +still exposes those memory types, but their usage is forbidden. VMA automatically +takes care of that - it returns `VK_ERROR_FEATURE_NOT_PRESENT` when an attempt +to allocate memory of such type is made. + +If you want to use this extension in connection with VMA, follow these steps: + +\section vk_amd_device_coherent_memory_initialization Initialization + +1) Call `vkEnumerateDeviceExtensionProperties` for the physical device. +Check if the extension is supported - if returned array of `VkExtensionProperties` contains "VK_AMD_device_coherent_memory". + +2) Call `vkGetPhysicalDeviceFeatures2` for the physical device instead of old `vkGetPhysicalDeviceFeatures`. +Attach additional structure `VkPhysicalDeviceCoherentMemoryFeaturesAMD` to `VkPhysicalDeviceFeatures2::pNext` to be returned. +Check if the device feature is really supported - check if `VkPhysicalDeviceCoherentMemoryFeaturesAMD::deviceCoherentMemory` is true. + +3) While creating device with `vkCreateDevice`, enable this extension - add "VK_AMD_device_coherent_memory" +to the list passed as `VkDeviceCreateInfo::ppEnabledExtensionNames`. + +4) While creating the device, also don't set `VkDeviceCreateInfo::pEnabledFeatures`. +Fill in `VkPhysicalDeviceFeatures2` structure instead and pass it as `VkDeviceCreateInfo::pNext`. +Enable this device feature - attach additional structure `VkPhysicalDeviceCoherentMemoryFeaturesAMD` to +`VkPhysicalDeviceFeatures2::pNext` and set its member `deviceCoherentMemory` to `VK_TRUE`. + +5) While creating #VmaAllocator with vmaCreateAllocator() inform VMA that you +have enabled this extension and feature - add #VMA_ALLOCATOR_CREATE_AMD_DEVICE_COHERENT_MEMORY_BIT +to VmaAllocatorCreateInfo::flags. + +\section vk_amd_device_coherent_memory_usage Usage + +After following steps described above, you can create VMA allocations and custom pools +out of the special `DEVICE_COHERENT` and `DEVICE_UNCACHED` memory types on eligible +devices. There are multiple ways to do it, for example: + +- You can request or prefer to allocate out of such memory types by adding + `VK_MEMORY_PROPERTY_DEVICE_COHERENT_BIT_AMD` to VmaAllocationCreateInfo::requiredFlags + or VmaAllocationCreateInfo::preferredFlags. Those flags can be freely mixed with + other ways of \ref choosing_memory_type, like setting VmaAllocationCreateInfo::usage. +- If you manually found memory type index to use for this purpose, force allocation + from this specific index by setting VmaAllocationCreateInfo::memoryTypeBits `= 1u << index`. + +\section vk_amd_device_coherent_memory_more_information More information + +To learn more about this extension, see [VK_AMD_device_coherent_memory in Vulkan specification](https://www.khronos.org/registry/vulkan/specs/1.2-extensions/html/chap44.html#VK_AMD_device_coherent_memory) + +Example use of this extension can be found in the code of the sample and test suite +accompanying this library. + + +\page enabling_buffer_device_address Enabling buffer device address + +Device extension VK_KHR_buffer_device_address +allow to fetch raw GPU pointer to a buffer and pass it for usage in a shader code. +It is promoted to core Vulkan 1.2. + +If you want to use this feature in connection with VMA, follow these steps: + +\section enabling_buffer_device_address_initialization Initialization + +1) (For Vulkan version < 1.2) Call `vkEnumerateDeviceExtensionProperties` for the physical device. +Check if the extension is supported - if returned array of `VkExtensionProperties` contains +"VK_KHR_buffer_device_address". + +2) Call `vkGetPhysicalDeviceFeatures2` for the physical device instead of old `vkGetPhysicalDeviceFeatures`. +Attach additional structure `VkPhysicalDeviceBufferDeviceAddressFeatures*` to `VkPhysicalDeviceFeatures2::pNext` to be returned. +Check if the device feature is really supported - check if `VkPhysicalDeviceBufferDeviceAddressFeatures*::bufferDeviceAddress` is true. + +3) (For Vulkan version < 1.2) While creating device with `vkCreateDevice`, enable this extension - add +"VK_KHR_buffer_device_address" to the list passed as `VkDeviceCreateInfo::ppEnabledExtensionNames`. + +4) While creating the device, also don't set `VkDeviceCreateInfo::pEnabledFeatures`. +Fill in `VkPhysicalDeviceFeatures2` structure instead and pass it as `VkDeviceCreateInfo::pNext`. +Enable this device feature - attach additional structure `VkPhysicalDeviceBufferDeviceAddressFeatures*` to +`VkPhysicalDeviceFeatures2::pNext` and set its member `bufferDeviceAddress` to `VK_TRUE`. + +5) While creating #VmaAllocator with vmaCreateAllocator() inform VMA that you +have enabled this feature - add #VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT +to VmaAllocatorCreateInfo::flags. + +\section enabling_buffer_device_address_usage Usage + +After following steps described above, you can create buffers with `VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT*` using VMA. +The library automatically adds `VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT*` to +allocated memory blocks wherever it might be needed. + +Please note that the library supports only `VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT*`. +The second part of this functionality related to "capture and replay" is not supported, +as it is intended for usage in debugging tools like RenderDoc, not in everyday Vulkan usage. + +\section enabling_buffer_device_address_more_information More information + +To learn more about this extension, see [VK_KHR_buffer_device_address in Vulkan specification](https://www.khronos.org/registry/vulkan/specs/1.2-extensions/html/chap46.html#VK_KHR_buffer_device_address) + +Example use of this extension can be found in the code of the sample and test suite +accompanying this library. + \page general_considerations General considerations \section general_considerations_thread_safety Thread safety @@ -1619,10 +1991,15 @@ Features deliberately excluded from the scope of this library: - Data transfer. Uploading (straming) and downloading data of buffers and images between CPU and GPU memory and related synchronization is responsibility of the user. + Defining some "texture" object that would automatically stream its data from a + staging copy in CPU memory to GPU memory would rather be a feature of another, + higher-level library implemented on top of VMA. - Allocations for imported/exported external memory. They tend to require explicit memory type index and dedicated allocation anyway, so they don't interact with main features of this library. Such special purpose allocations should be made manually, using `vkCreateBuffer()` and `vkAllocateMemory()`. +- Sub-allocation of parts of one large buffer. Although recommended as a good practice, + it is the user's responsibility to implement such logic on top of VMA. - Recreation of buffers and images. Although the library has functions for buffer and image creation (vmaCreateBuffer(), vmaCreateImage()), you need to recreate these objects yourself after defragmentation. That's because the big @@ -1632,8 +2009,9 @@ Features deliberately excluded from the scope of this library: objects in CPU memory (not Vulkan memory), allocation failures are not checked and handled gracefully, because that would complicate code significantly and is usually not needed in desktop PC applications anyway. + Success of an allocation is just checked with an assert. - Code free of any compiler warnings. Maintaining the library to compile and - work correctly on so many different platforms is hard enough. Being free of + work correctly on so many different platforms is hard enough. Being free of any warnings, on any version of any compiler, is simply not feasible. - This is a C++ library with C interface. Bindings or ports to any other programming languages are welcomed as external projects and @@ -1641,28 +2019,66 @@ Features deliberately excluded from the scope of this library: */ +#ifdef __cplusplus +extern "C" { +#endif + /* Define this macro to 0/1 to disable/enable support for recording functionality, available through VmaAllocatorCreateInfo::pRecordSettings. */ #ifndef VMA_RECORDING_ENABLED - #ifdef _WIN32 - #define VMA_RECORDING_ENABLED 1 - #else - #define VMA_RECORDING_ENABLED 0 - #endif + #define VMA_RECORDING_ENABLED 0 #endif -#ifndef NOMINMAX +#if !defined(NOMINMAX) && defined(VMA_IMPLEMENTATION) #define NOMINMAX // For windows.h #endif +#if defined(__ANDROID__) && defined(VK_NO_PROTOTYPES) && VMA_STATIC_VULKAN_FUNCTIONS + extern PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr; + extern PFN_vkGetDeviceProcAddr vkGetDeviceProcAddr; + extern PFN_vkGetPhysicalDeviceProperties vkGetPhysicalDeviceProperties; + extern PFN_vkGetPhysicalDeviceMemoryProperties vkGetPhysicalDeviceMemoryProperties; + extern PFN_vkAllocateMemory vkAllocateMemory; + extern PFN_vkFreeMemory vkFreeMemory; + extern PFN_vkMapMemory vkMapMemory; + extern PFN_vkUnmapMemory vkUnmapMemory; + extern PFN_vkFlushMappedMemoryRanges vkFlushMappedMemoryRanges; + extern PFN_vkInvalidateMappedMemoryRanges vkInvalidateMappedMemoryRanges; + extern PFN_vkBindBufferMemory vkBindBufferMemory; + extern PFN_vkBindImageMemory vkBindImageMemory; + extern PFN_vkGetBufferMemoryRequirements vkGetBufferMemoryRequirements; + extern PFN_vkGetImageMemoryRequirements vkGetImageMemoryRequirements; + extern PFN_vkCreateBuffer vkCreateBuffer; + extern PFN_vkDestroyBuffer vkDestroyBuffer; + extern PFN_vkCreateImage vkCreateImage; + extern PFN_vkDestroyImage vkDestroyImage; + extern PFN_vkCmdCopyBuffer vkCmdCopyBuffer; + #if VMA_VULKAN_VERSION >= 1001000 + extern PFN_vkGetBufferMemoryRequirements2 vkGetBufferMemoryRequirements2; + extern PFN_vkGetImageMemoryRequirements2 vkGetImageMemoryRequirements2; + extern PFN_vkBindBufferMemory2 vkBindBufferMemory2; + extern PFN_vkBindImageMemory2 vkBindImageMemory2; + extern PFN_vkGetPhysicalDeviceMemoryProperties2 vkGetPhysicalDeviceMemoryProperties2; + #endif // #if VMA_VULKAN_VERSION >= 1001000 +#endif // #if defined(__ANDROID__) && VMA_STATIC_VULKAN_FUNCTIONS && VK_NO_PROTOTYPES + #ifndef VULKAN_H_ #include #endif -#if VMA_RECORDING_ENABLED - #include +// Define this macro to declare maximum supported Vulkan version in format AAABBBCCC, +// where AAA = major, BBB = minor, CCC = patch. +// If you want to use version > 1.0, it still needs to be enabled via VmaAllocatorCreateInfo::vulkanApiVersion. +#if !defined(VMA_VULKAN_VERSION) + #if defined(VK_VERSION_1_2) + #define VMA_VULKAN_VERSION 1002000 + #elif defined(VK_VERSION_1_1) + #define VMA_VULKAN_VERSION 1001000 + #else + #define VMA_VULKAN_VERSION 1000000 + #endif #endif #if !defined(VMA_DEDICATED_ALLOCATION) @@ -1673,6 +2089,105 @@ available through VmaAllocatorCreateInfo::pRecordSettings. #endif #endif +#if !defined(VMA_BIND_MEMORY2) + #if VK_KHR_bind_memory2 + #define VMA_BIND_MEMORY2 1 + #else + #define VMA_BIND_MEMORY2 0 + #endif +#endif + +#if !defined(VMA_MEMORY_BUDGET) + #if VK_EXT_memory_budget && (VK_KHR_get_physical_device_properties2 || VMA_VULKAN_VERSION >= 1001000) + #define VMA_MEMORY_BUDGET 1 + #else + #define VMA_MEMORY_BUDGET 0 + #endif +#endif + +// Defined to 1 when VK_KHR_buffer_device_address device extension or equivalent core Vulkan 1.2 feature is defined in its headers. +#if !defined(VMA_BUFFER_DEVICE_ADDRESS) + #if VK_KHR_buffer_device_address || VMA_VULKAN_VERSION >= 1002000 + #define VMA_BUFFER_DEVICE_ADDRESS 1 + #else + #define VMA_BUFFER_DEVICE_ADDRESS 0 + #endif +#endif + +// Defined to 1 when VK_EXT_memory_priority device extension is defined in Vulkan headers. +#if !defined(VMA_MEMORY_PRIORITY) + #if VK_EXT_memory_priority + #define VMA_MEMORY_PRIORITY 1 + #else + #define VMA_MEMORY_PRIORITY 0 + #endif +#endif + +// Define these macros to decorate all public functions with additional code, +// before and after returned type, appropriately. This may be useful for +// exporting the functions when compiling VMA as a separate library. Example: +// #define VMA_CALL_PRE __declspec(dllexport) +// #define VMA_CALL_POST __cdecl +#ifndef VMA_CALL_PRE + #define VMA_CALL_PRE +#endif +#ifndef VMA_CALL_POST + #define VMA_CALL_POST +#endif + +// Define this macro to decorate pointers with an attribute specifying the +// length of the array they point to if they are not null. +// +// The length may be one of +// - The name of another parameter in the argument list where the pointer is declared +// - The name of another member in the struct where the pointer is declared +// - The name of a member of a struct type, meaning the value of that member in +// the context of the call. For example +// VMA_LEN_IF_NOT_NULL("VkPhysicalDeviceMemoryProperties::memoryHeapCount"), +// this means the number of memory heaps available in the device associated +// with the VmaAllocator being dealt with. +#ifndef VMA_LEN_IF_NOT_NULL + #define VMA_LEN_IF_NOT_NULL(len) +#endif + +// The VMA_NULLABLE macro is defined to be _Nullable when compiling with Clang. +// see: https://clang.llvm.org/docs/AttributeReference.html#nullable +#ifndef VMA_NULLABLE + #ifdef __clang__ + #define VMA_NULLABLE _Nullable + #else + #define VMA_NULLABLE + #endif +#endif + +// The VMA_NOT_NULL macro is defined to be _Nonnull when compiling with Clang. +// see: https://clang.llvm.org/docs/AttributeReference.html#nonnull +#ifndef VMA_NOT_NULL + #ifdef __clang__ + #define VMA_NOT_NULL _Nonnull + #else + #define VMA_NOT_NULL + #endif +#endif + +// If non-dispatchable handles are represented as pointers then we can give +// then nullability annotations +#ifndef VMA_NOT_NULL_NON_DISPATCHABLE + #if defined(__LP64__) || defined(_WIN64) || (defined(__x86_64__) && !defined(__ILP32__) ) || defined(_M_X64) || defined(__ia64) || defined (_M_IA64) || defined(__aarch64__) || defined(__powerpc64__) + #define VMA_NOT_NULL_NON_DISPATCHABLE VMA_NOT_NULL + #else + #define VMA_NOT_NULL_NON_DISPATCHABLE + #endif +#endif + +#ifndef VMA_NULLABLE_NON_DISPATCHABLE + #if defined(__LP64__) || defined(_WIN64) || (defined(__x86_64__) && !defined(__ILP32__) ) || defined(_M_X64) || defined(__ia64) || defined (_M_IA64) || defined(__aarch64__) || defined(__powerpc64__) + #define VMA_NULLABLE_NON_DISPATCHABLE VMA_NULLABLE + #else + #define VMA_NULLABLE_NON_DISPATCHABLE + #endif +#endif + /** \struct VmaAllocator \brief Represents main object of this library initialized. @@ -1686,16 +2201,18 @@ VK_DEFINE_HANDLE(VmaAllocator) /// Callback function called after successful vkAllocateMemory. typedef void (VKAPI_PTR *PFN_vmaAllocateDeviceMemoryFunction)( - VmaAllocator allocator, - uint32_t memoryType, - VkDeviceMemory memory, - VkDeviceSize size); + VmaAllocator VMA_NOT_NULL allocator, + uint32_t memoryType, + VkDeviceMemory VMA_NOT_NULL_NON_DISPATCHABLE memory, + VkDeviceSize size, + void* VMA_NULLABLE pUserData); /// Callback function called before vkFreeMemory. typedef void (VKAPI_PTR *PFN_vmaFreeDeviceMemoryFunction)( - VmaAllocator allocator, - uint32_t memoryType, - VkDeviceMemory memory, - VkDeviceSize size); + VmaAllocator VMA_NOT_NULL allocator, + uint32_t memoryType, + VkDeviceMemory VMA_NOT_NULL_NON_DISPATCHABLE memory, + VkDeviceSize size, + void* VMA_NULLABLE pUserData); /** \brief Set of callbacks that the library will call for `vkAllocateMemory` and `vkFreeMemory`. @@ -1706,9 +2223,11 @@ Used in VmaAllocatorCreateInfo::pDeviceMemoryCallbacks. */ typedef struct VmaDeviceMemoryCallbacks { /// Optional, can be null. - PFN_vmaAllocateDeviceMemoryFunction pfnAllocate; + PFN_vmaAllocateDeviceMemoryFunction VMA_NULLABLE pfnAllocate; /// Optional, can be null. - PFN_vmaFreeDeviceMemoryFunction pfnFree; + PFN_vmaFreeDeviceMemoryFunction VMA_NULLABLE pfnFree; + /// Optional, can be null. + void* VMA_NULLABLE pUserData; } VmaDeviceMemoryCallbacks; /// Flags for created #VmaAllocator. @@ -1720,6 +2239,9 @@ typedef enum VmaAllocatorCreateFlagBits { VMA_ALLOCATOR_CREATE_EXTERNALLY_SYNCHRONIZED_BIT = 0x00000001, /** \brief Enables usage of VK_KHR_dedicated_allocation extension. + The flag works only if VmaAllocatorCreateInfo::vulkanApiVersion `== VK_API_VERSION_1_0`. + When it's `VK_API_VERSION_1_1`, the flag is ignored because the extension has been promoted to Vulkan 1.1. + Using this extenion will automatically allocate dedicated blocks of memory for some buffers and images instead of suballocating place for them out of bigger memory blocks (as if you explicitly used #VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT @@ -1731,15 +2253,95 @@ typedef enum VmaAllocatorCreateFlagBits { VmaAllocatorCreateInfo::device, and you want them to be used internally by this library: - - VK_KHR_get_memory_requirements2 - - VK_KHR_dedicated_allocation + - VK_KHR_get_memory_requirements2 (device extension) + - VK_KHR_dedicated_allocation (device extension) -When this flag is set, you can experience following warnings reported by Vulkan -validation layer. You can ignore them. + When this flag is set, you can experience following warnings reported by Vulkan + validation layer. You can ignore them. -> vkBindBufferMemory(): Binding memory to buffer 0x2d but vkGetBufferMemoryRequirements() has not been called on that buffer. + > vkBindBufferMemory(): Binding memory to buffer 0x2d but vkGetBufferMemoryRequirements() has not been called on that buffer. */ VMA_ALLOCATOR_CREATE_KHR_DEDICATED_ALLOCATION_BIT = 0x00000002, + /** + Enables usage of VK_KHR_bind_memory2 extension. + + The flag works only if VmaAllocatorCreateInfo::vulkanApiVersion `== VK_API_VERSION_1_0`. + When it's `VK_API_VERSION_1_1`, the flag is ignored because the extension has been promoted to Vulkan 1.1. + + You may set this flag only if you found out that this device extension is supported, + you enabled it while creating Vulkan device passed as VmaAllocatorCreateInfo::device, + and you want it to be used internally by this library. + + The extension provides functions `vkBindBufferMemory2KHR` and `vkBindImageMemory2KHR`, + which allow to pass a chain of `pNext` structures while binding. + This flag is required if you use `pNext` parameter in vmaBindBufferMemory2() or vmaBindImageMemory2(). + */ + VMA_ALLOCATOR_CREATE_KHR_BIND_MEMORY2_BIT = 0x00000004, + /** + Enables usage of VK_EXT_memory_budget extension. + + You may set this flag only if you found out that this device extension is supported, + you enabled it while creating Vulkan device passed as VmaAllocatorCreateInfo::device, + and you want it to be used internally by this library, along with another instance extension + VK_KHR_get_physical_device_properties2, which is required by it (or Vulkan 1.1, where this extension is promoted). + + The extension provides query for current memory usage and budget, which will probably + be more accurate than an estimation used by the library otherwise. + */ + VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT = 0x00000008, + /** + Enables usage of VK_AMD_device_coherent_memory extension. + + You may set this flag only if you: + + - found out that this device extension is supported and enabled it while creating Vulkan device passed as VmaAllocatorCreateInfo::device, + - checked that `VkPhysicalDeviceCoherentMemoryFeaturesAMD::deviceCoherentMemory` is true and set it while creating the Vulkan device, + - want it to be used internally by this library. + + The extension and accompanying device feature provide access to memory types with + `VK_MEMORY_PROPERTY_DEVICE_COHERENT_BIT_AMD` and `VK_MEMORY_PROPERTY_DEVICE_UNCACHED_BIT_AMD` flags. + They are useful mostly for writing breadcrumb markers - a common method for debugging GPU crash/hang/TDR. + + When the extension is not enabled, such memory types are still enumerated, but their usage is illegal. + To protect from this error, if you don't create the allocator with this flag, it will refuse to allocate any memory or create a custom pool in such memory type, + returning `VK_ERROR_FEATURE_NOT_PRESENT`. + */ + VMA_ALLOCATOR_CREATE_AMD_DEVICE_COHERENT_MEMORY_BIT = 0x00000010, + /** + Enables usage of "buffer device address" feature, which allows you to use function + `vkGetBufferDeviceAddress*` to get raw GPU pointer to a buffer and pass it for usage inside a shader. + + You may set this flag only if you: + + 1. (For Vulkan version < 1.2) Found as available and enabled device extension + VK_KHR_buffer_device_address. + This extension is promoted to core Vulkan 1.2. + 2. Found as available and enabled device feature `VkPhysicalDeviceBufferDeviceAddressFeatures::bufferDeviceAddress`. + + When this flag is set, you can create buffers with `VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT` using VMA. + The library automatically adds `VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT` to + allocated memory blocks wherever it might be needed. + + For more information, see documentation chapter \ref enabling_buffer_device_address. + */ + VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT = 0x00000020, + /** + Enables usage of VK_EXT_memory_priority extension in the library. + + You may set this flag only if you found available and enabled this device extension, + along with `VkPhysicalDeviceMemoryPriorityFeaturesEXT::memoryPriority == VK_TRUE`, + while creating Vulkan device passed as VmaAllocatorCreateInfo::device. + + When this flag is used, VmaAllocationCreateInfo::priority and VmaPoolCreateInfo::priority + are used to set priorities of allocated Vulkan memory. Without it, these variables are ignored. + + A priority must be a floating-point value between 0 and 1, indicating the priority of the allocation relative to other memory allocations. + Larger values are higher priority. The granularity of the priorities is implementation-dependent. + It is automatically passed to every call to `vkAllocateMemory` done by the library using structure `VkMemoryPriorityAllocateInfoEXT`. + The value to be used for default priority is 0.5. + For more details, see the documentation of the VK_EXT_memory_priority extension. + */ + VMA_ALLOCATOR_CREATE_EXT_MEMORY_PRIORITY_BIT = 0x00000040, VMA_ALLOCATOR_CREATE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF } VmaAllocatorCreateFlagBits; @@ -1750,26 +2352,33 @@ typedef VkFlags VmaAllocatorCreateFlags; Used in VmaAllocatorCreateInfo::pVulkanFunctions. */ typedef struct VmaVulkanFunctions { - PFN_vkGetPhysicalDeviceProperties vkGetPhysicalDeviceProperties; - PFN_vkGetPhysicalDeviceMemoryProperties vkGetPhysicalDeviceMemoryProperties; - PFN_vkAllocateMemory vkAllocateMemory; - PFN_vkFreeMemory vkFreeMemory; - PFN_vkMapMemory vkMapMemory; - PFN_vkUnmapMemory vkUnmapMemory; - PFN_vkFlushMappedMemoryRanges vkFlushMappedMemoryRanges; - PFN_vkInvalidateMappedMemoryRanges vkInvalidateMappedMemoryRanges; - PFN_vkBindBufferMemory vkBindBufferMemory; - PFN_vkBindImageMemory vkBindImageMemory; - PFN_vkGetBufferMemoryRequirements vkGetBufferMemoryRequirements; - PFN_vkGetImageMemoryRequirements vkGetImageMemoryRequirements; - PFN_vkCreateBuffer vkCreateBuffer; - PFN_vkDestroyBuffer vkDestroyBuffer; - PFN_vkCreateImage vkCreateImage; - PFN_vkDestroyImage vkDestroyImage; - PFN_vkCmdCopyBuffer vkCmdCopyBuffer; -#if VMA_DEDICATED_ALLOCATION - PFN_vkGetBufferMemoryRequirements2KHR vkGetBufferMemoryRequirements2KHR; - PFN_vkGetImageMemoryRequirements2KHR vkGetImageMemoryRequirements2KHR; + PFN_vkGetPhysicalDeviceProperties VMA_NULLABLE vkGetPhysicalDeviceProperties; + PFN_vkGetPhysicalDeviceMemoryProperties VMA_NULLABLE vkGetPhysicalDeviceMemoryProperties; + PFN_vkAllocateMemory VMA_NULLABLE vkAllocateMemory; + PFN_vkFreeMemory VMA_NULLABLE vkFreeMemory; + PFN_vkMapMemory VMA_NULLABLE vkMapMemory; + PFN_vkUnmapMemory VMA_NULLABLE vkUnmapMemory; + PFN_vkFlushMappedMemoryRanges VMA_NULLABLE vkFlushMappedMemoryRanges; + PFN_vkInvalidateMappedMemoryRanges VMA_NULLABLE vkInvalidateMappedMemoryRanges; + PFN_vkBindBufferMemory VMA_NULLABLE vkBindBufferMemory; + PFN_vkBindImageMemory VMA_NULLABLE vkBindImageMemory; + PFN_vkGetBufferMemoryRequirements VMA_NULLABLE vkGetBufferMemoryRequirements; + PFN_vkGetImageMemoryRequirements VMA_NULLABLE vkGetImageMemoryRequirements; + PFN_vkCreateBuffer VMA_NULLABLE vkCreateBuffer; + PFN_vkDestroyBuffer VMA_NULLABLE vkDestroyBuffer; + PFN_vkCreateImage VMA_NULLABLE vkCreateImage; + PFN_vkDestroyImage VMA_NULLABLE vkDestroyImage; + PFN_vkCmdCopyBuffer VMA_NULLABLE vkCmdCopyBuffer; +#if VMA_DEDICATED_ALLOCATION || VMA_VULKAN_VERSION >= 1001000 + PFN_vkGetBufferMemoryRequirements2KHR VMA_NULLABLE vkGetBufferMemoryRequirements2KHR; + PFN_vkGetImageMemoryRequirements2KHR VMA_NULLABLE vkGetImageMemoryRequirements2KHR; +#endif +#if VMA_BIND_MEMORY2 || VMA_VULKAN_VERSION >= 1001000 + PFN_vkBindBufferMemory2KHR VMA_NULLABLE vkBindBufferMemory2KHR; + PFN_vkBindImageMemory2KHR VMA_NULLABLE vkBindImageMemory2KHR; +#endif +#if VMA_MEMORY_BUDGET || VMA_VULKAN_VERSION >= 1001000 + PFN_vkGetPhysicalDeviceMemoryProperties2KHR VMA_NULLABLE vkGetPhysicalDeviceMemoryProperties2KHR; #endif } VmaVulkanFunctions; @@ -1781,7 +2390,7 @@ typedef enum VmaRecordFlagBits { It may degrade performance though. */ VMA_RECORD_FLUSH_AFTER_CALL_BIT = 0x00000001, - + VMA_RECORD_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF } VmaRecordFlagBits; typedef VkFlags VmaRecordFlags; @@ -1798,7 +2407,7 @@ typedef struct VmaRecordSettings It will be opened for the whole time #VmaAllocator object is alive. If opening this file fails, creation of the whole allocator object fails. */ - const char* pFilePath; + const char* VMA_NOT_NULL pFilePath; } VmaRecordSettings; /// Description of a Allocator to be created. @@ -1808,19 +2417,19 @@ typedef struct VmaAllocatorCreateInfo VmaAllocatorCreateFlags flags; /// Vulkan physical device. /** It must be valid throughout whole lifetime of created allocator. */ - VkPhysicalDevice physicalDevice; + VkPhysicalDevice VMA_NOT_NULL physicalDevice; /// Vulkan device. /** It must be valid throughout whole lifetime of created allocator. */ - VkDevice device; + VkDevice VMA_NOT_NULL device; /// Preferred size of a single `VkDeviceMemory` block to be allocated from large heaps > 1 GiB. Optional. /** Set to 0 to use default, which is currently 256 MiB. */ VkDeviceSize preferredLargeHeapBlockSize; /// Custom CPU memory allocation callbacks. Optional. /** Optional, can be null. When specified, will also be used for all CPU-side memory allocations. */ - const VkAllocationCallbacks* pAllocationCallbacks; + const VkAllocationCallbacks* VMA_NULLABLE pAllocationCallbacks; /// Informative callbacks for `vkAllocateMemory`, `vkFreeMemory`. Optional. /** Optional, can be null. */ - const VmaDeviceMemoryCallbacks* pDeviceMemoryCallbacks; + const VmaDeviceMemoryCallbacks* VMA_NULLABLE pDeviceMemoryCallbacks; /** \brief Maximum number of additional frames that are in use at the same time as current frame. This value is used only when you make allocations with @@ -1859,52 +2468,88 @@ typedef struct VmaAllocatorCreateInfo blocks to system RAM. This driver behavior can also be controlled using VK_AMD_memory_overallocation_behavior extension. */ - const VkDeviceSize* pHeapSizeLimit; - /** \brief Pointers to Vulkan functions. Can be null if you leave define `VMA_STATIC_VULKAN_FUNCTIONS 1`. + const VkDeviceSize* VMA_NULLABLE VMA_LEN_IF_NOT_NULL("VkPhysicalDeviceMemoryProperties::memoryHeapCount") pHeapSizeLimit; - If you leave define `VMA_STATIC_VULKAN_FUNCTIONS 1` in configuration section, - you can pass null as this member, because the library will fetch pointers to - Vulkan functions internally in a static way, like: + /** \brief Pointers to Vulkan functions. Can be null. - vulkanFunctions.vkAllocateMemory = &vkAllocateMemory; - - Fill this member if you want to provide your own pointers to Vulkan functions, - e.g. fetched using `vkGetInstanceProcAddr()` and `vkGetDeviceProcAddr()`. + For details see [Pointers to Vulkan functions](@ref config_Vulkan_functions). */ - const VmaVulkanFunctions* pVulkanFunctions; + const VmaVulkanFunctions* VMA_NULLABLE pVulkanFunctions; /** \brief Parameters for recording of VMA calls. Can be null. If not null, it enables recording of calls to VMA functions to a file. If support for recording is not enabled using `VMA_RECORDING_ENABLED` macro, creation of the allocator object fails with `VK_ERROR_FEATURE_NOT_PRESENT`. */ - const VmaRecordSettings* pRecordSettings; + const VmaRecordSettings* VMA_NULLABLE pRecordSettings; + /** \brief Handle to Vulkan instance object. + + Starting from version 3.0.0 this member is no longer optional, it must be set! + */ + VkInstance VMA_NOT_NULL instance; + /** \brief Optional. The highest version of Vulkan that the application is designed to use. + + It must be a value in the format as created by macro `VK_MAKE_VERSION` or a constant like: `VK_API_VERSION_1_1`, `VK_API_VERSION_1_0`. + The patch version number specified is ignored. Only the major and minor versions are considered. + It must be less or equal (preferably equal) to value as passed to `vkCreateInstance` as `VkApplicationInfo::apiVersion`. + Only versions 1.0, 1.1, 1.2 are supported by the current implementation. + Leaving it initialized to zero is equivalent to `VK_API_VERSION_1_0`. + */ + uint32_t vulkanApiVersion; } VmaAllocatorCreateInfo; /// Creates Allocator object. -VkResult vmaCreateAllocator( - const VmaAllocatorCreateInfo* pCreateInfo, - VmaAllocator* pAllocator); +VMA_CALL_PRE VkResult VMA_CALL_POST vmaCreateAllocator( + const VmaAllocatorCreateInfo* VMA_NOT_NULL pCreateInfo, + VmaAllocator VMA_NULLABLE * VMA_NOT_NULL pAllocator); /// Destroys allocator object. -void vmaDestroyAllocator( - VmaAllocator allocator); +VMA_CALL_PRE void VMA_CALL_POST vmaDestroyAllocator( + VmaAllocator VMA_NULLABLE allocator); + +/** \brief Information about existing #VmaAllocator object. +*/ +typedef struct VmaAllocatorInfo +{ + /** \brief Handle to Vulkan instance object. + + This is the same value as has been passed through VmaAllocatorCreateInfo::instance. + */ + VkInstance VMA_NOT_NULL instance; + /** \brief Handle to Vulkan physical device object. + + This is the same value as has been passed through VmaAllocatorCreateInfo::physicalDevice. + */ + VkPhysicalDevice VMA_NOT_NULL physicalDevice; + /** \brief Handle to Vulkan device object. + + This is the same value as has been passed through VmaAllocatorCreateInfo::device. + */ + VkDevice VMA_NOT_NULL device; +} VmaAllocatorInfo; + +/** \brief Returns information about existing #VmaAllocator object - handle to Vulkan device etc. + +It might be useful if you want to keep just the #VmaAllocator handle and fetch other required handles to +`VkPhysicalDevice`, `VkDevice` etc. every time using this function. +*/ +VMA_CALL_PRE void VMA_CALL_POST vmaGetAllocatorInfo(VmaAllocator VMA_NOT_NULL allocator, VmaAllocatorInfo* VMA_NOT_NULL pAllocatorInfo); /** PhysicalDeviceProperties are fetched from physicalDevice by the allocator. You can access it here, without fetching it again on your own. */ -void vmaGetPhysicalDeviceProperties( - VmaAllocator allocator, - const VkPhysicalDeviceProperties** ppPhysicalDeviceProperties); +VMA_CALL_PRE void VMA_CALL_POST vmaGetPhysicalDeviceProperties( + VmaAllocator VMA_NOT_NULL allocator, + const VkPhysicalDeviceProperties* VMA_NULLABLE * VMA_NOT_NULL ppPhysicalDeviceProperties); /** PhysicalDeviceMemoryProperties are fetched from physicalDevice by the allocator. You can access it here, without fetching it again on your own. */ -void vmaGetMemoryProperties( - VmaAllocator allocator, - const VkPhysicalDeviceMemoryProperties** ppPhysicalDeviceMemoryProperties); +VMA_CALL_PRE void VMA_CALL_POST vmaGetMemoryProperties( + VmaAllocator VMA_NOT_NULL allocator, + const VkPhysicalDeviceMemoryProperties* VMA_NULLABLE * VMA_NOT_NULL ppPhysicalDeviceMemoryProperties); /** \brief Given Memory Type Index, returns Property Flags of this memory type. @@ -1912,10 +2557,10 @@ void vmaGetMemoryProperties( This is just a convenience function. Same information can be obtained using vmaGetMemoryProperties(). */ -void vmaGetMemoryTypeProperties( - VmaAllocator allocator, +VMA_CALL_PRE void VMA_CALL_POST vmaGetMemoryTypeProperties( + VmaAllocator VMA_NOT_NULL allocator, uint32_t memoryTypeIndex, - VkMemoryPropertyFlags* pFlags); + VkMemoryPropertyFlags* VMA_NOT_NULL pFlags); /** \brief Sets index of the current frame. @@ -1925,8 +2570,8 @@ This function must be used if you make allocations with when a new frame begins. Allocations queried using vmaGetAllocationInfo() cannot become lost in the current frame. */ -void vmaSetCurrentFrameIndex( - VmaAllocator allocator, +VMA_CALL_PRE void VMA_CALL_POST vmaSetCurrentFrameIndex( + VmaAllocator VMA_NOT_NULL allocator, uint32_t frameIndex); /** \brief Calculated statistics of memory usage in entire allocator. @@ -1955,26 +2600,91 @@ typedef struct VmaStats VmaStatInfo total; } VmaStats; -/// Retrieves statistics from current state of the Allocator. -void vmaCalculateStats( - VmaAllocator allocator, - VmaStats* pStats); +/** \brief Retrieves statistics from current state of the Allocator. +This function is called "calculate" not "get" because it has to traverse all +internal data structures, so it may be quite slow. For faster but more brief statistics +suitable to be called every frame or every allocation, use vmaGetBudget(). + +Note that when using allocator from multiple threads, returned information may immediately +become outdated. +*/ +VMA_CALL_PRE void VMA_CALL_POST vmaCalculateStats( + VmaAllocator VMA_NOT_NULL allocator, + VmaStats* VMA_NOT_NULL pStats); + +/** \brief Statistics of current memory usage and available budget, in bytes, for specific memory heap. +*/ +typedef struct VmaBudget +{ + /** \brief Sum size of all `VkDeviceMemory` blocks allocated from particular heap, in bytes. + */ + VkDeviceSize blockBytes; + + /** \brief Sum size of all allocations created in particular heap, in bytes. + + Usually less or equal than `blockBytes`. + Difference `blockBytes - allocationBytes` is the amount of memory allocated but unused - + available for new allocations or wasted due to fragmentation. + + It might be greater than `blockBytes` if there are some allocations in lost state, as they account + to this value as well. + */ + VkDeviceSize allocationBytes; + + /** \brief Estimated current memory usage of the program, in bytes. + + Fetched from system using `VK_EXT_memory_budget` extension if enabled. + + It might be different than `blockBytes` (usually higher) due to additional implicit objects + also occupying the memory, like swapchain, pipelines, descriptor heaps, command buffers, or + `VkDeviceMemory` blocks allocated outside of this library, if any. + */ + VkDeviceSize usage; + + /** \brief Estimated amount of memory available to the program, in bytes. + + Fetched from system using `VK_EXT_memory_budget` extension if enabled. + + It might be different (most probably smaller) than `VkMemoryHeap::size[heapIndex]` due to factors + external to the program, like other programs also consuming system resources. + Difference `budget - usage` is the amount of additional memory that can probably + be allocated without problems. Exceeding the budget may result in various problems. + */ + VkDeviceSize budget; +} VmaBudget; + +/** \brief Retrieves information about current memory budget for all memory heaps. + +\param[out] pBudget Must point to array with number of elements at least equal to number of memory heaps in physical device used. + +This function is called "get" not "calculate" because it is very fast, suitable to be called +every frame or every allocation. For more detailed statistics use vmaCalculateStats(). + +Note that when using allocator from multiple threads, returned information may immediately +become outdated. +*/ +VMA_CALL_PRE void VMA_CALL_POST vmaGetBudget( + VmaAllocator VMA_NOT_NULL allocator, + VmaBudget* VMA_NOT_NULL pBudget); + +#ifndef VMA_STATS_STRING_ENABLED #define VMA_STATS_STRING_ENABLED 1 +#endif #if VMA_STATS_STRING_ENABLED /// Builds and returns statistics as string in JSON format. /** @param[out] ppStatsString Must be freed using vmaFreeStatsString() function. */ -void vmaBuildStatsString( - VmaAllocator allocator, - char** ppStatsString, +VMA_CALL_PRE void VMA_CALL_POST vmaBuildStatsString( + VmaAllocator VMA_NOT_NULL allocator, + char* VMA_NULLABLE * VMA_NOT_NULL ppStatsString, VkBool32 detailedMap); -void vmaFreeStatsString( - VmaAllocator allocator, - char* pStatsString); +VMA_CALL_PRE void VMA_CALL_POST vmaFreeStatsString( + VmaAllocator VMA_NOT_NULL allocator, + char* VMA_NULLABLE pStatsString); #endif // #if VMA_STATS_STRING_ENABLED @@ -2000,7 +2710,7 @@ typedef enum VmaMemoryUsage It is roughly equivalent of `D3D12_HEAP_TYPE_DEFAULT`. Usage: - + - Resources written and read by device, e.g. images used as attachments. - Resources transferred from host once (immutable) or infrequently and read by device multiple times, e.g. textures to be sampled, vertex buffers, uniform @@ -2025,7 +2735,7 @@ typedef enum VmaMemoryUsage Memory that is both mappable on host (guarantees to be `HOST_VISIBLE`) and preferably fast to access by GPU. CPU access is typically uncached. Writes may be write-combined. - Usage: Resources written frequently by host (dynamic), read by device. E.g. textures, vertex buffers, uniform buffers updated every frame or every draw call. + Usage: Resources written frequently by host (dynamic), read by device. E.g. textures (with LINEAR layout), vertex buffers, uniform buffers updated every frame or every draw call. */ VMA_MEMORY_USAGE_CPU_TO_GPU = 3, /** Memory mappable on host (guarantees to be `HOST_VISIBLE`) and cached. @@ -2037,34 +2747,49 @@ typedef enum VmaMemoryUsage - Any resources read or accessed randomly on host, e.g. CPU-side copy of vertex buffer used as source of transfer, but also used for collision detection. */ VMA_MEMORY_USAGE_GPU_TO_CPU = 4, + /** CPU memory - memory that is preferably not `DEVICE_LOCAL`, but also not guaranteed to be `HOST_VISIBLE`. + + Usage: Staging copy of resources moved from GPU memory to CPU memory as part + of custom paging/residency mechanism, to be moved back to GPU memory when needed. + */ + VMA_MEMORY_USAGE_CPU_COPY = 5, + /** Lazily allocated GPU memory having `VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT`. + Exists mostly on mobile platforms. Using it on desktop PC or other GPUs with no such memory type present will fail the allocation. + + Usage: Memory for transient attachment images (color attachments, depth attachments etc.), created with `VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT`. + + Allocations with this usage are always created as dedicated - it implies #VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT. + */ + VMA_MEMORY_USAGE_GPU_LAZILY_ALLOCATED = 6, + VMA_MEMORY_USAGE_MAX_ENUM = 0x7FFFFFFF } VmaMemoryUsage; /// Flags to be passed as VmaAllocationCreateInfo::flags. typedef enum VmaAllocationCreateFlagBits { /** \brief Set this flag if the allocation should have its own memory block. - + Use it for special, big resources, like fullscreen images used as attachments. - + You should not use this flag if VmaAllocationCreateInfo::pool is not null. */ VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT = 0x00000001, /** \brief Set this flag to only try to allocate from existing `VkDeviceMemory` blocks and never create new such block. - + If new allocation cannot be placed in any of the existing blocks, allocation fails with `VK_ERROR_OUT_OF_DEVICE_MEMORY` error. - + You should not use #VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT and #VMA_ALLOCATION_CREATE_NEVER_ALLOCATE_BIT at the same time. It makes no sense. - + If VmaAllocationCreateInfo::pool is not null, this flag is implied and ignored. */ VMA_ALLOCATION_CREATE_NEVER_ALLOCATE_BIT = 0x00000002, /** \brief Set this flag to use a memory that will be persistently mapped and retrieve pointer to it. - + Pointer to mapped memory will be returned through VmaAllocationInfo::pMappedData. - Is it valid to use this flag for allocation made from memory type that is not + It is valid to use this flag for allocation made from memory type that is not `HOST_VISIBLE`. This flag is then ignored and memory is not mapped. This is useful if you need an allocation that is efficient to use on GPU (`DEVICE_LOCAL`) and still want to map it directly if possible on platforms that @@ -2104,6 +2829,16 @@ typedef enum VmaAllocationCreateFlagBits { This flag is only allowed for custom pools created with #VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT flag. */ VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT = 0x00000040, + /** Create both buffer/image and allocation, but don't bind them together. + It is useful when you want to bind yourself to do some more advanced binding, e.g. using some extensions. + The flag is meaningful only with functions that bind by default: vmaCreateBuffer(), vmaCreateImage(). + Otherwise it is ignored. + */ + VMA_ALLOCATION_CREATE_DONT_BIND_BIT = 0x00000080, + /** Create allocation only if additional device memory required for it, if any, won't exceed + memory budget. Otherwise return `VK_ERROR_OUT_OF_DEVICE_MEMORY`. + */ + VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT = 0x00000100, /** Allocation strategy that chooses smallest possible free range for the allocation. @@ -2147,19 +2882,19 @@ typedef struct VmaAllocationCreateInfo /// Use #VmaAllocationCreateFlagBits enum. VmaAllocationCreateFlags flags; /** \brief Intended usage of memory. - + You can leave #VMA_MEMORY_USAGE_UNKNOWN if you specify memory requirements in other way. \n If `pool` is not null, this member is ignored. */ VmaMemoryUsage usage; /** \brief Flags that must be set in a Memory Type chosen for an allocation. - + Leave 0 if you specify memory requirements in other way. \n If `pool` is not null, this member is ignored.*/ VkMemoryPropertyFlags requiredFlags; /** \brief Flags that preferably should be set in a memory type chosen for an allocation. - - Set to 0 if no additional flags are prefered. \n + + Set to 0 if no additional flags are preferred. \n If `pool` is not null, this member is ignored. */ VkMemoryPropertyFlags preferredFlags; /** \brief Bitmask containing one bit set for every memory type acceptable for this allocation. @@ -2175,14 +2910,21 @@ typedef struct VmaAllocationCreateInfo Leave `VK_NULL_HANDLE` to allocate from default pool. If not null, members: `usage`, `requiredFlags`, `preferredFlags`, `memoryTypeBits` are ignored. */ - VmaPool pool; + VmaPool VMA_NULLABLE pool; /** \brief Custom general-purpose pointer that will be stored in #VmaAllocation, can be read as VmaAllocationInfo::pUserData and changed using vmaSetAllocationUserData(). - + If #VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT is used, it must be either null or pointer to a null-terminated string. The string will be then copied to internal buffer, so it doesn't need to be valid after allocation call. */ - void* pUserData; + void* VMA_NULLABLE pUserData; + /** \brief A floating-point value between 0 and 1, indicating the priority of the allocation relative to other memory allocations. + + It is used only when #VMA_ALLOCATOR_CREATE_EXT_MEMORY_PRIORITY_BIT flag was used during creation of the #VmaAllocator object + and this allocation ends up as dedicated or is explicitly forced as dedicated using #VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT. + Otherwise, it has the priority of a memory block where it is placed and this variable is ignored. + */ + float priority; } VmaAllocationCreateInfo; /** @@ -2201,11 +2943,11 @@ device doesn't support any memory type with requested features for the specific type of resource you want to use it for. Please check parameters of your resource, like image layout (OPTIMAL versus LINEAR) or mip level count. */ -VkResult vmaFindMemoryTypeIndex( - VmaAllocator allocator, +VMA_CALL_PRE VkResult VMA_CALL_POST vmaFindMemoryTypeIndex( + VmaAllocator VMA_NOT_NULL allocator, uint32_t memoryTypeBits, - const VmaAllocationCreateInfo* pAllocationCreateInfo, - uint32_t* pMemoryTypeIndex); + const VmaAllocationCreateInfo* VMA_NOT_NULL pAllocationCreateInfo, + uint32_t* VMA_NOT_NULL pMemoryTypeIndex); /** \brief Helps to find memoryTypeIndex, given VkBufferCreateInfo and VmaAllocationCreateInfo. @@ -2219,11 +2961,11 @@ It is just a convenience function, equivalent to calling: - `vmaFindMemoryTypeIndex` - `vkDestroyBuffer` */ -VkResult vmaFindMemoryTypeIndexForBufferInfo( - VmaAllocator allocator, - const VkBufferCreateInfo* pBufferCreateInfo, - const VmaAllocationCreateInfo* pAllocationCreateInfo, - uint32_t* pMemoryTypeIndex); +VMA_CALL_PRE VkResult VMA_CALL_POST vmaFindMemoryTypeIndexForBufferInfo( + VmaAllocator VMA_NOT_NULL allocator, + const VkBufferCreateInfo* VMA_NOT_NULL pBufferCreateInfo, + const VmaAllocationCreateInfo* VMA_NOT_NULL pAllocationCreateInfo, + uint32_t* VMA_NOT_NULL pMemoryTypeIndex); /** \brief Helps to find memoryTypeIndex, given VkImageCreateInfo and VmaAllocationCreateInfo. @@ -2237,11 +2979,11 @@ It is just a convenience function, equivalent to calling: - `vmaFindMemoryTypeIndex` - `vkDestroyImage` */ -VkResult vmaFindMemoryTypeIndexForImageInfo( - VmaAllocator allocator, - const VkImageCreateInfo* pImageCreateInfo, - const VmaAllocationCreateInfo* pAllocationCreateInfo, - uint32_t* pMemoryTypeIndex); +VMA_CALL_PRE VkResult VMA_CALL_POST vmaFindMemoryTypeIndexForImageInfo( + VmaAllocator VMA_NOT_NULL allocator, + const VkImageCreateInfo* VMA_NOT_NULL pImageCreateInfo, + const VmaAllocationCreateInfo* VMA_NOT_NULL pAllocationCreateInfo, + uint32_t* VMA_NOT_NULL pMemoryTypeIndex); /// Flags to be passed as VmaPoolCreateInfo::flags. typedef enum VmaPoolCreateFlagBits { @@ -2328,7 +3070,7 @@ typedef struct VmaPoolCreateInfo { /** \brief Maximum number of blocks that can be allocated in this pool. Optional. Set to 0 to use default, which is `SIZE_MAX`, which means no limit. - + Set to same value as VmaPoolCreateInfo::minBlockCount to have fixed amount of memory allocated throughout whole lifetime of this pool. */ @@ -2347,6 +3089,12 @@ typedef struct VmaPoolCreateInfo { become lost, set this value to 0. */ uint32_t frameInUseCount; + /** \brief A floating-point value between 0 and 1, indicating the priority of the allocations in this pool relative to other memory allocations. + + It is used only when #VMA_ALLOCATOR_CREATE_EXT_MEMORY_PRIORITY_BIT flag was used during creation of the #VmaAllocator object. + Otherwise, this variable is ignored. + */ + float priority; } VmaPoolCreateInfo; /** \brief Describes parameter of existing #VmaPool. @@ -2382,16 +3130,16 @@ typedef struct VmaPoolStats { @param pCreateInfo Parameters of pool to create. @param[out] pPool Handle to created pool. */ -VkResult vmaCreatePool( - VmaAllocator allocator, - const VmaPoolCreateInfo* pCreateInfo, - VmaPool* pPool); +VMA_CALL_PRE VkResult VMA_CALL_POST vmaCreatePool( + VmaAllocator VMA_NOT_NULL allocator, + const VmaPoolCreateInfo* VMA_NOT_NULL pCreateInfo, + VmaPool VMA_NULLABLE * VMA_NOT_NULL pPool); /** \brief Destroys #VmaPool object and frees Vulkan device memory. */ -void vmaDestroyPool( - VmaAllocator allocator, - VmaPool pool); +VMA_CALL_PRE void VMA_CALL_POST vmaDestroyPool( + VmaAllocator VMA_NOT_NULL allocator, + VmaPool VMA_NULLABLE pool); /** \brief Retrieves statistics of existing #VmaPool object. @@ -2399,10 +3147,10 @@ void vmaDestroyPool( @param pool Pool object. @param[out] pPoolStats Statistics of specified pool. */ -void vmaGetPoolStats( - VmaAllocator allocator, - VmaPool pool, - VmaPoolStats* pPoolStats); +VMA_CALL_PRE void VMA_CALL_POST vmaGetPoolStats( + VmaAllocator VMA_NOT_NULL allocator, + VmaPool VMA_NOT_NULL pool, + VmaPoolStats* VMA_NOT_NULL pPoolStats); /** \brief Marks all allocations in given pool as lost if they are not used in current frame or VmaPoolCreateInfo::frameInUseCount back from now. @@ -2410,10 +3158,10 @@ void vmaGetPoolStats( @param pool Pool. @param[out] pLostAllocationCount Number of allocations marked as lost. Optional - pass null if you don't need this information. */ -void vmaMakePoolAllocationsLost( - VmaAllocator allocator, - VmaPool pool, - size_t* pLostAllocationCount); +VMA_CALL_PRE void VMA_CALL_POST vmaMakePoolAllocationsLost( + VmaAllocator VMA_NOT_NULL allocator, + VmaPool VMA_NOT_NULL pool, + size_t* VMA_NULLABLE pLostAllocationCount); /** \brief Checks magic number in margins around all allocations in given memory pool in search for corruptions. @@ -2429,7 +3177,28 @@ Possible return values: `VMA_ASSERT` is also fired in that case. - Other value: Error returned by Vulkan, e.g. memory mapping failure. */ -VkResult vmaCheckPoolCorruption(VmaAllocator allocator, VmaPool pool); +VMA_CALL_PRE VkResult VMA_CALL_POST vmaCheckPoolCorruption(VmaAllocator VMA_NOT_NULL allocator, VmaPool VMA_NOT_NULL pool); + +/** \brief Retrieves name of a custom pool. + +After the call `ppName` is either null or points to an internally-owned null-terminated string +containing name of the pool that was previously set. The pointer becomes invalid when the pool is +destroyed or its name is changed using vmaSetPoolName(). +*/ +VMA_CALL_PRE void VMA_CALL_POST vmaGetPoolName( + VmaAllocator VMA_NOT_NULL allocator, + VmaPool VMA_NOT_NULL pool, + const char* VMA_NULLABLE * VMA_NOT_NULL ppName); + +/** \brief Sets name of a custom pool. + +`pName` can be either null or pointer to a null-terminated string with new name for the pool. +Function makes internal copy of the string, so it can be changed or freed immediately after this call. +*/ +VMA_CALL_PRE void VMA_CALL_POST vmaSetPoolName( + VmaAllocator VMA_NOT_NULL allocator, + VmaPool VMA_NOT_NULL pool, + const char* VMA_NULLABLE pName); /** \struct VmaAllocation \brief Represents single memory allocation. @@ -2461,20 +3230,25 @@ VK_DEFINE_HANDLE(VmaAllocation) */ typedef struct VmaAllocationInfo { /** \brief Memory type index that this allocation was allocated from. - + It never changes. */ uint32_t memoryType; /** \brief Handle to Vulkan memory object. Same memory object can be shared by multiple allocations. - + It can change after call to vmaDefragment() if this allocation is passed to the function, or if allocation is lost. If the allocation is lost, it is equal to `VK_NULL_HANDLE`. */ - VkDeviceMemory deviceMemory; - /** \brief Offset into deviceMemory object to the beginning of this allocation, in bytes. (deviceMemory, offset) pair is unique to this allocation. + VkDeviceMemory VMA_NULLABLE_NON_DISPATCHABLE deviceMemory; + /** \brief Offset in `VkDeviceMemory` object to the beginning of this allocation, in bytes. `(deviceMemory, offset)` pair is unique to this allocation. + + You usually don't need to use this offset. If you create a buffer or an image together with the allocation using e.g. function + vmaCreateBuffer(), vmaCreateImage(), functions that operate on these resources refer to the beginning of the buffer or image, + not entire device memory block. Functions like vmaMapMemory(), vmaBindBufferMemory() also refer to the beginning of the allocation + and apply this offset automatically. It can change after call to vmaDefragment() if this allocation is passed to the function, or if allocation is lost. */ @@ -2482,22 +3256,28 @@ typedef struct VmaAllocationInfo { /** \brief Size of this allocation, in bytes. It never changes, unless allocation is lost. + + \note Allocation size returned in this variable may be greater than the size + requested for the resource e.g. as `VkBufferCreateInfo::size`. Whole size of the + allocation is accessible for operations on memory e.g. using a pointer after + mapping with vmaMapMemory(), but operations on the resource e.g. using + `vkCmdCopyBuffer` must be limited to the size of the resource. */ VkDeviceSize size; /** \brief Pointer to the beginning of this allocation as mapped data. If the allocation hasn't been mapped using vmaMapMemory() and hasn't been - created with #VMA_ALLOCATION_CREATE_MAPPED_BIT flag, this value null. + created with #VMA_ALLOCATION_CREATE_MAPPED_BIT flag, this value is null. It can change after call to vmaMapMemory(), vmaUnmapMemory(). It can also change after call to vmaDefragment() if this allocation is passed to the function. */ - void* pMappedData; + void* VMA_NULLABLE pMappedData; /** \brief Custom general-purpose pointer that was passed as VmaAllocationCreateInfo::pUserData or set using vmaSetAllocationUserData(). It can change after call to vmaSetAllocationUserData() for this allocation. */ - void* pUserData; + void* VMA_NULLABLE pUserData; } VmaAllocationInfo; /** \brief General purpose memory allocation. @@ -2510,12 +3290,12 @@ You should free the memory using vmaFreeMemory() or vmaFreeMemoryPages(). It is recommended to use vmaAllocateMemoryForBuffer(), vmaAllocateMemoryForImage(), vmaCreateBuffer(), vmaCreateImage() instead whenever possible. */ -VkResult vmaAllocateMemory( - VmaAllocator allocator, - const VkMemoryRequirements* pVkMemoryRequirements, - const VmaAllocationCreateInfo* pCreateInfo, - VmaAllocation* pAllocation, - VmaAllocationInfo* pAllocationInfo); +VMA_CALL_PRE VkResult VMA_CALL_POST vmaAllocateMemory( + VmaAllocator VMA_NOT_NULL allocator, + const VkMemoryRequirements* VMA_NOT_NULL pVkMemoryRequirements, + const VmaAllocationCreateInfo* VMA_NOT_NULL pCreateInfo, + VmaAllocation VMA_NULLABLE * VMA_NOT_NULL pAllocation, + VmaAllocationInfo* VMA_NULLABLE pAllocationInfo); /** \brief General purpose memory allocation for multiple allocation objects at once. @@ -2536,13 +3316,13 @@ All allocations are made using same parameters. All of them are created out of t If any allocation fails, all allocations already made within this function call are also freed, so that when returned result is not `VK_SUCCESS`, `pAllocation` array is always entirely filled with `VK_NULL_HANDLE`. */ -VkResult vmaAllocateMemoryPages( - VmaAllocator allocator, - const VkMemoryRequirements* pVkMemoryRequirements, - const VmaAllocationCreateInfo* pCreateInfo, +VMA_CALL_PRE VkResult VMA_CALL_POST vmaAllocateMemoryPages( + VmaAllocator VMA_NOT_NULL allocator, + const VkMemoryRequirements* VMA_NOT_NULL VMA_LEN_IF_NOT_NULL(allocationCount) pVkMemoryRequirements, + const VmaAllocationCreateInfo* VMA_NOT_NULL VMA_LEN_IF_NOT_NULL(allocationCount) pCreateInfo, size_t allocationCount, - VmaAllocation* pAllocations, - VmaAllocationInfo* pAllocationInfo); + VmaAllocation VMA_NULLABLE * VMA_NOT_NULL VMA_LEN_IF_NOT_NULL(allocationCount) pAllocations, + VmaAllocationInfo* VMA_NULLABLE VMA_LEN_IF_NOT_NULL(allocationCount) pAllocationInfo); /** @param[out] pAllocation Handle to allocated memory. @@ -2550,28 +3330,28 @@ VkResult vmaAllocateMemoryPages( You should free the memory using vmaFreeMemory(). */ -VkResult vmaAllocateMemoryForBuffer( - VmaAllocator allocator, - VkBuffer buffer, - const VmaAllocationCreateInfo* pCreateInfo, - VmaAllocation* pAllocation, - VmaAllocationInfo* pAllocationInfo); +VMA_CALL_PRE VkResult VMA_CALL_POST vmaAllocateMemoryForBuffer( + VmaAllocator VMA_NOT_NULL allocator, + VkBuffer VMA_NOT_NULL_NON_DISPATCHABLE buffer, + const VmaAllocationCreateInfo* VMA_NOT_NULL pCreateInfo, + VmaAllocation VMA_NULLABLE * VMA_NOT_NULL pAllocation, + VmaAllocationInfo* VMA_NULLABLE pAllocationInfo); /// Function similar to vmaAllocateMemoryForBuffer(). -VkResult vmaAllocateMemoryForImage( - VmaAllocator allocator, - VkImage image, - const VmaAllocationCreateInfo* pCreateInfo, - VmaAllocation* pAllocation, - VmaAllocationInfo* pAllocationInfo); +VMA_CALL_PRE VkResult VMA_CALL_POST vmaAllocateMemoryForImage( + VmaAllocator VMA_NOT_NULL allocator, + VkImage VMA_NOT_NULL_NON_DISPATCHABLE image, + const VmaAllocationCreateInfo* VMA_NOT_NULL pCreateInfo, + VmaAllocation VMA_NULLABLE * VMA_NOT_NULL pAllocation, + VmaAllocationInfo* VMA_NULLABLE pAllocationInfo); /** \brief Frees memory previously allocated using vmaAllocateMemory(), vmaAllocateMemoryForBuffer(), or vmaAllocateMemoryForImage(). Passing `VK_NULL_HANDLE` as `allocation` is valid. Such function call is just skipped. */ -void vmaFreeMemory( - VmaAllocator allocator, - VmaAllocation allocation); +VMA_CALL_PRE void VMA_CALL_POST vmaFreeMemory( + VmaAllocator VMA_NOT_NULL allocator, + const VmaAllocation VMA_NULLABLE allocation); /** \brief Frees memory and destroys multiple allocations. @@ -2583,39 +3363,26 @@ It may be internally optimized to be more efficient than calling vmaFreeMemory() Allocations in `pAllocations` array can come from any memory pools and types. Passing `VK_NULL_HANDLE` as elements of `pAllocations` array is valid. Such entries are just skipped. */ -void vmaFreeMemoryPages( - VmaAllocator allocator, +VMA_CALL_PRE void VMA_CALL_POST vmaFreeMemoryPages( + VmaAllocator VMA_NOT_NULL allocator, size_t allocationCount, - VmaAllocation* pAllocations); + const VmaAllocation VMA_NULLABLE * VMA_NOT_NULL VMA_LEN_IF_NOT_NULL(allocationCount) pAllocations); -/** \brief Tries to resize an allocation in place, if there is enough free memory after it. +/** \brief Deprecated. -Tries to change allocation's size without moving or reallocating it. -You can both shrink and grow allocation size. -When growing, it succeeds only when the allocation belongs to a memory block with enough -free space after it. - -Returns `VK_SUCCESS` if allocation's size has been successfully changed. -Returns `VK_ERROR_OUT_OF_POOL_MEMORY` if allocation's size could not be changed. - -After successful call to this function, VmaAllocationInfo::size of this allocation changes. -All other parameters stay the same: memory pool and type, alignment, offset, mapped pointer. - -- Calling this function on allocation that is in lost state fails with result `VK_ERROR_VALIDATION_FAILED_EXT`. -- Calling this function with `newSize` same as current allocation size does nothing and returns `VK_SUCCESS`. -- Resizing dedicated allocations, as well as allocations created in pools that use linear - or buddy algorithm, is not supported. - The function returns `VK_ERROR_FEATURE_NOT_PRESENT` in such cases. - Support may be added in the future. +\deprecated +In version 2.2.0 it used to try to change allocation's size without moving or reallocating it. +In current version it returns `VK_SUCCESS` only if `newSize` equals current allocation's size. +Otherwise returns `VK_ERROR_OUT_OF_POOL_MEMORY`, indicating that allocation's size could not be changed. */ -VkResult vmaResizeAllocation( - VmaAllocator allocator, - VmaAllocation allocation, +VMA_CALL_PRE VkResult VMA_CALL_POST vmaResizeAllocation( + VmaAllocator VMA_NOT_NULL allocator, + VmaAllocation VMA_NOT_NULL allocation, VkDeviceSize newSize); /** \brief Returns current information about specified allocation and atomically marks it as used in current frame. -Current paramters of given allocation are returned in `pAllocationInfo`. +Current paramteres of given allocation are returned in `pAllocationInfo`. This function also atomically "touches" allocation - marks it as used in current frame, just like vmaTouchAllocation(). @@ -2629,10 +3396,10 @@ you can avoid calling it too often. (e.g. due to defragmentation or allocation becoming lost). - If you just want to check if allocation is not lost, vmaTouchAllocation() will work faster. */ -void vmaGetAllocationInfo( - VmaAllocator allocator, - VmaAllocation allocation, - VmaAllocationInfo* pAllocationInfo); +VMA_CALL_PRE void VMA_CALL_POST vmaGetAllocationInfo( + VmaAllocator VMA_NOT_NULL allocator, + VmaAllocation VMA_NOT_NULL allocation, + VmaAllocationInfo* VMA_NOT_NULL pAllocationInfo); /** \brief Returns `VK_TRUE` if allocation is not lost and atomically marks it as used in current frame. @@ -2648,9 +3415,9 @@ Lost allocation and the buffer/image still need to be destroyed. If the allocation has been created without #VMA_ALLOCATION_CREATE_CAN_BECOME_LOST_BIT flag, this function always returns `VK_TRUE`. */ -VkBool32 vmaTouchAllocation( - VmaAllocator allocator, - VmaAllocation allocation); +VMA_CALL_PRE VkBool32 VMA_CALL_POST vmaTouchAllocation( + VmaAllocator VMA_NOT_NULL allocator, + VmaAllocation VMA_NOT_NULL allocation); /** \brief Sets pUserData in given allocation to new value. @@ -2665,10 +3432,10 @@ If the flag was not used, the value of pointer `pUserData` is just copied to allocation's `pUserData`. It is opaque, so you can use it however you want - e.g. as a pointer, ordinal number or some handle to you own data. */ -void vmaSetAllocationUserData( - VmaAllocator allocator, - VmaAllocation allocation, - void* pUserData); +VMA_CALL_PRE void VMA_CALL_POST vmaSetAllocationUserData( + VmaAllocator VMA_NOT_NULL allocator, + VmaAllocation VMA_NOT_NULL allocation, + void* VMA_NULLABLE pUserData); /** \brief Creates new allocation that is in lost state from the beginning. @@ -2680,9 +3447,9 @@ Returned allocation is not tied to any specific memory pool or memory type and not bound to any image or buffer. It has size = 0. It cannot be turned into a real, non-empty allocation. */ -void vmaCreateLostAllocation( - VmaAllocator allocator, - VmaAllocation* pAllocation); +VMA_CALL_PRE void VMA_CALL_POST vmaCreateLostAllocation( + VmaAllocator VMA_NOT_NULL allocator, + VmaAllocation VMA_NULLABLE * VMA_NOT_NULL pAllocation); /** \brief Maps memory represented by given allocation and returns pointer to it. @@ -2717,23 +3484,33 @@ This function fails when used on allocation made in memory type that is not This function always fails when called for allocation that was created with #VMA_ALLOCATION_CREATE_CAN_BECOME_LOST_BIT flag. Such allocations cannot be mapped. + +This function doesn't automatically flush or invalidate caches. +If the allocation is made from a memory types that is not `HOST_COHERENT`, +you also need to use vmaInvalidateAllocation() / vmaFlushAllocation(), as required by Vulkan specification. */ -VkResult vmaMapMemory( - VmaAllocator allocator, - VmaAllocation allocation, - void** ppData); +VMA_CALL_PRE VkResult VMA_CALL_POST vmaMapMemory( + VmaAllocator VMA_NOT_NULL allocator, + VmaAllocation VMA_NOT_NULL allocation, + void* VMA_NULLABLE * VMA_NOT_NULL ppData); /** \brief Unmaps memory represented by given allocation, mapped previously using vmaMapMemory(). For details, see description of vmaMapMemory(). + +This function doesn't automatically flush or invalidate caches. +If the allocation is made from a memory types that is not `HOST_COHERENT`, +you also need to use vmaInvalidateAllocation() / vmaFlushAllocation(), as required by Vulkan specification. */ -void vmaUnmapMemory( - VmaAllocator allocator, - VmaAllocation allocation); +VMA_CALL_PRE void VMA_CALL_POST vmaUnmapMemory( + VmaAllocator VMA_NOT_NULL allocator, + VmaAllocation VMA_NOT_NULL allocation); /** \brief Flushes memory of given allocation. Calls `vkFlushMappedMemoryRanges()` for memory associated with given range of given allocation. +It needs to be called after writing to a mapped memory for memory types that are not `HOST_COHERENT`. +Unmap operation doesn't do that automatically. - `offset` must be relative to the beginning of allocation. - `size` can be `VK_WHOLE_SIZE`. It means all memory from `offset` the the end of given allocation. @@ -2742,12 +3519,25 @@ Calls `vkFlushMappedMemoryRanges()` for memory associated with given range of gi - If `size` is 0, this call is ignored. - If memory type that the `allocation` belongs to is not `HOST_VISIBLE` or it is `HOST_COHERENT`, this call is ignored. + +Warning! `offset` and `size` are relative to the contents of given `allocation`. +If you mean whole allocation, you can pass 0 and `VK_WHOLE_SIZE`, respectively. +Do not pass allocation's offset as `offset`!!! + +This function returns the `VkResult` from `vkFlushMappedMemoryRanges` if it is +called, otherwise `VK_SUCCESS`. */ -void vmaFlushAllocation(VmaAllocator allocator, VmaAllocation allocation, VkDeviceSize offset, VkDeviceSize size); +VMA_CALL_PRE VkResult VMA_CALL_POST vmaFlushAllocation( + VmaAllocator VMA_NOT_NULL allocator, + VmaAllocation VMA_NOT_NULL allocation, + VkDeviceSize offset, + VkDeviceSize size); /** \brief Invalidates memory of given allocation. Calls `vkInvalidateMappedMemoryRanges()` for memory associated with given range of given allocation. +It needs to be called before reading from a mapped memory for memory types that are not `HOST_COHERENT`. +Map operation doesn't do that automatically. - `offset` must be relative to the beginning of allocation. - `size` can be `VK_WHOLE_SIZE`. It means all memory from `offset` the the end of given allocation. @@ -2756,8 +3546,61 @@ Calls `vkInvalidateMappedMemoryRanges()` for memory associated with given range - If `size` is 0, this call is ignored. - If memory type that the `allocation` belongs to is not `HOST_VISIBLE` or it is `HOST_COHERENT`, this call is ignored. + +Warning! `offset` and `size` are relative to the contents of given `allocation`. +If you mean whole allocation, you can pass 0 and `VK_WHOLE_SIZE`, respectively. +Do not pass allocation's offset as `offset`!!! + +This function returns the `VkResult` from `vkInvalidateMappedMemoryRanges` if +it is called, otherwise `VK_SUCCESS`. */ -void vmaInvalidateAllocation(VmaAllocator allocator, VmaAllocation allocation, VkDeviceSize offset, VkDeviceSize size); +VMA_CALL_PRE VkResult VMA_CALL_POST vmaInvalidateAllocation( + VmaAllocator VMA_NOT_NULL allocator, + VmaAllocation VMA_NOT_NULL allocation, + VkDeviceSize offset, + VkDeviceSize size); + +/** \brief Flushes memory of given set of allocations. + +Calls `vkFlushMappedMemoryRanges()` for memory associated with given ranges of given allocations. +For more information, see documentation of vmaFlushAllocation(). + +\param allocator +\param allocationCount +\param allocations +\param offsets If not null, it must point to an array of offsets of regions to flush, relative to the beginning of respective allocations. Null means all ofsets are zero. +\param sizes If not null, it must point to an array of sizes of regions to flush in respective allocations. Null means `VK_WHOLE_SIZE` for all allocations. + +This function returns the `VkResult` from `vkFlushMappedMemoryRanges` if it is +called, otherwise `VK_SUCCESS`. +*/ +VMA_CALL_PRE VkResult VMA_CALL_POST vmaFlushAllocations( + VmaAllocator VMA_NOT_NULL allocator, + uint32_t allocationCount, + const VmaAllocation VMA_NOT_NULL * VMA_NULLABLE VMA_LEN_IF_NOT_NULL(allocationCount) allocations, + const VkDeviceSize* VMA_NULLABLE VMA_LEN_IF_NOT_NULL(allocationCount) offsets, + const VkDeviceSize* VMA_NULLABLE VMA_LEN_IF_NOT_NULL(allocationCount) sizes); + +/** \brief Invalidates memory of given set of allocations. + +Calls `vkInvalidateMappedMemoryRanges()` for memory associated with given ranges of given allocations. +For more information, see documentation of vmaInvalidateAllocation(). + +\param allocator +\param allocationCount +\param allocations +\param offsets If not null, it must point to an array of offsets of regions to flush, relative to the beginning of respective allocations. Null means all ofsets are zero. +\param sizes If not null, it must point to an array of sizes of regions to flush in respective allocations. Null means `VK_WHOLE_SIZE` for all allocations. + +This function returns the `VkResult` from `vkInvalidateMappedMemoryRanges` if it is +called, otherwise `VK_SUCCESS`. +*/ +VMA_CALL_PRE VkResult VMA_CALL_POST vmaInvalidateAllocations( + VmaAllocator VMA_NOT_NULL allocator, + uint32_t allocationCount, + const VmaAllocation VMA_NOT_NULL * VMA_NULLABLE VMA_LEN_IF_NOT_NULL(allocationCount) allocations, + const VkDeviceSize* VMA_NULLABLE VMA_LEN_IF_NOT_NULL(allocationCount) offsets, + const VkDeviceSize* VMA_NULLABLE VMA_LEN_IF_NOT_NULL(allocationCount) sizes); /** \brief Checks magic number in margins around all allocations in given memory types (in both default and custom pools) in search for corruptions. @@ -2775,7 +3618,7 @@ Possible return values: `VMA_ASSERT` is also fired in that case. - Other value: Error returned by Vulkan, e.g. memory mapping failure. */ -VkResult vmaCheckCorruption(VmaAllocator allocator, uint32_t memoryTypeBits); +VMA_CALL_PRE VkResult VMA_CALL_POST vmaCheckCorruption(VmaAllocator VMA_NOT_NULL allocator, uint32_t memoryTypeBits); /** \struct VmaDefragmentationContext \brief Represents Opaque object that represents started defragmentation process. @@ -2787,6 +3630,7 @@ VK_DEFINE_HANDLE(VmaDefragmentationContext) /// Flags to be used in vmaDefragmentationBegin(). None at the moment. Reserved for future use. typedef enum VmaDefragmentationFlagBits { + VMA_DEFRAGMENTATION_FLAG_INCREMENTAL = 0x1, VMA_DEFRAGMENTATION_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF } VmaDefragmentationFlagBits; typedef VkFlags VmaDefragmentationFlags; @@ -2810,13 +3654,13 @@ typedef struct VmaDefragmentationInfo2 { It is safe to pass allocations that are in the lost state - they are ignored. All allocations not present in this array are considered non-moveable during this defragmentation. */ - VmaAllocation* pAllocations; + const VmaAllocation VMA_NOT_NULL * VMA_NULLABLE VMA_LEN_IF_NOT_NULL(allocationCount) pAllocations; /** \brief Optional, output. Pointer to array that will be filled with information whether the allocation at certain index has been changed during defragmentation. The array should have `allocationCount` elements. You can pass null if you are not interested in this information. */ - VkBool32* pAllocationsChanged; + VkBool32* VMA_NULLABLE VMA_LEN_IF_NOT_NULL(allocationCount) pAllocationsChanged; /** \brief Numer of pools in `pPools` array. */ uint32_t poolCount; @@ -2835,9 +3679,9 @@ typedef struct VmaDefragmentationInfo2 { Using this array is equivalent to specifying all allocations from the pools in `pAllocations`. It might be more efficient. */ - VmaPool* pPools; + const VmaPool VMA_NOT_NULL * VMA_NULLABLE VMA_LEN_IF_NOT_NULL(poolCount) pPools; /** \brief Maximum total numbers of bytes that can be copied while moving allocations to different places using transfers on CPU side, like `memcpy()`, `memmove()`. - + `VK_WHOLE_SIZE` means no limit. */ VkDeviceSize maxCpuBytesToMove; @@ -2847,7 +3691,7 @@ typedef struct VmaDefragmentationInfo2 { */ uint32_t maxCpuAllocationsToMove; /** \brief Maximum total numbers of bytes that can be copied while moving allocations to different places using transfers on GPU side, posted to `commandBuffer`. - + `VK_WHOLE_SIZE` means no limit. */ VkDeviceSize maxGpuBytesToMove; @@ -2864,16 +3708,31 @@ typedef struct VmaDefragmentationInfo2 { Passing null means that only CPU defragmentation will be performed. */ - VkCommandBuffer commandBuffer; + VkCommandBuffer VMA_NULLABLE commandBuffer; } VmaDefragmentationInfo2; +typedef struct VmaDefragmentationPassMoveInfo { + VmaAllocation VMA_NOT_NULL allocation; + VkDeviceMemory VMA_NOT_NULL_NON_DISPATCHABLE memory; + VkDeviceSize offset; +} VmaDefragmentationPassMoveInfo; + +/** \brief Parameters for incremental defragmentation steps. + +To be used with function vmaBeginDefragmentationPass(). +*/ +typedef struct VmaDefragmentationPassInfo { + uint32_t moveCount; + VmaDefragmentationPassMoveInfo* VMA_NOT_NULL VMA_LEN_IF_NOT_NULL(moveCount) pMoves; +} VmaDefragmentationPassInfo; + /** \brief Deprecated. Optional configuration parameters to be passed to function vmaDefragment(). \deprecated This is a part of the old interface. It is recommended to use structure #VmaDefragmentationInfo2 and function vmaDefragmentationBegin() instead. */ typedef struct VmaDefragmentationInfo { /** \brief Maximum total numbers of bytes that can be copied while moving allocations to different places. - + Default is `VK_WHOLE_SIZE`, which means no limit. */ VkDeviceSize maxBytesToMove; @@ -2921,21 +3780,34 @@ Warning! Between the call to vmaDefragmentationBegin() and vmaDefragmentationEnd They become valid after call to vmaDefragmentationEnd(). - If `pInfo->commandBuffer` is not null, you must submit that command buffer and make sure it finished execution before calling vmaDefragmentationEnd(). + +For more information and important limitations regarding defragmentation, see documentation chapter: +[Defragmentation](@ref defragmentation). */ -VkResult vmaDefragmentationBegin( - VmaAllocator allocator, - const VmaDefragmentationInfo2* pInfo, - VmaDefragmentationStats* pStats, - VmaDefragmentationContext *pContext); +VMA_CALL_PRE VkResult VMA_CALL_POST vmaDefragmentationBegin( + VmaAllocator VMA_NOT_NULL allocator, + const VmaDefragmentationInfo2* VMA_NOT_NULL pInfo, + VmaDefragmentationStats* VMA_NULLABLE pStats, + VmaDefragmentationContext VMA_NULLABLE * VMA_NOT_NULL pContext); /** \brief Ends defragmentation process. Use this function to finish defragmentation started by vmaDefragmentationBegin(). It is safe to pass `context == null`. The function then does nothing. */ -VkResult vmaDefragmentationEnd( - VmaAllocator allocator, - VmaDefragmentationContext context); +VMA_CALL_PRE VkResult VMA_CALL_POST vmaDefragmentationEnd( + VmaAllocator VMA_NOT_NULL allocator, + VmaDefragmentationContext VMA_NULLABLE context); + +VMA_CALL_PRE VkResult VMA_CALL_POST vmaBeginDefragmentationPass( + VmaAllocator VMA_NOT_NULL allocator, + VmaDefragmentationContext VMA_NULLABLE context, + VmaDefragmentationPassInfo* VMA_NOT_NULL pInfo +); +VMA_CALL_PRE VkResult VMA_CALL_POST vmaEndDefragmentationPass( + VmaAllocator VMA_NOT_NULL allocator, + VmaDefragmentationContext VMA_NULLABLE context +); /** \brief Deprecated. Compacts memory by moving allocations. @@ -2977,13 +3849,13 @@ you should measure that on your platform. For more information, see [Defragmentation](@ref defragmentation) chapter. */ -VkResult vmaDefragment( - VmaAllocator allocator, - VmaAllocation* pAllocations, +VMA_CALL_PRE VkResult VMA_CALL_POST vmaDefragment( + VmaAllocator VMA_NOT_NULL allocator, + const VmaAllocation VMA_NOT_NULL * VMA_NOT_NULL VMA_LEN_IF_NOT_NULL(allocationCount) pAllocations, size_t allocationCount, - VkBool32* pAllocationsChanged, - const VmaDefragmentationInfo *pDefragmentationInfo, - VmaDefragmentationStats* pDefragmentationStats); + VkBool32* VMA_NULLABLE VMA_LEN_IF_NOT_NULL(allocationCount) pAllocationsChanged, + const VmaDefragmentationInfo* VMA_NULLABLE pDefragmentationInfo, + VmaDefragmentationStats* VMA_NULLABLE pDefragmentationStats); /** \brief Binds buffer to allocation. @@ -2997,10 +3869,27 @@ allocations, calls to `vkBind*Memory()` or `vkMapMemory()` won't happen from mul It is recommended to use function vmaCreateBuffer() instead of this one. */ -VkResult vmaBindBufferMemory( - VmaAllocator allocator, - VmaAllocation allocation, - VkBuffer buffer); +VMA_CALL_PRE VkResult VMA_CALL_POST vmaBindBufferMemory( + VmaAllocator VMA_NOT_NULL allocator, + VmaAllocation VMA_NOT_NULL allocation, + VkBuffer VMA_NOT_NULL_NON_DISPATCHABLE buffer); + +/** \brief Binds buffer to allocation with additional parameters. + +@param allocationLocalOffset Additional offset to be added while binding, relative to the beginnig of the `allocation`. Normally it should be 0. +@param pNext A chain of structures to be attached to `VkBindBufferMemoryInfoKHR` structure used internally. Normally it should be null. + +This function is similar to vmaBindBufferMemory(), but it provides additional parameters. + +If `pNext` is not null, #VmaAllocator object must have been created with #VMA_ALLOCATOR_CREATE_KHR_BIND_MEMORY2_BIT flag +or with VmaAllocatorCreateInfo::vulkanApiVersion `>= VK_API_VERSION_1_1`. Otherwise the call fails. +*/ +VMA_CALL_PRE VkResult VMA_CALL_POST vmaBindBufferMemory2( + VmaAllocator VMA_NOT_NULL allocator, + VmaAllocation VMA_NOT_NULL allocation, + VkDeviceSize allocationLocalOffset, + VkBuffer VMA_NOT_NULL_NON_DISPATCHABLE buffer, + const void* VMA_NULLABLE pNext); /** \brief Binds image to allocation. @@ -3014,10 +3903,27 @@ allocations, calls to `vkBind*Memory()` or `vkMapMemory()` won't happen from mul It is recommended to use function vmaCreateImage() instead of this one. */ -VkResult vmaBindImageMemory( - VmaAllocator allocator, - VmaAllocation allocation, - VkImage image); +VMA_CALL_PRE VkResult VMA_CALL_POST vmaBindImageMemory( + VmaAllocator VMA_NOT_NULL allocator, + VmaAllocation VMA_NOT_NULL allocation, + VkImage VMA_NOT_NULL_NON_DISPATCHABLE image); + +/** \brief Binds image to allocation with additional parameters. + +@param allocationLocalOffset Additional offset to be added while binding, relative to the beginnig of the `allocation`. Normally it should be 0. +@param pNext A chain of structures to be attached to `VkBindImageMemoryInfoKHR` structure used internally. Normally it should be null. + +This function is similar to vmaBindImageMemory(), but it provides additional parameters. + +If `pNext` is not null, #VmaAllocator object must have been created with #VMA_ALLOCATOR_CREATE_KHR_BIND_MEMORY2_BIT flag +or with VmaAllocatorCreateInfo::vulkanApiVersion `>= VK_API_VERSION_1_1`. Otherwise the call fails. +*/ +VMA_CALL_PRE VkResult VMA_CALL_POST vmaBindImageMemory2( + VmaAllocator VMA_NOT_NULL allocator, + VmaAllocation VMA_NOT_NULL allocation, + VkDeviceSize allocationLocalOffset, + VkImage VMA_NOT_NULL_NON_DISPATCHABLE image, + const void* VMA_NULLABLE pNext); /** @param[out] pBuffer Buffer that was created. @@ -3037,21 +3943,25 @@ If the function succeeded, you must destroy both buffer and allocation when you no longer need them using either convenience function vmaDestroyBuffer() or separately, using `vkDestroyBuffer()` and vmaFreeMemory(). -If VMA_ALLOCATOR_CREATE_KHR_DEDICATED_ALLOCATION_BIT flag was used, +If #VMA_ALLOCATOR_CREATE_KHR_DEDICATED_ALLOCATION_BIT flag was used, VK_KHR_dedicated_allocation extension is used internally to query driver whether it requires or prefers the new buffer to have dedicated allocation. If yes, and if dedicated allocation is possible (VmaAllocationCreateInfo::pool is null -and VMA_ALLOCATION_CREATE_NEVER_ALLOCATE_BIT is not used), it creates dedicated +and #VMA_ALLOCATION_CREATE_NEVER_ALLOCATE_BIT is not used), it creates dedicated allocation for this buffer, just like when using -VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT. +#VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT. + +\note This function creates a new `VkBuffer`. Sub-allocation of parts of one large buffer, +although recommended as a good practice, is out of scope of this library and could be implemented +by the user as a higher-level logic on top of VMA. */ -VkResult vmaCreateBuffer( - VmaAllocator allocator, - const VkBufferCreateInfo* pBufferCreateInfo, - const VmaAllocationCreateInfo* pAllocationCreateInfo, - VkBuffer* pBuffer, - VmaAllocation* pAllocation, - VmaAllocationInfo* pAllocationInfo); +VMA_CALL_PRE VkResult VMA_CALL_POST vmaCreateBuffer( + VmaAllocator VMA_NOT_NULL allocator, + const VkBufferCreateInfo* VMA_NOT_NULL pBufferCreateInfo, + const VmaAllocationCreateInfo* VMA_NOT_NULL pAllocationCreateInfo, + VkBuffer VMA_NULLABLE_NON_DISPATCHABLE * VMA_NOT_NULL pBuffer, + VmaAllocation VMA_NULLABLE * VMA_NOT_NULL pAllocation, + VmaAllocationInfo* VMA_NULLABLE pAllocationInfo); /** \brief Destroys Vulkan buffer and frees allocated memory. @@ -3064,19 +3974,19 @@ vmaFreeMemory(allocator, allocation); It it safe to pass null as buffer and/or allocation. */ -void vmaDestroyBuffer( - VmaAllocator allocator, - VkBuffer buffer, - VmaAllocation allocation); +VMA_CALL_PRE void VMA_CALL_POST vmaDestroyBuffer( + VmaAllocator VMA_NOT_NULL allocator, + VkBuffer VMA_NULLABLE_NON_DISPATCHABLE buffer, + VmaAllocation VMA_NULLABLE allocation); /// Function similar to vmaCreateBuffer(). -VkResult vmaCreateImage( - VmaAllocator allocator, - const VkImageCreateInfo* pImageCreateInfo, - const VmaAllocationCreateInfo* pAllocationCreateInfo, - VkImage* pImage, - VmaAllocation* pAllocation, - VmaAllocationInfo* pAllocationInfo); +VMA_CALL_PRE VkResult VMA_CALL_POST vmaCreateImage( + VmaAllocator VMA_NOT_NULL allocator, + const VkImageCreateInfo* VMA_NOT_NULL pImageCreateInfo, + const VmaAllocationCreateInfo* VMA_NOT_NULL pAllocationCreateInfo, + VkImage VMA_NULLABLE_NON_DISPATCHABLE * VMA_NOT_NULL pImage, + VmaAllocation VMA_NULLABLE * VMA_NOT_NULL pAllocation, + VmaAllocationInfo* VMA_NULLABLE pAllocationInfo); /** \brief Destroys Vulkan image and frees allocated memory. @@ -3089,10 +3999,10 @@ vmaFreeMemory(allocator, allocation); It it safe to pass null as image and/or allocation. */ -void vmaDestroyImage( - VmaAllocator allocator, - VkImage image, - VmaAllocation allocation); +VMA_CALL_PRE void VMA_CALL_POST vmaDestroyImage( + VmaAllocator VMA_NOT_NULL allocator, + VkImage VMA_NULLABLE_NON_DISPATCHABLE image, + VmaAllocation VMA_NULLABLE allocation); #ifdef __cplusplus } @@ -3111,6 +4021,17 @@ void vmaDestroyImage( #include #include #include +#include + +#if VMA_RECORDING_ENABLED + #include + #if defined(_WIN32) + #include + #else + #include + #include + #endif +#endif /******************************************************************************* CONFIGURATION SECTION @@ -3124,12 +4045,23 @@ Define this macro to 1 to make the library fetch pointers to Vulkan functions internally, like: vulkanFunctions.vkAllocateMemory = &vkAllocateMemory; - -Define to 0 if you are going to provide you own pointers to Vulkan functions via -VmaAllocatorCreateInfo::pVulkanFunctions. */ #if !defined(VMA_STATIC_VULKAN_FUNCTIONS) && !defined(VK_NO_PROTOTYPES) -#define VMA_STATIC_VULKAN_FUNCTIONS 1 + #define VMA_STATIC_VULKAN_FUNCTIONS 1 +#endif + +/* +Define this macro to 1 to make the library fetch pointers to Vulkan functions +internally, like: + + vulkanFunctions.vkAllocateMemory = (PFN_vkAllocateMemory)vkGetDeviceProcAddr(m_hDevice, vkAllocateMemory); +*/ +#if !defined(VMA_DYNAMIC_VULKAN_FUNCTIONS) + #define VMA_DYNAMIC_VULKAN_FUNCTIONS 1 + #if defined(VK_NO_PROTOTYPES) + extern PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr; + extern PFN_vkGetDeviceProcAddr vkGetDeviceProcAddr; + #endif #endif // Define this macro to 1 to make the library use STL containers instead of its own implementation. @@ -3161,6 +4093,10 @@ the containers. #endif #endif +/* +THESE INCLUDES ARE NOT ENABLED BY DEFAULT. +Library has its own container implementation. +*/ #if VMA_USE_STL_VECTOR #include #endif @@ -3180,7 +4116,6 @@ remove them if not needed. #include // for assert #include // for min, max #include -#include // for std::atomic #ifndef VMA_NULL // Value used as null pointer. Define it to e.g.: nullptr, NULL, 0, (void*)0. @@ -3189,7 +4124,7 @@ remove them if not needed. #if defined(__ANDROID_API__) && (__ANDROID_API__ < 16) #include -void *aligned_alloc(size_t alignment, size_t size) +static void* vma_aligned_alloc(size_t alignment, size_t size) { // alignment must be >= sizeof(void*) if(alignment < sizeof(void*)) @@ -3199,10 +4134,27 @@ void *aligned_alloc(size_t alignment, size_t size) return memalign(alignment, size); } -#elif defined(__APPLE__) || defined(__ANDROID__) +#elif defined(__APPLE__) || defined(__ANDROID__) || (defined(__linux__) && defined(__GLIBCXX__) && !defined(_GLIBCXX_HAVE_ALIGNED_ALLOC)) #include -void *aligned_alloc(size_t alignment, size_t size) + +#if defined(__APPLE__) +#include +#endif + +static void* vma_aligned_alloc(size_t alignment, size_t size) { +#if defined(__APPLE__) && (defined(MAC_OS_X_VERSION_10_16) || defined(__IPHONE_14_0)) +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_16 || __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_14_0 + // For C++14, usr/include/malloc/_malloc.h declares aligned_alloc()) only + // with the MacOSX11.0 SDK in Xcode 12 (which is what adds + // MAC_OS_X_VERSION_10_16), even though the function is marked + // availabe for 10.15. That's why the preprocessor checks for 10.16 but + // the __builtin_available checks for 10.15. + // People who use C++17 could call aligned_alloc with the 10.15 SDK already. + if (__builtin_available(macOS 10.15, iOS 13, *)) + return aligned_alloc(alignment, size); +#endif +#endif // alignment must be >= sizeof(void*) if(alignment < sizeof(void*)) { @@ -3214,6 +4166,28 @@ void *aligned_alloc(size_t alignment, size_t size) return pointer; return VMA_NULL; } +#elif defined(_WIN32) +static void* vma_aligned_alloc(size_t alignment, size_t size) +{ + return _aligned_malloc(size, alignment); +} +#else +static void* vma_aligned_alloc(size_t alignment, size_t size) +{ + return aligned_alloc(alignment, size); +} +#endif + +#if defined(_WIN32) +static void vma_aligned_free(void* ptr) +{ + _aligned_free(ptr); +} +#else +static void vma_aligned_free(void* ptr) +{ + free(ptr); +} #endif // If your compiler is not compatible with C++11 and definition of @@ -3223,20 +4197,20 @@ void *aligned_alloc(size_t alignment, size_t size) // Normal assert to check for programmer's errors, especially in Debug configuration. #ifndef VMA_ASSERT - #ifdef _DEBUG - #define VMA_ASSERT(expr) assert(expr) - #else + #ifdef NDEBUG #define VMA_ASSERT(expr) + #else + #define VMA_ASSERT(expr) assert(expr) #endif #endif // Assert that will be called very often, like inside data structures e.g. operator[]. // Making it non-empty can make program slow. #ifndef VMA_HEAVY_ASSERT - #ifdef _DEBUG - #define VMA_HEAVY_ASSERT(expr) //VMA_ASSERT(expr) - #else + #ifdef NDEBUG #define VMA_HEAVY_ASSERT(expr) + #else + #define VMA_HEAVY_ASSERT(expr) //VMA_ASSERT(expr) #endif #endif @@ -3245,19 +4219,16 @@ void *aligned_alloc(size_t alignment, size_t size) #endif #ifndef VMA_SYSTEM_ALIGNED_MALLOC - #if defined(_WIN32) - #define VMA_SYSTEM_ALIGNED_MALLOC(size, alignment) (_aligned_malloc((size), (alignment))) - #else - #define VMA_SYSTEM_ALIGNED_MALLOC(size, alignment) (aligned_alloc((alignment), (size) )) - #endif + #define VMA_SYSTEM_ALIGNED_MALLOC(size, alignment) vma_aligned_alloc((alignment), (size)) #endif -#ifndef VMA_SYSTEM_FREE - #if defined(_WIN32) - #define VMA_SYSTEM_FREE(ptr) _aligned_free(ptr) +#ifndef VMA_SYSTEM_ALIGNED_FREE + // VMA_SYSTEM_FREE is the old name, but might have been defined by the user + #if defined(VMA_SYSTEM_FREE) + #define VMA_SYSTEM_ALIGNED_FREE(ptr) VMA_SYSTEM_FREE(ptr) #else - #define VMA_SYSTEM_FREE(ptr) free(ptr) - #endif + #define VMA_SYSTEM_ALIGNED_FREE(ptr) vma_aligned_free(ptr) + #endif #endif #ifndef VMA_MIN @@ -3308,6 +4279,7 @@ void *aligned_alloc(size_t alignment, size_t size) public: void Lock() { m_Mutex.lock(); } void Unlock() { m_Mutex.unlock(); } + bool TryLock() { return m_Mutex.try_lock(); } private: std::mutex m_Mutex; }; @@ -3324,22 +4296,27 @@ void *aligned_alloc(size_t alignment, size_t size) public: void LockRead() { m_Mutex.lock_shared(); } void UnlockRead() { m_Mutex.unlock_shared(); } + bool TryLockRead() { return m_Mutex.try_lock_shared(); } void LockWrite() { m_Mutex.lock(); } void UnlockWrite() { m_Mutex.unlock(); } + bool TryLockWrite() { return m_Mutex.try_lock(); } private: std::shared_mutex m_Mutex; }; #define VMA_RW_MUTEX VmaRWMutex - #elif defined(_WIN32) + #elif defined(_WIN32) && defined(WINVER) && WINVER >= 0x0600 // Use SRWLOCK from WinAPI. + // Minimum supported client = Windows Vista, server = Windows Server 2008. class VmaRWMutex { public: VmaRWMutex() { InitializeSRWLock(&m_Lock); } void LockRead() { AcquireSRWLockShared(&m_Lock); } void UnlockRead() { ReleaseSRWLockShared(&m_Lock); } + bool TryLockRead() { return TryAcquireSRWLockShared(&m_Lock) != FALSE; } void LockWrite() { AcquireSRWLockExclusive(&m_Lock); } void UnlockWrite() { ReleaseSRWLockExclusive(&m_Lock); } + bool TryLockWrite() { return TryAcquireSRWLockExclusive(&m_Lock) != FALSE; } private: SRWLOCK m_Lock; }; @@ -3351,8 +4328,10 @@ void *aligned_alloc(size_t alignment, size_t size) public: void LockRead() { m_Mutex.Lock(); } void UnlockRead() { m_Mutex.Unlock(); } + bool TryLockRead() { return m_Mutex.TryLock(); } void LockWrite() { m_Mutex.Lock(); } void UnlockWrite() { m_Mutex.Unlock(); } + bool TryLockWrite() { return m_Mutex.TryLock(); } private: VMA_MUTEX m_Mutex; }; @@ -3361,15 +4340,16 @@ void *aligned_alloc(size_t alignment, size_t size) #endif // #ifndef VMA_RW_MUTEX /* -If providing your own implementation, you need to implement a subset of std::atomic: - -- Constructor(uint32_t desired) -- uint32_t load() const -- void store(uint32_t desired) -- bool compare_exchange_weak(uint32_t& expected, uint32_t desired) +If providing your own implementation, you need to implement a subset of std::atomic. */ #ifndef VMA_ATOMIC_UINT32 - #define VMA_ATOMIC_UINT32 std::atomic + #include + #define VMA_ATOMIC_UINT32 std::atomic +#endif + +#ifndef VMA_ATOMIC_UINT64 + #include + #define VMA_ATOMIC_UINT64 std::atomic #endif #ifndef VMA_DEBUG_ALWAYS_DEDICATED_MEMORY @@ -3458,6 +4438,12 @@ static const uint8_t VMA_ALLOCATION_FILL_PATTERN_DESTROYED = 0xEF; END OF CONFIGURATION */ +// # Copy of some Vulkan definitions so we don't need to check their existence just to handle few constants. + +static const uint32_t VK_MEMORY_PROPERTY_DEVICE_COHERENT_BIT_AMD_COPY = 0x00000040; +static const uint32_t VK_MEMORY_PROPERTY_DEVICE_UNCACHED_BIT_AMD_COPY = 0x00000080; +static const uint32_t VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT_COPY = 0x00020000; + static const uint32_t VMA_ALLOCATION_INTERNAL_STRATEGY_MIN_OFFSET = 0x10000000u; static VkAllocationCallbacks VmaEmptyAllocationCallbacks = { @@ -3466,34 +4452,12 @@ static VkAllocationCallbacks VmaEmptyAllocationCallbacks = { // Returns number of bits set to 1 in (v). static inline uint32_t VmaCountBitsSet(uint32_t v) { - uint32_t c = v - ((v >> 1) & 0x55555555); - c = ((c >> 2) & 0x33333333) + (c & 0x33333333); - c = ((c >> 4) + c) & 0x0F0F0F0F; - c = ((c >> 8) + c) & 0x00FF00FF; - c = ((c >> 16) + c) & 0x0000FFFF; - return c; -} - -// Aligns given value up to nearest multiply of align value. For example: VmaAlignUp(11, 8) = 16. -// Use types like uint32_t, uint64_t as T. -template -static inline T VmaAlignUp(T val, T align) -{ - return (val + align - 1) / align * align; -} -// Aligns given value down to nearest multiply of align value. For example: VmaAlignUp(11, 8) = 8. -// Use types like uint32_t, uint64_t as T. -template -static inline T VmaAlignDown(T val, T align) -{ - return val / align * align; -} - -// Division with mathematical rounding to nearest number. -template -static inline T VmaRoundDiv(T x, T y) -{ - return (x + (y / (T)2)) / y; + uint32_t c = v - ((v >> 1) & 0x55555555); + c = ((c >> 2) & 0x33333333) + (c & 0x33333333); + c = ((c >> 4) + c) & 0x0F0F0F0F; + c = ((c >> 8) + c) & 0x00FF00FF; + c = ((c >> 16) + c) & 0x0000FFFF; + return c; } /* @@ -3507,10 +4471,34 @@ inline bool VmaIsPow2(T x) return (x & (x-1)) == 0; } +// Aligns given value up to nearest multiply of align value. For example: VmaAlignUp(11, 8) = 16. +// Use types like uint32_t, uint64_t as T. +template +static inline T VmaAlignUp(T val, T alignment) +{ + VMA_HEAVY_ASSERT(VmaIsPow2(alignment)); + return (val + alignment - 1) & ~(alignment - 1); +} +// Aligns given value down to nearest multiply of align value. For example: VmaAlignUp(11, 8) = 8. +// Use types like uint32_t, uint64_t as T. +template +static inline T VmaAlignDown(T val, T alignment) +{ + VMA_HEAVY_ASSERT(VmaIsPow2(alignment)); + return val & ~(alignment - 1); +} + +// Division with mathematical rounding to nearest number. +template +static inline T VmaRoundDiv(T x, T y) +{ + return (x + (y / (T)2)) / y; +} + // Returns smallest power of 2 greater or equal to v. static inline uint32_t VmaNextPow2(uint32_t v) { - v--; + v--; v |= v >> 1; v |= v >> 2; v |= v >> 4; @@ -3521,7 +4509,7 @@ static inline uint32_t VmaNextPow2(uint32_t v) } static inline uint64_t VmaNextPow2(uint64_t v) { - v--; + v--; v |= v >> 1; v |= v >> 2; v |= v >> 4; @@ -3560,6 +4548,8 @@ static inline bool VmaStrIsEmpty(const char* pStr) return pStr == VMA_NULL || *pStr == '\0'; } +#if VMA_STATS_STRING_ENABLED + static const char* VmaAlgorithmToStr(uint32_t algorithm) { switch(algorithm) @@ -3576,6 +4566,8 @@ static const char* VmaAlgorithmToStr(uint32_t algorithm) } } +#endif // #if VMA_STATS_STRING_ENABLED + #ifndef VMA_SORT template @@ -3662,7 +4654,7 @@ static inline bool VmaIsBufferImageGranularityConflict( { VMA_SWAP(suballocType1, suballocType2); } - + switch(suballocType1) { case VMA_SUBALLOCATION_TYPE_FREE: @@ -3691,16 +4683,21 @@ static inline bool VmaIsBufferImageGranularityConflict( static void VmaWriteMagicValue(void* pData, VkDeviceSize offset) { +#if VMA_DEBUG_MARGIN > 0 && VMA_DEBUG_DETECT_CORRUPTION uint32_t* pDst = (uint32_t*)((char*)pData + offset); const size_t numberCount = VMA_DEBUG_MARGIN / sizeof(uint32_t); for(size_t i = 0; i < numberCount; ++i, ++pDst) { *pDst = VMA_CORRUPTION_DETECTION_MAGIC_VALUE; } +#else + // no-op +#endif } static bool VmaValidateMagicValue(const void* pData, VkDeviceSize offset) { +#if VMA_DEBUG_MARGIN > 0 && VMA_DEBUG_DETECT_CORRUPTION const uint32_t* pSrc = (const uint32_t*)((const char*)pData + offset); const size_t numberCount = VMA_DEBUG_MARGIN / sizeof(uint32_t); for(size_t i = 0; i < numberCount; ++i, ++pSrc) @@ -3710,9 +4707,22 @@ static bool VmaValidateMagicValue(const void* pData, VkDeviceSize offset) return false; } } +#endif return true; } +/* +Fills structure with parameters of an example buffer to be used for transfers +during GPU memory defragmentation. +*/ +static void VmaFillGpuDefragmentationBufferCreateInfo(VkBufferCreateInfo& outBufCreateInfo) +{ + memset(&outBufCreateInfo, 0, sizeof(outBufCreateInfo)); + outBufCreateInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + outBufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; + outBufCreateInfo.size = (VkDeviceSize)VMA_DEFAULT_LARGE_HEAP_BLOCK_SIZE; // Example size. +} + // Helper RAII class to lock a mutex in constructor and unlock it in destructor (at the end of scope). struct VmaMutexLock { @@ -3773,12 +4783,12 @@ Returned value is the found element, if present in the collection or place where new element with value (key) should be inserted. */ template -static IterT VmaBinaryFindFirstNotLess(IterT beg, IterT end, const KeyT &key, CmpLess cmp) +static IterT VmaBinaryFindFirstNotLess(IterT beg, IterT end, const KeyT &key, const CmpLess& cmp) { size_t down = 0, up = (end - beg); while(down < up) { - const size_t mid = (down + up) / 2; + const size_t mid = down + (up - down) / 2; // Overflow-safe midpoint calculation if(cmp(*(beg+mid), key)) { down = mid + 1; @@ -3791,6 +4801,19 @@ static IterT VmaBinaryFindFirstNotLess(IterT beg, IterT end, const KeyT &key, Cm return beg + down; } +template +IterT VmaBinaryFindSorted(const IterT& beg, const IterT& end, const KeyT& value, const CmpLess& cmp) +{ + IterT it = VmaBinaryFindFirstNotLess( + beg, end, value, cmp); + if(it == end || + (!cmp(*it, value) && !cmp(value, *it))) + { + return it; + } + return end; +} + /* Returns true if all pointers in the array are not-null and unique. Warning! O(n^2) complexity. Use only inside VMA_HEAVY_ASSERT. @@ -3817,15 +4840,23 @@ static bool VmaValidatePointerArray(uint32_t count, const T* arr) return true; } +template +static inline void VmaPnextChainPushFront(MainT* mainStruct, NewT* newStruct) +{ + newStruct->pNext = mainStruct->pNext; + mainStruct->pNext = newStruct; +} + //////////////////////////////////////////////////////////////////////////////// // Memory allocation static void* VmaMalloc(const VkAllocationCallbacks* pAllocationCallbacks, size_t size, size_t alignment) { + void* result = VMA_NULL; if((pAllocationCallbacks != VMA_NULL) && (pAllocationCallbacks->pfnAllocation != VMA_NULL)) { - return (*pAllocationCallbacks->pfnAllocation)( + result = (*pAllocationCallbacks->pfnAllocation)( pAllocationCallbacks->pUserData, size, alignment, @@ -3833,8 +4864,10 @@ static void* VmaMalloc(const VkAllocationCallbacks* pAllocationCallbacks, size_t } else { - return VMA_SYSTEM_ALIGNED_MALLOC(size, alignment); + result = VMA_SYSTEM_ALIGNED_MALLOC(size, alignment); } + VMA_ASSERT(result != VMA_NULL && "CPU memory allocation failed."); + return result; } static void VmaFree(const VkAllocationCallbacks* pAllocationCallbacks, void* ptr) @@ -3846,7 +4879,7 @@ static void VmaFree(const VkAllocationCallbacks* pAllocationCallbacks, void* ptr } else { - VMA_SYSTEM_FREE(ptr); + VMA_SYSTEM_ALIGNED_FREE(ptr); } } @@ -3886,6 +4919,30 @@ static void vma_delete_array(const VkAllocationCallbacks* pAllocationCallbacks, } } +static char* VmaCreateStringCopy(const VkAllocationCallbacks* allocs, const char* srcStr) +{ + if(srcStr != VMA_NULL) + { + const size_t len = strlen(srcStr); + char* const result = vma_new_array(allocs, char, len + 1); + memcpy(result, srcStr, len + 1); + return result; + } + else + { + return VMA_NULL; + } +} + +static void VmaFreeString(const VkAllocationCallbacks* allocs, char* str) +{ + if(str != VMA_NULL) + { + const size_t len = strlen(str); + vma_delete_array(allocs, str, len + 1); + } +} + // STL-compatible allocator. template class VmaStlAllocator @@ -3893,7 +4950,7 @@ class VmaStlAllocator public: const VkAllocationCallbacks* const m_pCallbacks; typedef T value_type; - + VmaStlAllocator(const VkAllocationCallbacks* pCallbacks) : m_pCallbacks(pCallbacks) { } template VmaStlAllocator(const VmaStlAllocator& src) : m_pCallbacks(src.m_pCallbacks) { } @@ -3956,7 +5013,12 @@ public: m_Capacity(count) { } - + + // This version of the constructor is here for compatibility with pre-C++14 std::vector. + // value is unused. + VmaVector(size_t count, const T& value, const AllocatorT& allocator) + : VmaVector(count, allocator) {} + VmaVector(const VmaVector& src) : m_Allocator(src.m_Allocator), m_pArray(src.m_Count ? (T*)VmaAllocateArray(src.m_Allocator.m_pCallbacks, src.m_Count) : VMA_NULL), @@ -3968,7 +5030,7 @@ public: memcpy(m_pArray, src.m_pArray, m_Count * sizeof(T)); } } - + ~VmaVector() { VmaFree(m_Allocator.m_pCallbacks, m_pArray); @@ -3986,12 +5048,12 @@ public: } return *this; } - + bool empty() const { return m_Count == 0; } size_t size() const { return m_Count; } T* data() { return m_pArray; } const T* data() const { return m_pArray; } - + T& operator[](size_t index) { VMA_HEAVY_ASSERT(index < m_Count); @@ -4027,12 +5089,12 @@ public: void reserve(size_t newCapacity, bool freeMemory = false) { newCapacity = VMA_MAX(newCapacity, m_Count); - + if((newCapacity < m_Capacity) && !freeMemory) { newCapacity = m_Capacity; } - + if(newCapacity != m_Capacity) { T* const newArray = newCapacity ? VmaAllocateArray(m_Allocator, newCapacity) : VMA_NULL; @@ -4182,19 +5244,174 @@ bool VmaVectorRemoveSorted(VectorT& vector, const typename VectorT::value_type& return false; } -template -IterT VmaVectorFindSorted(const IterT& beg, const IterT& end, const KeyT& value) +//////////////////////////////////////////////////////////////////////////////// +// class VmaSmallVector + +/* +This is a vector (a variable-sized array), optimized for the case when the array is small. + +It contains some number of elements in-place, which allows it to avoid heap allocation +when the actual number of elements is below that threshold. This allows normal "small" +cases to be fast without losing generality for large inputs. +*/ + +template +class VmaSmallVector { - CmpLess comparator; - IterT it = VmaBinaryFindFirstNotLess( - beg, end, value, comparator); - if(it == end || - (!comparator(*it, value) && !comparator(value, *it))) +public: + typedef T value_type; + + VmaSmallVector(const AllocatorT& allocator) : + m_Count(0), + m_DynamicArray(allocator) { - return it; } - return end; -} + VmaSmallVector(size_t count, const AllocatorT& allocator) : + m_Count(count), + m_DynamicArray(count > N ? count : 0, allocator) + { + } + template + VmaSmallVector(const VmaSmallVector& src) = delete; + template + VmaSmallVector& operator=(const VmaSmallVector& rhs) = delete; + + bool empty() const { return m_Count == 0; } + size_t size() const { return m_Count; } + T* data() { return m_Count > N ? m_DynamicArray.data() : m_StaticArray; } + const T* data() const { return m_Count > N ? m_DynamicArray.data() : m_StaticArray; } + + T& operator[](size_t index) + { + VMA_HEAVY_ASSERT(index < m_Count); + return data()[index]; + } + const T& operator[](size_t index) const + { + VMA_HEAVY_ASSERT(index < m_Count); + return data()[index]; + } + + T& front() + { + VMA_HEAVY_ASSERT(m_Count > 0); + return data()[0]; + } + const T& front() const + { + VMA_HEAVY_ASSERT(m_Count > 0); + return data()[0]; + } + T& back() + { + VMA_HEAVY_ASSERT(m_Count > 0); + return data()[m_Count - 1]; + } + const T& back() const + { + VMA_HEAVY_ASSERT(m_Count > 0); + return data()[m_Count - 1]; + } + + void resize(size_t newCount, bool freeMemory = false) + { + if(newCount > N && m_Count > N) + { + // Any direction, staying in m_DynamicArray + m_DynamicArray.resize(newCount, freeMemory); + } + else if(newCount > N && m_Count <= N) + { + // Growing, moving from m_StaticArray to m_DynamicArray + m_DynamicArray.resize(newCount, freeMemory); + if(m_Count > 0) + { + memcpy(m_DynamicArray.data(), m_StaticArray, m_Count * sizeof(T)); + } + } + else if(newCount <= N && m_Count > N) + { + // Shrinking, moving from m_DynamicArray to m_StaticArray + if(newCount > 0) + { + memcpy(m_StaticArray, m_DynamicArray.data(), newCount * sizeof(T)); + } + m_DynamicArray.resize(0, freeMemory); + } + else + { + // Any direction, staying in m_StaticArray - nothing to do here + } + m_Count = newCount; + } + + void clear(bool freeMemory = false) + { + m_DynamicArray.clear(freeMemory); + m_Count = 0; + } + + void insert(size_t index, const T& src) + { + VMA_HEAVY_ASSERT(index <= m_Count); + const size_t oldCount = size(); + resize(oldCount + 1); + T* const dataPtr = data(); + if(index < oldCount) + { + // I know, this could be more optimal for case where memmove can be memcpy directly from m_StaticArray to m_DynamicArray. + memmove(dataPtr + (index + 1), dataPtr + index, (oldCount - index) * sizeof(T)); + } + dataPtr[index] = src; + } + + void remove(size_t index) + { + VMA_HEAVY_ASSERT(index < m_Count); + const size_t oldCount = size(); + if(index < oldCount - 1) + { + // I know, this could be more optimal for case where memmove can be memcpy directly from m_DynamicArray to m_StaticArray. + T* const dataPtr = data(); + memmove(dataPtr + index, dataPtr + (index + 1), (oldCount - index - 1) * sizeof(T)); + } + resize(oldCount - 1); + } + + void push_back(const T& src) + { + const size_t newIndex = size(); + resize(newIndex + 1); + data()[newIndex] = src; + } + + void pop_back() + { + VMA_HEAVY_ASSERT(m_Count > 0); + resize(size() - 1); + } + + void push_front(const T& src) + { + insert(0, src); + } + + void pop_front() + { + VMA_HEAVY_ASSERT(m_Count > 0); + remove(0); + } + + typedef T* iterator; + + iterator begin() { return data(); } + iterator end() { return data() + m_Count; } + +private: + size_t m_Count; + T m_StaticArray[N]; // Used when m_Size <= N + VmaVector m_DynamicArray; // Used when m_Size > N +}; //////////////////////////////////////////////////////////////////////////////// // class VmaPoolAllocator @@ -4211,15 +5428,14 @@ class VmaPoolAllocator public: VmaPoolAllocator(const VkAllocationCallbacks* pAllocationCallbacks, uint32_t firstBlockCapacity); ~VmaPoolAllocator(); - void Clear(); - T* Alloc(); + template T* Alloc(Types... args); void Free(T* ptr); private: union Item { uint32_t NextFreeIndex; - T Value; + alignas(T) char Value[sizeof(T)]; }; struct ItemBlock @@ -4228,7 +5444,7 @@ private: uint32_t Capacity; uint32_t FirstFreeIndex; }; - + const VkAllocationCallbacks* m_pAllocationCallbacks; const uint32_t m_FirstBlockCapacity; VmaVector< ItemBlock, VmaStlAllocator > m_ItemBlocks; @@ -4247,12 +5463,6 @@ VmaPoolAllocator::VmaPoolAllocator(const VkAllocationCallbacks* pAllocationCa template VmaPoolAllocator::~VmaPoolAllocator() -{ - Clear(); -} - -template -void VmaPoolAllocator::Clear() { for(size_t i = m_ItemBlocks.size(); i--; ) vma_delete_array(m_pAllocationCallbacks, m_ItemBlocks[i].pItems, m_ItemBlocks[i].Capacity); @@ -4260,7 +5470,7 @@ void VmaPoolAllocator::Clear() } template -T* VmaPoolAllocator::Alloc() +template T* VmaPoolAllocator::Alloc(Types... args) { for(size_t i = m_ItemBlocks.size(); i--; ) { @@ -4270,7 +5480,9 @@ T* VmaPoolAllocator::Alloc() { Item* const pItem = &block.pItems[block.FirstFreeIndex]; block.FirstFreeIndex = pItem->NextFreeIndex; - return &pItem->Value; + T* result = (T*)&pItem->Value; + new(result)T(std::forward(args)...); // Explicit constructor call. + return result; } } @@ -4278,7 +5490,9 @@ T* VmaPoolAllocator::Alloc() ItemBlock& newBlock = CreateNewBlock(); Item* const pItem = &newBlock.pItems[0]; newBlock.FirstFreeIndex = pItem->NextFreeIndex; - return &pItem->Value; + T* result = (T*)&pItem->Value; + new(result)T(std::forward(args)...); // Explicit constructor call. + return result; } template @@ -4288,14 +5502,15 @@ void VmaPoolAllocator::Free(T* ptr) for(size_t i = m_ItemBlocks.size(); i--; ) { ItemBlock& block = m_ItemBlocks[i]; - + // Casting to union. Item* pItemPtr; memcpy(&pItemPtr, &ptr, sizeof(pItemPtr)); - + // Check if pItemPtr is in address range of this block. if((pItemPtr >= block.pItems) && (pItemPtr < block.pItems + block.Capacity)) { + ptr->~T(); // Explicit destructor call. const uint32_t index = static_cast(pItemPtr - block.pItems); pItemPtr->NextFreeIndex = block.FirstFreeIndex; block.FirstFreeIndex = index; @@ -4368,7 +5583,7 @@ public: ItemType* PushFront(const T& value); void PopBack(); void PopFront(); - + // Item can be null - it means PushBack. ItemType* InsertBefore(ItemType* pItem); // Item can be null - it means PushFront. @@ -4678,7 +5893,7 @@ public: VMA_HEAVY_ASSERT(m_pList == rhs.m_pList); return m_pItem != rhs.m_pItem; } - + private: VmaRawList* m_pList; VmaListItem* m_pItem; @@ -4706,7 +5921,7 @@ public: m_pItem(src.m_pItem) { } - + const T& operator*() const { VMA_HEAVY_ASSERT(m_pItem != VMA_NULL); @@ -4761,7 +5976,7 @@ public: VMA_HEAVY_ASSERT(m_pList == rhs.m_pList); return m_pItem != rhs.m_pItem; } - + private: const_iterator(const VmaRawList* pList, const VmaListItem* pItem) : m_pList(pList), @@ -4840,7 +6055,7 @@ public: void insert(const PairType& pair); iterator find(const KeyT& key); void erase(iterator it); - + private: VmaVector< PairType, VmaStlAllocator > m_Vector; }; @@ -4924,28 +6139,27 @@ public: }; /* - This struct cannot have constructor or destructor. It must be POD because it is - allocated using VmaPoolAllocator. + This struct is allocated using VmaPoolAllocator. */ - void Ctor(uint32_t currentFrameIndex, bool userDataString) + VmaAllocation_T(uint32_t currentFrameIndex, bool userDataString) : + m_Alignment{1}, + m_Size{0}, + m_pUserData{VMA_NULL}, + m_LastUseFrameIndex{currentFrameIndex}, + m_MemoryTypeIndex{0}, + m_Type{(uint8_t)ALLOCATION_TYPE_NONE}, + m_SuballocationType{(uint8_t)VMA_SUBALLOCATION_TYPE_UNKNOWN}, + m_MapCount{0}, + m_Flags{userDataString ? (uint8_t)FLAG_USER_DATA_STRING : (uint8_t)0} { - m_Alignment = 1; - m_Size = 0; - m_pUserData = VMA_NULL; - m_LastUseFrameIndex = currentFrameIndex; - m_Type = (uint8_t)ALLOCATION_TYPE_NONE; - m_SuballocationType = (uint8_t)VMA_SUBALLOCATION_TYPE_UNKNOWN; - m_MapCount = 0; - m_Flags = userDataString ? (uint8_t)FLAG_USER_DATA_STRING : 0; - #if VMA_STATS_STRING_ENABLED m_CreationFrameIndex = currentFrameIndex; m_BufferImageUsage = 0; #endif } - void Dtor() + ~VmaAllocation_T() { VMA_ASSERT((m_MapCount & ~MAP_COUNT_FLAG_PERSISTENT_MAP) == 0 && "Allocation was not unmapped before destruction."); @@ -4958,6 +6172,7 @@ public: VkDeviceSize offset, VkDeviceSize alignment, VkDeviceSize size, + uint32_t memoryTypeIndex, VmaSuballocationType suballocationType, bool mapped, bool canBecomeLost) @@ -4967,6 +6182,7 @@ public: m_Type = (uint8_t)ALLOCATION_TYPE_BLOCK; m_Alignment = alignment; m_Size = size; + m_MemoryTypeIndex = memoryTypeIndex; m_MapCount = mapped ? MAP_COUNT_FLAG_PERSISTENT_MAP : 0; m_SuballocationType = (uint8_t)suballocationType; m_BlockAllocation.m_Block = block; @@ -4979,6 +6195,7 @@ public: VMA_ASSERT(m_Type == ALLOCATION_TYPE_NONE); VMA_ASSERT(m_LastUseFrameIndex.load() == VMA_FRAME_INDEX_LOST); m_Type = (uint8_t)ALLOCATION_TYPE_BLOCK; + m_MemoryTypeIndex = 0; m_BlockAllocation.m_Block = VMA_NULL; m_BlockAllocation.m_Offset = 0; m_BlockAllocation.m_CanBecomeLost = true; @@ -4987,9 +6204,8 @@ public: void ChangeBlockAllocation( VmaAllocator hAllocator, VmaDeviceMemoryBlock* block, - VkDeviceSize offset); + VkDeviceSize offset); - void ChangeSize(VkDeviceSize newSize); void ChangeOffset(VkDeviceSize newOffset); // pMappedData not null means allocation is created with MAPPED flag. @@ -5005,9 +6221,9 @@ public: m_Type = (uint8_t)ALLOCATION_TYPE_DEDICATED; m_Alignment = 0; m_Size = size; + m_MemoryTypeIndex = memoryTypeIndex; m_SuballocationType = (uint8_t)suballocationType; m_MapCount = (pMappedData != VMA_NULL) ? MAP_COUNT_FLAG_PERSISTENT_MAP : 0; - m_DedicatedAllocation.m_MemoryTypeIndex = memoryTypeIndex; m_DedicatedAllocation.m_hMemory = hMemory; m_DedicatedAllocation.m_pMappedData = pMappedData; } @@ -5027,11 +6243,11 @@ public: } VkDeviceSize GetOffset() const; VkDeviceMemory GetMemory() const; - uint32_t GetMemoryTypeIndex() const; + uint32_t GetMemoryTypeIndex() const { return m_MemoryTypeIndex; } bool IsPersistentMap() const { return (m_MapCount & MAP_COUNT_FLAG_PERSISTENT_MAP) != 0; } void* GetMappedData() const; bool CanBecomeLost() const; - + uint32_t GetLastUseFrameIndex() const { return m_LastUseFrameIndex.load(); @@ -5044,7 +6260,7 @@ public: - If hAllocation.LastUseFrameIndex + frameInUseCount < allocator.CurrentFrameIndex, makes it lost by setting LastUseFrameIndex = VMA_FRAME_INDEX_LOST and returns true. - Else, returns false. - + If hAllocation is already lost, assert - you should not call it then. If hAllocation was not created with CAN_BECOME_LOST_BIT, assert. */ @@ -5086,6 +6302,7 @@ private: VkDeviceSize m_Size; void* m_pUserData; VMA_ATOMIC_UINT32 m_LastUseFrameIndex; + uint32_t m_MemoryTypeIndex; uint8_t m_Type; // ALLOCATION_TYPE uint8_t m_SuballocationType; // VmaSuballocationType // Bit 0x80 is set when allocation was created with VMA_ALLOCATION_CREATE_MAPPED_BIT. @@ -5104,7 +6321,6 @@ private: // Allocation for an object that has its own private VkDeviceMemory. struct DedicatedAllocation { - uint32_t m_MemoryTypeIndex; VkDeviceMemory m_hMemory; void* m_pMappedData; // Not null means memory is mapped. }; @@ -5260,9 +6476,6 @@ public: virtual void Free(const VmaAllocation allocation) = 0; virtual void FreeAtOffset(VkDeviceSize offset) = 0; - // Tries to resize (grow or shrink) space for given allocation, in place. - virtual bool ResizeAllocation(const VmaAllocation alloc, VkDeviceSize newSize) { return false; } - protected: const VkAllocationCallbacks* GetAllocationCallbacks() const { return m_pAllocationCallbacks; } @@ -5341,11 +6554,9 @@ public: virtual void Free(const VmaAllocation allocation); virtual void FreeAtOffset(VkDeviceSize offset); - virtual bool ResizeAllocation(const VmaAllocation alloc, VkDeviceSize newSize); - //////////////////////////////////////////////////////////////////////////////// // For defragmentation - + bool IsBufferImageGranularityConflictPossible( VkDeviceSize bufferImageGranularity, VmaSuballocationType& inOutPrevSuballocType) const; @@ -5556,7 +6767,7 @@ private: SuballocationVectorType& AccessSuballocations2nd() { return m_1stVectorIndex ? m_Suballocations0 : m_Suballocations1; } const SuballocationVectorType& AccessSuballocations1st() const { return m_1stVectorIndex ? m_Suballocations1 : m_Suballocations0; } const SuballocationVectorType& AccessSuballocations2nd() const { return m_1stVectorIndex ? m_Suballocations0 : m_Suballocations1; } - + // Number of items in 1st vector with hAllocation = null at the beginning. size_t m_1stNullItemsBeginCount; // Number of other items in 1st vector with hAllocation = null somewhere in the middle. @@ -5767,7 +6978,7 @@ public: uint32_t algorithm); // Always call before destruction. void Destroy(VmaAllocator allocator); - + VmaPool GetParentPool() const { return m_hParentPool; } VkDeviceMemory GetDeviceMemory() const { return m_hMemory; } uint32_t GetMemoryTypeIndex() const { return m_MemoryTypeIndex; } @@ -5789,11 +7000,15 @@ public: VkResult BindBufferMemory( const VmaAllocator hAllocator, const VmaAllocation hAllocation, - VkBuffer hBuffer); + VkDeviceSize allocationLocalOffset, + VkBuffer hBuffer, + const void* pNext); VkResult BindImageMemory( const VmaAllocator hAllocator, const VmaAllocation hAllocation, - VkImage hImage); + VkDeviceSize allocationLocalOffset, + VkImage hImage, + const void* pNext); private: VmaPool m_hParentPool; // VK_NULL_HANDLE if not belongs to custom pool. @@ -5826,6 +7041,9 @@ struct VmaDefragmentationMove VkDeviceSize srcOffset; VkDeviceSize dstOffset; VkDeviceSize size; + VmaAllocation hAllocation; + VmaDeviceMemoryBlock* pSrcBlock; + VmaDeviceMemoryBlock* pDstBlock; }; class VmaDefragmentationAlgorithm; @@ -5849,14 +7067,16 @@ public: size_t maxBlockCount, VkDeviceSize bufferImageGranularity, uint32_t frameInUseCount, - bool isCustomPool, bool explicitBlockSize, - uint32_t algorithm); + uint32_t algorithm, + float priority); ~VmaBlockVector(); VkResult CreateMinBlocks(); + VmaAllocator GetAllocator() const { return m_hAllocator; } VmaPool GetParentPool() const { return m_hParentPool; } + bool IsCustomPool() const { return m_hParentPool != VMA_NULL; } uint32_t GetMemoryTypeIndex() const { return m_MemoryTypeIndex; } VkDeviceSize GetPreferredBlockSize() const { return m_PreferredBlockSize; } VkDeviceSize GetBufferImageGranularity() const { return m_BufferImageGranularity; } @@ -5865,7 +7085,7 @@ public: void GetPoolStats(VmaPoolStats* pStats); - bool IsEmpty() const { return m_Blocks.empty(); } + bool IsEmpty(); bool IsCorruptionDetectionEnabled() const; VkResult Allocate( @@ -5877,8 +7097,7 @@ public: size_t allocationCount, VmaAllocation* pAllocations); - void Free( - VmaAllocation hAllocation); + void Free(const VmaAllocation hAllocation); // Adds statistics of this BlockVector to pStats. void AddStats(VmaStats* pStats); @@ -5895,12 +7114,21 @@ public: // Saves results in pCtx->res. void Defragment( class VmaBlockVectorDefragmentationContext* pCtx, - VmaDefragmentationStats* pStats, + VmaDefragmentationStats* pStats, VmaDefragmentationFlags flags, VkDeviceSize& maxCpuBytesToMove, uint32_t& maxCpuAllocationsToMove, VkDeviceSize& maxGpuBytesToMove, uint32_t& maxGpuAllocationsToMove, VkCommandBuffer commandBuffer); void DefragmentationEnd( class VmaBlockVectorDefragmentationContext* pCtx, + uint32_t flags, + VmaDefragmentationStats* pStats); + + uint32_t ProcessDefragmentations( + class VmaBlockVectorDefragmentationContext *pCtx, + VmaDefragmentationPassMoveInfo* pMove, uint32_t maxMoves); + + void CommitDefragmentations( + class VmaBlockVectorDefragmentationContext *pCtx, VmaDefragmentationStats* pStats); //////////////////////////////////////////////////////////////////////////////// @@ -5922,14 +7150,14 @@ private: const size_t m_MaxBlockCount; const VkDeviceSize m_BufferImageGranularity; const uint32_t m_FrameInUseCount; - const bool m_IsCustomPool; const bool m_ExplicitBlockSize; const uint32_t m_Algorithm; - /* There can be at most one allocation that is completely empty - a - hysteresis to avoid pessimistic case of alternating creation and destruction - of a VkDeviceMemory. */ - bool m_HasEmptyBlock; + const float m_Priority; VMA_RW_MUTEX m_Mutex; + + /* There can be at most one allocation that is completely empty (except when minBlockCount > 0) - + a hysteresis to avoid pessimistic case of alternating creation and destruction of a VkDeviceMemory. */ + bool m_HasEmptyBlock; // Incrementally sorted by sumFreeSize, ascending. VmaVector< VmaDeviceMemoryBlock*, VmaStlAllocator > m_Blocks; uint32_t m_NextBlockId; @@ -5972,7 +7200,7 @@ private: // Saves result to pCtx->res. void ApplyDefragmentationMovesGpu( class VmaBlockVectorDefragmentationContext* pDefragCtx, - const VmaVector< VmaDefragmentationMove, VmaStlAllocator >& moves, + VmaVector< VmaDefragmentationMove, VmaStlAllocator >& moves, VkCommandBuffer commandBuffer); /* @@ -5980,6 +7208,8 @@ private: - updated with new data. */ void FreeEmptyBlocks(VmaDefragmentationStats* pDefragmentationStats); + + void UpdateHasEmptyBlock(); }; struct VmaPool_T @@ -5997,12 +7227,16 @@ public: uint32_t GetId() const { return m_Id; } void SetId(uint32_t id) { VMA_ASSERT(m_Id == 0); m_Id = id; } + const char* GetName() const { return m_Name; } + void SetName(const char* pName); + #if VMA_STATS_STRING_ENABLED //void PrintDetailedMap(class VmaStringBuilder& sb); #endif private: uint32_t m_Id; + char* m_Name; }; /* @@ -6035,7 +7269,8 @@ public: virtual VkResult Defragment( VmaVector< VmaDefragmentationMove, VmaStlAllocator >& moves, VkDeviceSize maxBytesToMove, - uint32_t maxAllocationsToMove) = 0; + uint32_t maxAllocationsToMove, + VmaDefragmentationFlags flags) = 0; virtual VkDeviceSize GetBytesMoved() const = 0; virtual uint32_t GetAllocationsMoved() const = 0; @@ -6080,7 +7315,8 @@ public: virtual VkResult Defragment( VmaVector< VmaDefragmentationMove, VmaStlAllocator >& moves, VkDeviceSize maxBytesToMove, - uint32_t maxAllocationsToMove); + uint32_t maxAllocationsToMove, + VmaDefragmentationFlags flags); virtual VkDeviceSize GetBytesMoved() const { return m_BytesMoved; } virtual uint32_t GetAllocationsMoved() const { return m_AllocationsMoved; } @@ -6181,7 +7417,8 @@ private: VkResult DefragmentRound( VmaVector< VmaDefragmentationMove, VmaStlAllocator >& moves, VkDeviceSize maxBytesToMove, - uint32_t maxAllocationsToMove); + uint32_t maxAllocationsToMove, + bool freeOldAllocations); size_t CalcBlocksWithNonMovableCount() const; @@ -6207,7 +7444,8 @@ public: virtual VkResult Defragment( VmaVector< VmaDefragmentationMove, VmaStlAllocator >& moves, VkDeviceSize maxBytesToMove, - uint32_t maxAllocationsToMove); + uint32_t maxAllocationsToMove, + VmaDefragmentationFlags flags); virtual VkDeviceSize GetBytesMoved() const { return m_BytesMoved; } virtual uint32_t GetAllocationsMoved() const { return m_AllocationsMoved; } @@ -6287,7 +7525,7 @@ private: } } } - + if(bestIndex != SIZE_MAX) { outBlockInfoIndex = m_FreeSpaces[bestIndex].blockInfoIndex; @@ -6346,12 +7584,6 @@ struct VmaBlockDefragmentationContext }; uint32_t flags; VkBuffer hBuffer; - - VmaBlockDefragmentationContext() : - flags(0), - hBuffer(VK_NULL_HANDLE) - { - } }; class VmaBlockVectorDefragmentationContext @@ -6361,13 +7593,16 @@ public: VkResult res; bool mutexLocked; VmaVector< VmaBlockDefragmentationContext, VmaStlAllocator > blockContexts; + VmaVector< VmaDefragmentationMove, VmaStlAllocator > defragmentationMoves; + uint32_t defragmentationMovesProcessed; + uint32_t defragmentationMovesCommitted; + bool hasDefragmentationPlan; VmaBlockVectorDefragmentationContext( VmaAllocator hAllocator, VmaPool hCustomPool, // Optional. VmaBlockVector* pBlockVector, - uint32_t currFrameIndex, - uint32_t flags); + uint32_t currFrameIndex); ~VmaBlockVectorDefragmentationContext(); VmaPool GetCustomPool() const { return m_hCustomPool; } @@ -6377,7 +7612,7 @@ public: void AddAllocation(VmaAllocation hAlloc, VkBool32* pChanged); void AddAll() { m_AllAllocations = true; } - void Begin(bool overlappingMoveSupported); + void Begin(bool overlappingMoveSupported, VmaDefragmentationFlags flags); private: const VmaAllocator m_hAllocator; @@ -6386,7 +7621,6 @@ private: // Redundant, for convenience not to fetch from m_hCustomPool->m_BlockVector or m_hAllocator->m_pBlockVectors. VmaBlockVector* const m_pBlockVector; const uint32_t m_CurrFrameIndex; - const uint32_t m_AlgorithmFlags; // Owner of this object. VmaDefragmentationAlgorithm* m_pAlgorithm; @@ -6412,10 +7646,10 @@ public: VmaDefragmentationStats* pStats); ~VmaDefragmentationContext_T(); - void AddPools(uint32_t poolCount, VmaPool* pPools); + void AddPools(uint32_t poolCount, const VmaPool* pPools); void AddAllocations( uint32_t allocationCount, - VmaAllocation* pAllocations, + const VmaAllocation* pAllocations, VkBool32* pAllocationsChanged); /* @@ -6427,13 +7661,22 @@ public: VkResult Defragment( VkDeviceSize maxCpuBytesToMove, uint32_t maxCpuAllocationsToMove, VkDeviceSize maxGpuBytesToMove, uint32_t maxGpuAllocationsToMove, - VkCommandBuffer commandBuffer, VmaDefragmentationStats* pStats); + VkCommandBuffer commandBuffer, VmaDefragmentationStats* pStats, VmaDefragmentationFlags flags); + + VkResult DefragmentPassBegin(VmaDefragmentationPassInfo* pInfo); + VkResult DefragmentPassEnd(); private: const VmaAllocator m_hAllocator; const uint32_t m_CurrFrameIndex; const uint32_t m_Flags; VmaDefragmentationStats* const m_pStats; + + VkDeviceSize m_MaxCpuBytesToMove; + uint32_t m_MaxCpuAllocationsToMove; + VkDeviceSize m_MaxGpuBytesToMove; + uint32_t m_MaxGpuAllocationsToMove; + // Owner of these objects. VmaBlockVectorDefragmentationContext* m_DefaultPoolContexts[VK_MAX_MEMORY_TYPES]; // Owner of these objects. @@ -6450,7 +7693,11 @@ public: void WriteConfiguration( const VkPhysicalDeviceProperties& devProps, const VkPhysicalDeviceMemoryProperties& memProps, - bool dedicatedAllocationExtensionEnabled); + uint32_t vulkanApiVersion, + bool dedicatedAllocationExtensionEnabled, + bool bindMemory2ExtensionEnabled, + bool memoryBudgetExtensionEnabled, + bool deviceCoherentMemoryExtensionEnabled); ~VmaRecorder(); void RecordCreateAllocator(uint32_t frameIndex); @@ -6485,10 +7732,6 @@ public: void RecordFreeMemoryPages(uint32_t frameIndex, uint64_t allocationCount, const VmaAllocation* pAllocations); - void RecordResizeAllocation( - uint32_t frameIndex, - VmaAllocation allocation, - VkDeviceSize newSize); void RecordSetAllocationUserData(uint32_t frameIndex, VmaAllocation allocation, const void* pUserData); @@ -6525,6 +7768,9 @@ public: VmaDefragmentationContext ctx); void RecordDefragmentationEnd(uint32_t frameIndex, VmaDefragmentationContext ctx); + void RecordSetPoolName(uint32_t frameIndex, + VmaPool pool, + const char* name); private: struct CallParams @@ -6548,8 +7794,7 @@ private: VmaRecordFlags m_Flags; FILE* m_File; VMA_MUTEX m_FileMutex; - int64_t m_Freq; - int64_t m_StartCounter; + std::chrono::time_point m_RecordingStartTime; void GetBasicParams(CallParams& outParams); @@ -6582,7 +7827,7 @@ class VmaAllocationObjectAllocator public: VmaAllocationObjectAllocator(const VkAllocationCallbacks* pAllocationCallbacks); - VmaAllocation Allocate(); + template VmaAllocation Allocate(Types... args); void Free(VmaAllocation hAlloc); private: @@ -6590,22 +7835,77 @@ private: VmaPoolAllocator m_Allocator; }; +struct VmaCurrentBudgetData +{ + VMA_ATOMIC_UINT64 m_BlockBytes[VK_MAX_MEMORY_HEAPS]; + VMA_ATOMIC_UINT64 m_AllocationBytes[VK_MAX_MEMORY_HEAPS]; + +#if VMA_MEMORY_BUDGET + VMA_ATOMIC_UINT32 m_OperationsSinceBudgetFetch; + VMA_RW_MUTEX m_BudgetMutex; + uint64_t m_VulkanUsage[VK_MAX_MEMORY_HEAPS]; + uint64_t m_VulkanBudget[VK_MAX_MEMORY_HEAPS]; + uint64_t m_BlockBytesAtBudgetFetch[VK_MAX_MEMORY_HEAPS]; +#endif // #if VMA_MEMORY_BUDGET + + VmaCurrentBudgetData() + { + for(uint32_t heapIndex = 0; heapIndex < VK_MAX_MEMORY_HEAPS; ++heapIndex) + { + m_BlockBytes[heapIndex] = 0; + m_AllocationBytes[heapIndex] = 0; +#if VMA_MEMORY_BUDGET + m_VulkanUsage[heapIndex] = 0; + m_VulkanBudget[heapIndex] = 0; + m_BlockBytesAtBudgetFetch[heapIndex] = 0; +#endif + } + +#if VMA_MEMORY_BUDGET + m_OperationsSinceBudgetFetch = 0; +#endif + } + + void AddAllocation(uint32_t heapIndex, VkDeviceSize allocationSize) + { + m_AllocationBytes[heapIndex] += allocationSize; +#if VMA_MEMORY_BUDGET + ++m_OperationsSinceBudgetFetch; +#endif + } + + void RemoveAllocation(uint32_t heapIndex, VkDeviceSize allocationSize) + { + VMA_ASSERT(m_AllocationBytes[heapIndex] >= allocationSize); // DELME + m_AllocationBytes[heapIndex] -= allocationSize; +#if VMA_MEMORY_BUDGET + ++m_OperationsSinceBudgetFetch; +#endif + } +}; + // Main allocator object. struct VmaAllocator_T { VMA_CLASS_NO_COPY(VmaAllocator_T) public: bool m_UseMutex; - bool m_UseKhrDedicatedAllocation; + uint32_t m_VulkanApiVersion; + bool m_UseKhrDedicatedAllocation; // Can be set only if m_VulkanApiVersion < VK_MAKE_VERSION(1, 1, 0). + bool m_UseKhrBindMemory2; // Can be set only if m_VulkanApiVersion < VK_MAKE_VERSION(1, 1, 0). + bool m_UseExtMemoryBudget; + bool m_UseAmdDeviceCoherentMemory; + bool m_UseKhrBufferDeviceAddress; + bool m_UseExtMemoryPriority; VkDevice m_hDevice; + VkInstance m_hInstance; bool m_AllocationCallbacksSpecified; VkAllocationCallbacks m_AllocationCallbacks; VmaDeviceMemoryCallbacks m_DeviceMemoryCallbacks; VmaAllocationObjectAllocator m_AllocationObjectAllocator; - - // Number of bytes free out of limit, or VK_WHOLE_SIZE if no limit for that heap. - VkDeviceSize m_HeapSizeLimit[VK_MAX_MEMORY_HEAPS]; - VMA_MUTEX m_HeapSizeLimitMutex; + + // Each bit (1 << i) is set if HeapSizeLimit is enabled for that heap, so cannot allocate more than the heap size. + uint32_t m_HeapSizeLimitMask; VkPhysicalDeviceProperties m_PhysicalDeviceProperties; VkPhysicalDeviceMemoryProperties m_MemProps; @@ -6618,6 +7918,8 @@ public: AllocationVectorType* m_pDedicatedAllocations[VK_MAX_MEMORY_TYPES]; VMA_RW_MUTEX m_DedicatedAllocationsMutex[VK_MAX_MEMORY_TYPES]; + VmaCurrentBudgetData m_Budget; + VmaAllocator_T(const VmaAllocatorCreateInfo* pCreateInfo); VkResult Init(const VmaAllocatorCreateInfo* pCreateInfo); ~VmaAllocator_T(); @@ -6631,6 +7933,8 @@ public: return m_VulkanFunctions; } + VkPhysicalDevice GetPhysicalDevice() const { return m_PhysicalDevice; } + VkDeviceSize GetBufferImageGranularity() const { return VMA_MAX( @@ -6665,6 +7969,8 @@ public: return m_PhysicalDeviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU; } + uint32_t GetGlobalMemoryTypeBits() const { return m_GlobalMemoryTypeBits; } + #if VMA_RECORDING_ENABLED VmaRecorder* GetRecorder() const { return m_pRecorder; } #endif @@ -6686,6 +7992,7 @@ public: bool requiresDedicatedAllocation, bool prefersDedicatedAllocation, VkBuffer dedicatedBuffer, + VkBufferUsageFlags dedicatedBufferUsage, // UINT32_MAX when unknown. VkImage dedicatedImage, const VmaAllocationCreateInfo& createInfo, VmaSuballocationType suballocType, @@ -6703,6 +8010,9 @@ public: void CalculateStats(VmaStats* pStats); + void GetBudget( + VmaBudget* outBudget, uint32_t firstHeap, uint32_t heapCount); + #if VMA_STATS_STRING_ENABLED void PrintDetailedMap(class VmaJsonWriter& json); #endif @@ -6714,6 +8024,12 @@ public: VkResult DefragmentationEnd( VmaDefragmentationContext context); + VkResult DefragmentationPassBegin( + VmaDefragmentationPassInfo* pInfo, + VmaDefragmentationContext context); + VkResult DefragmentationPassEnd( + VmaDefragmentationContext context); + void GetAllocationInfo(VmaAllocation hAllocation, VmaAllocationInfo* pAllocationInfo); bool TouchAllocation(VmaAllocation hAllocation); @@ -6732,28 +8048,62 @@ public: void CreateLostAllocation(VmaAllocation* pAllocation); + // Call to Vulkan function vkAllocateMemory with accompanying bookkeeping. VkResult AllocateVulkanMemory(const VkMemoryAllocateInfo* pAllocateInfo, VkDeviceMemory* pMemory); + // Call to Vulkan function vkFreeMemory with accompanying bookkeeping. void FreeVulkanMemory(uint32_t memoryType, VkDeviceSize size, VkDeviceMemory hMemory); + // Call to Vulkan function vkBindBufferMemory or vkBindBufferMemory2KHR. + VkResult BindVulkanBuffer( + VkDeviceMemory memory, + VkDeviceSize memoryOffset, + VkBuffer buffer, + const void* pNext); + // Call to Vulkan function vkBindImageMemory or vkBindImageMemory2KHR. + VkResult BindVulkanImage( + VkDeviceMemory memory, + VkDeviceSize memoryOffset, + VkImage image, + const void* pNext); VkResult Map(VmaAllocation hAllocation, void** ppData); void Unmap(VmaAllocation hAllocation); - VkResult BindBufferMemory(VmaAllocation hAllocation, VkBuffer hBuffer); - VkResult BindImageMemory(VmaAllocation hAllocation, VkImage hImage); + VkResult BindBufferMemory( + VmaAllocation hAllocation, + VkDeviceSize allocationLocalOffset, + VkBuffer hBuffer, + const void* pNext); + VkResult BindImageMemory( + VmaAllocation hAllocation, + VkDeviceSize allocationLocalOffset, + VkImage hImage, + const void* pNext); - void FlushOrInvalidateAllocation( + VkResult FlushOrInvalidateAllocation( VmaAllocation hAllocation, VkDeviceSize offset, VkDeviceSize size, VMA_CACHE_OPERATION op); + VkResult FlushOrInvalidateAllocations( + uint32_t allocationCount, + const VmaAllocation* allocations, + const VkDeviceSize* offsets, const VkDeviceSize* sizes, + VMA_CACHE_OPERATION op); void FillAllocation(const VmaAllocation hAllocation, uint8_t pattern); + /* + Returns bit mask of memory types that can support defragmentation on GPU as + they support creation of required buffer for copy operations. + */ + uint32_t GetGpuDefragmentationMemoryTypeBits(); + private: VkDeviceSize m_PreferredLargeHeapBlockSize; VkPhysicalDevice m_PhysicalDevice; VMA_ATOMIC_UINT32 m_CurrentFrameIndex; - + VMA_ATOMIC_UINT32 m_GpuDefragmentationMemoryTypeBits; // UINT32_MAX means uninitialized. + VMA_RW_MUTEX m_PoolsMutex; // Protected by m_PoolsMutex. Sorted by pointer value. VmaVector > m_Pools; @@ -6761,12 +8111,27 @@ private: VmaVulkanFunctions m_VulkanFunctions; + // Global bit mask AND-ed with any memoryTypeBits to disallow certain memory types. + uint32_t m_GlobalMemoryTypeBits; + #if VMA_RECORDING_ENABLED VmaRecorder* m_pRecorder; #endif void ImportVulkanFunctions(const VmaVulkanFunctions* pVulkanFunctions); +#if VMA_STATIC_VULKAN_FUNCTIONS == 1 + void ImportVulkanFunctions_Static(); +#endif + + void ImportVulkanFunctions_Custom(const VmaVulkanFunctions* pVulkanFunctions); + +#if VMA_DYNAMIC_VULKAN_FUNCTIONS == 1 + void ImportVulkanFunctions_Dynamic(); +#endif + + void ValidateVulkanFunctions(); + VkDeviceSize CalcPreferredBlockSize(uint32_t memTypeIndex); VkResult AllocateMemoryOfType( @@ -6774,6 +8139,7 @@ private: VkDeviceSize alignment, bool dedicatedAllocation, VkBuffer dedicatedBuffer, + VkBufferUsageFlags dedicatedBufferUsage, VkImage dedicatedImage, const VmaAllocationCreateInfo& createInfo, uint32_t memTypeIndex, @@ -6797,16 +8163,35 @@ private: VkDeviceSize size, VmaSuballocationType suballocType, uint32_t memTypeIndex, + bool withinBudget, bool map, bool isUserDataString, void* pUserData, + float priority, VkBuffer dedicatedBuffer, + VkBufferUsageFlags dedicatedBufferUsage, VkImage dedicatedImage, size_t allocationCount, VmaAllocation* pAllocations); - // Tries to free pMemory as Dedicated Memory. Returns true if found and freed. - void FreeDedicatedMemory(VmaAllocation allocation); + void FreeDedicatedMemory(const VmaAllocation allocation); + + /* + Calculates and returns bit mask of memory types that can support defragmentation + on GPU as they support creation of required buffer for copy operations. + */ + uint32_t CalculateGpuDefragmentationMemoryTypeBits() const; + + uint32_t CalculateGlobalMemoryTypeBits() const; + + bool GetFlushOrInvalidateRange( + VmaAllocation allocation, + VkDeviceSize offset, VkDeviceSize size, + VkMappedMemoryRange& outRange) const; + +#if VMA_MEMORY_BUDGET + void UpdateVulkanBudget(); +#endif // #if VMA_MEMORY_BUDGET }; //////////////////////////////////////////////////////////////////////////////// @@ -6892,15 +8277,29 @@ void VmaStringBuilder::Add(const char* pStr) void VmaStringBuilder::AddNumber(uint32_t num) { char buf[11]; - VmaUint32ToStr(buf, sizeof(buf), num); - Add(buf); + buf[10] = '\0'; + char *p = &buf[10]; + do + { + *--p = '0' + (num % 10); + num /= 10; + } + while(num); + Add(p); } void VmaStringBuilder::AddNumber(uint64_t num) { char buf[21]; - VmaUint64ToStr(buf, sizeof(buf), num); - Add(buf); + buf[20] = '\0'; + char *p = &buf[20]; + do + { + *--p = '0' + (num % 10); + num /= 10; + } + while(num); + Add(p); } void VmaStringBuilder::AddPointer(const void* ptr) @@ -6926,10 +8325,10 @@ public: void BeginObject(bool singleLine = false); void EndObject(); - + void BeginArray(bool singleLine = false); void EndArray(); - + void WriteString(const char* pStr); void BeginString(const char* pStr = VMA_NULL); void ContinueString(const char* pStr); @@ -6937,7 +8336,7 @@ public: void ContinueString(uint64_t n); void ContinueString_Pointer(const void* ptr); void EndString(const char* pStr = VMA_NULL); - + void WriteNumber(uint32_t n); void WriteNumber(uint64_t n); void WriteBool(bool b); @@ -7185,7 +8584,7 @@ void VmaJsonWriter::WriteIndent(bool oneLess) if(!m_Stack.empty() && !m_Stack.back().singleLineMode) { m_SB.AddNewLine(); - + size_t count = m_Stack.size(); if(count > 0 && oneLess) { @@ -7212,11 +8611,7 @@ void VmaAllocation_T::SetUserData(VmaAllocator hAllocator, void* pUserData) if(pUserData != VMA_NULL) { - const char* const newStrSrc = (char*)pUserData; - const size_t newStrLen = strlen(newStrSrc); - char* const newStrDst = vma_new_array(hAllocator, char, newStrLen + 1); - memcpy(newStrDst, newStrSrc, newStrLen + 1); - m_pUserData = newStrDst; + m_pUserData = VmaCreateStringCopy(hAllocator->GetAllocationCallbacks(), (const char*)pUserData); } } else @@ -7247,12 +8642,6 @@ void VmaAllocation_T::ChangeBlockAllocation( m_BlockAllocation.m_Offset = offset; } -void VmaAllocation_T::ChangeSize(VkDeviceSize newSize) -{ - VMA_ASSERT(newSize > 0); - m_Size = newSize; -} - void VmaAllocation_T::ChangeOffset(VkDeviceSize newOffset) { VMA_ASSERT(m_Type == ALLOCATION_TYPE_BLOCK); @@ -7287,20 +8676,6 @@ VkDeviceMemory VmaAllocation_T::GetMemory() const } } -uint32_t VmaAllocation_T::GetMemoryTypeIndex() const -{ - switch(m_Type) - { - case ALLOCATION_TYPE_BLOCK: - return m_BlockAllocation.m_Block->GetMemoryTypeIndex(); - case ALLOCATION_TYPE_DEDICATED: - return m_DedicatedAllocation.m_MemoryTypeIndex; - default: - VMA_ASSERT(0); - return UINT32_MAX; - } -} - void* VmaAllocation_T::GetMappedData() const { switch(m_Type) @@ -7425,13 +8800,8 @@ void VmaAllocation_T::PrintParameters(class VmaJsonWriter& json) const void VmaAllocation_T::FreeUserDataString(VmaAllocator hAllocator) { VMA_ASSERT(IsUserDataString()); - if(m_pUserData != VMA_NULL) - { - char* const oldStr = (char*)m_pUserData; - const size_t oldStrLen = strlen(oldStr); - vma_delete_array(hAllocator, oldStr, oldStrLen + 1); - m_pUserData = VMA_NULL; - } + VmaFreeString(hAllocator->GetAllocationCallbacks(), (char*)m_pUserData); + m_pUserData = VMA_NULL; } void VmaAllocation_T::BlockAllocMap() @@ -7628,7 +8998,7 @@ void VmaBlockMetadata::PrintDetailedMap_Allocation(class VmaJsonWriter& json, VmaAllocation hAllocation) const { json.BeginObject(true); - + json.WriteString("Offset"); json.WriteNumber(offset); @@ -7642,7 +9012,7 @@ void VmaBlockMetadata::PrintDetailedMap_UnusedRange(class VmaJsonWriter& json, VkDeviceSize size) const { json.BeginObject(true); - + json.WriteString("Offset"); json.WriteNumber(offset); @@ -7702,7 +9072,7 @@ void VmaBlockMetadata_Generic::Init(VkDeviceSize size) bool VmaBlockMetadata_Generic::Validate() const { VMA_VALIDATE(!m_Suballocations.empty()); - + // Expected offset of new suballocation as calculated from previous ones. VkDeviceSize calculatedOffset = 0; // Expected number of free suballocations as calculated from traversing their list. @@ -7720,7 +9090,7 @@ bool VmaBlockMetadata_Generic::Validate() const ++suballocItem) { const VmaSuballocation& subAlloc = *suballocItem; - + // Actual offset of this suballocation doesn't match expected one. VMA_VALIDATE(subAlloc.offset == calculatedOffset); @@ -7763,7 +9133,7 @@ bool VmaBlockMetadata_Generic::Validate() const for(size_t i = 0; i < m_FreeSuballocationsBySize.size(); ++i) { VmaSuballocationList::iterator suballocItem = m_FreeSuballocationsBySize[i]; - + // Only free suballocations can be registered in m_FreeSuballocationsBySize. VMA_VALIDATE(suballocItem->type == VMA_SUBALLOCATION_TYPE_FREE); // They must be sorted by size ascending. @@ -7805,7 +9175,7 @@ void VmaBlockMetadata_Generic::CalcAllocationStatInfo(VmaStatInfo& outInfo) cons const uint32_t rangeCount = (uint32_t)m_Suballocations.size(); outInfo.allocationCount = rangeCount - m_FreeCount; outInfo.unusedRangeCount = m_FreeCount; - + outInfo.unusedBytes = m_SumFreeSize; outInfo.usedBytes = GetSize() - outInfo.unusedBytes; @@ -8064,7 +9434,7 @@ bool VmaBlockMetadata_Generic::MakeRequestedAllocationsLost( VMA_HEAVY_ASSERT(Validate()); VMA_ASSERT(pAllocationRequest->item != m_Suballocations.end()); VMA_ASSERT(pAllocationRequest->item->type == VMA_SUBALLOCATION_TYPE_FREE); - + return true; } @@ -8208,133 +9578,6 @@ void VmaBlockMetadata_Generic::FreeAtOffset(VkDeviceSize offset) VMA_ASSERT(0 && "Not found!"); } -bool VmaBlockMetadata_Generic::ResizeAllocation(const VmaAllocation alloc, VkDeviceSize newSize) -{ - typedef VmaSuballocationList::iterator iter_type; - for(iter_type suballocItem = m_Suballocations.begin(); - suballocItem != m_Suballocations.end(); - ++suballocItem) - { - VmaSuballocation& suballoc = *suballocItem; - if(suballoc.hAllocation == alloc) - { - iter_type nextItem = suballocItem; - ++nextItem; - - // Should have been ensured on higher level. - VMA_ASSERT(newSize != alloc->GetSize() && newSize > 0); - - // Shrinking. - if(newSize < alloc->GetSize()) - { - const VkDeviceSize sizeDiff = suballoc.size - newSize; - - // There is next item. - if(nextItem != m_Suballocations.end()) - { - // Next item is free. - if(nextItem->type == VMA_SUBALLOCATION_TYPE_FREE) - { - // Grow this next item backward. - UnregisterFreeSuballocation(nextItem); - nextItem->offset -= sizeDiff; - nextItem->size += sizeDiff; - RegisterFreeSuballocation(nextItem); - } - // Next item is not free. - else - { - // Create free item after current one. - VmaSuballocation newFreeSuballoc; - newFreeSuballoc.hAllocation = VK_NULL_HANDLE; - newFreeSuballoc.offset = suballoc.offset + newSize; - newFreeSuballoc.size = sizeDiff; - newFreeSuballoc.type = VMA_SUBALLOCATION_TYPE_FREE; - iter_type newFreeSuballocIt = m_Suballocations.insert(nextItem, newFreeSuballoc); - RegisterFreeSuballocation(newFreeSuballocIt); - - ++m_FreeCount; - } - } - // This is the last item. - else - { - // Create free item at the end. - VmaSuballocation newFreeSuballoc; - newFreeSuballoc.hAllocation = VK_NULL_HANDLE; - newFreeSuballoc.offset = suballoc.offset + newSize; - newFreeSuballoc.size = sizeDiff; - newFreeSuballoc.type = VMA_SUBALLOCATION_TYPE_FREE; - m_Suballocations.push_back(newFreeSuballoc); - - iter_type newFreeSuballocIt = m_Suballocations.end(); - RegisterFreeSuballocation(--newFreeSuballocIt); - - ++m_FreeCount; - } - - suballoc.size = newSize; - m_SumFreeSize += sizeDiff; - } - // Growing. - else - { - const VkDeviceSize sizeDiff = newSize - suballoc.size; - - // There is next item. - if(nextItem != m_Suballocations.end()) - { - // Next item is free. - if(nextItem->type == VMA_SUBALLOCATION_TYPE_FREE) - { - // There is not enough free space, including margin. - if(nextItem->size < sizeDiff + VMA_DEBUG_MARGIN) - { - return false; - } - - // There is more free space than required. - if(nextItem->size > sizeDiff) - { - // Move and shrink this next item. - UnregisterFreeSuballocation(nextItem); - nextItem->offset += sizeDiff; - nextItem->size -= sizeDiff; - RegisterFreeSuballocation(nextItem); - } - // There is exactly the amount of free space required. - else - { - // Remove this next free item. - UnregisterFreeSuballocation(nextItem); - m_Suballocations.erase(nextItem); - --m_FreeCount; - } - } - // Next item is not free - there is no space to grow. - else - { - return false; - } - } - // This is the last item - there is no space to grow. - else - { - return false; - } - - suballoc.size = newSize; - m_SumFreeSize -= sizeDiff; - } - - // We cannot call Validate() here because alloc object is updated to new size outside of this call. - return true; - } - } - VMA_ASSERT(0 && "Not found!"); - return false; -} - bool VmaBlockMetadata_Generic::ValidateFreeSuballocationList() const { VkDeviceSize lastSize = 0; @@ -8368,7 +9611,7 @@ bool VmaBlockMetadata_Generic::CheckAllocation( VMA_ASSERT(allocType != VMA_SUBALLOCATION_TYPE_FREE); VMA_ASSERT(suballocItem != m_Suballocations.cend()); VMA_ASSERT(pOffset != VMA_NULL); - + *itemsToMakeLostCount = 0; *pSumFreeSize = 0; *pSumItemSize = 0; @@ -8401,19 +9644,19 @@ bool VmaBlockMetadata_Generic::CheckAllocation( // Start from offset equal to beginning of this suballocation. *pOffset = suballocItem->offset; - + // Apply VMA_DEBUG_MARGIN at the beginning. if(VMA_DEBUG_MARGIN > 0) { *pOffset += VMA_DEBUG_MARGIN; } - + // Apply alignment. *pOffset = VmaAlignUp(*pOffset, allocAlignment); // Check previous suballocations for BufferImageGranularity conflicts. // Make bigger alignment if necessary. - if(bufferImageGranularity > 1) + if(bufferImageGranularity > 1 && bufferImageGranularity != allocAlignment) { bool bufferImageGranularityConflict = false; VmaSuballocationList::const_iterator prevSuballocItem = suballocItem; @@ -8438,14 +9681,14 @@ bool VmaBlockMetadata_Generic::CheckAllocation( *pOffset = VmaAlignUp(*pOffset, bufferImageGranularity); } } - + // Now that we have final *pOffset, check if we are past suballocItem. // If yes, return false - this function should be called for another suballocItem as starting point. if(*pOffset >= suballocItem->offset + suballocItem->size) { return false; } - + // Calculate padding at the beginning based on current offset. const VkDeviceSize paddingBegin = *pOffset - suballocItem->offset; @@ -8497,7 +9740,7 @@ bool VmaBlockMetadata_Generic::CheckAllocation( // Check next suballocations for BufferImageGranularity conflicts. // If conflict exists, we must mark more allocations lost or fail. - if(bufferImageGranularity > 1) + if(allocSize % bufferImageGranularity || *pOffset % bufferImageGranularity) { VmaSuballocationList::const_iterator nextSuballocItem = lastSuballocItem; ++nextSuballocItem; @@ -8544,19 +9787,19 @@ bool VmaBlockMetadata_Generic::CheckAllocation( // Start from offset equal to beginning of this suballocation. *pOffset = suballoc.offset; - + // Apply VMA_DEBUG_MARGIN at the beginning. if(VMA_DEBUG_MARGIN > 0) { *pOffset += VMA_DEBUG_MARGIN; } - + // Apply alignment. *pOffset = VmaAlignUp(*pOffset, allocAlignment); - + // Check previous suballocations for BufferImageGranularity conflicts. // Make bigger alignment if necessary. - if(bufferImageGranularity > 1) + if(bufferImageGranularity > 1 && bufferImageGranularity != allocAlignment) { bool bufferImageGranularityConflict = false; VmaSuballocationList::const_iterator prevSuballocItem = suballocItem; @@ -8581,7 +9824,7 @@ bool VmaBlockMetadata_Generic::CheckAllocation( *pOffset = VmaAlignUp(*pOffset, bufferImageGranularity); } } - + // Calculate padding at the beginning based on current offset. const VkDeviceSize paddingBegin = *pOffset - suballoc.offset; @@ -8596,7 +9839,7 @@ bool VmaBlockMetadata_Generic::CheckAllocation( // Check next suballocations for BufferImageGranularity conflicts. // If conflict exists, allocation cannot be made here. - if(bufferImageGranularity > 1) + if(allocSize % bufferImageGranularity || *pOffset % bufferImageGranularity) { VmaSuballocationList::const_iterator nextSuballocItem = suballocItem; ++nextSuballocItem; @@ -8628,7 +9871,7 @@ void VmaBlockMetadata_Generic::MergeFreeWithNext(VmaSuballocationList::iterator { VMA_ASSERT(item != m_Suballocations.end()); VMA_ASSERT(item->type == VMA_SUBALLOCATION_TYPE_FREE); - + VmaSuballocationList::iterator nextItem = item; ++nextItem; VMA_ASSERT(nextItem != m_Suballocations.end()); @@ -8645,7 +9888,7 @@ VmaSuballocationList::iterator VmaBlockMetadata_Generic::FreeSuballocation(VmaSu VmaSuballocation& suballoc = *suballocItem; suballoc.type = VMA_SUBALLOCATION_TYPE_FREE; suballoc.hAllocation = VK_NULL_HANDLE; - + // Update totals. ++m_FreeCount; m_SumFreeSize += suballoc.size; @@ -8653,7 +9896,7 @@ VmaSuballocationList::iterator VmaBlockMetadata_Generic::FreeSuballocation(VmaSu // Merge with previous and/or next suballocation if it's also free. bool mergeWithNext = false; bool mergeWithPrev = false; - + VmaSuballocationList::iterator nextItem = suballocItem; ++nextItem; if((nextItem != m_Suballocations.end()) && (nextItem->type == VMA_SUBALLOCATION_TYPE_FREE)) @@ -8951,7 +10194,7 @@ VkDeviceSize VmaBlockMetadata_Linear::GetUnusedRangeSizeMax() const { return size; } - + const SuballocationVectorType& suballocations1st = AccessSuballocations1st(); switch(m_2ndVectorMode) @@ -9038,7 +10281,7 @@ void VmaBlockMetadata_Linear::CalcAllocationStatInfo(VmaStatInfo& outInfo) const if(nextAlloc2ndIndex < suballoc2ndCount) { const VmaSuballocation& suballoc = suballocations2nd[nextAlloc2ndIndex]; - + // 1. Process free space before this allocation. if(lastOffset < suballoc.offset) { @@ -9049,13 +10292,13 @@ void VmaBlockMetadata_Linear::CalcAllocationStatInfo(VmaStatInfo& outInfo) const outInfo.unusedRangeSizeMin = VMA_MIN(outInfo.unusedRangeSizeMin, unusedRangeSize); outInfo.unusedRangeSizeMax = VMA_MIN(outInfo.unusedRangeSizeMax, unusedRangeSize); } - + // 2. Process this allocation. // There is allocation with suballoc.offset, suballoc.size. outInfo.usedBytes += suballoc.size; outInfo.allocationSizeMin = VMA_MIN(outInfo.allocationSizeMin, suballoc.size); outInfo.allocationSizeMax = VMA_MIN(outInfo.allocationSizeMax, suballoc.size); - + // 3. Prepare for next iteration. lastOffset = suballoc.offset + suballoc.size; ++nextAlloc2ndIndex; @@ -9095,7 +10338,7 @@ void VmaBlockMetadata_Linear::CalcAllocationStatInfo(VmaStatInfo& outInfo) const if(nextAlloc1stIndex < suballoc1stCount) { const VmaSuballocation& suballoc = suballocations1st[nextAlloc1stIndex]; - + // 1. Process free space before this allocation. if(lastOffset < suballoc.offset) { @@ -9106,13 +10349,13 @@ void VmaBlockMetadata_Linear::CalcAllocationStatInfo(VmaStatInfo& outInfo) const outInfo.unusedRangeSizeMin = VMA_MIN(outInfo.unusedRangeSizeMin, unusedRangeSize); outInfo.unusedRangeSizeMax = VMA_MIN(outInfo.unusedRangeSizeMax, unusedRangeSize); } - + // 2. Process this allocation. // There is allocation with suballoc.offset, suballoc.size. outInfo.usedBytes += suballoc.size; outInfo.allocationSizeMin = VMA_MIN(outInfo.allocationSizeMin, suballoc.size); outInfo.allocationSizeMax = VMA_MIN(outInfo.allocationSizeMax, suballoc.size); - + // 3. Prepare for next iteration. lastOffset = suballoc.offset + suballoc.size; ++nextAlloc1stIndex; @@ -9151,7 +10394,7 @@ void VmaBlockMetadata_Linear::CalcAllocationStatInfo(VmaStatInfo& outInfo) const if(nextAlloc2ndIndex != SIZE_MAX) { const VmaSuballocation& suballoc = suballocations2nd[nextAlloc2ndIndex]; - + // 1. Process free space before this allocation. if(lastOffset < suballoc.offset) { @@ -9162,13 +10405,13 @@ void VmaBlockMetadata_Linear::CalcAllocationStatInfo(VmaStatInfo& outInfo) const outInfo.unusedRangeSizeMin = VMA_MIN(outInfo.unusedRangeSizeMin, unusedRangeSize); outInfo.unusedRangeSizeMax = VMA_MIN(outInfo.unusedRangeSizeMax, unusedRangeSize); } - + // 2. Process this allocation. // There is allocation with suballoc.offset, suballoc.size. outInfo.usedBytes += suballoc.size; outInfo.allocationSizeMin = VMA_MIN(outInfo.allocationSizeMin, suballoc.size); outInfo.allocationSizeMax = VMA_MIN(outInfo.allocationSizeMax, suballoc.size); - + // 3. Prepare for next iteration. lastOffset = suballoc.offset + suballoc.size; --nextAlloc2ndIndex; @@ -9224,7 +10467,7 @@ void VmaBlockMetadata_Linear::AddPoolStats(VmaPoolStats& inoutStats) const if(nextAlloc2ndIndex < suballoc2ndCount) { const VmaSuballocation& suballoc = suballocations2nd[nextAlloc2ndIndex]; - + // 1. Process free space before this allocation. if(lastOffset < suballoc.offset) { @@ -9234,11 +10477,11 @@ void VmaBlockMetadata_Linear::AddPoolStats(VmaPoolStats& inoutStats) const ++inoutStats.unusedRangeCount; inoutStats.unusedRangeSizeMax = VMA_MAX(inoutStats.unusedRangeSizeMax, unusedRangeSize); } - + // 2. Process this allocation. // There is allocation with suballoc.offset, suballoc.size. ++inoutStats.allocationCount; - + // 3. Prepare for next iteration. lastOffset = suballoc.offset + suballoc.size; ++nextAlloc2ndIndex; @@ -9277,7 +10520,7 @@ void VmaBlockMetadata_Linear::AddPoolStats(VmaPoolStats& inoutStats) const if(nextAlloc1stIndex < suballoc1stCount) { const VmaSuballocation& suballoc = suballocations1st[nextAlloc1stIndex]; - + // 1. Process free space before this allocation. if(lastOffset < suballoc.offset) { @@ -9287,11 +10530,11 @@ void VmaBlockMetadata_Linear::AddPoolStats(VmaPoolStats& inoutStats) const ++inoutStats.unusedRangeCount; inoutStats.unusedRangeSizeMax = VMA_MAX(inoutStats.unusedRangeSizeMax, unusedRangeSize); } - + // 2. Process this allocation. // There is allocation with suballoc.offset, suballoc.size. ++inoutStats.allocationCount; - + // 3. Prepare for next iteration. lastOffset = suballoc.offset + suballoc.size; ++nextAlloc1stIndex; @@ -9329,7 +10572,7 @@ void VmaBlockMetadata_Linear::AddPoolStats(VmaPoolStats& inoutStats) const if(nextAlloc2ndIndex != SIZE_MAX) { const VmaSuballocation& suballoc = suballocations2nd[nextAlloc2ndIndex]; - + // 1. Process free space before this allocation. if(lastOffset < suballoc.offset) { @@ -9339,11 +10582,11 @@ void VmaBlockMetadata_Linear::AddPoolStats(VmaPoolStats& inoutStats) const ++inoutStats.unusedRangeCount; inoutStats.unusedRangeSizeMax = VMA_MAX(inoutStats.unusedRangeSizeMax, unusedRangeSize); } - + // 2. Process this allocation. // There is allocation with suballoc.offset, suballoc.size. ++inoutStats.allocationCount; - + // 3. Prepare for next iteration. lastOffset = suballoc.offset + suballoc.size; --nextAlloc2ndIndex; @@ -9401,19 +10644,19 @@ void VmaBlockMetadata_Linear::PrintDetailedMap(class VmaJsonWriter& json) const if(nextAlloc2ndIndex < suballoc2ndCount) { const VmaSuballocation& suballoc = suballocations2nd[nextAlloc2ndIndex]; - + // 1. Process free space before this allocation. if(lastOffset < suballoc.offset) { // There is free space from lastOffset to suballoc.offset. ++unusedRangeCount; } - + // 2. Process this allocation. // There is allocation with suballoc.offset, suballoc.size. ++alloc2ndCount; usedBytes += suballoc.size; - + // 3. Prepare for next iteration. lastOffset = suballoc.offset + suballoc.size; ++nextAlloc2ndIndex; @@ -9450,19 +10693,19 @@ void VmaBlockMetadata_Linear::PrintDetailedMap(class VmaJsonWriter& json) const if(nextAlloc1stIndex < suballoc1stCount) { const VmaSuballocation& suballoc = suballocations1st[nextAlloc1stIndex]; - + // 1. Process free space before this allocation. if(lastOffset < suballoc.offset) { // There is free space from lastOffset to suballoc.offset. ++unusedRangeCount; } - + // 2. Process this allocation. // There is allocation with suballoc.offset, suballoc.size. ++alloc1stCount; usedBytes += suballoc.size; - + // 3. Prepare for next iteration. lastOffset = suballoc.offset + suballoc.size; ++nextAlloc1stIndex; @@ -9497,19 +10740,19 @@ void VmaBlockMetadata_Linear::PrintDetailedMap(class VmaJsonWriter& json) const if(nextAlloc2ndIndex != SIZE_MAX) { const VmaSuballocation& suballoc = suballocations2nd[nextAlloc2ndIndex]; - + // 1. Process free space before this allocation. if(lastOffset < suballoc.offset) { // There is free space from lastOffset to suballoc.offset. ++unusedRangeCount; } - + // 2. Process this allocation. // There is allocation with suballoc.offset, suballoc.size. ++alloc2ndCount; usedBytes += suballoc.size; - + // 3. Prepare for next iteration. lastOffset = suballoc.offset + suballoc.size; --nextAlloc2ndIndex; @@ -9552,7 +10795,7 @@ void VmaBlockMetadata_Linear::PrintDetailedMap(class VmaJsonWriter& json) const if(nextAlloc2ndIndex < suballoc2ndCount) { const VmaSuballocation& suballoc = suballocations2nd[nextAlloc2ndIndex]; - + // 1. Process free space before this allocation. if(lastOffset < suballoc.offset) { @@ -9560,11 +10803,11 @@ void VmaBlockMetadata_Linear::PrintDetailedMap(class VmaJsonWriter& json) const const VkDeviceSize unusedRangeSize = suballoc.offset - lastOffset; PrintDetailedMap_UnusedRange(json, lastOffset, unusedRangeSize); } - + // 2. Process this allocation. // There is allocation with suballoc.offset, suballoc.size. PrintDetailedMap_Allocation(json, suballoc.offset, suballoc.hAllocation); - + // 3. Prepare for next iteration. lastOffset = suballoc.offset + suballoc.size; ++nextAlloc2ndIndex; @@ -9599,7 +10842,7 @@ void VmaBlockMetadata_Linear::PrintDetailedMap(class VmaJsonWriter& json) const if(nextAlloc1stIndex < suballoc1stCount) { const VmaSuballocation& suballoc = suballocations1st[nextAlloc1stIndex]; - + // 1. Process free space before this allocation. if(lastOffset < suballoc.offset) { @@ -9607,11 +10850,11 @@ void VmaBlockMetadata_Linear::PrintDetailedMap(class VmaJsonWriter& json) const const VkDeviceSize unusedRangeSize = suballoc.offset - lastOffset; PrintDetailedMap_UnusedRange(json, lastOffset, unusedRangeSize); } - + // 2. Process this allocation. // There is allocation with suballoc.offset, suballoc.size. PrintDetailedMap_Allocation(json, suballoc.offset, suballoc.hAllocation); - + // 3. Prepare for next iteration. lastOffset = suballoc.offset + suballoc.size; ++nextAlloc1stIndex; @@ -9647,7 +10890,7 @@ void VmaBlockMetadata_Linear::PrintDetailedMap(class VmaJsonWriter& json) const if(nextAlloc2ndIndex != SIZE_MAX) { const VmaSuballocation& suballoc = suballocations2nd[nextAlloc2ndIndex]; - + // 1. Process free space before this allocation. if(lastOffset < suballoc.offset) { @@ -9655,11 +10898,11 @@ void VmaBlockMetadata_Linear::PrintDetailedMap(class VmaJsonWriter& json) const const VkDeviceSize unusedRangeSize = suballoc.offset - lastOffset; PrintDetailedMap_UnusedRange(json, lastOffset, unusedRangeSize); } - + // 2. Process this allocation. // There is allocation with suballoc.offset, suballoc.size. PrintDetailedMap_Allocation(json, suballoc.offset, suballoc.hAllocation); - + // 3. Prepare for next iteration. lastOffset = suballoc.offset + suballoc.size; --nextAlloc2ndIndex; @@ -9764,7 +11007,7 @@ bool VmaBlockMetadata_Linear::CreateAllocationRequest_UpperAddress( // Check next suballocations from 2nd for BufferImageGranularity conflicts. // Make bigger alignment if necessary. - if(bufferImageGranularity > 1 && !suballocations2nd.empty()) + if(bufferImageGranularity > 1 && bufferImageGranularity != allocAlignment && !suballocations2nd.empty()) { bool bufferImageGranularityConflict = false; for(size_t nextSuballocIndex = suballocations2nd.size(); nextSuballocIndex--; ) @@ -9869,7 +11112,7 @@ bool VmaBlockMetadata_Linear::CreateAllocationRequest_LowerAddress( // Check previous suballocations for BufferImageGranularity conflicts. // Make bigger alignment if necessary. - if(bufferImageGranularity > 1 && !suballocations1st.empty()) + if(bufferImageGranularity > 1 && bufferImageGranularity != allocAlignment && !suballocations1st.empty()) { bool bufferImageGranularityConflict = false; for(size_t prevSuballocIndex = suballocations1st.size(); prevSuballocIndex--; ) @@ -9901,7 +11144,7 @@ bool VmaBlockMetadata_Linear::CreateAllocationRequest_LowerAddress( { // Check next suballocations for BufferImageGranularity conflicts. // If conflict exists, allocation cannot be made here. - if(bufferImageGranularity > 1 && m_2ndVectorMode == SECOND_VECTOR_DOUBLE_STACK) + if((allocSize % bufferImageGranularity || resultOffset % bufferImageGranularity) && m_2ndVectorMode == SECOND_VECTOR_DOUBLE_STACK) { for(size_t nextSuballocIndex = suballocations2nd.size(); nextSuballocIndex--; ) { @@ -9959,7 +11202,7 @@ bool VmaBlockMetadata_Linear::CreateAllocationRequest_LowerAddress( // Check previous suballocations for BufferImageGranularity conflicts. // Make bigger alignment if necessary. - if(bufferImageGranularity > 1 && !suballocations2nd.empty()) + if(bufferImageGranularity > 1 && bufferImageGranularity != allocAlignment && !suballocations2nd.empty()) { bool bufferImageGranularityConflict = false; for(size_t prevSuballocIndex = suballocations2nd.size(); prevSuballocIndex--; ) @@ -10017,7 +11260,7 @@ bool VmaBlockMetadata_Linear::CreateAllocationRequest_LowerAddress( // Check next suballocations for BufferImageGranularity conflicts. // If conflict exists, we must mark more allocations lost or fail. - if(bufferImageGranularity > 1) + if(allocSize % bufferImageGranularity || resultOffset % bufferImageGranularity) { while(index1st < suballocations1st.size()) { @@ -10063,7 +11306,7 @@ bool VmaBlockMetadata_Linear::CreateAllocationRequest_LowerAddress( { // Check next suballocations for BufferImageGranularity conflicts. // If conflict exists, allocation cannot be made here. - if(bufferImageGranularity > 1) + if(allocSize % bufferImageGranularity || resultOffset % bufferImageGranularity) { for(size_t nextSuballocIndex = index1st; nextSuballocIndex < suballocations1st.size(); @@ -10111,7 +11354,7 @@ bool VmaBlockMetadata_Linear::MakeRequestedAllocationsLost( } VMA_ASSERT(m_2ndVectorMode == SECOND_VECTOR_EMPTY || m_2ndVectorMode == SECOND_VECTOR_RING_BUFFER); - + // We always start from 1st. SuballocationVectorType* suballocations = &AccessSuballocations1st(); size_t index = m_1stNullItemsBeginCount; @@ -10160,14 +11403,14 @@ bool VmaBlockMetadata_Linear::MakeRequestedAllocationsLost( CleanupAfterFree(); //VMA_HEAVY_ASSERT(Validate()); // Already called by ClanupAfterFree(). - + return true; } uint32_t VmaBlockMetadata_Linear::MakeAllocationsLost(uint32_t currentFrameIndex, uint32_t frameInUseCount) { uint32_t lostAllocationCount = 0; - + SuballocationVectorType& suballocations1st = AccessSuballocations1st(); for(size_t i = m_1stNullItemsBeginCount, count = suballocations1st.size(); i < count; ++i) { @@ -10374,10 +11617,11 @@ void VmaBlockMetadata_Linear::FreeAtOffset(VkDeviceSize offset) VmaSuballocation refSuballoc; refSuballoc.offset = offset; // Rest of members stays uninitialized intentionally for better performance. - SuballocationVectorType::iterator it = VmaVectorFindSorted( + SuballocationVectorType::iterator it = VmaBinaryFindSorted( suballocations1st.begin() + m_1stNullItemsBeginCount, suballocations1st.end(), - refSuballoc); + refSuballoc, + VmaSuballocationOffsetLess()); if(it != suballocations1st.end()) { it->type = VMA_SUBALLOCATION_TYPE_FREE; @@ -10396,8 +11640,8 @@ void VmaBlockMetadata_Linear::FreeAtOffset(VkDeviceSize offset) refSuballoc.offset = offset; // Rest of members stays uninitialized intentionally for better performance. SuballocationVectorType::iterator it = m_2ndVectorMode == SECOND_VECTOR_RING_BUFFER ? - VmaVectorFindSorted(suballocations2nd.begin(), suballocations2nd.end(), refSuballoc) : - VmaVectorFindSorted(suballocations2nd.begin(), suballocations2nd.end(), refSuballoc); + VmaBinaryFindSorted(suballocations2nd.begin(), suballocations2nd.end(), refSuballoc, VmaSuballocationOffsetLess()) : + VmaBinaryFindSorted(suballocations2nd.begin(), suballocations2nd.end(), refSuballoc, VmaSuballocationOffsetGreater()); if(it != suballocations2nd.end()) { it->type = VMA_SUBALLOCATION_TYPE_FREE; @@ -10468,7 +11712,7 @@ void VmaBlockMetadata_Linear::CleanupAfterFree() suballocations2nd[0].hAllocation == VK_NULL_HANDLE) { --m_2ndNullItemsCount; - suballocations2nd.remove(0); + VmaVectorRemove(suballocations2nd, 0); } if(ShouldCompact1st()) @@ -10590,7 +11834,7 @@ bool VmaBlockMetadata_Buddy::Validate() const node = node->free.next) { VMA_VALIDATE(node->type == Node::TYPE_FREE); - + if(node->free.next == VMA_NULL) { VMA_VALIDATE(m_FreeList[level].back == node); @@ -10776,7 +12020,7 @@ void VmaBlockMetadata_Buddy::Alloc( const uint32_t targetLevel = AllocSizeToLevel(allocSize); uint32_t currLevel = (uint32_t)(uintptr_t)request.customData; - + Node* currNode = m_FreeList[currLevel].front; VMA_ASSERT(currNode != VMA_NULL && currNode->type == Node::TYPE_FREE); while(currNode->offset != request.offset) @@ -10784,14 +12028,14 @@ void VmaBlockMetadata_Buddy::Alloc( currNode = currNode->free.next; VMA_ASSERT(currNode != VMA_NULL && currNode->type == Node::TYPE_FREE); } - + // Go down, splitting free nodes. while(currLevel < targetLevel) { // currNode is already first free node at currLevel. // Remove it from list of free nodes at this currLevel. RemoveFromFreeList(currLevel, currNode); - + const uint32_t childrenLevel = currLevel + 1; // Create two free sub-nodes. @@ -10953,7 +12197,7 @@ void VmaBlockMetadata_Buddy::FreeAtOffset(VmaAllocation alloc, VkDeviceSize offs vma_delete(GetAllocationCallbacks(), node->buddy); vma_delete(GetAllocationCallbacks(), node); parent->type = Node::TYPE_FREE; - + node = parent; --level; //m_SumFreeSize += LevelToNodeSize(level) % 2; // Useful only when level node sizes can be non power of 2. @@ -11067,7 +12311,7 @@ void VmaBlockMetadata_Buddy::PrintDetailedMapNode(class VmaJsonWriter& json, con PrintDetailedMap_UnusedRange(json, node->offset, levelNodeSize); break; case Node::TYPE_ALLOCATION: - { + { PrintDetailedMap_Allocation(json, node->offset, node->allocation.alloc); const VkDeviceSize allocSize = node->allocation.alloc->GetSize(); if(allocSize < levelNodeSize) @@ -11156,7 +12400,7 @@ bool VmaDeviceMemoryBlock::Validate() const { VMA_VALIDATE((m_hMemory != VK_NULL_HANDLE) && (m_pMetadata->GetSize() != 0)); - + return m_pMetadata->Validate(); } @@ -11287,33 +12531,35 @@ VkResult VmaDeviceMemoryBlock::ValidateMagicValueAroundAllocation(VmaAllocator h VkResult VmaDeviceMemoryBlock::BindBufferMemory( const VmaAllocator hAllocator, const VmaAllocation hAllocation, - VkBuffer hBuffer) + VkDeviceSize allocationLocalOffset, + VkBuffer hBuffer, + const void* pNext) { VMA_ASSERT(hAllocation->GetType() == VmaAllocation_T::ALLOCATION_TYPE_BLOCK && hAllocation->GetBlock() == this); + VMA_ASSERT(allocationLocalOffset < hAllocation->GetSize() && + "Invalid allocationLocalOffset. Did you forget that this offset is relative to the beginning of the allocation, not the whole memory block?"); + const VkDeviceSize memoryOffset = hAllocation->GetOffset() + allocationLocalOffset; // This lock is important so that we don't call vkBind... and/or vkMap... simultaneously on the same VkDeviceMemory from multiple threads. VmaMutexLock lock(m_Mutex, hAllocator->m_UseMutex); - return hAllocator->GetVulkanFunctions().vkBindBufferMemory( - hAllocator->m_hDevice, - hBuffer, - m_hMemory, - hAllocation->GetOffset()); + return hAllocator->BindVulkanBuffer(m_hMemory, memoryOffset, hBuffer, pNext); } VkResult VmaDeviceMemoryBlock::BindImageMemory( const VmaAllocator hAllocator, const VmaAllocation hAllocation, - VkImage hImage) + VkDeviceSize allocationLocalOffset, + VkImage hImage, + const void* pNext) { VMA_ASSERT(hAllocation->GetType() == VmaAllocation_T::ALLOCATION_TYPE_BLOCK && hAllocation->GetBlock() == this); + VMA_ASSERT(allocationLocalOffset < hAllocation->GetSize() && + "Invalid allocationLocalOffset. Did you forget that this offset is relative to the beginning of the allocation, not the whole memory block?"); + const VkDeviceSize memoryOffset = hAllocation->GetOffset() + allocationLocalOffset; // This lock is important so that we don't call vkBind... and/or vkMap... simultaneously on the same VkDeviceMemory from multiple threads. VmaMutexLock lock(m_Mutex, hAllocator->m_UseMutex); - return hAllocator->GetVulkanFunctions().vkBindImageMemory( - hAllocator->m_hDevice, - hImage, - m_hMemory, - hAllocation->GetOffset()); + return hAllocator->BindVulkanImage(m_hMemory, memoryOffset, hImage, pNext); } static void InitStatInfo(VmaStatInfo& outInfo) @@ -11358,10 +12604,11 @@ VmaPool_T::VmaPool_T( createInfo.maxBlockCount, (createInfo.flags & VMA_POOL_CREATE_IGNORE_BUFFER_IMAGE_GRANULARITY_BIT) != 0 ? 1 : hAllocator->GetBufferImageGranularity(), createInfo.frameInUseCount, - true, // isCustomPool createInfo.blockSize != 0, // explicitBlockSize - createInfo.flags & VMA_POOL_CREATE_ALGORITHM_MASK), // algorithm - m_Id(0) + createInfo.flags & VMA_POOL_CREATE_ALGORITHM_MASK, + createInfo.priority), // algorithm + m_Id(0), + m_Name(VMA_NULL) { } @@ -11369,6 +12616,21 @@ VmaPool_T::~VmaPool_T() { } +void VmaPool_T::SetName(const char* pName) +{ + const VkAllocationCallbacks* allocs = m_BlockVector.GetAllocator()->GetAllocationCallbacks(); + VmaFreeString(allocs, m_Name); + + if(pName != VMA_NULL) + { + m_Name = VmaCreateStringCopy(allocs, pName); + } + else + { + m_Name = VMA_NULL; + } +} + #if VMA_STATS_STRING_ENABLED #endif // #if VMA_STATS_STRING_ENABLED @@ -11382,9 +12644,9 @@ VmaBlockVector::VmaBlockVector( size_t maxBlockCount, VkDeviceSize bufferImageGranularity, uint32_t frameInUseCount, - bool isCustomPool, bool explicitBlockSize, - uint32_t algorithm) : + uint32_t algorithm, + float priority) : m_hAllocator(hAllocator), m_hParentPool(hParentPool), m_MemoryTypeIndex(memoryTypeIndex), @@ -11393,9 +12655,9 @@ VmaBlockVector::VmaBlockVector( m_MaxBlockCount(maxBlockCount), m_BufferImageGranularity(bufferImageGranularity), m_FrameInUseCount(frameInUseCount), - m_IsCustomPool(isCustomPool), m_ExplicitBlockSize(explicitBlockSize), m_Algorithm(algorithm), + m_Priority(priority), m_HasEmptyBlock(false), m_Blocks(VmaStlAllocator(hAllocator->GetAllocationCallbacks())), m_NextBlockId(0) @@ -11446,6 +12708,12 @@ void VmaBlockVector::GetPoolStats(VmaPoolStats* pStats) } } +bool VmaBlockVector::IsEmpty() +{ + VmaMutexLockRead lock(m_Mutex, m_hAllocator->m_UseMutex); + return m_Blocks.empty(); +} + bool VmaBlockVector::IsCorruptionDetectionEnabled() const { const uint32_t requiredMemFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; @@ -11518,9 +12786,20 @@ VkResult VmaBlockVector::AllocatePage( bool canMakeOtherLost = (createInfo.flags & VMA_ALLOCATION_CREATE_CAN_MAKE_OTHER_LOST_BIT) != 0; const bool mapped = (createInfo.flags & VMA_ALLOCATION_CREATE_MAPPED_BIT) != 0; const bool isUserDataString = (createInfo.flags & VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT) != 0; + + VkDeviceSize freeMemory; + { + const uint32_t heapIndex = m_hAllocator->MemoryTypeIndexToHeapIndex(m_MemoryTypeIndex); + VmaBudget heapBudget = {}; + m_hAllocator->GetBudget(&heapBudget, heapIndex, 1); + freeMemory = (heapBudget.usage < heapBudget.budget) ? (heapBudget.budget - heapBudget.usage) : 0; + } + + const bool canFallbackToDedicated = !IsCustomPool(); const bool canCreateNewBlock = ((createInfo.flags & VMA_ALLOCATION_CREATE_NEVER_ALLOCATE_BIT) == 0) && - (m_Blocks.size() < m_MaxBlockCount); + (m_Blocks.size() < m_MaxBlockCount) && + (freeMemory >= size || !canFallbackToDedicated); uint32_t strategy = createInfo.flags & VMA_ALLOCATION_CREATE_STRATEGY_MASK; // If linearAlgorithm is used, canMakeOtherLost is available only when used as ring buffer. @@ -11587,7 +12866,7 @@ VkResult VmaBlockVector::AllocatePage( pAllocation); if(res == VK_SUCCESS) { - VMA_DEBUG_LOG(" Returned from last block #%u", (uint32_t)(m_Blocks.size() - 1)); + VMA_DEBUG_LOG(" Returned from last block #%u", pCurrBlock->GetId()); return VK_SUCCESS; } } @@ -11613,7 +12892,7 @@ VkResult VmaBlockVector::AllocatePage( pAllocation); if(res == VK_SUCCESS) { - VMA_DEBUG_LOG(" Returned from existing block #%u", (uint32_t)blockIndex); + VMA_DEBUG_LOG(" Returned from existing block #%u", pCurrBlock->GetId()); return VK_SUCCESS; } } @@ -11637,7 +12916,7 @@ VkResult VmaBlockVector::AllocatePage( pAllocation); if(res == VK_SUCCESS) { - VMA_DEBUG_LOG(" Returned from existing block #%u", (uint32_t)blockIndex); + VMA_DEBUG_LOG(" Returned from existing block #%u", pCurrBlock->GetId()); return VK_SUCCESS; } } @@ -11672,7 +12951,8 @@ VkResult VmaBlockVector::AllocatePage( } size_t newBlockIndex = 0; - VkResult res = CreateBlock(newBlockSize, &newBlockIndex); + VkResult res = (newBlockSize <= freeMemory || !canFallbackToDedicated) ? + CreateBlock(newBlockSize, &newBlockIndex) : VK_ERROR_OUT_OF_DEVICE_MEMORY; // Allocation of this size failed? Try 1/2, 1/4, 1/8 of m_PreferredBlockSize. if(!m_ExplicitBlockSize) { @@ -11683,7 +12963,8 @@ VkResult VmaBlockVector::AllocatePage( { newBlockSize = smallerNewBlockSize; ++newBlockSizeShift; - res = CreateBlock(newBlockSize, &newBlockIndex); + res = (newBlockSize <= freeMemory || !canFallbackToDedicated) ? + CreateBlock(newBlockSize, &newBlockIndex) : VK_ERROR_OUT_OF_DEVICE_MEMORY; } else { @@ -11709,7 +12990,7 @@ VkResult VmaBlockVector::AllocatePage( pAllocation); if(res == VK_SUCCESS) { - VMA_DEBUG_LOG(" Created new block Size=%llu", newBlockSize); + VMA_DEBUG_LOG(" Created new block #%u Size=%llu", pBlock->GetId(), newBlockSize); return VK_SUCCESS; } else @@ -11823,26 +13104,23 @@ VkResult VmaBlockVector::AllocatePage( m_FrameInUseCount, &bestRequest)) { - // We no longer have an empty Allocation. - if(pBestRequestBlock->m_pMetadata->IsEmpty()) - { - m_HasEmptyBlock = false; - } // Allocate from this pBlock. - *pAllocation = m_hAllocator->m_AllocationObjectAllocator.Allocate(); - (*pAllocation)->Ctor(currentFrameIndex, isUserDataString); + *pAllocation = m_hAllocator->m_AllocationObjectAllocator.Allocate(currentFrameIndex, isUserDataString); pBestRequestBlock->m_pMetadata->Alloc(bestRequest, suballocType, size, *pAllocation); + UpdateHasEmptyBlock(); (*pAllocation)->InitBlockAllocation( pBestRequestBlock, bestRequest.offset, alignment, size, + m_MemoryTypeIndex, suballocType, mapped, (createInfo.flags & VMA_ALLOCATION_CREATE_CAN_BECOME_LOST_BIT) != 0); VMA_HEAVY_ASSERT(pBestRequestBlock->Validate()); VMA_DEBUG_LOG(" Returned from existing block"); (*pAllocation)->SetUserData(m_hAllocator, createInfo.pUserData); + m_hAllocator->m_Budget.AddAllocation(m_hAllocator->MemoryTypeIndexToHeapIndex(m_MemoryTypeIndex), size); if(VMA_DEBUG_INITIALIZE_ALLOCATIONS) { m_hAllocator->FillAllocation(*pAllocation, VMA_ALLOCATION_FILL_PATTERN_CREATED); @@ -11875,10 +13153,18 @@ VkResult VmaBlockVector::AllocatePage( } void VmaBlockVector::Free( - VmaAllocation hAllocation) + const VmaAllocation hAllocation) { VmaDeviceMemoryBlock* pBlockToDelete = VMA_NULL; + bool budgetExceeded = false; + { + const uint32_t heapIndex = m_hAllocator->MemoryTypeIndexToHeapIndex(m_MemoryTypeIndex); + VmaBudget heapBudget = {}; + m_hAllocator->GetBudget(&heapBudget, heapIndex, 1); + budgetExceeded = heapBudget.usage >= heapBudget.budget; + } + // Scope for lock. { VmaMutexLockWrite lock(m_Mutex, m_hAllocator->m_UseMutex); @@ -11901,42 +13187,39 @@ void VmaBlockVector::Free( VMA_DEBUG_LOG(" Freed from MemoryTypeIndex=%u", m_MemoryTypeIndex); + const bool canDeleteBlock = m_Blocks.size() > m_MinBlockCount; // pBlock became empty after this deallocation. if(pBlock->m_pMetadata->IsEmpty()) { - // Already has empty Allocation. We don't want to have two, so delete this one. - if(m_HasEmptyBlock && m_Blocks.size() > m_MinBlockCount) + // Already has empty block. We don't want to have two, so delete this one. + if((m_HasEmptyBlock || budgetExceeded) && canDeleteBlock) { pBlockToDelete = pBlock; Remove(pBlock); } - // We now have first empty block. - else - { - m_HasEmptyBlock = true; - } + // else: We now have an empty block - leave it. } // pBlock didn't become empty, but we have another empty block - find and free that one. // (This is optional, heuristics.) - else if(m_HasEmptyBlock) + else if(m_HasEmptyBlock && canDeleteBlock) { VmaDeviceMemoryBlock* pLastBlock = m_Blocks.back(); - if(pLastBlock->m_pMetadata->IsEmpty() && m_Blocks.size() > m_MinBlockCount) + if(pLastBlock->m_pMetadata->IsEmpty()) { pBlockToDelete = pLastBlock; m_Blocks.pop_back(); - m_HasEmptyBlock = false; } } + UpdateHasEmptyBlock(); IncrementallySortBlocks(); } - // Destruction of a free Allocation. Deferred until this point, outside of mutex + // Destruction of a free block. Deferred until this point, outside of mutex // lock, for performance reason. if(pBlockToDelete != VMA_NULL) { - VMA_DEBUG_LOG(" Deleted empty allocation"); + VMA_DEBUG_LOG(" Deleted empty block"); pBlockToDelete->Destroy(m_hAllocator); vma_delete(m_hAllocator, pBlockToDelete); } @@ -12025,26 +13308,22 @@ VkResult VmaBlockVector::AllocateFromBlock( return res; } } - - // We no longer have an empty Allocation. - if(pBlock->m_pMetadata->IsEmpty()) - { - m_HasEmptyBlock = false; - } - - *pAllocation = m_hAllocator->m_AllocationObjectAllocator.Allocate(); - (*pAllocation)->Ctor(currentFrameIndex, isUserDataString); + + *pAllocation = m_hAllocator->m_AllocationObjectAllocator.Allocate(currentFrameIndex, isUserDataString); pBlock->m_pMetadata->Alloc(currRequest, suballocType, size, *pAllocation); + UpdateHasEmptyBlock(); (*pAllocation)->InitBlockAllocation( pBlock, currRequest.offset, alignment, size, + m_MemoryTypeIndex, suballocType, mapped, (allocFlags & VMA_ALLOCATION_CREATE_CAN_BECOME_LOST_BIT) != 0); VMA_HEAVY_ASSERT(pBlock->Validate()); (*pAllocation)->SetUserData(m_hAllocator, pUserData); + m_hAllocator->m_Budget.AddAllocation(m_hAllocator->MemoryTypeIndexToHeapIndex(m_MemoryTypeIndex), size); if(VMA_DEBUG_INITIALIZE_ALLOCATIONS) { m_hAllocator->FillAllocation(*pAllocation, VMA_ALLOCATION_FILL_PATTERN_CREATED); @@ -12064,6 +13343,26 @@ VkResult VmaBlockVector::CreateBlock(VkDeviceSize blockSize, size_t* pNewBlockIn VkMemoryAllocateInfo allocInfo = { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO }; allocInfo.memoryTypeIndex = m_MemoryTypeIndex; allocInfo.allocationSize = blockSize; + +#if VMA_BUFFER_DEVICE_ADDRESS + // Every standalone block can potentially contain a buffer with VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT - always enable the feature. + VkMemoryAllocateFlagsInfoKHR allocFlagsInfo = { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_FLAGS_INFO_KHR }; + if(m_hAllocator->m_UseKhrBufferDeviceAddress) + { + allocFlagsInfo.flags = VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT_KHR; + VmaPnextChainPushFront(&allocInfo, &allocFlagsInfo); + } +#endif // #if VMA_BUFFER_DEVICE_ADDRESS + +#if VMA_MEMORY_PRIORITY + VkMemoryPriorityAllocateInfoEXT priorityInfo = { VK_STRUCTURE_TYPE_MEMORY_PRIORITY_ALLOCATE_INFO_EXT }; + if(m_hAllocator->m_UseExtMemoryPriority) + { + priorityInfo.priority = m_Priority; + VmaPnextChainPushFront(&allocInfo, &priorityInfo); + } +#endif // #if VMA_MEMORY_PRIORITY + VkDeviceMemory mem = VK_NULL_HANDLE; VkResult res = m_hAllocator->AllocateVulkanMemory(&allocInfo, &mem); if(res < 0) @@ -12112,7 +13411,7 @@ void VmaBlockVector::ApplyDefragmentationMovesCpu( void* pMappedData; }; VmaVector< BlockInfo, VmaStlAllocator > - blockInfo(blockCount, VmaStlAllocator(m_hAllocator->GetAllocationCallbacks())); + blockInfo(blockCount, BlockInfo(), VmaStlAllocator(m_hAllocator->GetAllocationCallbacks())); memset(blockInfo.data(), 0, blockCount * sizeof(BlockInfo)); // Go over all moves. Mark blocks that are used with BLOCK_FLAG_USED. @@ -12214,7 +13513,7 @@ void VmaBlockVector::ApplyDefragmentationMovesCpu( void VmaBlockVector::ApplyDefragmentationMovesGpu( class VmaBlockVectorDefragmentationContext* pDefragCtx, - const VmaVector< VmaDefragmentationMove, VmaStlAllocator >& moves, + VmaVector< VmaDefragmentationMove, VmaStlAllocator >& moves, VkCommandBuffer commandBuffer) { const size_t blockCount = m_Blocks.size(); @@ -12227,17 +13526,21 @@ void VmaBlockVector::ApplyDefragmentationMovesGpu( for(size_t moveIndex = 0; moveIndex < moveCount; ++moveIndex) { const VmaDefragmentationMove& move = moves[moveIndex]; - pDefragCtx->blockContexts[move.srcBlockIndex].flags |= VmaBlockDefragmentationContext::BLOCK_FLAG_USED; - pDefragCtx->blockContexts[move.dstBlockIndex].flags |= VmaBlockDefragmentationContext::BLOCK_FLAG_USED; + + //if(move.type == VMA_ALLOCATION_TYPE_UNKNOWN) + { + // Old school move still require us to map the whole block + pDefragCtx->blockContexts[move.srcBlockIndex].flags |= VmaBlockDefragmentationContext::BLOCK_FLAG_USED; + pDefragCtx->blockContexts[move.dstBlockIndex].flags |= VmaBlockDefragmentationContext::BLOCK_FLAG_USED; + } } VMA_ASSERT(pDefragCtx->res == VK_SUCCESS); // Go over all blocks. Create and bind buffer for whole block if necessary. { - VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; - bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | - VK_BUFFER_USAGE_TRANSFER_DST_BIT; + VkBufferCreateInfo bufCreateInfo; + VmaFillGpuDefragmentationBufferCreateInfo(bufCreateInfo); for(size_t blockIndex = 0; pDefragCtx->res == VK_SUCCESS && blockIndex < blockCount; ++blockIndex) { @@ -12260,9 +13563,6 @@ void VmaBlockVector::ApplyDefragmentationMovesGpu( // Go over all moves. Post data transfer commands to command buffer. if(pDefragCtx->res == VK_SUCCESS) { - const VkDeviceSize nonCoherentAtomSize = m_hAllocator->m_PhysicalDeviceProperties.limits.nonCoherentAtomSize; - VkMappedMemoryRange memRange = { VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE }; - for(size_t moveIndex = 0; moveIndex < moveCount; ++moveIndex) { const VmaDefragmentationMove& move = moves[moveIndex]; @@ -12290,7 +13590,6 @@ void VmaBlockVector::ApplyDefragmentationMovesGpu( void VmaBlockVector::FreeEmptyBlocks(VmaDefragmentationStats* pDefragmentationStats) { - m_HasEmptyBlock = false; for(size_t blockIndex = m_Blocks.size(); blockIndex--; ) { VmaDeviceMemoryBlock* pBlock = m_Blocks[blockIndex]; @@ -12310,10 +13609,25 @@ void VmaBlockVector::FreeEmptyBlocks(VmaDefragmentationStats* pDefragmentationSt } else { - m_HasEmptyBlock = true; + break; } } } + UpdateHasEmptyBlock(); +} + +void VmaBlockVector::UpdateHasEmptyBlock() +{ + m_HasEmptyBlock = false; + for(size_t index = 0, count = m_Blocks.size(); index < count; ++index) + { + VmaDeviceMemoryBlock* const pBlock = m_Blocks[index]; + if(pBlock->m_pMetadata->IsEmpty()) + { + m_HasEmptyBlock = true; + break; + } + } } #if VMA_STATS_STRING_ENABLED @@ -12324,8 +13638,15 @@ void VmaBlockVector::PrintDetailedMap(class VmaJsonWriter& json) json.BeginObject(); - if(m_IsCustomPool) + if(IsCustomPool()) { + const char* poolName = m_hParentPool->GetName(); + if(poolName != VMA_NULL && poolName[0] != '\0') + { + json.WriteString("Name"); + json.WriteString(poolName); + } + json.WriteString("MemoryTypeIndex"); json.WriteNumber(m_MemoryTypeIndex); @@ -12385,22 +13706,22 @@ void VmaBlockVector::PrintDetailedMap(class VmaJsonWriter& json) void VmaBlockVector::Defragment( class VmaBlockVectorDefragmentationContext* pCtx, - VmaDefragmentationStats* pStats, + VmaDefragmentationStats* pStats, VmaDefragmentationFlags flags, VkDeviceSize& maxCpuBytesToMove, uint32_t& maxCpuAllocationsToMove, VkDeviceSize& maxGpuBytesToMove, uint32_t& maxGpuAllocationsToMove, VkCommandBuffer commandBuffer) { pCtx->res = VK_SUCCESS; - + const VkMemoryPropertyFlags memPropFlags = m_hAllocator->m_MemProps.memoryTypes[m_MemoryTypeIndex].propertyFlags; const bool isHostVisible = (memPropFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) != 0; - const bool isHostCoherent = (memPropFlags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) != 0; const bool canDefragmentOnCpu = maxCpuBytesToMove > 0 && maxCpuAllocationsToMove > 0 && isHostVisible; const bool canDefragmentOnGpu = maxGpuBytesToMove > 0 && maxGpuAllocationsToMove > 0 && - !IsCorruptionDetectionEnabled(); + !IsCorruptionDetectionEnabled() && + ((1u << m_MemoryTypeIndex) & m_hAllocator->GetGpuDefragmentationMemoryTypeBits()) != 0; // There are options to defragment this memory type. if(canDefragmentOnCpu || canDefragmentOnGpu) @@ -12422,19 +13743,28 @@ void VmaBlockVector::Defragment( if(m_hAllocator->m_UseMutex) { - m_Mutex.LockWrite(); - pCtx->mutexLocked = true; + if(flags & VMA_DEFRAGMENTATION_FLAG_INCREMENTAL) + { + if(!m_Mutex.TryLockWrite()) + { + pCtx->res = VK_ERROR_INITIALIZATION_FAILED; + return; + } + } + else + { + m_Mutex.LockWrite(); + pCtx->mutexLocked = true; + } } - pCtx->Begin(overlappingMoveSupported); + pCtx->Begin(overlappingMoveSupported, flags); // Defragment. const VkDeviceSize maxBytesToMove = defragmentOnGpu ? maxGpuBytesToMove : maxCpuBytesToMove; const uint32_t maxAllocationsToMove = defragmentOnGpu ? maxGpuAllocationsToMove : maxCpuAllocationsToMove; - VmaVector< VmaDefragmentationMove, VmaStlAllocator > moves = - VmaVector< VmaDefragmentationMove, VmaStlAllocator >(VmaStlAllocator(m_hAllocator->GetAllocationCallbacks())); - pCtx->res = pCtx->GetAlgorithm()->Defragment(moves, maxBytesToMove, maxAllocationsToMove); + pCtx->res = pCtx->GetAlgorithm()->Defragment(pCtx->defragmentationMoves, maxBytesToMove, maxAllocationsToMove, flags); // Accumulate statistics. if(pStats != VMA_NULL) @@ -12456,16 +13786,27 @@ void VmaBlockVector::Defragment( maxCpuAllocationsToMove -= allocationsMoved; } } - + + if(flags & VMA_DEFRAGMENTATION_FLAG_INCREMENTAL) + { + if(m_hAllocator->m_UseMutex) + m_Mutex.UnlockWrite(); + + if(pCtx->res >= VK_SUCCESS && !pCtx->defragmentationMoves.empty()) + pCtx->res = VK_NOT_READY; + + return; + } + if(pCtx->res >= VK_SUCCESS) { if(defragmentOnGpu) { - ApplyDefragmentationMovesGpu(pCtx, moves, commandBuffer); + ApplyDefragmentationMovesGpu(pCtx, pCtx->defragmentationMoves, commandBuffer); } else { - ApplyDefragmentationMovesCpu(pCtx, moves); + ApplyDefragmentationMovesCpu(pCtx, pCtx->defragmentationMoves); } } } @@ -12473,22 +13814,36 @@ void VmaBlockVector::Defragment( void VmaBlockVector::DefragmentationEnd( class VmaBlockVectorDefragmentationContext* pCtx, + uint32_t flags, VmaDefragmentationStats* pStats) { - // Destroy buffers. - for(size_t blockIndex = pCtx->blockContexts.size(); blockIndex--; ) + if(flags & VMA_DEFRAGMENTATION_FLAG_INCREMENTAL && m_hAllocator->m_UseMutex) { - VmaBlockDefragmentationContext& blockCtx = pCtx->blockContexts[blockIndex]; - if(blockCtx.hBuffer) - { - (*m_hAllocator->GetVulkanFunctions().vkDestroyBuffer)( - m_hAllocator->m_hDevice, blockCtx.hBuffer, m_hAllocator->GetAllocationCallbacks()); - } + VMA_ASSERT(pCtx->mutexLocked == false); + + // Incremental defragmentation doesn't hold the lock, so when we enter here we don't actually have any + // lock protecting us. Since we mutate state here, we have to take the lock out now + m_Mutex.LockWrite(); + pCtx->mutexLocked = true; } - if(pCtx->res >= VK_SUCCESS) + // If the mutex isn't locked we didn't do any work and there is nothing to delete. + if(pCtx->mutexLocked || !m_hAllocator->m_UseMutex) { - FreeEmptyBlocks(pStats); + // Destroy buffers. + for(size_t blockIndex = pCtx->blockContexts.size(); blockIndex--;) + { + VmaBlockDefragmentationContext &blockCtx = pCtx->blockContexts[blockIndex]; + if(blockCtx.hBuffer) + { + (*m_hAllocator->GetVulkanFunctions().vkDestroyBuffer)(m_hAllocator->m_hDevice, blockCtx.hBuffer, m_hAllocator->GetAllocationCallbacks()); + } + } + + if(pCtx->res >= VK_SUCCESS) + { + FreeEmptyBlocks(pStats); + } } if(pCtx->mutexLocked) @@ -12498,6 +13853,48 @@ void VmaBlockVector::DefragmentationEnd( } } +uint32_t VmaBlockVector::ProcessDefragmentations( + class VmaBlockVectorDefragmentationContext *pCtx, + VmaDefragmentationPassMoveInfo* pMove, uint32_t maxMoves) +{ + VmaMutexLockWrite lock(m_Mutex, m_hAllocator->m_UseMutex); + + const uint32_t moveCount = VMA_MIN(uint32_t(pCtx->defragmentationMoves.size()) - pCtx->defragmentationMovesProcessed, maxMoves); + + for(uint32_t i = 0; i < moveCount; ++ i) + { + VmaDefragmentationMove& move = pCtx->defragmentationMoves[pCtx->defragmentationMovesProcessed + i]; + + pMove->allocation = move.hAllocation; + pMove->memory = move.pDstBlock->GetDeviceMemory(); + pMove->offset = move.dstOffset; + + ++ pMove; + } + + pCtx->defragmentationMovesProcessed += moveCount; + + return moveCount; +} + +void VmaBlockVector::CommitDefragmentations( + class VmaBlockVectorDefragmentationContext *pCtx, + VmaDefragmentationStats* pStats) +{ + VmaMutexLockWrite lock(m_Mutex, m_hAllocator->m_UseMutex); + + for(uint32_t i = pCtx->defragmentationMovesCommitted; i < pCtx->defragmentationMovesProcessed; ++ i) + { + const VmaDefragmentationMove &move = pCtx->defragmentationMoves[i]; + + move.pSrcBlock->m_pMetadata->FreeAtOffset(move.srcOffset); + move.hAllocation->ChangeBlockAllocation(m_hAllocator, move.pDstBlock, move.dstOffset); + } + + pCtx->defragmentationMovesCommitted = pCtx->defragmentationMovesProcessed; + FreeEmptyBlocks(pStats); +} + size_t VmaBlockVector::CalcAllocationCount() const { size_t result = 0; @@ -12596,8 +13993,8 @@ VmaDefragmentationAlgorithm_Generic::VmaDefragmentationAlgorithm_Generic( uint32_t currentFrameIndex, bool overlappingMoveSupported) : VmaDefragmentationAlgorithm(hAllocator, pBlockVector, currentFrameIndex), - m_AllAllocations(false), m_AllocationCount(0), + m_AllAllocations(false), m_BytesMoved(0), m_AllocationsMoved(0), m_Blocks(VmaStlAllocator(hAllocator->GetAllocationCallbacks())) @@ -12648,7 +14045,8 @@ void VmaDefragmentationAlgorithm_Generic::AddAllocation(VmaAllocation hAlloc, Vk VkResult VmaDefragmentationAlgorithm_Generic::DefragmentRound( VmaVector< VmaDefragmentationMove, VmaStlAllocator >& moves, VkDeviceSize maxBytesToMove, - uint32_t maxAllocationsToMove) + uint32_t maxAllocationsToMove, + bool freeOldAllocations) { if(m_Blocks.empty()) { @@ -12703,7 +14101,7 @@ VkResult VmaDefragmentationAlgorithm_Generic::DefragmentRound( srcAllocIndex = m_Blocks[srcBlockIndex]->m_Allocations.size() - 1; } } - + BlockInfo* pSrcBlockInfo = m_Blocks[srcBlockIndex]; AllocationInfo& allocInfo = pSrcBlockInfo->m_Allocations[srcAllocIndex]; @@ -12740,12 +14138,16 @@ VkResult VmaDefragmentationAlgorithm_Generic::DefragmentRound( return VK_SUCCESS; } - VmaDefragmentationMove move; + VmaDefragmentationMove move = {}; move.srcBlockIndex = pSrcBlockInfo->m_OriginalBlockIndex; move.dstBlockIndex = pDstBlockInfo->m_OriginalBlockIndex; move.srcOffset = srcOffset; move.dstOffset = dstAllocRequest.offset; move.size = size; + move.hAllocation = allocInfo.m_hAllocation; + move.pSrcBlock = pSrcBlockInfo->m_pBlock; + move.pDstBlock = pDstBlockInfo->m_pBlock; + moves.push_back(move); pDstBlockInfo->m_pBlock->m_pMetadata->Alloc( @@ -12753,9 +14155,12 @@ VkResult VmaDefragmentationAlgorithm_Generic::DefragmentRound( suballocType, size, allocInfo.m_hAllocation); - pSrcBlockInfo->m_pBlock->m_pMetadata->FreeAtOffset(srcOffset); - - allocInfo.m_hAllocation->ChangeBlockAllocation(m_hAllocator, pDstBlockInfo->m_pBlock, dstAllocRequest.offset); + + if(freeOldAllocations) + { + pSrcBlockInfo->m_pBlock->m_pMetadata->FreeAtOffset(srcOffset); + allocInfo.m_hAllocation->ChangeBlockAllocation(m_hAllocator, pDstBlockInfo->m_pBlock, dstAllocRequest.offset); + } if(allocInfo.m_pChanged != VMA_NULL) { @@ -12808,7 +14213,8 @@ size_t VmaDefragmentationAlgorithm_Generic::CalcBlocksWithNonMovableCount() cons VkResult VmaDefragmentationAlgorithm_Generic::Defragment( VmaVector< VmaDefragmentationMove, VmaStlAllocator >& moves, VkDeviceSize maxBytesToMove, - uint32_t maxAllocationsToMove) + uint32_t maxAllocationsToMove, + VmaDefragmentationFlags flags) { if(!m_AllAllocations && m_AllocationCount == 0) { @@ -12836,7 +14242,7 @@ VkResult VmaDefragmentationAlgorithm_Generic::Defragment( } pBlockInfo->CalcHasNonMovableAllocations(); - + // This is a choice based on research. // Option 1: pBlockInfo->SortAllocationsByOffsetDescending(); @@ -12854,7 +14260,7 @@ VkResult VmaDefragmentationAlgorithm_Generic::Defragment( VkResult result = VK_SUCCESS; for(uint32_t round = 0; (round < roundCount) && (result == VK_SUCCESS); ++round) { - result = DefragmentRound(moves, maxBytesToMove, maxAllocationsToMove); + result = DefragmentRound(moves, maxBytesToMove, maxAllocationsToMove, !(flags & VMA_DEFRAGMENTATION_FLAG_INCREMENTAL)); } return result; @@ -12906,7 +14312,8 @@ VmaDefragmentationAlgorithm_Fast::~VmaDefragmentationAlgorithm_Fast() VkResult VmaDefragmentationAlgorithm_Fast::Defragment( VmaVector< VmaDefragmentationMove, VmaStlAllocator >& moves, VkDeviceSize maxBytesToMove, - uint32_t maxAllocationsToMove) + uint32_t maxAllocationsToMove, + VmaDefragmentationFlags flags) { VMA_ASSERT(m_AllAllocations || m_pBlockVector->CalcAllocationCount() == m_AllocationCount); @@ -12962,6 +14369,7 @@ VkResult VmaDefragmentationAlgorithm_Fast::Defragment( } const VkDeviceSize srcAllocOffset = srcSuballocIt->offset; + VmaDefragmentationMove move = {}; // Try to place it in one of free spaces from the database. size_t freeSpaceInfoIndex; VkDeviceSize dstAllocOffset; @@ -12971,7 +14379,6 @@ VkResult VmaDefragmentationAlgorithm_Fast::Defragment( size_t freeSpaceOrigBlockIndex = m_BlockInfos[freeSpaceInfoIndex].origBlockIndex; VmaDeviceMemoryBlock* pFreeSpaceBlock = m_pBlockVector->GetBlock(freeSpaceOrigBlockIndex); VmaBlockMetadata_Generic* pFreeSpaceMetadata = (VmaBlockMetadata_Generic*)pFreeSpaceBlock->m_pMetadata; - VkDeviceSize freeSpaceBlockSize = pFreeSpaceMetadata->GetSize(); // Same block if(freeSpaceInfoIndex == srcBlockInfoIndex) @@ -12985,7 +14392,7 @@ VkResult VmaDefragmentationAlgorithm_Fast::Defragment( suballoc.hAllocation->ChangeOffset(dstAllocOffset); m_BytesMoved += srcAllocSize; ++m_AllocationsMoved; - + VmaSuballocationList::iterator nextSuballocIt = srcSuballocIt; ++nextSuballocIt; pSrcMetadata->m_Suballocations.erase(srcSuballocIt); @@ -12993,10 +14400,12 @@ VkResult VmaDefragmentationAlgorithm_Fast::Defragment( InsertSuballoc(pFreeSpaceMetadata, suballoc); - VmaDefragmentationMove move = { - srcOrigBlockIndex, freeSpaceOrigBlockIndex, - srcAllocOffset, dstAllocOffset, - srcAllocSize }; + move.srcBlockIndex = srcOrigBlockIndex; + move.dstBlockIndex = freeSpaceOrigBlockIndex; + move.srcOffset = srcAllocOffset; + move.dstOffset = dstAllocOffset; + move.size = srcAllocSize; + moves.push_back(move); } // Different block @@ -13019,10 +14428,12 @@ VkResult VmaDefragmentationAlgorithm_Fast::Defragment( InsertSuballoc(pFreeSpaceMetadata, suballoc); - VmaDefragmentationMove move = { - srcOrigBlockIndex, freeSpaceOrigBlockIndex, - srcAllocOffset, dstAllocOffset, - srcAllocSize }; + move.srcBlockIndex = srcOrigBlockIndex; + move.dstBlockIndex = freeSpaceOrigBlockIndex; + move.srcOffset = srcAllocOffset; + move.dstOffset = dstAllocOffset; + move.size = srcAllocSize; + moves.push_back(move); } } @@ -13077,10 +14488,13 @@ VkResult VmaDefragmentationAlgorithm_Fast::Defragment( m_BytesMoved += srcAllocSize; ++m_AllocationsMoved; ++srcSuballocIt; - VmaDefragmentationMove move = { - srcOrigBlockIndex, dstOrigBlockIndex, - srcAllocOffset, dstAllocOffset, - srcAllocSize }; + + move.srcBlockIndex = srcOrigBlockIndex; + move.dstBlockIndex = dstOrigBlockIndex; + move.srcOffset = srcAllocOffset; + move.dstOffset = dstAllocOffset; + move.size = srcAllocSize; + moves.push_back(move); } } @@ -13106,10 +14520,12 @@ VkResult VmaDefragmentationAlgorithm_Fast::Defragment( pDstMetadata->m_Suballocations.push_back(suballoc); - VmaDefragmentationMove move = { - srcOrigBlockIndex, dstOrigBlockIndex, - srcAllocOffset, dstAllocOffset, - srcAllocSize }; + move.srcBlockIndex = srcOrigBlockIndex; + move.dstBlockIndex = dstOrigBlockIndex; + move.srcOffset = srcAllocOffset; + move.dstOffset = dstAllocOffset; + move.size = srcAllocSize; + moves.push_back(move); } } @@ -13117,7 +14533,7 @@ VkResult VmaDefragmentationAlgorithm_Fast::Defragment( } m_BlockInfos.clear(); - + PostprocessMetadata(); return VK_SUCCESS; @@ -13159,7 +14575,7 @@ void VmaDefragmentationAlgorithm_Fast::PostprocessMetadata() VmaBlockMetadata_Generic* const pMetadata = (VmaBlockMetadata_Generic*)m_pBlockVector->GetBlock(blockIndex)->m_pMetadata; const VkDeviceSize blockSize = pMetadata->GetSize(); - + // No allocations in this block - entire area is free. if(pMetadata->m_Suballocations.empty()) { @@ -13255,16 +14671,18 @@ VmaBlockVectorDefragmentationContext::VmaBlockVectorDefragmentationContext( VmaAllocator hAllocator, VmaPool hCustomPool, VmaBlockVector* pBlockVector, - uint32_t currFrameIndex, - uint32_t algorithmFlags) : + uint32_t currFrameIndex) : res(VK_SUCCESS), mutexLocked(false), blockContexts(VmaStlAllocator(hAllocator->GetAllocationCallbacks())), + defragmentationMoves(VmaStlAllocator(hAllocator->GetAllocationCallbacks())), + defragmentationMovesProcessed(0), + defragmentationMovesCommitted(0), + hasDefragmentationPlan(0), m_hAllocator(hAllocator), m_hCustomPool(hCustomPool), m_pBlockVector(pBlockVector), m_CurrFrameIndex(currFrameIndex), - m_AlgorithmFlags(algorithmFlags), m_pAlgorithm(VMA_NULL), m_Allocations(VmaStlAllocator(hAllocator->GetAllocationCallbacks())), m_AllAllocations(false) @@ -13282,7 +14700,7 @@ void VmaBlockVectorDefragmentationContext::AddAllocation(VmaAllocation hAlloc, V m_Allocations.push_back(info); } -void VmaBlockVectorDefragmentationContext::Begin(bool overlappingMoveSupported) +void VmaBlockVectorDefragmentationContext::Begin(bool overlappingMoveSupported, VmaDefragmentationFlags flags) { const bool allAllocations = m_AllAllocations || m_Allocations.size() == m_pBlockVector->CalcAllocationCount(); @@ -13296,10 +14714,12 @@ void VmaBlockVectorDefragmentationContext::Begin(bool overlappingMoveSupported) - VMA_DEBUG_MARGIN is 0. - All allocations in this block vector are moveable. - There is no possibility of image/buffer granularity conflict. + - The defragmentation is not incremental */ if(VMA_DEBUG_MARGIN == 0 && allAllocations && - !m_pBlockVector->IsBufferImageGranularityConflictPossible()) + !m_pBlockVector->IsBufferImageGranularityConflictPossible() && + !(flags & VMA_DEFRAGMENTATION_FLAG_INCREMENTAL)) { m_pAlgorithm = vma_new(m_hAllocator, VmaDefragmentationAlgorithm_Fast)( m_hAllocator, m_pBlockVector, m_CurrFrameIndex, overlappingMoveSupported); @@ -13345,7 +14765,7 @@ VmaDefragmentationContext_T::~VmaDefragmentationContext_T() for(size_t i = m_CustomPoolContexts.size(); i--; ) { VmaBlockVectorDefragmentationContext* pBlockVectorCtx = m_CustomPoolContexts[i]; - pBlockVectorCtx->GetBlockVector()->DefragmentationEnd(pBlockVectorCtx, m_pStats); + pBlockVectorCtx->GetBlockVector()->DefragmentationEnd(pBlockVectorCtx, m_Flags, m_pStats); vma_delete(m_hAllocator, pBlockVectorCtx); } for(size_t i = m_hAllocator->m_MemProps.memoryTypeCount; i--; ) @@ -13353,13 +14773,13 @@ VmaDefragmentationContext_T::~VmaDefragmentationContext_T() VmaBlockVectorDefragmentationContext* pBlockVectorCtx = m_DefaultPoolContexts[i]; if(pBlockVectorCtx) { - pBlockVectorCtx->GetBlockVector()->DefragmentationEnd(pBlockVectorCtx, m_pStats); + pBlockVectorCtx->GetBlockVector()->DefragmentationEnd(pBlockVectorCtx, m_Flags, m_pStats); vma_delete(m_hAllocator, pBlockVectorCtx); } } } -void VmaDefragmentationContext_T::AddPools(uint32_t poolCount, VmaPool* pPools) +void VmaDefragmentationContext_T::AddPools(uint32_t poolCount, const VmaPool* pPools) { for(uint32_t poolIndex = 0; poolIndex < poolCount; ++poolIndex) { @@ -13369,7 +14789,7 @@ void VmaDefragmentationContext_T::AddPools(uint32_t poolCount, VmaPool* pPools) if(pool->m_BlockVector.GetAlgorithm() == 0) { VmaBlockVectorDefragmentationContext* pBlockVectorDefragCtx = VMA_NULL; - + for(size_t i = m_CustomPoolContexts.size(); i--; ) { if(m_CustomPoolContexts[i]->GetCustomPool() == pool) @@ -13378,15 +14798,14 @@ void VmaDefragmentationContext_T::AddPools(uint32_t poolCount, VmaPool* pPools) break; } } - + if(!pBlockVectorDefragCtx) { pBlockVectorDefragCtx = vma_new(m_hAllocator, VmaBlockVectorDefragmentationContext)( m_hAllocator, pool, &pool->m_BlockVector, - m_CurrFrameIndex, - m_Flags); + m_CurrFrameIndex); m_CustomPoolContexts.push_back(pBlockVectorDefragCtx); } @@ -13397,7 +14816,7 @@ void VmaDefragmentationContext_T::AddPools(uint32_t poolCount, VmaPool* pPools) void VmaDefragmentationContext_T::AddAllocations( uint32_t allocationCount, - VmaAllocation* pAllocations, + const VmaAllocation* pAllocations, VkBool32* pAllocationsChanged) { // Dispatch pAllocations among defragmentators. Create them when necessary. @@ -13433,8 +14852,7 @@ void VmaDefragmentationContext_T::AddAllocations( m_hAllocator, hAllocPool, &hAllocPool->m_BlockVector, - m_CurrFrameIndex, - m_Flags); + m_CurrFrameIndex); m_CustomPoolContexts.push_back(pBlockVectorDefragCtx); } } @@ -13450,8 +14868,7 @@ void VmaDefragmentationContext_T::AddAllocations( m_hAllocator, VMA_NULL, // hCustomPool m_hAllocator->m_pBlockVectors[memTypeIndex], - m_CurrFrameIndex, - m_Flags); + m_CurrFrameIndex); m_DefaultPoolContexts[memTypeIndex] = pBlockVectorDefragCtx; } } @@ -13469,13 +14886,30 @@ void VmaDefragmentationContext_T::AddAllocations( VkResult VmaDefragmentationContext_T::Defragment( VkDeviceSize maxCpuBytesToMove, uint32_t maxCpuAllocationsToMove, VkDeviceSize maxGpuBytesToMove, uint32_t maxGpuAllocationsToMove, - VkCommandBuffer commandBuffer, VmaDefragmentationStats* pStats) + VkCommandBuffer commandBuffer, VmaDefragmentationStats* pStats, VmaDefragmentationFlags flags) { if(pStats) { memset(pStats, 0, sizeof(VmaDefragmentationStats)); } + if(flags & VMA_DEFRAGMENTATION_FLAG_INCREMENTAL) + { + // For incremental defragmetnations, we just earmark how much we can move + // The real meat is in the defragmentation steps + m_MaxCpuBytesToMove = maxCpuBytesToMove; + m_MaxCpuAllocationsToMove = maxCpuAllocationsToMove; + + m_MaxGpuBytesToMove = maxGpuBytesToMove; + m_MaxGpuAllocationsToMove = maxGpuAllocationsToMove; + + if(m_MaxCpuBytesToMove == 0 && m_MaxCpuAllocationsToMove == 0 && + m_MaxGpuBytesToMove == 0 && m_MaxGpuAllocationsToMove == 0) + return VK_SUCCESS; + + return VK_NOT_READY; + } + if(commandBuffer == VK_NULL_HANDLE) { maxGpuBytesToMove = 0; @@ -13495,7 +14929,7 @@ VkResult VmaDefragmentationContext_T::Defragment( VMA_ASSERT(pBlockVectorCtx->GetBlockVector()); pBlockVectorCtx->GetBlockVector()->Defragment( pBlockVectorCtx, - pStats, + pStats, flags, maxCpuBytesToMove, maxCpuAllocationsToMove, maxGpuBytesToMove, maxGpuAllocationsToMove, commandBuffer); @@ -13515,7 +14949,7 @@ VkResult VmaDefragmentationContext_T::Defragment( VMA_ASSERT(pBlockVectorCtx && pBlockVectorCtx->GetBlockVector()); pBlockVectorCtx->GetBlockVector()->Defragment( pBlockVectorCtx, - pStats, + pStats, flags, maxCpuBytesToMove, maxCpuAllocationsToMove, maxGpuBytesToMove, maxGpuAllocationsToMove, commandBuffer); @@ -13528,6 +14962,132 @@ VkResult VmaDefragmentationContext_T::Defragment( return res; } +VkResult VmaDefragmentationContext_T::DefragmentPassBegin(VmaDefragmentationPassInfo* pInfo) +{ + VmaDefragmentationPassMoveInfo* pCurrentMove = pInfo->pMoves; + uint32_t movesLeft = pInfo->moveCount; + + // Process default pools. + for(uint32_t memTypeIndex = 0; + memTypeIndex < m_hAllocator->GetMemoryTypeCount(); + ++memTypeIndex) + { + VmaBlockVectorDefragmentationContext *pBlockVectorCtx = m_DefaultPoolContexts[memTypeIndex]; + if(pBlockVectorCtx) + { + VMA_ASSERT(pBlockVectorCtx->GetBlockVector()); + + if(!pBlockVectorCtx->hasDefragmentationPlan) + { + pBlockVectorCtx->GetBlockVector()->Defragment( + pBlockVectorCtx, + m_pStats, m_Flags, + m_MaxCpuBytesToMove, m_MaxCpuAllocationsToMove, + m_MaxGpuBytesToMove, m_MaxGpuAllocationsToMove, + VK_NULL_HANDLE); + + if(pBlockVectorCtx->res < VK_SUCCESS) + continue; + + pBlockVectorCtx->hasDefragmentationPlan = true; + } + + const uint32_t processed = pBlockVectorCtx->GetBlockVector()->ProcessDefragmentations( + pBlockVectorCtx, + pCurrentMove, movesLeft); + + movesLeft -= processed; + pCurrentMove += processed; + } + } + + // Process custom pools. + for(size_t customCtxIndex = 0, customCtxCount = m_CustomPoolContexts.size(); + customCtxIndex < customCtxCount; + ++customCtxIndex) + { + VmaBlockVectorDefragmentationContext *pBlockVectorCtx = m_CustomPoolContexts[customCtxIndex]; + VMA_ASSERT(pBlockVectorCtx && pBlockVectorCtx->GetBlockVector()); + + if(!pBlockVectorCtx->hasDefragmentationPlan) + { + pBlockVectorCtx->GetBlockVector()->Defragment( + pBlockVectorCtx, + m_pStats, m_Flags, + m_MaxCpuBytesToMove, m_MaxCpuAllocationsToMove, + m_MaxGpuBytesToMove, m_MaxGpuAllocationsToMove, + VK_NULL_HANDLE); + + if(pBlockVectorCtx->res < VK_SUCCESS) + continue; + + pBlockVectorCtx->hasDefragmentationPlan = true; + } + + const uint32_t processed = pBlockVectorCtx->GetBlockVector()->ProcessDefragmentations( + pBlockVectorCtx, + pCurrentMove, movesLeft); + + movesLeft -= processed; + pCurrentMove += processed; + } + + pInfo->moveCount = pInfo->moveCount - movesLeft; + + return VK_SUCCESS; +} +VkResult VmaDefragmentationContext_T::DefragmentPassEnd() +{ + VkResult res = VK_SUCCESS; + + // Process default pools. + for(uint32_t memTypeIndex = 0; + memTypeIndex < m_hAllocator->GetMemoryTypeCount(); + ++memTypeIndex) + { + VmaBlockVectorDefragmentationContext *pBlockVectorCtx = m_DefaultPoolContexts[memTypeIndex]; + if(pBlockVectorCtx) + { + VMA_ASSERT(pBlockVectorCtx->GetBlockVector()); + + if(!pBlockVectorCtx->hasDefragmentationPlan) + { + res = VK_NOT_READY; + continue; + } + + pBlockVectorCtx->GetBlockVector()->CommitDefragmentations( + pBlockVectorCtx, m_pStats); + + if(pBlockVectorCtx->defragmentationMoves.size() != pBlockVectorCtx->defragmentationMovesCommitted) + res = VK_NOT_READY; + } + } + + // Process custom pools. + for(size_t customCtxIndex = 0, customCtxCount = m_CustomPoolContexts.size(); + customCtxIndex < customCtxCount; + ++customCtxIndex) + { + VmaBlockVectorDefragmentationContext *pBlockVectorCtx = m_CustomPoolContexts[customCtxIndex]; + VMA_ASSERT(pBlockVectorCtx && pBlockVectorCtx->GetBlockVector()); + + if(!pBlockVectorCtx->hasDefragmentationPlan) + { + res = VK_NOT_READY; + continue; + } + + pBlockVectorCtx->GetBlockVector()->CommitDefragmentations( + pBlockVectorCtx, m_pStats); + + if(pBlockVectorCtx->defragmentationMoves.size() != pBlockVectorCtx->defragmentationMovesCommitted) + res = VK_NOT_READY; + } + + return res; +} + //////////////////////////////////////////////////////////////////////////////// // VmaRecorder @@ -13537,8 +15097,7 @@ VmaRecorder::VmaRecorder() : m_UseMutex(true), m_Flags(0), m_File(VMA_NULL), - m_Freq(INT64_MAX), - m_StartCounter(INT64_MAX) + m_RecordingStartTime(std::chrono::high_resolution_clock::now()) { } @@ -13547,19 +15106,27 @@ VkResult VmaRecorder::Init(const VmaRecordSettings& settings, bool useMutex) m_UseMutex = useMutex; m_Flags = settings.flags; - QueryPerformanceFrequency((LARGE_INTEGER*)&m_Freq); - QueryPerformanceCounter((LARGE_INTEGER*)&m_StartCounter); - +#if defined(_WIN32) // Open file for writing. errno_t err = fopen_s(&m_File, settings.pFilePath, "wb"); + if(err != 0) { return VK_ERROR_INITIALIZATION_FAILED; } +#else + // Open file for writing. + m_File = fopen(settings.pFilePath, "wb"); + + if(m_File == 0) + { + return VK_ERROR_INITIALIZATION_FAILED; + } +#endif // Write header. fprintf(m_File, "%s\n", "Vulkan Memory Allocator,Calls recording"); - fprintf(m_File, "%s\n", "1,5"); + fprintf(m_File, "%s\n", "1,8"); return VK_SUCCESS; } @@ -13755,20 +15322,6 @@ void VmaRecorder::RecordFreeMemoryPages(uint32_t frameIndex, Flush(); } -void VmaRecorder::RecordResizeAllocation( - uint32_t frameIndex, - VmaAllocation allocation, - VkDeviceSize newSize) -{ - CallParams callParams; - GetBasicParams(callParams); - - VmaMutexLock lock(m_FileMutex, m_UseMutex); - fprintf(m_File, "%u,%.3f,%u,vmaResizeAllocation,%p,%llu\n", callParams.threadId, callParams.time, frameIndex, - allocation, newSize); - Flush(); -} - void VmaRecorder::RecordSetAllocationUserData(uint32_t frameIndex, VmaAllocation allocation, const void* pUserData) @@ -14006,6 +15559,19 @@ void VmaRecorder::RecordDefragmentationEnd(uint32_t frameIndex, Flush(); } +void VmaRecorder::RecordSetPoolName(uint32_t frameIndex, + VmaPool pool, + const char* name) +{ + CallParams callParams; + GetBasicParams(callParams); + + VmaMutexLock lock(m_FileMutex, m_UseMutex); + fprintf(m_File, "%u,%.3f,%u,vmaSetPoolName,%p,%s\n", callParams.threadId, callParams.time, frameIndex, + pool, name != VMA_NULL ? name : ""); + Flush(); +} + VmaRecorder::UserDataString::UserDataString(VmaAllocationCreateFlags allocFlags, const void* pUserData) { if(pUserData != VMA_NULL) @@ -14016,7 +15582,8 @@ VmaRecorder::UserDataString::UserDataString(VmaAllocationCreateFlags allocFlags, } else { - sprintf_s(m_PtrStr, "%p", pUserData); + // If VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT is not specified, convert the string's memory address to a string and store it. + snprintf(m_PtrStr, 17, "%p", pUserData); m_Str = m_PtrStr; } } @@ -14029,10 +15596,16 @@ VmaRecorder::UserDataString::UserDataString(VmaAllocationCreateFlags allocFlags, void VmaRecorder::WriteConfiguration( const VkPhysicalDeviceProperties& devProps, const VkPhysicalDeviceMemoryProperties& memProps, - bool dedicatedAllocationExtensionEnabled) + uint32_t vulkanApiVersion, + bool dedicatedAllocationExtensionEnabled, + bool bindMemory2ExtensionEnabled, + bool memoryBudgetExtensionEnabled, + bool deviceCoherentMemoryExtensionEnabled) { fprintf(m_File, "Config,Begin\n"); + fprintf(m_File, "VulkanApiVersion,%u,%u\n", VK_VERSION_MAJOR(vulkanApiVersion), VK_VERSION_MINOR(vulkanApiVersion)); + fprintf(m_File, "PhysicalDevice,apiVersion,%u\n", devProps.apiVersion); fprintf(m_File, "PhysicalDevice,driverVersion,%u\n", devProps.driverVersion); fprintf(m_File, "PhysicalDevice,vendorID,%u\n", devProps.vendorID); @@ -14058,6 +15631,9 @@ void VmaRecorder::WriteConfiguration( } fprintf(m_File, "Extension,VK_KHR_dedicated_allocation,%u\n", dedicatedAllocationExtensionEnabled ? 1 : 0); + fprintf(m_File, "Extension,VK_KHR_bind_memory2,%u\n", bindMemory2ExtensionEnabled ? 1 : 0); + fprintf(m_File, "Extension,VK_EXT_memory_budget,%u\n", memoryBudgetExtensionEnabled ? 1 : 0); + fprintf(m_File, "Extension,VK_AMD_device_coherent_memory,%u\n", deviceCoherentMemoryExtensionEnabled ? 1 : 0); fprintf(m_File, "Macro,VMA_DEBUG_ALWAYS_DEDICATED_MEMORY,%u\n", VMA_DEBUG_ALWAYS_DEDICATED_MEMORY ? 1 : 0); fprintf(m_File, "Macro,VMA_DEBUG_ALIGNMENT,%llu\n", (VkDeviceSize)VMA_DEBUG_ALIGNMENT); @@ -14074,11 +15650,22 @@ void VmaRecorder::WriteConfiguration( void VmaRecorder::GetBasicParams(CallParams& outParams) { - outParams.threadId = GetCurrentThreadId(); + #if defined(_WIN32) + outParams.threadId = GetCurrentThreadId(); + #else + // Use C++11 features to get thread id and convert it to uint32_t. + // There is room for optimization since sstream is quite slow. + // Is there a better way to convert std::this_thread::get_id() to uint32_t? + std::thread::id thread_id = std::this_thread::get_id(); + std::stringstream thread_id_to_string_converter; + thread_id_to_string_converter << thread_id; + std::string thread_id_as_string = thread_id_to_string_converter.str(); + outParams.threadId = static_cast(std::stoi(thread_id_as_string.c_str())); + #endif - LARGE_INTEGER counter; - QueryPerformanceCounter(&counter); - outParams.time = (double)(counter.QuadPart - m_StartCounter) / (double)m_Freq; + auto current_time = std::chrono::high_resolution_clock::now(); + + outParams.time = std::chrono::duration(current_time - m_RecordingStartTime).count(); } void VmaRecorder::PrintPointerList(uint64_t count, const VmaAllocation* pItems) @@ -14111,10 +15698,10 @@ VmaAllocationObjectAllocator::VmaAllocationObjectAllocator(const VkAllocationCal { } -VmaAllocation VmaAllocationObjectAllocator::Allocate() +template VmaAllocation VmaAllocationObjectAllocator::Allocate(Types... args) { VmaMutexLock mutexLock(m_Mutex); - return m_Allocator.Alloc(); + return m_Allocator.Alloc(std::forward(args)...); } void VmaAllocationObjectAllocator::Free(VmaAllocation hAlloc) @@ -14128,50 +15715,102 @@ void VmaAllocationObjectAllocator::Free(VmaAllocation hAlloc) VmaAllocator_T::VmaAllocator_T(const VmaAllocatorCreateInfo* pCreateInfo) : m_UseMutex((pCreateInfo->flags & VMA_ALLOCATOR_CREATE_EXTERNALLY_SYNCHRONIZED_BIT) == 0), + m_VulkanApiVersion(pCreateInfo->vulkanApiVersion != 0 ? pCreateInfo->vulkanApiVersion : VK_API_VERSION_1_0), m_UseKhrDedicatedAllocation((pCreateInfo->flags & VMA_ALLOCATOR_CREATE_KHR_DEDICATED_ALLOCATION_BIT) != 0), + m_UseKhrBindMemory2((pCreateInfo->flags & VMA_ALLOCATOR_CREATE_KHR_BIND_MEMORY2_BIT) != 0), + m_UseExtMemoryBudget((pCreateInfo->flags & VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT) != 0), + m_UseAmdDeviceCoherentMemory((pCreateInfo->flags & VMA_ALLOCATOR_CREATE_AMD_DEVICE_COHERENT_MEMORY_BIT) != 0), + m_UseKhrBufferDeviceAddress((pCreateInfo->flags & VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT) != 0), + m_UseExtMemoryPriority((pCreateInfo->flags & VMA_ALLOCATOR_CREATE_EXT_MEMORY_PRIORITY_BIT) != 0), m_hDevice(pCreateInfo->device), + m_hInstance(pCreateInfo->instance), m_AllocationCallbacksSpecified(pCreateInfo->pAllocationCallbacks != VMA_NULL), m_AllocationCallbacks(pCreateInfo->pAllocationCallbacks ? *pCreateInfo->pAllocationCallbacks : VmaEmptyAllocationCallbacks), m_AllocationObjectAllocator(&m_AllocationCallbacks), + m_HeapSizeLimitMask(0), m_PreferredLargeHeapBlockSize(0), m_PhysicalDevice(pCreateInfo->physicalDevice), m_CurrentFrameIndex(0), + m_GpuDefragmentationMemoryTypeBits(UINT32_MAX), m_Pools(VmaStlAllocator(GetAllocationCallbacks())), - m_NextPoolId(0) + m_NextPoolId(0), + m_GlobalMemoryTypeBits(UINT32_MAX) #if VMA_RECORDING_ENABLED ,m_pRecorder(VMA_NULL) #endif { + if(m_VulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0)) + { + m_UseKhrDedicatedAllocation = false; + m_UseKhrBindMemory2 = false; + } + if(VMA_DEBUG_DETECT_CORRUPTION) { // Needs to be multiply of uint32_t size because we are going to write VMA_CORRUPTION_DETECTION_MAGIC_VALUE to it. VMA_ASSERT(VMA_DEBUG_MARGIN % sizeof(uint32_t) == 0); } - VMA_ASSERT(pCreateInfo->physicalDevice && pCreateInfo->device); + VMA_ASSERT(pCreateInfo->physicalDevice && pCreateInfo->device && pCreateInfo->instance); -#if !(VMA_DEDICATED_ALLOCATION) - if((pCreateInfo->flags & VMA_ALLOCATOR_CREATE_KHR_DEDICATED_ALLOCATION_BIT) != 0) + if(m_VulkanApiVersion < VK_MAKE_VERSION(1, 1, 0)) { - VMA_ASSERT(0 && "VMA_ALLOCATOR_CREATE_KHR_DEDICATED_ALLOCATION_BIT set but required extensions are disabled by preprocessor macros."); +#if !(VMA_DEDICATED_ALLOCATION) + if((pCreateInfo->flags & VMA_ALLOCATOR_CREATE_KHR_DEDICATED_ALLOCATION_BIT) != 0) + { + VMA_ASSERT(0 && "VMA_ALLOCATOR_CREATE_KHR_DEDICATED_ALLOCATION_BIT set but required extensions are disabled by preprocessor macros."); + } +#endif +#if !(VMA_BIND_MEMORY2) + if((pCreateInfo->flags & VMA_ALLOCATOR_CREATE_KHR_BIND_MEMORY2_BIT) != 0) + { + VMA_ASSERT(0 && "VMA_ALLOCATOR_CREATE_KHR_BIND_MEMORY2_BIT set but required extension is disabled by preprocessor macros."); + } +#endif + } +#if !(VMA_MEMORY_BUDGET) + if((pCreateInfo->flags & VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT) != 0) + { + VMA_ASSERT(0 && "VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT set but required extension is disabled by preprocessor macros."); + } +#endif +#if !(VMA_BUFFER_DEVICE_ADDRESS) + if(m_UseKhrBufferDeviceAddress) + { + VMA_ASSERT(0 && "VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT is set but required extension or Vulkan 1.2 is not available in your Vulkan header or its support in VMA has been disabled by a preprocessor macro."); + } +#endif +#if VMA_VULKAN_VERSION < 1002000 + if(m_VulkanApiVersion >= VK_MAKE_VERSION(1, 2, 0)) + { + VMA_ASSERT(0 && "vulkanApiVersion >= VK_API_VERSION_1_2 but required Vulkan version is disabled by preprocessor macros."); + } +#endif +#if VMA_VULKAN_VERSION < 1001000 + if(m_VulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0)) + { + VMA_ASSERT(0 && "vulkanApiVersion >= VK_API_VERSION_1_1 but required Vulkan version is disabled by preprocessor macros."); + } +#endif +#if !(VMA_MEMORY_PRIORITY) + if(m_UseExtMemoryPriority) + { + VMA_ASSERT(0 && "VMA_ALLOCATOR_CREATE_EXT_MEMORY_PRIORITY_BIT is set but required extension is not available in your Vulkan header or its support in VMA has been disabled by a preprocessor macro."); } #endif memset(&m_DeviceMemoryCallbacks, 0 ,sizeof(m_DeviceMemoryCallbacks)); memset(&m_PhysicalDeviceProperties, 0, sizeof(m_PhysicalDeviceProperties)); memset(&m_MemProps, 0, sizeof(m_MemProps)); - + memset(&m_pBlockVectors, 0, sizeof(m_pBlockVectors)); memset(&m_pDedicatedAllocations, 0, sizeof(m_pDedicatedAllocations)); - - for(uint32_t i = 0; i < VK_MAX_MEMORY_HEAPS; ++i) - { - m_HeapSizeLimit[i] = VK_WHOLE_SIZE; - } + memset(&m_VulkanFunctions, 0, sizeof(m_VulkanFunctions)); if(pCreateInfo->pDeviceMemoryCallbacks != VMA_NULL) { + m_DeviceMemoryCallbacks.pUserData = pCreateInfo->pDeviceMemoryCallbacks->pUserData; m_DeviceMemoryCallbacks.pfnAllocate = pCreateInfo->pDeviceMemoryCallbacks->pfnAllocate; m_DeviceMemoryCallbacks.pfnFree = pCreateInfo->pDeviceMemoryCallbacks->pfnFree; } @@ -14189,6 +15828,8 @@ VmaAllocator_T::VmaAllocator_T(const VmaAllocatorCreateInfo* pCreateInfo) : m_PreferredLargeHeapBlockSize = (pCreateInfo->preferredLargeHeapBlockSize != 0) ? pCreateInfo->preferredLargeHeapBlockSize : static_cast(VMA_DEFAULT_LARGE_HEAP_BLOCK_SIZE); + m_GlobalMemoryTypeBits = CalculateGlobalMemoryTypeBits(); + if(pCreateInfo->pHeapSizeLimit != VMA_NULL) { for(uint32_t heapIndex = 0; heapIndex < GetMemoryHeapCount(); ++heapIndex) @@ -14196,7 +15837,7 @@ VmaAllocator_T::VmaAllocator_T(const VmaAllocatorCreateInfo* pCreateInfo) : const VkDeviceSize limit = pCreateInfo->pHeapSizeLimit[heapIndex]; if(limit != VK_WHOLE_SIZE) { - m_HeapSizeLimit[heapIndex] = limit; + m_HeapSizeLimitMask |= 1u << heapIndex; if(limit < m_MemProps.memoryHeaps[heapIndex].size) { m_MemProps.memoryHeaps[heapIndex].size = limit; @@ -14218,9 +15859,9 @@ VmaAllocator_T::VmaAllocator_T(const VmaAllocatorCreateInfo* pCreateInfo) : SIZE_MAX, GetBufferImageGranularity(), pCreateInfo->frameInUseCount, - false, // isCustomPool false, // explicitBlockSize - false); // linearAlgorithm + false, // linearAlgorithm + 0.5f); // priority (0.5 is the default per Vulkan spec) // No need to call m_pBlockVectors[memTypeIndex][blockVectorTypeIndex]->CreateMinBlocks here, // becase minBlockCount is 0. m_pDedicatedAllocations[memTypeIndex] = vma_new(this, AllocationVectorType)(VmaStlAllocator(GetAllocationCallbacks())); @@ -14245,7 +15886,11 @@ VkResult VmaAllocator_T::Init(const VmaAllocatorCreateInfo* pCreateInfo) m_pRecorder->WriteConfiguration( m_PhysicalDeviceProperties, m_MemProps, - m_UseKhrDedicatedAllocation); + m_VulkanApiVersion, + m_UseKhrDedicatedAllocation, + m_UseKhrBindMemory2, + m_UseExtMemoryBudget, + m_UseAmdDeviceCoherentMemory); m_pRecorder->RecordCreateAllocator(GetCurrentFrameIndex()); #else VMA_ASSERT(0 && "VmaAllocatorCreateInfo::pRecordSettings used, but not supported due to VMA_RECORDING_ENABLED not defined to 1."); @@ -14253,6 +15898,13 @@ VkResult VmaAllocator_T::Init(const VmaAllocatorCreateInfo* pCreateInfo) #endif } +#if VMA_MEMORY_BUDGET + if(m_UseExtMemoryBudget) + { + UpdateVulkanBudget(); + } +#endif // #if VMA_MEMORY_BUDGET + return res; } @@ -14265,7 +15917,7 @@ VmaAllocator_T::~VmaAllocator_T() vma_delete(this, m_pRecorder); } #endif - + VMA_ASSERT(m_Pools.empty()); for(size_t i = GetMemoryTypeCount(); i--; ) @@ -14283,66 +15935,174 @@ VmaAllocator_T::~VmaAllocator_T() void VmaAllocator_T::ImportVulkanFunctions(const VmaVulkanFunctions* pVulkanFunctions) { #if VMA_STATIC_VULKAN_FUNCTIONS == 1 - m_VulkanFunctions.vkGetPhysicalDeviceProperties = &vkGetPhysicalDeviceProperties; - m_VulkanFunctions.vkGetPhysicalDeviceMemoryProperties = &vkGetPhysicalDeviceMemoryProperties; - m_VulkanFunctions.vkAllocateMemory = &vkAllocateMemory; - m_VulkanFunctions.vkFreeMemory = &vkFreeMemory; - m_VulkanFunctions.vkMapMemory = &vkMapMemory; - m_VulkanFunctions.vkUnmapMemory = &vkUnmapMemory; - m_VulkanFunctions.vkFlushMappedMemoryRanges = &vkFlushMappedMemoryRanges; - m_VulkanFunctions.vkInvalidateMappedMemoryRanges = &vkInvalidateMappedMemoryRanges; - m_VulkanFunctions.vkBindBufferMemory = &vkBindBufferMemory; - m_VulkanFunctions.vkBindImageMemory = &vkBindImageMemory; - m_VulkanFunctions.vkGetBufferMemoryRequirements = &vkGetBufferMemoryRequirements; - m_VulkanFunctions.vkGetImageMemoryRequirements = &vkGetImageMemoryRequirements; - m_VulkanFunctions.vkCreateBuffer = &vkCreateBuffer; - m_VulkanFunctions.vkDestroyBuffer = &vkDestroyBuffer; - m_VulkanFunctions.vkCreateImage = &vkCreateImage; - m_VulkanFunctions.vkDestroyImage = &vkDestroyImage; - m_VulkanFunctions.vkCmdCopyBuffer = &vkCmdCopyBuffer; -#if VMA_DEDICATED_ALLOCATION - if(m_UseKhrDedicatedAllocation) + ImportVulkanFunctions_Static(); +#endif + + if(pVulkanFunctions != VMA_NULL) { - m_VulkanFunctions.vkGetBufferMemoryRequirements2KHR = - (PFN_vkGetBufferMemoryRequirements2KHR)vkGetDeviceProcAddr(m_hDevice, "vkGetBufferMemoryRequirements2KHR"); - m_VulkanFunctions.vkGetImageMemoryRequirements2KHR = - (PFN_vkGetImageMemoryRequirements2KHR)vkGetDeviceProcAddr(m_hDevice, "vkGetImageMemoryRequirements2KHR"); + ImportVulkanFunctions_Custom(pVulkanFunctions); } -#endif // #if VMA_DEDICATED_ALLOCATION + +#if VMA_DYNAMIC_VULKAN_FUNCTIONS == 1 + ImportVulkanFunctions_Dynamic(); +#endif + + ValidateVulkanFunctions(); +} + +#if VMA_STATIC_VULKAN_FUNCTIONS == 1 + +void VmaAllocator_T::ImportVulkanFunctions_Static() +{ + // Vulkan 1.0 + m_VulkanFunctions.vkGetPhysicalDeviceProperties = (PFN_vkGetPhysicalDeviceProperties)vkGetPhysicalDeviceProperties; + m_VulkanFunctions.vkGetPhysicalDeviceMemoryProperties = (PFN_vkGetPhysicalDeviceMemoryProperties)vkGetPhysicalDeviceMemoryProperties; + m_VulkanFunctions.vkAllocateMemory = (PFN_vkAllocateMemory)vkAllocateMemory; + m_VulkanFunctions.vkFreeMemory = (PFN_vkFreeMemory)vkFreeMemory; + m_VulkanFunctions.vkMapMemory = (PFN_vkMapMemory)vkMapMemory; + m_VulkanFunctions.vkUnmapMemory = (PFN_vkUnmapMemory)vkUnmapMemory; + m_VulkanFunctions.vkFlushMappedMemoryRanges = (PFN_vkFlushMappedMemoryRanges)vkFlushMappedMemoryRanges; + m_VulkanFunctions.vkInvalidateMappedMemoryRanges = (PFN_vkInvalidateMappedMemoryRanges)vkInvalidateMappedMemoryRanges; + m_VulkanFunctions.vkBindBufferMemory = (PFN_vkBindBufferMemory)vkBindBufferMemory; + m_VulkanFunctions.vkBindImageMemory = (PFN_vkBindImageMemory)vkBindImageMemory; + m_VulkanFunctions.vkGetBufferMemoryRequirements = (PFN_vkGetBufferMemoryRequirements)vkGetBufferMemoryRequirements; + m_VulkanFunctions.vkGetImageMemoryRequirements = (PFN_vkGetImageMemoryRequirements)vkGetImageMemoryRequirements; + m_VulkanFunctions.vkCreateBuffer = (PFN_vkCreateBuffer)vkCreateBuffer; + m_VulkanFunctions.vkDestroyBuffer = (PFN_vkDestroyBuffer)vkDestroyBuffer; + m_VulkanFunctions.vkCreateImage = (PFN_vkCreateImage)vkCreateImage; + m_VulkanFunctions.vkDestroyImage = (PFN_vkDestroyImage)vkDestroyImage; + m_VulkanFunctions.vkCmdCopyBuffer = (PFN_vkCmdCopyBuffer)vkCmdCopyBuffer; + + // Vulkan 1.1 +#if VMA_VULKAN_VERSION >= 1001000 + if(m_VulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0)) + { + m_VulkanFunctions.vkGetBufferMemoryRequirements2KHR = (PFN_vkGetBufferMemoryRequirements2)vkGetBufferMemoryRequirements2; + m_VulkanFunctions.vkGetImageMemoryRequirements2KHR = (PFN_vkGetImageMemoryRequirements2)vkGetImageMemoryRequirements2; + m_VulkanFunctions.vkBindBufferMemory2KHR = (PFN_vkBindBufferMemory2)vkBindBufferMemory2; + m_VulkanFunctions.vkBindImageMemory2KHR = (PFN_vkBindImageMemory2)vkBindImageMemory2; + m_VulkanFunctions.vkGetPhysicalDeviceMemoryProperties2KHR = (PFN_vkGetPhysicalDeviceMemoryProperties2)vkGetPhysicalDeviceMemoryProperties2; + } +#endif +} + #endif // #if VMA_STATIC_VULKAN_FUNCTIONS == 1 +void VmaAllocator_T::ImportVulkanFunctions_Custom(const VmaVulkanFunctions* pVulkanFunctions) +{ + VMA_ASSERT(pVulkanFunctions != VMA_NULL); + #define VMA_COPY_IF_NOT_NULL(funcName) \ if(pVulkanFunctions->funcName != VMA_NULL) m_VulkanFunctions.funcName = pVulkanFunctions->funcName; - if(pVulkanFunctions != VMA_NULL) - { - VMA_COPY_IF_NOT_NULL(vkGetPhysicalDeviceProperties); - VMA_COPY_IF_NOT_NULL(vkGetPhysicalDeviceMemoryProperties); - VMA_COPY_IF_NOT_NULL(vkAllocateMemory); - VMA_COPY_IF_NOT_NULL(vkFreeMemory); - VMA_COPY_IF_NOT_NULL(vkMapMemory); - VMA_COPY_IF_NOT_NULL(vkUnmapMemory); - VMA_COPY_IF_NOT_NULL(vkFlushMappedMemoryRanges); - VMA_COPY_IF_NOT_NULL(vkInvalidateMappedMemoryRanges); - VMA_COPY_IF_NOT_NULL(vkBindBufferMemory); - VMA_COPY_IF_NOT_NULL(vkBindImageMemory); - VMA_COPY_IF_NOT_NULL(vkGetBufferMemoryRequirements); - VMA_COPY_IF_NOT_NULL(vkGetImageMemoryRequirements); - VMA_COPY_IF_NOT_NULL(vkCreateBuffer); - VMA_COPY_IF_NOT_NULL(vkDestroyBuffer); - VMA_COPY_IF_NOT_NULL(vkCreateImage); - VMA_COPY_IF_NOT_NULL(vkDestroyImage); - VMA_COPY_IF_NOT_NULL(vkCmdCopyBuffer); -#if VMA_DEDICATED_ALLOCATION - VMA_COPY_IF_NOT_NULL(vkGetBufferMemoryRequirements2KHR); - VMA_COPY_IF_NOT_NULL(vkGetImageMemoryRequirements2KHR); + VMA_COPY_IF_NOT_NULL(vkGetPhysicalDeviceProperties); + VMA_COPY_IF_NOT_NULL(vkGetPhysicalDeviceMemoryProperties); + VMA_COPY_IF_NOT_NULL(vkAllocateMemory); + VMA_COPY_IF_NOT_NULL(vkFreeMemory); + VMA_COPY_IF_NOT_NULL(vkMapMemory); + VMA_COPY_IF_NOT_NULL(vkUnmapMemory); + VMA_COPY_IF_NOT_NULL(vkFlushMappedMemoryRanges); + VMA_COPY_IF_NOT_NULL(vkInvalidateMappedMemoryRanges); + VMA_COPY_IF_NOT_NULL(vkBindBufferMemory); + VMA_COPY_IF_NOT_NULL(vkBindImageMemory); + VMA_COPY_IF_NOT_NULL(vkGetBufferMemoryRequirements); + VMA_COPY_IF_NOT_NULL(vkGetImageMemoryRequirements); + VMA_COPY_IF_NOT_NULL(vkCreateBuffer); + VMA_COPY_IF_NOT_NULL(vkDestroyBuffer); + VMA_COPY_IF_NOT_NULL(vkCreateImage); + VMA_COPY_IF_NOT_NULL(vkDestroyImage); + VMA_COPY_IF_NOT_NULL(vkCmdCopyBuffer); + +#if VMA_DEDICATED_ALLOCATION || VMA_VULKAN_VERSION >= 1001000 + VMA_COPY_IF_NOT_NULL(vkGetBufferMemoryRequirements2KHR); + VMA_COPY_IF_NOT_NULL(vkGetImageMemoryRequirements2KHR); +#endif + +#if VMA_BIND_MEMORY2 || VMA_VULKAN_VERSION >= 1001000 + VMA_COPY_IF_NOT_NULL(vkBindBufferMemory2KHR); + VMA_COPY_IF_NOT_NULL(vkBindImageMemory2KHR); +#endif + +#if VMA_MEMORY_BUDGET + VMA_COPY_IF_NOT_NULL(vkGetPhysicalDeviceMemoryProperties2KHR); #endif - } #undef VMA_COPY_IF_NOT_NULL +} - // If these asserts are hit, you must either #define VMA_STATIC_VULKAN_FUNCTIONS 1 - // or pass valid pointers as VmaAllocatorCreateInfo::pVulkanFunctions. +#if VMA_DYNAMIC_VULKAN_FUNCTIONS == 1 + +void VmaAllocator_T::ImportVulkanFunctions_Dynamic() +{ +#define VMA_FETCH_INSTANCE_FUNC(memberName, functionPointerType, functionNameString) \ + if(m_VulkanFunctions.memberName == VMA_NULL) \ + m_VulkanFunctions.memberName = \ + (functionPointerType)vkGetInstanceProcAddr(m_hInstance, functionNameString); +#define VMA_FETCH_DEVICE_FUNC(memberName, functionPointerType, functionNameString) \ + if(m_VulkanFunctions.memberName == VMA_NULL) \ + m_VulkanFunctions.memberName = \ + (functionPointerType)vkGetDeviceProcAddr(m_hDevice, functionNameString); + + VMA_FETCH_INSTANCE_FUNC(vkGetPhysicalDeviceProperties, PFN_vkGetPhysicalDeviceProperties, "vkGetPhysicalDeviceProperties"); + VMA_FETCH_INSTANCE_FUNC(vkGetPhysicalDeviceMemoryProperties, PFN_vkGetPhysicalDeviceMemoryProperties, "vkGetPhysicalDeviceMemoryProperties"); + VMA_FETCH_DEVICE_FUNC(vkAllocateMemory, PFN_vkAllocateMemory, "vkAllocateMemory"); + VMA_FETCH_DEVICE_FUNC(vkFreeMemory, PFN_vkFreeMemory, "vkFreeMemory"); + VMA_FETCH_DEVICE_FUNC(vkMapMemory, PFN_vkMapMemory, "vkMapMemory"); + VMA_FETCH_DEVICE_FUNC(vkUnmapMemory, PFN_vkUnmapMemory, "vkUnmapMemory"); + VMA_FETCH_DEVICE_FUNC(vkFlushMappedMemoryRanges, PFN_vkFlushMappedMemoryRanges, "vkFlushMappedMemoryRanges"); + VMA_FETCH_DEVICE_FUNC(vkInvalidateMappedMemoryRanges, PFN_vkInvalidateMappedMemoryRanges, "vkInvalidateMappedMemoryRanges"); + VMA_FETCH_DEVICE_FUNC(vkBindBufferMemory, PFN_vkBindBufferMemory, "vkBindBufferMemory"); + VMA_FETCH_DEVICE_FUNC(vkBindImageMemory, PFN_vkBindImageMemory, "vkBindImageMemory"); + VMA_FETCH_DEVICE_FUNC(vkGetBufferMemoryRequirements, PFN_vkGetBufferMemoryRequirements, "vkGetBufferMemoryRequirements"); + VMA_FETCH_DEVICE_FUNC(vkGetImageMemoryRequirements, PFN_vkGetImageMemoryRequirements, "vkGetImageMemoryRequirements"); + VMA_FETCH_DEVICE_FUNC(vkCreateBuffer, PFN_vkCreateBuffer, "vkCreateBuffer"); + VMA_FETCH_DEVICE_FUNC(vkDestroyBuffer, PFN_vkDestroyBuffer, "vkDestroyBuffer"); + VMA_FETCH_DEVICE_FUNC(vkCreateImage, PFN_vkCreateImage, "vkCreateImage"); + VMA_FETCH_DEVICE_FUNC(vkDestroyImage, PFN_vkDestroyImage, "vkDestroyImage"); + VMA_FETCH_DEVICE_FUNC(vkCmdCopyBuffer, PFN_vkCmdCopyBuffer, "vkCmdCopyBuffer"); + +#if VMA_VULKAN_VERSION >= 1001000 + if(m_VulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0)) + { + VMA_FETCH_DEVICE_FUNC(vkGetBufferMemoryRequirements2KHR, PFN_vkGetBufferMemoryRequirements2, "vkGetBufferMemoryRequirements2"); + VMA_FETCH_DEVICE_FUNC(vkGetImageMemoryRequirements2KHR, PFN_vkGetImageMemoryRequirements2, "vkGetImageMemoryRequirements2"); + VMA_FETCH_DEVICE_FUNC(vkBindBufferMemory2KHR, PFN_vkBindBufferMemory2, "vkBindBufferMemory2"); + VMA_FETCH_DEVICE_FUNC(vkBindImageMemory2KHR, PFN_vkBindImageMemory2, "vkBindImageMemory2"); + VMA_FETCH_INSTANCE_FUNC(vkGetPhysicalDeviceMemoryProperties2KHR, PFN_vkGetPhysicalDeviceMemoryProperties2, "vkGetPhysicalDeviceMemoryProperties2"); + } +#endif + +#if VMA_DEDICATED_ALLOCATION + if(m_UseKhrDedicatedAllocation) + { + VMA_FETCH_DEVICE_FUNC(vkGetBufferMemoryRequirements2KHR, PFN_vkGetBufferMemoryRequirements2KHR, "vkGetBufferMemoryRequirements2KHR"); + VMA_FETCH_DEVICE_FUNC(vkGetImageMemoryRequirements2KHR, PFN_vkGetImageMemoryRequirements2KHR, "vkGetImageMemoryRequirements2KHR"); + } +#endif + +#if VMA_BIND_MEMORY2 + if(m_UseKhrBindMemory2) + { + VMA_FETCH_DEVICE_FUNC(vkBindBufferMemory2KHR, PFN_vkBindBufferMemory2KHR, "vkBindBufferMemory2KHR"); + VMA_FETCH_DEVICE_FUNC(vkBindImageMemory2KHR, PFN_vkBindImageMemory2KHR, "vkBindImageMemory2KHR"); + } +#endif // #if VMA_BIND_MEMORY2 + +#if VMA_MEMORY_BUDGET + if(m_UseExtMemoryBudget) + { + VMA_FETCH_INSTANCE_FUNC(vkGetPhysicalDeviceMemoryProperties2KHR, PFN_vkGetPhysicalDeviceMemoryProperties2KHR, "vkGetPhysicalDeviceMemoryProperties2KHR"); + } +#endif // #if VMA_MEMORY_BUDGET + +#undef VMA_FETCH_DEVICE_FUNC +#undef VMA_FETCH_INSTANCE_FUNC +} + +#endif // #if VMA_DYNAMIC_VULKAN_FUNCTIONS == 1 + +void VmaAllocator_T::ValidateVulkanFunctions() +{ VMA_ASSERT(m_VulkanFunctions.vkGetPhysicalDeviceProperties != VMA_NULL); VMA_ASSERT(m_VulkanFunctions.vkGetPhysicalDeviceMemoryProperties != VMA_NULL); VMA_ASSERT(m_VulkanFunctions.vkAllocateMemory != VMA_NULL); @@ -14360,13 +16120,29 @@ void VmaAllocator_T::ImportVulkanFunctions(const VmaVulkanFunctions* pVulkanFunc VMA_ASSERT(m_VulkanFunctions.vkCreateImage != VMA_NULL); VMA_ASSERT(m_VulkanFunctions.vkDestroyImage != VMA_NULL); VMA_ASSERT(m_VulkanFunctions.vkCmdCopyBuffer != VMA_NULL); -#if VMA_DEDICATED_ALLOCATION - if(m_UseKhrDedicatedAllocation) + +#if VMA_DEDICATED_ALLOCATION || VMA_VULKAN_VERSION >= 1001000 + if(m_VulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0) || m_UseKhrDedicatedAllocation) { VMA_ASSERT(m_VulkanFunctions.vkGetBufferMemoryRequirements2KHR != VMA_NULL); VMA_ASSERT(m_VulkanFunctions.vkGetImageMemoryRequirements2KHR != VMA_NULL); } #endif + +#if VMA_BIND_MEMORY2 || VMA_VULKAN_VERSION >= 1001000 + if(m_VulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0) || m_UseKhrBindMemory2) + { + VMA_ASSERT(m_VulkanFunctions.vkBindBufferMemory2KHR != VMA_NULL); + VMA_ASSERT(m_VulkanFunctions.vkBindImageMemory2KHR != VMA_NULL); + } +#endif + +#if VMA_MEMORY_BUDGET || VMA_VULKAN_VERSION >= 1001000 + if(m_UseExtMemoryBudget || m_VulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0)) + { + VMA_ASSERT(m_VulkanFunctions.vkGetPhysicalDeviceMemoryProperties2KHR != VMA_NULL); + } +#endif } VkDeviceSize VmaAllocator_T::CalcPreferredBlockSize(uint32_t memTypeIndex) @@ -14374,7 +16150,7 @@ VkDeviceSize VmaAllocator_T::CalcPreferredBlockSize(uint32_t memTypeIndex) const uint32_t heapIndex = MemoryTypeIndexToHeapIndex(memTypeIndex); const VkDeviceSize heapSize = m_MemProps.memoryHeaps[heapIndex].size; const bool isSmallHeap = heapSize <= VMA_SMALL_HEAP_MAX_SIZE; - return isSmallHeap ? (heapSize / 8) : m_PreferredLargeHeapBlockSize; + return VmaAlignUp(isSmallHeap ? (heapSize / 8) : m_PreferredLargeHeapBlockSize, (VkDeviceSize)32); } VkResult VmaAllocator_T::AllocateMemoryOfType( @@ -14382,6 +16158,7 @@ VkResult VmaAllocator_T::AllocateMemoryOfType( VkDeviceSize alignment, bool dedicatedAllocation, VkBuffer dedicatedBuffer, + VkBufferUsageFlags dedicatedBufferUsage, VkImage dedicatedImage, const VmaAllocationCreateInfo& createInfo, uint32_t memTypeIndex, @@ -14400,6 +16177,11 @@ VkResult VmaAllocator_T::AllocateMemoryOfType( { finalCreateInfo.flags &= ~VMA_ALLOCATION_CREATE_MAPPED_BIT; } + // If memory is lazily allocated, it should be always dedicated. + if(finalCreateInfo.usage == VMA_MEMORY_USAGE_GPU_LAZILY_ALLOCATED) + { + finalCreateInfo.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT; + } VmaBlockVector* const blockVector = m_pBlockVectors[memTypeIndex]; VMA_ASSERT(blockVector); @@ -14430,10 +16212,13 @@ VkResult VmaAllocator_T::AllocateMemoryOfType( size, suballocType, memTypeIndex, + (finalCreateInfo.flags & VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT) != 0, (finalCreateInfo.flags & VMA_ALLOCATION_CREATE_MAPPED_BIT) != 0, (finalCreateInfo.flags & VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT) != 0, finalCreateInfo.pUserData, + finalCreateInfo.priority, dedicatedBuffer, + dedicatedBufferUsage, dedicatedImage, allocationCount, pAllocations); @@ -14465,10 +16250,13 @@ VkResult VmaAllocator_T::AllocateMemoryOfType( size, suballocType, memTypeIndex, + (finalCreateInfo.flags & VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT) != 0, (finalCreateInfo.flags & VMA_ALLOCATION_CREATE_MAPPED_BIT) != 0, (finalCreateInfo.flags & VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT) != 0, finalCreateInfo.pUserData, + finalCreateInfo.priority, dedicatedBuffer, + dedicatedBufferUsage, dedicatedImage, allocationCount, pAllocations); @@ -14492,40 +16280,85 @@ VkResult VmaAllocator_T::AllocateDedicatedMemory( VkDeviceSize size, VmaSuballocationType suballocType, uint32_t memTypeIndex, + bool withinBudget, bool map, bool isUserDataString, void* pUserData, + float priority, VkBuffer dedicatedBuffer, + VkBufferUsageFlags dedicatedBufferUsage, VkImage dedicatedImage, size_t allocationCount, VmaAllocation* pAllocations) { VMA_ASSERT(allocationCount > 0 && pAllocations); + if(withinBudget) + { + const uint32_t heapIndex = MemoryTypeIndexToHeapIndex(memTypeIndex); + VmaBudget heapBudget = {}; + GetBudget(&heapBudget, heapIndex, 1); + if(heapBudget.usage + size * allocationCount > heapBudget.budget) + { + return VK_ERROR_OUT_OF_DEVICE_MEMORY; + } + } + VkMemoryAllocateInfo allocInfo = { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO }; allocInfo.memoryTypeIndex = memTypeIndex; allocInfo.allocationSize = size; -#if VMA_DEDICATED_ALLOCATION +#if VMA_DEDICATED_ALLOCATION || VMA_VULKAN_VERSION >= 1001000 VkMemoryDedicatedAllocateInfoKHR dedicatedAllocInfo = { VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO_KHR }; - if(m_UseKhrDedicatedAllocation) + if(m_UseKhrDedicatedAllocation || m_VulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0)) { if(dedicatedBuffer != VK_NULL_HANDLE) { VMA_ASSERT(dedicatedImage == VK_NULL_HANDLE); dedicatedAllocInfo.buffer = dedicatedBuffer; - allocInfo.pNext = &dedicatedAllocInfo; + VmaPnextChainPushFront(&allocInfo, &dedicatedAllocInfo); } else if(dedicatedImage != VK_NULL_HANDLE) { dedicatedAllocInfo.image = dedicatedImage; - allocInfo.pNext = &dedicatedAllocInfo; + VmaPnextChainPushFront(&allocInfo, &dedicatedAllocInfo); } } -#endif // #if VMA_DEDICATED_ALLOCATION +#endif // #if VMA_DEDICATED_ALLOCATION || VMA_VULKAN_VERSION >= 1001000 + +#if VMA_BUFFER_DEVICE_ADDRESS + VkMemoryAllocateFlagsInfoKHR allocFlagsInfo = { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_FLAGS_INFO_KHR }; + if(m_UseKhrBufferDeviceAddress) + { + bool canContainBufferWithDeviceAddress = true; + if(dedicatedBuffer != VK_NULL_HANDLE) + { + canContainBufferWithDeviceAddress = dedicatedBufferUsage == UINT32_MAX || // Usage flags unknown + (dedicatedBufferUsage & VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT_EXT) != 0; + } + else if(dedicatedImage != VK_NULL_HANDLE) + { + canContainBufferWithDeviceAddress = false; + } + if(canContainBufferWithDeviceAddress) + { + allocFlagsInfo.flags = VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT_KHR; + VmaPnextChainPushFront(&allocInfo, &allocFlagsInfo); + } + } +#endif // #if VMA_BUFFER_DEVICE_ADDRESS + +#if VMA_MEMORY_PRIORITY + VkMemoryPriorityAllocateInfoEXT priorityInfo = { VK_STRUCTURE_TYPE_MEMORY_PRIORITY_ALLOCATE_INFO_EXT }; + if(m_UseExtMemoryPriority) + { + priorityInfo.priority = priority; + VmaPnextChainPushFront(&allocInfo, &priorityInfo); + } +#endif // #if VMA_MEMORY_PRIORITY size_t allocIndex; - VkResult res; + VkResult res = VK_SUCCESS; for(allocIndex = 0; allocIndex < allocationCount; ++allocIndex) { res = AllocateDedicatedMemoryPage( @@ -14565,7 +16398,7 @@ VkResult VmaAllocator_T::AllocateDedicatedMemory( { VmaAllocation currAlloc = pAllocations[allocIndex]; VkDeviceMemory hMemory = currAlloc->GetMemory(); - + /* There is no need to call this, because Vulkan spec allows to skip vkUnmapMemory before vkFreeMemory. @@ -14575,11 +16408,10 @@ VkResult VmaAllocator_T::AllocateDedicatedMemory( (*m_VulkanFunctions.vkUnmapMemory)(m_hDevice, hMemory); } */ - - FreeVulkanMemory(memTypeIndex, currAlloc->GetSize(), hMemory); + FreeVulkanMemory(memTypeIndex, currAlloc->GetSize(), hMemory); + m_Budget.RemoveAllocation(MemoryTypeIndexToHeapIndex(memTypeIndex), currAlloc->GetSize()); currAlloc->SetUserData(this, VMA_NULL); - currAlloc->Dtor(); m_AllocationObjectAllocator.Free(currAlloc); } @@ -14625,10 +16457,10 @@ VkResult VmaAllocator_T::AllocateDedicatedMemoryPage( } } - *pAllocation = m_AllocationObjectAllocator.Allocate(); - (*pAllocation)->Ctor(m_CurrentFrameIndex.load(), isUserDataString); + *pAllocation = m_AllocationObjectAllocator.Allocate(m_CurrentFrameIndex.load(), isUserDataString); (*pAllocation)->InitDedicatedAllocation(memTypeIndex, hMemory, suballocType, pMappedData, size); (*pAllocation)->SetUserData(this, pUserData); + m_Budget.AddAllocation(MemoryTypeIndexToHeapIndex(memTypeIndex), size); if(VMA_DEBUG_INITIALIZE_ALLOCATIONS) { FillAllocation(*pAllocation, VMA_ALLOCATION_FILL_PATTERN_CREATED); @@ -14643,8 +16475,8 @@ void VmaAllocator_T::GetBufferMemoryRequirements( bool& requiresDedicatedAllocation, bool& prefersDedicatedAllocation) const { -#if VMA_DEDICATED_ALLOCATION - if(m_UseKhrDedicatedAllocation) +#if VMA_DEDICATED_ALLOCATION || VMA_VULKAN_VERSION >= 1001000 + if(m_UseKhrDedicatedAllocation || m_VulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0)) { VkBufferMemoryRequirementsInfo2KHR memReqInfo = { VK_STRUCTURE_TYPE_BUFFER_MEMORY_REQUIREMENTS_INFO_2_KHR }; memReqInfo.buffer = hBuffer; @@ -14652,7 +16484,7 @@ void VmaAllocator_T::GetBufferMemoryRequirements( VkMemoryDedicatedRequirementsKHR memDedicatedReq = { VK_STRUCTURE_TYPE_MEMORY_DEDICATED_REQUIREMENTS_KHR }; VkMemoryRequirements2KHR memReq2 = { VK_STRUCTURE_TYPE_MEMORY_REQUIREMENTS_2_KHR }; - memReq2.pNext = &memDedicatedReq; + VmaPnextChainPushFront(&memReq2, &memDedicatedReq); (*m_VulkanFunctions.vkGetBufferMemoryRequirements2KHR)(m_hDevice, &memReqInfo, &memReq2); @@ -14661,7 +16493,7 @@ void VmaAllocator_T::GetBufferMemoryRequirements( prefersDedicatedAllocation = (memDedicatedReq.prefersDedicatedAllocation != VK_FALSE); } else -#endif // #if VMA_DEDICATED_ALLOCATION +#endif // #if VMA_DEDICATED_ALLOCATION || VMA_VULKAN_VERSION >= 1001000 { (*m_VulkanFunctions.vkGetBufferMemoryRequirements)(m_hDevice, hBuffer, &memReq); requiresDedicatedAllocation = false; @@ -14675,8 +16507,8 @@ void VmaAllocator_T::GetImageMemoryRequirements( bool& requiresDedicatedAllocation, bool& prefersDedicatedAllocation) const { -#if VMA_DEDICATED_ALLOCATION - if(m_UseKhrDedicatedAllocation) +#if VMA_DEDICATED_ALLOCATION || VMA_VULKAN_VERSION >= 1001000 + if(m_UseKhrDedicatedAllocation || m_VulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0)) { VkImageMemoryRequirementsInfo2KHR memReqInfo = { VK_STRUCTURE_TYPE_IMAGE_MEMORY_REQUIREMENTS_INFO_2_KHR }; memReqInfo.image = hImage; @@ -14684,7 +16516,7 @@ void VmaAllocator_T::GetImageMemoryRequirements( VkMemoryDedicatedRequirementsKHR memDedicatedReq = { VK_STRUCTURE_TYPE_MEMORY_DEDICATED_REQUIREMENTS_KHR }; VkMemoryRequirements2KHR memReq2 = { VK_STRUCTURE_TYPE_MEMORY_REQUIREMENTS_2_KHR }; - memReq2.pNext = &memDedicatedReq; + VmaPnextChainPushFront(&memReq2, &memDedicatedReq); (*m_VulkanFunctions.vkGetImageMemoryRequirements2KHR)(m_hDevice, &memReqInfo, &memReq2); @@ -14693,7 +16525,7 @@ void VmaAllocator_T::GetImageMemoryRequirements( prefersDedicatedAllocation = (memDedicatedReq.prefersDedicatedAllocation != VK_FALSE); } else -#endif // #if VMA_DEDICATED_ALLOCATION +#endif // #if VMA_DEDICATED_ALLOCATION || VMA_VULKAN_VERSION >= 1001000 { (*m_VulkanFunctions.vkGetImageMemoryRequirements)(m_hDevice, hImage, &memReq); requiresDedicatedAllocation = false; @@ -14706,6 +16538,7 @@ VkResult VmaAllocator_T::AllocateMemory( bool requiresDedicatedAllocation, bool prefersDedicatedAllocation, VkBuffer dedicatedBuffer, + VkBufferUsageFlags dedicatedBufferUsage, VkImage dedicatedImage, const VmaAllocationCreateInfo& createInfo, VmaSuballocationType suballocType, @@ -14757,11 +16590,20 @@ VkResult VmaAllocator_T::AllocateMemory( const VkDeviceSize alignmentForPool = VMA_MAX( vkMemReq.alignment, GetMemoryTypeMinAlignment(createInfo.pool->m_BlockVector.GetMemoryTypeIndex())); + + VmaAllocationCreateInfo createInfoForPool = createInfo; + // If memory type is not HOST_VISIBLE, disable MAPPED. + if((createInfoForPool.flags & VMA_ALLOCATION_CREATE_MAPPED_BIT) != 0 && + (m_MemProps.memoryTypes[createInfo.pool->m_BlockVector.GetMemoryTypeIndex()].propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) == 0) + { + createInfoForPool.flags &= ~VMA_ALLOCATION_CREATE_MAPPED_BIT; + } + return createInfo.pool->m_BlockVector.Allocate( m_CurrentFrameIndex.load(), vkMemReq.size, alignmentForPool, - createInfo, + createInfoForPool, suballocType, allocationCount, pAllocations); @@ -14783,6 +16625,7 @@ VkResult VmaAllocator_T::AllocateMemory( alignmentForMemType, requiresDedicatedAllocation || prefersDedicatedAllocation, dedicatedBuffer, + dedicatedBufferUsage, dedicatedImage, createInfo, memTypeIndex, @@ -14808,12 +16651,13 @@ VkResult VmaAllocator_T::AllocateMemory( alignmentForMemType = VMA_MAX( vkMemReq.alignment, GetMemoryTypeMinAlignment(memTypeIndex)); - + res = AllocateMemoryOfType( vkMemReq.size, alignmentForMemType, requiresDedicatedAllocation || prefersDedicatedAllocation, dedicatedBuffer, + dedicatedBufferUsage, dedicatedImage, createInfo, memTypeIndex, @@ -14887,8 +16731,9 @@ void VmaAllocator_T::FreeMemory( } } + // Do this regardless of whether the allocation is lost. Lost allocations still account to Budget.AllocationBytes. + m_Budget.RemoveAllocation(MemoryTypeIndexToHeapIndex(allocation->GetMemoryTypeIndex()), allocation->GetSize()); allocation->SetUserData(this, VMA_NULL); - allocation->Dtor(); m_AllocationObjectAllocator.Free(allocation); } } @@ -14898,6 +16743,7 @@ VkResult VmaAllocator_T::ResizeAllocation( const VmaAllocation alloc, VkDeviceSize newSize) { + // This function is deprecated and so it does nothing. It's left for backward compatibility. if(newSize == 0 || alloc->GetLastUseFrameIndex() == VMA_FRAME_INDEX_LOST) { return VK_ERROR_VALIDATION_FAILED_EXT; @@ -14906,26 +16752,7 @@ VkResult VmaAllocator_T::ResizeAllocation( { return VK_SUCCESS; } - - switch(alloc->GetType()) - { - case VmaAllocation_T::ALLOCATION_TYPE_DEDICATED: - return VK_ERROR_FEATURE_NOT_PRESENT; - case VmaAllocation_T::ALLOCATION_TYPE_BLOCK: - if(alloc->GetBlock()->m_pMetadata->ResizeAllocation(alloc, newSize)) - { - alloc->ChangeSize(newSize); - VMA_HEAVY_ASSERT(alloc->GetBlock()->m_pMetadata->Validate()); - return VK_SUCCESS; - } - else - { - return VK_ERROR_OUT_OF_POOL_MEMORY; - } - default: - VMA_ASSERT(0); - return VK_ERROR_VALIDATION_FAILED_EXT; - } + return VK_ERROR_OUT_OF_POOL_MEMORY; } void VmaAllocator_T::CalculateStats(VmaStats* pStats) @@ -14936,7 +16763,7 @@ void VmaAllocator_T::CalculateStats(VmaStats* pStats) InitStatInfo(pStats->memoryType[i]); for(size_t i = 0; i < VK_MAX_MEMORY_HEAPS; ++i) InitStatInfo(pStats->memoryHeap[i]); - + // Process default pools. for(uint32_t memTypeIndex = 0; memTypeIndex < GetMemoryTypeCount(); ++memTypeIndex) { @@ -14979,6 +16806,58 @@ void VmaAllocator_T::CalculateStats(VmaStats* pStats) VmaPostprocessCalcStatInfo(pStats->memoryHeap[i]); } +void VmaAllocator_T::GetBudget(VmaBudget* outBudget, uint32_t firstHeap, uint32_t heapCount) +{ +#if VMA_MEMORY_BUDGET + if(m_UseExtMemoryBudget) + { + if(m_Budget.m_OperationsSinceBudgetFetch < 30) + { + VmaMutexLockRead lockRead(m_Budget.m_BudgetMutex, m_UseMutex); + for(uint32_t i = 0; i < heapCount; ++i, ++outBudget) + { + const uint32_t heapIndex = firstHeap + i; + + outBudget->blockBytes = m_Budget.m_BlockBytes[heapIndex]; + outBudget->allocationBytes = m_Budget.m_AllocationBytes[heapIndex]; + + if(m_Budget.m_VulkanUsage[heapIndex] + outBudget->blockBytes > m_Budget.m_BlockBytesAtBudgetFetch[heapIndex]) + { + outBudget->usage = m_Budget.m_VulkanUsage[heapIndex] + + outBudget->blockBytes - m_Budget.m_BlockBytesAtBudgetFetch[heapIndex]; + } + else + { + outBudget->usage = 0; + } + + // Have to take MIN with heap size because explicit HeapSizeLimit is included in it. + outBudget->budget = VMA_MIN( + m_Budget.m_VulkanBudget[heapIndex], m_MemProps.memoryHeaps[heapIndex].size); + } + } + else + { + UpdateVulkanBudget(); // Outside of mutex lock + GetBudget(outBudget, firstHeap, heapCount); // Recursion + } + } + else +#endif + { + for(uint32_t i = 0; i < heapCount; ++i, ++outBudget) + { + const uint32_t heapIndex = firstHeap + i; + + outBudget->blockBytes = m_Budget.m_BlockBytes[heapIndex]; + outBudget->allocationBytes = m_Budget.m_AllocationBytes[heapIndex]; + + outBudget->usage = outBudget->blockBytes; + outBudget->budget = m_MemProps.memoryHeaps[heapIndex].size * 8 / 10; // 80% heuristics. + } + } +} + static const uint32_t VMA_VENDOR_ID_AMD = 4098; VkResult VmaAllocator_T::DefragmentationBegin( @@ -15001,7 +16880,7 @@ VkResult VmaAllocator_T::DefragmentationBegin( VkResult res = (*pContext)->Defragment( info.maxCpuBytesToMove, info.maxCpuAllocationsToMove, info.maxGpuBytesToMove, info.maxGpuAllocationsToMove, - info.commandBuffer, pStats); + info.commandBuffer, pStats, info.flags); if(res != VK_NOT_READY) { @@ -15019,6 +16898,19 @@ VkResult VmaAllocator_T::DefragmentationEnd( return VK_SUCCESS; } +VkResult VmaAllocator_T::DefragmentationPassBegin( + VmaDefragmentationPassInfo* pInfo, + VmaDefragmentationContext context) +{ + return context->DefragmentPassBegin(pInfo); +} +VkResult VmaAllocator_T::DefragmentationPassEnd( + VmaDefragmentationContext context) +{ + return context->DefragmentPassEnd(); + +} + void VmaAllocator_T::GetAllocationInfo(VmaAllocation hAllocation, VmaAllocationInfo* pAllocationInfo) { if(hAllocation->CanBecomeLost()) @@ -15157,6 +17049,12 @@ VkResult VmaAllocator_T::CreatePool(const VmaPoolCreateInfo* pCreateInfo, VmaPoo { return VK_ERROR_INITIALIZATION_FAILED; } + // Memory type index out of range or forbidden. + if(pCreateInfo->memoryTypeIndex >= GetMemoryTypeCount() || + ((1u << pCreateInfo->memoryTypeIndex) & m_GlobalMemoryTypeBits) == 0) + { + return VK_ERROR_FEATURE_NOT_PRESENT; + } const VkDeviceSize preferredBlockSize = CalcPreferredBlockSize(newCreateInfo.memoryTypeIndex); @@ -15200,6 +17098,13 @@ void VmaAllocator_T::GetPoolStats(VmaPool pool, VmaPoolStats* pPoolStats) void VmaAllocator_T::SetCurrentFrameIndex(uint32_t frameIndex) { m_CurrentFrameIndex.store(frameIndex); + +#if VMA_MEMORY_BUDGET + if(m_UseExtMemoryBudget) + { + UpdateVulkanBudget(); + } +#endif // #if VMA_MEMORY_BUDGET } void VmaAllocator_T::MakePoolAllocationsLost( @@ -15268,8 +17173,7 @@ VkResult VmaAllocator_T::CheckCorruption(uint32_t memoryTypeBits) void VmaAllocator_T::CreateLostAllocation(VmaAllocation* pAllocation) { - *pAllocation = m_AllocationObjectAllocator.Allocate(); - (*pAllocation)->Ctor(VMA_FRAME_INDEX_LOST, false); + *pAllocation = m_AllocationObjectAllocator.Allocate(VMA_FRAME_INDEX_LOST, false); (*pAllocation)->InitLost(); } @@ -15277,31 +17181,47 @@ VkResult VmaAllocator_T::AllocateVulkanMemory(const VkMemoryAllocateInfo* pAlloc { const uint32_t heapIndex = MemoryTypeIndexToHeapIndex(pAllocateInfo->memoryTypeIndex); - VkResult res; - if(m_HeapSizeLimit[heapIndex] != VK_WHOLE_SIZE) + // HeapSizeLimit is in effect for this heap. + if((m_HeapSizeLimitMask & (1u << heapIndex)) != 0) { - VmaMutexLock lock(m_HeapSizeLimitMutex, m_UseMutex); - if(m_HeapSizeLimit[heapIndex] >= pAllocateInfo->allocationSize) + const VkDeviceSize heapSize = m_MemProps.memoryHeaps[heapIndex].size; + VkDeviceSize blockBytes = m_Budget.m_BlockBytes[heapIndex]; + for(;;) { - res = (*m_VulkanFunctions.vkAllocateMemory)(m_hDevice, pAllocateInfo, GetAllocationCallbacks(), pMemory); - if(res == VK_SUCCESS) + const VkDeviceSize blockBytesAfterAllocation = blockBytes + pAllocateInfo->allocationSize; + if(blockBytesAfterAllocation > heapSize) { - m_HeapSizeLimit[heapIndex] -= pAllocateInfo->allocationSize; + return VK_ERROR_OUT_OF_DEVICE_MEMORY; + } + if(m_Budget.m_BlockBytes[heapIndex].compare_exchange_strong(blockBytes, blockBytesAfterAllocation)) + { + break; } - } - else - { - res = VK_ERROR_OUT_OF_DEVICE_MEMORY; } } else { - res = (*m_VulkanFunctions.vkAllocateMemory)(m_hDevice, pAllocateInfo, GetAllocationCallbacks(), pMemory); + m_Budget.m_BlockBytes[heapIndex] += pAllocateInfo->allocationSize; } - if(res == VK_SUCCESS && m_DeviceMemoryCallbacks.pfnAllocate != VMA_NULL) + // VULKAN CALL vkAllocateMemory. + VkResult res = (*m_VulkanFunctions.vkAllocateMemory)(m_hDevice, pAllocateInfo, GetAllocationCallbacks(), pMemory); + + if(res == VK_SUCCESS) { - (*m_DeviceMemoryCallbacks.pfnAllocate)(this, pAllocateInfo->memoryTypeIndex, *pMemory, pAllocateInfo->allocationSize); +#if VMA_MEMORY_BUDGET + ++m_Budget.m_OperationsSinceBudgetFetch; +#endif + + // Informative callback. + if(m_DeviceMemoryCallbacks.pfnAllocate != VMA_NULL) + { + (*m_DeviceMemoryCallbacks.pfnAllocate)(this, pAllocateInfo->memoryTypeIndex, *pMemory, pAllocateInfo->allocationSize, m_DeviceMemoryCallbacks.pUserData); + } + } + else + { + m_Budget.m_BlockBytes[heapIndex] -= pAllocateInfo->allocationSize; } return res; @@ -15309,18 +17229,77 @@ VkResult VmaAllocator_T::AllocateVulkanMemory(const VkMemoryAllocateInfo* pAlloc void VmaAllocator_T::FreeVulkanMemory(uint32_t memoryType, VkDeviceSize size, VkDeviceMemory hMemory) { + // Informative callback. if(m_DeviceMemoryCallbacks.pfnFree != VMA_NULL) { - (*m_DeviceMemoryCallbacks.pfnFree)(this, memoryType, hMemory, size); + (*m_DeviceMemoryCallbacks.pfnFree)(this, memoryType, hMemory, size, m_DeviceMemoryCallbacks.pUserData); } + // VULKAN CALL vkFreeMemory. (*m_VulkanFunctions.vkFreeMemory)(m_hDevice, hMemory, GetAllocationCallbacks()); - const uint32_t heapIndex = MemoryTypeIndexToHeapIndex(memoryType); - if(m_HeapSizeLimit[heapIndex] != VK_WHOLE_SIZE) + m_Budget.m_BlockBytes[MemoryTypeIndexToHeapIndex(memoryType)] -= size; +} + +VkResult VmaAllocator_T::BindVulkanBuffer( + VkDeviceMemory memory, + VkDeviceSize memoryOffset, + VkBuffer buffer, + const void* pNext) +{ + if(pNext != VMA_NULL) { - VmaMutexLock lock(m_HeapSizeLimitMutex, m_UseMutex); - m_HeapSizeLimit[heapIndex] += size; +#if VMA_VULKAN_VERSION >= 1001000 || VMA_BIND_MEMORY2 + if((m_UseKhrBindMemory2 || m_VulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0)) && + m_VulkanFunctions.vkBindBufferMemory2KHR != VMA_NULL) + { + VkBindBufferMemoryInfoKHR bindBufferMemoryInfo = { VK_STRUCTURE_TYPE_BIND_BUFFER_MEMORY_INFO_KHR }; + bindBufferMemoryInfo.pNext = pNext; + bindBufferMemoryInfo.buffer = buffer; + bindBufferMemoryInfo.memory = memory; + bindBufferMemoryInfo.memoryOffset = memoryOffset; + return (*m_VulkanFunctions.vkBindBufferMemory2KHR)(m_hDevice, 1, &bindBufferMemoryInfo); + } + else +#endif // #if VMA_VULKAN_VERSION >= 1001000 || VMA_BIND_MEMORY2 + { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } + } + else + { + return (*m_VulkanFunctions.vkBindBufferMemory)(m_hDevice, buffer, memory, memoryOffset); + } +} + +VkResult VmaAllocator_T::BindVulkanImage( + VkDeviceMemory memory, + VkDeviceSize memoryOffset, + VkImage image, + const void* pNext) +{ + if(pNext != VMA_NULL) + { +#if VMA_VULKAN_VERSION >= 1001000 || VMA_BIND_MEMORY2 + if((m_UseKhrBindMemory2 || m_VulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0)) && + m_VulkanFunctions.vkBindImageMemory2KHR != VMA_NULL) + { + VkBindImageMemoryInfoKHR bindBufferMemoryInfo = { VK_STRUCTURE_TYPE_BIND_IMAGE_MEMORY_INFO_KHR }; + bindBufferMemoryInfo.pNext = pNext; + bindBufferMemoryInfo.image = image; + bindBufferMemoryInfo.memory = memory; + bindBufferMemoryInfo.memoryOffset = memoryOffset; + return (*m_VulkanFunctions.vkBindImageMemory2KHR)(m_hDevice, 1, &bindBufferMemoryInfo); + } + else +#endif // #if VMA_BIND_MEMORY2 + { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } + } + else + { + return (*m_VulkanFunctions.vkBindImageMemory)(m_hDevice, image, memory, memoryOffset); } } @@ -15372,23 +17351,23 @@ void VmaAllocator_T::Unmap(VmaAllocation hAllocation) } } -VkResult VmaAllocator_T::BindBufferMemory(VmaAllocation hAllocation, VkBuffer hBuffer) +VkResult VmaAllocator_T::BindBufferMemory( + VmaAllocation hAllocation, + VkDeviceSize allocationLocalOffset, + VkBuffer hBuffer, + const void* pNext) { VkResult res = VK_SUCCESS; switch(hAllocation->GetType()) { case VmaAllocation_T::ALLOCATION_TYPE_DEDICATED: - res = GetVulkanFunctions().vkBindBufferMemory( - m_hDevice, - hBuffer, - hAllocation->GetMemory(), - 0); //memoryOffset + res = BindVulkanBuffer(hAllocation->GetMemory(), allocationLocalOffset, hBuffer, pNext); break; case VmaAllocation_T::ALLOCATION_TYPE_BLOCK: { - VmaDeviceMemoryBlock* pBlock = hAllocation->GetBlock(); + VmaDeviceMemoryBlock* const pBlock = hAllocation->GetBlock(); VMA_ASSERT(pBlock && "Binding buffer to allocation that doesn't belong to any block. Is the allocation lost?"); - res = pBlock->BindBufferMemory(this, hAllocation, hBuffer); + res = pBlock->BindBufferMemory(this, hAllocation, allocationLocalOffset, hBuffer, pNext); break; } default: @@ -15397,23 +17376,23 @@ VkResult VmaAllocator_T::BindBufferMemory(VmaAllocation hAllocation, VkBuffer hB return res; } -VkResult VmaAllocator_T::BindImageMemory(VmaAllocation hAllocation, VkImage hImage) +VkResult VmaAllocator_T::BindImageMemory( + VmaAllocation hAllocation, + VkDeviceSize allocationLocalOffset, + VkImage hImage, + const void* pNext) { VkResult res = VK_SUCCESS; switch(hAllocation->GetType()) { case VmaAllocation_T::ALLOCATION_TYPE_DEDICATED: - res = GetVulkanFunctions().vkBindImageMemory( - m_hDevice, - hImage, - hAllocation->GetMemory(), - 0); //memoryOffset + res = BindVulkanImage(hAllocation->GetMemory(), allocationLocalOffset, hImage, pNext); break; case VmaAllocation_T::ALLOCATION_TYPE_BLOCK: { VmaDeviceMemoryBlock* pBlock = hAllocation->GetBlock(); VMA_ASSERT(pBlock && "Binding image to allocation that doesn't belong to any block. Is the allocation lost?"); - res = pBlock->BindImageMemory(this, hAllocation, hImage); + res = pBlock->BindImageMemory(this, hAllocation, allocationLocalOffset, hImage, pNext); break; } default: @@ -15422,83 +17401,74 @@ VkResult VmaAllocator_T::BindImageMemory(VmaAllocation hAllocation, VkImage hIma return res; } -void VmaAllocator_T::FlushOrInvalidateAllocation( +VkResult VmaAllocator_T::FlushOrInvalidateAllocation( VmaAllocation hAllocation, VkDeviceSize offset, VkDeviceSize size, VMA_CACHE_OPERATION op) { - const uint32_t memTypeIndex = hAllocation->GetMemoryTypeIndex(); - if(size > 0 && IsMemoryTypeNonCoherent(memTypeIndex)) + VkResult res = VK_SUCCESS; + + VkMappedMemoryRange memRange = {}; + if(GetFlushOrInvalidateRange(hAllocation, offset, size, memRange)) { - const VkDeviceSize allocationSize = hAllocation->GetSize(); - VMA_ASSERT(offset <= allocationSize); - - const VkDeviceSize nonCoherentAtomSize = m_PhysicalDeviceProperties.limits.nonCoherentAtomSize; - - VkMappedMemoryRange memRange = { VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE }; - memRange.memory = hAllocation->GetMemory(); - - switch(hAllocation->GetType()) - { - case VmaAllocation_T::ALLOCATION_TYPE_DEDICATED: - memRange.offset = VmaAlignDown(offset, nonCoherentAtomSize); - if(size == VK_WHOLE_SIZE) - { - memRange.size = allocationSize - memRange.offset; - } - else - { - VMA_ASSERT(offset + size <= allocationSize); - memRange.size = VMA_MIN( - VmaAlignUp(size + (offset - memRange.offset), nonCoherentAtomSize), - allocationSize - memRange.offset); - } - break; - - case VmaAllocation_T::ALLOCATION_TYPE_BLOCK: - { - // 1. Still within this allocation. - memRange.offset = VmaAlignDown(offset, nonCoherentAtomSize); - if(size == VK_WHOLE_SIZE) - { - size = allocationSize - offset; - } - else - { - VMA_ASSERT(offset + size <= allocationSize); - } - memRange.size = VmaAlignUp(size + (offset - memRange.offset), nonCoherentAtomSize); - - // 2. Adjust to whole block. - const VkDeviceSize allocationOffset = hAllocation->GetOffset(); - VMA_ASSERT(allocationOffset % nonCoherentAtomSize == 0); - const VkDeviceSize blockSize = hAllocation->GetBlock()->m_pMetadata->GetSize(); - memRange.offset += allocationOffset; - memRange.size = VMA_MIN(memRange.size, blockSize - memRange.offset); - - break; - } - - default: - VMA_ASSERT(0); - } - switch(op) { case VMA_CACHE_FLUSH: - (*GetVulkanFunctions().vkFlushMappedMemoryRanges)(m_hDevice, 1, &memRange); + res = (*GetVulkanFunctions().vkFlushMappedMemoryRanges)(m_hDevice, 1, &memRange); break; case VMA_CACHE_INVALIDATE: - (*GetVulkanFunctions().vkInvalidateMappedMemoryRanges)(m_hDevice, 1, &memRange); + res = (*GetVulkanFunctions().vkInvalidateMappedMemoryRanges)(m_hDevice, 1, &memRange); break; default: VMA_ASSERT(0); } } // else: Just ignore this call. + return res; } -void VmaAllocator_T::FreeDedicatedMemory(VmaAllocation allocation) +VkResult VmaAllocator_T::FlushOrInvalidateAllocations( + uint32_t allocationCount, + const VmaAllocation* allocations, + const VkDeviceSize* offsets, const VkDeviceSize* sizes, + VMA_CACHE_OPERATION op) +{ + typedef VmaStlAllocator RangeAllocator; + typedef VmaSmallVector RangeVector; + RangeVector ranges = RangeVector(RangeAllocator(GetAllocationCallbacks())); + + for(uint32_t allocIndex = 0; allocIndex < allocationCount; ++allocIndex) + { + const VmaAllocation alloc = allocations[allocIndex]; + const VkDeviceSize offset = offsets != VMA_NULL ? offsets[allocIndex] : 0; + const VkDeviceSize size = sizes != VMA_NULL ? sizes[allocIndex] : VK_WHOLE_SIZE; + VkMappedMemoryRange newRange; + if(GetFlushOrInvalidateRange(alloc, offset, size, newRange)) + { + ranges.push_back(newRange); + } + } + + VkResult res = VK_SUCCESS; + if(!ranges.empty()) + { + switch(op) + { + case VMA_CACHE_FLUSH: + res = (*GetVulkanFunctions().vkFlushMappedMemoryRanges)(m_hDevice, (uint32_t)ranges.size(), ranges.data()); + break; + case VMA_CACHE_INVALIDATE: + res = (*GetVulkanFunctions().vkInvalidateMappedMemoryRanges)(m_hDevice, (uint32_t)ranges.size(), ranges.data()); + break; + default: + VMA_ASSERT(0); + } + } + // else: Just ignore this call. + return res; +} + +void VmaAllocator_T::FreeDedicatedMemory(const VmaAllocation allocation) { VMA_ASSERT(allocation && allocation->GetType() == VmaAllocation_T::ALLOCATION_TYPE_DEDICATED); @@ -15512,7 +17482,7 @@ void VmaAllocator_T::FreeDedicatedMemory(VmaAllocation allocation) } VkDeviceMemory hMemory = allocation->GetMemory(); - + /* There is no need to call this, because Vulkan spec allows to skip vkUnmapMemory before vkFreeMemory. @@ -15522,12 +17492,164 @@ void VmaAllocator_T::FreeDedicatedMemory(VmaAllocation allocation) (*m_VulkanFunctions.vkUnmapMemory)(m_hDevice, hMemory); } */ - + FreeVulkanMemory(memTypeIndex, allocation->GetSize(), hMemory); VMA_DEBUG_LOG(" Freed DedicatedMemory MemoryTypeIndex=%u", memTypeIndex); } +uint32_t VmaAllocator_T::CalculateGpuDefragmentationMemoryTypeBits() const +{ + VkBufferCreateInfo dummyBufCreateInfo; + VmaFillGpuDefragmentationBufferCreateInfo(dummyBufCreateInfo); + + uint32_t memoryTypeBits = 0; + + // Create buffer. + VkBuffer buf = VK_NULL_HANDLE; + VkResult res = (*GetVulkanFunctions().vkCreateBuffer)( + m_hDevice, &dummyBufCreateInfo, GetAllocationCallbacks(), &buf); + if(res == VK_SUCCESS) + { + // Query for supported memory types. + VkMemoryRequirements memReq; + (*GetVulkanFunctions().vkGetBufferMemoryRequirements)(m_hDevice, buf, &memReq); + memoryTypeBits = memReq.memoryTypeBits; + + // Destroy buffer. + (*GetVulkanFunctions().vkDestroyBuffer)(m_hDevice, buf, GetAllocationCallbacks()); + } + + return memoryTypeBits; +} + +uint32_t VmaAllocator_T::CalculateGlobalMemoryTypeBits() const +{ + // Make sure memory information is already fetched. + VMA_ASSERT(GetMemoryTypeCount() > 0); + + uint32_t memoryTypeBits = UINT32_MAX; + + if(!m_UseAmdDeviceCoherentMemory) + { + // Exclude memory types that have VK_MEMORY_PROPERTY_DEVICE_COHERENT_BIT_AMD. + for(uint32_t memTypeIndex = 0; memTypeIndex < GetMemoryTypeCount(); ++memTypeIndex) + { + if((m_MemProps.memoryTypes[memTypeIndex].propertyFlags & VK_MEMORY_PROPERTY_DEVICE_COHERENT_BIT_AMD_COPY) != 0) + { + memoryTypeBits &= ~(1u << memTypeIndex); + } + } + } + + return memoryTypeBits; +} + +bool VmaAllocator_T::GetFlushOrInvalidateRange( + VmaAllocation allocation, + VkDeviceSize offset, VkDeviceSize size, + VkMappedMemoryRange& outRange) const +{ + const uint32_t memTypeIndex = allocation->GetMemoryTypeIndex(); + if(size > 0 && IsMemoryTypeNonCoherent(memTypeIndex)) + { + const VkDeviceSize nonCoherentAtomSize = m_PhysicalDeviceProperties.limits.nonCoherentAtomSize; + const VkDeviceSize allocationSize = allocation->GetSize(); + VMA_ASSERT(offset <= allocationSize); + + outRange.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE; + outRange.pNext = VMA_NULL; + outRange.memory = allocation->GetMemory(); + + switch(allocation->GetType()) + { + case VmaAllocation_T::ALLOCATION_TYPE_DEDICATED: + outRange.offset = VmaAlignDown(offset, nonCoherentAtomSize); + if(size == VK_WHOLE_SIZE) + { + outRange.size = allocationSize - outRange.offset; + } + else + { + VMA_ASSERT(offset + size <= allocationSize); + outRange.size = VMA_MIN( + VmaAlignUp(size + (offset - outRange.offset), nonCoherentAtomSize), + allocationSize - outRange.offset); + } + break; + case VmaAllocation_T::ALLOCATION_TYPE_BLOCK: + { + // 1. Still within this allocation. + outRange.offset = VmaAlignDown(offset, nonCoherentAtomSize); + if(size == VK_WHOLE_SIZE) + { + size = allocationSize - offset; + } + else + { + VMA_ASSERT(offset + size <= allocationSize); + } + outRange.size = VmaAlignUp(size + (offset - outRange.offset), nonCoherentAtomSize); + + // 2. Adjust to whole block. + const VkDeviceSize allocationOffset = allocation->GetOffset(); + VMA_ASSERT(allocationOffset % nonCoherentAtomSize == 0); + const VkDeviceSize blockSize = allocation->GetBlock()->m_pMetadata->GetSize(); + outRange.offset += allocationOffset; + outRange.size = VMA_MIN(outRange.size, blockSize - outRange.offset); + + break; + } + default: + VMA_ASSERT(0); + } + return true; + } + return false; +} + +#if VMA_MEMORY_BUDGET + +void VmaAllocator_T::UpdateVulkanBudget() +{ + VMA_ASSERT(m_UseExtMemoryBudget); + + VkPhysicalDeviceMemoryProperties2KHR memProps = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_PROPERTIES_2_KHR }; + + VkPhysicalDeviceMemoryBudgetPropertiesEXT budgetProps = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_BUDGET_PROPERTIES_EXT }; + VmaPnextChainPushFront(&memProps, &budgetProps); + + GetVulkanFunctions().vkGetPhysicalDeviceMemoryProperties2KHR(m_PhysicalDevice, &memProps); + + { + VmaMutexLockWrite lockWrite(m_Budget.m_BudgetMutex, m_UseMutex); + + for(uint32_t heapIndex = 0; heapIndex < GetMemoryHeapCount(); ++heapIndex) + { + m_Budget.m_VulkanUsage[heapIndex] = budgetProps.heapUsage[heapIndex]; + m_Budget.m_VulkanBudget[heapIndex] = budgetProps.heapBudget[heapIndex]; + m_Budget.m_BlockBytesAtBudgetFetch[heapIndex] = m_Budget.m_BlockBytes[heapIndex].load(); + + // Some bugged drivers return the budget incorrectly, e.g. 0 or much bigger than heap size. + if(m_Budget.m_VulkanBudget[heapIndex] == 0) + { + m_Budget.m_VulkanBudget[heapIndex] = m_MemProps.memoryHeaps[heapIndex].size * 8 / 10; // 80% heuristics. + } + else if(m_Budget.m_VulkanBudget[heapIndex] > m_MemProps.memoryHeaps[heapIndex].size) + { + m_Budget.m_VulkanBudget[heapIndex] = m_MemProps.memoryHeaps[heapIndex].size; + } + if(m_Budget.m_VulkanUsage[heapIndex] == 0 && m_Budget.m_BlockBytesAtBudgetFetch[heapIndex] > 0) + { + m_Budget.m_VulkanUsage[heapIndex] = m_Budget.m_BlockBytesAtBudgetFetch[heapIndex]; + } + } + m_Budget.m_OperationsSinceBudgetFetch = 0; + } +} + +#endif // #if VMA_MEMORY_BUDGET + void VmaAllocator_T::FillAllocation(const VmaAllocation hAllocation, uint8_t pattern) { if(VMA_DEBUG_INITIALIZE_ALLOCATIONS && @@ -15549,6 +17671,17 @@ void VmaAllocator_T::FillAllocation(const VmaAllocation hAllocation, uint8_t pat } } +uint32_t VmaAllocator_T::GetGpuDefragmentationMemoryTypeBits() +{ + uint32_t memoryTypeBits = m_GpuDefragmentationMemoryTypeBits.load(); + if(memoryTypeBits == UINT32_MAX) + { + memoryTypeBits = CalculateGpuDefragmentationMemoryTypeBits(); + m_GpuDefragmentationMemoryTypeBits.store(memoryTypeBits); + } + return memoryTypeBits; +} + #if VMA_STATS_STRING_ENABLED void VmaAllocator_T::PrintDetailedMap(VmaJsonWriter& json) @@ -15571,7 +17704,7 @@ void VmaAllocator_T::PrintDetailedMap(VmaJsonWriter& json) json.BeginString("Type "); json.ContinueString(memTypeIndex); json.EndString(); - + json.BeginArray(); for(size_t i = 0; i < pDedicatedAllocVector->size(); ++i) @@ -15642,17 +17775,19 @@ void VmaAllocator_T::PrintDetailedMap(VmaJsonWriter& json) //////////////////////////////////////////////////////////////////////////////// // Public interface -VkResult vmaCreateAllocator( +VMA_CALL_PRE VkResult VMA_CALL_POST vmaCreateAllocator( const VmaAllocatorCreateInfo* pCreateInfo, VmaAllocator* pAllocator) { VMA_ASSERT(pCreateInfo && pAllocator); + VMA_ASSERT(pCreateInfo->vulkanApiVersion == 0 || + (VK_VERSION_MAJOR(pCreateInfo->vulkanApiVersion) == 1 && VK_VERSION_MINOR(pCreateInfo->vulkanApiVersion) <= 2)); VMA_DEBUG_LOG("vmaCreateAllocator"); *pAllocator = vma_new(pCreateInfo->pAllocationCallbacks, VmaAllocator_T)(pCreateInfo); return (*pAllocator)->Init(pCreateInfo); } -void vmaDestroyAllocator( +VMA_CALL_PRE void VMA_CALL_POST vmaDestroyAllocator( VmaAllocator allocator) { if(allocator != VK_NULL_HANDLE) @@ -15663,7 +17798,15 @@ void vmaDestroyAllocator( } } -void vmaGetPhysicalDeviceProperties( +VMA_CALL_PRE void VMA_CALL_POST vmaGetAllocatorInfo(VmaAllocator allocator, VmaAllocatorInfo* pAllocatorInfo) +{ + VMA_ASSERT(allocator && pAllocatorInfo); + pAllocatorInfo->instance = allocator->m_hInstance; + pAllocatorInfo->physicalDevice = allocator->GetPhysicalDevice(); + pAllocatorInfo->device = allocator->m_hDevice; +} + +VMA_CALL_PRE void VMA_CALL_POST vmaGetPhysicalDeviceProperties( VmaAllocator allocator, const VkPhysicalDeviceProperties **ppPhysicalDeviceProperties) { @@ -15671,7 +17814,7 @@ void vmaGetPhysicalDeviceProperties( *ppPhysicalDeviceProperties = &allocator->m_PhysicalDeviceProperties; } -void vmaGetMemoryProperties( +VMA_CALL_PRE void VMA_CALL_POST vmaGetMemoryProperties( VmaAllocator allocator, const VkPhysicalDeviceMemoryProperties** ppPhysicalDeviceMemoryProperties) { @@ -15679,7 +17822,7 @@ void vmaGetMemoryProperties( *ppPhysicalDeviceMemoryProperties = &allocator->m_MemProps; } -void vmaGetMemoryTypeProperties( +VMA_CALL_PRE void VMA_CALL_POST vmaGetMemoryTypeProperties( VmaAllocator allocator, uint32_t memoryTypeIndex, VkMemoryPropertyFlags* pFlags) @@ -15689,7 +17832,7 @@ void vmaGetMemoryTypeProperties( *pFlags = allocator->m_MemProps.memoryTypes[memoryTypeIndex].propertyFlags; } -void vmaSetCurrentFrameIndex( +VMA_CALL_PRE void VMA_CALL_POST vmaSetCurrentFrameIndex( VmaAllocator allocator, uint32_t frameIndex) { @@ -15701,7 +17844,7 @@ void vmaSetCurrentFrameIndex( allocator->SetCurrentFrameIndex(frameIndex); } -void vmaCalculateStats( +VMA_CALL_PRE void VMA_CALL_POST vmaCalculateStats( VmaAllocator allocator, VmaStats* pStats) { @@ -15710,9 +17853,18 @@ void vmaCalculateStats( allocator->CalculateStats(pStats); } +VMA_CALL_PRE void VMA_CALL_POST vmaGetBudget( + VmaAllocator allocator, + VmaBudget* pBudget) +{ + VMA_ASSERT(allocator && pBudget); + VMA_DEBUG_GLOBAL_MUTEX_LOCK + allocator->GetBudget(pBudget, 0, allocator->GetMemoryHeapCount()); +} + #if VMA_STATS_STRING_ENABLED -void vmaBuildStatsString( +VMA_CALL_PRE void VMA_CALL_POST vmaBuildStatsString( VmaAllocator allocator, char** ppStatsString, VkBool32 detailedMap) @@ -15725,12 +17877,15 @@ void vmaBuildStatsString( VmaJsonWriter json(allocator->GetAllocationCallbacks(), sb); json.BeginObject(); + VmaBudget budget[VK_MAX_MEMORY_HEAPS]; + allocator->GetBudget(budget, 0, allocator->GetMemoryHeapCount()); + VmaStats stats; allocator->CalculateStats(&stats); json.WriteString("Total"); VmaPrintStatInfo(json, stats.total); - + for(uint32_t heapIndex = 0; heapIndex < allocator->GetMemoryHeapCount(); ++heapIndex) { json.BeginString("Heap "); @@ -15749,6 +17904,20 @@ void vmaBuildStatsString( } json.EndArray(); + json.WriteString("Budget"); + json.BeginObject(); + { + json.WriteString("BlockBytes"); + json.WriteNumber(budget[heapIndex].blockBytes); + json.WriteString("AllocationBytes"); + json.WriteNumber(budget[heapIndex].allocationBytes); + json.WriteString("Usage"); + json.WriteNumber(budget[heapIndex].usage); + json.WriteString("Budget"); + json.WriteNumber(budget[heapIndex].budget); + } + json.EndObject(); + if(stats.memoryHeap[heapIndex].blockCount > 0) { json.WriteString("Stats"); @@ -15788,6 +17957,18 @@ void vmaBuildStatsString( { json.WriteString("LAZILY_ALLOCATED"); } + if((flags & VK_MEMORY_PROPERTY_PROTECTED_BIT) != 0) + { + json.WriteString(" PROTECTED"); + } + if((flags & VK_MEMORY_PROPERTY_DEVICE_COHERENT_BIT_AMD_COPY) != 0) + { + json.WriteString(" DEVICE_COHERENT"); + } + if((flags & VK_MEMORY_PROPERTY_DEVICE_UNCACHED_BIT_AMD_COPY) != 0) + { + json.WriteString(" DEVICE_UNCACHED"); + } json.EndArray(); if(stats.memoryType[typeIndex].blockCount > 0) @@ -15820,7 +18001,7 @@ void vmaBuildStatsString( *ppStatsString = pChars; } -void vmaFreeStatsString( +VMA_CALL_PRE void VMA_CALL_POST vmaFreeStatsString( VmaAllocator allocator, char* pStatsString) { @@ -15837,7 +18018,7 @@ void vmaFreeStatsString( /* This function is not protected by any mutex because it just reads immutable data. */ -VkResult vmaFindMemoryTypeIndex( +VMA_CALL_PRE VkResult VMA_CALL_POST vmaFindMemoryTypeIndex( VmaAllocator allocator, uint32_t memoryTypeBits, const VmaAllocationCreateInfo* pAllocationCreateInfo, @@ -15847,19 +18028,16 @@ VkResult vmaFindMemoryTypeIndex( VMA_ASSERT(pAllocationCreateInfo != VMA_NULL); VMA_ASSERT(pMemoryTypeIndex != VMA_NULL); + memoryTypeBits &= allocator->GetGlobalMemoryTypeBits(); + if(pAllocationCreateInfo->memoryTypeBits != 0) { memoryTypeBits &= pAllocationCreateInfo->memoryTypeBits; } - + uint32_t requiredFlags = pAllocationCreateInfo->requiredFlags; uint32_t preferredFlags = pAllocationCreateInfo->preferredFlags; - - const bool mapped = (pAllocationCreateInfo->flags & VMA_ALLOCATION_CREATE_MAPPED_BIT) != 0; - if(mapped) - { - preferredFlags |= VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT; - } + uint32_t notPreferredFlags = 0; // Convert usage to requiredFlags and preferredFlags. switch(pAllocationCreateInfo->usage) @@ -15884,12 +18062,26 @@ VkResult vmaFindMemoryTypeIndex( break; case VMA_MEMORY_USAGE_GPU_TO_CPU: requiredFlags |= VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT; - preferredFlags |= VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT; + preferredFlags |= VK_MEMORY_PROPERTY_HOST_CACHED_BIT; + break; + case VMA_MEMORY_USAGE_CPU_COPY: + notPreferredFlags |= VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; + break; + case VMA_MEMORY_USAGE_GPU_LAZILY_ALLOCATED: + requiredFlags |= VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT; break; default: + VMA_ASSERT(0); break; } + // Avoid DEVICE_COHERENT unless explicitly requested. + if(((pAllocationCreateInfo->requiredFlags | pAllocationCreateInfo->preferredFlags) & + (VK_MEMORY_PROPERTY_DEVICE_COHERENT_BIT_AMD_COPY | VK_MEMORY_PROPERTY_DEVICE_UNCACHED_BIT_AMD_COPY)) == 0) + { + notPreferredFlags |= VK_MEMORY_PROPERTY_DEVICE_COHERENT_BIT_AMD_COPY; + } + *pMemoryTypeIndex = UINT32_MAX; uint32_t minCost = UINT32_MAX; for(uint32_t memTypeIndex = 0, memTypeBit = 1; @@ -15905,7 +18097,8 @@ VkResult vmaFindMemoryTypeIndex( if((requiredFlags & ~currFlags) == 0) { // Calculate cost as number of bits from preferredFlags not present in this memory type. - uint32_t currCost = VmaCountBitsSet(preferredFlags & ~currFlags); + uint32_t currCost = VmaCountBitsSet(preferredFlags & ~currFlags) + + VmaCountBitsSet(currFlags & notPreferredFlags); // Remember memory type with lowest cost. if(currCost < minCost) { @@ -15922,7 +18115,7 @@ VkResult vmaFindMemoryTypeIndex( return (*pMemoryTypeIndex != UINT32_MAX) ? VK_SUCCESS : VK_ERROR_FEATURE_NOT_PRESENT; } -VkResult vmaFindMemoryTypeIndexForBufferInfo( +VMA_CALL_PRE VkResult VMA_CALL_POST vmaFindMemoryTypeIndexForBufferInfo( VmaAllocator allocator, const VkBufferCreateInfo* pBufferCreateInfo, const VmaAllocationCreateInfo* pAllocationCreateInfo, @@ -15955,7 +18148,7 @@ VkResult vmaFindMemoryTypeIndexForBufferInfo( return res; } -VkResult vmaFindMemoryTypeIndexForImageInfo( +VMA_CALL_PRE VkResult VMA_CALL_POST vmaFindMemoryTypeIndexForImageInfo( VmaAllocator allocator, const VkImageCreateInfo* pImageCreateInfo, const VmaAllocationCreateInfo* pAllocationCreateInfo, @@ -15988,44 +18181,44 @@ VkResult vmaFindMemoryTypeIndexForImageInfo( return res; } -VkResult vmaCreatePool( - VmaAllocator allocator, - const VmaPoolCreateInfo* pCreateInfo, - VmaPool* pPool) +VMA_CALL_PRE VkResult VMA_CALL_POST vmaCreatePool( + VmaAllocator allocator, + const VmaPoolCreateInfo* pCreateInfo, + VmaPool* pPool) { VMA_ASSERT(allocator && pCreateInfo && pPool); - + VMA_DEBUG_LOG("vmaCreatePool"); - + VMA_DEBUG_GLOBAL_MUTEX_LOCK - + VkResult res = allocator->CreatePool(pCreateInfo, pPool); - + #if VMA_RECORDING_ENABLED if(allocator->GetRecorder() != VMA_NULL) { allocator->GetRecorder()->RecordCreatePool(allocator->GetCurrentFrameIndex(), *pCreateInfo, *pPool); } #endif - + return res; } -void vmaDestroyPool( +VMA_CALL_PRE void VMA_CALL_POST vmaDestroyPool( VmaAllocator allocator, VmaPool pool) { VMA_ASSERT(allocator); - + if(pool == VK_NULL_HANDLE) { return; } - + VMA_DEBUG_LOG("vmaDestroyPool"); - + VMA_DEBUG_GLOBAL_MUTEX_LOCK - + #if VMA_RECORDING_ENABLED if(allocator->GetRecorder() != VMA_NULL) { @@ -16036,7 +18229,7 @@ void vmaDestroyPool( allocator->DestroyPool(pool); } -void vmaGetPoolStats( +VMA_CALL_PRE void VMA_CALL_POST vmaGetPoolStats( VmaAllocator allocator, VmaPool pool, VmaPoolStats* pPoolStats) @@ -16048,7 +18241,7 @@ void vmaGetPoolStats( allocator->GetPoolStats(pool, pPoolStats); } -void vmaMakePoolAllocationsLost( +VMA_CALL_PRE void VMA_CALL_POST vmaMakePoolAllocationsLost( VmaAllocator allocator, VmaPool pool, size_t* pLostAllocationCount) @@ -16067,7 +18260,7 @@ void vmaMakePoolAllocationsLost( allocator->MakePoolAllocationsLost(pool, pLostAllocationCount); } -VkResult vmaCheckPoolCorruption(VmaAllocator allocator, VmaPool pool) +VMA_CALL_PRE VkResult VMA_CALL_POST vmaCheckPoolCorruption(VmaAllocator allocator, VmaPool pool) { VMA_ASSERT(allocator && pool); @@ -16078,7 +18271,42 @@ VkResult vmaCheckPoolCorruption(VmaAllocator allocator, VmaPool pool) return allocator->CheckPoolCorruption(pool); } -VkResult vmaAllocateMemory( +VMA_CALL_PRE void VMA_CALL_POST vmaGetPoolName( + VmaAllocator allocator, + VmaPool pool, + const char** ppName) +{ + VMA_ASSERT(allocator && pool && ppName); + + VMA_DEBUG_LOG("vmaGetPoolName"); + + VMA_DEBUG_GLOBAL_MUTEX_LOCK + + *ppName = pool->GetName(); +} + +VMA_CALL_PRE void VMA_CALL_POST vmaSetPoolName( + VmaAllocator allocator, + VmaPool pool, + const char* pName) +{ + VMA_ASSERT(allocator && pool); + + VMA_DEBUG_LOG("vmaSetPoolName"); + + VMA_DEBUG_GLOBAL_MUTEX_LOCK + + pool->SetName(pName); + +#if VMA_RECORDING_ENABLED + if(allocator->GetRecorder() != VMA_NULL) + { + allocator->GetRecorder()->RecordSetPoolName(allocator->GetCurrentFrameIndex(), pool, pName); + } +#endif +} + +VMA_CALL_PRE VkResult VMA_CALL_POST vmaAllocateMemory( VmaAllocator allocator, const VkMemoryRequirements* pVkMemoryRequirements, const VmaAllocationCreateInfo* pCreateInfo, @@ -16091,11 +18319,12 @@ VkResult vmaAllocateMemory( VMA_DEBUG_GLOBAL_MUTEX_LOCK - VkResult result = allocator->AllocateMemory( + VkResult result = allocator->AllocateMemory( *pVkMemoryRequirements, false, // requiresDedicatedAllocation false, // prefersDedicatedAllocation VK_NULL_HANDLE, // dedicatedBuffer + UINT32_MAX, // dedicatedBufferUsage VK_NULL_HANDLE, // dedicatedImage *pCreateInfo, VMA_SUBALLOCATION_TYPE_UNKNOWN, @@ -16112,16 +18341,16 @@ VkResult vmaAllocateMemory( *pAllocation); } #endif - + if(pAllocationInfo != VMA_NULL && result == VK_SUCCESS) { allocator->GetAllocationInfo(*pAllocation, pAllocationInfo); } - return result; + return result; } -VkResult vmaAllocateMemoryPages( +VMA_CALL_PRE VkResult VMA_CALL_POST vmaAllocateMemoryPages( VmaAllocator allocator, const VkMemoryRequirements* pVkMemoryRequirements, const VmaAllocationCreateInfo* pCreateInfo, @@ -16140,11 +18369,12 @@ VkResult vmaAllocateMemoryPages( VMA_DEBUG_GLOBAL_MUTEX_LOCK - VkResult result = allocator->AllocateMemory( + VkResult result = allocator->AllocateMemory( *pVkMemoryRequirements, false, // requiresDedicatedAllocation false, // prefersDedicatedAllocation VK_NULL_HANDLE, // dedicatedBuffer + UINT32_MAX, // dedicatedBufferUsage VK_NULL_HANDLE, // dedicatedImage *pCreateInfo, VMA_SUBALLOCATION_TYPE_UNKNOWN, @@ -16162,7 +18392,7 @@ VkResult vmaAllocateMemoryPages( pAllocations); } #endif - + if(pAllocationInfo != VMA_NULL && result == VK_SUCCESS) { for(size_t i = 0; i < allocationCount; ++i) @@ -16171,10 +18401,10 @@ VkResult vmaAllocateMemoryPages( } } - return result; + return result; } -VkResult vmaAllocateMemoryForBuffer( +VMA_CALL_PRE VkResult VMA_CALL_POST vmaAllocateMemoryForBuffer( VmaAllocator allocator, VkBuffer buffer, const VmaAllocationCreateInfo* pCreateInfo, @@ -16199,6 +18429,7 @@ VkResult vmaAllocateMemoryForBuffer( requiresDedicatedAllocation, prefersDedicatedAllocation, buffer, // dedicatedBuffer + UINT32_MAX, // dedicatedBufferUsage VK_NULL_HANDLE, // dedicatedImage *pCreateInfo, VMA_SUBALLOCATION_TYPE_BUFFER, @@ -16223,10 +18454,10 @@ VkResult vmaAllocateMemoryForBuffer( allocator->GetAllocationInfo(*pAllocation, pAllocationInfo); } - return result; + return result; } -VkResult vmaAllocateMemoryForImage( +VMA_CALL_PRE VkResult VMA_CALL_POST vmaAllocateMemoryForImage( VmaAllocator allocator, VkImage image, const VmaAllocationCreateInfo* pCreateInfo, @@ -16250,6 +18481,7 @@ VkResult vmaAllocateMemoryForImage( requiresDedicatedAllocation, prefersDedicatedAllocation, VK_NULL_HANDLE, // dedicatedBuffer + UINT32_MAX, // dedicatedBufferUsage image, // dedicatedImage *pCreateInfo, VMA_SUBALLOCATION_TYPE_IMAGE_UNKNOWN, @@ -16274,22 +18506,22 @@ VkResult vmaAllocateMemoryForImage( allocator->GetAllocationInfo(*pAllocation, pAllocationInfo); } - return result; + return result; } -void vmaFreeMemory( +VMA_CALL_PRE void VMA_CALL_POST vmaFreeMemory( VmaAllocator allocator, VmaAllocation allocation) { VMA_ASSERT(allocator); - + if(allocation == VK_NULL_HANDLE) { return; } - + VMA_DEBUG_LOG("vmaFreeMemory"); - + VMA_DEBUG_GLOBAL_MUTEX_LOCK #if VMA_RECORDING_ENABLED @@ -16300,16 +18532,16 @@ void vmaFreeMemory( allocation); } #endif - + allocator->FreeMemory( 1, // allocationCount &allocation); } -void vmaFreeMemoryPages( +VMA_CALL_PRE void VMA_CALL_POST vmaFreeMemoryPages( VmaAllocator allocator, size_t allocationCount, - VmaAllocation* pAllocations) + const VmaAllocation* pAllocations) { if(allocationCount == 0) { @@ -16317,9 +18549,9 @@ void vmaFreeMemoryPages( } VMA_ASSERT(allocator); - + VMA_DEBUG_LOG("vmaFreeMemoryPages"); - + VMA_DEBUG_GLOBAL_MUTEX_LOCK #if VMA_RECORDING_ENABLED @@ -16331,35 +18563,25 @@ void vmaFreeMemoryPages( pAllocations); } #endif - + allocator->FreeMemory(allocationCount, pAllocations); } -VkResult vmaResizeAllocation( +VMA_CALL_PRE VkResult VMA_CALL_POST vmaResizeAllocation( VmaAllocator allocator, VmaAllocation allocation, VkDeviceSize newSize) { VMA_ASSERT(allocator && allocation); - + VMA_DEBUG_LOG("vmaResizeAllocation"); - + VMA_DEBUG_GLOBAL_MUTEX_LOCK -#if VMA_RECORDING_ENABLED - if(allocator->GetRecorder() != VMA_NULL) - { - allocator->GetRecorder()->RecordResizeAllocation( - allocator->GetCurrentFrameIndex(), - allocation, - newSize); - } -#endif - return allocator->ResizeAllocation(allocation, newSize); } -void vmaGetAllocationInfo( +VMA_CALL_PRE void VMA_CALL_POST vmaGetAllocationInfo( VmaAllocator allocator, VmaAllocation allocation, VmaAllocationInfo* pAllocationInfo) @@ -16380,7 +18602,7 @@ void vmaGetAllocationInfo( allocator->GetAllocationInfo(allocation, pAllocationInfo); } -VkBool32 vmaTouchAllocation( +VMA_CALL_PRE VkBool32 VMA_CALL_POST vmaTouchAllocation( VmaAllocator allocator, VmaAllocation allocation) { @@ -16400,7 +18622,7 @@ VkBool32 vmaTouchAllocation( return allocator->TouchAllocation(allocation); } -void vmaSetAllocationUserData( +VMA_CALL_PRE void VMA_CALL_POST vmaSetAllocationUserData( VmaAllocator allocator, VmaAllocation allocation, void* pUserData) @@ -16422,7 +18644,7 @@ void vmaSetAllocationUserData( #endif } -void vmaCreateLostAllocation( +VMA_CALL_PRE void VMA_CALL_POST vmaCreateLostAllocation( VmaAllocator allocator, VmaAllocation* pAllocation) { @@ -16442,7 +18664,7 @@ void vmaCreateLostAllocation( #endif } -VkResult vmaMapMemory( +VMA_CALL_PRE VkResult VMA_CALL_POST vmaMapMemory( VmaAllocator allocator, VmaAllocation allocation, void** ppData) @@ -16465,7 +18687,7 @@ VkResult vmaMapMemory( return res; } -void vmaUnmapMemory( +VMA_CALL_PRE void VMA_CALL_POST vmaUnmapMemory( VmaAllocator allocator, VmaAllocation allocation) { @@ -16485,7 +18707,7 @@ void vmaUnmapMemory( allocator->Unmap(allocation); } -void vmaFlushAllocation(VmaAllocator allocator, VmaAllocation allocation, VkDeviceSize offset, VkDeviceSize size) +VMA_CALL_PRE VkResult VMA_CALL_POST vmaFlushAllocation(VmaAllocator allocator, VmaAllocation allocation, VkDeviceSize offset, VkDeviceSize size) { VMA_ASSERT(allocator && allocation); @@ -16493,7 +18715,7 @@ void vmaFlushAllocation(VmaAllocator allocator, VmaAllocation allocation, VkDevi VMA_DEBUG_GLOBAL_MUTEX_LOCK - allocator->FlushOrInvalidateAllocation(allocation, offset, size, VMA_CACHE_FLUSH); + const VkResult res = allocator->FlushOrInvalidateAllocation(allocation, offset, size, VMA_CACHE_FLUSH); #if VMA_RECORDING_ENABLED if(allocator->GetRecorder() != VMA_NULL) @@ -16503,9 +18725,11 @@ void vmaFlushAllocation(VmaAllocator allocator, VmaAllocation allocation, VkDevi allocation, offset, size); } #endif + + return res; } -void vmaInvalidateAllocation(VmaAllocator allocator, VmaAllocation allocation, VkDeviceSize offset, VkDeviceSize size) +VMA_CALL_PRE VkResult VMA_CALL_POST vmaInvalidateAllocation(VmaAllocator allocator, VmaAllocation allocation, VkDeviceSize offset, VkDeviceSize size) { VMA_ASSERT(allocator && allocation); @@ -16513,7 +18737,7 @@ void vmaInvalidateAllocation(VmaAllocator allocator, VmaAllocation allocation, V VMA_DEBUG_GLOBAL_MUTEX_LOCK - allocator->FlushOrInvalidateAllocation(allocation, offset, size, VMA_CACHE_INVALIDATE); + const VkResult res = allocator->FlushOrInvalidateAllocation(allocation, offset, size, VMA_CACHE_INVALIDATE); #if VMA_RECORDING_ENABLED if(allocator->GetRecorder() != VMA_NULL) @@ -16523,9 +18747,75 @@ void vmaInvalidateAllocation(VmaAllocator allocator, VmaAllocation allocation, V allocation, offset, size); } #endif + + return res; } -VkResult vmaCheckCorruption(VmaAllocator allocator, uint32_t memoryTypeBits) +VMA_CALL_PRE VkResult VMA_CALL_POST vmaFlushAllocations( + VmaAllocator allocator, + uint32_t allocationCount, + const VmaAllocation* allocations, + const VkDeviceSize* offsets, + const VkDeviceSize* sizes) +{ + VMA_ASSERT(allocator); + + if(allocationCount == 0) + { + return VK_SUCCESS; + } + + VMA_ASSERT(allocations); + + VMA_DEBUG_LOG("vmaFlushAllocations"); + + VMA_DEBUG_GLOBAL_MUTEX_LOCK + + const VkResult res = allocator->FlushOrInvalidateAllocations(allocationCount, allocations, offsets, sizes, VMA_CACHE_FLUSH); + +#if VMA_RECORDING_ENABLED + if(allocator->GetRecorder() != VMA_NULL) + { + //TODO + } +#endif + + return res; +} + +VMA_CALL_PRE VkResult VMA_CALL_POST vmaInvalidateAllocations( + VmaAllocator allocator, + uint32_t allocationCount, + const VmaAllocation* allocations, + const VkDeviceSize* offsets, + const VkDeviceSize* sizes) +{ + VMA_ASSERT(allocator); + + if(allocationCount == 0) + { + return VK_SUCCESS; + } + + VMA_ASSERT(allocations); + + VMA_DEBUG_LOG("vmaInvalidateAllocations"); + + VMA_DEBUG_GLOBAL_MUTEX_LOCK + + const VkResult res = allocator->FlushOrInvalidateAllocations(allocationCount, allocations, offsets, sizes, VMA_CACHE_INVALIDATE); + +#if VMA_RECORDING_ENABLED + if(allocator->GetRecorder() != VMA_NULL) + { + //TODO + } +#endif + + return res; +} + +VMA_CALL_PRE VkResult VMA_CALL_POST vmaCheckCorruption(VmaAllocator allocator, uint32_t memoryTypeBits) { VMA_ASSERT(allocator); @@ -16536,9 +18826,9 @@ VkResult vmaCheckCorruption(VmaAllocator allocator, uint32_t memoryTypeBits) return allocator->CheckCorruption(memoryTypeBits); } -VkResult vmaDefragment( +VMA_CALL_PRE VkResult VMA_CALL_POST vmaDefragment( VmaAllocator allocator, - VmaAllocation* pAllocations, + const VmaAllocation* pAllocations, size_t allocationCount, VkBool32* pAllocationsChanged, const VmaDefragmentationInfo *pDefragmentationInfo, @@ -16571,7 +18861,7 @@ VkResult vmaDefragment( return res; } -VkResult vmaDefragmentationBegin( +VMA_CALL_PRE VkResult VMA_CALL_POST vmaDefragmentationBegin( VmaAllocator allocator, const VmaDefragmentationInfo2* pInfo, VmaDefragmentationStats* pStats, @@ -16607,7 +18897,7 @@ VkResult vmaDefragmentationBegin( return res; } -VkResult vmaDefragmentationEnd( +VMA_CALL_PRE VkResult VMA_CALL_POST vmaDefragmentationEnd( VmaAllocator allocator, VmaDefragmentationContext context) { @@ -16635,7 +18925,43 @@ VkResult vmaDefragmentationEnd( } } -VkResult vmaBindBufferMemory( +VMA_CALL_PRE VkResult VMA_CALL_POST vmaBeginDefragmentationPass( + VmaAllocator allocator, + VmaDefragmentationContext context, + VmaDefragmentationPassInfo* pInfo + ) +{ + VMA_ASSERT(allocator); + VMA_ASSERT(pInfo); + + VMA_DEBUG_LOG("vmaBeginDefragmentationPass"); + + VMA_DEBUG_GLOBAL_MUTEX_LOCK + + if(context == VK_NULL_HANDLE) + { + pInfo->moveCount = 0; + return VK_SUCCESS; + } + + return allocator->DefragmentationPassBegin(pInfo, context); +} +VMA_CALL_PRE VkResult VMA_CALL_POST vmaEndDefragmentationPass( + VmaAllocator allocator, + VmaDefragmentationContext context) +{ + VMA_ASSERT(allocator); + + VMA_DEBUG_LOG("vmaEndDefragmentationPass"); + VMA_DEBUG_GLOBAL_MUTEX_LOCK + + if(context == VK_NULL_HANDLE) + return VK_SUCCESS; + + return allocator->DefragmentationPassEnd(context); +} + +VMA_CALL_PRE VkResult VMA_CALL_POST vmaBindBufferMemory( VmaAllocator allocator, VmaAllocation allocation, VkBuffer buffer) @@ -16646,10 +18972,26 @@ VkResult vmaBindBufferMemory( VMA_DEBUG_GLOBAL_MUTEX_LOCK - return allocator->BindBufferMemory(allocation, buffer); + return allocator->BindBufferMemory(allocation, 0, buffer, VMA_NULL); } -VkResult vmaBindImageMemory( +VMA_CALL_PRE VkResult VMA_CALL_POST vmaBindBufferMemory2( + VmaAllocator allocator, + VmaAllocation allocation, + VkDeviceSize allocationLocalOffset, + VkBuffer buffer, + const void* pNext) +{ + VMA_ASSERT(allocator && allocation && buffer); + + VMA_DEBUG_LOG("vmaBindBufferMemory2"); + + VMA_DEBUG_GLOBAL_MUTEX_LOCK + + return allocator->BindBufferMemory(allocation, allocationLocalOffset, buffer, pNext); +} + +VMA_CALL_PRE VkResult VMA_CALL_POST vmaBindImageMemory( VmaAllocator allocator, VmaAllocation allocation, VkImage image) @@ -16660,10 +19002,26 @@ VkResult vmaBindImageMemory( VMA_DEBUG_GLOBAL_MUTEX_LOCK - return allocator->BindImageMemory(allocation, image); + return allocator->BindImageMemory(allocation, 0, image, VMA_NULL); } -VkResult vmaCreateBuffer( +VMA_CALL_PRE VkResult VMA_CALL_POST vmaBindImageMemory2( + VmaAllocator allocator, + VmaAllocation allocation, + VkDeviceSize allocationLocalOffset, + VkImage image, + const void* pNext) +{ + VMA_ASSERT(allocator && allocation && image); + + VMA_DEBUG_LOG("vmaBindImageMemory2"); + + VMA_DEBUG_GLOBAL_MUTEX_LOCK + + return allocator->BindImageMemory(allocation, allocationLocalOffset, image, pNext); +} + +VMA_CALL_PRE VkResult VMA_CALL_POST vmaCreateBuffer( VmaAllocator allocator, const VkBufferCreateInfo* pBufferCreateInfo, const VmaAllocationCreateInfo* pAllocationCreateInfo, @@ -16677,9 +19035,15 @@ VkResult vmaCreateBuffer( { return VK_ERROR_VALIDATION_FAILED_EXT; } - + if((pBufferCreateInfo->usage & VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT_COPY) != 0 && + !allocator->m_UseKhrBufferDeviceAddress) + { + VMA_ASSERT(0 && "Creating a buffer with VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT is not valid if VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT was not used."); + return VK_ERROR_VALIDATION_FAILED_EXT; + } + VMA_DEBUG_LOG("vmaCreateBuffer"); - + VMA_DEBUG_GLOBAL_MUTEX_LOCK *pBuffer = VK_NULL_HANDLE; @@ -16700,30 +19064,13 @@ VkResult vmaCreateBuffer( allocator->GetBufferMemoryRequirements(*pBuffer, vkMemReq, requiresDedicatedAllocation, prefersDedicatedAllocation); - // Make sure alignment requirements for specific buffer usages reported - // in Physical Device Properties are included in alignment reported by memory requirements. - if((pBufferCreateInfo->usage & VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT) != 0) - { - VMA_ASSERT(vkMemReq.alignment % - allocator->m_PhysicalDeviceProperties.limits.minTexelBufferOffsetAlignment == 0); - } - if((pBufferCreateInfo->usage & VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT) != 0) - { - VMA_ASSERT(vkMemReq.alignment % - allocator->m_PhysicalDeviceProperties.limits.minUniformBufferOffsetAlignment == 0); - } - if((pBufferCreateInfo->usage & VK_BUFFER_USAGE_STORAGE_BUFFER_BIT) != 0) - { - VMA_ASSERT(vkMemReq.alignment % - allocator->m_PhysicalDeviceProperties.limits.minStorageBufferOffsetAlignment == 0); - } - // 3. Allocate memory using allocator. res = allocator->AllocateMemory( vkMemReq, requiresDedicatedAllocation, prefersDedicatedAllocation, *pBuffer, // dedicatedBuffer + pBufferCreateInfo->usage, // dedicatedBufferUsage VK_NULL_HANDLE, // dedicatedImage *pAllocationCreateInfo, VMA_SUBALLOCATION_TYPE_BUFFER, @@ -16744,7 +19091,10 @@ VkResult vmaCreateBuffer( if(res >= 0) { // 3. Bind buffer with memory. - res = allocator->BindBufferMemory(*pAllocation, *pBuffer); + if((pAllocationCreateInfo->flags & VMA_ALLOCATION_CREATE_DONT_BIND_BIT) == 0) + { + res = allocator->BindBufferMemory(*pAllocation, 0, *pBuffer, VMA_NULL); + } if(res >= 0) { // All steps succeeded. @@ -16773,7 +19123,7 @@ VkResult vmaCreateBuffer( return res; } -void vmaDestroyBuffer( +VMA_CALL_PRE void VMA_CALL_POST vmaDestroyBuffer( VmaAllocator allocator, VkBuffer buffer, VmaAllocation allocation) @@ -16811,7 +19161,7 @@ void vmaDestroyBuffer( } } -VkResult vmaCreateImage( +VMA_CALL_PRE VkResult VMA_CALL_POST vmaCreateImage( VmaAllocator allocator, const VkImageCreateInfo* pImageCreateInfo, const VmaAllocationCreateInfo* pAllocationCreateInfo, @@ -16848,7 +19198,7 @@ VkResult vmaCreateImage( VmaSuballocationType suballocType = pImageCreateInfo->tiling == VK_IMAGE_TILING_OPTIMAL ? VMA_SUBALLOCATION_TYPE_IMAGE_OPTIMAL : VMA_SUBALLOCATION_TYPE_IMAGE_LINEAR; - + // 2. Allocate memory using allocator. VkMemoryRequirements vkMemReq = {}; bool requiresDedicatedAllocation = false; @@ -16861,6 +19211,7 @@ VkResult vmaCreateImage( requiresDedicatedAllocation, prefersDedicatedAllocation, VK_NULL_HANDLE, // dedicatedBuffer + UINT32_MAX, // dedicatedBufferUsage *pImage, // dedicatedImage *pAllocationCreateInfo, suballocType, @@ -16881,7 +19232,10 @@ VkResult vmaCreateImage( if(res >= 0) { // 3. Bind image with memory. - res = allocator->BindImageMemory(*pAllocation, *pImage); + if((pAllocationCreateInfo->flags & VMA_ALLOCATION_CREATE_DONT_BIND_BIT) == 0) + { + res = allocator->BindImageMemory(*pAllocation, 0, *pImage, VMA_NULL); + } if(res >= 0) { // All steps succeeded. @@ -16910,7 +19264,7 @@ VkResult vmaCreateImage( return res; } -void vmaDestroyImage( +VMA_CALL_PRE void VMA_CALL_POST vmaDestroyImage( VmaAllocator allocator, VkImage image, VmaAllocation allocation) diff --git a/Source/ThirdParty/recastnavigation/DetourNavMeshQuery.h b/Source/ThirdParty/recastnavigation/DetourNavMeshQuery.h index 1c23e4857..d9f940e2d 100644 --- a/Source/ThirdParty/recastnavigation/DetourNavMeshQuery.h +++ b/Source/ThirdParty/recastnavigation/DetourNavMeshQuery.h @@ -34,6 +34,7 @@ /// @ingroup detour class dtQueryFilter { +public: float m_areaCost[DT_MAX_AREAS]; ///< Cost per area type. (Used by default implementation.) unsigned short m_includeFlags; ///< Flags for polygons that can be visited. (Used by default implementation.) unsigned short m_excludeFlags; ///< Flags for polygons that should not be visted. (Used by default implementation.) diff --git a/Source/Tools/Flax.Build/Bindings/ApiTypeInfo.cs b/Source/Tools/Flax.Build/Bindings/ApiTypeInfo.cs index 60074883c..3e2ad1b31 100644 --- a/Source/Tools/Flax.Build/Bindings/ApiTypeInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/ApiTypeInfo.cs @@ -1,16 +1,17 @@ -// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved. +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using System.Collections.Generic; +using System.IO; namespace Flax.Build.Bindings { /// /// The native type information for bindings generator. /// - public class ApiTypeInfo + public class ApiTypeInfo : IBindingsCache { public ApiTypeInfo Parent; - public List Children; + public List Children = new List(); public string NativeName; public string Name; public string Namespace; @@ -22,6 +23,7 @@ namespace Flax.Build.Bindings public virtual bool IsClass => false; public virtual bool IsStruct => false; public virtual bool IsEnum => false; + public virtual bool IsInterface => false; public virtual bool IsValueType => false; public virtual bool IsScriptingObject => false; public virtual bool IsPod => false; @@ -90,6 +92,35 @@ namespace Flax.Build.Bindings Children.Add(apiTypeInfo); } + public virtual void Write(BinaryWriter writer) + { + BindingsGenerator.Write(writer, NativeName); + BindingsGenerator.Write(writer, Name); + BindingsGenerator.Write(writer, Namespace); + BindingsGenerator.Write(writer, Attributes); + BindingsGenerator.Write(writer, Comment); + writer.Write(IsInBuild); + BindingsGenerator.Write(writer, Children); + } + + public virtual void Read(BinaryReader reader) + { + NativeName = BindingsGenerator.Read(reader, NativeName); + Name = BindingsGenerator.Read(reader, Name); + Namespace = BindingsGenerator.Read(reader, Namespace); + Attributes = BindingsGenerator.Read(reader, Attributes); + Comment = BindingsGenerator.Read(reader, Comment); + IsInBuild = reader.ReadBoolean(); + Children = BindingsGenerator.Read(reader, Children); + + // Fix hierarchy + for (int i = 0; i < Children.Count; i++) + { + var child = Children[i]; + child.Parent = this; + } + } + public override string ToString() { return Name; diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Api.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Api.cs index d3ba0d0a9..82128eaa7 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Api.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Api.cs @@ -14,6 +14,7 @@ namespace Flax.Build.Bindings public const string Enum = "API_ENUM"; public const string Class = "API_CLASS"; public const string Struct = "API_STRUCT"; + public const string Interface = "API_INTERFACE"; public const string Function = "API_FUNCTION"; public const string Property = "API_PROPERTY"; public const string Field = "API_FIELD"; @@ -27,6 +28,7 @@ namespace Flax.Build.Bindings Enum, Class, Struct, + Interface, }; } diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs index 15d8825a8..d09d21bbd 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs @@ -66,6 +66,14 @@ namespace Flax.Build.Bindings return null; } + // Special case for Engine TEXT macro + if (value.Contains("TEXT(\"")) + return value.Replace("TEXT(\"", "(\""); + + // Special case for value constructors + if (value.Contains('(') && value.Contains(')')) + return "new " + value; + // Convert from C++ to C# switch (value) { @@ -124,8 +132,8 @@ namespace Flax.Build.Bindings return result; } - // ScriptingObjectReference or AssetReference or WeakAssetReference - if ((typeInfo.Type == "ScriptingObjectReference" || typeInfo.Type == "AssetReference" || typeInfo.Type == "WeakAssetReference") && typeInfo.GenericArgs != null) + // ScriptingObjectReference or AssetReference or WeakAssetReference or SoftObjectReference + if ((typeInfo.Type == "ScriptingObjectReference" || typeInfo.Type == "AssetReference" || typeInfo.Type == "WeakAssetReference" || typeInfo.Type == "SoftObjectReference") && typeInfo.GenericArgs != null) return typeInfo.GenericArgs[0].Type.Replace("::", "."); // Array or Span @@ -180,8 +188,8 @@ namespace Flax.Build.Bindings return "IntPtr"; } - // ScriptingObjectReference or AssetReference or WeakAssetReference - if ((typeInfo.Type == "ScriptingObjectReference" || typeInfo.Type == "AssetReference" || typeInfo.Type == "WeakAssetReference") && typeInfo.GenericArgs != null) + // ScriptingObjectReference or AssetReference or WeakAssetReference or SoftObjectReference + if ((typeInfo.Type == "ScriptingObjectReference" || typeInfo.Type == "AssetReference" || typeInfo.Type == "WeakAssetReference" || typeInfo.Type == "SoftObjectReference") && typeInfo.GenericArgs != null) return "IntPtr"; return GenerateCSharpNativeToManaged(buildData, typeInfo, caller); @@ -223,8 +231,8 @@ namespace Flax.Build.Bindings return "FlaxEngine.Object.GetUnmanagedPtr"; } - // ScriptingObjectReference or AssetReference or WeakAssetReference - if ((typeInfo.Type == "ScriptingObjectReference" || typeInfo.Type == "AssetReference" || typeInfo.Type == "WeakAssetReference") && typeInfo.GenericArgs != null) + // ScriptingObjectReference or AssetReference or WeakAssetReference or SoftObjectReference + if ((typeInfo.Type == "ScriptingObjectReference" || typeInfo.Type == "AssetReference" || typeInfo.Type == "WeakAssetReference" || typeInfo.Type == "SoftObjectReference") && typeInfo.GenericArgs != null) return "FlaxEngine.Object.GetUnmanagedPtr"; // Default @@ -386,14 +394,36 @@ namespace Flax.Build.Bindings } } - private static void GenerateCSharpAttributes(BuildData buildData, StringBuilder contents, string indent, string attributes, string[] comment, bool canUseTooltip, bool useUnmanaged) + private static bool IsDefaultValueSupported(string value) + { + // TEXT macro (eg. TEXT("text")) + // TODO: support string for default value attribute + if (value.Contains("TEXT(\"")) + return false; + + // Value constructors (eg. Vector2(1, 2)) + // TODO: support value constructors for default value attribute + if (value.Contains('(') && value.Contains(')')) + return false; + + // Constants (eg. Vector2::Zero) + // TODO: support constants for default value attribute + if (value.Contains("::")) + return false; + + return true; + } + + private static void GenerateCSharpAttributes(BuildData buildData, StringBuilder contents, string indent, ApiTypeInfo apiTypeInfo, string attributes, string[] comment, bool canUseTooltip, bool useUnmanaged, string defaultValue = null) { var writeTooltip = true; + var writeDefaultValue = true; if (!string.IsNullOrEmpty(attributes)) { // Write attributes contents.Append(indent).Append('[').Append(attributes).Append(']').AppendLine(); - writeTooltip = !attributes.Contains("Tooltip("); + writeTooltip = !attributes.Contains("Tooltip(") && !attributes.Contains("HideInEditor"); + writeDefaultValue = !attributes.Contains("DefaultValue("); } if (useUnmanaged) @@ -417,20 +447,27 @@ namespace Flax.Build.Bindings contents.Append(indent).Append("[Tooltip(\"").Append(tooltip).Append("\")]").AppendLine(); } } + if (!string.IsNullOrEmpty(defaultValue) && writeDefaultValue && IsDefaultValueSupported(defaultValue)) + { + // Write default value attribute + defaultValue = GenerateCSharpDefaultValueNativeToManaged(buildData, defaultValue, apiTypeInfo); + contents.Append(indent).Append("[DefaultValue(").Append(defaultValue).Append(")]").AppendLine(); + } } - private static void GenerateCSharpAttributes(BuildData buildData, StringBuilder contents, string indent, ApiTypeInfo apiTypeInfo, bool useUnmanaged) + private static void GenerateCSharpAttributes(BuildData buildData, StringBuilder contents, string indent, ApiTypeInfo apiTypeInfo, bool useUnmanaged, string defaultValue = null) { - GenerateCSharpAttributes(buildData, contents, indent, apiTypeInfo.Attributes, apiTypeInfo.Comment, true, useUnmanaged); + GenerateCSharpAttributes(buildData, contents, indent, apiTypeInfo, apiTypeInfo.Attributes, apiTypeInfo.Comment, true, useUnmanaged, defaultValue); } - private static void GenerateCSharpAttributes(BuildData buildData, StringBuilder contents, string indent, MemberInfo memberInfo, bool useUnmanaged) + private static void GenerateCSharpAttributes(BuildData buildData, StringBuilder contents, string indent, ApiTypeInfo apiTypeInfo, MemberInfo memberInfo, bool useUnmanaged, string defaultValue = null) { - GenerateCSharpAttributes(buildData, contents, indent, memberInfo.Attributes, memberInfo.Comment, true, useUnmanaged); + GenerateCSharpAttributes(buildData, contents, indent, apiTypeInfo, memberInfo.Attributes, memberInfo.Comment, true, useUnmanaged, defaultValue); } private static void GenerateCSharpClass(BuildData buildData, StringBuilder contents, string indent, ClassInfo classInfo) { + var useUnmanaged = classInfo.IsStatic || classInfo.IsScriptingObject; contents.AppendLine(); // Namespace begin @@ -446,7 +483,7 @@ namespace Flax.Build.Bindings GenerateCSharpComment(contents, indent, classInfo.Comment); // Class begin - GenerateCSharpAttributes(buildData, contents, indent, classInfo, true); + GenerateCSharpAttributes(buildData, contents, indent, classInfo, useUnmanaged); contents.Append(indent); if (classInfo.Access == AccessLevel.Public) contents.Append("public "); @@ -461,7 +498,7 @@ namespace Flax.Build.Bindings else if (classInfo.IsAbstract) contents.Append("abstract "); contents.Append("unsafe partial class ").Append(classInfo.Name); - if (classInfo.BaseType != null && classInfo.BaseTypeInheritance != AccessLevel.Private) + if (classInfo.BaseType != null && !classInfo.IsBaseTypeHidden) contents.Append(" : ").Append(GenerateCSharpNativeToManaged(buildData, classInfo.BaseType, classInfo)); contents.AppendLine(); contents.Append(indent + "{"); @@ -482,6 +519,10 @@ namespace Flax.Build.Bindings // Events foreach (var eventInfo in classInfo.Events) { + if (!useUnmanaged) + throw new NotImplementedException("TODO: support events inside non-static and non-scripting API class types."); + var paramsCount = eventInfo.Type.GenericArgs?.Count ?? 0; + contents.AppendLine(); foreach (var comment in eventInfo.Comment) @@ -491,7 +532,7 @@ namespace Flax.Build.Bindings contents.Append(indent).Append(comment.Replace("::", ".")).AppendLine(); } - GenerateCSharpAttributes(buildData, contents, indent, eventInfo, true); + GenerateCSharpAttributes(buildData, contents, indent, classInfo, eventInfo, useUnmanaged); contents.Append(indent); if (eventInfo.Access == AccessLevel.Public) contents.Append("public "); @@ -502,10 +543,10 @@ namespace Flax.Build.Bindings if (eventInfo.IsStatic) contents.Append("static "); string eventSignature = "event Action"; - if (eventInfo.Type.GenericArgs.Count != 0) + if (paramsCount != 0) { eventSignature += '<'; - for (var i = 0; i < eventInfo.Type.GenericArgs.Count; i++) + for (var i = 0; i < paramsCount; i++) { if (i != 0) eventSignature += ", "; @@ -543,7 +584,7 @@ namespace Flax.Build.Bindings if (eventInfo.IsStatic) contents.Append("static "); contents.Append($"void Internal_{eventInfo.Name}_Invoke("); - for (var i = 0; i < eventInfo.Type.GenericArgs.Count; i++) + for (var i = 0; i < paramsCount; i++) { if (i != 0) contents.Append(", "); @@ -554,7 +595,7 @@ namespace Flax.Build.Bindings contents.Append(indent).Append('{').AppendLine(); contents.Append(indent).Append(" Internal_").Append(eventInfo.Name); contents.Append('('); - for (var i = 0; i < eventInfo.Type.GenericArgs.Count; i++) + for (var i = 0; i < paramsCount; i++) { if (i != 0) contents.Append(", "); @@ -586,7 +627,7 @@ namespace Flax.Build.Bindings contents.Append(indent).Append(comment.Replace("::", ".")).AppendLine(); } - GenerateCSharpAttributes(buildData, contents, indent, fieldInfo, true); + GenerateCSharpAttributes(buildData, contents, indent, classInfo, fieldInfo, useUnmanaged, fieldInfo.DefaultValue); contents.Append(indent); if (fieldInfo.Access == AccessLevel.Public) contents.Append("public "); @@ -597,8 +638,19 @@ namespace Flax.Build.Bindings if (fieldInfo.IsStatic) contents.Append("static "); var returnValueType = GenerateCSharpNativeToManaged(buildData, fieldInfo.Type, classInfo); - contents.Append(returnValueType).Append(' ').AppendLine(fieldInfo.Name); - contents.AppendLine(indent + "{"); + contents.Append(returnValueType).Append(' ').Append(fieldInfo.Name); + if (!useUnmanaged) + { + if (fieldInfo.DefaultValue != null) + { + var defaultValue = GenerateCSharpDefaultValueNativeToManaged(buildData, fieldInfo.DefaultValue, classInfo); + if (!string.IsNullOrEmpty(defaultValue)) + contents.Append(" = ").Append(defaultValue); + } + contents.AppendLine(";"); + continue; + } + contents.AppendLine().AppendLine(indent + "{"); indent += " "; contents.Append(indent).Append("get { "); @@ -608,7 +660,7 @@ namespace Flax.Build.Bindings if (!fieldInfo.IsReadOnly) { contents.Append(indent).Append("set { "); - GenerateCSharpWrapperFunctionCall(buildData, contents, classInfo, fieldInfo.Setter, true); + GenerateCSharpWrapperFunctionCall(buildData, contents, classInfo, fieldInfo.Setter, useUnmanaged); contents.Append(" }").AppendLine(); } @@ -623,6 +675,9 @@ namespace Flax.Build.Bindings // Properties foreach (var propertyInfo in classInfo.Properties) { + if (!useUnmanaged) + throw new NotImplementedException("TODO: support properties inside non-static and non-scripting API class types."); + contents.AppendLine(); foreach (var comment in propertyInfo.Comment) @@ -632,7 +687,7 @@ namespace Flax.Build.Bindings contents.Append(indent).Append(comment.Replace("::", ".")).AppendLine(); } - GenerateCSharpAttributes(buildData, contents, indent, propertyInfo, true); + GenerateCSharpAttributes(buildData, contents, indent, classInfo, propertyInfo, useUnmanaged); contents.Append(indent); if (propertyInfo.Access == AccessLevel.Public) contents.Append("public "); @@ -678,11 +733,14 @@ namespace Flax.Build.Bindings // Functions foreach (var functionInfo in classInfo.Functions) { + if (!useUnmanaged) + throw new Exception($"Not supported function {functionInfo.Name} inside non-static and non-scripting class type {classInfo.Name}."); + if (!functionInfo.NoProxy) { contents.AppendLine(); GenerateCSharpComment(contents, indent, functionInfo.Comment); - GenerateCSharpAttributes(buildData, contents, indent, functionInfo, true); + GenerateCSharpAttributes(buildData, contents, indent, classInfo, functionInfo, true); contents.Append(indent); if (functionInfo.Access == AccessLevel.Public) contents.Append("public "); @@ -794,7 +852,7 @@ namespace Flax.Build.Bindings foreach (var comment in fieldInfo.Comment) contents.Append(indent).Append(comment).AppendLine(); - GenerateCSharpAttributes(buildData, contents, indent, fieldInfo, fieldInfo.IsStatic); + GenerateCSharpAttributes(buildData, contents, indent, structureInfo, fieldInfo, fieldInfo.IsStatic, fieldInfo.DefaultValue); contents.Append(indent); if (fieldInfo.Access == AccessLevel.Public) contents.Append("public "); @@ -818,7 +876,7 @@ namespace Flax.Build.Bindings contents.AppendLine(); foreach (var comment in fieldInfo.Comment) contents.Append(indent).Append(comment).AppendLine(); - GenerateCSharpAttributes(buildData, contents, indent, fieldInfo, fieldInfo.IsStatic); + GenerateCSharpAttributes(buildData, contents, indent, structureInfo, fieldInfo, fieldInfo.IsStatic); contents.Append(indent); if (fieldInfo.Access == AccessLevel.Public) contents.Append("public "); @@ -924,7 +982,7 @@ namespace Flax.Build.Bindings foreach (var comment in entryInfo.Comment) contents.Append(indent).Append(comment).AppendLine(); - GenerateCSharpAttributes(buildData, contents, indent, entryInfo.Attributes, entryInfo.Comment, true, false); + GenerateCSharpAttributes(buildData, contents, indent, enumInfo, entryInfo.Attributes, entryInfo.Comment, true, false); contents.Append(indent).Append(entryInfo.Name); if (!string.IsNullOrEmpty(entryInfo.Value)) contents.Append(" = ").Append(entryInfo.Value); @@ -970,9 +1028,6 @@ namespace Flax.Build.Bindings private static void GenerateCSharp(BuildData buildData, ModuleInfo moduleInfo, ref BindingsResult bindings) { - if (!bindings.UseBindings) - return; - var contents = new StringBuilder(); buildData.Modules.TryGetValue(moduleInfo.Module, out var moduleBuildInfo); @@ -1023,12 +1078,6 @@ namespace Flax.Build.Bindings // Save generated file contents.AppendLine("#endif"); Utilities.WriteFileIfChanged(bindings.GeneratedCSharpFilePath, contents.ToString()); - - // Ensure that generated file is included into build - if (useBindings && moduleBuildInfo != null && !moduleBuildInfo.SourceFiles.Contains(bindings.GeneratedCSharpFilePath)) - { - moduleBuildInfo.SourceFiles.Add(bindings.GeneratedCSharpFilePath); - } } internal struct GuidInterop diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cache.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cache.cs new file mode 100644 index 000000000..bd3cd2c2e --- /dev/null +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cache.cs @@ -0,0 +1,266 @@ +// (c) 2012-2020 Wojciech Figat. All rights reserved. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using Flax.Build.NativeCpp; + +namespace Flax.Build.Bindings +{ + internal interface IBindingsCache + { + void Write(BinaryWriter writer); + void Read(BinaryReader reader); + } + + partial class BindingsGenerator + { + private static readonly Dictionary _typeCache = new Dictionary(); + + internal static void Write(BinaryWriter writer, string e) + { + var valid = e != null; + writer.Write(valid); + if (valid) + writer.Write(e); + } + + internal static void Write(BinaryWriter writer, string[] list) + { + if (list != null) + { + writer.Write(list.Length); + for (int i = 0; i < list.Length; i++) + writer.Write(list[i]); + } + else + { + writer.Write(0); + } + } + + internal static void Write(BinaryWriter writer, IEnumerable list) + { + if (list != null) + { + writer.Write(list.Count()); + foreach (var e in list) + writer.Write(e); + } + else + { + writer.Write(0); + } + } + + internal static void Write(BinaryWriter writer, T e) where T : IBindingsCache + { + if (e != null) + { + writer.Write(e.GetType().FullName); + e.Write(writer); + } + else + { + writer.Write(string.Empty); + } + } + + internal static void Write(BinaryWriter writer, IList list) where T : IBindingsCache + { + if (list != null) + { + var count = list.Count; + writer.Write(count); + for (int i = 0; i < count; i++) + { + var e = list[i]; + writer.Write(e.GetType().FullName); + e.Write(writer); + } + } + else + { + writer.Write(0); + } + } + + internal static string Read(BinaryReader reader, string e) + { + var valid = reader.ReadBoolean(); + if (valid) + e = reader.ReadString(); + return e; + } + + internal static string[] Read(BinaryReader reader, string[] list) + { + var count = reader.ReadInt32(); + if (count != 0) + { + list = new string[count]; + for (int i = 0; i < count; i++) + list[i] = reader.ReadString(); + } + return list; + } + + internal static T Read(BinaryReader reader, T e) where T : IBindingsCache + { + var typename = reader.ReadString(); + if (string.IsNullOrEmpty(typename)) + return e; + if (!_typeCache.TryGetValue(typename, out var type)) + { + type = Builder.BuildTypes.FirstOrDefault(x => x.FullName == typename); + if (type == null) + { + var msg = $"Missing type {typename}."; + Log.Error(msg); + throw new Exception(msg); + } + _typeCache.Add(typename, type); + } + e = (T)Activator.CreateInstance(type); + e.Read(reader); + return e; + } + + internal static List Read(BinaryReader reader, List list) where T : IBindingsCache + { + var count = reader.ReadInt32(); + if (count != 0) + { + list = new List(count); + for (int i = 0; i < count; i++) + { + var typename = reader.ReadString(); + if (!_typeCache.TryGetValue(typename, out var type)) + { + type = Builder.BuildTypes.FirstOrDefault(x => x.FullName == typename); + if (type == null) + { + var msg = $"Missing type {typename}."; + Log.Error(msg); + throw new Exception(msg); + } + _typeCache.Add(typename, type); + } + var e = (T)Activator.CreateInstance(type); + e.Read(reader); + list.Add(e); + } + } + return list; + } + + private static string GetCachePath(Module module, BuildOptions moduleOptions) + { + return Path.Combine(moduleOptions.IntermediateFolder, module.Name + ".Bindings.cache"); + } + + private static void SaveCache(ModuleInfo moduleInfo, BuildOptions moduleOptions, List headerFiles) + { + var path = GetCachePath(moduleInfo.Module, moduleOptions); + using (var stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read)) + using (var writer = new BinaryWriter(stream, Encoding.UTF8)) + { + // Version + writer.Write(4); + writer.Write(File.GetLastWriteTime(Assembly.GetExecutingAssembly().Location).Ticks); + + // Build options + writer.Write(moduleOptions.IntermediateFolder); + writer.Write((int)moduleOptions.Platform.Target); + writer.Write((int)moduleOptions.Architecture); + writer.Write((int)moduleOptions.Configuration); + Write(writer, moduleOptions.PublicDefinitions); + Write(writer, moduleOptions.PrivateDefinitions); + Write(writer, moduleOptions.CompileEnv.PreprocessorDefinitions); + + // Header files + writer.Write(headerFiles.Count); + for (int i = 0; i < headerFiles.Count; i++) + { + var headerFile = headerFiles[i]; + writer.Write(headerFile); + writer.Write(File.GetLastWriteTime(headerFile).Ticks); + } + + // Info + moduleInfo.Write(writer); + } + } + + private static bool LoadCache(ref ModuleInfo moduleInfo, BuildOptions moduleOptions, List headerFiles) + { + var path = GetCachePath(moduleInfo.Module, moduleOptions); + if (!File.Exists(path)) + return false; + using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)) + using (var reader = new BinaryReader(stream, Encoding.UTF8)) + { + // Version + var version = reader.ReadInt32(); + if (version != 4) + return false; + if (File.GetLastWriteTime(Assembly.GetExecutingAssembly().Location).Ticks != reader.ReadInt64()) + return false; + + // Build options + if (reader.ReadString() != moduleOptions.IntermediateFolder || + reader.ReadInt32() != (int)moduleOptions.Platform.Target || + reader.ReadInt32() != (int)moduleOptions.Architecture || + reader.ReadInt32() != (int)moduleOptions.Configuration) + return false; + var publicDefinitions = Read(reader, Utilities.GetEmptyArray()); + if (publicDefinitions.Length != moduleOptions.PublicDefinitions.Count || publicDefinitions.Any(x => !moduleOptions.PublicDefinitions.Contains(x))) + return false; + var privateDefinitions = Read(reader, Utilities.GetEmptyArray()); + if (privateDefinitions.Length != moduleOptions.PrivateDefinitions.Count || privateDefinitions.Any(x => !moduleOptions.PrivateDefinitions.Contains(x))) + return false; + var preprocessorDefinitions = Read(reader, Utilities.GetEmptyArray()); + if (preprocessorDefinitions.Length != moduleOptions.CompileEnv.PreprocessorDefinitions.Count || preprocessorDefinitions.Any(x => !moduleOptions.CompileEnv.PreprocessorDefinitions.Contains(x))) + return false; + + // Header files + var headerFilesCount = reader.ReadInt32(); + if (headerFilesCount != headerFiles.Count) + return false; + for (int i = 0; i < headerFilesCount; i++) + { + var headerFile = headerFiles[i]; + if (headerFile != reader.ReadString()) + return false; + if (File.GetLastWriteTime(headerFile).Ticks > reader.ReadInt64()) + return false; + } + + // Info + var newModuleInfo = new ModuleInfo + { + Module = moduleInfo.Module, + Name = moduleInfo.Name, + Namespace = moduleInfo.Namespace, + IsFromCache = true, + }; + try + { + newModuleInfo.Read(reader); + + // Skip parsing and use data loaded from cache + moduleInfo = newModuleInfo; + return true; + } + catch + { + // Skip loading cache + return false; + } + } + } + } +} diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs index 3f088ffdf..58f1edaae 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs @@ -100,7 +100,7 @@ namespace Flax.Build.Bindings return value; if (typeInfo.Type == "String") return $"Variant(StringView({value}))"; - if (typeInfo.Type == "AssetReference" || typeInfo.Type == "WeakAssetReference" || typeInfo.Type == "ScriptingObjectReference") + if (typeInfo.Type == "AssetReference" || typeInfo.Type == "WeakAssetReference" || typeInfo.Type == "ScriptingObjectReference" || typeInfo.Type == "SoftObjectReference") return $"Variant({value}.Get())"; if (typeInfo.IsArray) { @@ -149,7 +149,7 @@ namespace Flax.Build.Bindings return $"((StringView){value}).GetText()"; if (typeInfo.Type == "AssetReference" || typeInfo.Type == "WeakAssetReference") return $"ScriptingObject::Cast<{typeInfo.GenericArgs[0].Type}>((Asset*){value})"; - if (typeInfo.Type == "ScriptingObjectReference") + if (typeInfo.Type == "ScriptingObjectReference" || typeInfo.Type == "SoftObjectReference") return $"ScriptingObject::Cast<{typeInfo.GenericArgs[0].Type}>((ScriptingObject*){value})"; if (typeInfo.IsArray) throw new Exception($"Not supported type to convert from the Variant to fixed-size array '{typeInfo}[{typeInfo.ArraySize}]'."); @@ -340,8 +340,8 @@ namespace Flax.Build.Bindings } } - // ScriptingObjectReference or AssetReference or WeakAssetReference - if ((typeInfo.Type == "ScriptingObjectReference" || typeInfo.Type == "AssetReference" || typeInfo.Type == "WeakAssetReference") && typeInfo.GenericArgs != null) + // ScriptingObjectReference or AssetReference or WeakAssetReference or SoftObjectReference + if ((typeInfo.Type == "ScriptingObjectReference" || typeInfo.Type == "AssetReference" || typeInfo.Type == "WeakAssetReference" || typeInfo.Type == "SoftObjectReference") && typeInfo.GenericArgs != null) { type = "MonoObject*"; return "{0}.GetManagedInstance()"; @@ -448,8 +448,8 @@ namespace Flax.Build.Bindings type = "MonoReflectionType*"; return "MUtils::UnboxVariantType({0})"; default: - // ScriptingObjectReference or AssetReference or WeakAssetReference - if ((typeInfo.Type == "ScriptingObjectReference" || typeInfo.Type == "AssetReference" || typeInfo.Type == "WeakAssetReference") && typeInfo.GenericArgs != null) + // ScriptingObjectReference or AssetReference or WeakAssetReference or SoftObjectReference + if ((typeInfo.Type == "ScriptingObjectReference" || typeInfo.Type == "AssetReference" || typeInfo.Type == "WeakAssetReference" || typeInfo.Type == "SoftObjectReference") && typeInfo.GenericArgs != null) { // For non-pod types converting only, other API converts managed to unmanaged object in C# wrapper code) if (CppNonPodTypesConvertingGeneration) @@ -831,7 +831,7 @@ namespace Flax.Build.Bindings contents.AppendLine(" ScriptingTypeHandle managedTypeHandle = object->GetTypeHandle();"); contents.AppendLine(" const ScriptingType* managedTypePtr = &managedTypeHandle.GetType();"); - contents.AppendLine(" while (managedTypePtr->Class.Spawn != &ManagedBinaryModule::ManagedObjectSpawn)"); + contents.AppendLine(" while (managedTypePtr->Script.Spawn != &ManagedBinaryModule::ManagedObjectSpawn)"); contents.AppendLine(" {"); contents.AppendLine(" managedTypeHandle = managedTypePtr->GetBaseType();"); contents.AppendLine(" managedTypePtr = &managedTypeHandle.GetType();"); @@ -840,7 +840,7 @@ namespace Flax.Build.Bindings contents.AppendLine(" if (IsDuringWrapperCall)"); contents.AppendLine(" {"); contents.AppendLine(" // Prevent stack overflow by calling native base method"); - contents.AppendLine(" const auto scriptVTableBase = managedTypePtr->Class.ScriptVTableBase;"); + contents.AppendLine(" const auto scriptVTableBase = managedTypePtr->Script.ScriptVTableBase;"); contents.Append($" return (object->**({functionInfo.UniqueName}_Signature*)&scriptVTableBase[{scriptVTableIndex} + 2])("); separator = false; for (var i = 0; i < functionInfo.Parameters.Count; i++) @@ -853,7 +853,7 @@ namespace Flax.Build.Bindings } contents.AppendLine(");"); contents.AppendLine(" }"); - contents.AppendLine(" auto scriptVTable = (MMethod**)managedTypePtr->Class.ScriptVTable;"); + contents.AppendLine(" auto scriptVTable = (MMethod**)managedTypePtr->Script.ScriptVTable;"); contents.AppendLine($" ASSERT(scriptVTable && scriptVTable[{scriptVTableIndex}]);"); contents.AppendLine($" auto method = scriptVTable[{scriptVTableIndex}];"); contents.AppendLine(" MonoObject* exception = nullptr;"); @@ -960,7 +960,7 @@ namespace Flax.Build.Bindings var classInfo = typeInfo as ClassInfo; var structureInfo = typeInfo as StructureInfo; var baseType = classInfo?.BaseType ?? structureInfo?.BaseType; - if (baseType != null && baseType.Type == "ISerializable") + if (classInfo != null && classInfo.IsBaseTypeHidden) baseType = null; CppAutoSerializeFields.Clear(); CppAutoSerializeProperties.Clear(); @@ -1049,6 +1049,25 @@ namespace Flax.Build.Bindings contents.Append('}').AppendLine(); } + private static string GenerateCppInterfaceInheritanceTable(BuildData buildData, StringBuilder contents, ModuleInfo moduleInfo, ClassStructInfo typeInfo, string typeNameNative) + { + var interfacesPtr = "nullptr"; + var interfaces = typeInfo.Interfaces; + if (interfaces != null) + { + interfacesPtr = typeNameNative + "_Interfaces"; + contents.Append("static const ScriptingType::InterfaceImplementation ").Append(interfacesPtr).AppendLine("[] = {"); + for (int i = 0; i < interfaces.Count; i++) + { + var interfaceInfo = interfaces[i]; + contents.Append(" { &").Append(interfaceInfo.NativeName).Append("::TypeInitializer, (int16)VTABLE_OFFSET(").Append(typeInfo.NativeName).Append(", ").Append(interfaceInfo.NativeName).AppendLine(") },"); + } + contents.AppendLine(" { nullptr, 0 },"); + contents.AppendLine("};"); + } + return interfacesPtr; + } + private static void GenerateCppClass(BuildData buildData, StringBuilder contents, ModuleInfo moduleInfo, ClassInfo classInfo) { var classTypeNameNative = classInfo.FullNameNative; @@ -1057,6 +1076,7 @@ namespace Flax.Build.Bindings var classTypeNameInternal = classInfo.NativeName; if (classInfo.Parent != null && !(classInfo.Parent is FileInfo)) classTypeNameInternal = classInfo.Parent.FullNameNative + '_' + classTypeNameInternal; + var useScripting = classInfo.IsStatic || classInfo.IsScriptingObject; if (classInfo.IsAutoSerialization) GenerateCppAutoSerialization(buildData, contents, moduleInfo, classInfo, classTypeNameNative); @@ -1069,32 +1089,34 @@ namespace Flax.Build.Bindings // Events foreach (var eventInfo in classInfo.Events) { - CppIncludeFiles.Add("Engine/Scripting/ManagedCLR/MEvent.h"); + if (!useScripting) + continue; + var paramsCount = eventInfo.Type.GenericArgs?.Count ?? 0; // C# event invoking wrapper (calls C# event from C++ delegate) + CppIncludeFiles.Add("Engine/Scripting/ManagedCLR/MEvent.h"); contents.Append(" "); if (eventInfo.IsStatic) contents.Append("static "); contents.AppendFormat("void {0}_ManagedWrapper(", eventInfo.Name); - for (var i = 0; i < eventInfo.Type.GenericArgs.Count; i++) + for (var i = 0; i < paramsCount; i++) { if (i != 0) contents.Append(", "); - contents.Append(eventInfo.Type.GenericArgs[i]); - contents.Append(" arg" + i); + contents.Append(eventInfo.Type.GenericArgs[i]).Append(" arg" + i); } contents.Append(')').AppendLine(); contents.Append(" {").AppendLine(); contents.Append(" static MMethod* mmethod = nullptr;").AppendLine(); contents.Append(" if (!mmethod)").AppendLine(); - contents.AppendFormat(" mmethod = {1}::GetStaticClass()->GetMethod(\"Internal_{0}_Invoke\", {2});", eventInfo.Name, classTypeNameNative, eventInfo.Type.GenericArgs.Count).AppendLine(); + contents.AppendFormat(" mmethod = {1}::GetStaticClass()->GetMethod(\"Internal_{0}_Invoke\", {2});", eventInfo.Name, classTypeNameNative, paramsCount).AppendLine(); contents.Append(" CHECK(mmethod);").AppendLine(); contents.Append(" MonoObject* exception = nullptr;").AppendLine(); - if (eventInfo.Type.GenericArgs.Count == 0) + if (paramsCount == 0) contents.AppendLine(" void** params = nullptr;"); else - contents.AppendLine($" void* params[{eventInfo.Type.GenericArgs.Count}];"); - for (var i = 0; i < eventInfo.Type.GenericArgs.Count; i++) + contents.AppendLine($" void* params[{paramsCount}];"); + for (var i = 0; i < paramsCount; i++) { var paramType = eventInfo.Type.GenericArgs[i]; var paramName = "arg" + i; @@ -1117,7 +1139,7 @@ namespace Flax.Build.Bindings contents.Append("bool bind)").AppendLine(); contents.Append(" {").AppendLine(); contents.Append(" Function(params, {paramsCount}), {classTypeNameNative}::TypeInitializer, StringView(TEXT(\"{eventInfo.Name}\"), {eventInfo.Name.Length}));"); + contents.Append(" }").AppendLine().AppendLine(); + + // Scripting event wrapper binding method (binds/unbinds generic wrapper to C++ delegate) + contents.AppendFormat(" static void {0}_Bind(", eventInfo.Name); + contents.AppendFormat("{0}* obj, void* instance, bool bind)", classTypeNameNative).AppendLine(); + contents.Append(" {").AppendLine(); + contents.Append(" Function f;").AppendLine(); + if (eventInfo.IsStatic) + contents.AppendFormat(" f.Bind<{0}_Wrapper>();", eventInfo.Name).AppendLine(); + else + contents.AppendFormat(" f.Bind<{1}Internal, &{1}Internal::{0}_Wrapper>(({1}Internal*)instance);", eventInfo.Name, classTypeNameInternal).AppendLine(); + contents.Append(" if (bind)").AppendLine(); + contents.AppendFormat(" {0}{1}.Bind(f);", bindPrefix, eventInfo.Name).AppendLine(); + contents.Append(" else").AppendLine(); + contents.AppendFormat(" {0}{1}.Unbind(f);", bindPrefix, eventInfo.Name).AppendLine(); + contents.Append(" }").AppendLine().AppendLine(); } // Fields foreach (var fieldInfo in classInfo.Fields) { + if (!useScripting) + continue; if (fieldInfo.Getter != null) GenerateCppWrapperFunction(buildData, contents, classInfo, fieldInfo.Getter, "{0}"); if (fieldInfo.Setter != null) @@ -1148,6 +1222,8 @@ namespace Flax.Build.Bindings // Properties foreach (var propertyInfo in classInfo.Properties) { + if (!useScripting) + continue; if (propertyInfo.Getter != null) GenerateCppWrapperFunction(buildData, contents, classInfo, propertyInfo.Getter); if (propertyInfo.Setter != null) @@ -1157,6 +1233,8 @@ namespace Flax.Build.Bindings // Functions foreach (var functionInfo in classInfo.Functions) { + if (!useScripting) + throw new Exception($"Not supported function {functionInfo.Name} inside non-static and non-scripting class type {classInfo.Name}."); GenerateCppWrapperFunction(buildData, contents, classInfo, functionInfo); } @@ -1266,50 +1344,88 @@ namespace Flax.Build.Bindings // Runtime initialization (internal methods binding) contents.AppendLine(" static void InitRuntime()"); contents.AppendLine(" {"); - foreach (var eventInfo in classInfo.Events) + if (useScripting) { - contents.AppendLine($" ADD_INTERNAL_CALL(\"{classTypeNameManagedInternalCall}::Internal_{eventInfo.Name}_Bind\", &{eventInfo.Name}_ManagedBind);"); - } - foreach (var fieldInfo in classInfo.Fields) - { - if (fieldInfo.Getter != null) - contents.AppendLine($" ADD_INTERNAL_CALL(\"{classTypeNameManagedInternalCall}::Internal_{fieldInfo.Getter.UniqueName}\", &{fieldInfo.Getter.UniqueName});"); - if (fieldInfo.Setter != null) - contents.AppendLine($" ADD_INTERNAL_CALL(\"{classTypeNameManagedInternalCall}::Internal_{fieldInfo.Setter.UniqueName}\", &{fieldInfo.Setter.UniqueName});"); - } - foreach (var propertyInfo in classInfo.Properties) - { - if (propertyInfo.Getter != null) - contents.AppendLine($" ADD_INTERNAL_CALL(\"{classTypeNameManagedInternalCall}::Internal_{propertyInfo.Getter.UniqueName}\", &{propertyInfo.Getter.UniqueName});"); - if (propertyInfo.Setter != null) - contents.AppendLine($" ADD_INTERNAL_CALL(\"{classTypeNameManagedInternalCall}::Internal_{propertyInfo.Setter.UniqueName}\", &{propertyInfo.Setter.UniqueName});"); - } - foreach (var functionInfo in classInfo.Functions) - { - contents.AppendLine($" ADD_INTERNAL_CALL(\"{classTypeNameManagedInternalCall}::Internal_{functionInfo.UniqueName}\", &{functionInfo.UniqueName});"); + foreach (var eventInfo in classInfo.Events) + { + contents.AppendLine($" ADD_INTERNAL_CALL(\"{classTypeNameManagedInternalCall}::Internal_{eventInfo.Name}_Bind\", &{eventInfo.Name}_ManagedBind);"); + + // Register scripting event binder + contents.AppendLine($" ScriptingEvents::EventsTable[Pair({classTypeNameNative}::TypeInitializer, StringView(TEXT(\"{eventInfo.Name}\"), {eventInfo.Name.Length}))] = (void(*)(ScriptingObject*, void*, bool)){classTypeNameInternal}Internal::{eventInfo.Name}_Bind;"); + } + foreach (var fieldInfo in classInfo.Fields) + { + if (fieldInfo.Getter != null) + contents.AppendLine($" ADD_INTERNAL_CALL(\"{classTypeNameManagedInternalCall}::Internal_{fieldInfo.Getter.UniqueName}\", &{fieldInfo.Getter.UniqueName});"); + if (fieldInfo.Setter != null) + contents.AppendLine($" ADD_INTERNAL_CALL(\"{classTypeNameManagedInternalCall}::Internal_{fieldInfo.Setter.UniqueName}\", &{fieldInfo.Setter.UniqueName});"); + } + foreach (var propertyInfo in classInfo.Properties) + { + if (propertyInfo.Getter != null) + contents.AppendLine($" ADD_INTERNAL_CALL(\"{classTypeNameManagedInternalCall}::Internal_{propertyInfo.Getter.UniqueName}\", &{propertyInfo.Getter.UniqueName});"); + if (propertyInfo.Setter != null) + contents.AppendLine($" ADD_INTERNAL_CALL(\"{classTypeNameManagedInternalCall}::Internal_{propertyInfo.Setter.UniqueName}\", &{propertyInfo.Setter.UniqueName});"); + } + foreach (var functionInfo in classInfo.Functions) + { + contents.AppendLine($" ADD_INTERNAL_CALL(\"{classTypeNameManagedInternalCall}::Internal_{functionInfo.UniqueName}\", &{functionInfo.UniqueName});"); + } } GenerateCppClassInitRuntime?.Invoke(buildData, classInfo, contents); - contents.AppendLine(" }"); - contents.Append('}'); - contents.Append(';'); - contents.AppendLine(); + contents.AppendLine(" }").AppendLine(); + + if (!useScripting && !classInfo.IsAbstract) + { + // Constructor + contents.AppendLine(" static void Ctor(void* ptr)"); + contents.AppendLine(" {"); + contents.AppendLine($" new(ptr){classTypeNameNative}();"); + contents.AppendLine(" }").AppendLine(); + + // Destructor + contents.AppendLine(" static void Dtor(void* ptr)"); + contents.AppendLine(" {"); + contents.AppendLine($" (({classTypeNameNative}*)ptr)->~{classInfo.NativeName}();"); + contents.AppendLine(" }").AppendLine(); + } + + contents.Append('}').Append(';').AppendLine(); contents.AppendLine(); + // Interfaces + var interfacesTable = GenerateCppInterfaceInheritanceTable(buildData, contents, moduleInfo, classInfo, classTypeNameNative); + // Type initializer contents.Append($"ScriptingTypeInitializer {classTypeNameNative}::TypeInitializer((BinaryModule*)GetBinaryModule{moduleInfo.Name}(), "); contents.Append($"StringAnsiView(\"{classTypeNameManaged}\", {classTypeNameManaged.Length}), "); contents.Append($"sizeof({classTypeNameNative}), "); contents.Append($"&{classTypeNameInternal}Internal::InitRuntime, "); - if (!classInfo.IsStatic && !classInfo.NoSpawn) - contents.Append($"(ScriptingType::SpawnHandler)&{classTypeNameNative}::Spawn, "); + if (useScripting) + { + if (classInfo.IsStatic || classInfo.NoSpawn) + contents.Append("&ScriptingType::DefaultSpawn, "); + else + contents.Append($"(ScriptingType::SpawnHandler)&{classTypeNameNative}::Spawn, "); + if (classInfo.BaseType != null && useScripting) + contents.Append($"&{classInfo.BaseType}::TypeInitializer, "); + else + contents.Append("nullptr, "); + contents.Append(setupScriptVTable); + } else - contents.Append("&ScriptingType::DefaultSpawn, "); - if (classInfo.BaseType != null) - contents.Append($"&{classInfo.BaseType}::TypeInitializer, "); - else - contents.Append("nullptr, "); - contents.Append(setupScriptVTable); + { + if (classInfo.IsAbstract) + contents.Append("nullptr, nullptr, "); + else + contents.Append($"&{classTypeNameInternal}Internal::Ctor, &{classTypeNameInternal}Internal::Dtor, "); + if (classInfo.BaseType != null) + contents.Append($"&{classInfo.BaseType}::TypeInitializer"); + else + contents.Append("nullptr"); + } + contents.Append(", ").Append(interfacesTable); contents.Append(");"); contents.AppendLine(); @@ -1504,10 +1620,46 @@ namespace Flax.Build.Bindings } } - private static bool GenerateCppType(BuildData buildData, StringBuilder contents, ModuleInfo moduleInfo, object type) + private static void GenerateCppInterface(BuildData buildData, StringBuilder contents, ModuleInfo moduleInfo, InterfaceInfo interfaceInfo) + { + var interfaceTypeNameNative = interfaceInfo.FullNameNative; + var interfaceTypeNameManaged = interfaceInfo.FullNameManaged; + var interfaceTypeNameManagedInternalCall = interfaceTypeNameManaged.Replace('+', '/'); + var interfaceTypeNameInternal = interfaceInfo.NativeName; + if (interfaceInfo.Parent != null && !(interfaceInfo.Parent is FileInfo)) + interfaceTypeNameInternal = interfaceInfo.Parent.FullNameNative + '_' + interfaceTypeNameInternal; + + contents.AppendLine(); + contents.AppendFormat("class {0}Internal", interfaceTypeNameInternal).AppendLine(); + contents.Append('{').AppendLine(); + contents.AppendLine("public:"); + + // Runtime initialization (internal methods binding) + contents.AppendLine(" static void InitRuntime()"); + contents.AppendLine(" {"); + contents.AppendLine(" }").AppendLine(); + + contents.Append('}').Append(';').AppendLine(); + contents.AppendLine(); + + // Type initializer + contents.Append($"ScriptingTypeInitializer {interfaceTypeNameNative}::TypeInitializer((BinaryModule*)GetBinaryModule{moduleInfo.Name}(), "); + contents.Append($"StringAnsiView(\"{interfaceTypeNameManaged}\", {interfaceTypeNameManaged.Length}), "); + contents.Append($"&{interfaceTypeNameInternal}Internal::InitRuntime"); + contents.Append(");"); + contents.AppendLine(); + + // Nested types + foreach (var apiTypeInfo in interfaceInfo.Children) + { + GenerateCppType(buildData, contents, moduleInfo, apiTypeInfo); + } + } + + private static void GenerateCppType(BuildData buildData, StringBuilder contents, ModuleInfo moduleInfo, object type) { if (type is ApiTypeInfo apiTypeInfo && apiTypeInfo.IsInBuild) - return false; + return; try { @@ -1515,18 +1667,16 @@ namespace Flax.Build.Bindings GenerateCppClass(buildData, contents, moduleInfo, classInfo); else if (type is StructureInfo structureInfo) GenerateCppStruct(buildData, contents, moduleInfo, structureInfo); + else if (type is InterfaceInfo interfaceInfo) + GenerateCppInterface(buildData, contents, moduleInfo, interfaceInfo); else if (type is InjectCppCodeInfo injectCppCodeInfo) contents.AppendLine(injectCppCodeInfo.Code); - else - return false; } catch { Log.Error($"Failed to generate C++ bindings for {type}."); throw; } - - return true; } private static void GenerateCppCppUsedNonPodTypes(BuildData buildData, ApiTypeInfo apiType) @@ -1574,21 +1724,16 @@ namespace Flax.Build.Bindings CppReferencesFiles.Add(fileInfo); } } - var headerPos = contents.Length; foreach (var child in moduleInfo.Children) { foreach (var apiTypeInfo in child.Children) { - if (GenerateCppType(buildData, contents, moduleInfo, apiTypeInfo)) - bindings.UseBindings = true; + GenerateCppType(buildData, contents, moduleInfo, apiTypeInfo); } } - if (!bindings.UseBindings) - return; - GenerateCppModuleSource?.Invoke(buildData, moduleInfo, contents); { @@ -1882,7 +2027,7 @@ namespace Flax.Build.Bindings var binaryModuleSourcePath = Path.Combine(project.ProjectFolderPath, "Source", binaryModuleName + ".Gen.cpp"); contents.AppendLine("// This code was auto-generated. Do not modify it."); contents.AppendLine(); - contents.AppendLine($"#include \"Engine/Scripting/BinaryModule.h\""); + contents.AppendLine("#include \"Engine/Scripting/BinaryModule.h\""); contents.AppendLine($"#include \"{binaryModuleName}.Gen.h\""); contents.AppendLine(); contents.AppendLine($"StaticallyLinkedBinaryModuleInitializer StaticallyLinkedBinaryModule{binaryModuleName}(GetBinaryModule{binaryModuleName});"); diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs index a7d6782d1..7714e6abd 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs @@ -17,6 +17,7 @@ namespace Flax.Build.Bindings public Stack ScopeTypeStack; public Stack ScopeAccessStack; public Dictionary PreprocessorDefines; + public List StringCache; public ApiTypeInfo ValidScopeInfoFromStack { @@ -46,14 +47,12 @@ namespace Flax.Build.Bindings } } - private static List _commentCache; - private static string[] ParseComment(ref ParsingContext context) { - if (_commentCache == null) - _commentCache = new List(); + if (context.StringCache == null) + context.StringCache = new List(); else - _commentCache.Clear(); + context.StringCache.Clear(); int tokensCount = 0; bool isValid = true; @@ -77,7 +76,7 @@ namespace Flax.Build.Bindings if (commentLine.StartsWith("// ")) commentLine = "/// " + commentLine.Substring(3); - _commentCache.Insert(0, commentLine); + context.StringCache.Insert(0, commentLine); break; } default: @@ -90,14 +89,14 @@ namespace Flax.Build.Bindings for (var i = 0; i < tokensCount; i++) context.Tokenizer.NextToken(true, true); - if (_commentCache.Count == 1) + if (context.StringCache.Count == 1) { // Ensure to have summary begin/end pair - _commentCache.Insert(0, "/// "); - _commentCache.Add("/// "); + context.StringCache.Insert(0, "/// "); + context.StringCache.Add("/// "); } - return _commentCache.ToArray(); + return context.StringCache.ToArray(); } private struct TagParameter @@ -383,17 +382,101 @@ namespace Flax.Build.Bindings return name; } + private static void ParseInheritance(ref ParsingContext context, ClassStructInfo desc, out bool isFinal) + { + desc.BaseType = null; + desc.BaseTypeInheritance = AccessLevel.Private; + + var token = context.Tokenizer.NextToken(); + isFinal = token.Value == "final"; + if (isFinal) + token = context.Tokenizer.NextToken(); + + if (token.Type == TokenType.Colon) + { + while (token.Type != TokenType.LeftCurlyBrace) + { + var accessToken = context.Tokenizer.ExpectToken(TokenType.Identifier); + switch (accessToken.Value) + { + case "public": + desc.BaseTypeInheritance = AccessLevel.Public; + token = context.Tokenizer.ExpectToken(TokenType.Identifier); + break; + case "protected": + desc.BaseTypeInheritance = AccessLevel.Protected; + token = context.Tokenizer.ExpectToken(TokenType.Identifier); + break; + case "private": + token = context.Tokenizer.ExpectToken(TokenType.Identifier); + break; + default: + token = accessToken; + break; + } + + var baseTypeInfo = new TypeInfo + { + Type = token.Value, + }; + if (token.Value.Length > 2 && token.Value[0] == 'I' && char.IsUpper(token.Value[1])) + { + // Interface + if (desc.InterfaceNames == null) + desc.InterfaceNames = new List(); + desc.InterfaceNames.Add(baseTypeInfo); + token = context.Tokenizer.NextToken(); + continue; + } + + if (desc.BaseType != null) + { + // Allow for multiple base classes, just the first one needs to be a valid base type + break; + throw new Exception($"Invalid '{desc.Name}' inheritance (only single base class is allowed for scripting types, excluding interfaces)."); + } + desc.BaseType = baseTypeInfo; + token = context.Tokenizer.NextToken(); + if (token.Type == TokenType.LeftCurlyBrace) + { + break; + } + if (token.Type == TokenType.LeftAngleBracket) + { + var genericType = context.Tokenizer.ExpectToken(TokenType.Identifier); + token = context.Tokenizer.ExpectToken(TokenType.RightAngleBracket); + desc.BaseType.GenericArgs = new List + { + new TypeInfo + { + Type = genericType.Value, + } + }; + + // TODO: find better way to resolve this (custom base type attribute?) + if (desc.BaseType.Type == "ShaderAssetTypeBase") + { + desc.BaseType = desc.BaseType.GenericArgs[0]; + } + + token = context.Tokenizer.NextToken(); + } + } + token = context.Tokenizer.PreviousToken(); + } + else + { + // No base type + token = context.Tokenizer.PreviousToken(); + } + } + private static ClassInfo ParseClass(ref ParsingContext context) { var desc = new ClassInfo { - Children = new List(), Access = context.CurrentAccessLevel, BaseTypeInheritance = AccessLevel.Private, - Functions = new List(), - Properties = new List(), - Fields = new List(), - Events = new List(), }; // Read the documentation comment @@ -410,64 +493,8 @@ namespace Flax.Build.Bindings // Read name desc.Name = desc.NativeName = ParseName(ref context); - // Read class inheritance - token = context.Tokenizer.NextToken(); - var isFinal = token.Value == "final"; - if (isFinal) - token = context.Tokenizer.NextToken(); - if (token.Type == TokenType.Colon) - { - // Current class does have inheritance defined - var accessToken = context.Tokenizer.ExpectToken(TokenType.Identifier); - switch (accessToken.Value) - { - case "public": - desc.BaseTypeInheritance = AccessLevel.Public; - token = context.Tokenizer.ExpectToken(TokenType.Identifier); - break; - case "protected": - desc.BaseTypeInheritance = AccessLevel.Protected; - token = context.Tokenizer.ExpectToken(TokenType.Identifier); - break; - case "private": - token = context.Tokenizer.ExpectToken(TokenType.Identifier); - break; - } - - desc.BaseType = new TypeInfo - { - Type = token.Value, - }; - token = context.Tokenizer.NextToken(); - if (token.Type == TokenType.LeftAngleBracket) - { - var genericType = context.Tokenizer.ExpectToken(TokenType.Identifier); - context.Tokenizer.ExpectToken(TokenType.RightAngleBracket); - desc.BaseType.GenericArgs = new List - { - new TypeInfo - { - Type = genericType.Value, - } - }; - - // TODO: find better way to resolve this (custom base type attribute?) - if (desc.BaseType.Type == "ShaderAssetTypeBase") - { - desc.BaseType = desc.BaseType.GenericArgs[0]; - } - } - else - { - token = context.Tokenizer.PreviousToken(); - } - } - else - { - // No base type - token = context.Tokenizer.PreviousToken(); - desc.BaseType = null; - } + // Read inheritance + ParseInheritance(ref context, desc, out var isFinal); // Process tag parameters foreach (var tag in tagParams) @@ -523,12 +550,72 @@ namespace Flax.Build.Bindings return desc; } + private static InterfaceInfo ParseInterface(ref ParsingContext context) + { + var desc = new InterfaceInfo + { + Access = context.CurrentAccessLevel, + }; + + // Read the documentation comment + desc.Comment = ParseComment(ref context); + + // Read parameters from the tag + var tagParams = ParseTagParameters(ref context); + + // Read 'class' keyword + var token = context.Tokenizer.NextToken(); + if (token.Value != "class") + throw new Exception($"Invalid API_INTERFACE usage (expected 'class' keyword but got '{token.Value} {context.Tokenizer.NextToken().Value}')."); + + // Read name + desc.Name = desc.NativeName = ParseName(ref context); + if (desc.Name.Length < 2 || desc.Name[0] != 'I' || !char.IsUpper(desc.Name[1])) + throw new Exception($"Invalid API_INTERFACE name '{desc.Name}' (it must start with 'I' character followed by the uppercase character)."); + + // Read inheritance + ParseInheritance(ref context, desc, out _); + + // Process tag parameters + foreach (var tag in tagParams) + { + switch (tag.Tag.ToLower()) + { + case "public": + desc.Access = AccessLevel.Public; + break; + case "protected": + desc.Access = AccessLevel.Protected; + break; + case "private": + desc.Access = AccessLevel.Private; + break; + case "inbuild": + desc.IsInBuild = true; + break; + case "attributes": + desc.Attributes = tag.Value; + break; + case "name": + desc.Name = tag.Value; + break; + case "namespace": + desc.Namespace = tag.Value; + break; + default: + Log.Warning($"Unknown or not supported tag parameter {tag} used on interface {desc.Name} at line {context.Tokenizer.CurrentLine}"); + break; + } + } + + return desc; + } + private static FunctionInfo ParseFunction(ref ParsingContext context) { var desc = new FunctionInfo { Access = context.CurrentAccessLevel, - Parameters = new List(), }; // Read the documentation comment @@ -727,9 +814,7 @@ namespace Flax.Build.Bindings { var desc = new EnumInfo { - Children = new List(), Access = context.CurrentAccessLevel, - Entries = new List(), }; // Read the documentation comment @@ -884,10 +969,7 @@ namespace Flax.Build.Bindings { var desc = new StructureInfo { - Children = new List(), Access = context.CurrentAccessLevel, - Fields = new List(), - Functions = new List(), }; // Read the documentation comment @@ -904,55 +986,8 @@ namespace Flax.Build.Bindings // Read name desc.Name = desc.NativeName = ParseName(ref context); - // Read structure inheritance - token = context.Tokenizer.NextToken(); - if (token.Type == TokenType.Colon) - { - // Current class does have inheritance defined - var accessToken = context.Tokenizer.ExpectToken(TokenType.Identifier); - switch (accessToken.Value) - { - case "public": - token = context.Tokenizer.ExpectToken(TokenType.Identifier); - break; - case "protected": - token = context.Tokenizer.ExpectToken(TokenType.Identifier); - break; - case "private": - token = context.Tokenizer.ExpectToken(TokenType.Identifier); - break; - default: - token = accessToken; - break; - } - - desc.BaseType = new TypeInfo - { - Type = token.Value, - }; - token = context.Tokenizer.NextToken(); - if (token.Type == TokenType.LeftAngleBracket) - { - var genericType = context.Tokenizer.ExpectToken(TokenType.Identifier); - context.Tokenizer.ExpectToken(TokenType.RightAngleBracket); - desc.BaseType.GenericArgs = new List - { - new TypeInfo - { - Type = genericType.Value, - } - }; - } - else - { - token = context.Tokenizer.PreviousToken(); - } - } - else - { - // No base type - token = context.Tokenizer.PreviousToken(); - } + // Read inheritance + ParseInheritance(ref context, desc, out _); // Process tag parameters foreach (var tag in tagParams) @@ -1003,11 +1038,30 @@ namespace Flax.Build.Bindings // Read parameters from the tag var tagParams = ParseTagParameters(ref context); + context.Tokenizer.SkipUntil(TokenType.Identifier); - // Read 'static' keyword - desc.IsStatic = context.Tokenizer.NextToken().Value == "static"; - if (!desc.IsStatic) - context.Tokenizer.PreviousToken(); + // Read 'static' or 'mutable' + Token token; + var isMutable = false; + while (true) + { + token = context.Tokenizer.CurrentToken; + if (!desc.IsStatic && token.Value == "static") + { + desc.IsStatic = true; + context.Tokenizer.NextToken(); + } + else if (!isMutable && token.Value == "mutable") + { + isMutable = true; + context.Tokenizer.NextToken(); + } + else + { + context.Tokenizer.PreviousToken(); + break; + } + } // Read type desc.Type = ParseType(ref context); @@ -1016,11 +1070,10 @@ namespace Flax.Build.Bindings desc.Name = ParseName(ref context); // Read ';' or default value or array size or bit-field size - var token = context.Tokenizer.ExpectAnyTokens(new[] { TokenType.SemiColon, TokenType.Equal, TokenType.LeftBracket, TokenType.Colon }); + token = context.Tokenizer.ExpectAnyTokens(new[] { TokenType.SemiColon, TokenType.Equal, TokenType.LeftBracket, TokenType.Colon }); if (token.Type == TokenType.Equal) { - //context.Tokenizer.SkipUntil(TokenType.SemiColon, out var defaultValue); - context.Tokenizer.SkipUntil(TokenType.SemiColon); + context.Tokenizer.SkipUntil(TokenType.SemiColon, out desc.DefaultValue, false); } else if (token.Type == TokenType.LeftBracket) { @@ -1135,7 +1188,6 @@ namespace Flax.Build.Bindings context.Tokenizer.ExpectToken(TokenType.LeftParent); var desc = new InjectCppCodeInfo { - Children = new List(), Code = context.Tokenizer.ExpectToken(TokenType.String).Value.Replace("\\\"", "\""), }; desc.Code = desc.Code.Substring(1, desc.Code.Length - 2); diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.cs index 45394b510..a7e1508eb 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.cs @@ -4,6 +4,8 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Threading; +using System.Threading.Tasks; using Flax.Build.NativeCpp; using BuildData = Flax.Build.Builder.BuildData; @@ -58,16 +60,12 @@ namespace Flax.Build.Bindings private static ModuleInfo ParseModuleInner(BuildData buildData, Module module, BuildOptions moduleOptions = null) { - if (buildData.ModulesInfo.TryGetValue(module, out var moduleInfo)) - return moduleInfo; - // Setup bindings module info descriptor - moduleInfo = new ModuleInfo + var moduleInfo = new ModuleInfo { Module = module, Name = module.BinaryModuleName, Namespace = string.Empty, - Children = new List(), }; if (string.IsNullOrEmpty(moduleInfo.Name)) throw new Exception("Module name cannot be empty."); @@ -81,320 +79,375 @@ namespace Flax.Build.Bindings throw new Exception($"Cannot parse module {module.Name} without options."); // Collect all header files that can have public symbols for API - var headerFiles = moduleOptions.SourceFiles.Where(x => x.EndsWith(".h")).ToList(); - - // Skip if no header files to process + var headerFiles = new List(moduleOptions.SourceFiles.Count / 2); + for (int i = 0; i < moduleOptions.SourceFiles.Count; i++) + { + if (moduleOptions.SourceFiles[i].EndsWith(".h", StringComparison.OrdinalIgnoreCase)) + headerFiles.Add(moduleOptions.SourceFiles[i]); + } if (headerFiles.Count == 0) return moduleInfo; - - // Find and load files with API tags - string[] headerFilesContents = null; - //using (new ProfileEventScope("LoadHeaderFiles")) + if (module.Name == "Core") { - var anyApi = false; + // Special case for Core module to ignore API tags defines for (int i = 0; i < headerFiles.Count; i++) { - // Skip scripting types definitions file - if (headerFiles[i].Replace('\\', '/').EndsWith("Engine/Core/Config.h", StringComparison.Ordinal) || - headerFiles[i].EndsWith("EditorContextAPI.h", StringComparison.Ordinal)) - continue; - - // Check if file contains any valid API tag - var contents = File.ReadAllText(headerFiles[i]); - for (int j = 0; j < ApiTokens.SearchTags.Length; j++) + if (headerFiles[i].EndsWith("Config.h", StringComparison.Ordinal)) { - if (contents.Contains(ApiTokens.SearchTags[j])) - { - if (headerFilesContents == null) - headerFilesContents = new string[headerFiles.Count]; - headerFilesContents[i] = contents; - anyApi = true; - break; - } - } - } - - if (!anyApi) - return moduleInfo; - } - - // Skip if none of the headers was modified after last time generated C++ file was edited - // TODO: skip parsing if module has API cache file -> then load it from disk - /*if (!forceGenerate) - { - var lastGenerateTime = File.GetLastWriteTime(bindings.GeneratedCppFilePath); - var anyModified = false; - for (int i = 0; i < headerFiles.Count; i++) - { - if (File.GetLastWriteTime(headerFiles[i]) > lastGenerateTime) - { - anyModified = true; + headerFiles.RemoveAt(i); break; } } + } - if (!anyModified) - return; - }*/ + // Sort file paths to have stable results + headerFiles.Sort(); - Log.Verbose($"Parsing API bindings for {module.Name} ({moduleInfo.Name})"); - - // Process all header files to generate the module API code reflection - var context = new ParsingContext + // Load cache + using (new ProfileEventScope("LoadCache")) { - CurrentAccessLevel = AccessLevel.Public, - ScopeTypeStack = new Stack(), - ScopeAccessStack = new Stack(), - PreprocessorDefines = new Dictionary(), - }; - for (int i = 0; i < headerFiles.Count; i++) - { - if (headerFilesContents[i] == null) - continue; - var fileInfo = new FileInfo + if (LoadCache(ref moduleInfo, moduleOptions, headerFiles)) { - Parent = null, - Children = new List(), - Name = headerFiles[i], - Namespace = moduleInfo.Name, - }; - moduleInfo.AddChild(fileInfo); + buildData.ModulesInfo[module] = moduleInfo; - try - { - // Tokenize the source - var tokenizer = new Tokenizer(); - tokenizer.Tokenize(headerFilesContents[i]); - - // Init the context - context.Tokenizer = tokenizer; - context.File = fileInfo; - context.ScopeInfo = null; - context.ScopeTypeStack.Clear(); - context.ScopeAccessStack.Clear(); - context.PreprocessorDefines.Clear(); - context.EnterScope(fileInfo); - - // Process the source code - ApiTypeInfo scopeType = null; - Token prevToken = null; - while (true) + // Initialize API + using (new ProfileEventScope("Init")) { - // Move to the next token - var token = tokenizer.NextToken(); - if (token == null) - continue; - if (token.Type == TokenType.EndOfFile) - break; - - // Parse API_.. tags in source code - if (token.Type == TokenType.Identifier && token.Value.StartsWith("API_", StringComparison.Ordinal)) - { - if (string.Equals(token.Value, ApiTokens.Class, StringComparison.Ordinal)) - { - if (!(context.ScopeInfo is FileInfo)) - throw new NotImplementedException("TODO: add support for nested classes in scripting API"); - - var classInfo = ParseClass(ref context); - scopeType = classInfo; - context.ScopeInfo.AddChild(scopeType); - context.CurrentAccessLevel = AccessLevel.Public; - } - else if (string.Equals(token.Value, ApiTokens.Property, StringComparison.Ordinal)) - { - var propertyInfo = ParseProperty(ref context); - } - else if (string.Equals(token.Value, ApiTokens.Function, StringComparison.Ordinal)) - { - var functionInfo = ParseFunction(ref context); - - if (context.ScopeInfo is ClassInfo classInfo) - classInfo.Functions.Add(functionInfo); - else if (context.ScopeInfo is StructureInfo structureInfo) - structureInfo.Functions.Add(functionInfo); - else - throw new Exception($"Not supported free-function {functionInfo.Name} at line {tokenizer.CurrentLine}. Place it in the class to use API bindings for it."); - } - else if (string.Equals(token.Value, ApiTokens.Enum, StringComparison.Ordinal)) - { - var enumInfo = ParseEnum(ref context); - context.ScopeInfo.AddChild(enumInfo); - } - else if (string.Equals(token.Value, ApiTokens.Struct, StringComparison.Ordinal)) - { - var structureInfo = ParseStructure(ref context); - scopeType = structureInfo; - context.ScopeInfo.AddChild(scopeType); - context.CurrentAccessLevel = AccessLevel.Public; - } - else if (string.Equals(token.Value, ApiTokens.Field, StringComparison.Ordinal)) - { - var fieldInfo = ParseField(ref context); - var scopeInfo = context.ValidScopeInfoFromStack; - - if (scopeInfo is ClassInfo classInfo) - classInfo.Fields.Add(fieldInfo); - else if (scopeInfo is StructureInfo structureInfo) - structureInfo.Fields.Add(fieldInfo); - else - throw new Exception($"Not supported location for field {fieldInfo.Name} at line {tokenizer.CurrentLine}. Place it in the class or structure to use API bindings for it."); - } - else if (string.Equals(token.Value, ApiTokens.Event, StringComparison.Ordinal)) - { - var eventInfo = ParseEvent(ref context); - var scopeInfo = context.ValidScopeInfoFromStack; - - if (scopeInfo is ClassInfo classInfo) - classInfo.Events.Add(eventInfo); - else - throw new Exception($"Not supported location for event {eventInfo.Name} at line {tokenizer.CurrentLine}. Place it in the class to use API bindings for it."); - } - else if (string.Equals(token.Value, ApiTokens.InjectCppCode, StringComparison.Ordinal)) - { - var injectCppCodeInfo = ParseInjectCppCode(ref context); - fileInfo.AddChild(injectCppCodeInfo); - } - else if (string.Equals(token.Value, ApiTokens.AutoSerialization, StringComparison.Ordinal)) - { - if (context.ScopeInfo is ClassInfo classInfo) - classInfo.IsAutoSerialization = true; - else if (context.ScopeInfo is StructureInfo structureInfo) - structureInfo.IsAutoSerialization = true; - else - throw new Exception($"Not supported location for {ApiTokens.AutoSerialization} at line {tokenizer.CurrentLine}. Place it in the class or structure that uses API bindings."); - } - } - - // Track access level inside class - if (context.ScopeInfo != null && token.Type == TokenType.Colon && prevToken != null && prevToken.Type == TokenType.Identifier) - { - if (string.Equals(prevToken.Value, "public", StringComparison.Ordinal)) - { - context.CurrentAccessLevel = AccessLevel.Public; - } - else if (string.Equals(prevToken.Value, "protected", StringComparison.Ordinal)) - { - context.CurrentAccessLevel = AccessLevel.Protected; - } - else if (string.Equals(prevToken.Value, "private", StringComparison.Ordinal)) - { - context.CurrentAccessLevel = AccessLevel.Private; - } - } - - // Handle preprocessor blocks - if (token.Type == TokenType.Preprocessor) - { - token = tokenizer.NextToken(); - switch (token.Value) - { - case "define": - { - token = tokenizer.NextToken(); - var name = token.Value; - var value = string.Empty; - token = tokenizer.NextToken(true); - while (token.Type != TokenType.Newline) - { - value += token.Value; - token = tokenizer.NextToken(true); - } - value = value.Trim(); - context.PreprocessorDefines[name] = value; - break; - } - case "if": - { - // Parse condition - var condition = string.Empty; - token = tokenizer.NextToken(true); - while (token.Type != TokenType.Newline) - { - condition += token.Value; - token = tokenizer.NextToken(true); - } - - // Replace contents with defines - condition = condition.Trim(); - condition = ReplacePreProcessorDefines(condition, context.PreprocessorDefines.Keys); - condition = ReplacePreProcessorDefines(condition, moduleOptions.PublicDefinitions); - condition = ReplacePreProcessorDefines(condition, moduleOptions.PrivateDefinitions); - condition = ReplacePreProcessorDefines(condition, moduleOptions.CompileEnv.PreprocessorDefinitions); - condition = condition.Replace("false", "0"); - condition = condition.Replace("true", "1"); - - // Check condition - // TODO: support expressions in preprocessor defines in API headers? - if (condition != "1") - { - // Skip chunk of code - ParsePreprocessorIf(fileInfo, tokenizer, ref token); - } - - break; - } - case "ifdef": - { - // Parse condition - var define = string.Empty; - token = tokenizer.NextToken(true); - while (token.Type != TokenType.Newline) - { - define += token.Value; - token = tokenizer.NextToken(true); - } - - // Check condition - define = define.Trim(); - if (!context.PreprocessorDefines.ContainsKey(define) && !moduleOptions.CompileEnv.PreprocessorDefinitions.Contains(define)) - { - ParsePreprocessorIf(fileInfo, tokenizer, ref token); - } - - break; - } - } - } - - // Scope tracking - if (token.Type == TokenType.LeftCurlyBrace) - { - context.ScopeTypeStack.Push(scopeType); - context.ScopeInfo = context.ScopeTypeStack.Peek(); - scopeType = null; - } - else if (token.Type == TokenType.RightCurlyBrace) - { - context.ScopeTypeStack.Pop(); - if (context.ScopeTypeStack.Count == 0) - throw new Exception($"Mismatch of the {{}} braces pair in file '{fileInfo.Name}' at line {tokenizer.CurrentLine}."); - context.ScopeInfo = context.ScopeTypeStack.Peek(); - if (context.ScopeInfo is FileInfo) - context.CurrentAccessLevel = AccessLevel.Public; - } - - prevToken = token; + moduleInfo.Init(buildData); } - } - catch (Exception ex) - { - Log.Error($"Failed to parse '{fileInfo.Name}' file to generate bindings."); - Log.Exception(ex); - throw; + + return moduleInfo; } } + // Parse bindings + Log.Verbose($"Parsing API bindings for {module.Name} ({moduleInfo.Name})"); + int concurrency = Math.Min(Math.Max(1, (int)(Environment.ProcessorCount * Configuration.ConcurrencyProcessorScale)), Configuration.MaxConcurrency); + concurrency = 1; // Disable concurrency for parsing (the gain is unnoticeable or even worse in some cases) + if (concurrency == 1 || headerFiles.Count < 2 * concurrency) + { + // Single-threaded + for (int i = 0; i < headerFiles.Count; i++) + { + using (new ProfileEventScope(Path.GetFileName(headerFiles[i]))) + { + ParseModuleInnerAsync(moduleInfo, moduleOptions, headerFiles, i); + } + } + } + else + { + // Multi-threaded + ThreadPool.GetMinThreads(out var workerThreads, out var completionPortThreads); + if (workerThreads != concurrency) + ThreadPool.SetMaxThreads(concurrency, completionPortThreads); + Parallel.For(0, headerFiles.Count, (i, state) => + { + using (new ProfileEventScope(Path.GetFileName(headerFiles[i]))) + { + ParseModuleInnerAsync(moduleInfo, moduleOptions, headerFiles, i); + } + }); + } + + // Save cache + using (new ProfileEventScope("SaveCache")) + { + SaveCache(moduleInfo, moduleOptions, headerFiles); + } + // Initialize API - moduleInfo.Init(buildData); + using (new ProfileEventScope("Init")) + { + moduleInfo.Init(buildData); + } return moduleInfo; } + private static void ParseModuleInnerAsync(ModuleInfo moduleInfo, BuildOptions moduleOptions, List headerFiles, int workIndex) + { + // Find and load files with API tags + bool hasApi = false; + string headerFileContents = File.ReadAllText(headerFiles[workIndex]); + for (int j = 0; j < ApiTokens.SearchTags.Length; j++) + { + if (headerFileContents.Contains(ApiTokens.SearchTags[j])) + { + hasApi = true; + break; + } + } + if (!hasApi) + return; + + // Process header file to generate the module API code reflection + var fileInfo = new FileInfo + { + Parent = null, + Name = headerFiles[workIndex], + Namespace = moduleInfo.Name, + }; + lock (moduleInfo) + { + moduleInfo.AddChild(fileInfo); + } + + try + { + // Tokenize the source + var tokenizer = new Tokenizer(); + tokenizer.Tokenize(headerFileContents); + + // Init the context + var context = new ParsingContext + { + File = fileInfo, + Tokenizer = tokenizer, + ScopeInfo = null, + CurrentAccessLevel = AccessLevel.Public, + ScopeTypeStack = new Stack(), + ScopeAccessStack = new Stack(), + PreprocessorDefines = new Dictionary(), + }; + context.EnterScope(fileInfo); + + // Process the source code + ApiTypeInfo scopeType = null; + Token prevToken = null; + while (true) + { + // Move to the next token + var token = tokenizer.NextToken(); + if (token == null) + continue; + if (token.Type == TokenType.EndOfFile) + break; + + // Parse API_.. tags in source code + if (token.Type == TokenType.Identifier && token.Value.StartsWith("API_", StringComparison.Ordinal)) + { + if (string.Equals(token.Value, ApiTokens.Class, StringComparison.Ordinal)) + { + if (!(context.ScopeInfo is FileInfo)) + throw new NotImplementedException("TODO: add support for nested classes in scripting API"); + + var classInfo = ParseClass(ref context); + scopeType = classInfo; + context.ScopeInfo.AddChild(scopeType); + context.CurrentAccessLevel = AccessLevel.Public; + } + else if (string.Equals(token.Value, ApiTokens.Property, StringComparison.Ordinal)) + { + var propertyInfo = ParseProperty(ref context); + } + else if (string.Equals(token.Value, ApiTokens.Function, StringComparison.Ordinal)) + { + var functionInfo = ParseFunction(ref context); + + if (context.ScopeInfo is ClassInfo classInfo) + classInfo.Functions.Add(functionInfo); + else if (context.ScopeInfo is StructureInfo structureInfo) + structureInfo.Functions.Add(functionInfo); + else + throw new Exception($"Not supported free-function {functionInfo.Name} at line {tokenizer.CurrentLine}. Place it in the class to use API bindings for it."); + } + else if (string.Equals(token.Value, ApiTokens.Enum, StringComparison.Ordinal)) + { + var enumInfo = ParseEnum(ref context); + context.ScopeInfo.AddChild(enumInfo); + } + else if (string.Equals(token.Value, ApiTokens.Struct, StringComparison.Ordinal)) + { + var structureInfo = ParseStructure(ref context); + scopeType = structureInfo; + context.ScopeInfo.AddChild(scopeType); + context.CurrentAccessLevel = AccessLevel.Public; + } + else if (string.Equals(token.Value, ApiTokens.Field, StringComparison.Ordinal)) + { + var fieldInfo = ParseField(ref context); + var scopeInfo = context.ValidScopeInfoFromStack; + + if (scopeInfo is ClassInfo classInfo) + classInfo.Fields.Add(fieldInfo); + else if (scopeInfo is StructureInfo structureInfo) + structureInfo.Fields.Add(fieldInfo); + else + throw new Exception($"Not supported location for field {fieldInfo.Name} at line {tokenizer.CurrentLine}. Place it in the class or structure to use API bindings for it."); + } + else if (string.Equals(token.Value, ApiTokens.Event, StringComparison.Ordinal)) + { + var eventInfo = ParseEvent(ref context); + var scopeInfo = context.ValidScopeInfoFromStack; + + if (scopeInfo is ClassInfo classInfo) + classInfo.Events.Add(eventInfo); + else + throw new Exception($"Not supported location for event {eventInfo.Name} at line {tokenizer.CurrentLine}. Place it in the class to use API bindings for it."); + } + else if (string.Equals(token.Value, ApiTokens.InjectCppCode, StringComparison.Ordinal)) + { + var injectCppCodeInfo = ParseInjectCppCode(ref context); + fileInfo.AddChild(injectCppCodeInfo); + } + else if (string.Equals(token.Value, ApiTokens.Interface, StringComparison.Ordinal)) + { + if (!(context.ScopeInfo is FileInfo)) + throw new NotImplementedException("TODO: add support for nested interfaces in scripting API"); + + var interfaceInfo = ParseInterface(ref context); + scopeType = interfaceInfo; + context.ScopeInfo.AddChild(scopeType); + context.CurrentAccessLevel = AccessLevel.Public; + } + else if (string.Equals(token.Value, ApiTokens.AutoSerialization, StringComparison.Ordinal)) + { + if (context.ScopeInfo is ClassInfo classInfo) + classInfo.IsAutoSerialization = true; + else if (context.ScopeInfo is StructureInfo structureInfo) + structureInfo.IsAutoSerialization = true; + else + throw new Exception($"Not supported location for {ApiTokens.AutoSerialization} at line {tokenizer.CurrentLine}. Place it in the class or structure that uses API bindings."); + } + } + + // Track access level inside class + if (context.ScopeInfo != null && token.Type == TokenType.Colon && prevToken != null && prevToken.Type == TokenType.Identifier) + { + if (string.Equals(prevToken.Value, "public", StringComparison.Ordinal)) + { + context.CurrentAccessLevel = AccessLevel.Public; + } + else if (string.Equals(prevToken.Value, "protected", StringComparison.Ordinal)) + { + context.CurrentAccessLevel = AccessLevel.Protected; + } + else if (string.Equals(prevToken.Value, "private", StringComparison.Ordinal)) + { + context.CurrentAccessLevel = AccessLevel.Private; + } + } + + // Handle preprocessor blocks + if (token.Type == TokenType.Preprocessor) + { + token = tokenizer.NextToken(); + switch (token.Value) + { + case "define": + { + token = tokenizer.NextToken(); + var name = token.Value; + var value = string.Empty; + token = tokenizer.NextToken(true); + while (token.Type != TokenType.Newline) + { + value += token.Value; + token = tokenizer.NextToken(true); + } + value = value.Trim(); + context.PreprocessorDefines[name] = value; + break; + } + case "if": + { + // Parse condition + var condition = string.Empty; + token = tokenizer.NextToken(true); + while (token.Type != TokenType.Newline) + { + var tokenValue = token.Value.Trim(); + if (tokenValue.Length == 0) + { + token = tokenizer.NextToken(true); + continue; + } + + // Very simple defines processing + tokenValue = ReplacePreProcessorDefines(tokenValue, context.PreprocessorDefines.Keys); + tokenValue = ReplacePreProcessorDefines(tokenValue, moduleOptions.PublicDefinitions); + tokenValue = ReplacePreProcessorDefines(tokenValue, moduleOptions.PrivateDefinitions); + tokenValue = ReplacePreProcessorDefines(tokenValue, moduleOptions.CompileEnv.PreprocessorDefinitions); + tokenValue = tokenValue.Replace("false", "0"); + tokenValue = tokenValue.Replace("true", "1"); + tokenValue = tokenValue.Replace("||", "|"); + if (tokenValue.Length != 0 && tokenValue != "1" && tokenValue != "0" && tokenValue != "|") + tokenValue = "0"; + + condition += tokenValue; + token = tokenizer.NextToken(true); + } + + // Filter condition + condition = condition.Replace("1|1", "1"); + condition = condition.Replace("1|0", "1"); + condition = condition.Replace("0|1", "1"); + + // Skip chunk of code of condition fails + if (condition != "1") + { + ParsePreprocessorIf(fileInfo, tokenizer, ref token); + } + + break; + } + case "ifdef": + { + // Parse condition + var define = string.Empty; + token = tokenizer.NextToken(true); + while (token.Type != TokenType.Newline) + { + define += token.Value; + token = tokenizer.NextToken(true); + } + + // Check condition + define = define.Trim(); + if (!context.PreprocessorDefines.ContainsKey(define) && !moduleOptions.CompileEnv.PreprocessorDefinitions.Contains(define)) + { + ParsePreprocessorIf(fileInfo, tokenizer, ref token); + } + + break; + } + } + } + + // Scope tracking + if (token.Type == TokenType.LeftCurlyBrace) + { + context.ScopeTypeStack.Push(scopeType); + context.ScopeInfo = context.ScopeTypeStack.Peek(); + scopeType = null; + } + else if (token.Type == TokenType.RightCurlyBrace) + { + context.ScopeTypeStack.Pop(); + if (context.ScopeTypeStack.Count == 0) + throw new Exception($"Mismatch of the {{}} braces pair in file '{fileInfo.Name}' at line {tokenizer.CurrentLine}."); + context.ScopeInfo = context.ScopeTypeStack.Peek(); + if (context.ScopeInfo is FileInfo) + context.CurrentAccessLevel = AccessLevel.Public; + } + + prevToken = token; + } + } + catch (Exception ex) + { + Log.Error($"Failed to parse '{fileInfo.Name}' file to generate bindings."); + Log.Exception(ex); + throw; + } + } + private static string ReplacePreProcessorDefines(string text, IEnumerable defines) { foreach (var define in defines) { - if (text.Contains(define)) + if (string.Equals(text, define, StringComparison.Ordinal)) text = text.Replace(define, "1"); } return text; @@ -426,41 +479,82 @@ namespace Flax.Build.Bindings throw new Exception($"Invalid #if/endif pairing in file '{fileInfo.Name}'. Failed to generate API bindings for it."); } + private static bool UseBindings(object type) + { + var apiTypeInfo = type as ApiTypeInfo; + if (apiTypeInfo != null && apiTypeInfo.IsInBuild) + return false; + if ((type is ModuleInfo || type is FileInfo) && apiTypeInfo != null) + { + foreach (var child in apiTypeInfo.Children) + { + if (UseBindings(child)) + return true; + } + } + return type is ClassInfo || + type is StructureInfo || + type is InterfaceInfo || + type is InjectCppCodeInfo; + } + /// /// The API bindings generation utility that can produce scripting bindings for another languages to the native code. /// public static void GenerateBindings(BuildData buildData, Module module, ref BuildOptions moduleOptions, out BindingsResult bindings) { // Parse module (or load from cache) + var moduleInfo = ParseModule(buildData, module, moduleOptions); bindings = new BindingsResult { + UseBindings = UseBindings(moduleInfo), GeneratedCppFilePath = Path.Combine(moduleOptions.IntermediateFolder, module.Name + ".Bindings.Gen.cpp"), GeneratedCSharpFilePath = Path.Combine(moduleOptions.IntermediateFolder, module.Name + ".Bindings.Gen.cs"), }; - var moduleInfo = ParseModule(buildData, module, moduleOptions); + + if (bindings.UseBindings) + { + buildData.Modules.TryGetValue(moduleInfo.Module, out var moduleBuildInfo); + + // Ensure that generated files are included into build + if (!moduleBuildInfo.SourceFiles.Contains(bindings.GeneratedCSharpFilePath)) + moduleBuildInfo.SourceFiles.Add(bindings.GeneratedCSharpFilePath); + } + + // Skip if module is cached (no scripting API changed) + if (moduleInfo.IsFromCache) + return; // Process parsed API - foreach (var child in moduleInfo.Children) + using (new ProfileEventScope("Process")) { - try + foreach (var child in moduleInfo.Children) { - foreach (var apiTypeInfo in child.Children) - ProcessAndValidate(buildData, apiTypeInfo); - } - catch (Exception) - { - if (child is FileInfo fileInfo) - Log.Error($"Failed to validate '{fileInfo.Name}' file to generate bindings."); - throw; + try + { + foreach (var apiTypeInfo in child.Children) + ProcessAndValidate(buildData, apiTypeInfo); + } + catch (Exception) + { + if (child is FileInfo fileInfo) + Log.Error($"Failed to validate '{fileInfo.Name}' file to generate bindings."); + throw; + } } } // Generate bindings for scripting - Log.Verbose($"Generating API bindings for {module.Name} ({moduleInfo.Name})"); - GenerateCpp(buildData, moduleInfo, ref bindings); - GenerateCSharp(buildData, moduleInfo, ref bindings); + if (bindings.UseBindings) + { + Log.Verbose($"Generating API bindings for {module.Name} ({moduleInfo.Name})"); + using (new ProfileEventScope("Cpp")) + GenerateCpp(buildData, moduleInfo, ref bindings); + using (new ProfileEventScope("CSharp")) + GenerateCSharp(buildData, moduleInfo, ref bindings); - // TODO: add support for extending this code and support generating bindings for other scripting languages + // TODO: add support for extending this code and support generating bindings for other scripting languages + } } /// @@ -568,7 +662,6 @@ namespace Flax.Build.Bindings { foreach (var fieldInfo in structureInfo.Fields) { - // TODO: support bit-fields in structure fields if (fieldInfo.Type.IsBitField) throw new NotImplementedException($"TODO: support bit-fields in structure fields (found field {fieldInfo} in structure {structureInfo.Name})"); diff --git a/Source/Tools/Flax.Build/Bindings/ClassInfo.cs b/Source/Tools/Flax.Build/Bindings/ClassInfo.cs index 1a62132b2..c68ff1a59 100644 --- a/Source/Tools/Flax.Build/Bindings/ClassInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/ClassInfo.cs @@ -1,6 +1,7 @@ -// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved. +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using System.Collections.Generic; +using System.IO; using System.Linq; namespace Flax.Build.Bindings @@ -8,7 +9,7 @@ namespace Flax.Build.Bindings /// /// The native class information for bindings generator. /// - public class ClassInfo : ApiTypeInfo + public class ClassInfo : ClassStructInfo { private static readonly HashSet InBuildScriptingObjectTypes = new HashSet { @@ -22,19 +23,17 @@ namespace Flax.Build.Bindings "Actor", }; - public AccessLevel Access; - public TypeInfo BaseType; - public AccessLevel BaseTypeInheritance; + public bool IsBaseTypeHidden; public bool IsStatic; public bool IsSealed; public bool IsAbstract; public bool IsAutoSerialization; public bool NoSpawn; public bool NoConstructor; - public List Functions; - public List Properties; - public List Fields; - public List Events; + public List Functions = new List(); + public List Properties = new List(); + public List Fields = new List(); + public List Events = new List(); internal HashSet UniqueFunctionNames; @@ -50,6 +49,9 @@ namespace Flax.Build.Bindings { base.Init(buildData); + // Internal base types are usually hidden from bindings (used in core-only internally) + IsBaseTypeHidden = BaseTypeInheritance == AccessLevel.Private || BaseType == null; + // Cache if it it Scripting Object type if (InBuildScriptingObjectTypes.Contains(Name)) _isScriptingObject = true; @@ -73,6 +75,40 @@ namespace Flax.Build.Bindings } } + public override void Write(BinaryWriter writer) + { + // TODO: convert into flags + writer.Write(IsStatic); + writer.Write(IsSealed); + writer.Write(IsAbstract); + writer.Write(IsAutoSerialization); + writer.Write(NoSpawn); + writer.Write(NoConstructor); + BindingsGenerator.Write(writer, Functions); + BindingsGenerator.Write(writer, Properties); + BindingsGenerator.Write(writer, Fields); + BindingsGenerator.Write(writer, Events); + + base.Write(writer); + } + + public override void Read(BinaryReader reader) + { + // TODO: convert into flags + IsStatic = reader.ReadBoolean(); + IsSealed = reader.ReadBoolean(); + IsAbstract = reader.ReadBoolean(); + IsAutoSerialization = reader.ReadBoolean(); + NoSpawn = reader.ReadBoolean(); + NoConstructor = reader.ReadBoolean(); + Functions = BindingsGenerator.Read(reader, Functions); + Properties = BindingsGenerator.Read(reader, Properties); + Fields = BindingsGenerator.Read(reader, Fields); + Events = BindingsGenerator.Read(reader, Events); + + base.Read(reader); + } + public int GetScriptVTableSize(Builder.BuildData buildData, out int offset) { if (_scriptVTableSize == -1) diff --git a/Source/Tools/Flax.Build/Bindings/ClassStructInfo.cs b/Source/Tools/Flax.Build/Bindings/ClassStructInfo.cs new file mode 100644 index 000000000..2f0dad003 --- /dev/null +++ b/Source/Tools/Flax.Build/Bindings/ClassStructInfo.cs @@ -0,0 +1,60 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +using System.Collections.Generic; +using System.IO; + +namespace Flax.Build.Bindings +{ + /// + /// The native class/structure information for bindings generator. + /// + public abstract class ClassStructInfo : ApiTypeInfo + { + public AccessLevel Access; + public AccessLevel BaseTypeInheritance; + public TypeInfo BaseType; + public List Interfaces; // Optional + public List InterfaceNames; // Optional + + public override void Init(Builder.BuildData buildData) + { + base.Init(buildData); + + if (Interfaces == null && InterfaceNames != null && InterfaceNames.Count != 0) + { + Interfaces = new List(); + for (var i = 0; i < InterfaceNames.Count; i++) + { + var interfaceName = InterfaceNames[i]; + var apiTypeInfo = BindingsGenerator.FindApiTypeInfo(buildData, interfaceName, this); + if (apiTypeInfo is InterfaceInfo interfaceInfo) + { + Interfaces.Add(interfaceInfo); + } + } + if (Interfaces.Count == 0) + Interfaces = null; + } + } + + public override void Write(BinaryWriter writer) + { + writer.Write((byte)Access); + writer.Write((byte)BaseTypeInheritance); + BindingsGenerator.Write(writer, BaseType); + BindingsGenerator.Write(writer, InterfaceNames); + + base.Write(writer); + } + + public override void Read(BinaryReader reader) + { + Access = (AccessLevel)reader.ReadByte(); + BaseTypeInheritance = (AccessLevel)reader.ReadByte(); + BaseType = BindingsGenerator.Read(reader, BaseType); + InterfaceNames = BindingsGenerator.Read(reader, InterfaceNames); + + base.Read(reader); + } + } +} diff --git a/Source/Tools/Flax.Build/Bindings/EnumInfo.cs b/Source/Tools/Flax.Build/Bindings/EnumInfo.cs index eeb750255..ddfcb3a38 100644 --- a/Source/Tools/Flax.Build/Bindings/EnumInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/EnumInfo.cs @@ -1,7 +1,8 @@ -// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved. +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using System; using System.Collections.Generic; +using System.IO; namespace Flax.Build.Bindings { @@ -10,7 +11,7 @@ namespace Flax.Build.Bindings /// public class EnumInfo : ApiTypeInfo { - public struct EntryInfo + public struct EntryInfo : IBindingsCache { public string Name; public string[] Comment; @@ -21,11 +22,27 @@ namespace Flax.Build.Bindings { return Name + (string.IsNullOrEmpty(Value) ? string.Empty : " = " + Value); } + + public void Write(BinaryWriter writer) + { + writer.Write(Name); + BindingsGenerator.Write(writer, Comment); + BindingsGenerator.Write(writer, Value); + BindingsGenerator.Write(writer, Attributes); + } + + public void Read(BinaryReader reader) + { + Name = reader.ReadString(); + Comment = BindingsGenerator.Read(reader, Comment); + Value = BindingsGenerator.Read(reader, Value); + Attributes = BindingsGenerator.Read(reader, Attributes); + } } public AccessLevel Access; public TypeInfo UnderlyingType; - public List Entries; + public List Entries = new List(); public override bool IsValueType => true; public override bool IsEnum => true; @@ -36,6 +53,24 @@ namespace Flax.Build.Bindings throw new NotSupportedException("API enums cannot have sub-types."); } + public override void Write(BinaryWriter writer) + { + writer.Write((byte)Access); + BindingsGenerator.Write(writer, UnderlyingType); + BindingsGenerator.Write(writer, Entries); + + base.Write(writer); + } + + public override void Read(BinaryReader reader) + { + Access = (AccessLevel)reader.ReadByte(); + UnderlyingType = BindingsGenerator.Read(reader, UnderlyingType); + Entries = BindingsGenerator.Read(reader, Entries); + + base.Read(reader); + } + public override string ToString() { return "enum " + Name; diff --git a/Source/Tools/Flax.Build/Bindings/EventInfo.cs b/Source/Tools/Flax.Build/Bindings/EventInfo.cs index 7d98979b4..905bee803 100644 --- a/Source/Tools/Flax.Build/Bindings/EventInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/EventInfo.cs @@ -1,4 +1,6 @@ -// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved. +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +using System.IO; namespace Flax.Build.Bindings { @@ -9,6 +11,20 @@ namespace Flax.Build.Bindings { public TypeInfo Type; + public override void Write(BinaryWriter writer) + { + BindingsGenerator.Write(writer, Type); + + base.Write(writer); + } + + public override void Read(BinaryReader reader) + { + Type = BindingsGenerator.Read(reader, Type); + + base.Read(reader); + } + public override string ToString() { var result = string.Empty; diff --git a/Source/Tools/Flax.Build/Bindings/FieldInfo.cs b/Source/Tools/Flax.Build/Bindings/FieldInfo.cs index 29c8b0617..d01cde5e7 100644 --- a/Source/Tools/Flax.Build/Bindings/FieldInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/FieldInfo.cs @@ -1,4 +1,6 @@ -// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved. +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +using System.IO; namespace Flax.Build.Bindings { @@ -12,6 +14,31 @@ namespace Flax.Build.Bindings public bool NoArray; public FunctionInfo Getter; public FunctionInfo Setter; + public string DefaultValue; + + public bool HasDefaultValue => !string.IsNullOrEmpty(DefaultValue); + + public override void Write(BinaryWriter writer) + { + BindingsGenerator.Write(writer, Type); + // TODO: convert into flags + writer.Write(IsReadOnly); + writer.Write(NoArray); + BindingsGenerator.Write(writer, DefaultValue); + + base.Write(writer); + } + + public override void Read(BinaryReader reader) + { + Type = BindingsGenerator.Read(reader, Type); + // TODO: convert into flags + IsReadOnly = reader.ReadBoolean(); + NoArray = reader.ReadBoolean(); + DefaultValue = BindingsGenerator.Read(reader, DefaultValue); + + base.Read(reader); + } public override string ToString() { @@ -19,6 +46,8 @@ namespace Flax.Build.Bindings if (IsStatic) result += "static "; result += Type + " " + Name; + if (HasDefaultValue) + result += " = " + DefaultValue; return result; } } diff --git a/Source/Tools/Flax.Build/Bindings/FileInfo.cs b/Source/Tools/Flax.Build/Bindings/FileInfo.cs index 38f2e491f..6c4b4dcfe 100644 --- a/Source/Tools/Flax.Build/Bindings/FileInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/FileInfo.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved. +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using System; @@ -7,7 +7,7 @@ namespace Flax.Build.Bindings /// /// The native file information for bindings generator. /// - public class FileInfo : ApiTypeInfo, IComparable + public class FileInfo : ApiTypeInfo, IComparable, IComparable { public override void AddChild(ApiTypeInfo apiTypeInfo) { @@ -26,5 +26,12 @@ namespace Flax.Build.Bindings { return System.IO.Path.GetFileName(Name); } + + public int CompareTo(object obj) + { + if (obj is ApiTypeInfo apiTypeInfo) + return Name.CompareTo(apiTypeInfo.Name); + return 0; + } } } diff --git a/Source/Tools/Flax.Build/Bindings/FunctionInfo.cs b/Source/Tools/Flax.Build/Bindings/FunctionInfo.cs index 81ea02796..a78c9242a 100644 --- a/Source/Tools/Flax.Build/Bindings/FunctionInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/FunctionInfo.cs @@ -1,6 +1,7 @@ -// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved. +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using System.Collections.Generic; +using System.IO; namespace Flax.Build.Bindings { @@ -9,7 +10,7 @@ namespace Flax.Build.Bindings /// public class FunctionInfo : MemberInfo { - public struct ParameterInfo + public struct ParameterInfo : IBindingsCache { public string Name; public TypeInfo Type; @@ -25,6 +26,28 @@ namespace Flax.Build.Bindings return Attributes != null && Attributes.Contains(name); } + public void Write(BinaryWriter writer) + { + writer.Write(Name); + BindingsGenerator.Write(writer, Type); + BindingsGenerator.Write(writer, DefaultValue); + BindingsGenerator.Write(writer, Attributes); + // TODO: convert into flags + writer.Write(IsRef); + writer.Write(IsOut); + } + + public void Read(BinaryReader reader) + { + Name = reader.ReadString(); + Type = BindingsGenerator.Read(reader, Type); + DefaultValue = BindingsGenerator.Read(reader, DefaultValue); + Attributes = BindingsGenerator.Read(reader, Attributes); + // TODO: convert into flags + IsRef = reader.ReadBoolean(); + IsOut = reader.ReadBoolean(); + } + public override string ToString() { var result = Type + " " + Name; @@ -42,12 +65,36 @@ namespace Flax.Build.Bindings public string UniqueName; public TypeInfo ReturnType; - public List Parameters; + public List Parameters = new List(); public bool IsVirtual; public bool IsConst; public bool NoProxy; public GlueInfo Glue; + public override void Write(BinaryWriter writer) + { + BindingsGenerator.Write(writer, ReturnType); + BindingsGenerator.Write(writer, Parameters); + // TODO: convert into flags + writer.Write(IsVirtual); + writer.Write(IsConst); + writer.Write(NoProxy); + + base.Write(writer); + } + + public override void Read(BinaryReader reader) + { + ReturnType = BindingsGenerator.Read(reader, ReturnType); + Parameters = BindingsGenerator.Read(reader, Parameters); + // TODO: convert into flags + IsVirtual = reader.ReadBoolean(); + IsConst = reader.ReadBoolean(); + NoProxy = reader.ReadBoolean(); + + base.Read(reader); + } + public override string ToString() { var result = string.Empty; diff --git a/Source/Tools/Flax.Build/Bindings/InheritanceInfo.cs b/Source/Tools/Flax.Build/Bindings/InheritanceInfo.cs index 01ab2e292..4a8673660 100644 --- a/Source/Tools/Flax.Build/Bindings/InheritanceInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/InheritanceInfo.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved. +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. namespace Flax.Build.Bindings { diff --git a/Source/Tools/Flax.Build/Bindings/InjectCppCodeInfo.cs b/Source/Tools/Flax.Build/Bindings/InjectCppCodeInfo.cs index 7deee98c6..0bb53fc99 100644 --- a/Source/Tools/Flax.Build/Bindings/InjectCppCodeInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/InjectCppCodeInfo.cs @@ -1,4 +1,6 @@ -// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved. +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +using System.IO; namespace Flax.Build.Bindings { @@ -9,6 +11,20 @@ namespace Flax.Build.Bindings { public string Code; + public override void Write(BinaryWriter writer) + { + writer.Write(Code); + + base.Write(writer); + } + + public override void Read(BinaryReader reader) + { + Code = reader.ReadString(); + + base.Read(reader); + } + /// public override string ToString() { diff --git a/Source/Tools/Flax.Build/Bindings/InterfaceInfo.cs b/Source/Tools/Flax.Build/Bindings/InterfaceInfo.cs new file mode 100644 index 000000000..e0225f288 --- /dev/null +++ b/Source/Tools/Flax.Build/Bindings/InterfaceInfo.cs @@ -0,0 +1,24 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +namespace Flax.Build.Bindings +{ + /// + /// The native class/structure interface information for bindings generator. + /// + public class InterfaceInfo : ClassStructInfo + { + public override bool IsInterface => true; + + public override void AddChild(ApiTypeInfo apiTypeInfo) + { + apiTypeInfo.Namespace = null; + + base.AddChild(apiTypeInfo); + } + + public override string ToString() + { + return "interface " + Name; + } + } +} diff --git a/Source/Tools/Flax.Build/Bindings/LangType.cs b/Source/Tools/Flax.Build/Bindings/LangType.cs index d74560bc6..7f5f2da03 100644 --- a/Source/Tools/Flax.Build/Bindings/LangType.cs +++ b/Source/Tools/Flax.Build/Bindings/LangType.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved. +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using System; diff --git a/Source/Tools/Flax.Build/Bindings/MemberInfo.cs b/Source/Tools/Flax.Build/Bindings/MemberInfo.cs index 63034f57a..49187c101 100644 --- a/Source/Tools/Flax.Build/Bindings/MemberInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/MemberInfo.cs @@ -1,11 +1,13 @@ -// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved. +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +using System.IO; namespace Flax.Build.Bindings { /// /// The native member information for bindings generator. /// - public class MemberInfo + public class MemberInfo : IBindingsCache { public string Name; public string[] Comment; @@ -17,5 +19,23 @@ namespace Flax.Build.Bindings { return Attributes != null && Attributes.Contains(name); } + + public virtual void Write(BinaryWriter writer) + { + writer.Write(Name); + BindingsGenerator.Write(writer, Comment); + writer.Write(IsStatic); + writer.Write((byte)Access); + BindingsGenerator.Write(writer, Attributes); + } + + public virtual void Read(BinaryReader reader) + { + Name = reader.ReadString(); + Comment = BindingsGenerator.Read(reader, Comment); + IsStatic = reader.ReadBoolean(); + Access = (AccessLevel)reader.ReadByte(); + Attributes = BindingsGenerator.Read(reader, Attributes); + } } } diff --git a/Source/Tools/Flax.Build/Bindings/ModuleInfo.cs b/Source/Tools/Flax.Build/Bindings/ModuleInfo.cs index 554d46c01..68742b554 100644 --- a/Source/Tools/Flax.Build/Bindings/ModuleInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/ModuleInfo.cs @@ -1,4 +1,7 @@ -// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved. +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +using System; +using System.IO; namespace Flax.Build.Bindings { @@ -8,10 +11,41 @@ namespace Flax.Build.Bindings public class ModuleInfo : ApiTypeInfo { public Module Module; + public bool IsFromCache; public override string ToString() { return "module " + Name; } + + /// + public override void Init(Builder.BuildData buildData) + { + base.Init(buildData); + + // Sort module files to prevent bindings rebuild due to order changes (list might be created in async) + Children.Sort(); + } + + public override void Write(BinaryWriter writer) + { + writer.Write(Module.Name); + writer.Write(Module.FilePath); + BindingsGenerator.Write(writer, Module.BinaryModuleName); + writer.Write(Module.BuildNativeCode); + + base.Write(writer); + } + + public override void Read(BinaryReader reader) + { + if (reader.ReadString() != Module.Name || + reader.ReadString() != Module.FilePath || + BindingsGenerator.Read(reader, Module.BinaryModuleName) != Module.BinaryModuleName || + reader.ReadBoolean() != Module.BuildNativeCode) + throw new Exception(); + + base.Read(reader); + } } } diff --git a/Source/Tools/Flax.Build/Bindings/PropertyInfo.cs b/Source/Tools/Flax.Build/Bindings/PropertyInfo.cs index b36821511..96b0f8b93 100644 --- a/Source/Tools/Flax.Build/Bindings/PropertyInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/PropertyInfo.cs @@ -1,4 +1,6 @@ -// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved. +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +using System.IO; namespace Flax.Build.Bindings { @@ -11,6 +13,24 @@ namespace Flax.Build.Bindings public FunctionInfo Getter; public FunctionInfo Setter; + public override void Write(BinaryWriter writer) + { + BindingsGenerator.Write(writer, Type); + BindingsGenerator.Write(writer, Getter); + BindingsGenerator.Write(writer, Setter); + + base.Write(writer); + } + + public override void Read(BinaryReader reader) + { + Type = BindingsGenerator.Read(reader, Type); + Getter = BindingsGenerator.Read(reader, Getter); + Setter = BindingsGenerator.Read(reader, Setter); + + base.Read(reader); + } + public override string ToString() { return Type + " " + Name; diff --git a/Source/Tools/Flax.Build/Bindings/StructureInfo.cs b/Source/Tools/Flax.Build/Bindings/StructureInfo.cs index ca0a02247..882ed6295 100644 --- a/Source/Tools/Flax.Build/Bindings/StructureInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/StructureInfo.cs @@ -1,19 +1,18 @@ -// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved. +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using System; using System.Collections.Generic; +using System.IO; namespace Flax.Build.Bindings { /// /// The native structure information for bindings generator. /// - public class StructureInfo : ApiTypeInfo + public class StructureInfo : ClassStructInfo { - public AccessLevel Access; - public TypeInfo BaseType; - public List Fields; - public List Functions; + public List Fields = new List(); + public List Functions = new List(); public bool IsAutoSerialization; public bool ForceNoPod; @@ -27,7 +26,7 @@ namespace Flax.Build.Bindings { base.Init(buildData); - if (ForceNoPod) + if (ForceNoPod || (InterfaceNames != null && InterfaceNames.Count != 0)) { _isPod = false; return; @@ -45,6 +44,26 @@ namespace Flax.Build.Bindings } } + public override void Write(BinaryWriter writer) + { + BindingsGenerator.Write(writer, Fields); + BindingsGenerator.Write(writer, Functions); + writer.Write(IsAutoSerialization); + writer.Write(ForceNoPod); + + base.Write(writer); + } + + public override void Read(BinaryReader reader) + { + Fields = BindingsGenerator.Read(reader, Fields); + Functions = BindingsGenerator.Read(reader, Functions); + IsAutoSerialization = reader.ReadBoolean(); + ForceNoPod = reader.ReadBoolean(); + + base.Read(reader); + } + public override void AddChild(ApiTypeInfo apiTypeInfo) { if (apiTypeInfo is EnumInfo) diff --git a/Source/Tools/Flax.Build/Bindings/TypeInfo.cs b/Source/Tools/Flax.Build/Bindings/TypeInfo.cs index 962aa9523..2d15f6651 100644 --- a/Source/Tools/Flax.Build/Bindings/TypeInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/TypeInfo.cs @@ -1,7 +1,8 @@ -// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved. +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Text; @@ -10,7 +11,7 @@ namespace Flax.Build.Bindings /// /// The native type information for bindings generator. /// - public class TypeInfo : IEquatable + public class TypeInfo : IEquatable, IBindingsCache { public string Type; public bool IsConst; @@ -51,6 +52,34 @@ namespace Flax.Build.Bindings return true; } + public void Write(BinaryWriter writer) + { + BindingsGenerator.Write(writer, Type); + // TODO: pack as flags + writer.Write(IsConst); + writer.Write(IsRef); + writer.Write(IsPtr); + writer.Write(IsArray); + writer.Write(IsBitField); + writer.Write(ArraySize); + writer.Write(BitSize); + BindingsGenerator.Write(writer, GenericArgs); + } + + public void Read(BinaryReader reader) + { + Type = BindingsGenerator.Read(reader, Type); + // TODO: convert into flags + IsConst = reader.ReadBoolean(); + IsRef = reader.ReadBoolean(); + IsPtr = reader.ReadBoolean(); + IsArray = reader.ReadBoolean(); + IsBitField = reader.ReadBoolean(); + ArraySize = reader.ReadInt32(); + BitSize = reader.ReadInt32(); + GenericArgs = BindingsGenerator.Read(reader, GenericArgs); + } + public override string ToString() { var sb = new StringBuilder(64); diff --git a/Source/Tools/Flax.Build/Build/Builder.Rules.cs b/Source/Tools/Flax.Build/Build/Builder.Rules.cs index 9982e3601..1c4609c2e 100644 --- a/Source/Tools/Flax.Build/Build/Builder.Rules.cs +++ b/Source/Tools/Flax.Build/Build/Builder.Rules.cs @@ -12,12 +12,31 @@ namespace Flax.Build partial class Builder { private static RulesAssembly _rules; + private static Type[] _buildTypes; /// /// The build configuration files postfix. /// public static string BuildFilesPostfix = ".Build.cs"; + /// + /// The cached list of types from Flax.Build assembly. Reused by other build tool utilities to improve performance. + /// + internal static Type[] BuildTypes + { + get + { + if (_buildTypes == null) + { + using (new ProfileEventScope("CacheBuildTypes")) + { + _buildTypes = typeof(Program).Assembly.GetTypes(); + } + } + return _buildTypes; + } + } + /// /// The rules assembly data. /// @@ -128,7 +147,7 @@ namespace Flax.Build using (new ProfileEventScope("InitInBuildPlugins")) { - foreach (var type in typeof(Program).Assembly.GetTypes().Where(x => !x.IsAbstract && x.IsSubclassOf(typeof(Plugin)))) + foreach (var type in BuildTypes.Where(x => x.IsClass && !x.IsAbstract && x.IsSubclassOf(typeof(Plugin)))) { var plugin = (Plugin)Activator.CreateInstance(type); plugin.Init(); @@ -176,67 +195,74 @@ namespace Flax.Build // Prepare targets and modules objects Type[] types; - Target[] targetObjects; - Module[] moduleObjects; - Plugin[] pluginObjects; + var targetObjects = new List(16); + var moduleObjects = new List(256); + var pluginObjects = new List(); using (new ProfileEventScope("GetTypes")) { types = assembly.GetTypes(); - targetObjects = types.Where(x => !x.IsAbstract && x.IsSubclassOf(typeof(Target))).Select(type => + for (var i = 0; i < types.Length; i++) { - var target = (Target)Activator.CreateInstance(type); - - var targetFilename = target.Name + BuildFilesPostfix; - target.FilePath = files.FirstOrDefault(path => string.Equals(Path.GetFileName(path), targetFilename, StringComparison.OrdinalIgnoreCase)); - if (target.FilePath == null) + var type = types[i]; + if (!type.IsClass || type.IsAbstract) + continue; + if (type.IsSubclassOf(typeof(Target))) { - targetFilename = target.Name + "Target" + BuildFilesPostfix; + var target = (Target)Activator.CreateInstance(type); + + var targetFilename = target.Name + BuildFilesPostfix; target.FilePath = files.FirstOrDefault(path => string.Equals(Path.GetFileName(path), targetFilename, StringComparison.OrdinalIgnoreCase)); if (target.FilePath == null) { - if (target.Name.EndsWith("Target")) - { - targetFilename = target.Name.Substring(0, target.Name.Length - "Target".Length) + BuildFilesPostfix; - target.FilePath = files.FirstOrDefault(path => string.Equals(Path.GetFileName(path), targetFilename, StringComparison.OrdinalIgnoreCase)); - } + targetFilename = target.Name + "Target" + BuildFilesPostfix; + target.FilePath = files.FirstOrDefault(path => string.Equals(Path.GetFileName(path), targetFilename, StringComparison.OrdinalIgnoreCase)); if (target.FilePath == null) { - throw new Exception(string.Format("Failed to find source file path for {0}", target)); + if (target.Name.EndsWith("Target")) + { + targetFilename = target.Name.Substring(0, target.Name.Length - "Target".Length) + BuildFilesPostfix; + target.FilePath = files.FirstOrDefault(path => string.Equals(Path.GetFileName(path), targetFilename, StringComparison.OrdinalIgnoreCase)); + } + if (target.FilePath == null) + { + throw new Exception(string.Format("Failed to find source file path for {0}", target)); + } } } + target.FolderPath = Path.GetDirectoryName(target.FilePath); + target.Init(); + targetObjects.Add(target); } - target.FolderPath = Path.GetDirectoryName(target.FilePath); - target.Init(); - return target; - }).ToArray(); - moduleObjects = types.Where(x => !x.IsAbstract && x.IsSubclassOf(typeof(Module))).Select(type => - { - var module = (Module)Activator.CreateInstance(type); - - var moduleFilename = module.Name + BuildFilesPostfix; - module.FilePath = files.FirstOrDefault(path => string.Equals(Path.GetFileName(path), moduleFilename, StringComparison.OrdinalIgnoreCase)); - if (module.FilePath == null) + else if (type.IsSubclassOf(typeof(Module))) { - moduleFilename = module.Name + "Module" + BuildFilesPostfix; + var module = (Module)Activator.CreateInstance(type); + + var moduleFilename = module.Name + BuildFilesPostfix; module.FilePath = files.FirstOrDefault(path => string.Equals(Path.GetFileName(path), moduleFilename, StringComparison.OrdinalIgnoreCase)); if (module.FilePath == null) { - throw new Exception(string.Format("Failed to find source file path for {0}", module)); + moduleFilename = module.Name + "Module" + BuildFilesPostfix; + module.FilePath = files.FirstOrDefault(path => string.Equals(Path.GetFileName(path), moduleFilename, StringComparison.OrdinalIgnoreCase)); + if (module.FilePath == null) + { + throw new Exception(string.Format("Failed to find source file path for {0}", module)); + } } + module.FolderPath = Path.GetDirectoryName(module.FilePath); + module.Init(); + moduleObjects.Add(module); } - module.FolderPath = Path.GetDirectoryName(module.FilePath); - module.Init(); - return module; - }).ToArray(); - pluginObjects = types.Where(x => !x.IsAbstract && x.IsSubclassOf(typeof(Plugin))).Select(type => - { - var plugin = (Plugin)Activator.CreateInstance(type); - plugin.Init(); - return plugin; - }).ToArray(); + else if (type.IsSubclassOf(typeof(Plugin))) + { + var plugin = (Plugin)Activator.CreateInstance(type); + + plugin.Init(); + pluginObjects.Add(plugin); + } + } } - _rules = new RulesAssembly(assembly, targetObjects, moduleObjects, pluginObjects); + _rules = new RulesAssembly(assembly, targetObjects.ToArray(), moduleObjects.ToArray(), pluginObjects.ToArray()); } return _rules; diff --git a/Source/Tools/Flax.Build/Build/Graph/LocalExecutor.cs b/Source/Tools/Flax.Build/Build/Graph/LocalExecutor.cs index c874dba51..391b6952f 100644 --- a/Source/Tools/Flax.Build/Build/Graph/LocalExecutor.cs +++ b/Source/Tools/Flax.Build/Build/Graph/LocalExecutor.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.IO; using System.Threading; using Flax.Build.Graph; @@ -27,12 +28,12 @@ namespace Flax.Build.BuildSystem.Graph /// /// The maximum amount of threads to be used for the parallel execution. /// - public int ThreadCountMax = 1410; + public int ThreadCountMax = Configuration.MaxConcurrency; /// /// The amount of threads to allocate per processor. Use it to allocate more threads for faster execution or use less to keep reduce CPU usage during build. /// - public float ProcessorCountScale = 1.0f; + public float ProcessorCountScale = Configuration.ConcurrencyProcessorScale; /// public override int Execute(List tasks) @@ -178,7 +179,12 @@ namespace Flax.Build.BuildSystem.Graph private int ExecuteTask(Task task) { - ProcessStartInfo startInfo = new ProcessStartInfo + string name = "Task"; + if (task.ProducedFiles != null && task.ProducedFiles.Count != 0) + name = Path.GetFileName(task.ProducedFiles[0]); + var profilerEvent = Profiling.Begin(name); + + var startInfo = new ProcessStartInfo { WorkingDirectory = task.WorkingDirectory, FileName = task.CommandPath, @@ -230,10 +236,13 @@ namespace Flax.Build.BuildSystem.Graph // Hang until process end process.WaitForExit(); + Profiling.End(profilerEvent); return process.ExitCode; } finally { + Profiling.End(profilerEvent); + // Ensure to cleanup data process?.Close(); } diff --git a/Source/Tools/Flax.Build/Build/Module.cs b/Source/Tools/Flax.Build/Build/Module.cs index dae700728..726cbf946 100644 --- a/Source/Tools/Flax.Build/Build/Module.cs +++ b/Source/Tools/Flax.Build/Build/Module.cs @@ -111,5 +111,16 @@ namespace Flax.Build // By default deploy all C++ header files files.AddRange(Directory.GetFiles(FolderPath, "*.h", SearchOption.AllDirectories)); } + + /// + /// Adds the file to the build sources if exists. + /// + /// The options. + /// The source file path. + protected void AddSourceFileIfExists(BuildOptions options, string path) + { + if (File.Exists(path)) + options.SourceFiles.Add(path); + } } } diff --git a/Source/Tools/Flax.Build/Build/NativeCpp/Builder.NativeCpp.cs b/Source/Tools/Flax.Build/Build/NativeCpp/Builder.NativeCpp.cs index 70d3722fb..af50cfe1e 100644 --- a/Source/Tools/Flax.Build/Build/NativeCpp/Builder.NativeCpp.cs +++ b/Source/Tools/Flax.Build/Build/NativeCpp/Builder.NativeCpp.cs @@ -330,7 +330,12 @@ namespace Flax.Build } // Collect all files to compile - var cppFiles = moduleOptions.SourceFiles.Where(x => x.EndsWith(".cpp")).ToList(); + var cppFiles = new List(moduleOptions.SourceFiles.Count / 2); + for (int i = 0; i < moduleOptions.SourceFiles.Count; i++) + { + if (moduleOptions.SourceFiles[i].EndsWith(".cpp", StringComparison.OrdinalIgnoreCase)) + cppFiles.Add(moduleOptions.SourceFiles[i]); + } if (!string.IsNullOrEmpty(module.BinaryModuleName)) { diff --git a/Source/Tools/Flax.Build/Build/Platform.cs b/Source/Tools/Flax.Build/Build/Platform.cs index 07a18a2a9..15addce27 100644 --- a/Source/Tools/Flax.Build/Build/Platform.cs +++ b/Source/Tools/Flax.Build/Build/Platform.cs @@ -157,7 +157,7 @@ namespace Flax.Build if (_platforms == null) { using (new ProfileEventScope("GetPlatforms")) - _platforms = typeof(Platform).Assembly.GetTypes().Where(x => !x.IsAbstract && x.IsSubclassOf(typeof(Platform))).Select(Activator.CreateInstance).Cast().ToArray(); + _platforms = Builder.BuildTypes.Where(x => x.IsClass && !x.IsAbstract && x.IsSubclassOf(typeof(Platform))).Select(Activator.CreateInstance).Cast().ToArray(); } foreach (var platform in _platforms) diff --git a/Source/Tools/Flax.Build/Build/Plugins/VisualScriptingPlugin.cs b/Source/Tools/Flax.Build/Build/Plugins/VisualScriptingPlugin.cs index ecfd97d88..b2656ce0d 100644 --- a/Source/Tools/Flax.Build/Build/Plugins/VisualScriptingPlugin.cs +++ b/Source/Tools/Flax.Build/Build/Plugins/VisualScriptingPlugin.cs @@ -48,7 +48,7 @@ namespace Flax.Build.Plugins contents.AppendLine(" if (IsDuringWrapperCall)"); contents.AppendLine(" {"); contents.AppendLine(" // Prevent stack overflow by calling base method"); - contents.AppendLine(" const auto scriptVTableBase = object->GetType().Class.ScriptVTableBase;"); + contents.AppendLine(" const auto scriptVTableBase = object->GetType().Script.ScriptVTableBase;"); contents.Append($" return (object->**({functionInfo.UniqueName}_Signature*)&scriptVTableBase[{scriptVTableIndex} + 2])("); separator = false; for (var i = 0; i < functionInfo.Parameters.Count; i++) @@ -61,7 +61,7 @@ namespace Flax.Build.Plugins } contents.AppendLine(");"); contents.AppendLine(" }"); - contents.AppendLine(" auto scriptVTable = (VisualScript::Method**)object->GetType().Class.ScriptVTable;"); + contents.AppendLine(" auto scriptVTable = (VisualScript::Method**)object->GetType().Script.ScriptVTable;"); contents.AppendLine($" ASSERT(scriptVTable && scriptVTable[{scriptVTableIndex}]);"); if (functionInfo.Parameters.Count != 0) diff --git a/Source/Tools/Flax.Build/Build/Profiling.cs b/Source/Tools/Flax.Build/Build/Profiling.cs index 98bc2c62a..c5cc89bcb 100644 --- a/Source/Tools/Flax.Build/Build/Profiling.cs +++ b/Source/Tools/Flax.Build/Build/Profiling.cs @@ -4,6 +4,7 @@ using System; using System.IO; using System.Collections.Generic; using System.Text; +using System.Threading; namespace Flax.Build { @@ -12,7 +13,7 @@ namespace Flax.Build /// public class ProfileEventScope : IDisposable { - private int _id; + private readonly int _id; /// /// Initializes a new instance of the class. @@ -61,6 +62,11 @@ namespace Flax.Build /// The event call depth (for parent-children events evaluation). /// public int Depth; + + /// + /// The calling thread id. + /// + public int ThreadId; } private static int _depth; @@ -78,6 +84,7 @@ namespace Flax.Build e.StartTime = DateTime.Now; e.Duration = TimeSpan.Zero; e.Depth = _depth++; + e.ThreadId = Thread.CurrentThread.ManagedThreadId; _events.Add(e); return _events.Count - 1; } @@ -147,8 +154,8 @@ namespace Flax.Build for (int i = 0; i < _events.Count; i++) { var e = _events[i]; - - contents.Append($"{{ \"pid\":1, \"tid\":1, \"ts\":{(int)((e.StartTime - startTime).TotalMilliseconds * 1000.0)}, \"dur\":{(int)(e.Duration.TotalMilliseconds * 1000.0)}, \"ph\":\"X\", \"name\":\"{e.Name}\", \"args\":{{ \"startTime\":\"{e.StartTime.ToShortTimeString()}\" }} }}\n"); + + contents.Append($"{{ \"pid\":{e.ThreadId}, \"tid\":1, \"ts\":{(int)((e.StartTime - startTime).TotalMilliseconds * 1000.0)}, \"dur\":{(int)(e.Duration.TotalMilliseconds * 1000.0)}, \"ph\":\"X\", \"name\":\"{e.Name}\", \"args\":{{ \"startTime\":\"{e.StartTime.ToShortTimeString()}\" }} }}\n"); if (i + 1 != _events.Count) contents.Append(","); diff --git a/Source/Tools/Flax.Build/Build/Sdk.cs b/Source/Tools/Flax.Build/Build/Sdk.cs index 816404679..5c7044705 100644 --- a/Source/Tools/Flax.Build/Build/Sdk.cs +++ b/Source/Tools/Flax.Build/Build/Sdk.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved. +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using System; using System.Collections.Generic; @@ -68,7 +68,7 @@ namespace Flax.Build using (new ProfileEventScope("GetSdks")) { _sdks = new Dictionary(); - var types = typeof(Sdk).Assembly.GetTypes().Where(x => !x.IsAbstract && x.IsSubclassOf(typeof(Sdk))); + var types = Builder.BuildTypes.Where(x => !x.IsAbstract && x.IsSubclassOf(typeof(Sdk))); foreach (var type in types) { object instance = null; diff --git a/Source/Tools/Flax.Build/Configuration.cs b/Source/Tools/Flax.Build/Configuration.cs index 4fb58c4f2..bd9785d35 100644 --- a/Source/Tools/Flax.Build/Configuration.cs +++ b/Source/Tools/Flax.Build/Configuration.cs @@ -1,5 +1,7 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. +using System.Collections.Generic; + namespace Flax.Build { /// @@ -133,6 +135,18 @@ namespace Flax.Build [CommandLine("logfile", "", "The log file path relative to the working directory. Set to empty to disable it/")] public static string LogFile = "Cache/Intermediate/Log.txt"; + /// + /// The maximum allowed concurrency for a build system (maximum active worker threads count). + /// + [CommandLine("maxConcurrency", "", "The maximum allowed concurrency for a build system (maximum active worker threads count).")] + public static int MaxConcurrency = 1410; + + /// + /// The concurrency scale for a build system that specifies how many worker threads allocate per-logical processor. + /// + [CommandLine("concurrencyProcessorScale", "", "The concurrency scale for a build system that specifies how many worker threads allocate per-logical processor.")] + public static float ConcurrencyProcessorScale = 1.0f; + /// /// The output binaries folder path relative to the working directory. /// @@ -186,5 +200,16 @@ namespace Flax.Build /// [CommandLine("customProjectFormat", "", "Generates code project files for a custom project format type. Valid only with -genproject option.")] public static string ProjectFormatCustom = null; + + /// + /// Overrides the compiler to use for building. Eg. v140 overrides the toolset when building for Windows. + /// + [CommandLine("compiler", "", "Overrides the compiler to use for building. Eg. v140 overrides the toolset when building for Windows.")] + public static string Compiler = null; + + /// + /// Custom configuration defines provided via command line for the build tool. + /// + public static List CustomDefines = new List(); } } diff --git a/Source/Tools/Flax.Build/Deps/Dependencies/PhysX.cs b/Source/Tools/Flax.Build/Deps/Dependencies/PhysX.cs index 3415501bd..32a408828 100644 --- a/Source/Tools/Flax.Build/Deps/Dependencies/PhysX.cs +++ b/Source/Tools/Flax.Build/Deps/Dependencies/PhysX.cs @@ -312,7 +312,7 @@ namespace Flax.Deps.Dependencies } // Get the source - //CloneGitRepoSingleBranch(root, "https://github.com/NVIDIAGameWorks/PhysX.git", "4.1"); + CloneGitRepoSingleBranch(root, "https://github.com/NVIDIAGameWorks/PhysX.git", "4.1"); foreach (var platform in options.Platforms) { diff --git a/Source/Tools/Flax.Build/Deps/DepsBuilder.cs b/Source/Tools/Flax.Build/Deps/DepsBuilder.cs index 7ac4b0d69..6d6eaf5b3 100644 --- a/Source/Tools/Flax.Build/Deps/DepsBuilder.cs +++ b/Source/Tools/Flax.Build/Deps/DepsBuilder.cs @@ -51,7 +51,7 @@ namespace Flax.Deps } // Get all deps - var dependencies = typeof(DepsBuilder).Assembly.GetTypes().Where(x => x.IsSubclassOf(typeof(Dependency))).Select(Activator.CreateInstance).Cast().ToArray(); + var dependencies = Builder.BuildTypes.Where(x => x.IsSubclassOf(typeof(Dependency))).Select(Activator.CreateInstance).Cast().ToArray(); if (dependencies.Length == 0) Log.Warning("No dependencies found!"); for (var i = 0; i < dependencies.Length; i++) diff --git a/Source/Tools/Flax.Build/Flax.Build.csproj b/Source/Tools/Flax.Build/Flax.Build.csproj index a8221455f..236348032 100644 --- a/Source/Tools/Flax.Build/Flax.Build.csproj +++ b/Source/Tools/Flax.Build/Flax.Build.csproj @@ -64,17 +64,20 @@ + + + diff --git a/Source/Tools/Flax.Build/Log.cs b/Source/Tools/Flax.Build/Log.cs index ba948d7ca..3bd972f42 100644 --- a/Source/Tools/Flax.Build/Log.cs +++ b/Source/Tools/Flax.Build/Log.cs @@ -36,7 +36,7 @@ namespace Flax.Build var path = Path.GetDirectoryName(Configuration.LogFile); if (!string.IsNullOrEmpty(path) && !Directory.Exists(path)) Directory.CreateDirectory(path); - _logFile = new FileStream(Configuration.LogFile, FileMode.Create); + _logFile = new FileStream(Configuration.LogFile, FileMode.Create, FileAccess.Write, FileShare.Read); _logFileWriter = new StreamWriter(_logFile); } } diff --git a/Source/Tools/Flax.Build/Platforms/Android/AndroidNdk.cs b/Source/Tools/Flax.Build/Platforms/Android/AndroidNdk.cs index f6a7816cf..d02f440ae 100644 --- a/Source/Tools/Flax.Build/Platforms/Android/AndroidNdk.cs +++ b/Source/Tools/Flax.Build/Platforms/Android/AndroidNdk.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved. +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using System; using System.IO; diff --git a/Source/Tools/Flax.Build/Platforms/Android/AndroidSdk.cs b/Source/Tools/Flax.Build/Platforms/Android/AndroidSdk.cs index d2d0278bf..ec83fb90f 100644 --- a/Source/Tools/Flax.Build/Platforms/Android/AndroidSdk.cs +++ b/Source/Tools/Flax.Build/Platforms/Android/AndroidSdk.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved. +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using System; using System.IO; diff --git a/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchain.cs b/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchain.cs index 8f247a189..40f338a65 100644 --- a/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchain.cs +++ b/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchain.cs @@ -20,7 +20,7 @@ namespace Flax.Build.Platforms /// The platform. /// The target architecture. public WindowsToolchain(WindowsPlatform platform, TargetArchitecture architecture) - : base(platform, architecture, WindowsPlatformToolset.v140, WindowsPlatformSDK.Latest) + : base(platform, architecture, WindowsPlatformToolset.Latest, WindowsPlatformSDK.Latest) { } diff --git a/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchainBase.cs b/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchainBase.cs index 4d3289a86..919971f68 100644 --- a/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchainBase.cs +++ b/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchainBase.cs @@ -78,6 +78,13 @@ namespace Flax.Build.Platforms var toolsets = WindowsPlatformBase.GetToolsets(); var sdks = WindowsPlatformBase.GetSDKs(); + // Pick the overriden toolset + if (Configuration.Compiler != null) + { + if (Enum.TryParse(Configuration.Compiler, out WindowsPlatformToolset compiler)) + toolsetVer = compiler; + } + // Pick the newest installed Visual Studio version if using the default toolset if (toolsetVer == WindowsPlatformToolset.Default) { diff --git a/Source/Tools/Flax.Build/Program.cs b/Source/Tools/Flax.Build/Program.cs index fc6d86b3b..89e71ace0 100644 --- a/Source/Tools/Flax.Build/Program.cs +++ b/Source/Tools/Flax.Build/Program.cs @@ -32,6 +32,16 @@ namespace Flax.Build { // Setup CommandLine.Configure(typeof(Configuration)); + foreach (var option in CommandLine.GetOptions()) + { + if (option.Name.Length > 1 && option.Name[0] == 'D') + { + var define = option.Name.Substring(1); + if (!string.IsNullOrEmpty(option.Value)) + define += "=" + option.Value; + Configuration.CustomDefines.Add(define); + } + } if (Configuration.CurrentDirectory != null) Environment.CurrentDirectory = Configuration.CurrentDirectory; Globals.Root = Directory.GetCurrentDirectory(); diff --git a/Source/Tools/Flax.Build/Utilities/Tokenizer.cs b/Source/Tools/Flax.Build/Utilities/Tokenizer.cs index 70d0d7fc1..e73ddcbca 100644 --- a/Source/Tools/Flax.Build/Utilities/Tokenizer.cs +++ b/Source/Tools/Flax.Build/Utilities/Tokenizer.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved. +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using System; using System.Collections.Generic; @@ -460,6 +460,24 @@ namespace Flax.Build } } + /// + /// Skips all tokens until the tokenizer steps into token of given type (and it is also skipped, so, NextToken will give the next token). + /// + /// The expected token type. + /// The output contents of the skipped tokens. + /// When false, all white-space tokens will be ignored. + public void SkipUntil(TokenType tokenType, out string context, bool includeWhitespaces) + { + context = string.Empty; + while (NextToken(true).Type != tokenType) + { + var token = CurrentToken; + if (!includeWhitespaces && (token.Type == TokenType.Newline || token.Type == TokenType.Whitespace)) + continue; + context += token.Value; + } + } + /// /// Disposes the . /// diff --git a/Source/Tools/Flax.Build/Utilities/TwoWayEnumerator.cs b/Source/Tools/Flax.Build/Utilities/TwoWayEnumerator.cs index 0347a88a8..ec9f71091 100644 --- a/Source/Tools/Flax.Build/Utilities/TwoWayEnumerator.cs +++ b/Source/Tools/Flax.Build/Utilities/TwoWayEnumerator.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved. +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using System; using System.Collections.Generic; diff --git a/Source/Tools/Flax.Build/Utilities/Utilities.cs b/Source/Tools/Flax.Build/Utilities/Utilities.cs index c37faa678..accf86209 100644 --- a/Source/Tools/Flax.Build/Utilities/Utilities.cs +++ b/Source/Tools/Flax.Build/Utilities/Utilities.cs @@ -14,6 +14,16 @@ namespace Flax.Build /// public static class Utilities { + /// + /// Gets the empty array of the given type (shared one). + /// + /// The type. + /// The empty array object. + public static T[] GetEmptyArray() + { + return Enumerable.Empty() as T[]; + } + /// /// Gets the size of the file as a readable string. /// diff --git a/Source/Tools/FlaxEngine.Tests/TestTransform.cs b/Source/Tools/FlaxEngine.Tests/TestTransform.cs index d947c295e..38f71e217 100644 --- a/Source/Tools/FlaxEngine.Tests/TestTransform.cs +++ b/Source/Tools/FlaxEngine.Tests/TestTransform.cs @@ -143,7 +143,29 @@ namespace FlaxEngine.Tests Transform ab = a.LocalToWorld(b); Transform ba = a.WorldToLocal(ab); - Assert.IsTrue(Transform.NearEqual(ref b, ref ba, 0.00001f)); + Assert.IsTrue(Transform.NearEqual(ref b, ref ba, 0.00001f), $"Got: {b} but expected {ba}"); + } + } + + /// + /// Test conversions between transform local/world space + /// + [Test] + public void TestAddSubtract() + { + var rand = new Random(10); + for (int i = 0; i < 10; i++) + { + Transform a = new Transform(rand.NextVector3(), Quaternion.Euler(i * 10, 0, i), rand.NextVector3() * 10.0f); + Transform b = new Transform(rand.NextVector3(), Quaternion.Euler(i, 1, 22), rand.NextVector3() * 0.3f); + + Transform ab = a + b; + Transform newA = ab - b; + Assert.IsTrue(Transform.NearEqual(ref a, ref newA, 0.00001f), $"Got: {newA} but expected {a}"); + + Transform ba = b + a; + Transform newB = ba - a; + Assert.IsTrue(Transform.NearEqual(ref b, ref newB, 0.00001f), $"Got: {newB} but expected {b}"); } } }