Add storing shader asset includes paths in compact format for portability

This commit is contained in:
Wojtek Figat
2023-11-04 15:26:18 +01:00
parent 50bcbf980e
commit 19752e4f3b
4 changed files with 159 additions and 96 deletions

View File

@@ -327,6 +327,18 @@ public:
bool operator!=(const String& other) const;
public:
using StringViewBase::StartsWith;
FORCE_INLINE bool StartsWith(const StringView& prefix, StringSearchCase searchCase = StringSearchCase::IgnoreCase) const
{
return StringViewBase::StartsWith(prefix, searchCase);
}
using StringViewBase::EndsWith;
FORCE_INLINE bool EndsWith(const StringView& suffix, StringSearchCase searchCase = StringSearchCase::IgnoreCase) const
{
return StringViewBase::EndsWith(suffix, searchCase);
}
/// <summary>
/// Gets the left most given number of characters.
/// </summary>
@@ -511,6 +523,18 @@ public:
bool operator!=(const StringAnsi& other) const;
public:
using StringViewBase::StartsWith;
FORCE_INLINE bool StartsWith(const StringAnsiView& prefix, StringSearchCase searchCase = StringSearchCase::IgnoreCase) const
{
return StringViewBase::StartsWith(prefix, searchCase);
}
using StringViewBase::EndsWith;
FORCE_INLINE bool EndsWith(const StringAnsiView& suffix, StringSearchCase searchCase = StringSearchCase::IgnoreCase) const
{
return StringViewBase::EndsWith(suffix, searchCase);
}
/// <summary>
/// Retrieves substring created from characters starting from startIndex to the String end.
/// </summary>

View File

