You're breathtaking!

This commit is contained in:
Wojtek Figat
2020-12-07 23:40:54 +01:00
commit 6fb9eee74c
5143 changed files with 1153594 additions and 0 deletions

View File

@@ -0,0 +1,72 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#pragma once
#include "Engine/Core/Types/String.h"
#include "Engine/Core/Types/Guid.h"
#include "Engine/Core/Collections/Array.h"
#include "Engine/Graphics/Shaders/Config.h"
class MemoryWriteStream;
/// <summary>
/// Shader compilation options container
/// </summary>
struct FLAXENGINE_API ShaderCompilationOptions
{
public:
/// <summary>
/// Name of the target object (name of the shader or material for better logging readability)
/// </summary>
String TargetName;
/// <summary>
/// Unique ID of the target object
/// </summary>
Guid TargetID = Guid::Empty;
/// <summary>
/// Shader source code (null terminated)
/// </summary>
const char* Source = nullptr;
/// <summary>
/// Shader source code length
/// </summary>
uint32 SourceLength = 0;
public:
/// <summary>
/// Target shader profile
/// </summary>
ShaderProfile Profile = ShaderProfile::Unknown;
/// <summary>
/// Disables shaders compiler optimizations. Can be used to debug shaders on a target platform or to speed up the shaders compilation time.
/// </summary>
bool NoOptimize = false;
/// <summary>
/// Enables shader debug data generation (depends on the target platform rendering backend).
/// </summary>
bool GenerateDebugData = false;
/// <summary>
/// Enable/disable promoting warnings to compilation errors
/// </summary>
bool TreatWarningsAsErrors = false;
/// <summary>
/// Custom macros for the shader compilation
/// </summary>
Array<ShaderMacro> Macros;
public:
/// <summary>
/// Output stream to write compiled shader cache to
/// </summary>
MemoryWriteStream* Output = nullptr;
};

View File

@@ -0,0 +1,38 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
using System.Collections.Generic;
using System.IO;
using Flax.Build.NativeCpp;
/// <summary>
/// DirectX shaders compiler module using D3DCompiler.
/// </summary>
public class ShaderCompilerD3D : ShaderCompiler
{
/// <inheritdoc />
public override void Setup(BuildOptions options)
{
base.Setup(options);
options.SourceFiles.Clear();
options.SourcePaths.Clear();
options.SourceFiles.Add(Path.Combine(FolderPath, "ShaderCompilerD3D.h"));
options.SourceFiles.Add(Path.Combine(FolderPath, "ShaderCompilerD3D.cpp"));
options.PublicDefinitions.Add("COMPILE_WITH_D3D_SHADER_COMPILER");
var depsRoot = options.DepsFolder;
options.OutputFiles.Add("dxguid.lib");
options.OutputFiles.Add("d3dcompiler.lib");
options.DependencyFiles.Add(Path.Combine(depsRoot, "d3dcompiler_47.dll"));
options.DelayLoadLibraries.Add("d3dcompiler_47.dll");
}
/// <inheritdoc />
public override void GetFilesToDeploy(List<string> files)
{
base.GetFilesToDeploy(files);
files.Add(Path.Combine(FolderPath, "ShaderCompilerD3D.h"));
}
}

View File

@@ -0,0 +1,351 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#if COMPILE_WITH_D3D_SHADER_COMPILER
#include "ShaderCompilerD3D.h"
#include "Engine/Core/Log.h"
#include "Engine/Serialization/MemoryWriteStream.h"
#include "Engine/Graphics/Config.h"
#include "Engine/GraphicsDevice/DirectX/IncludeDirectXHeaders.h"
#include "Engine/Platform/Windows/ComPtr.h"
#include <d3dcompiler.h>
/// <summary>
/// Helper class to include source for D3D shaders compiler.
/// </summary>
class IncludeD3D : public ID3DInclude
{
private:
ShaderCompilationContext* _context;
public:
IncludeD3D(ShaderCompilationContext* context)
{
_context = context;
}
HRESULT STDMETHODCALLTYPE Open(D3D_INCLUDE_TYPE IncludeType, LPCSTR pFileName, LPCVOID pParentData, LPCVOID* ppData, UINT* pBytes) override
{
const char* source;
int32 sourceLength;
const StringAnsi filename(pFileName);
if (ShaderCompiler::GetIncludedFileSource(_context, "", filename.Get(), source, sourceLength))
return E_FAIL;
*ppData = source;
*pBytes = sourceLength;
return S_OK;
}
HRESULT STDMETHODCALLTYPE Close(LPCVOID pData) override
{
return S_OK;
}
};
ShaderCompilerD3D::ShaderCompilerD3D(ShaderProfile profile)
: ShaderCompiler(profile)
{
}
#ifdef GPU_USE_SHADERS_DEBUG_LAYER
namespace
{
bool ProcessDebugInfo(ShaderCompilationContext* context, ShaderFunctionMeta& meta, int32 permutationIndex, void* srcData, uint32 srcDataSize)
{
// TODO: test with D3D_DISASM_ENABLE_INSTRUCTION_CYCLE option
ComPtr<ID3DBlob> debug;
HRESULT result = D3DDisassemble(srcData, srcDataSize, D3D_DISASM_ENABLE_DEFAULT_VALUE_PRINTS | D3D_DISASM_ENABLE_INSTRUCTION_NUMBERING, nullptr, &debug);
if (FAILED(result))
{
LOG(Warning, "DirectX error: {0} at {1}:{2}", result, TEXT(__FILE__), __LINE__);
context->OnError("D3DDisassemble failed.");
return true;
}
// Extract debug info
char* debugData = static_cast<char*>(debug->GetBufferPointer());
const int32 debugDataSize = static_cast<int32>(debug->GetBufferSize());
context->OnCollectDebugInfo(meta, permutationIndex, debugData, debugDataSize);
return false;
}
}
#endif
namespace
{
bool ProcessShader(ShaderCompilationContext* context, Array<ShaderCompiler::ShaderResourceBuffer>& constantBuffers, ID3D11ShaderReflection* reflector, D3D11_SHADER_DESC& desc, ShaderBindings& bindings)
{
// Extract constant buffers usage information
for (uint32 a = 0; a < desc.ConstantBuffers; a++)
{
// Get CB
auto cb = reflector->GetConstantBufferByIndex(a);
// Get CB description
D3D11_SHADER_BUFFER_DESC cbDesc;
cb->GetDesc(&cbDesc);
// Check buffer type
if (cbDesc.Type == D3D_CT_CBUFFER)
{
// Find CB slot index
int32 slot = INVALID_INDEX;
for (uint32 b = 0; b < desc.BoundResources; b++)
{
D3D11_SHADER_INPUT_BIND_DESC bDesc;
reflector->GetResourceBindingDesc(b, &bDesc);
if (StringUtils::Compare(bDesc.Name, cbDesc.Name) == 0)
{
slot = bDesc.BindPoint;
break;
}
}
if (slot == INVALID_INDEX)
{
context->OnError("Missing bound resource.");
return true;
}
// Set flag
bindings.UsedCBsMask |= 1 << slot;
// Try to add CB to the list
for (int32 b = 0; b < constantBuffers.Count(); b++)
{
auto& cc = constantBuffers[b];
if (cc.Slot == slot)
{
cc.IsUsed = true;
cc.Size = cbDesc.Size;
break;
}
}
}
}
// Extract resources usage
for (uint32 i = 0; i < desc.BoundResources; i++)
{
// Get resource description
D3D11_SHADER_INPUT_BIND_DESC resDesc;
reflector->GetResourceBindingDesc(i, &resDesc);
switch (resDesc.Type)
{
// Sampler
case D3D_SIT_SAMPLER:
break;
// Constant Buffer
case D3D_SIT_CBUFFER:
case D3D_SIT_TBUFFER:
break;
// Shader Resource
case D3D_SIT_TEXTURE:
case D3D_SIT_STRUCTURED:
case D3D_SIT_BYTEADDRESS:
bindings.UsedSRsMask |= 1 << resDesc.BindPoint;
break;
// Unordered Access
case D3D_SIT_UAV_RWTYPED:
case D3D_SIT_UAV_RWSTRUCTURED:
case D3D_SIT_UAV_RWBYTEADDRESS:
case D3D_SIT_UAV_APPEND_STRUCTURED:
case D3D_SIT_UAV_CONSUME_STRUCTURED:
case D3D_SIT_UAV_RWSTRUCTURED_WITH_COUNTER:
bindings.UsedUAsMask |= 1 << resDesc.BindPoint;
break;
}
}
return false;
}
}
bool ShaderCompilerD3D::CompileShader(ShaderFunctionMeta& meta, WritePermutationData customDataWrite)
{
if (WriteShaderFunctionBegin(_context, meta))
return true;
// Prepare
auto options = _context->Options;
auto type = meta.GetStage();
IncludeD3D include(_context);
StringAnsi profileName;
switch (type)
{
case ShaderStage::Vertex:
profileName = "vs";
break;
case ShaderStage::Hull:
profileName = "hs";
break;
case ShaderStage::Domain:
profileName = "ds";
break;
case ShaderStage::Geometry:
profileName = "gs";
break;
case ShaderStage::Pixel:
profileName = "ps";
break;
case ShaderStage::Compute:
profileName = "cs";
break;
default:
return true;
}
if (_profile == ShaderProfile::DirectX_SM5)
{
profileName += "_5_0";
}
else
{
profileName += "_4_0";
if (type == ShaderStage::Domain || type == ShaderStage::Hull)
{
_context->OnError("Tessellation is not supported on DirectX 10");
return true;
}
}
// Compile all shader function permutations
for (int32 permutationIndex = 0; permutationIndex < meta.Permutations.Count(); permutationIndex++)
{
_macros.Clear();
// Get function permutation macros
meta.GetDefinitionsForPermutation(permutationIndex, _macros);
// Add additional define for compiled function name
GetDefineForFunction(meta, _macros);
// Add custom and global macros (global last because contain null define to indicate ending)
_macros.Add(_context->Options->Macros);
_macros.Add(_globalMacros);
// Compile
ComPtr<ID3DBlob> errors;
ComPtr<ID3DBlob> shader;
HRESULT result = D3DCompile2(
options->Source,
options->SourceLength,
_context->TargetNameAnsi,
reinterpret_cast<const D3D_SHADER_MACRO*>(_macros.Get()),
&include,
meta.Name.Get(),
profileName.Get(),
_flags,
0,
0,
nullptr,
0,
&shader,
&errors);
// Check compilation result
if (FAILED(result))
{
const auto msg = static_cast<const char*>(errors->GetBufferPointer());
_context->OnError(msg);
return true;
}
void* shaderBuffer = shader->GetBufferPointer();
uint32 shaderBufferSize = static_cast<int32>(shader->GetBufferSize());
// Perform shader reflection
ComPtr<ID3D11ShaderReflection> reflector;
result = D3DReflect(shader->GetBufferPointer(), shader->GetBufferSize(), IID_ID3D11ShaderReflection, (void**)&reflector);
if (FAILED(result))
{
LOG(Warning, "DirectX error: {0} at {1}:{2}", result, TEXT(__FILE__), __LINE__);
_context->OnError("D3DReflect failed.");
return true;
}
// Get shader description
D3D11_SHADER_DESC desc;
reflector->GetDesc(&desc);
// Process shader reflection data
ShaderBindings bindings = { desc.InstructionCount, 0, 0, 0 };
if (ProcessShader(_context, _constantBuffers, reflector.Get(), desc, bindings))
return true;
#ifdef GPU_USE_SHADERS_DEBUG_LAYER
// Generate debug information
if (ProcessDebugInfo(_context, meta, permutationIndex, shaderBuffer, shaderBufferSize))
return true;
#endif
// Strip shader bytecode for an optimization
ComPtr<ID3DBlob> shaderStripped;
if (!options->GenerateDebugData)
{
//auto prevShaderBufferSize = shaderBufferSize;
// Strip shader bytes
result = D3DStripShader(
shaderBuffer,
shaderBufferSize,
D3DCOMPILER_STRIP_REFLECTION_DATA | D3DCOMPILER_STRIP_DEBUG_INFO | D3DCOMPILER_STRIP_TEST_BLOBS,
&shaderStripped);
if (FAILED(result))
{
LOG(Warning, "Cannot strip shader.");
return true;
}
// Set new buffer
shaderBuffer = shaderStripped->GetBufferPointer();
shaderBufferSize = static_cast<int32>(shaderStripped->GetBufferSize());
//auto strippedBytes = prevShaderBufferSize - shaderBufferSize;
//auto strippedBytesPercentage = Math::FloorToInt(strippedBytes * 100.0f / prevShaderBufferSize);
}
if (WriteShaderFunctionPermutation(_context, meta, permutationIndex, bindings, shaderBuffer, shaderBufferSize))
return true;
if (customDataWrite && customDataWrite(_context, meta, permutationIndex, _macros))
return true;
}
return WriteShaderFunctionEnd(_context, meta);
}
bool ShaderCompilerD3D::OnCompileBegin()
{
if (ShaderCompiler::OnCompileBegin())
return true;
_globalMacros.Add({ "DIRECTX", "1" });
_flags = 0;
if (_context->Options->NoOptimize)
_flags |= D3DCOMPILE_SKIP_OPTIMIZATION;
else
_flags |= D3DCOMPILE_OPTIMIZATION_LEVEL3;
if (_context->Options->GenerateDebugData)
_flags |= D3DCOMPILE_DEBUG;
if (_context->Options->TreatWarningsAsErrors)
_flags |= D3DCOMPILE_WARNINGS_ARE_ERRORS;
#if GRAPHICS_API_DIRECTX12
_flags |= D3DCOMPILE_ALL_RESOURCES_BOUND;
#endif
return false;
}
#endif

View File

@@ -0,0 +1,34 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#pragma once
#if COMPILE_WITH_D3D_SHADER_COMPILER
#include "Engine/ShadersCompilation/ShaderCompiler.h"
/// <summary>
/// Implementation of shaders compiler for DirectX platform using D3DCompiler.
/// </summary>
class ShaderCompilerD3D : public ShaderCompiler
{
private:
Array<char> _funcNameDefineBuffer;
uint32 _flags;
public:
/// <summary>
/// Initializes a new instance of the <see cref="ShaderCompilerD3D"/> class.
/// </summary>
/// <param name="profile">The profile.</param>
ShaderCompilerD3D(ShaderProfile profile);
protected:
// [ShaderCompiler]
bool CompileShader(ShaderFunctionMeta& meta, WritePermutationData customDataWrite = nullptr) override;
bool OnCompileBegin() override;
};
#endif

View File

@@ -0,0 +1,38 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
using System.Collections.Generic;
using System.IO;
using Flax.Build.NativeCpp;
/// <summary>
/// DirectX shaders compiler module using https://github.com/microsoft/DirectXShaderCompiler.
/// </summary>
public class ShaderCompilerDX : ShaderCompiler
{
/// <inheritdoc />
public override void Setup(BuildOptions options)
{
base.Setup(options);
options.SourceFiles.Clear();
options.SourcePaths.Clear();
options.SourceFiles.Add(Path.Combine(FolderPath, "ShaderCompilerDX.h"));
options.SourceFiles.Add(Path.Combine(FolderPath, "ShaderCompilerDX.cpp"));
options.PublicDefinitions.Add("COMPILE_WITH_DX_SHADER_COMPILER");
var depsRoot = options.DepsFolder;
options.OutputFiles.Add("dxcompiler.lib");
options.DependencyFiles.Add(Path.Combine(depsRoot, "dxcompiler.dll"));
options.DependencyFiles.Add(Path.Combine(depsRoot, "dxil.dll"));
options.DelayLoadLibraries.Add("dxcompiler.dll");
}
/// <inheritdoc />
public override void GetFilesToDeploy(List<string> files)
{
base.GetFilesToDeploy(files);
files.Add(Path.Combine(FolderPath, "ShaderCompilerDX.h"));
}
}

View File

