828 lines
29 KiB
C++
828 lines
29 KiB
C++
// Copyright (c) 2012-2022 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;
|
|
}
|
|
}
|
|
|
|
bool IsUavType(const glslang::TType& type)
|
|
{
|
|
if (type.getQualifier().isReadOnly())
|
|
return false;
|
|
return (type.getBasicType() == glslang::EbtSampler && type.getSampler().isImage()) || (type.getQualifier().storage == glslang::EvqBuffer);
|
|
}
|
|
|
|
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)
|
|
{
|
|
if (IsUavType(type))
|
|
{
|
|
// Buffer UAV
|
|
descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER;
|
|
resourceType = SpirvShaderResourceType::Buffer;
|
|
resourceBindingType = SpirvShaderResourceBindingType::UAV;
|
|
}
|
|
else
|
|
{
|
|
// 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;
|
|
|
|
if (WriteShaderFunctionPermutation(_context, meta, permutationIndex, bindings, &header, sizeof(header), &spirv[0], spirvBytesCount))
|
|
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
|