get rid of timestamp comparison for shaders, compare actual data

# Conflicts:
#	Source/Engine/Graphics/Materials/MaterialShader.h
#	Source/Engine/Tests/Tests.Build.cs
This commit is contained in:
Roman Zhu
2026-08-30 22:05:42 +02:00
committed by Wojtek Figat
parent 3fcad576a1
commit c727ffa70d
4 changed files with 95 additions and 2 deletions
@@ -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<FileSystemWatcher*> 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<const ProjectInfo*>& 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);
}
}
@@ -47,6 +47,16 @@ public:
// Compacts the full shader file path into portable format with project name prefix such as './<ProjectName>/ShaderFile.hlsl'.
static String CompactShaderPath(StringView path);
#if USE_EDITOR
/// <summary>
/// Checks whether a shader asset embeds the current source file contents.
/// </summary>
/// <param name="sourcePath">The shader source file path.</param>
/// <param name="assetPath">The shader asset file path.</param>
/// <returns>True when the embedded source matches, otherwise false.</returns>
static bool IsShaderSourceAssetUpToDate(const StringView& sourcePath, const StringView& assetPath);
#endif
private:
static ShaderCompiler* RequestCompiler(ShaderProfile profile, PlatformType platform);
static void FreeCompiler(ShaderCompiler* compiler);
@@ -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 <ThirdParty/catch2/catch.hpp>
#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<byte> 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
+1
View File
@@ -21,6 +21,7 @@ public class Tests : EngineModule
base.Setup(options);
options.PrivateDependencies.Add("ModelTool");
options.PrivateDependencies.Add("ShadersCompilation");
}
/// <inheritdoc />