@@ -0,0 +1,403 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#if COMPILE_WITH_DX_SHADER_COMPILER
#include "ShaderCompilerDX.h"
#include "Engine/Core/Log.h"
#include "Engine/Threading/Threading.h"
#include "Engine/Graphics/Config.h"
#include "Engine/Utilities/StringConverter.h"
#include "Engine/Platform/Win32/IncludeWindowsHeaders.h"
#include "Engine/Platform/Windows/ComPtr.h"
#include <d3d12shader.h>
#include <ThirdParty/DirectXShaderCompiler/dxcapi.h>
#ifndef DXIL_FOURCC
#define DXIL_FOURCC(ch0, ch1, ch2, ch3) ((uint32)(uint8)(ch0) | (uint32)(uint8)(ch1) << 8 | (uint32)(uint8)(ch2) << 16 | (uint32)(uint8)(ch3) << 24)
#endif
/// <summary>
/// Helper class to include source for DX shaders compiler.
/// </summary>
class IncludeDX : public IDxcIncludeHandler
{
private:
ShaderCompilationContext* _context;
IDxcLibrary* _library;
public:
IncludeDX(ShaderCompilationContext* context, IDxcLibrary* library)
{
_context = context;
_library = library;
}
ULONG STDMETHODCALLTYPE AddRef() override
{
return 1;
}
ULONG STDMETHODCALLTYPE Release() override
{
return 1;
}
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject) override
{
if (riid == __uuidof(IDxcIncludeHandler) || riid == __uuidof(IUnknown))
{
AddRef();
*ppvObject = this;
return S_OK;
}
*ppvObject = nullptr;
return E_NOINTERFACE;
}
HRESULT STDMETHODCALLTYPE LoadSource(_In_ LPCWSTR pFilename, _COM_Outptr_result_maybenull_ IDxcBlob** ppIncludeSource) override
{
*ppIncludeSource = nullptr;
const char* source;
int32 sourceLength;
const StringAnsi filename(pFilename);
if (ShaderCompiler::GetIncludedFileSource(_context, "", filename.Get(), source, sourceLength))
return E_FAIL;
IDxcBlobEncoding* textBlob;
if (FAILED(_library->CreateBlobWithEncodingFromPinned((LPBYTE)source, sourceLength, CP_UTF8, &textBlob)))
return E_FAIL;
*ppIncludeSource = textBlob;
return S_OK;
}
};
ShaderCompilerDX::ShaderCompilerDX(ShaderProfile profile)
: ShaderCompiler(profile)
{
IDxcCompiler2* compiler = nullptr;
IDxcLibrary* library = nullptr;
IDxcContainerReflection* containerReflection = nullptr;
if (FAILED(DxcCreateInstance(CLSID_DxcCompiler, __uuidof(compiler), reinterpret_cast<void**>(&compiler))) ||
FAILED(DxcCreateInstance(CLSID_DxcLibrary, __uuidof(library), reinterpret_cast<void**>(&library))) ||
FAILED(DxcCreateInstance(CLSID_DxcContainerReflection, __uuidof(containerReflection), reinterpret_cast<void**>(&containerReflection))))
{
LOG(Error, "DxcCreateInstance failed");
}
_compiler = compiler;
_library = library;
_containerReflection = containerReflection;
static bool PrintVersion = true;
if (PrintVersion)
{
PrintVersion = false;
IDxcVersionInfo* version = nullptr;
if (compiler && SUCCEEDED(compiler->QueryInterface(__uuidof(version), reinterpret_cast<void**>(&version))))
{
UINT32 major, minor;
version->GetVersion(&major, &minor);
LOG(Info, "DXC version {0}.{1}", major, minor);
version->Release();
}
}
}
ShaderCompilerDX::~ShaderCompilerDX()
{
auto compiler = (IDxcCompiler2*)_compiler;
if (compiler)
compiler->Release();
auto library = (IDxcLibrary*)_library;
if (library)
library->Release();
auto containerReflection = (IDxcContainerReflection*)_containerReflection;
if (containerReflection)
containerReflection->Release();
}
namespace
{
bool ProcessShader(ShaderCompilationContext* context, Array<ShaderCompiler::ShaderResourceBuffer>& constantBuffers, ID3D12ShaderReflection* shaderReflection, D3D12_SHADER_DESC& desc, ShaderBindings& bindings)
{
// Extract constant buffers usage information
for (uint32 a = 0; a < desc.ConstantBuffers; a++)
{
// Get CB
auto cb = shaderReflection->GetConstantBufferByIndex(a);
// Get CB description
D3D12_SHADER_BUFFER_DESC cbDesc;
cb->GetDesc(&cbDesc);
// Check buffer type
if (cbDesc.Type == D3D_CT_CBUFFER)
{
// Find CB slot index
int32 slot = INVALID_INDEX;
for (uint32 b = 0; b < desc.BoundResources; b++)
{
D3D12_SHADER_INPUT_BIND_DESC bDesc;
shaderReflection->GetResourceBindingDesc(b, &bDesc);
if (StringUtils::Compare(bDesc.Name, cbDesc.Name) == 0)
{
slot = bDesc.BindPoint;
break;
}
}
if (slot == INVALID_INDEX)
{
context->OnError("Missing bound resource.");
return true;
}
// Set flag
bindings.UsedCBsMask |= 1 << slot;
// Try to add CB to the list
for (int32 b = 0; b < constantBuffers.Count(); b++)
{
auto& cc = constantBuffers[b];
if (cc.Slot == slot)
{
cc.IsUsed = true;
cc.Size = cbDesc.Size;
break;
}
}
}
}
// Extract resources usage
for (uint32 i = 0; i < desc.BoundResources; i++)
{
// Get resource description
D3D12_SHADER_INPUT_BIND_DESC resDesc;
shaderReflection->GetResourceBindingDesc(i, &resDesc);
switch (resDesc.Type)
{
// Sampler
case D3D_SIT_SAMPLER:
break;
// Constant Buffer
case D3D_SIT_CBUFFER:
case D3D_SIT_TBUFFER:
break;
// Shader Resource
case D3D_SIT_TEXTURE:
case D3D_SIT_STRUCTURED:
case D3D_SIT_BYTEADDRESS:
bindings.UsedSRsMask |= 1 << resDesc.BindPoint;
break;
// Unordered Access
case D3D_SIT_UAV_RWTYPED:
case D3D_SIT_UAV_RWSTRUCTURED:
case D3D_SIT_UAV_RWBYTEADDRESS:
case D3D_SIT_UAV_APPEND_STRUCTURED:
case D3D_SIT_UAV_CONSUME_STRUCTURED:
case D3D_SIT_UAV_RWSTRUCTURED_WITH_COUNTER:
bindings.UsedUAsMask |= 1 << resDesc.BindPoint;
break;
}
}
return false;
}
}
bool ShaderCompilerDX::CompileShader(ShaderFunctionMeta& meta, WritePermutationData customDataWrite)
{
if (WriteShaderFunctionBegin(_context, meta))
return true;
// Prepare
auto options = _context->Options;
auto compiler = (IDxcCompiler2*)_compiler;
auto library = (IDxcLibrary*)_library;
auto containerReflection = (IDxcContainerReflection*)_containerReflection;
auto type = meta.GetStage();
IncludeDX include(_context, library);
const Char* targetProfile;
switch (type)
{
case ShaderStage::Vertex:
targetProfile = TEXT("vs_6_0");
break;
case ShaderStage::Hull:
targetProfile = TEXT("hs_6_0");
break;
case ShaderStage::Domain:
targetProfile = TEXT("ds_6_0");
break;
case ShaderStage::Geometry:
targetProfile = TEXT("gs_6_0");
break;
case ShaderStage::Pixel:
targetProfile = TEXT("ps_6_0");
break;
case ShaderStage::Compute:
targetProfile = TEXT("cs_6_0");
break;
default:
return true;
}
ComPtr<IDxcBlobEncoding> textBlob;
if (FAILED(library->CreateBlobWithEncodingFromPinned((LPBYTE)options->Source, options->SourceLength, CP_UTF8, &textBlob)))
return true;
const StringAsUTF16<> entryPoint(meta.Name.Get(), meta.Name.Length());
Array<String> definesStrings;
Array<DxcDefine> defines;
Array<Char*, FixedAllocation<16>> args;
if (_context->Options->NoOptimize)
args.Add(TEXT("-Od"));
else
args.Add(TEXT("-O3"));
if (_context->Options->TreatWarningsAsErrors)
args.Add(TEXT("-WX"));
if (_context->Options->GenerateDebugData)
args.Add(TEXT("-Zi"));
// Compile all shader function permutations
for (int32 permutationIndex = 0; permutationIndex < meta.Permutations.Count(); permutationIndex++)
{
_macros.Clear();
// Get function permutation macros
meta.GetDefinitionsForPermutation(permutationIndex, _macros);
// Add additional define for compiled function name
GetDefineForFunction(meta, _macros);
// Add custom and global macros (global last because contain null define to indicate ending)
_macros.Add(_context->Options->Macros);
_macros.Add(_globalMacros);
// Convert defines from char* to Char*
const int32 macrosCount = _macros.Count() - 1;
definesStrings.Resize(macrosCount * 2);
defines.Resize(macrosCount);
for (int32 i = 0; i < macrosCount; i++)
{
auto& macro = _macros[i];
auto& define = defines[i];
auto& defineName = definesStrings[i * 2];
auto& defineValue = definesStrings[i * 2 + 1];
defineName = macro.Name;
defineValue = macro.Definition;
define.Name = defineName.GetText();
define.Value = defineValue.Get();
}
// Compile
ComPtr<IDxcOperationResult> results;
HRESULT result = compiler->Compile(
textBlob.Get(),
options->TargetName.Get(),
entryPoint.Get(),
targetProfile,
(LPCWSTR*)args.Get(),
args.Count(),
defines.Get(),
defines.Count(),
&include,
&results);
if (SUCCEEDED(result) && results)
results->GetStatus(&result);
if (FAILED(result))
{
if (results)
{
ComPtr<IDxcBlobEncoding> error;
results->GetErrorBuffer(&error);
if (error && error->GetBufferSize() > 0)
{
ComPtr<IDxcBlobEncoding> errorUtf8;
library->GetBlobAsUtf8(error, &errorUtf8);
if (errorUtf8)
{
_context->OnError((const char*)errorUtf8->GetBufferPointer());
}
}
}
return true;
}
// Get the output
ComPtr<IDxcBlob> shaderBuffer = nullptr;
if (FAILED(results->GetResult(&shaderBuffer)))
{
LOG(Error, "IDxcOperationResult::GetResult failed.");
return true;
}
#ifdef GPU_USE_SHADERS_DEBUG_LAYER
// Generate debug information
{
// Disassemble compiled shader
ComPtr<IDxcBlobEncoding> disassembly;
if (FAILED(compiler->Disassemble(shaderBuffer, &disassembly)))
return true;
ComPtr<IDxcBlobEncoding> disassemblyUtf8;
if (FAILED(library->GetBlobAsUtf8(disassembly, &disassemblyUtf8)))
return true;
// Extract debug info
_context->OnCollectDebugInfo(meta, permutationIndex, (const char*)disassemblyUtf8->GetBufferPointer(), (int32)disassemblyUtf8->GetBufferSize());
}
#endif
// Perform shader reflection
if (FAILED(containerReflection->Load(shaderBuffer)))
{
LOG(Error, "IDxcContainerReflection::Load failed.");
return true;
}
const uint32 dxilPartKind = DXIL_FOURCC('D', 'X', 'I', 'L');
uint32 dxilPartIndex = ~0u;
if (FAILED(containerReflection->FindFirstPartKind(dxilPartKind, &dxilPartIndex)))
{
LOG(Error, "IDxcContainerReflection::FindFirstPartKind failed.");
return true;
}
ComPtr<ID3D12ShaderReflection> shaderReflection;
if (FAILED(containerReflection->GetPartReflection(dxilPartIndex, IID_PPV_ARGS(&shaderReflection))))
{
LOG(Error, "IDxcContainerReflection::GetPartReflection failed.");
return true;
}
// Get shader description
D3D12_SHADER_DESC desc;
Platform::MemoryClear(&desc, sizeof(desc));
shaderReflection->GetDesc(&desc);
// Process shader reflection data
ShaderBindings bindings = { desc.InstructionCount, 0, 0, 0 };
if (ProcessShader(_context, _constantBuffers, shaderReflection.Get(), desc, bindings))
return true;
if (WriteShaderFunctionPermutation(_context, meta, permutationIndex, bindings, shaderBuffer->GetBufferPointer(), (int32)shaderBuffer->GetBufferSize()))
return true;
if (customDataWrite && customDataWrite(_context, meta, permutationIndex, _macros))
return true;
}
return WriteShaderFunctionEnd(_context, meta);
}
bool ShaderCompilerDX::OnCompileBegin()
{
if (ShaderCompiler::OnCompileBegin())
return true;
_globalMacros.Add({ "DIRECTX", "1" });
return false;
}
#endif

View File

@@ -0,0 +1,41 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#pragma once
#if COMPILE_WITH_DX_SHADER_COMPILER
#include "Engine/ShadersCompilation/ShaderCompiler.h"
/// <summary>
/// Implementation of shaders compiler for DirectX platform using https://github.com/microsoft/DirectXShaderCompiler.
/// </summary>
class ShaderCompilerDX : public ShaderCompiler
{
private:
Array<char> _funcNameDefineBuffer;
void* _compiler;
void* _library;
void* _containerReflection;
public:
/// <summary>
/// Initializes a new instance of the <see cref="ShaderCompilerDX"/> class.
/// </summary>
/// <param name="profile">The profile.</param>
ShaderCompilerDX(ShaderProfile profile);
/// <summary>
/// Finalizes an instance of the <see cref="ShaderCompilerDX"/> class.
/// </summary>
~ShaderCompilerDX();
protected:
// [ShaderCompiler]
bool CompileShader(ShaderFunctionMeta& meta, WritePermutationData customDataWrite = nullptr) override;
bool OnCompileBegin() override;
};
#endif

View File

@@ -0,0 +1,9 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#pragma once
#include "../Config.h"
#define XSC_ENABLE_LANGUAGE_EXT 1
#include <Xsc/Xsc.h>
#pragma comment(lib, "XShaderCompiler.lib")

View File

@@ -0,0 +1,17 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
using Flax.Build.NativeCpp;
/// <summary>
/// OpenGL shaders compiler module.
/// </summary>
public class ShaderCompilerOGL : ShaderCompiler
{
/// <inheritdoc />
public override void Setup(BuildOptions options)
{
base.Setup(options);
options.PublicDefinitions.Add("COMPILE_WITH_OGL_SHADER_COMPILER");
}
}

View File

@@ -0,0 +1,585 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#if COMPILE_WITH_OGL_SHADER_COMPILER
#include "ShaderCompilerOGL.h"
#include "Engine/Platform/Platform.h"
#include "Engine/Threading/Threading.h"
#include "Engine/Serialization/MemoryWriteStream.h"
#include "Engine/Core/Log.h"
#include "../RenderToolsOGL.h"
#include "../ShaderAPI.h"
#include <fstream>
#include <iostream>
#include <LZ4/lz4.h>
ShaderCompilerOGL::ShaderCompilerOGL(ShaderProfile profile)
: _profile(profile)
, _context(nullptr)
, _sourceStream(0)
{
// Check for supported profiles
ASSERT(profile == ShaderProfile::GLSL_410 || profile == ShaderProfile::GLSL_440);
}
ShaderCompilerOGL::~ShaderCompilerOGL()
{
}
#define USE_DETAILED_LOG 1
class XscLog : public Xsc::Log
{
public:
std::stringstream Log;
private:
#if USE_DETAILED_LOG
static void PrintMultiLineString(std::stringstream& output, const std::string& str, const std::string& indent)
{
// Determine at which position the actual text begins (excluding the "error (X:Y) : " or the like)
auto textStartPos = str.find(" : ");
if (textStartPos != std::string::npos)
textStartPos += 3;
else
textStartPos = 0;
std::string newLineIndent(textStartPos, ' ');
size_t start = 0;
bool useNewLineIndent = false;
while (start < str.size())
{
output << indent;
if (useNewLineIndent)
output << newLineIndent;
// Print next line
auto end = str.find('\n', start);
if (end != std::string::npos)
{
output << str.substr(start, end - start);
start = end + 1;
}
else
{
output << str.substr(start);
start = end;
}
output << std::endl;
useNewLineIndent = true;
}
}
void PrintReport(std::stringstream& output, const Xsc::Report& report, const std::string& indent)
{
// Print optional context description
auto context = report.Context();
if (!context.empty())
PrintMultiLineString(output, context, indent);
// Print report message
auto msg = report.Message();
PrintMultiLineString(output, msg, indent);
// Print optional line and line-marker
if (report.HasLine())
{
const auto& line = report.Line();
const auto& marker = report.Marker();
// Print line
output << indent << line << std::endl;
// Print line marker
output << indent << marker << std::endl;
}
// Print optional hints
auto hints = report.GetHints();
if (hints.size() > 0)
{
for (auto hint : hints)
output << indent << hint << std::endl;
}
}
#endif
public:
// [Log]
void SubmitReport(const Xsc::Report& report) override
{
#if !USE_DETAILED_LOG
std::string str = report.Message();
switch (report.Type())
{
case Xsc::ReportTypes::Info:
Log << "Info: " << str << std::endl;
break;
case Xsc::ReportTypes::Warning:
Log << "Warning: " << str << std::endl;
break;
case Xsc::ReportTypes::Error:
Log << "Error: " << str << std::endl;
break;
}
#else
switch (report.Type())
{
case Xsc::ReportTypes::Info:
Log << "Info: " << std::endl;
break;
case Xsc::ReportTypes::Warning:
Log << "Warning: " << std::endl;
break;
case Xsc::ReportTypes::Error:
Log << "Error: " << std::endl;
break;
}
PrintReport(Log, report, FullIndent());
#endif
}
};
int GetUniformSize(Xsc::Reflection::DataType type)
{
switch (type)
{
//case Xsc::Reflection::DataType::Undefined:
// String types:
//case Xsc::Reflection::DataType::String:
// Scalar types
case Xsc::Reflection::DataType::Bool: return 1;
case Xsc::Reflection::DataType::Int: return 4;
case Xsc::Reflection::DataType::UInt: return 4;
case Xsc::Reflection::DataType::Half: return 2;
case Xsc::Reflection::DataType::Float: return 4;
case Xsc::Reflection::DataType::Double: return 8;
// Vector types
case Xsc::Reflection::DataType::Bool2: return 1 * 2;
case Xsc::Reflection::DataType::Bool3: return 1 * 3;
case Xsc::Reflection::DataType::Bool4: return 1 * 4;
case Xsc::Reflection::DataType::Int2: return 4 * 2;
case Xsc::Reflection::DataType::Int3: return 4 * 3;
case Xsc::Reflection::DataType::Int4: return 4 * 4;
case Xsc::Reflection::DataType::UInt2: return 4 * 2;
case Xsc::Reflection::DataType::UInt3: return 4 * 3;
case Xsc::Reflection::DataType::UInt4: return 4 * 4;
case Xsc::Reflection::DataType::Half2: return 2 * 2;
case Xsc::Reflection::DataType::Half3: return 2 * 3;
case Xsc::Reflection::DataType::Half4: return 2 * 4;
case Xsc::Reflection::DataType::Float2: return 4 * 2;
case Xsc::Reflection::DataType::Float3: return 4 * 3;
case Xsc::Reflection::DataType::Float4: return 4 * 4;
case Xsc::Reflection::DataType::Double2: return 8 * 2;
case Xsc::Reflection::DataType::Double3: return 8 * 3;
case Xsc::Reflection::DataType::Double4: return 8 * 4;
// Matrix types
case Xsc::Reflection::DataType::Bool2x2: return 1 * 2 * 2;
case Xsc::Reflection::DataType::Bool2x3: return 1 * 2 * 3;
case Xsc::Reflection::DataType::Bool2x4: return 1 * 2 * 4;
case Xsc::Reflection::DataType::Bool3x2: return 1 * 3 * 2;
case Xsc::Reflection::DataType::Bool3x3: return 1 * 3 * 4;
case Xsc::Reflection::DataType::Bool3x4: return 1 * 3 * 5;
case Xsc::Reflection::DataType::Bool4x2: return 1 * 4 * 2;
case Xsc::Reflection::DataType::Bool4x3: return 1 * 4 * 3;
case Xsc::Reflection::DataType::Bool4x4: return 1 * 4 * 4;
case Xsc::Reflection::DataType::Int2x2: return 4 * 2 * 2;
case Xsc::Reflection::DataType::Int2x3: return 4 * 2 * 3;
case Xsc::Reflection::DataType::Int2x4: return 4 * 2 * 4;
case Xsc::Reflection::DataType::Int3x2: return 4 * 3 * 2;
case Xsc::Reflection::DataType::Int3x3: return 4 * 3 * 3;
case Xsc::Reflection::DataType::Int3x4: return 4 * 3 * 4;
case Xsc::Reflection::DataType::Int4x2: return 4 * 4 * 2;
case Xsc::Reflection::DataType::Int4x3: return 4 * 4 * 3;
case Xsc::Reflection::DataType::Int4x4: return 4 * 4 * 4;
case Xsc::Reflection::DataType::UInt2x2: return 4 * 2 * 2;
case Xsc::Reflection::DataType::UInt2x3: return 4 * 2 * 3;
case Xsc::Reflection::DataType::UInt2x4: return 4 * 2 * 4;
case Xsc::Reflection::DataType::UInt3x2: return 4 * 3 * 2;
case Xsc::Reflection::DataType::UInt3x3: return 4 * 3 * 3;
case Xsc::Reflection::DataType::UInt3x4: return 4 * 3 * 4;
case Xsc::Reflection::DataType::UInt4x2: return 4 * 4 * 2;
case Xsc::Reflection::DataType::UInt4x3: return 4 * 4 * 3;
case Xsc::Reflection::DataType::UInt4x4: return 4 * 4 * 4;
case Xsc::Reflection::DataType::Half2x2: return 2 * 2 * 2;
case Xsc::Reflection::DataType::Half2x3: return 2 * 2 * 3;
case Xsc::Reflection::DataType::Half2x4: return 2 * 2 * 4;
case Xsc::Reflection::DataType::Half3x2: return 2 * 3 * 2;
case Xsc::Reflection::DataType::Half3x3: return 2 * 3 * 3;
case Xsc::Reflection::DataType::Half3x4: return 2 * 3 * 4;
case Xsc::Reflection::DataType::Half4x2: return 2 * 4 * 2;
case Xsc::Reflection::DataType::Half4x3: return 2 * 4 * 3;
case Xsc::Reflection::DataType::Half4x4: return 2 * 4 * 4;
case Xsc::Reflection::DataType::Float2x2: return 4 * 2 * 2;
case Xsc::Reflection::DataType::Float2x3: return 4 * 2 * 3;
case Xsc::Reflection::DataType::Float2x4: return 4 * 2 * 4;
case Xsc::Reflection::DataType::Float3x2: return 4 * 3 * 2;
case Xsc::Reflection::DataType::Float3x3: return 4 * 3 * 3;
case Xsc::Reflection::DataType::Float3x4: return 4 * 3 * 4;
case Xsc::Reflection::DataType::Float4x2: return 4 * 4 * 2;
case Xsc::Reflection::DataType::Float4x3: return 4 * 4 * 3;
case Xsc::Reflection::DataType::Float4x4: return 4 * 4 * 4;
case Xsc::Reflection::DataType::Double2x2: return 8 * 2 * 2;
case Xsc::Reflection::DataType::Double2x3: return 8 * 2 * 3;
case Xsc::Reflection::DataType::Double2x4: return 8 * 2 * 4;
case Xsc::Reflection::DataType::Double3x2: return 8 * 3 * 2;
case Xsc::Reflection::DataType::Double3x3: return 8 * 3 * 3;
case Xsc::Reflection::DataType::Double3x4: return 8 * 3 * 4;
case Xsc::Reflection::DataType::Double4x2: return 8 * 4 * 2;
case Xsc::Reflection::DataType::Double4x3: return 8 * 4 * 3;
case Xsc::Reflection::DataType::Double4x4: return 8 * 4 * 4;
}
return 0;
}
bool ShaderCompilerOGL::ProcessShader(Xsc::Reflection::ReflectionData& data, uint32& cbMask)
{
// Reset masks
cbMask = 0;
srMask = 0;
uaMask = 0;
// Extract constant buffers usage information
for (const auto& cb : data.constantBuffers)
{
if (cb.location < 0)
{
_context->OnError("Missing bound resource.");
return true;
}
// Set flag
cbMask |= 1 << cb.location;
// Try to add CB to the list
for (int32 b = 0; b < _constantBuffers.Count(); b++)
{
auto& cc = _constantBuffers[b];
if (cc.Slot == cb.location)
{
// Calculate the size (based on uniforms used in this buffer)
cc.Size = 0;
for (const auto& uniform : data.uniforms)
{
if (uniform.type == Xsc::Reflection::UniformType::Variable
&& uniform.uniformBlock == cb.location)
{
cc.Size += GetUniformSize((Xsc::Reflection::DataType)uniform.baseType);
}
}
cc.IsUsed = true;
break;
}
}
}
// Extract resources usage
for (const auto& sr : data.textures)
{
if (sr.location < 0)
{
_context->OnError("Missing bound resource.");
return true;
}
// Set flag
srMask |= 1 << sr.location;
}
return false;
}
bool ShaderCompilerOGL::CompileShader(ShaderFunctionMeta& meta, WritePermutationData customDataWrite)
{
// Prepare
auto output = _context->Output;
auto options = _context->Options;
auto type = meta.GetStage();
auto permutationsCount = meta.GetPermutationsCount();
// Construct shader target
switch (type)
{
case ShaderStage::Vertex: _inputDesc.shaderTarget = Xsc::ShaderTarget::VertexShader; break;
case ShaderStage::Hull: _inputDesc.shaderTarget = Xsc::ShaderTarget::TessellationControlShader; break;
case ShaderStage::Domain: _inputDesc.shaderTarget = Xsc::ShaderTarget::TessellationEvaluationShader; break;
case ShaderStage::Geometry: _inputDesc.shaderTarget = Xsc::ShaderTarget::GeometryShader; break;
case ShaderStage::Pixel: _inputDesc.shaderTarget = Xsc::ShaderTarget::FragmentShader; break;
case ShaderStage::Compute: _inputDesc.shaderTarget = Xsc::ShaderTarget::ComputeShader; break;
default: return true;
}
// [Output] Type
output->WriteByte(static_cast<byte>(type));
// [Output] Permutations count
output->WriteByte(permutationsCount);
// [Output] Shader function name
output->WriteStringAnsi(meta.Name, 11);
// Compile all shader function permutations
for (int32 permutationIndex = 0; permutationIndex < permutationsCount; permutationIndex++)
{
_macros.Clear();
// Get function permutation macros
meta.GetDefinitionsForPermutation(permutationIndex, _macros);
// Add additional define for compiled function name
GetDefineForFunction(meta, _macros);
// Add custom and global macros (global last because contain null define to indicate ending)
_macros.Add(_context->Options->Macros);
_macros.Add(_globalMacros);
// Setup the source code
// TODO: use shared _sourceStream and reduce dynamic memory allocations
std::shared_ptr<std::stringstream> input = std::shared_ptr<std::stringstream>(new std::stringstream());
for (int32 i = 0; i < _macros.Count(); i++)
{
ASSERT(_macros[i].Name && _macros[i].Definition);
*input << "#define " << _macros[i].Name << " " << _macros[i].Definition << "\n";
}
*input << options->Source;
// Setup options for this permutation
std::stringstream outputStream;// TODO: reuse it to reduce dynamic memory allocations
_inputDesc.sourceCode = input;
_inputDesc.entryPoint = meta.Name;
_outputDesc.sourceCode = &outputStream;
// Compile
Xsc::Reflection::ReflectionData reflection;
try
{
// Captures and prints to log the full AST tree
#define DEBUG_AST 0
#if DEBUG_AST
_outputDesc.options.showAST = true;
std::stringstream buffer;
std::streambuf* old = std::cout.rdbuf(buffer.rdbuf());
#endif
// Cross-compile HLSL to GLSL
XscLog log;
bool result = Xsc::CompileShader(_inputDesc, _outputDesc, &log, &reflection);
#if DEBUG_AST
String text = String(buffer.str());
LOG_STR(Info, text);
#endif
// Check compilation result
if (!result)
{
auto str = log.Log.str();
_context->OnError(str.c_str());
return true;
}
}
catch (const std::exception& e)
{
_context->OnError(e.what());
return true;
}
// Process reflection data
uint32 cbMask, uaMsrMaskask, uaMask;
if (ProcessShader(reflection, cbMask, srMaskk, uaMask))
return true;
// Get the source code (and append the null-terminator)
auto str = outputStream.str();
str += "\0";
// Prepare the shader buffer (for raw mode)
int32 shaderBufferType = SHADER_DATA_FORMAT_RAW;
uint32 shaderBufferSize = (uint32)str.length() + 1;
uint32 shaderBufferOriginalSize = shaderBufferSize;
byte* shaderBuffer = (byte*)str.c_str();
// Try to compress the source code (don't compress if memory gain is too small)
{
float maxCompressionRatio = 0.75f;
// Try use LZ4
int32 srcSize = (int32)shaderBufferSize;
int32 estSize = LZ4_compressBound(srcSize);
_dataCompressedCache.Clear();
_dataCompressedCache.EnsureCapacity(estSize);
int32 dstSize = LZ4_compress_default(str.c_str(), (char*)_dataCompressedCache.Get(), srcSize, estSize);
float ratio = (float)dstSize / srcSize;
if (dstSize == 0)
{
LOG(Warning, "Shader source data LZ4 compression failed.");
}
else if (ratio <= maxCompressionRatio)
{
// Use compressed format
shaderBufferType = SHADER_DATA_FORMAT_LZ4;
shaderBufferSize = dstSize;
shaderBuffer = _dataCompressedCache.Get();
}
}
// [Output] Write cross-compiled shader function
output->WriteInt32(shaderBufferType);
output->WriteUint32(shaderBufferOriginalSize);
output->WriteUint32(shaderBufferSize);
output->WriteBytes(shaderBuffer, shaderBufferSize);
// [Output] Shader meta
uint32 instructionCount = 0;// TODO: use AST reflection to count the shader instructions
output->WriteUint32(instructionCount);
output->WriteUint32(cbMask);
output->WriteUint32(srMask);
output->WriteUint32(uaMask);
// Custom data
if (customDataWrite && customDataWrite(output, meta, permutationIndex, _macros))
return true;
}
return false;
}
class FlaxIncludeHandler : public Xsc::IncludeHandler
{
public:
FlaxIncludeHandler()
{
}
std::unique_ptr<std::istream> Include(const std::string& includeName, bool useSearchPathsFirst) override
{
const String filename(includeName);
auto shaderApi = ShaderAPI::Instance();
ScopeLock lock(shaderApi->Locker);
const auto shader = shaderApi->GetShaderSource(filename);
if (shader == nullptr)
return nullptr;
std::unique_ptr<std::stringstream> stream = std::unique_ptr<std::stringstream>(new std::stringstream());
*stream << shader->Get();
return stream;
}
};
bool ShaderCompilerOGL::Compile(ShaderCompilationContext* context)
{
// Clear cache
_globalMacros.Clear();
_macros.Clear();
_constantBuffers.Clear();
_context = context;
// Prepare
auto options = context->Options;
auto output = context->Output;
auto meta = context->Meta;
bool use440 = _profile == ShaderProfile::GLSL_440;
int32 shadersCount = meta->GetShadersCount();
FlaxIncludeHandler includeHandler;
// Setup global shader macros
_globalMacros.EnsureCapacity(32);
_macros.EnsureCapacity(32);
_globalMacros.Add({ "OPENGL", "1" });
GetGlobalDefines(_globalMacros);
// Allocate shader source stream data (it will include compilation macros)
//uint32 macrosCountApprox = _globalMacros.Count() + options->Macros.Count() + 10;
//_sourceStream.Reset(Math::Max<uint32>(_sourceStream.GetCapacity(), sizeof(Char) * Math::RoundUpToPowerOf2<uint32>(options->SourceLength + 32 * macrosCountApprox)));
// TODO: preallocate _sourceStream to reduce dynamic allocations
// Setup cross-compiler options
{
switch (_profile)
{
case ShaderProfile::GLSL_440: _inputDesc.shaderVersion = Xsc::InputShaderVersion::HLSL5; break;
case ShaderProfile::GLSL_410: _inputDesc.shaderVersion = Xsc::InputShaderVersion::HLSL4; break;
}
_inputDesc.filename = context->TargetNameAnsi;
_inputDesc.extensions = Xsc::Extensions::LayoutAttribute;
_inputDesc.includeHandler = &includeHandler;
}
{
switch (_profile)
{
case ShaderProfile::GLSL_440: _outputDesc.shaderVersion = Xsc::OutputShaderVersion::GLSL440; break;
case ShaderProfile::GLSL_410: _outputDesc.shaderVersion = Xsc::OutputShaderVersion::GLSL410; break;
}
_outputDesc.options.optimize = !options->NoOptimize;
_outputDesc.options.separateShaders = true;
_outputDesc.options.separateSamplers = true;
_outputDesc.options.preserveComments = false;
_outputDesc.options.explicitBinding = true;
_outputDesc.formatting.writeGeneratorHeader = false;
_outputDesc.formatting.blanks = false;
_outputDesc.nameMangling.inputPrefix = "f_";
_outputDesc.nameMangling.outputPrefix = "f_";
_outputDesc.nameMangling.useAlwaysSemantics = true;
_outputDesc.nameMangling.renameBufferFields = true;
}
// Setup constant buffers cache
_constantBuffers.EnsureCapacity(meta->CB.Count(), false);
for (int32 i = 0; i < meta->CB.Count(); i++)
_constantBuffers.Add({ meta->CB[i].Slot, false, 0 });
// [Output] Write version number
output->WriteInt32(1);
// [Output] Write amount of shaders
output->WriteInt32(shadersCount);
// Compile all shaders
if (CompileShaders(context))
return true;
// [Output] Constant Buffers
{
int32 cbsCount = _constantBuffers.Count();
ASSERT(cbsCount == meta->CB.Count());
// Find maximum used slot index
byte maxCbSlot = 0;
for (int32 i = 0; i < cbsCount; i++)
{
maxCbSlot = Math::Max(maxCbSlot, _constantBuffers[i].Slot);
}
output->WriteByte(static_cast<byte>(cbsCount));
output->WriteByte(maxCbSlot);
// TODO: do we still need to serialize max cb slot?
for (int32 i = 0; i < cbsCount; i++)
{
output->WriteByte(_constantBuffers[i].Slot);
output->WriteUint32(_constantBuffers[i].Size);
}
}
return false;
}
#endif