@@ -3,6 +3,7 @@
#if COMPILE_WITH_SHADER_COMPILER
#include "ShaderCompiler.h"
#include "ShadersCompilation.h"
#include "Engine/Core/Log.h"
#include "Engine/Core/Collections/Dictionary.h"
#include "Engine/Engine/Globals.h"
@@ -14,10 +15,6 @@
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Serialization/MemoryWriteStream.h"
#include "Engine/Utilities/StringConverter.h"
#if USE_EDITOR
#include "Editor/Editor.h"
#include "Editor/ProjectInfo.h"
#endif
namespace IncludedFiles
{
@@ -30,31 +27,6 @@ namespace IncludedFiles
CriticalSection Locker;
Dictionary<String, File*> Files;
#if USE_EDITOR
bool FindProject(const ProjectInfo* project, HashSet<const ProjectInfo*>& projects, const StringView& projectName, String& path)
{
if (!project || projects.Contains(project))
return false;
projects.Add(project);
// Check the project name
if (project->Name == projectName)
{
path = project->ProjectFolderPath;
return true;
}
// Initialize referenced projects
for (const auto& reference : project->References)
{
if (reference.Project && FindProject(reference.Project, projects, projectName, path))
return true;
}
return false;
}
#endif
}
bool ShaderCompiler::Compile(ShaderCompilationContext* context)
@@ -124,7 +96,8 @@ bool ShaderCompiler::Compile(ShaderCompilationContext* context)
output->WriteInt32(context->Includes.Count());
for (auto& include : context->Includes)
{
output->WriteString(include.Item, 11);
String compactPath = ShadersCompilation::CompactShaderPath(include.Item);
output->WriteString(compactPath, 11);
const auto date = FileSystem::GetFileLastEditTime(include.Item);
output->Write(date);
}
@@ -138,75 +111,17 @@ bool ShaderCompiler::GetIncludedFileSource(ShaderCompilationContext* context, co
source = nullptr;
sourceLength = 0;
// Skip to the last root start './' but preserve the leading one
const int32 includedFileLength = StringUtils::Length(includedFile);
for (int32 i = includedFileLength - 2; i >= 2; i--)
// Get actual file path
const String includedFileName(includedFile);
String path = ShadersCompilation::ResolveShaderPath(includedFileName);
if (!FileSystem::FileExists(path))
{
if (StringUtils::Compare(includedFile + i, "./", 2) == 0)
{
includedFile = includedFile + i;
break;
}
LOG(Error, "Unknown shader source file '{0}' included in '{1}'.{2}", includedFileName, String(sourceFile), String::Empty);
return true;
}
ScopeLock lock(IncludedFiles::Locker);
// Find the included file path
String path;
#if USE_EDITOR
if (StringUtils::Compare(includedFile, "./", 2) == 0)
{
int32 projectNameEnd = -1;
for (int32 i = 2; i < includedFileLength; i++)
{
if (includedFile[i] == '/')
{
projectNameEnd = i;
break;
}
}
if (projectNameEnd == -1)
{
LOG(Error, "Unknown shader source file '{0}' included in '{1}'.{2}", String(includedFile), String(sourceFile), TEXT("Missing project name after root path."));
return true;
}
const StringAsUTF16<120> projectName(includedFile + 2, projectNameEnd - 2);
if (StringUtils::Compare(projectName.Get(), TEXT("FlaxPlatforms")) == 0)
{
// Hard-coded redirect to platform-specific includes
path /= Globals::StartupFolder / TEXT("Source/Platforms");
}
else
{
HashSet<const ProjectInfo*> projects;
if (!IncludedFiles::FindProject(Editor::Project, projects, StringView(projectName.Get(), projectNameEnd - 2), path))
{
LOG(Error, "Unknown shader source file '{0}' included in '{1}'.{2}", String(includedFile), String(sourceFile), TEXT("Failed to find the project of the given name."));
return true;
}
path /= TEXT("Source/Shaders/");
}
const StringAsUTF16<250> localPath(includedFile + projectNameEnd + 1, includedFileLength - projectNameEnd - 1);
path /= localPath.Get();
}
#else
if (StringUtils::Compare(includedFile, "./Flax/", 7) == 0)
{
// Engine project relative shader path
const auto includedFileStr = String(includedFile + 6);
path = Globals::StartupFolder / TEXT("Source/Shaders") / includedFileStr;
}
#endif
else if (FileSystem::FileExists(path = String(includedFile)))
{
// Absolute shader path
}
else
{
LOG(Error, "Unknown shader source file '{0}' included in '{1}'.{2}", String(includedFile), String(sourceFile), String::Empty);
return true;
}
// Try to reuse file
IncludedFiles::File* result = nullptr;
if (!IncludedFiles::Files.TryGet(path, result) || FileSystem::GetFileLastEditTime(path) > result->LastEditTime)

View File

@@ -33,7 +33,6 @@
#include "Editor/Editor.h"
#include "Editor/ProjectInfo.h"
#endif
#if COMPILE_WITH_D3D_SHADER_COMPILER
#include "DirectX/ShaderCompilerD3D.h"
#endif
@@ -55,6 +54,49 @@ namespace ShadersCompilationImpl
CriticalSection Locker;
Array<ShaderCompiler*> Compilers;
Array<ShaderCompiler*> ReadyCompilers;
#if USE_EDITOR
const ProjectInfo* FindProjectByName(const ProjectInfo* project, HashSet<const ProjectInfo*>& projects, const StringView& projectName)
{
if (!project || projects.Contains(project))
return nullptr;
projects.Add(project);
// Check the project name
if (project->Name == projectName)
return project;
// Search referenced projects
for (const auto& reference : project->References)
{
const ProjectInfo* result = FindProjectByName(reference.Project, projects, projectName);
if (result)
return result;
}
return nullptr;
}
const ProjectInfo* FindProjectByPath(const ProjectInfo* project, HashSet<const ProjectInfo*>& projects, const StringView& projectPath)
{
if (!project || projects.Contains(project))
return nullptr;
projects.Add(project);
// Search referenced projects (depth first to handle plugin projects first)
for (const auto& reference : project->References)
{
const ProjectInfo* result = FindProjectByPath(reference.Project, projects, projectPath);
if (result)
return result;
}
// Check the project path
if (projectPath.StartsWith(project->ProjectFolderPath))
return project;
return nullptr;
}
#endif
}
using namespace ShadersCompilationImpl;
@@ -361,11 +403,89 @@ void ShadersCompilation::ExtractShaderIncludes(byte* shaderCache, int32 shaderCa
{
String& include = includes.AddOne();
stream.ReadString(&include, 11);
include = ShadersCompilation::ResolveShaderPath(include);
DateTime lastEditTime;
stream.Read(lastEditTime);
}
}
String ShadersCompilation::ResolveShaderPath(StringView path)
{
// Skip to the last root start './' but preserve the leading one
for (int32 i = path.Length() - 2; i >= 2; i--)
{
if (StringUtils::Compare(path.Get() + i, TEXT("./"), 2) == 0)
{
path = path.Substring(i);
break;
}
}
// Find the included file path
String result;
#if USE_EDITOR
if (path.StartsWith(StringView(TEXT("./"), 2)))
{
int32 projectNameEnd = -1;
for (int32 i = 2; i < path.Length(); i++)
{
if (path[i] == '/')
{
projectNameEnd = i;
break;
}
}
if (projectNameEnd == -1)
return String::Empty; // Invalid project path
StringView projectName = path.Substring(2, projectNameEnd - 2);
if (projectName.StartsWith(StringView(TEXT("FlaxPlatforms"))))
{
// Hard-coded redirect to platform-specific includes
result = Globals::StartupFolder / TEXT("Source/Platforms");
}
else
{
HashSet<const ProjectInfo*> projects;
const ProjectInfo* project = FindProjectByName(Editor::Project, projects, StringView(projectName.Get(), projectNameEnd - 2));
if (project)
result = project->ProjectFolderPath / TEXT("/Source/Shaders/");
else
return String::Empty;
}
result /= path.Substring(projectNameEnd + 1);
}
#else
if (path.StartsWith(StringView(TEXT("./Flax/"), 7)))
{
// Engine project relative shader path
result = Globals::StartupFolder / TEXT("Source/Shaders") / path.Substring(6);
}
#endif
else
{
// Absolute shader path
result = path;
}
return result;
}
String ShadersCompilation::CompactShaderPath(StringView path)
{
#if USE_EDITOR
// Try to use file path relative to the project shader sources folder
HashSet<const ProjectInfo*> projects;
const ProjectInfo* project = FindProjectByPath(Editor::Project, projects, path);
if (project)
{
String projectSourcesPath = project->ProjectFolderPath / TEXT("/Source/Shaders/");
if (path.StartsWith(projectSourcesPath))
return String::Format(TEXT("./{}/{}"), project->Name, path.Substring(projectSourcesPath.Length()));
}
#endif
return String(path);
}
#if USE_EDITOR
namespace

View File

@@ -14,7 +14,6 @@ class Asset;
class FLAXENGINE_API ShadersCompilation
{
public:
/// <summary>
/// Compiles the shader.
/// </summary>
@@ -43,6 +42,11 @@ public:
/// <param name="includes">The output included.</param>
static void ExtractShaderIncludes(byte* shaderCache, int32 shaderCacheLength, Array<String>& includes);
// Resolves shader path name into absolute file path. Resolves './<ProjectName>/ShaderFile.hlsl' cases into a full path.
static String ResolveShaderPath(StringView path);
// Compacts the full shader file path into portable format with project name prefix such as './<ProjectName>/ShaderFile.hlsl'.
static String CompactShaderPath(StringView path);
private:
static ShaderCompiler* CreateCompiler(ShaderProfile profile);