diff --git a/Source/Engine/ShadersCompilation/ShadersCompilation.cpp b/Source/Engine/ShadersCompilation/ShadersCompilation.cpp index c8f68f007..e471bb47d 100644 --- a/Source/Engine/ShadersCompilation/ShadersCompilation.cpp +++ b/Source/Engine/ShadersCompilation/ShadersCompilation.cpp @@ -26,6 +26,8 @@ #if USE_EDITOR #define COMPILE_WITH_ASSETS_IMPORTER 1 // Hack to use shaders importing in this module #include "Engine/ContentImporters/AssetsImportingManager.h" +#include "Engine/Content/Storage/ContentStorageManager.h" +#include "Engine/Utilities/Encryption.h" #include "Engine/Platform/FileSystemWatcher.h" #include "Engine/Platform/FileSystem.h" #include "Engine/Platform/File.h" @@ -495,6 +497,37 @@ String ShadersCompilation::CompactShaderPath(StringView path) #if USE_EDITOR +bool ShadersCompilation::IsShaderSourceAssetUpToDate(const StringView& sourcePath, const StringView& assetPath) +{ + PROFILE_CPU(); + StringAnsi source; + if (File::ReadAllText(sourcePath, source)) + return false; + if (!source.HasChars() || source[source.Length() - 1] != '\n') + source.Append('\n'); + + const auto storage = ContentStorageManager::GetStorage(assetPath); + AssetInitData data; + if (!storage + || storage->GetEntriesCount() != 1 + || storage->GetEntry(0).TypeName != Shader::TypeName + || storage->LoadAssetHeader(0, data) + || data.SerializedVersion != Shader::SerializedVersion) + return false; + + FlaxChunk* sourceChunk = data.Header.Chunks[SHADER_FILE_CHUNK_SOURCE]; + if (!sourceChunk || storage->LoadAssetChunk(sourceChunk) || !sourceChunk->Data.IsValid()) + return false; + + BytesContainer embeddedSource; + embeddedSource.Copy(sourceChunk->Data); + if (embeddedSource.Length() != source.Length() + 1) + return false; + Encryption::DecryptBytes(embeddedSource.Get(), embeddedSource.Length()); + embeddedSource.Get()[embeddedSource.Length() - 1] = 0; + return Platform::MemoryCompare(embeddedSource.Get(), source.Get(), source.Length()) == 0; +} + namespace { Array ShadersSourcesWatchers; @@ -511,6 +544,13 @@ namespace return result; } + bool ImportShaderIfChanged(const StringView& sourcePath, const StringView& assetPath, Guid& assetId) + { + if (ShadersCompilation::IsShaderSourceAssetUpToDate(sourcePath, assetPath)) + return false; + return AssetsImportingManager::Import(sourcePath, assetPath, assetId); + } + void OnWatcherShadersEvent(const String& path, FileSystemAction action) { if (action == FileSystemAction::Delete || !path.EndsWith(TEXT(".shader"))) @@ -533,7 +573,7 @@ namespace const String name = StringUtils::GetPathWithoutExtension(localPath); const String outputPath = shadersAssetsPath / name + ASSET_FILES_EXTENSION_WITH_DOT; Guid id = GetShaderAssetId(name); - AssetsImportingManager::ImportIfEdited(path, outputPath, id); + ImportShaderIfChanged(path, outputPath, id); } void RegisterShaderWatchers(const ProjectInfo* project, HashSet& projects) @@ -562,7 +602,7 @@ namespace const String name = StringUtils::GetPathWithoutExtension(localPath); const String outputPath = shadersAssetsPath / name + ASSET_FILES_EXTENSION_WITH_DOT; Guid id = GetShaderAssetId(name); - AssetsImportingManager::ImportIfEdited(path, outputPath, id); + ImportShaderIfChanged(path, outputPath, id); } } diff --git a/Source/Engine/ShadersCompilation/ShadersCompilation.h b/Source/Engine/ShadersCompilation/ShadersCompilation.h index b32c9f51a..af0a9ec61 100644 --- a/Source/Engine/ShadersCompilation/ShadersCompilation.h +++ b/Source/Engine/ShadersCompilation/ShadersCompilation.h @@ -47,6 +47,16 @@ public: // Compacts the full shader file path into portable format with project name prefix such as './/ShaderFile.hlsl'. static String CompactShaderPath(StringView path); +#if USE_EDITOR + /// + /// Checks whether a shader asset embeds the current source file contents. + /// + /// The shader source file path. + /// The shader asset file path. + /// True when the embedded source matches, otherwise false. + static bool IsShaderSourceAssetUpToDate(const StringView& sourcePath, const StringView& assetPath); +#endif + private: static ShaderCompiler* RequestCompiler(ShaderProfile profile, PlatformType platform); static void FreeCompiler(ShaderCompiler* compiler); diff --git a/Source/Engine/Tests/TestShaderSourceSync.cpp b/Source/Engine/Tests/TestShaderSourceSync.cpp new file mode 100644 index 000000000..96bad398b --- /dev/null +++ b/Source/Engine/Tests/TestShaderSourceSync.cpp @@ -0,0 +1,42 @@ +// Copyright (c) Wojciech Figat. All rights reserved. + +#include "Engine/Core/ScopeExit.h" +#include "Engine/Core/Types/DataContainer.h" +#include "Engine/Engine/Globals.h" +#include "Engine/Platform/File.h" +#include "Engine/Platform/FileSystem.h" +#include "Engine/ShadersCompilation/ShadersCompilation.h" +#include + +#if COMPILE_WITH_SHADER_COMPILER && USE_EDITOR + +TEST_CASE("Shader source asset synchronization ignores timestamps") +{ + const String sourcePath = Globals::StartupFolder / TEXT("Source/Shaders/VolumetricFog.shader"); + const String assetPath = Globals::EngineContentFolder / TEXT("Shaders/VolumetricFog.flax"); + REQUIRE(FileSystem::FileExists(sourcePath)); + REQUIRE(FileSystem::FileExists(assetPath)); + CHECK(ShadersCompilation::IsShaderSourceAssetUpToDate(sourcePath, assetPath)); + + const String tempRoot = Globals::TemporaryFolder / (TEXT("ShaderSourceSync-") + Guid::New().ToString(Guid::FormatType::N)); + REQUIRE(!FileSystem::CreateDirectory(tempRoot)); + SCOPE_EXIT + { + FileSystem::DeleteDirectory(tempRoot, true); + }; + + DataContainer assetData; + StringAnsi modifiedSource; + REQUIRE(!File::ReadAllBytes(assetPath, assetData)); + REQUIRE(!File::ReadAllText(sourcePath, modifiedSource)); + modifiedSource.Append("// Deliberately different source\n"); + + const String tempSourcePath = tempRoot / TEXT("VolumetricFog.shader"); + const String tempAssetPath = tempRoot / TEXT("VolumetricFog.flax"); + REQUIRE(!File::WriteAllBytes(tempSourcePath, modifiedSource.Get(), modifiedSource.Length())); + REQUIRE(!File::WriteAllBytes(tempAssetPath, assetData.Get(), assetData.Length())); + CHECK_FALSE(FileSystem::GetFileLastEditTime(tempSourcePath) > FileSystem::GetFileLastEditTime(tempAssetPath)); + CHECK_FALSE(ShadersCompilation::IsShaderSourceAssetUpToDate(tempSourcePath, tempAssetPath)); +} + +#endif diff --git a/Source/Engine/Tests/Tests.Build.cs b/Source/Engine/Tests/Tests.Build.cs index 1056d1002..d55456176 100644 --- a/Source/Engine/Tests/Tests.Build.cs +++ b/Source/Engine/Tests/Tests.Build.cs @@ -21,6 +21,7 @@ public class Tests : EngineModule base.Setup(options); options.PrivateDependencies.Add("ModelTool"); + options.PrivateDependencies.Add("ShadersCompilation"); } ///