View File

@@ -0,0 +1,50 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#pragma once
#if COMPILE_WITH_OGL_SHADER_COMPILER
#include "Engine/ShadersCompilation/ShaderCompiler.h"
#include "IncludeXShaderCompiler.h"
#include "../IncludeOpenGLHeaders.h"
#include "Engine/Serialization/MemoryWriteStream.h"
#include <sstream>
/// <summary>
/// Implementation of shaders compiler for OpenGL and OpenGL ES platforms
/// </summary>
class ShaderCompilerOGL : public ShaderCompiler
{
private:
ShaderProfile _profile;
ShaderCompilationContext* _context;
Array<ShaderMacro> _globalMacros;
Array<ShaderMacro> _macros;
Array<ShaderResourceBuffer> _constantBuffers;
Array<byte> _dataCompressedCache;
Xsc::ShaderInput _inputDesc;
Xsc::ShaderOutput _outputDesc;
std::stringstream _sourceStream;
public:
/// <summary>
/// Initializes a new instance of the <see cref="ShaderCompilerOGL"/> class.
/// </summary>
/// <param name="profile">The profile.</param>
ShaderCompilerOGL(ShaderProfile profile);
private:
bool ProcessShader(Xsc::Reflection::ReflectionData& data, uint32& cbMask, uint32& srMask, uint32& uaMask)
protected:
// [ShaderCompiler]
bool CompileShader(ShaderFunctionMeta& meta, WritePermutationData customDataWrite = nullptr) override;
bool OnCompileBegin() override;
};
#endif

View File

@@ -0,0 +1,21 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#pragma once
#include "../Config.h"
#if COMPILE_WITH_SHADER_COMPILER
#include "Engine/Utilities/TextProcessing.h"
namespace ShaderProcessing
{
typedef TextProcessing Reader;
typedef Reader::Token Token;
typedef Reader::SeparatorData Separator;
}
#endif
// Don't count ending '\0' character
#define MACRO_LENGTH(macro) (ARRAY_COUNT(macro) - 1)

View File

@@ -0,0 +1,36 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#pragma once
#include "ITokenReader.h"
#if COMPILE_WITH_SHADER_COMPILER
namespace ShaderProcessing
{
/// <summary>
/// Interface for shader functions readers like Pixel Shader readers or Constant Buffer readers
/// </summary>
class IShaderFunctionReader : public ITokenReader
{
public:
/// <summary>
/// Virtual destructor
/// </summary>
virtual ~IShaderFunctionReader()
{
}
public:
/// <summary>
/// Collects shader function reader results to the final Shader Meta
/// </summary>
/// <param name="parser">Parser object</param>
/// <param name="result">Parsing result</param>
virtual void CollectResults(IShaderParser* parser, ShaderMeta* result) = 0;
};
}
#endif

View File

@@ -0,0 +1,90 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#pragma once
#include "Config.h"
#if COMPILE_WITH_SHADER_COMPILER
namespace ShaderProcessing
{
struct ParserMacros
{
const Array<ShaderMacro>* Data;
ParserMacros(const Array<ShaderMacro>& data)
{
Data = &data;
}
Token GetValue(Token& token) const
{
for (int32 i = 0; i < Data->Count(); i++)
{
if (token == Data->At(i).Name)
{
// Use macro value
return Token(Data->At(i).Definition);
}
}
// Fallback to token
return token;
}
};
/// <summary>
/// Interface describing shader source code parser
/// </summary>
class IShaderParser
{
public:
/// <summary>
/// Virtual destructor
/// </summary>
virtual ~IShaderParser()
{
}
public:
/// <summary>
/// Gets the parser feature level of the target platform graphics backend.
/// </summary>
/// <returns>The graphics feature level</returns>
virtual FeatureLevel GetFeatureLevel() const = 0;
/// <summary>
/// Gets the parser macros.
/// </summary>
/// <returns>The macros</returns>
virtual ParserMacros GetMacros() const = 0;
/// <summary>
/// Gets value indicating that shader processing operation failed
/// </summary>
/// <returns>True if shader processing failed, otherwise false</returns>
virtual bool Failed() const = 0;
/// <summary>
/// Gets source code reader
/// </summary>
/// <returns>Source code reader</returns>
virtual Reader& GetReader() = 0;
/// <summary>
/// Event send to the parser on reading shader source code error
/// </summary>
/// <param name="message">Message to send</param>
virtual void OnError(const String& message) = 0;
/// <summary>
/// Event send to the parser on reading shader source code warning
/// </summary>
/// <param name="message">Message to send</param>
virtual void OnWarning(const String& message) = 0;
};
}
#endif

View File

@@ -0,0 +1,44 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#pragma once
#include "Config.h"
#include "IShaderParser.h"
#if COMPILE_WITH_SHADER_COMPILER
namespace ShaderProcessing
{
/// <summary>
/// Interface for objects that can read shader source code tokens
/// </summary>
class ITokenReader
{
public:
/// <summary>
/// Virtual destructor
/// </summary>
virtual ~ITokenReader()
{
}
public:
/// <summary>
/// Checks if given token can be processed by this reader
/// </summary>
/// <param name="token">Starting token to check</param>
/// <returns>True if given token is valid starting token, otherwise false</returns>
virtual bool CheckStartToken(const Token& token) = 0;
/// <summary>
/// Start processing source after reading start token
/// </summary>
/// <param name="parser">Parser object</param>
/// <param name="text">Source code reader</param>
virtual void Process(IShaderParser* parser, Reader& text) = 0;
};
}
#endif

View File

@@ -0,0 +1,74 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#pragma once
#include "Engine/Core/Collections/Array.h"
#include "ITokenReader.h"
#if COMPILE_WITH_SHADER_COMPILER
namespace ShaderProcessing
{
/// <summary>
/// Interface for objects that can have child token readers
/// </summary>
template<typename Type>
class ITokenReadersContainerBase
{
protected:
Array<Type*> _childReaders;
public:
/// <summary>
/// Virtual destructor
/// </summary>
virtual ~ITokenReadersContainerBase()
{
// Cleanup
_childReaders.ClearDelete();
}
protected:
/// <summary>
/// Try to process given token by any child reader
/// </summary>
/// <param name="token">Starting token to check</param>
/// <param name="parser">Parser object</param>
/// <returns>True if no token processing has been done, otherwise false</returns>
virtual bool ProcessChildren(const Token& token, IShaderParser* parser)
{
for (int32 i = 0; i < _childReaders.Count(); i++)
{
if (_childReaders[i]->CheckStartToken(token))
{
_childReaders[i]->Process(parser, parser->GetReader());
return false;
}
}
return true;
}
};
/// <summary>
/// Interface for objects that can have child ITokenReader objects
/// </summary>
class ITokenReadersContainer : public ITokenReadersContainerBase<ITokenReader>
{
public:
/// <summary>
/// Virtual destructor
/// </summary>
virtual ~ITokenReadersContainer()
{
// Cleanup
_childReaders.ClearDelete();
}
};
}
#endif

View File

@@ -0,0 +1,131 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#pragma once
#include "ShaderFunctionReader.h"
#if COMPILE_WITH_SHADER_COMPILER
namespace ShaderProcessing
{
/// <summary>
/// Constant Buffers reader
/// </summary>
class ConstantBufferReader : public ShaderMetaReader<ConstantBufferMeta>
{
private:
Token _endToken;
DECLARE_SHADER_META_READER_HEADER("META_CB_BEGIN", CB);
ConstantBufferReader()
: _endToken("META_CB_END")
{
}
~ConstantBufferReader()
{
}
// [ShaderMetaReader]
void OnParseBefore(IShaderParser* parser, Reader& text) override
{
Token token;
// Clear current meta
_current.Name.Clear();
// Here we read '(x)\n' where 'x' is a shader function slot
text.ReadToken(&token);
if (StringUtils::Parse(token.Start, token.Length, &_current.Slot))
{
parser->OnError(TEXT("Invalid constant buffer slot index."));
return;
}
// Read buffer name
text.ReadToken(&token);
_current.Name = token.ToString();
// Check if name is unique
for (int32 i = 0; i < _cache.Count(); i++)
{
if (_cache[i].Name == _current.Name)
{
parser->OnError(String::Format(TEXT("Duplicated constant buffer \'{0}\'. Buffer with that name already exists."), String(_current.Name)));
return;
}
}
// Read rest of the line
text.ReadLine();
}
void OnParse(IShaderParser* parser, Reader& text) override
{
Token token;
// Read function properties
bool foundEnd = false;
while (text.CanRead())
{
text.ReadToken(&token);
// Try to find the ending
if (token == _endToken)
{
foundEnd = true;
break;
}
}
// Check if end has not been found
if (!foundEnd)
{
parser->OnError(String::Format(TEXT("Missing constant buffer \'{0}\' ending."), String(_current.Name)));
return;
}
}
void OnParseAfter(IShaderParser* parser, Reader& text) override
{
// Cache buffer
_cache.Add(_current);
}
void CollectResults(IShaderParser* parser, ShaderMeta* result) override
{
// Validate constant buffer slots overlapping
for (int32 i = 0; i < _cache.Count(); i++)
{
auto& first = _cache[i];
for (int32 j = i + 1; j < _cache.Count(); j++)
{
auto& second = _cache[j];
if (first.Slot == second.Slot)
{
parser->OnError(TEXT("Constant buffers slots are overlapping."));
return;
}
}
}
// Validate amount of used constant buffers
for (int32 i = 0; i < _cache.Count(); i++)
{
auto& f = _cache[i];
if (f.Slot >= MAX_CONSTANT_BUFFER_SLOTS)
{
parser->OnError(String::Format(TEXT("Constant buffer {0} is using invalid slot {1}. Maximum supported slot is {2}."), String(f.Name), f.Slot, MAX_CONSTANT_BUFFER_SLOTS - 1));
return;
}
}
// Base
ShaderMetaReader::CollectResults(parser, result);
}
};
}
#endif

View File

@@ -0,0 +1,29 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#pragma once
#include "ShaderFunctionReader.h"
#if COMPILE_WITH_SHADER_COMPILER
namespace ShaderProcessing
{
/// <summary>
/// Compute Shaders reader
/// </summary>
class ComputeShaderFunctionReader : public ShaderFunctionReader<ComputeShaderMeta>
{
DECLARE_SHADER_META_READER_HEADER("META_CS", CS);
ComputeShaderFunctionReader()
{
_childReaders.Add(New<StripLineReader>("numthreads"));
}
~ComputeShaderFunctionReader()
{
}
};
}
#endif

View File

@@ -0,0 +1,29 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#pragma once
#include "ShaderFunctionReader.h"
#if COMPILE_WITH_SHADER_COMPILER
namespace ShaderProcessing
{
/// <summary>
/// Domain Shaders reader
/// </summary>
class DomainShaderFunctionReader : public ShaderFunctionReader<DomainShaderMeta>
{
DECLARE_SHADER_META_READER_HEADER("META_DS", DS);
DomainShaderFunctionReader()
{
_childReaders.Add(New<StripLineReader>("domain"));
}
~DomainShaderFunctionReader()
{
}
};
}
#endif

View File

@@ -0,0 +1,57 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#pragma once
#include "ShaderFunctionReader.h"
#if COMPILE_WITH_SHADER_COMPILER
namespace ShaderProcessing
{
/// <summary>
/// Geometry Shaders reader
/// </summary>
class GeometryShaderFunctionReader : public ShaderFunctionReader<GeometryShaderMeta>
{
class MaxVertexCountReader : public ITokenReader
{
private:
Token _startToken;
public:
MaxVertexCountReader()
: _startToken("maxvertexcount")
{
}
public:
// [ITokenReader]
bool CheckStartToken(const Token& token) override
{
return token == _startToken;
}
void Process(IShaderParser* parser, Reader& text) override
{
// Read line to end
text.ReadLine();
}
};
DECLARE_SHADER_META_READER_HEADER("META_GS", GS);
GeometryShaderFunctionReader()
{
_childReaders.Add(New<MaxVertexCountReader>());
}
~GeometryShaderFunctionReader()
{
}
};
}
#endif

