diff --git a/engine/include/XCEngine/Rendering/Builtin/BuiltinPassMetadataUtils.h b/engine/include/XCEngine/Rendering/Builtin/BuiltinPassMetadataUtils.h index 26a1346a..843c2327 100644 --- a/engine/include/XCEngine/Rendering/Builtin/BuiltinPassMetadataUtils.h +++ b/engine/include/XCEngine/Rendering/Builtin/BuiltinPassMetadataUtils.h @@ -45,6 +45,23 @@ inline bool IsObjectIdPassName(const Containers::String& value) { normalized == Containers::String("editorobjectid"); } +inline bool IsSkyboxPassName(const Containers::String& value) { + const Containers::String normalized = NormalizeBuiltinPassMetadataValue(value); + return normalized == Containers::String("skybox"); +} + +inline bool IsPostProcessPassName(const Containers::String& value) { + const Containers::String normalized = NormalizeBuiltinPassMetadataValue(value); + return normalized == Containers::String("postprocess") || + normalized == Containers::String("colorscale"); +} + +inline bool IsFinalColorPassName(const Containers::String& value) { + const Containers::String normalized = NormalizeBuiltinPassMetadataValue(value); + return normalized == Containers::String("finaloutput") || + normalized == Containers::String("finalcolor"); +} + inline bool MatchesBuiltinPassName( const Containers::String& value, BuiltinMaterialPass pass) { @@ -59,11 +76,61 @@ inline bool MatchesBuiltinPassName( return IsShadowCasterPassName(value); case BuiltinMaterialPass::ObjectId: return IsObjectIdPassName(value); + case BuiltinMaterialPass::Skybox: + return IsSkyboxPassName(value); + case BuiltinMaterialPass::PostProcess: + return IsPostProcessPassName(value); + case BuiltinMaterialPass::FinalColor: + return IsFinalColorPassName(value); default: return false; } } +inline bool TryResolveBuiltinMaterialPassHint( + const Containers::String& value, + BuiltinMaterialPass& outPass) { + const Containers::String normalized = NormalizeBuiltinPassMetadataValue(value); + if (normalized.Empty()) { + return false; + } + + if (IsForwardPassName(normalized)) { + outPass = BuiltinMaterialPass::ForwardLit; + return true; + } + if (IsUnlitPassName(normalized)) { + outPass = BuiltinMaterialPass::Unlit; + return true; + } + if (IsDepthOnlyPassName(normalized)) { + outPass = BuiltinMaterialPass::DepthOnly; + return true; + } + if (IsShadowCasterPassName(normalized)) { + outPass = BuiltinMaterialPass::ShadowCaster; + return true; + } + if (IsObjectIdPassName(normalized)) { + outPass = BuiltinMaterialPass::ObjectId; + return true; + } + if (IsSkyboxPassName(normalized)) { + outPass = BuiltinMaterialPass::Skybox; + return true; + } + if (IsPostProcessPassName(normalized)) { + outPass = BuiltinMaterialPass::PostProcess; + return true; + } + if (IsFinalColorPassName(normalized)) { + outPass = BuiltinMaterialPass::FinalColor; + return true; + } + + return false; +} + inline bool ShaderPassHasExplicitBuiltinMetadata(const Resources::ShaderPass& shaderPass) { if (!shaderPass.name.Empty() && shaderPass.name != Containers::String("Default")) { @@ -116,6 +183,27 @@ inline bool ShaderPassMatchesBuiltinPass( return hasMetadata; } +inline bool IsRedundantLegacyMaterialShaderPassHint( + const Resources::Shader* shader, + const Containers::String& shaderPass) { + if (shader == nullptr) { + return false; + } + + BuiltinMaterialPass builtinPass = BuiltinMaterialPass::ForwardLit; + if (!TryResolveBuiltinMaterialPassHint(shaderPass, builtinPass)) { + return false; + } + + for (const Resources::ShaderPass& pass : shader->GetPasses()) { + if (ShaderPassMatchesBuiltinPass(pass, builtinPass)) { + return true; + } + } + + return false; +} + inline BuiltinPassResourceSemantic ResolveBuiltinPassResourceSemantic( const Resources::ShaderResourceBindingDesc& binding) { Containers::String semantic = NormalizeBuiltinPassMetadataValue(binding.semantic); @@ -143,11 +231,34 @@ inline BuiltinPassResourceSemantic ResolveBuiltinPassResourceSemantic( return BuiltinPassResourceSemantic::ShadowReceiver; } + if (semantic == Containers::String("environment") || + semantic == Containers::String("environmentconstants")) { + return BuiltinPassResourceSemantic::Environment; + } + + if (semantic == Containers::String("passconstants") || + semantic == Containers::String("finalcolorconstants") || + semantic == Containers::String("postprocessconstants")) { + return BuiltinPassResourceSemantic::PassConstants; + } + if (semantic == Containers::String("basecolortexture") || semantic == Containers::String("maintex")) { return BuiltinPassResourceSemantic::BaseColorTexture; } + if (semantic == Containers::String("sourcecolortexture")) { + return BuiltinPassResourceSemantic::SourceColorTexture; + } + + if (semantic == Containers::String("skyboxpanoramictexture")) { + return BuiltinPassResourceSemantic::SkyboxPanoramicTexture; + } + + if (semantic == Containers::String("skyboxtexture")) { + return BuiltinPassResourceSemantic::SkyboxTexture; + } + if (semantic == Containers::String("shadowmaptexture") || semantic == Containers::String("shadowmap")) { return BuiltinPassResourceSemantic::ShadowMapTexture; @@ -175,8 +286,18 @@ inline const char* BuiltinPassResourceSemanticToString(BuiltinPassResourceSemant return "Lighting"; case BuiltinPassResourceSemantic::ShadowReceiver: return "ShadowReceiver"; + case BuiltinPassResourceSemantic::Environment: + return "Environment"; + case BuiltinPassResourceSemantic::PassConstants: + return "PassConstants"; case BuiltinPassResourceSemantic::BaseColorTexture: return "BaseColorTexture"; + case BuiltinPassResourceSemantic::SourceColorTexture: + return "SourceColorTexture"; + case BuiltinPassResourceSemantic::SkyboxPanoramicTexture: + return "SkyboxPanoramicTexture"; + case BuiltinPassResourceSemantic::SkyboxTexture: + return "SkyboxTexture"; case BuiltinPassResourceSemantic::ShadowMapTexture: return "ShadowMapTexture"; case BuiltinPassResourceSemantic::LinearClampSampler: @@ -238,11 +359,16 @@ inline bool IsBuiltinPassResourceTypeCompatible( case BuiltinPassResourceSemantic::Material: case BuiltinPassResourceSemantic::Lighting: case BuiltinPassResourceSemantic::ShadowReceiver: + case BuiltinPassResourceSemantic::Environment: + case BuiltinPassResourceSemantic::PassConstants: return type == Resources::ShaderResourceType::ConstantBuffer; case BuiltinPassResourceSemantic::BaseColorTexture: + case BuiltinPassResourceSemantic::SourceColorTexture: + case BuiltinPassResourceSemantic::SkyboxPanoramicTexture: case BuiltinPassResourceSemantic::ShadowMapTexture: - return type == Resources::ShaderResourceType::Texture2D || - type == Resources::ShaderResourceType::TextureCube; + return type == Resources::ShaderResourceType::Texture2D; + case BuiltinPassResourceSemantic::SkyboxTexture: + return type == Resources::ShaderResourceType::TextureCube; case BuiltinPassResourceSemantic::LinearClampSampler: case BuiltinPassResourceSemantic::ShadowMapSampler: return type == Resources::ShaderResourceType::Sampler; diff --git a/engine/src/Core/Asset/AssetDatabase.cpp b/engine/src/Core/Asset/AssetDatabase.cpp index a02902df..498e156d 100644 --- a/engine/src/Core/Asset/AssetDatabase.cpp +++ b/engine/src/Core/Asset/AssetDatabase.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -424,6 +425,19 @@ Containers::String ResolveTextureBindingPath( return NormalizeArtifactPathString(material.GetTextureBindingPath(bindingIndex)); } +Containers::String ResolveSerializedMaterialShaderPass(const Material& material) { + const Containers::String& shaderPass = material.GetShaderPass(); + if (shaderPass.Empty()) { + return Containers::String(); + } + + if (Rendering::IsRedundantLegacyMaterialShaderPassHint(material.GetShader(), shaderPass)) { + return Containers::String(); + } + + return shaderPass; +} + bool WriteMaterialArtifactFile( const fs::path& artifactPath, const Material& material, @@ -445,7 +459,7 @@ bool WriteMaterialArtifactFile( material.GetShader() != nullptr ? material.GetShader()->GetPath() : Containers::String()); - WriteString(output, material.GetShaderPass()); + WriteString(output, ResolveSerializedMaterialShaderPass(material)); MaterialArtifactHeader header; header.renderQueue = material.GetRenderQueue(); diff --git a/engine/src/Resources/Material/MaterialLoader.cpp b/engine/src/Resources/Material/MaterialLoader.cpp index 10bf59d3..69a3a0f1 100644 --- a/engine/src/Resources/Material/MaterialLoader.cpp +++ b/engine/src/Resources/Material/MaterialLoader.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -1472,6 +1473,18 @@ bool MaterialFileExists(const Containers::String& path) { return std::filesystem::exists(std::filesystem::path(resourceRoot.CStr()) / inputPath); } +void ApplyMaterialShaderPassHint(Material* material, const Containers::String& shaderPass) { + if (material == nullptr || shaderPass.Empty()) { + return; + } + + if (Rendering::IsRedundantLegacyMaterialShaderPassHint(material->GetShader(), shaderPass)) { + return; + } + + material->SetShaderPass(shaderPass); +} + ResourceHandle LoadShaderHandle(const Containers::String& shaderPath); template @@ -1560,9 +1573,10 @@ LoadResult LoadMaterialArtifact(const Containers::String& path) { const std::string magic(fileHeader.magic, fileHeader.magic + 7); const bool isLegacySchema = magic == "XCMAT02" && fileHeader.schemaVersion == 2u; + const bool isSchemaV3 = magic == "XCMAT03" && fileHeader.schemaVersion == 3u; const bool isCurrentSchema = - magic == "XCMAT03" && fileHeader.schemaVersion == kMaterialArtifactSchemaVersion; - if (!isLegacySchema && !isCurrentSchema) { + magic == "XCMAT04" && fileHeader.schemaVersion == kMaterialArtifactSchemaVersion; + if (!isLegacySchema && !isSchemaV3 && !isCurrentSchema) { return LoadResult("Invalid material artifact magic: " + path); } @@ -1594,9 +1608,7 @@ LoadResult LoadMaterialArtifact(const Containers::String& path) { material->SetShader(shaderHandle); } } - if (!shaderPass.Empty()) { - material->SetShaderPass(shaderPass); - } + ApplyMaterialShaderPassHint(material.get(), shaderPass); MaterialArtifactHeader header = {}; if (isLegacySchema) { @@ -1608,6 +1620,20 @@ LoadResult LoadMaterialArtifact(const Containers::String& path) { header.renderQueue = legacyHeader.renderQueue; header.renderState = legacyHeader.renderState; header.tagCount = legacyHeader.tagCount; + header.hasRenderStateOverride = 1u; + header.propertyCount = legacyHeader.propertyCount; + header.textureBindingCount = legacyHeader.textureBindingCount; + } else if (isSchemaV3) { + MaterialArtifactHeaderV3 legacyHeader = {}; + if (!ReadMaterialArtifactValue(data, offset, legacyHeader)) { + return LoadResult("Failed to parse material artifact body: " + path); + } + + header.renderQueue = legacyHeader.renderQueue; + header.renderState = legacyHeader.renderState; + header.tagCount = legacyHeader.tagCount; + header.hasRenderStateOverride = 1u; + header.keywordCount = legacyHeader.keywordCount; header.propertyCount = legacyHeader.propertyCount; header.textureBindingCount = legacyHeader.textureBindingCount; } else { @@ -1618,6 +1644,7 @@ LoadResult LoadMaterialArtifact(const Containers::String& path) { material->SetRenderQueue(header.renderQueue); material->SetRenderState(header.renderState); + material->SetRenderStateOverrideEnabled(header.hasRenderStateOverride != 0u); for (Core::uint32 tagIndex = 0; tagIndex < header.tagCount; ++tagIndex) { Containers::String tagName; @@ -1772,12 +1799,12 @@ bool MaterialLoader::ParseMaterialData(const Containers::Array& dat if (!TryParseStringValue(jsonText, "shaderPass", shaderPass)) { return false; } - material->SetShaderPass(shaderPass); + ApplyMaterialShaderPassHint(material, shaderPass); } else if (HasKey(jsonText, "pass")) { if (!TryParseStringValue(jsonText, "pass", shaderPass)) { return false; } - material->SetShaderPass(shaderPass); + ApplyMaterialShaderPassHint(material, shaderPass); } if (HasKey(jsonText, "renderQueue")) { diff --git a/tests/Rendering/integration/camera_post_process_scene/main.cpp b/tests/Rendering/integration/camera_post_process_scene/main.cpp index e875301d..c7e9885a 100644 --- a/tests/Rendering/integration/camera_post_process_scene/main.cpp +++ b/tests/Rendering/integration/camera_post_process_scene/main.cpp @@ -112,7 +112,6 @@ Material* CreateQuadMaterial(Texture* texture) { params.guid = ResourceGUID::Generate(params.path); material->Initialize(params); material->SetShader(ResourceManager::Get().Load(GetBuiltinUnlitShaderPath())); - material->SetShaderPass("Unlit"); material->SetTexture("_MainTex", ResourceHandle(texture)); return material; } diff --git a/tests/Rendering/integration/final_color_scene/main.cpp b/tests/Rendering/integration/final_color_scene/main.cpp index 5b81d6ad..1a11022c 100644 --- a/tests/Rendering/integration/final_color_scene/main.cpp +++ b/tests/Rendering/integration/final_color_scene/main.cpp @@ -113,7 +113,6 @@ Material* CreateQuadMaterial(Texture* texture) { params.guid = ResourceGUID::Generate(params.path); material->Initialize(params); material->SetShader(ResourceManager::Get().Load(GetBuiltinUnlitShaderPath())); - material->SetShaderPass("Unlit"); material->SetTexture("_MainTex", ResourceHandle(texture)); return material; } diff --git a/tests/Rendering/integration/post_process_scene/main.cpp b/tests/Rendering/integration/post_process_scene/main.cpp index 9da211a2..612bf954 100644 --- a/tests/Rendering/integration/post_process_scene/main.cpp +++ b/tests/Rendering/integration/post_process_scene/main.cpp @@ -112,7 +112,6 @@ Material* CreateQuadMaterial(Texture* texture) { params.guid = ResourceGUID::Generate(params.path); material->Initialize(params); material->SetShader(ResourceManager::Get().Load(GetBuiltinUnlitShaderPath())); - material->SetShaderPass("Unlit"); material->SetTexture("_MainTex", ResourceHandle(texture)); return material; } diff --git a/tests/Rendering/integration/unlit_scene/main.cpp b/tests/Rendering/integration/unlit_scene/main.cpp index 15480472..9305fdf2 100644 --- a/tests/Rendering/integration/unlit_scene/main.cpp +++ b/tests/Rendering/integration/unlit_scene/main.cpp @@ -63,7 +63,7 @@ Mesh* CreateQuadMesh() { vertices[3].position = Vector3(1.0f, 1.0f, 0.0f); vertices[3].uv0 = Vector2(1.0f, 0.0f); - const uint32_t indices[6] = { 0, 1, 2, 2, 1, 3 }; + const uint32_t indices[6] = { 0, 2, 1, 2, 3, 1 }; mesh->SetVertexData( vertices, sizeof(vertices), @@ -117,7 +117,6 @@ Material* CreateMaterial(Texture* texture) { params.guid = ResourceGUID::Generate(params.path); material->Initialize(params); material->SetShader(ResourceManager::Get().Load(GetBuiltinUnlitShaderPath())); - material->SetShaderPass("Unlit"); material->SetTexture("_MainTex", ResourceHandle(texture)); return material; } diff --git a/tests/Resources/Material/test_material_loader.cpp b/tests/Resources/Material/test_material_loader.cpp index 1c4f9d10..f73f8c26 100644 --- a/tests/Resources/Material/test_material_loader.cpp +++ b/tests/Resources/Material/test_material_loader.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -251,12 +252,36 @@ TEST(MaterialLoader, LoadValidMaterialParsesRenderMetadata) { EXPECT_EQ(material->GetRenderState().dstBlend, MaterialBlendFactor::InvSrcAlpha); EXPECT_FALSE(material->GetRenderState().depthWriteEnable); EXPECT_EQ(material->GetRenderState().depthFunc, MaterialComparisonFunc::LessEqual); + EXPECT_TRUE(material->HasRenderStateOverride()); delete material; std::remove(materialPath.string().c_str()); std::remove(shaderPath.string().c_str()); } +TEST(MaterialLoader, LoadMaterialWithoutRenderStateLeavesOverrideDisabled) { + const std::filesystem::path materialPath = + std::filesystem::current_path() / "material_loader_no_render_state.material"; + + { + std::ofstream materialFile(materialPath); + ASSERT_TRUE(materialFile.is_open()); + materialFile << "{ \"renderQueue\": \"Geometry\" }"; + } + + MaterialLoader loader; + LoadResult result = loader.Load(materialPath.string().c_str()); + ASSERT_TRUE(result); + ASSERT_NE(result.resource, nullptr); + + auto* material = static_cast(result.resource); + ASSERT_NE(material, nullptr); + EXPECT_FALSE(material->HasRenderStateOverride()); + + delete material; + std::remove(materialPath.string().c_str()); +} + TEST(MaterialLoader, LoadMaterialWithShaderManifestResolvesShaderPass) { namespace fs = std::filesystem; @@ -319,7 +344,7 @@ TEST(MaterialLoader, LoadMaterialWithShaderManifestResolvesShaderPass) { Material* material = static_cast(result.resource); ASSERT_NE(material, nullptr); ASSERT_NE(material->GetShader(), nullptr); - EXPECT_EQ(material->GetShaderPass(), "ForwardLit"); + EXPECT_TRUE(material->GetShaderPass().Empty()); ASSERT_NE(material->GetShader()->FindPass("ForwardLit"), nullptr); const ShaderStageVariant* vertexVariant = material->GetShader()->FindVariant("ForwardLit", ShaderType::Vertex, ShaderBackend::OpenGL); @@ -361,7 +386,7 @@ TEST(MaterialLoader, LoadMaterialWithPropertiesObjectAppliesTypedOverrides) { auto* material = static_cast(result.resource); ASSERT_NE(material, nullptr); ASSERT_NE(material->GetShader(), nullptr); - EXPECT_EQ(material->GetShaderPass(), "ForwardLit"); + EXPECT_TRUE(material->GetShaderPass().Empty()); EXPECT_EQ(material->GetFloat4("_BaseColor"), XCEngine::Math::Vector4(0.2f, 0.4f, 0.6f, 0.8f)); EXPECT_FLOAT_EQ(material->GetFloat("_Metallic"), 0.15f); EXPECT_EQ(material->GetInt("_Mode"), 5); @@ -372,6 +397,40 @@ TEST(MaterialLoader, LoadMaterialWithPropertiesObjectAppliesTypedOverrides) { fs::remove_all(rootPath); } +TEST(MaterialLoader, LoadBuiltinShaderMaterialDropsRedundantBuiltinShaderPassHint) { + namespace fs = std::filesystem; + + ResourceManager& manager = ResourceManager::Get(); + manager.Initialize(); + + const fs::path rootPath = fs::temp_directory_path() / "xc_material_loader_builtin_hint_strip_test"; + fs::remove_all(rootPath); + fs::create_directories(rootPath); + + const fs::path materialPath = rootPath / "builtin_unlit.material"; + WriteTextFile( + materialPath, + "{\n" + " \"shader\": \"" + std::string(GetBuiltinUnlitShaderPath().CStr()) + "\",\n" + " \"shaderPass\": \"Unlit\"\n" + "}\n"); + + MaterialLoader loader; + LoadResult result = loader.Load(materialPath.generic_string().c_str()); + ASSERT_TRUE(result); + ASSERT_NE(result.resource, nullptr); + + auto* material = static_cast(result.resource); + ASSERT_NE(material, nullptr); + ASSERT_NE(material->GetShader(), nullptr); + EXPECT_TRUE(material->GetShaderPass().Empty()); + EXPECT_NE(material->GetShader()->FindPass("Unlit"), nullptr); + + delete material; + manager.Shutdown(); + fs::remove_all(rootPath); +} + TEST(MaterialLoader, RejectsUnknownPropertyAgainstShaderSchema) { namespace fs = std::filesystem; @@ -676,6 +735,43 @@ TEST(MaterialLoader, AssetDatabaseCreatesMaterialArtifact) { fs::remove_all(projectRoot); } +TEST(MaterialLoader, AssetDatabaseMaterialArtifactPreservesImplicitRenderStateFlag) { + namespace fs = std::filesystem; + + const fs::path projectRoot = fs::temp_directory_path() / "xc_material_implicit_render_state_artifact"; + const fs::path assetsDir = projectRoot / "Assets"; + const fs::path materialPath = assetsDir / "implicit.material"; + + fs::remove_all(projectRoot); + fs::create_directories(assetsDir); + + WriteTextFile( + materialPath, + "{\n" + " \"renderQueue\": \"Geometry\"\n" + "}\n"); + + AssetDatabase database; + database.Initialize(projectRoot.string().c_str()); + + AssetDatabase::ResolvedAsset resolvedAsset; + ASSERT_TRUE(database.EnsureArtifact("Assets/implicit.material", ResourceType::Material, resolvedAsset)); + ASSERT_TRUE(resolvedAsset.artifactReady); + + MaterialLoader loader; + LoadResult result = loader.Load(resolvedAsset.artifactMainPath.CStr()); + ASSERT_TRUE(result); + ASSERT_NE(result.resource, nullptr); + + auto* material = static_cast(result.resource); + ASSERT_NE(material, nullptr); + EXPECT_FALSE(material->HasRenderStateOverride()); + + delete material; + database.Shutdown(); + fs::remove_all(projectRoot); +} + TEST(MaterialLoader, AssetDatabaseMaterialArtifactRoundTripsKeywords) { namespace fs = std::filesystem; @@ -728,6 +824,57 @@ TEST(MaterialLoader, AssetDatabaseMaterialArtifactRoundTripsKeywords) { fs::remove_all(projectRoot); } +TEST(MaterialLoader, AssetDatabaseMaterialArtifactStripsRedundantBuiltinShaderPassHint) { + namespace fs = std::filesystem; + + ResourceManager& manager = ResourceManager::Get(); + manager.Initialize(); + + const fs::path projectRoot = fs::temp_directory_path() / "xc_material_artifact_builtin_hint_strip_test"; + const fs::path assetsDir = projectRoot / "Assets"; + const fs::path materialPath = assetsDir / "builtin_unlit.material"; + + fs::remove_all(projectRoot); + fs::create_directories(assetsDir); + + WriteTextFile( + materialPath, + "{\n" + " \"shader\": \"" + std::string(GetBuiltinUnlitShaderPath().CStr()) + "\",\n" + " \"shaderPass\": \"Unlit\",\n" + " \"properties\": {\n" + " \"_BaseColor\": [1.0, 1.0, 1.0, 1.0]\n" + " }\n" + "}\n"); + + manager.SetResourceRoot(projectRoot.string().c_str()); + manager.RefreshProjectAssets(); + + AssetDatabase database; + database.Initialize(projectRoot.string().c_str()); + + AssetDatabase::ResolvedAsset resolvedAsset; + ASSERT_TRUE(database.EnsureArtifact("Assets/builtin_unlit.material", ResourceType::Material, resolvedAsset)); + ASSERT_TRUE(resolvedAsset.artifactReady); + + MaterialLoader loader; + LoadResult result = loader.Load(resolvedAsset.artifactMainPath.CStr()); + ASSERT_TRUE(result); + ASSERT_NE(result.resource, nullptr); + + auto* material = static_cast(result.resource); + ASSERT_NE(material, nullptr); + ASSERT_NE(material->GetShader(), nullptr); + EXPECT_TRUE(material->GetShaderPass().Empty()); + EXPECT_EQ(material->GetFloat4("_BaseColor"), XCEngine::Math::Vector4(1.0f, 1.0f, 1.0f, 1.0f)); + + delete material; + database.Shutdown(); + manager.SetResourceRoot(""); + manager.Shutdown(); + fs::remove_all(projectRoot); +} + TEST(MaterialLoader, ResourceManagerLoadsProjectMaterialTextureAsLazyDependency) { namespace fs = std::filesystem;