View File

@@ -0,0 +1,104 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#pragma once
#include "ShaderFunctionReader.h"
#include "Engine/Core/Math/Math.h"
#if COMPILE_WITH_SHADER_COMPILER
namespace ShaderProcessing
{
/// <summary>
/// Hull Shaders reader
/// </summary>
class HullShaderFunctionReader : public ShaderFunctionReader<HullShaderMeta>
{
class PatchSizeReader : public ITokenReader
{
protected:
HullShaderFunctionReader* _parent;
Token _startToken;
public:
PatchSizeReader(HullShaderFunctionReader* parent)
: _parent(parent)
, _startToken("META_HS_PATCH")
{
}
public:
// [ITokenReader]
bool CheckStartToken(const Token& token) override
{
return token == _startToken;
}
void Process(IShaderParser* parser, Reader& text) override
{
Token token;
auto& current = _parent->_current;
// Input control points count
text.ReadToken(&token);
token = parser->GetMacros().GetValue(token);
int32 controlPointsCount;
if (StringUtils::Parse(token.Start, token.Length, &controlPointsCount))
{
parser->OnError(TEXT("Cannot parse Hull shader input control points count."));
return;
}
if (Math::IsNotInRange(controlPointsCount, 1, 32))
{
parser->OnError(TEXT("Invalid amount of control points. Valid range is [1-32]."));
return;
}
current.ControlPointsCount = controlPointsCount;
}
};
DECLARE_SHADER_META_READER_HEADER("META_HS", HS);
HullShaderFunctionReader()
{
_childReaders.Add(New<PatchSizeReader>(this));
_childReaders.Add(New<StripLineReader>("domain"));
_childReaders.Add(New<StripLineReader>("partitioning"));
_childReaders.Add(New<StripLineReader>("outputtopology"));
_childReaders.Add(New<StripLineReader>("maxtessfactor"));
_childReaders.Add(New<StripLineReader>("outputcontrolpoints"));
_childReaders.Add(New<StripLineReader>("patchconstantfunc"));
}
~HullShaderFunctionReader()
{
}
void OnParseBefore(IShaderParser* parser, Reader& text) override
{
// Clear current meta
_current.ControlPointsCount = 0;
// Base
ShaderFunctionReader::OnParseBefore(parser, text);
}
void OnParseAfter(IShaderParser* parser, Reader& text) override
{
// Check if errors in parsed data
if (_current.ControlPointsCount == 0)
{
parser->OnError(String::Format(TEXT("Hull Shader \'{0}\' has missing META_HS_PATCH macro that defines the amount of the input control points from the Input Assembler."), String(_current.Name)));
return;
}
// Base
ShaderFunctionReader::OnParseAfter(parser, text);
}
};
}
#endif

View File

@@ -0,0 +1,28 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#pragma once
#include "ShaderFunctionReader.h"
#if COMPILE_WITH_SHADER_COMPILER
namespace ShaderProcessing
{
/// <summary>
/// Pixel Shaders reader
/// </summary>
class PixelShaderFunctionReader : public ShaderFunctionReader<PixelShaderMeta>
{
DECLARE_SHADER_META_READER_HEADER("META_PS", PS);
PixelShaderFunctionReader()
{
}
~PixelShaderFunctionReader()
{
}
};
}
#endif

View File

@@ -0,0 +1,155 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#pragma once
#include "ShaderFunctionReader.h"
#include "ShaderProcessing.h"
#include "Engine/Graphics/Shaders/Config.h"
#if COMPILE_WITH_SHADER_COMPILER
namespace ShaderProcessing
{
/// <summary>
/// Vertex Shaders reader
/// </summary>
class VertexShaderFunctionReader : public ShaderFunctionReader<VertexShaderMeta>
{
class InputLayoutReader : public ITokenReader
{
protected:
VertexShaderFunctionReader* _parent;
Token _startToken;
public:
InputLayoutReader(VertexShaderFunctionReader* parent)
: _parent(parent)
, _startToken("META_VS_IN_ELEMENT")
{
}
public:
// [ITokenReader]
bool CheckStartToken(const Token& token) override
{
return token == _startToken;
}
void Process(IShaderParser* parser, Reader& text) override
{
VertexShaderMeta::InputElement element;
Token token;
auto& current = _parent->_current;
// Semantic type
text.ReadToken(&token);
element.Type = ParseInputType(token);
// Semantic index
text.ReadToken(&token);
if (StringUtils::Parse(token.Start, token.Length, &element.Index))
{
parser->OnError(TEXT("Cannot parse token."));
return;
}
// Element format
text.ReadToken(&token);
element.Format = ParsePixelFormat(token);
if (element.Format == PixelFormat::Unknown)
{
parser->OnError(String::Format(TEXT("Unknown input data format \'{0}\' for the Vertex Shader."), String(token.ToString())));
return;
}
// Input slot
text.ReadToken(&token);
if (StringUtils::Parse(token.Start, token.Length, &element.InputSlot))
{
parser->OnError(TEXT("Cannot parse token."));
return;
}
// Aligned byte offset
text.ReadToken(&token);
if (token == "ALIGN")
{
element.AlignedByteOffset = INPUT_LAYOUT_ELEMENT_ALIGN;
}
else if (StringUtils::Parse(token.Start, token.Length, &element.AlignedByteOffset))
{
parser->OnError(TEXT("Cannot parse token."));
return;
}
// Input slot class
text.ReadToken(&token);
if (token == "PER_VERTEX")
{
element.InputSlotClass = INPUT_LAYOUT_ELEMENT_PER_VERTEX_DATA;
}
else if (token == "PER_INSTANCE")
{
element.InputSlotClass = INPUT_LAYOUT_ELEMENT_PER_INSTANCE_DATA;
}
else
{
parser->OnError(String::Format(TEXT("Invalid input slot class type \'{0}\'."), String(token.ToString())));
return;
}
// Instance data step rate
text.ReadToken(&token);
if (StringUtils::Parse(token.Start, token.Length, &element.InstanceDataStepRate))
{
parser->OnError(TEXT("Cannot parse token."));
return;
}
// Visible state
text.ReadToken(&token);
element.VisibleFlag = token.ToString();
current.InputLayout.Add(element);
}
};
DECLARE_SHADER_META_READER_HEADER("META_VS", VS);
VertexShaderFunctionReader()
{
_childReaders.Add(New<InputLayoutReader>(this));
}
~VertexShaderFunctionReader()
{
}
void OnParseBefore(IShaderParser* parser, Reader& text) override
{
// Clear current meta
_current.InputLayout.Clear();
// Base
ShaderFunctionReader::OnParseBefore(parser, text);
}
void OnParseAfter(IShaderParser* parser, Reader& text) override
{
// Check if errors in specified input layout
if (_current.InputLayout.Count() > VERTEX_SHADER_MAX_INPUT_ELEMENTS)
{
parser->OnError(String::Format(TEXT("Vertex Shader \'{0}\' has too many input layout elements specified. Maximum allowed amount is {1}."), String(_current.Name), VERTEX_SHADER_MAX_INPUT_ELEMENTS));
return;
}
// Base
ShaderFunctionReader::OnParseAfter(parser, text);
}
};
}
#endif

View File

@@ -0,0 +1,446 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#pragma once
#include "IShaderFunctionReader.h"
#include "ShaderMeta.h"
#include "Config.h"
#if COMPILE_WITH_SHADER_COMPILER
namespace ShaderProcessing
{
/// <summary>
/// Implementation of shader meta functions reader
/// </summary>
template<typename MetaType>
class ShaderMetaReader : public IShaderFunctionReader, public ITokenReadersContainer
{
protected:
Array<MetaType> _cache;
MetaType _current;
ShaderMetaReader()
{
}
~ShaderMetaReader()
{
}
protected:
/// <summary>
/// Event called before parsing shader function
/// </summary>
/// <param name="parser">Parser object</param>
/// <param name="text">Source code reader</param>
virtual void OnParseBefore(IShaderParser* parser, Reader& text) = 0;
/// <summary>
/// Event called for parsing shader function
/// </summary>
/// <param name="parser">Parser object</param>
/// <param name="text">Source code reader</param>
virtual void OnParse(IShaderParser* parser, Reader& text)
{
Token token;
// Read function properties
while (text.CanRead())
{
text.ReadToken(&token);
// Call children
if (ProcessChildren(token, parser))
break;
}
// Token should contain function output type, now read function name
text.ReadToken(&token);
_current.Name = token.ToString();
// Check if name is unique
for (int32 i = 0; i < _cache.Count(); i++)
{
if (_cache[i].Name == _current.Name)
{
parser->OnError(String::Format(TEXT("Duplicated function \'{0}\'. Function with that name already exists."), String(_current.Name)));
return;
}
}
}
/// <summary>
/// Event called after parsing shader function
/// </summary>
/// <param name="parser">Parser object</param>
/// <param name="text">Source code reader</param>
virtual void OnParseAfter(IShaderParser* parser, Reader& text) = 0;
/// <summary>
/// Process final results and send them to the Shader Meta object
/// </summary>
/// <param name="parser">Parser object</param>
/// <param name="result">Output shader meta</param>
virtual void FlushCache(IShaderParser* parser, ShaderMeta* result) = 0;
public:
// [IShaderFunctionReader]
void Process(IShaderParser* parser, Reader& text) override
{
OnParseBefore(parser, text);
if (parser->Failed())
return;
OnParse(parser, text);
if (parser->Failed())
return;
OnParseAfter(parser, text);
}
void CollectResults(IShaderParser* parser, ShaderMeta* result) override
{
// Validate function names
for (int32 i = 0; i < _cache.Count(); i++)
{
auto& first = _cache[i];
for (int32 j = i + 1; j < _cache.Count(); j++)
{
auto& second = _cache[j];
if (first.Name == second.Name)
{
parser->OnError(TEXT("Duplicated shader function names."));
return;
}
}
}
FlushCache(parser, result);
}
};
/// <summary>
/// Implementation of shader functions reader
/// </summary>
template<typename MetaType>
class ShaderFunctionReader : public ShaderMetaReader<MetaType>
{
public:
typedef ShaderMetaReader<MetaType> ShaderMetaReaderType;
protected:
class StripLineReader : public ITokenReader
{
private:
Token _startToken;
public:
explicit StripLineReader(const char* token)
: _startToken(token)
{
}
public:
// [ITokenReader]
bool CheckStartToken(const Token& token) override
{
return token == _startToken;
}
void Process(IShaderParser* parser, Reader& text) override
{
// Read line to end
text.ReadLine();
}
};
public:
/// <summary>
/// Shader function permutations reader
/// </summary>
class PermutationReader : public ITokenReader
{
protected:
ShaderFunctionReader* _parent;
int32 _startTokenPermutationSize;
const char* PermutationTokens[SHADER_PERMUTATIONS_MAX_PARAMS_COUNT] =
{
"META_PERMUTATION_1",
"META_PERMUTATION_2",
"META_PERMUTATION_3",
"META_PERMUTATION_4",
};
static_assert(SHADER_PERMUTATIONS_MAX_PARAMS_COUNT == 4, "Invalid maximum amount of shader permutations.");
public:
/// <summary>
/// Init
/// </summary>
/// <param name="parent">Parent shader function reader object</param>
PermutationReader(ShaderFunctionReader* parent)
: _parent(parent)
, _startTokenPermutationSize(0)
{
}
public:
/// <summary>
/// Clear cache
/// </summary>
void Clear()
{
_startTokenPermutationSize = 0;
}
public:
// [ITokenReader]
bool CheckStartToken(const Token& token) override
{
for (int32 i = 0; i < ARRAY_COUNT(PermutationTokens); i++)
{
if (token == PermutationTokens[i])
{
_startTokenPermutationSize = i + 1;
return true;
}
}
return false;
}
void Process(IShaderParser* parser, Reader& text) override
{
Token token;
auto& current = _parent->_current;
// Add permutation
int32 permutationIndex = current.Permutations.Count();
auto& permutation = current.Permutations.AddOne();
// Read all parameters for the permutation
ASSERT(_startTokenPermutationSize > 0);
for (int32 paramIndex = 0; paramIndex < _startTokenPermutationSize; paramIndex++)
{
// Check for missing end
if (!text.CanRead())
{
parser->OnError(TEXT("Missing ending of shader function permutation."));
return;
}
// Read definition name
text.ReadToken(&token);
if (token.Length == 0)
{
parser->OnError(TEXT("Incorrect shader permutation. Definition name is empty."));
return;
}
StringAnsi name = token.ToString();
// Read '=' character
if (token.Separator != Separator('='))
{
if (token.Separator.IsWhiteSpace())
text.EatWhiteSpaces();
if (text.PeekChar() != '=')
{
parser->OnError(TEXT("Incorrect shader permutation. Missing \'='\' character for definition value."));
return;
}
}
// Read definition value
text.ReadToken(&token);
if (token.Length == 0)
{
parser->OnError(TEXT("Incorrect shader permutation. Definition value is empty."));
return;
}
StringAnsi value = token.ToString();
// Read ',' or ')' character (depends on parameter index)
char checkChar = (paramIndex == _startTokenPermutationSize - 1) ? ')' : ',';
if (token.Separator != Separator(checkChar))
{
parser->OnError(TEXT("Incorrect shader permutation declaration."));
return;
}
// Check if hasn't been already defined in that permutation
if (current.HasDefinition(permutationIndex, name))
{
parser->OnError(String::Format(TEXT("Incorrect shader function permutation definition. Already defined \'{0}\'."), String(name)));
return;
}
// Add entry to the meta
permutation.Entries.Add({ name, value });
}
}
};
/// <summary>
/// Shader function flag reader
/// </summary>
class FlagReader : public ITokenReader
{
protected:
ShaderFunctionReader* _parent;
Token _startToken;
public:
FlagReader(ShaderFunctionReader* parent)
: _parent(parent)
, _startToken("META_FLAG")
{
}
public:
// [ITokenReader]
bool CheckStartToken(const Token& token) override
{
return token == _startToken;
}
void Process(IShaderParser* parser, Reader& text) override
{
Token token;
auto& current = _parent->_current;
// Shader Flag type
text.ReadToken(&token);
current.Flags |= ParseShaderFlags(token);
}
};
protected:
PermutationReader* _permutationReader;
ShaderFunctionReader()
{
_childReaders.Add(_permutationReader = New<PermutationReader>(this));
_childReaders.Add(New<FlagReader>(this));
}
~ShaderFunctionReader()
{
}
protected:
// [ShaderMetaReader]
void OnParseBefore(IShaderParser* parser, Reader& text) override
{
Token token;
// Clear current meta
_current.Name.Clear();
_current.Permutations.Clear();
_current.Flags = ShaderFlags::Default;
_current.MinFeatureLevel = FeatureLevel::ES2;
_permutationReader->Clear();
// Here we read '(x, y)\n' where 'x' is a shader function 'visible' flag, and 'y' is mini feature level
text.ReadToken(&token);
token = parser->GetMacros().GetValue(token);
if (token == "true" || token == "1")
{
// Visible shader
}
else if (token == "false" || token == "0")
{
// Hidden shader
_current.Flags = ShaderFlags::Hidden;
}
else
{
parser->OnError(TEXT("Invalid shader function \'isVisible\' option value."));
return;
}
text.ReadToken(&token);
token = parser->GetMacros().GetValue(token);
struct MinFeatureLevel
{
FeatureLevel Level;
const char* Token;
};
MinFeatureLevel levels[] =
{
{ FeatureLevel::ES2, "FEATURE_LEVEL_ES2" },
{ FeatureLevel::ES3, "FEATURE_LEVEL_ES3" },
{ FeatureLevel::ES3_1, "FEATURE_LEVEL_ES3_1" },
{ FeatureLevel::SM4, "FEATURE_LEVEL_SM4" },
{ FeatureLevel::SM5, "FEATURE_LEVEL_SM5" },
};
bool missing = true;
for (int32 i = 0; i < ARRAY_COUNT(levels); i++)
{
if (token == levels[i].Token)
{
_current.MinFeatureLevel = levels[i].Level;
missing = false;
break;
}
}
if (missing)
{
parser->OnError(TEXT("Invalid shader function \'minFeatureLevel\' option value."));
return;
}
// Read rest of the line
text.ReadLine();
}
void OnParseAfter(IShaderParser* parser, Reader& text) override
{
// Validate amount of permutations
if (_current.Permutations.Count() > SHADER_PERMUTATIONS_MAX_COUNT)
{
parser->OnError(String::Format(TEXT("Function \'{0}\' uses too many permutations. Maximum allowed amount is {1}."), String(_current.Name), SHADER_PERMUTATIONS_MAX_COUNT));
return;
}
// Check if shader has no permutations
if (_current.Permutations.IsEmpty())
{
// Just add blank permutation (rest of the code will work without any hacks for empty permutations list)
_current.Permutations.Add(ShaderPermutation());
}
// Check if use this shader program
if ((_current.Flags & ShaderFlags::Hidden) == false && _current.MinFeatureLevel <= parser->GetFeatureLevel())
{
// Cache read function
_cache.Add(_current);
}
}
};
}
#define DECLARE_SHADER_META_READER_HEADER(tokenName, shaderMetaMemberCollection) public: \
bool CheckStartToken(const Token& token) override \
{ return token == tokenName; } \
void FlushCache(IShaderParser* parser, ShaderMeta* result) \
{ result->shaderMetaMemberCollection.Add(_cache); }
#endif

View File

@@ -0,0 +1,377 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#pragma once
#if COMPILE_WITH_SHADER_COMPILER
#include "Engine/Core/Config.h"
#include "Engine/Core/Collections/Array.h"
#include "Engine/Core/Math/Math.h"
#include "Engine/Graphics/PixelFormat.h"
#include "Config.h"
struct ShaderPermutationEntry
{
StringAnsi Name;
StringAnsi Value;
};
struct ShaderPermutation
{
// TODO: maybe we could use macro SHADER_PERMUTATIONS_MAX_PARAMS_COUNT and reduce amount of dynamic allocations for permutations
Array<ShaderPermutationEntry> Entries;
Array<char> DebugData;
};
/// <summary>
/// Shader function metadata
/// </summary>
class ShaderFunctionMeta
{
public:
/// <summary>
/// Virtual destructor
/// </summary>
virtual ~ShaderFunctionMeta() = default;
public:
/// <summary>
/// Function name
/// </summary>
StringAnsi Name;
/// <summary>
/// Function flags.
/// </summary>
ShaderFlags Flags;
/// <summary>
/// The minimum graphics platform feature level to support this shader.
/// </summary>
FeatureLevel MinFeatureLevel;
/// <summary>
/// All possible values for the permutations and it's values to generate different permutation of this function
/// </summary>
Array<ShaderPermutation> Permutations;
public:
/// <summary>
/// Checks if definition name has been added to the given permutation
/// </summary>
/// <param name="permutationIndex">Shader permutation index</param>
/// <param name="defineName">Name of the definition to check</param>
/// <returns>True if definition of given name has been registered for the permutations, otherwise false</returns>
bool HasDefinition(int32 permutationIndex, const StringAnsi& defineName) const
{
ASSERT(Math::IsInRange(permutationIndex, 0, Permutations.Count() - 1));
auto& permutation = Permutations[permutationIndex];
for (int32 i = 0; i < permutation.Entries.Count(); i++)
{
if (permutation.Entries[i].Name == defineName)
return true;
}
return false;
}
/// <summary>
/// Checks if definition name has been added to the permutations collection
/// </summary>
/// <param name="defineName">Name of the definition to check</param>
/// <returns>True if definition of given name has been registered for the permutations, otherwise false</returns>
bool HasDefinition(const StringAnsi& defineName) const
{
for (int32 permutationIndex = 0; permutationIndex < Permutations.Count(); permutationIndex++)
{
if (HasDefinition(permutationIndex, defineName))
return true;
}
return false;
}
/// <summary>
/// Gets all macros for given shader permutation
/// </summary>
/// <param name="permutationIndex">Shader permutation index</param>
/// <param name="macros">Output array with permutation macros</param>
void GetDefinitionsForPermutation(int32 permutationIndex, Array<ShaderMacro>& macros) const
{
ASSERT(Math::IsInRange(permutationIndex, 0, Permutations.Count() - 1));
auto& permutation = Permutations[permutationIndex];
for (int32 i = 0; i < permutation.Entries.Count(); i++)
{
auto& e = permutation.Entries[i];
macros.Add({
e.Name.Get(),
e.Value.Get()
});
}
}
public:
/// <summary>
/// Gets shader function meta stage type
/// </summary>
/// <returns>Shader Stage type</returns>
virtual ShaderStage GetStage() const = 0;
};
/// <summary>
/// Vertex shader function meta
/// </summary>
class VertexShaderMeta : public ShaderFunctionMeta
{
public:
/// <summary>
/// Input element type
/// </summary>
enum class InputType : byte
{
Invalid = 0,
POSITION = 1,
COLOR = 2,
TEXCOORD = 3,
NORMAL = 4,
TANGENT = 5,
BITANGENT = 6,
ATTRIBUTE = 7,
BLENDINDICES = 8,
BLENDWEIGHT = 9,
};
/// <summary>
/// Input element
/// </summary>
struct InputElement
{
/// <summary>
/// Semantic type
/// </summary>
InputType Type;
/// <summary>
/// Semantic index
/// </summary>
byte Index;
/// <summary>
/// Element data format
/// </summary>
PixelFormat Format;
/// <summary>
/// An integer value that identifies the input-assembler
/// </summary>
byte InputSlot;
/// <summary>
/// Optional. Offset (in bytes) between each element. Use INPUT_LAYOUT_ELEMENT_ALIGN for convenience to define the current element directly after the previous one, including any packing if necessary
/// </summary>
uint32 AlignedByteOffset;
/// <summary>
/// Identifies the input data class for a single input slot. INPUT_LAYOUT_ELEMENT_PER_VERTEX_DATA or INPUT_LAYOUT_ELEMENT_PER_INSTANCE_DATA
/// </summary>
byte InputSlotClass;
/// <summary>
/// The number of instances to draw using the same per-instance data before advancing in the buffer by one element. This value must be 0 for an element that contains per-vertex data
/// </summary>
uint32 InstanceDataStepRate;
/// <summary>
/// The visible flag expression. Allows to show/hide element from the input layout based on input macros (also from permutation macros). use empty value to skip this feature.
/// </summary>
StringAnsi VisibleFlag;
};
public:
/// <summary>
/// Input layout description
/// </summary>
Array<InputElement> InputLayout;
public:
// [ShaderFunctionMeta]
ShaderStage GetStage() const override
{
return ShaderStage::Vertex;
}
};
/// <summary>
/// Hull (or tessellation control) shader function meta
/// </summary>
class HullShaderMeta : public ShaderFunctionMeta
{
public:
/// <summary>
/// The input control points count (valid range: 1-32).
/// </summary>
int32 ControlPointsCount;
public:
// [ShaderFunctionMeta]
ShaderStage GetStage() const override
{
return ShaderStage::Hull;
}
};
/// <summary>
/// Domain (or tessellation evaluation) shader function meta
/// </summary>
class DomainShaderMeta : public ShaderFunctionMeta
{
public:
// [ShaderFunctionMeta]
ShaderStage GetStage() const override
{
return ShaderStage::Domain;
}
};
/// <summary>
/// Geometry shader function meta
/// </summary>
class GeometryShaderMeta : public ShaderFunctionMeta
{
public:
// [ShaderFunctionMeta]
ShaderStage GetStage() const override
{
return ShaderStage::Geometry;
}
};
/// <summary>
/// Pixel shader function meta
/// </summary>
class PixelShaderMeta : public ShaderFunctionMeta
{
public:
// [ShaderFunctionMeta]
ShaderStage GetStage() const override
{
return ShaderStage::Pixel;
}
};
/// <summary>
/// Compute shader function meta
/// </summary>
class ComputeShaderMeta : public ShaderFunctionMeta
{
public:
// [ShaderFunctionMeta]
ShaderStage GetStage() const override
{
return ShaderStage::Compute;
}
};
/// <summary>
/// Constant buffer meta
/// </summary>
struct ConstantBufferMeta
{
/// <summary>
/// Slot index
/// </summary>
byte Slot;
/// <summary>
/// Buffer name
/// </summary>
StringAnsi Name;
};
/// <summary>
/// Shader source metadata
/// </summary>
class ShaderMeta
{
public:
/// <summary>
/// Vertex Shaders
/// </summary>
Array<VertexShaderMeta> VS;
/// <summary>
/// Hull Shaders
/// </summary>
Array<HullShaderMeta> HS;
/// <summary>
/// Domain Shaders
/// </summary>
Array<DomainShaderMeta> DS;
/// <summary>
/// Geometry Shaders
/// </summary>
Array<GeometryShaderMeta> GS;
/// <summary>
/// Pixel Shaders
/// </summary>
Array<PixelShaderMeta> PS;
/// <summary>
/// Compute Shaders
/// </summary>
Array<ComputeShaderMeta> CS;
/// <summary>
/// Constant Buffers
/// </summary>
Array<ConstantBufferMeta> CB;
public:
/// <summary>
/// Gets amount of shaders attached (not counting permutations)
/// </summary>
/// <returns>Amount of all shader programs</returns>
uint32 GetShadersCount() const
{
return VS.Count() + HS.Count() + DS.Count() + GS.Count() + PS.Count() + CS.Count();
}
/// <summary>
/// Gets all shader functions (all types)
/// </summary>
/// <param name="functions">Output collections of functions</param>
void GetShaders(Array<const ShaderFunctionMeta*>& functions) const
{
#define PEEK_SHADERS(collection) for (int32 i = 0; i < collection.Count(); i++) functions.Add(dynamic_cast<const ShaderFunctionMeta*>(&(collection[i])));
PEEK_SHADERS(VS);
PEEK_SHADERS(HS);
PEEK_SHADERS(DS);
PEEK_SHADERS(GS);
PEEK_SHADERS(PS);
PEEK_SHADERS(CS);
#undef PEEK_SHADERS
}
};
#endif

View File

@@ -0,0 +1,161 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#include "ShaderProcessing.h"
#if COMPILE_WITH_SHADER_COMPILER
#include "Engine/Core/Log.h"
VertexShaderMeta::InputType ShaderProcessing::ParseInputType(const Token& token)
{
struct InputDesc
{
VertexShaderMeta::InputType e;
const char* s;
};
#define _PARSE_ENTRY(x) { VertexShaderMeta::InputType::x, #x }
const InputDesc formats[] =
{
_PARSE_ENTRY(Invalid),
_PARSE_ENTRY(POSITION),
_PARSE_ENTRY(COLOR),
_PARSE_ENTRY(TEXCOORD),
_PARSE_ENTRY(NORMAL),
_PARSE_ENTRY(TANGENT),
_PARSE_ENTRY(BITANGENT),
_PARSE_ENTRY(ATTRIBUTE),
_PARSE_ENTRY(BLENDINDICES),
_PARSE_ENTRY(BLENDWEIGHT),
};
#undef _PARSE_ENTRY
for (int32 i = 0; i < ARRAY_COUNT(formats); i++)
{
if (token == formats[i].s)
return formats[i].e;
}
return VertexShaderMeta::InputType::Invalid;
}
PixelFormat ShaderProcessing::ParsePixelFormat(const Token& token)
{
struct DataDesc
{
PixelFormat e;
const char* s;
};
#define _PARSE_ENTRY(x) { PixelFormat::x, #x }
const DataDesc formats[] =
{
_PARSE_ENTRY(Unknown),
_PARSE_ENTRY(R32G32B32A32_Float),
_PARSE_ENTRY(R32G32B32A32_UInt),
_PARSE_ENTRY(R32G32B32A32_SInt),
_PARSE_ENTRY(R32G32B32_Float),
_PARSE_ENTRY(R32G32B32_UInt),
_PARSE_ENTRY(R32G32B32_SInt),
_PARSE_ENTRY(R16G16B16A16_Float),
_PARSE_ENTRY(R16G16B16A16_UNorm),
_PARSE_ENTRY(R16G16B16A16_UInt),
_PARSE_ENTRY(R16G16B16A16_SNorm),
_PARSE_ENTRY(R16G16B16A16_SInt),
_PARSE_ENTRY(R32G32_Float),
_PARSE_ENTRY(R32G32_UInt),
_PARSE_ENTRY(R32G32_SInt),
_PARSE_ENTRY(R10G10B10A2_UNorm),
_PARSE_ENTRY(R10G10B10A2_UInt),
_PARSE_ENTRY(R11G11B10_Float),
_PARSE_ENTRY(R8G8B8A8_UNorm),
_PARSE_ENTRY(R8G8B8A8_UNorm_sRGB),
_PARSE_ENTRY(R8G8B8A8_UInt),
_PARSE_ENTRY(R8G8B8A8_SNorm),
_PARSE_ENTRY(R8G8B8A8_SInt),
_PARSE_ENTRY(R16G16_Float),
_PARSE_ENTRY(R16G16_UNorm),
_PARSE_ENTRY(R16G16_UInt),
_PARSE_ENTRY(R16G16_SNorm),
_PARSE_ENTRY(R16G16_SInt),
_PARSE_ENTRY(R32_Float),
_PARSE_ENTRY(R32_UInt),
_PARSE_ENTRY(R32_SInt),
_PARSE_ENTRY(R8G8_UNorm),
_PARSE_ENTRY(R8G8_UInt),
_PARSE_ENTRY(R8G8_SNorm),
_PARSE_ENTRY(R8G8_SInt),
_PARSE_ENTRY(R16_Float),
_PARSE_ENTRY(R16_UNorm),
_PARSE_ENTRY(R16_UInt),
_PARSE_ENTRY(R16_SNorm),
_PARSE_ENTRY(R16_SInt),
_PARSE_ENTRY(R8_UNorm),
_PARSE_ENTRY(R8_UInt),
_PARSE_ENTRY(R8_SNorm),
_PARSE_ENTRY(R8_SInt),
_PARSE_ENTRY(A8_UNorm),
_PARSE_ENTRY(R1_UNorm),
_PARSE_ENTRY(R8G8_B8G8_UNorm),
_PARSE_ENTRY(G8R8_G8B8_UNorm),
_PARSE_ENTRY(BC1_UNorm),
_PARSE_ENTRY(BC1_UNorm_sRGB),
_PARSE_ENTRY(BC2_UNorm),
_PARSE_ENTRY(BC2_UNorm_sRGB),
_PARSE_ENTRY(BC3_UNorm),
_PARSE_ENTRY(BC3_UNorm_sRGB),
_PARSE_ENTRY(BC4_UNorm),
_PARSE_ENTRY(BC4_SNorm),
_PARSE_ENTRY(BC5_UNorm),
_PARSE_ENTRY(BC5_SNorm),
_PARSE_ENTRY(B5G6R5_UNorm),
_PARSE_ENTRY(B5G5R5A1_UNorm),
_PARSE_ENTRY(B8G8R8A8_UNorm),
_PARSE_ENTRY(B8G8R8X8_UNorm),
_PARSE_ENTRY(B8G8R8A8_UNorm_sRGB),
_PARSE_ENTRY(B8G8R8X8_UNorm_sRGB),
_PARSE_ENTRY(BC6H_Uf16),
_PARSE_ENTRY(BC6H_Sf16),
_PARSE_ENTRY(BC7_UNorm),
_PARSE_ENTRY(BC7_UNorm_sRGB),
};
#undef _PARSE_ENTRY
for (int32 i = 0; i < ARRAY_COUNT(formats); i++)
{
if (token.EqualsIgnoreCase(formats[i].s))
return formats[i].e;
}
return PixelFormat::Unknown;
}
ShaderFlags ShaderProcessing::ParseShaderFlags(const Token& token)
{
struct DataDesc
{
ShaderFlags e;
const char* s;
};
#define _PARSE_ENTRY(x) { ShaderFlags::x, #x }
const DataDesc data[] =
{
_PARSE_ENTRY(Default),
_PARSE_ENTRY(Hidden),
_PARSE_ENTRY(NoFastMath),
_PARSE_ENTRY(VertexToGeometryShader),
};
static_assert(ARRAY_COUNT(data) == 4, "Invalid amount of Shader Flag data entries.");
#undef _PARSE_ENTRY
for (int32 i = 0; i < ARRAY_COUNT(data); i++)
{
if (token.EqualsIgnoreCase(data[i].s))
return data[i].e;
}
return ShaderFlags::Default;
}
#endif

View File

@@ -0,0 +1,146 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#include "ShaderProcessing.h"
#if COMPILE_WITH_SHADER_COMPILER
#include "Engine/Core/Collections/Array.h"
#include "Engine/Utilities/TextProcessing.h"
#include "Engine/Core/Log.h"
#include "ShaderFunctionReader.CB.h"
#include "ShaderFunctionReader.VS.h"
#include "ShaderFunctionReader.HS.h"
#include "ShaderFunctionReader.DS.h"
#include "ShaderFunctionReader.GS.h"
#include "ShaderFunctionReader.PS.h"
#include "ShaderFunctionReader.CS.h"
#include "Config.h"
ShaderProcessing::Parser::Parser(const String& targetName, const char* source, int32 sourceLength, ParserMacros macros, FeatureLevel featureLevel)
: failed(false)
, targetName(targetName)
, text(source, sourceLength)
, _macros(macros)
, _featureLevel(featureLevel)
{
}
ShaderProcessing::Parser::~Parser()
{
}
bool ShaderProcessing::Parser::Process(const String& targetName, const char* source, int32 sourceLength, ParserMacros macros, FeatureLevel featureLevel, ShaderMeta* result)
{
Parser parser(targetName, source, sourceLength, macros, featureLevel);
parser.Process(result);
return parser.Failed();
}
void ShaderProcessing::Parser::Process(ShaderMeta* result)
{
init();
failed = process();
if (!failed)
failed = collectResults(result);
}
void ShaderProcessing::Parser::init()
{
// Init text processing tokens for hlsl language
text.Setup_HLSL();
// Init shader functions readers
_childReaders.Add(New<ConstantBufferReader>());
_childReaders.Add(New<VertexShaderFunctionReader>());
_childReaders.Add(New<HullShaderFunctionReader>());
_childReaders.Add(New<DomainShaderFunctionReader>());
_childReaders.Add(New<GeometryShaderFunctionReader>());
_childReaders.Add(New<PixelShaderFunctionReader>());
_childReaders.Add(New<ComputeShaderFunctionReader>());
}
bool ShaderProcessing::Parser::process()
{
const Token defineToken("#define");
const Separator singleLineCommentSeparator('/', '/');
const Separator multiLineCommentSeparator('/', '*');
// TODO: split parsing into two phrases: comments preprocessing and parsing
// Read whole source code
Token token;
while (text.CanRead())
{
text.ReadToken(&token);
// Single line comment
if (token.Separator == singleLineCommentSeparator)
{
// Read whole line
text.ReadLine();
}
// Multi line comment
else if (token.Separator == multiLineCommentSeparator)
{
// Read tokens until end sequence
char prev = ' ';
char c;
while (text.CanRead())
{
c = text.ReadChar();
if (prev == '*' && c == '/')
{
break;
}
prev = c;
}
// Check if comment is valid (has end before file end)
if (!text.CanRead())
{
OnWarning(TEXT("Missing multiline comment ending"));
}
}
// Preprocessor definition
else if (token == defineToken)
{
// Skip
text.ReadLine();
}
else
{
// Call children
ProcessChildren(token, this);
}
}
return false;
}
bool ShaderProcessing::Parser::collectResults(ShaderMeta* result)
{
// Collect results from all the readers
for (int32 i = 0; i < _childReaders.Count(); i++)
_childReaders[i]->CollectResults(this, result);
return false;
}
void ShaderProcessing::Parser::OnError(const String& message)
{
// Set flag
failed = true;
// Send event
LOG(Error, "Processing shader '{0}' error at line {1}. {2}", targetName, text.GetLine(), message);
}
void ShaderProcessing::Parser::OnWarning(const String& message)
{
// Send event
LOG(Warning, "Processing shader '{0}' warning at line {1}. {2}", targetName, text.GetLine(), message);
}
#endif

View File

@@ -0,0 +1,93 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#pragma once
#include "ShaderMeta.h"
#if COMPILE_WITH_SHADER_COMPILER
#include "Engine/Core/Types/String.h"
#include "IShaderFunctionReader.h"
#include "ITokenReadersContainer.h"
namespace ShaderProcessing
{
extern VertexShaderMeta::InputType ParseInputType(const Token& token);
extern PixelFormat ParsePixelFormat(const Token& token);
extern ShaderFlags ParseShaderFlags(const Token& token);
/// <summary>
/// Shader files meta data processing tool
/// </summary>
class Parser : public IShaderParser, public ITokenReadersContainerBase<IShaderFunctionReader>
{
private:
bool failed;
String targetName;
Reader text;
ParserMacros _macros;
FeatureLevel _featureLevel;
private:
Parser(const String& targetName, const char* source, int32 sourceLength, ParserMacros macros, FeatureLevel featureLevel);
~Parser();
public:
/// <summary>
/// Process shader source code and generate metadata
/// </summary>
/// <param name="targetName">Calling object name (used for warnings/errors logging)</param>
/// <param name="source">ANSI source code</param>
/// <param name="sourceLength">Amount of characters in the source code</param>
/// <param name="macros">The input macros.</param>
/// <param name="featureLevel">The target feature level.</param>
/// <param name="result">Output result with metadata</param>
/// <returns>True if cannot process the file (too many errors), otherwise false</returns>
static bool Process(const String& targetName, const char* source, int32 sourceLength, ParserMacros macros, FeatureLevel featureLevel, ShaderMeta* result);
public:
/// <summary>
/// Process shader source code and generate metadata
/// </summary>
/// <param name="result">Output result with metadata</param>
void Process(ShaderMeta* result);
private:
void init();
bool process();
bool collectResults(ShaderMeta* result);
public:
// [IShaderParser]
FeatureLevel GetFeatureLevel() const override
{
return _featureLevel;
}
bool Failed() const override
{
return failed;
}
Reader& GetReader() override
{
return text;
}
ParserMacros GetMacros() const override
{
return _macros;
}
void OnError(const String& message) override;
void OnWarning(const String& message) override;
};
}
#endif

View File

@@ -0,0 +1,37 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#if COMPILE_WITH_SHADER_COMPILER
#include "ShaderCompilationContext.h"
#include "Engine/Core/Log.h"
#include "Parser/ShaderMeta.h"
#include "Engine/Graphics/Config.h"
#include "Config.h"
void ShaderCompilationContext::OnError(const char* message)
{
LOG(Error, "Failed to compile '{0}'. {1}", Options->TargetName, String(message));
}
void ShaderCompilationContext::OnCollectDebugInfo(ShaderFunctionMeta& meta, int32 permutationIndex, const char* data, const int32 dataLength)
{
#ifdef GPU_USE_SHADERS_DEBUG_LAYER
// Cache data
meta.Permutations[permutationIndex].DebugData.Set(data, dataLength);
#endif
}
ShaderCompilationContext::ShaderCompilationContext(const ShaderCompilationOptions* options, ShaderMeta* meta)
: Options(options)
, Meta(meta)
, Output(options->Output)
{
// Convert target name to ANSI text (with limited length)
const int32 ansiNameLen = Math::Min<int32>(ARRAY_COUNT(TargetNameAnsi) - 1, options->TargetName.Length());
StringUtils::ConvertUTF162ANSI(*options->TargetName, TargetNameAnsi, ansiNameLen);
TargetNameAnsi[ansiNameLen] = 0;
}
#endif

View File

@@ -0,0 +1,77 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#pragma once
#if COMPILE_WITH_SHADER_COMPILER
#include "Config.h"
#include "Engine/Core/Collections/HashSet.h"
class ShaderMeta;
class ShaderFunctionMeta;
class MemoryWriteStream;
/// <summary>
/// Shader compilation context container
/// </summary>
class ShaderCompilationContext
{
public:
/// <summary>
/// The compilation options.
/// </summary>
const ShaderCompilationOptions* Options;
/// <summary>
/// The shader metadata container.
/// </summary>
ShaderMeta* Meta;
public:
/// <summary>
/// Output stream to write compiled shader cache to.
/// </summary>
MemoryWriteStream* Output;
/// <summary>
/// All source files included by this file (absolute paths). Generated during shader compilation.
/// </summary>
HashSet<String> Includes;
public:
/// <summary>
/// Name of the target object (in ASCII)
/// </summary>
char TargetNameAnsi[64];
public:
/// <summary>
/// Event called on compilation error
/// </summary>
/// <param name="message">Error message</param>
void OnError(const char* message);
/// <summary>
/// Event called on compilation debug data collecting
/// </summary>
/// <param name="meta">Target function meta</param>
/// <param name="permutationIndex">Permutation index</param>
/// <param name="data">Data pointer</param>
/// <param name="dataLength">Data size in bytes</param>
void OnCollectDebugInfo(ShaderFunctionMeta& meta, int32 permutationIndex, const char* data, const int32 dataLength);
public:
/// <summary>
/// Init
/// </summary>
/// <param name="options">Options</param>
/// <param name="meta">Metadata</param>
ShaderCompilationContext(const ShaderCompilationOptions* options, ShaderMeta* meta);
};
#endif

View File

@@ -0,0 +1,471 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#if COMPILE_WITH_SHADER_COMPILER
#include "ShaderCompiler.h"
#include "Engine/Core/Log.h"
#include "Engine/Core/Collections/Dictionary.h"
#include "Engine/Platform/File.h"
#include "Engine/Platform/FileSystem.h"
#include "Engine/Graphics/RenderTools.h"
#include "Engine/Threading/Threading.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Serialization/MemoryWriteStream.h"
#include "Engine/Utilities/StringConverter.h"
#if USE_EDITOR
#include "Editor/Editor.h"
#endif
namespace IncludedFiles
{
struct File
{
String Path;
DateTime LastEditTime;
Array<byte> Source;
};
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)
{
// Clear cache
_globalMacros.Clear();
_macros.Clear();
_constantBuffers.Clear();
_globalMacros.EnsureCapacity(32);
_macros.EnsureCapacity(32);
_context = context;
// Prepare
auto output = context->Output;
auto meta = context->Meta;
const int32 shadersCount = meta->GetShadersCount();
if (OnCompileBegin())
return true;
_globalMacros.Add({ nullptr, nullptr });
// Setup constant buffers cache
_constantBuffers.EnsureCapacity(meta->CB.Count(), false);
for (int32 i = 0; i < meta->CB.Count(); i++)
_constantBuffers.Add({ meta->CB[i].Slot, false, 0 });
// [Output] Version number
output->WriteInt32(7);
// [Output] Additional data start
const int32 additionalDataStartPos = output->GetPosition();
output->WriteInt32(-1);
// [Output] Amount of shaders
output->WriteInt32(shadersCount);
// Compile all shaders
if (CompileShaders())
return true;
// [Output] Constant Buffers
{
const int32 cbsCount = _constantBuffers.Count();
ASSERT(cbsCount == meta->CB.Count());
// Find maximum used slot index
byte maxCbSlot = 0;
for (int32 i = 0; i < cbsCount; i++)
{
maxCbSlot = Math::Max(maxCbSlot, _constantBuffers[i].Slot);
}
output->WriteByte(static_cast<byte>(cbsCount));
output->WriteByte(maxCbSlot);
// TODO: do we still need to serialize max cb slot?
for (int32 i = 0; i < cbsCount; i++)
{
output->WriteByte(_constantBuffers[i].Slot);
output->WriteUint32(_constantBuffers[i].Size);
}
}
// Additional Data Start
*(int32*)(output->GetHandle() + additionalDataStartPos) = output->GetPosition();
// [Output] Includes
output->WriteInt32(context->Includes.Count());
for (auto& include : context->Includes)
{
output->WriteString(include.Item, 11);
const auto date = FileSystem::GetFileLastEditTime(include.Item);
output->Write(&date);
}
return false;
}
bool ShaderCompiler::GetIncludedFileSource(ShaderCompilationContext* context, const char* sourceFile, const char* includedFile, const char*& source, int32& sourceLength)
{
PROFILE_CPU();
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--)
{
if (StringUtils::Compare(includedFile + i, "./", 2) == 0)
{
includedFile = includedFile + i;
break;
}
}
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)
{
// Remove old one
if (result)
{
Delete(result);
IncludedFiles::Files.Remove(path);
}
// Load file
result = New<IncludedFiles::File>();
result->Path = path;
result->LastEditTime = FileSystem::GetFileLastEditTime(path);
if (File::ReadAllBytes(result->Path, result->Source))
{
LOG(Error, "Failed to load shader source file '{0}' included in '{1}' (path: '{2}')", String(includedFile), String(sourceFile), path);
Delete(result);
return true;
}
IncludedFiles::Files.Add(path, result);
}
context->Includes.Add(path);
// Copy to output
source = (const char*)result->Source.Get();
sourceLength = result->Source.Count() - 1;
return false;
}
void ShaderCompiler::DisposeIncludedFilesCache()
{
ScopeLock lock(IncludedFiles::Locker);
IncludedFiles::Files.ClearDelete();
}
bool ShaderCompiler::CompileShaders()
{
auto meta = _context->Meta;
// Generate vertex shaders cache
for (int32 i = 0; i < meta->VS.Count(); i++)
{
auto& shader = meta->VS[i];
ASSERT(shader.GetStage() == ShaderStage::Vertex && (shader.Flags & ShaderFlags::Hidden) == 0);
if (CompileShader(shader, &WriteCustomDataVS))
{
LOG(Error, "Failed to compile \'{0}\'", String(shader.Name));
return true;
}
}
// Generate hull shaders cache
for (int32 i = 0; i < meta->HS.Count(); i++)
{
auto& shader = meta->HS[i];
ASSERT(shader.GetStage() == ShaderStage::Hull && (shader.Flags & ShaderFlags::Hidden) == 0);
if (CompileShader(shader, &WriteCustomDataHS))
{
LOG(Error, "Failed to compile \'{0}\'", String(shader.Name));
return true;
}
}
// Generate domain shaders cache
for (int32 i = 0; i < meta->DS.Count(); i++)
{
auto& shader = meta->DS[i];
ASSERT(shader.GetStage() == ShaderStage::Domain && (shader.Flags & ShaderFlags::Hidden) == 0);
if (CompileShader(shader))
{
LOG(Error, "Failed to compile \'{0}\'", String(shader.Name));
return true;
}
}
// Generate geometry shaders cache
for (int32 i = 0; i < meta->GS.Count(); i++)
{
auto& shader = meta->GS[i];
ASSERT(shader.GetStage() == ShaderStage::Geometry && (shader.Flags & ShaderFlags::Hidden) == 0);
if (CompileShader(shader))
{
LOG(Error, "Failed to compile \'{0}\'", String(shader.Name));
return true;
}
}
// Generate pixel shaders cache
for (int32 i = 0; i < meta->PS.Count(); i++)
{
auto& shader = meta->PS[i];
ASSERT(shader.GetStage() == ShaderStage::Pixel && (shader.Flags & ShaderFlags::Hidden) == 0);
if (CompileShader(shader))
{
LOG(Error, "Failed to compile \'{0}\'", String(shader.Name));
return true;
}
}
// Generate compute shaders cache
for (int32 i = 0; i < meta->CS.Count(); i++)
{
auto& shader = meta->CS[i];
ASSERT(shader.GetStage() == ShaderStage::Compute && (shader.Flags & ShaderFlags::Hidden) == 0);
if (CompileShader(shader))
{
LOG(Error, "Failed to compile \'{0}\'", String(shader.Name));
return true;
}
}
return false;
}
bool ShaderCompiler::OnCompileBegin()
{
// Setup global macros
static const char* Numbers[] =
{
// @formatter:off
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10",
// @formatter:on
};
const auto profile = GetProfile();
const auto featureLevel = RenderTools::GetFeatureLevel(profile);
_globalMacros.Add({ "FEATURE_LEVEL", Numbers[(int32)featureLevel] });
return false;
}
bool ShaderCompiler::OnCompileEnd()
{
return false;
}
bool ShaderCompiler::WriteShaderFunctionBegin(ShaderCompilationContext* context, ShaderFunctionMeta& meta)
{
auto output = context->Output;
// [Output] Type
output->WriteByte(static_cast<byte>(meta.GetStage()));
// [Output] Permutations count
output->WriteByte(meta.Permutations.Count());
// [Output] Shader function name
output->WriteStringAnsi(meta.Name, 11);
// [Output] Shader flags
output->WriteUint32((uint32)meta.Flags);
return false;
}
bool ShaderCompiler::WriteShaderFunctionPermutation(ShaderCompilationContext* context, ShaderFunctionMeta& meta, int32 permutationIndex, const ShaderBindings& bindings, const void* cache, int32 cacheSize)
{
auto output = context->Output;
// [Output] Write compiled shader cache
output->WriteUint32(cacheSize);
output->WriteBytes(cache, cacheSize);
// [Output] Shader bindings meta
output->Write(&bindings);
return false;
}
bool ShaderCompiler::WriteShaderFunctionEnd(ShaderCompilationContext* context, ShaderFunctionMeta& meta)
{
return false;
}
bool ShaderCompiler::WriteCustomDataVS(ShaderCompilationContext* context, ShaderFunctionMeta& meta, int32 permutationIndex, const Array<ShaderMacro>& macros)
{
auto output = context->Output;
auto& metaVS = *(VertexShaderMeta*)&meta;
auto& layout = metaVS.InputLayout;
// Get visible entries (based on `visible` flag switch)
int32 layoutSize = 0;
bool layoutVisible[VERTEX_SHADER_MAX_INPUT_ELEMENTS];
for (int32 i = 0; i < layout.Count(); i++)
{
auto& element = layout[i];
layoutVisible[i] = false;
// Parse using all input macros
StringAnsi value = element.VisibleFlag;
for (int32 j = 0; j < macros.Count() - 1; j++)
{
if (macros[j].Name == value)
{
value = macros[j].Definition;
break;
}
}
if (value == "true" || value == "1")
{
// Visible
layoutSize++;
layoutVisible[i] = true;
}
else if (value == "false" || value == "0")
{
// Hidden
}
else
{
LOG(Error, "Invalid vertex shader layout element \'visible\' option value.");
return true;
}
}
// [Output] Input Layout
output->WriteByte(static_cast<byte>(layoutSize));
for (int32 a = 0; a < layout.Count(); a++)
{
auto& element = layout[a];
if (!layoutVisible[a])
continue;
// TODO: serialize whole struct?
output->WriteByte(static_cast<byte>(element.Type));
output->WriteByte(element.Index);
output->WriteByte(static_cast<byte>(element.Format));
output->WriteByte(element.InputSlot);
output->WriteUint32(element.AlignedByteOffset);
output->WriteByte(element.InputSlotClass);
output->WriteUint32(element.InstanceDataStepRate);
}
return false;
}
bool ShaderCompiler::WriteCustomDataHS(ShaderCompilationContext* context, ShaderFunctionMeta& meta, int32 permutationIndex, const Array<ShaderMacro>& macros)
{
auto output = context->Output;
auto& metaHS = *(HullShaderMeta*)&meta;
// [Output] Control Points Count
output->WriteInt32(metaHS.ControlPointsCount);
return false;
}
void ShaderCompiler::GetDefineForFunction(ShaderFunctionMeta& meta, Array<ShaderMacro>& macros)
{
auto& functionName = meta.Name;
const int32 functionNameLength = static_cast<int32>(functionName.Length());
_funcNameDefineBuffer.Clear();
_funcNameDefineBuffer.EnsureCapacity(functionNameLength + 2);
_funcNameDefineBuffer.Add('_');
_funcNameDefineBuffer.Add(functionName.Get(), functionNameLength);
_funcNameDefineBuffer.Add('\0');
macros.Add({ _funcNameDefineBuffer.Get(), "1" });
}
#endif

View File

@@ -0,0 +1,106 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#pragma once
#if COMPILE_WITH_SHADER_COMPILER
#include "ShaderCompilationContext.h"
#include "Parser/ShaderMeta.h"
#include "Engine/Graphics/Shaders/GPUShaderProgram.h"
/// <summary>
/// Base class for the objects that can compile shaders source code.
/// </summary>
class ShaderCompiler
{
public:
struct ShaderResourceBuffer
{
byte Slot;
bool IsUsed;
uint32 Size;
};
private:
Array<char> _funcNameDefineBuffer;
protected:
ShaderProfile _profile;
ShaderCompilationContext* _context = nullptr;
Array<ShaderMacro> _globalMacros;
Array<ShaderMacro> _macros;
Array<ShaderResourceBuffer> _constantBuffers;
public:
/// <summary>
/// Initializes a new instance of the <see cref="ShaderCompiler"/> class.
/// </summary>
/// <param name="profile">The profile.</param>
ShaderCompiler(ShaderProfile profile)
: _profile(profile)
{
}
/// <summary>
/// Finalizes an instance of the <see cref="ShaderCompiler"/> class.
/// </summary>
virtual ~ShaderCompiler() = default;
public:
/// <summary>
/// Gets shader profile supported by this compiler.
/// </summary>
/// <returns>The shader profile.</returns>
FORCE_INLINE ShaderProfile GetProfile() const
{
return _profile;
}
/// <summary>
/// Performs the shader compilation.
/// </summary>
/// <param name="context">The compilation context.</param>
/// <returns>True if failed, otherwise false.</returns>
bool Compile(ShaderCompilationContext* context);
/// <summary>
/// Gets the included file source code. Handles system includes and absolute includes. Method is thread-safe.
/// </summary>
/// <param name="context">The compilation context.</param>
/// <param name="sourceFile">The source file that is being compiled.</param>
/// <param name="includedFile">The included file name (absolute or relative).</param>
/// <param name="source">The output source code of the file (null-terminated), null if failed to load.</param>
/// <param name="sourceLength">The output source code length of the file (characters count), 0 if failed to load.</param>
/// <returns>True if failed, otherwise false.</returns>
static bool GetIncludedFileSource(ShaderCompilationContext* context, const char* sourceFile, const char* includedFile, const char*& source, int32& sourceLength);
/// <summary>
/// Clears the cache used by the shader includes.
/// </summary>
static void DisposeIncludedFilesCache();
protected:
typedef bool (*WritePermutationData)(ShaderCompilationContext*, ShaderFunctionMeta&, int32, const Array<ShaderMacro>&);
virtual bool CompileShader(ShaderFunctionMeta& meta, WritePermutationData customDataWrite = nullptr) = 0;
bool CompileShaders();
virtual bool OnCompileBegin();
virtual bool OnCompileEnd();
static bool WriteShaderFunctionBegin(ShaderCompilationContext* context, ShaderFunctionMeta& meta);
static bool WriteShaderFunctionPermutation(ShaderCompilationContext* context, ShaderFunctionMeta& meta, int32 permutationIndex, const ShaderBindings& bindings, const void* cache, int32 cacheSize);
static bool WriteShaderFunctionEnd(ShaderCompilationContext* context, ShaderFunctionMeta& meta);
static bool WriteCustomDataVS(ShaderCompilationContext* context, ShaderFunctionMeta& meta, int32 permutationIndex, const Array<ShaderMacro>& macros);
static bool WriteCustomDataHS(ShaderCompilationContext* context, ShaderFunctionMeta& meta, int32 permutationIndex, const Array<ShaderMacro>& macros);
void GetDefineForFunction(ShaderFunctionMeta& meta, Array<ShaderMacro>& macros);
};
#endif

View File

@@ -0,0 +1,83 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#pragma once
#include "ShaderCompilationContext.h"
#include "Engine/Graphics/Config.h"
#if GPU_USE_SHADERS_DEBUG_LAYER && COMPILE_WITH_SHADER_COMPILER
#include "Engine/Platform/File.h"
#include "Engine/Platform/FileSystem.h"
#include "Engine/Serialization/MemoryWriteStream.h"
#include "Engine/Core/Types/StringBuilder.h"
#include "Engine/Engine/Globals.h"
#include "Parser/ShaderMeta.h"
/// <summary>
/// Tool class used to export debug information about shaders
/// </summary>
class ShaderDebugDataExporter
{
public:
/// <summary>
/// Exports compilation results info
/// </summary>
/// <param name="context">Compilation context</param>
/// <returns>True if failed, otherwise false</returns>
static bool Export(ShaderCompilationContext* context)
{
#if USE_EDITOR
static String ShadersDebugInfoFolder = Globals::ProjectCacheFolder / TEXT("Shaders/Debug");
#else
static String ShadersDebugInfoFolder = Globals::ProductLocalFolder / TEXT("Shaders/Debug");
#endif
// Create output folder
if (!FileSystem::DirectoryExists(ShadersDebugInfoFolder))
{
if (FileSystem::CreateDirectory(ShadersDebugInfoFolder))
return true;
}
// Prepare
auto options = context->Options;
String outputFilePath = ShadersDebugInfoFolder / String::Format(TEXT("ShaderDebug_{0}_{1}.txt"), options->TargetName, options->TargetID);
auto shadersCount = context->Meta->GetShadersCount();
StringBuilder info(1024 * shadersCount);
Array<const ShaderFunctionMeta*> functions(shadersCount);
context->Meta->GetShaders(functions);
// Generate output info
info.AppendFormat(TEXT("Target shader: {0} : {1}\nProfile: {2}\nCache size: {3} bytes\n"),
options->TargetName,
options->TargetID,
::ToString(options->Profile),
options->Output->GetPosition());
for (int32 i = 0; i < functions.Count(); i++)
{
const auto f = functions[i];
auto stageName = ::ToString(f->GetStage());
for (int32 permutationIndex = 0; permutationIndex < f->Permutations.Count(); permutationIndex++)
{
info.Append(TEXT("\n*********************************************************************\n"));
info.AppendFormat(TEXT("{0} Shader: {1}, Permutation: {2}\n"), stageName, String(f->Name), permutationIndex + 1);
info.Append(f->Permutations[permutationIndex].DebugData.Get());
}
}
#if PLATFORM_WINDOWS
// Change line endings on Windows platform
StringBuilder winNewLineFix = info;
WindowsFileSystem::ConvertLineEndingsToDos(StringView(*winNewLineFix, winNewLineFix.Length()), info.GetCharArray());
#endif
// Write to file
return File::WriteAllText(outputFilePath, info, Encoding::Unicode);
}
};
#endif

View File

@@ -0,0 +1,65 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Flax.Build;
using Flax.Build.NativeCpp;
using Flax.Build.Platforms;
/// <summary>
/// Shader compiler module base class.
/// </summary>
public abstract class ShaderCompiler : EngineModule
{
/// <inheritdoc />
public override void Setup(BuildOptions options)
{
base.Setup(options);
options.PrivateDefinitions.Add("COMPILE_WITH_SHADER_COMPILER");
}
/// <inheritdoc />
public override void GetFilesToDeploy(List<string> files)
{
}
}
/// <summary>
/// Shaders compilation module.
/// </summary>
public class ShadersCompilation : EngineModule
{
/// <inheritdoc />
public override void Setup(BuildOptions options)
{
base.Setup(options);
options.PublicDefinitions.Add("COMPILE_WITH_SHADER_COMPILER");
options.SourcePaths.Clear();
options.SourceFiles.AddRange(Directory.GetFiles(FolderPath, "*.*", SearchOption.TopDirectoryOnly));
options.SourcePaths.Add(Path.Combine(FolderPath, "Parser"));
switch (options.Platform.Target)
{
case TargetPlatform.Windows:
options.PrivateDependencies.Add("ShaderCompilerD3D");
if (WindowsPlatformBase.GetSDKs().Any(x => x.Key != WindowsPlatformSDK.v8_1))
options.PrivateDependencies.Add("ShaderCompilerDX");
//options.PrivateDependencies.Add("ShaderCompilerOGL");
options.PrivateDependencies.Add("ShaderCompilerVulkan");
break;
default: throw new InvalidPlatformException(options.Platform.Target);
}
if (Sdk.HasValid("PS4Sdk"))
options.PrivateDependencies.Add("ShaderCompilerPS4");
}
/// <inheritdoc />
public override void GetFilesToDeploy(List<string> files)
{
}
}

View File

@@ -0,0 +1,461 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#if COMPILE_WITH_SHADER_COMPILER
#include "ShadersCompilation.h"
#include "ShaderCompilationContext.h"
#include "ShaderDebugDataExporter.h"
#include "Engine/Engine/EngineService.h"
#include "Engine/Threading/Threading.h"
#include "Engine/Core/Log.h"
#include "Engine/Core/Math/Math.h"
#include "Parser/ShaderProcessing.h"
#include "Engine/Graphics/RenderTools.h"
#include "Engine/Graphics/Shaders/GPUShader.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Serialization/MemoryReadStream.h"
#include "Engine/Content/Asset.h"
#if USE_EDITOR
#define COMPILE_WITH_ASSETS_IMPORTER 1 // Hack to use shaders importing in this module
#include "Engine/ContentImporters/AssetsImportingManager.h"
#include "Engine/Platform/FileSystemWatcher.h"
#include "Engine/Platform/FileSystem.h"
#include "Editor/Editor.h"
#endif
#if COMPILE_WITH_D3D_SHADER_COMPILER
#include "DirectX/ShaderCompilerD3D.h"
#endif
#if COMPILE_WITH_DX_SHADER_COMPILER
#include "DirectX/ShaderCompilerDX.h"
#endif
#if COMPILE_WITH_OGL_SHADER_COMPILER
#include "OpenGL/ShaderCompilerOGL.h"
#endif
#if COMPILE_WITH_VK_SHADER_COMPILER
#include "Vulkan/ShaderCompilerVulkan.h"
#endif
#if COMPILE_WITH_PS4_SHADER_COMPILER
#include "Platforms/PS4/Engine/ShaderCompilerPS4/ShaderCompilerPS4.h"
#endif
namespace ShadersCompilationImpl
{
CriticalSection Locker;
Array<ShaderCompiler*> Compilers;
Array<ShaderCompiler*> ReadyCompilers;
}
using namespace ShadersCompilationImpl;
class ShadersCompilationService : public EngineService
{
public:
ShadersCompilationService()
: EngineService(TEXT("Shaders Compilation Service"), -100)
{
}
bool Init() override;
void Dispose() override;
};
ShadersCompilationService ShadersCompilationServiceInstance;
bool ShadersCompilation::Compile(const ShaderCompilationOptions& options)
{
PROFILE_CPU_NAMED("Shader.Compile");
// Validate input options
if (options.TargetName.IsEmpty() || !options.TargetID.IsValid())
{
LOG(Warning, "Unknown target object.");
return true;
}
if (options.Output == nullptr)
{
LOG(Warning, "Missing output.");
return true;
}
if (options.Profile == ShaderProfile::Unknown)
{
LOG(Warning, "Unknown shader profile.");
return true;
}
if (options.Source == nullptr || options.SourceLength < 1)
{
LOG(Warning, "Missing source code.");
return true;
}
const DateTime startTime = DateTime::NowUTC();
const FeatureLevel featureLevel = RenderTools::GetFeatureLevel(options.Profile);
// Process shader source to collect metadata
ShaderMeta meta;
if (ShaderProcessing::Parser::Process(options.TargetName, options.Source, options.SourceLength, options.Macros, featureLevel, &meta))
{
LOG(Warning, "Failed to parse source code.");
return true;
}
const int32 shadersCount = meta.GetShadersCount();
if (shadersCount == 0)
{
LOG(Warning, "Shader has no valid functions.");
}
// Perform actual compilation
bool result;
{
ShaderCompilationContext context(&options, &meta);
// Request shaders compiler
auto compiler = RequestCompiler(options.Profile);
if (compiler == nullptr)
{
LOG(Error, "Shader compiler request failed.");
return true;
}
ASSERT(compiler->GetProfile() == options.Profile);
// Call compilation process
result = compiler->Compile(&context);
// Dismiss compiler
FreeCompiler(compiler);
#if GPU_USE_SHADERS_DEBUG_LAYER
// Export debug data
ShaderDebugDataExporter::Export(&context);
#endif
}
// Print info if succeed
if (result == false)
{
const DateTime endTime = DateTime::NowUTC();
LOG(Info, "Shader compilation '{0}' succeed in {1} ms (profile: {2})", options.TargetName, Math::CeilToInt(static_cast<float>((endTime - startTime).GetTotalMilliseconds())), ::ToString(options.Profile));
}
return result;
}
ShaderCompiler* ShadersCompilation::CreateCompiler(ShaderProfile profile)
{
ShaderCompiler* result = nullptr;
switch (profile)
{
#if COMPILE_WITH_D3D_SHADER_COMPILER
// Direct 3D
case ShaderProfile::DirectX_SM4:
case ShaderProfile::DirectX_SM5:
result = New<ShaderCompilerD3D>(profile);
break;
#endif
#if COMPILE_WITH_DX_SHADER_COMPILER
case ShaderProfile::DirectX_SM6:
result = New<ShaderCompilerDX>(profile);
break;
#endif
#if COMPILE_WITH_OGL_SHADER_COMPILER
// OpenGL and OpenGL ES
case ShaderProfile::GLSL_410:
case ShaderProfile::GLSL_440:
result = New<ShaderCompilerOGL>(profile);
break;
#endif
#if COMPILE_WITH_VK_SHADER_COMPILER
// Vulkan
case ShaderProfile::Vulkan_SM5:
result = New<ShaderCompilerVulkan>(profile);
break;
#endif
#if COMPILE_WITH_PS4_SHADER_COMPILER
// PS4
case ShaderProfile::PS4:
result = New<ShaderCompilerPS4>();
break;
#endif
default:
break;
}
ASSERT_LOW_LAYER(result == nullptr || result->GetProfile() == profile);
return result;
}
ShaderCompiler* ShadersCompilation::RequestCompiler(ShaderProfile profile)
{
ShaderCompiler* compiler;
ScopeLock lock(Locker);
// Try to find ready compiler
for (int32 i = 0; i < ReadyCompilers.Count(); i++)
{
compiler = ReadyCompilers[i];
if (compiler->GetProfile() == profile)
{
// Use it
ReadyCompilers.RemoveAt(i);
return compiler;
}
}
// Create new compiler for a target profile
compiler = CreateCompiler(profile);
if (compiler == nullptr)
{
LOG(Error, "Cannot create Shader Compiler for profile {0}", ::ToString(profile));
return nullptr;
}
// Register new compiler
Compilers.Add(compiler);
return compiler;
}
void ShadersCompilation::FreeCompiler(ShaderCompiler* compiler)
{
ScopeLock lock(Locker);
ASSERT(compiler && ReadyCompilers.Contains(compiler) == false);
// Check if service has been disposed (this compiler is not in the compilers list)
if (Compilers.Contains(compiler) == false)
{
// Delete it manually
Delete(compiler);
}
else
{
// Make compiler free again
ReadyCompilers.Add(compiler);
}
}
namespace
{
CriticalSection ShaderIncludesMapLocker;
Dictionary<String, Array<Asset*>> ShaderIncludesMap;
Dictionary<String, FileSystemWatcher*> ShaderIncludesWatcher;
void OnShaderIncludesWatcherEvent(const String& path, FileSystemAction action)
{
if (action == FileSystemAction::Delete)
return;
Array<Asset*> toReload;
{
ScopeLock lock(ShaderIncludesMapLocker);
auto file = ShaderIncludesMap.Find(path);
if (file == ShaderIncludesMap.End())
return;
toReload = file->Value;
}
LOG(Info, "Shader include \'{0}\' has been modified.", path);
// Wait a little so app that was editing the file (e.g. Visual Studio, Notepad++) has enough time to flush whole file change
Platform::Sleep(100);
// Reload shaders using this include
for (Asset* asset : toReload)
{
asset->Reload();
}
}
}
void ShadersCompilation::RegisterForShaderReloads(Asset* asset, const String& includedPath)
{
ScopeLock lock(ShaderIncludesMapLocker);
// Add to collection
const bool alreadyAdded = ShaderIncludesMap.ContainsKey(includedPath);
auto& file = ShaderIncludesMap[includedPath];
ASSERT_LOW_LAYER(!file.Contains(asset));
file.Add(asset);
if (!alreadyAdded)
{
// Create a directory watcher to track the included file changes
const String directory = StringUtils::GetDirectoryName(includedPath);
if (!ShaderIncludesWatcher.ContainsKey(directory))
{
auto watcher = New<FileSystemWatcher>(directory, false);
watcher->OnEvent.Bind<OnShaderIncludesWatcherEvent>();
ShaderIncludesWatcher.Add(directory, watcher);
}
}
}
void ShadersCompilation::UnregisterForShaderReloads(Asset* asset)
{
ScopeLock lock(ShaderIncludesMapLocker);
// Remove asset reference
for (auto& file : ShaderIncludesMap)
{
file.Value.Remove(asset);
}
}
void ShadersCompilation::ExtractShaderIncludes(byte* shaderCache, int32 shaderCacheLength, Array<String>& includes)
{
MemoryReadStream stream(shaderCache, shaderCacheLength);
// Read cache format version
int32 version;
stream.ReadInt32(&version);
if (version != GPU_SHADER_CACHE_VERSION)
{
return;
}
// Read the location of additional data that contains list of included source files
int32 additionalDataStart;
stream.ReadInt32(&additionalDataStart);
stream.SetPosition(additionalDataStart);
// Read all includes
int32 includesCount;
stream.ReadInt32(&includesCount);
includes.Clear();
for (int32 i = 0; i < includesCount; i++)
{
String& include = includes.AddOne();
stream.ReadString(&include, 11);
DateTime lastEditTime;
stream.Read(&lastEditTime);
}
}
#if USE_EDITOR
namespace
{
Array<FileSystemWatcher*> ShadersSourcesWatchers;
// Tries to generate a stable and unique ID for the given shader name.
// Used in order to keep the same shader IDs and reduce version control issues with binary diff on ID.
Guid GetShaderAssetId(const String& name)
{
Guid result;
result.A = name.Length() * 100;
result.B = GetHash(name);
result.C = name.HasChars() ? name[0] : 0;
result.D = name.HasChars() ? name[name.Length() - 1] : 0;
return result;
}
void OnWatcherShadersEvent(const String& path, FileSystemAction action)
{
if (action == FileSystemAction::Delete || !path.EndsWith(TEXT(".shader")))
return;
LOG(Info, "Shader \'{0}\' has been modified.", path);
// Wait a little so app that was editing the file (e.g. Visual Studio, Notepad++) has enough time to flush whole file change
Platform::Sleep(100);
// Perform hot reload
const int32 srcSubDirStart = path.FindLast(TEXT("/Source/Shaders"));
if (srcSubDirStart == -1)
return;
String projectFolderPath = path.Substring(0, srcSubDirStart);
FileSystem::NormalizePath(projectFolderPath);
const String shadersAssetsPath = projectFolderPath / TEXT("/Content/Shaders");
const String shadersSourcePath = projectFolderPath / TEXT("/Source/Shaders");
const String localPath = FileSystem::ConvertAbsolutePathToRelative(shadersSourcePath, path);
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);
}
void RegisterShaderWatchers(const ProjectInfo* project, HashSet<const ProjectInfo*>& projects)
{
if (projects.Contains(project))
return;
projects.Add(project);
// Check if project uses shaders sources
const String shadersSourcePath = project->ProjectFolderPath / TEXT("/Source/Shaders");
if (FileSystem::DirectoryExists(shadersSourcePath))
{
// Track engine shaders editing
auto sourceWatcher = New<FileSystemWatcher>(shadersSourcePath, true);
sourceWatcher->OnEvent.Bind<OnWatcherShadersEvent>();
ShadersSourcesWatchers.Add(sourceWatcher);
// Reimport modified or import added shaders
Array<String> files(64);
const String shadersAssetsPath = project->ProjectFolderPath / TEXT("/Content/Shaders");
FileSystem::DirectoryGetFiles(files, shadersSourcePath, TEXT("*.shader"), DirectorySearchOption::AllDirectories);
for (int32 i = 0; i < files.Count(); i++)
{
const String& path = files[i];
const String localPath = FileSystem::ConvertAbsolutePathToRelative(shadersSourcePath, path);
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);
}
}
// Initialize referenced projects
for (const auto& reference : project->References)
{
if (reference.Project)
RegisterShaderWatchers(reference.Project, projects);
}
}
}
#endif
bool ShadersCompilationService::Init()
{
#if USE_EDITOR
// Initialize automatic shaders importing and reloading for all loaded projects (game, engine, plugins)
HashSet<const ProjectInfo*> projects;
RegisterShaderWatchers(Editor::Project, projects);
#endif
return false;
}
void ShadersCompilationService::Dispose()
{
#if USE_EDITOR
ShadersSourcesWatchers.ClearDelete();
#endif
Locker.Lock();
// Check if any compilation is running
if (ReadyCompilers.Count() != Compilers.Count())
{
LOG(Error, "Cannot dispose Shaders Compilation Service. One or more compilers are still in use.");
}
// Cleanup all compilers (delete only those which are not in use)
ReadyCompilers.ClearDelete();
Compilers.Clear();
Locker.Unlock();
// Cleanup shader includes
ShaderCompiler::DisposeIncludedFilesCache();
// Clear includes scanning
ShaderIncludesMapLocker.Lock();
ShaderIncludesMap.Clear();
ShaderIncludesWatcher.ClearDelete();
ShaderIncludesMapLocker.Unlock();
}
#endif

View File

@@ -0,0 +1,53 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#pragma once
#if COMPILE_WITH_SHADER_COMPILER
#include "ShaderCompiler.h"
class Asset;
/// <summary>
/// Shaders compilation service allows to compile shader source code for a desire platform. Supports multi-threading.
/// </summary>
class FLAXENGINE_API ShadersCompilation
{
public:
/// <summary>
/// Compiles the shader.
/// </summary>
/// <param name="options">Compilation options</param>
/// <returns>True if failed, otherwise false</returns>
static bool Compile(const ShaderCompilationOptions& options);
/// <summary>
/// Registers shader asset for the automated reloads on source includes changes.
/// </summary
/// <param name="asset">The asset.</param>
/// <param name="includedPath">The included file path.</param>
static void RegisterForShaderReloads(Asset* asset, const String& includedPath);
/// <summary>
/// Unregisters shader asset from the automated reloads on source includes changes.
/// </summary>
/// <param name="asset">The asset.</param>
static void UnregisterForShaderReloads(Asset* asset);
/// <summary>
/// Reads the included shader files stored in the shader cache data.
/// </summary>
/// <param name="shaderCache">The shader cache data.</param>
/// <param name="shaderCacheLength">The shader cache data length (in bytes).</param>
/// <param name="includes">The output included.</param>
static void ExtractShaderIncludes(byte* shaderCache, int32 shaderCacheLength, Array<String>& includes);
private:
static ShaderCompiler* CreateCompiler(ShaderProfile profile);
static ShaderCompiler* RequestCompiler(ShaderProfile profile);
static void FreeCompiler(ShaderCompiler* compiler);
};
#endif

View File

@@ -0,0 +1,20 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
using Flax.Build.NativeCpp;
/// <summary>
/// Vulkan shaders compiler module.
/// </summary>
public class ShaderCompilerVulkan : ShaderCompiler
{
/// <inheritdoc />
public override void Setup(BuildOptions options)
{
base.Setup(options);
options.PublicDefinitions.Add("COMPILE_WITH_VK_SHADER_COMPILER");
options.PrivateDependencies.Add("glslang");
options.PrivateDependencies.Add("spirv-tools");
}
}

View File

@@ -0,0 +1,814 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#if COMPILE_WITH_VK_SHADER_COMPILER
#include "ShaderCompilerVulkan.h"
#include "Engine/Core/Log.h"
#include "Engine/Platform/Platform.h"
#include "Engine/Threading/Threading.h"
#include "Engine/Serialization/MemoryWriteStream.h"
#include "Engine/GraphicsDevice/Vulkan/Types.h"
// Use glslang for HLSL to SPIR-V translation
// Source: https://github.com/KhronosGroup/glslang
// License: modified BSD
#define NV_EXTENSIONS 1
#define AMD_EXTENSIONS 1
#define ENABLE_HLSL 1
#define ENABLE_OPT 1
#include <ThirdParty/glslang/Public/ShaderLang.h>
#include <ThirdParty/glslang/MachineIndependent/iomapper.h>
#include <ThirdParty/glslang/SPIRV/GlslangToSpv.h>
#include <ThirdParty/spirv-tools/libspirv.hpp>
#define PRINT_UNIFORMS 0
#define PRINT_DESCRIPTORS 0
namespace
{
CriticalSection CompileShaderVulkanLocker;
int32 CompileShaderVulkanInstances = 0;
}
class Includer : public glslang::TShader::Includer
{
private:
ShaderCompilationContext* _context;
IncludeResult* include(const char* headerName, const char* includerName, int depth) const
{
const char* source;
int32 sourceLength;
if (ShaderCompiler::GetIncludedFileSource(_context, includerName, headerName, source, sourceLength))
return nullptr;
return New<IncludeResult>(headerName, source, sourceLength, nullptr);
}
public:
Includer(ShaderCompilationContext* context)
{
_context = context;
}
public:
// [glslang::TShader::Include]
IncludeResult* includeLocal(const char* headerName, const char* includerName, size_t inclusionDepth) override
{
return include(headerName, includerName, (int)inclusionDepth);
}
IncludeResult* includeSystem(const char* headerName, const char* includerName, size_t inclusionDepth) override
{
return include(headerName, includerName, (int)inclusionDepth);
}
void releaseInclude(IncludeResult* result) override
{
if (result)
Delete(result);
}
};
ShaderCompilerVulkan::ShaderCompilerVulkan(ShaderProfile profile)
: ShaderCompiler(profile)
{
ScopeLock lock(CompileShaderVulkanLocker);
if (CompileShaderVulkanInstances == 0)
{
glslang::InitializeProcess();
const auto ver = glslang::GetVersion();
LOG(Info, "Using glslang {0}.{1}.{2} compiler (SPIR-V version: {3})", ver.major, ver.minor, ver.patch, String(spvSoftwareVersionString()));
}
CompileShaderVulkanInstances++;
}
ShaderCompilerVulkan::~ShaderCompilerVulkan()
{
ScopeLock lock(CompileShaderVulkanLocker);
CompileShaderVulkanInstances--;
if (CompileShaderVulkanInstances == 0)
{
glslang::FinalizeProcess();
}
}
// @formatter:off
const TBuiltInResource DefaultTBuiltInResource =
{
/* .MaxLights = */ 32,
/* .MaxClipPlanes = */ 6,
/* .MaxTextureUnits = */ 32,
/* .MaxTextureCoords = */ 32,
/* .MaxVertexAttribs = */ 64,
/* .MaxVertexUniformComponents = */ 4096,
/* .MaxVaryingFloats = */ 64,
/* .MaxVertexTextureImageUnits = */ 32,
/* .MaxCombinedTextureImageUnits = */ 80,
/* .MaxTextureImageUnits = */ 32,
/* .MaxFragmentUniformComponents = */ 4096,
/* .MaxDrawBuffers = */ 32,
/* .MaxVertexUniformVectors = */ 128,
/* .MaxVaryingVectors = */ 8,
/* .MaxFragmentUniformVectors = */ 16,
/* .MaxVertexOutputVectors = */ 16,
/* .MaxFragmentInputVectors = */ 15,
/* .MinProgramTexelOffset = */ -8,
/* .MaxProgramTexelOffset = */ 7,
/* .MaxClipDistances = */ 8,
/* .MaxComputeWorkGroupCountX = */ 65535,
/* .MaxComputeWorkGroupCountY = */ 65535,
/* .MaxComputeWorkGroupCountZ = */ 65535,
/* .MaxComputeWorkGroupSizeX = */ 1024,
/* .MaxComputeWorkGroupSizeY = */ 1024,
/* .MaxComputeWorkGroupSizeZ = */ 64,
/* .MaxComputeUniformComponents = */ 1024,
/* .MaxComputeTextureImageUnits = */ 16,
/* .MaxComputeImageUniforms = */ 8,
/* .MaxComputeAtomicCounters = */ 8,
/* .MaxComputeAtomicCounterBuffers = */ 1,
/* .MaxVaryingComponents = */ 60,
/* .MaxVertexOutputComponents = */ 64,
/* .MaxGeometryInputComponents = */ 64,
/* .MaxGeometryOutputComponents = */ 128,
/* .MaxFragmentInputComponents = */ 128,
/* .MaxImageUnits = */ 8,
/* .MaxCombinedImageUnitsAndFragmentOutputs = */ 8,
/* .MaxCombinedShaderOutputResources = */ 8,
/* .MaxImageSamples = */ 0,
/* .MaxVertexImageUniforms = */ 0,
/* .MaxTessControlImageUniforms = */ 0,
/* .MaxTessEvaluationImageUniforms = */ 0,
/* .MaxGeometryImageUniforms = */ 0,
/* .MaxFragmentImageUniforms = */ 8,
/* .MaxCombinedImageUniforms = */ 8,
/* .MaxGeometryTextureImageUnits = */ 16,
/* .MaxGeometryOutputVertices = */ 256,
/* .MaxGeometryTotalOutputComponents = */ 1024,
/* .MaxGeometryUniformComponents = */ 1024,
/* .MaxGeometryVaryingComponents = */ 64,
/* .MaxTessControlInputComponents = */ 128,
/* .MaxTessControlOutputComponents = */ 128,
/* .MaxTessControlTextureImageUnits = */ 16,
/* .MaxTessControlUniformComponents = */ 1024,
/* .MaxTessControlTotalOutputComponents = */ 4096,
/* .MaxTessEvaluationInputComponents = */ 128,
/* .MaxTessEvaluationOutputComponents = */ 128,
/* .MaxTessEvaluationTextureImageUnits = */ 16,
/* .MaxTessEvaluationUniformComponents = */ 1024,
/* .MaxTessPatchComponents = */ 120,
/* .MaxPatchVertices = */ 32,
/* .MaxTessGenLevel = */ 64,
/* .MaxViewports = */ 16,
/* .MaxVertexAtomicCounters = */ 0,
/* .MaxTessControlAtomicCounters = */ 0,
/* .MaxTessEvaluationAtomicCounters = */ 0,
/* .MaxGeometryAtomicCounters = */ 0,
/* .MaxFragmentAtomicCounters = */ 8,
/* .MaxCombinedAtomicCounters = */ 8,
/* .MaxAtomicCounterBindings = */ 1,
/* .MaxVertexAtomicCounterBuffers = */ 0,
/* .MaxTessControlAtomicCounterBuffers = */ 0,
/* .MaxTessEvaluationAtomicCounterBuffers = */ 0,
/* .MaxGeometryAtomicCounterBuffers = */ 0,
/* .MaxFragmentAtomicCounterBuffers = */ 1,
/* .MaxCombinedAtomicCounterBuffers = */ 1,
/* .MaxAtomicCounterBufferSize = */ 16384,
/* .MaxTransformFeedbackBuffers = */ 4,
/* .MaxTransformFeedbackInterleavedComponents = */ 64,
/* .MaxCullDistances = */ 8,
/* .MaxCombinedClipAndCullDistances = */ 8,
/* .MaxSamples = */ 4,
/* .maxMeshOutputVerticesNV = */ 256,
/* .maxMeshOutputPrimitivesNV = */ 512,
/* .maxMeshWorkGroupSizeX_NV = */ 32,
/* .maxMeshWorkGroupSizeY_NV = */ 1,
/* .maxMeshWorkGroupSizeZ_NV = */ 1,
/* .maxTaskWorkGroupSizeX_NV = */ 32,
/* .maxTaskWorkGroupSizeY_NV = */ 1,
/* .maxTaskWorkGroupSizeZ_NV = */ 1,
/* .maxMeshViewCountNV = */ 4,
/* .maxDualSourceDrawBuffersEXT = */ 1,
/* .limits = */ {
/* .nonInductiveForLoops = */ 1,
/* .whileLoops = */ 1,
/* .doWhileLoops = */ 1,
/* .generalUniformIndexing = */ 1,
/* .generalAttributeMatrixVectorIndexing = */ 1,
/* .generalVaryingIndexing = */ 1,
/* .generalSamplerIndexing = */ 1,
/* .generalVariableIndexing = */ 1,
/* .generalConstantMatrixVectorIndexing = */ 1,
}
};
// @formatter:on
struct Descriptor
{
int32 Slot;
int32 Binding;
int32 Size;
SpirvShaderResourceBindingType BindingType;
VkDescriptorType DescriptorType;
SpirvShaderResourceType ResourceType;
std::string Name;
};
SpirvShaderResourceType GetTextureType(const glslang::TSampler& sampler)
{
switch (sampler.dim)
{
case glslang::Esd1D:
return sampler.isArrayed() ? SpirvShaderResourceType::Texture1DArray : SpirvShaderResourceType::Texture1D;
case glslang::Esd2D:
return sampler.isArrayed() ? SpirvShaderResourceType::Texture2DArray : SpirvShaderResourceType::Texture2D;
case glslang::Esd3D:
return SpirvShaderResourceType::Texture3D;
case glslang::EsdCube:
return SpirvShaderResourceType::TextureCube;
default:
CRASH;
return SpirvShaderResourceType::Unknown;
}
}
class DescriptorsCollector
{
public:
int32 Images;
int32 Buffers;
int32 DescriptorsCount;
Descriptor Descriptors[SpirvShaderDescriptorInfo::MaxDescriptors];
public:
DescriptorsCollector()
{
Images = 0;
Buffers = 0;
DescriptorsCount = 0;
}
public:
Descriptor* Add(glslang::TVarEntryInfo& ent)
{
const glslang::TType& type = ent.symbol->getType();
const char* name = ent.symbol->getName().c_str();
auto& qualifier = type.getQualifier();
if (DescriptorsCount == SpirvShaderDescriptorInfo::MaxDescriptors)
{
// Prevent too many descriptors
LOG(Warning, "Too many descriptors in use.");
return nullptr;
}
// Guess the descriptor type based on reflection information
VkDescriptorType descriptorType = VK_DESCRIPTOR_TYPE_MAX_ENUM;
SpirvShaderResourceType resourceType = SpirvShaderResourceType::Unknown;
SpirvShaderResourceBindingType resourceBindingType = SpirvShaderResourceBindingType::INVALID;
if (type.getBasicType() == glslang::EbtSampler)
{
if (!qualifier.hasBinding())
{
// Each resource must have binding specified (from HLSL shaders that do it explicitly)
LOG(Warning, "Found an uniform \'{0}\' without a binding qualifier. Each uniform must have an explicitly defined binding number.", String(name));
return nullptr;
}
if (type.getSampler().isCombined())
{
// Texture + Sampler combined is not supported
LOG(Warning, "Combined sampler \'{0}\' from glsl language is not supported.", String(name));
return nullptr;
}
if (type.getSampler().isPureSampler())
{
// Sampler
descriptorType = VK_DESCRIPTOR_TYPE_SAMPLER;
resourceType = SpirvShaderResourceType::Sampler;
resourceBindingType = SpirvShaderResourceBindingType::SAMPLER;
}
else if (type.getSampler().dim == glslang::EsdBuffer)
{
// Buffer SRV
descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER;
resourceType = SpirvShaderResourceType::Buffer;
resourceBindingType = SpirvShaderResourceBindingType::SRV;
}
else if (type.isTexture())
{
// Texture SRV
descriptorType = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE;
resourceType = GetTextureType(type.getSampler());
resourceBindingType = SpirvShaderResourceBindingType::SRV;
}
else if (type.isImage())
{
if (type.getSampler().dim == glslang::EsdBuffer)
{
// Buffer UAV
descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
resourceType = SpirvShaderResourceType::Buffer;
}
else
{
// Texture UAV
descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
resourceType = GetTextureType(type.getSampler());
}
resourceBindingType = SpirvShaderResourceBindingType::UAV;
}
}
else if (qualifier.storage == glslang::EvqUniform)
{
if (type.getBasicType() != glslang::EbtBlock)
{
// Skip uniforms that are not contained inside structures
LOG(Warning, "Invalid uniform \'{1} {0}\'. Shader uniforms that are not constant buffer blocks are not supported.", String(name), String(type.getBasicTypeString().c_str()));
return nullptr;
}
// CB
descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
resourceType = SpirvShaderResourceType::ConstantBuffer;
name = type.getTypeName().c_str();
resourceBindingType = SpirvShaderResourceBindingType::CB;
}
else if (qualifier.storage == glslang::EvqBuffer)
{
if (qualifier.isReadOnly())
{
// Buffer SRV
descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
resourceType = SpirvShaderResourceType::Buffer;
resourceBindingType = SpirvShaderResourceBindingType::SRV;
}
else
{
// Buffer UAV
descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
resourceType = SpirvShaderResourceType::Buffer;
resourceBindingType = SpirvShaderResourceBindingType::UAV;
}
}
// Get the output info about shader uniforms usage
switch (descriptorType)
{
case VK_DESCRIPTOR_TYPE_SAMPLER:
case VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER:
case VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE:
case VK_DESCRIPTOR_TYPE_STORAGE_IMAGE:
Images++;
break;
case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER:
case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC:
case VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER:
case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER:
case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC:
case VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER:
Buffers++;
break;
default:
LOG(Warning, "Invalid descriptor type {0} for symbol {1}.", (int32)descriptorType, String(name));
return nullptr;
}
const auto index = DescriptorsCount++;
auto& descriptor = Descriptors[index];
descriptor.Binding = index;
descriptor.Slot = qualifier.layoutBinding;
descriptor.Size = -1;
descriptor.BindingType = resourceBindingType;
descriptor.DescriptorType = descriptorType;
descriptor.ResourceType = resourceType;
descriptor.Name = name;
return &descriptor;
}
};
class MyIoMapResolver : public glslang::TDefaultIoResolverBase
{
private:
int32 _set;
DescriptorsCollector* _collector;
public:
MyIoMapResolver(int32 set, DescriptorsCollector* collector, const glslang::TIntermediate& intermediate)
: TDefaultIoResolverBase(intermediate)
, _set(set)
, _collector(collector)
{
}
public:
// [glslang::TDefaultIoResolverBase]
bool validateBinding(EShLanguage stage, glslang::TVarEntryInfo& ent) override
{
return true;
}
glslang::TResourceType getResourceType(const glslang::TType& type) override
{
if (isUavType(type))
return glslang::EResUav;
if (isSrvType(type))
return glslang::EResTexture;
if (isSamplerType(type))
return glslang::EResSampler;
if (isUboType(type))
return glslang::EResUbo;
return glslang::EResCount;
}
int resolveBinding(EShLanguage stage, glslang::TVarEntryInfo& ent) override
{
// Skip unused things
if (!ent.live)
return -1;
// Add resource
const auto descriptor = _collector->Add(ent);
if (descriptor)
return ent.newBinding = reserveSlot(_set, descriptor->Binding);
return ent.newBinding;
}
int resolveSet(EShLanguage stage, glslang::TVarEntryInfo& ent) override
{
// Skip unused things
if (!ent.live)
return -1;
// Use different slot per-stage
return ent.newSet = _set;
}
};
bool ShaderCompilerVulkan::CompileShader(ShaderFunctionMeta& meta, WritePermutationData customDataWrite)
{
// TODO: test without locking
ScopeLock lock(CompileShaderVulkanLocker);
Includer includer(_context);
// Prepare
if (WriteShaderFunctionBegin(_context, meta))
return true;
auto options = _context->Options;
auto type = meta.GetStage();
// Prepare
EShLanguage lang = EShLanguage::EShLangCount;
switch (type)
{
case ShaderStage::Vertex:
lang = EShLanguage::EShLangVertex;
break;
case ShaderStage::Hull:
lang = EShLanguage::EShLangTessControl;
break;
case ShaderStage::Domain:
lang = EShLanguage::EShLangTessEvaluation;
break;
case ShaderStage::Geometry:
lang = EShLanguage::EShLangGeometry;
break;
case ShaderStage::Pixel:
lang = EShLanguage::EShLangFragment;
break;
case ShaderStage::Compute:
lang = EShLanguage::EShLangCompute;
break;
default:
LOG(Error, "Unknown shader type.");
return true;
}
EShMessages messages = (EShMessages)(EShMsgReadHlsl | EShMsgSpvRules | EShMsgVulkanRules);
// Compile all shader function permutations
for (int32 permutationIndex = 0; permutationIndex < meta.Permutations.Count(); permutationIndex++)
{
#if PRINT_DESCRIPTORS
LOG(Warning, "VULKAN SHADER {0}: {1}[{2}]", _context->Options->TargetName, String(meta.Name), permutationIndex);
#endif
_macros.Clear();
// Get function permutation macros
meta.GetDefinitionsForPermutation(permutationIndex, _macros);
// Add additional define for compiled function name
GetDefineForFunction(meta, _macros);
// Add custom and global macros (global last because contain null define to indicate ending)
_macros.Add(_context->Options->Macros);
_macros.Add(_globalMacros);
// Offset inputs for some pipeline stages to match the descriptors sets layout
int32 stageSet;
switch (type)
{
case ShaderStage::Vertex:
case ShaderStage::Compute:
stageSet = 0;
break;
case ShaderStage::Pixel:
stageSet = 1;
break;
case ShaderStage::Geometry:
stageSet = 2;
break;
case ShaderStage::Hull:
stageSet = 3;
break;
case ShaderStage::Domain:
stageSet = 4;
break;
default:
LOG(Error, "Unknown shader type.");
return true;
}
// Parse HLSL shader using glslang
glslang::TShader shader(lang);
glslang::TProgram program;
shader.setEntryPoint(meta.Name.Get());
shader.setSourceEntryPoint(meta.Name.Get());
int lengths = options->SourceLength - 1;
const char* names = _context->TargetNameAnsi;
shader.setStringsWithLengthsAndNames(&options->Source, &lengths, &names, 1);
const int defaultVersion = 450;
std::string preamble;
for (int32 i = 0; i < _macros.Count() - 1; i++)
{
auto& macro = _macros[i];
preamble.append("#define ");
preamble.append(macro.Name);
if (macro.Definition)
{
preamble.append(" ");
preamble.append(macro.Definition);
}
preamble.append("\n");
}
shader.setPreamble(preamble.c_str());
shader.setInvertY(true);
//shader.setAutoMapLocations(true);
//shader.setAutoMapBindings(true);
//shader.setShiftBinding(glslang::TResourceType::EResUav, 500);
shader.setHlslIoMapping(true);
shader.setEnvInput(glslang::EShSourceHlsl, lang, glslang::EShClientVulkan, defaultVersion);
shader.setEnvClient(glslang::EShClientVulkan, glslang::EShTargetVulkan_1_0);
shader.setEnvTarget(glslang::EShTargetSpv, glslang::EShTargetSpv_1_0);
if (!shader.parse(&DefaultTBuiltInResource, defaultVersion, false, messages, includer))
{
const auto msg = shader.getInfoLog();
_context->OnError(msg);
return true;
}
program.addShader(&shader);
// Generate reflection information
if (!program.link(messages))
{
const auto msg = program.getInfoLog();
_context->OnError(msg);
return true;
}
if (!program.getIntermediate(lang))
{
const auto msg = program.getInfoLog();
_context->OnError(msg);
return true;
}
DescriptorsCollector descriptorsCollector;
MyIoMapResolver resolver(stageSet, &descriptorsCollector, *program.getIntermediate(lang));
if (!program.mapIO(&resolver))
{
const auto msg = program.getInfoLog();
_context->OnError(msg);
return true;
}
if (!program.buildReflection())
{
const auto msg = program.getInfoLog();
_context->OnError(msg);
return true;
}
// Process shader reflection data
SpirvShaderHeader header;
Platform::MemoryClear(&header, sizeof(header));
ShaderBindings bindings = { 0, 0, 0, 0 };
{
// Extract constant buffers usage information
for (int blockIndex = 0; blockIndex < program.getNumLiveUniformBlocks(); blockIndex++)
{
auto size = program.getUniformBlockSize(blockIndex);
auto uniform = program.getUniformBlockTType(blockIndex);
auto& qualifier = uniform->getQualifier();
auto binding = (int32)qualifier.layoutBinding;
if (!qualifier.hasBinding())
{
// Each uniform must have a valid binding
//LOG(Warning, "Found a uniform block \'{0}\' without a binding qualifier. Each uniform block must have an explicitly defined binding number.", String(uniform->getTypeName().c_str()));
continue;
}
// Shared storage buffer
if (qualifier.storage == glslang::EvqBuffer)
{
// RWBuffer
}
else
{
// Uniform buffer
bool found = false;
for (int32 i = 0; i < descriptorsCollector.DescriptorsCount; i++)
{
auto& descriptor = descriptorsCollector.Descriptors[i];
if (descriptor.BindingType == SpirvShaderResourceBindingType::CB && descriptor.Binding == binding)
{
found = true;
descriptor.Size = size;
break;
}
}
if (!found)
{
LOG(Warning, "Failed to find descriptor for the uniform block \'{0}\' of size {1} (bytes), binding: {2}.", String(uniform->getTypeName().c_str()), size, binding);
}
}
}
#if PRINT_UNIFORMS
// Debug printing all uniforms
for (int32 index = 0; index < program.getNumLiveUniformVariables(); index++)
{
auto uniform = program.getUniformTType(index);
auto qualifier = uniform->getQualifier();
if (!uniform->isArray())
LOG(Warning, "Shader {0}:{1} - uniform: {2} {3} at binding {4}",
_context->TargetNameAnsi,
String(meta.Name),
uniform->getCompleteString().c_str(),
program.getUniformName(index),
qualifier.layoutBinding
);
}
#endif
// Process all descriptors
header.DescriptorInfo.ImageInfosCount = descriptorsCollector.Images;
header.DescriptorInfo.BufferInfosCount = descriptorsCollector.Buffers;
for (int32 i = 0; i < descriptorsCollector.DescriptorsCount; i++)
{
auto& descriptor = descriptorsCollector.Descriptors[i];
auto& d = header.DescriptorInfo.DescriptorTypes[header.DescriptorInfo.DescriptorTypesCount++];
d.Binding = descriptor.Binding;
d.Set = stageSet;
d.Slot = descriptor.Slot;
d.BindingType = descriptor.BindingType;
d.DescriptorType = descriptor.DescriptorType;
d.ResourceType = descriptor.ResourceType;
switch (descriptor.BindingType)
{
case SpirvShaderResourceBindingType::CB:
bindings.UsedCBsMask |= 1 << descriptor.Slot;
break;
case SpirvShaderResourceBindingType::SRV:
bindings.UsedSRsMask |= 1 << descriptor.Slot;
break;
case SpirvShaderResourceBindingType::UAV:
bindings.UsedUAsMask |= 1 << descriptor.Slot;
break;
}
if (descriptor.BindingType == SpirvShaderResourceBindingType::CB)
{
if (descriptor.Size == -1)
{
// Skip unused constant buffers
continue;
}
if (descriptor.Size == 0)
{
LOG(Warning, "Found constant buffer \'{1}\' at slot {0} but it's not used or has no valid size.", descriptor.Slot, String(descriptor.Name.c_str()));
continue;
}
for (int32 b = 0; b < _constantBuffers.Count(); b++)
{
auto& cc = _constantBuffers[b];
if (cc.Slot == descriptor.Slot)
{
// Mark as used and cache some data
cc.IsUsed = true;
cc.Size = descriptor.Size;
}
}
}
#if PRINT_DESCRIPTORS
String type;
switch (descriptor.BindingType)
{
case SpirvShaderResourceBindingType::INVALID:
type = TEXT("INVALID");
break;
case SpirvShaderResourceBindingType::CB:
type = TEXT("CB");
break;
case SpirvShaderResourceBindingType::SAMPLER:
type = TEXT("SAMPLER");
break;
case SpirvShaderResourceBindingType::SRV:
type = TEXT("SRV");
break;
case SpirvShaderResourceBindingType::UAV:
type = TEXT("UAV");
break;
default:
type = TEXT("?");
}
LOG(Warning, "VULKAN SHADER RESOURCE: slot: {1}, binding: {2}, name: {0}, type: {3}", String(descriptor.Name.c_str()), descriptor.Slot, descriptor.Binding, type);
#endif
}
}
// Generate SPIR-V (optimize it at the same time)
std::vector<unsigned> spirv;
spv::SpvBuildLogger logger;
glslang::SpvOptions spvOptions;
spvOptions.generateDebugInfo = false;
spvOptions.disassemble = false;
spvOptions.disableOptimizer = options->NoOptimize;
spvOptions.optimizeSize = !options->NoOptimize;
spvOptions.stripDebugInfo = !options->GenerateDebugData;
#if BUILD_DEBUG
spvOptions.validate = true;
#else
spvOptions.validate = false;
#endif
glslang::GlslangToSpv(*program.getIntermediate(lang), spirv, &logger, &spvOptions);
const std::string spirvLogOutput = logger.getAllMessages();
if (!spirvLogOutput.empty())
{
LOG(Warning, "SPIR-V generator log:\n{0}", String(spirvLogOutput.c_str()));
}
if (spirv.empty())
{
LOG(Warning, "SPIR-V generator failed");
return true;
}
#if 0
// Dump SPIR-V as text for debugging
{
spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_0);
std::string spirvText;
tools.Disassemble(spirv, &spirvText);
_context->OnCollectDebugInfo(meta, permutationIndex, spirvText.c_str(), (int32)spirvText.size());
}
#endif
int32 spirvBytesCount = (int32)spirv.size() * sizeof(unsigned);
header.Type = SpirvShaderHeader::Types::Raw;
Array<byte> data;
data.Resize(sizeof(header) + spirvBytesCount);
Platform::MemoryCopy(data.Get(), &header, sizeof(header));
Platform::MemoryCopy(data.Get() + sizeof(header), &spirv[0], spirvBytesCount);
if (WriteShaderFunctionPermutation(_context, meta, permutationIndex, bindings, data.Get(), data.Count()))
return true;
if (customDataWrite && customDataWrite(_context, meta, permutationIndex, _macros))
return true;
}
return WriteShaderFunctionEnd(_context, meta);
}
bool ShaderCompilerVulkan::OnCompileBegin()
{
if (ShaderCompiler::OnCompileBegin())
return true;
//_globalMacros.Add({ "VULKAN", "1" }); // glslang compiler adds VULKAN define if EShMsgVulkanRules flag is specified
// TODO: handle options->TreatWarningsAsErrors
return false;
}
#endif

View File

@@ -0,0 +1,38 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#pragma once
#if COMPILE_WITH_VK_SHADER_COMPILER
#include "Engine/ShadersCompilation/ShaderCompiler.h"
/// <summary>
/// Implementation of shaders compiler for Vulkan rendering backend.
/// </summary>
class ShaderCompilerVulkan : public ShaderCompiler
{
private:
Array<char> _funcNameDefineBuffer;
public:
/// <summary>
/// Initializes a new instance of the <see cref="ShaderCompilerVulkan"/> class.
/// </summary>
/// <param name="profile">The profile.</param>
ShaderCompilerVulkan(ShaderProfile profile);
/// <summary>
/// Finalizes an instance of the <see cref="ShaderCompilerVulkan"/> class.
/// </summary>
~ShaderCompilerVulkan();
protected:
// [ShaderCompiler]
bool CompileShader(ShaderFunctionMeta& meta, WritePermutationData customDataWrite = nullptr) override;
bool OnCompileBegin() override;
};
#endif