in renderdoc/driver/shaders/spirv/spirv_reflect.cpp [903:1903]
void Reflector::MakeReflection(const GraphicsAPI sourceAPI, const ShaderStage stage,
const rdcstr &entryPoint, const rdcarray<SpecConstant> &specInfo,
ShaderReflection &reflection, SPIRVPatchData &patchData) const
{
// set global properties
reflection.entryPoint = entryPoint;
reflection.stage = stage;
reflection.encoding =
sourceAPI == GraphicsAPI::OpenGL ? ShaderEncoding::OpenGLSPIRV : ShaderEncoding::SPIRV;
reflection.rawBytes.assign((byte *)m_SPIRV.data(), m_SPIRV.size() * sizeof(uint32_t));
CheckDebuggable(reflection.debugInfo.debuggable, reflection.debugInfo.debugStatus);
const EntryPoint *entry = NULL;
for(const EntryPoint &e : entries)
{
if(entryPoint == e.name && MakeShaderStage(e.executionModel) == stage)
{
entry = &e;
break;
}
}
if(!entry)
{
RDCERR("Entry point %s for stage %s not found in module", entryPoint.c_str(),
ToStr(stage).c_str());
return;
}
// pick up execution mode size
if(stage == ShaderStage::Compute || stage == ShaderStage::Task || stage == ShaderStage::Mesh)
{
const EntryPoint &e = *entry;
if(e.executionModes.localSizeId.x != Id())
{
reflection.dispatchThreadsDimension[0] =
EvaluateConstant(e.executionModes.localSizeId.x, specInfo).value.u32v[0];
reflection.dispatchThreadsDimension[1] =
EvaluateConstant(e.executionModes.localSizeId.y, specInfo).value.u32v[0];
reflection.dispatchThreadsDimension[2] =
EvaluateConstant(e.executionModes.localSizeId.z, specInfo).value.u32v[0];
}
else if(e.executionModes.localSize.x > 0)
{
reflection.dispatchThreadsDimension[0] = e.executionModes.localSize.x;
reflection.dispatchThreadsDimension[1] = e.executionModes.localSize.y;
reflection.dispatchThreadsDimension[2] = e.executionModes.localSize.z;
}
{
int idx = e.executionModes.others.indexOf(rdcspv::ExecutionMode::OutputVertices);
if(idx >= 0)
patchData.maxVertices = e.executionModes.others[idx].outputVertices;
}
{
int idx = e.executionModes.others.indexOf(rdcspv::ExecutionMode::OutputPrimitivesEXT);
if(idx >= 0)
patchData.maxPrimitives = e.executionModes.others[idx].outputPrimitivesEXT;
}
// vulkan spec says "If an object is decorated with the WorkgroupSize decoration, this must take
// precedence over any execution mode set for LocalSize."
for(auto it : constants)
{
const Constant &c = it.second;
if(decorations[c.id].builtIn == BuiltIn::WorkgroupSize)
{
RDCASSERT(c.children.size() == 3);
for(size_t i = 0; i < c.children.size() && i < 3; i++)
reflection.dispatchThreadsDimension[i] =
EvaluateConstant(c.children[i], specInfo).value.u32v[0];
}
}
}
else
{
reflection.dispatchThreadsDimension[0] = reflection.dispatchThreadsDimension[1] =
reflection.dispatchThreadsDimension[2] = 0;
}
switch(sourceLanguage)
{
case SourceLanguage::ESSL:
case SourceLanguage::GLSL: reflection.debugInfo.encoding = ShaderEncoding::GLSL; break;
case SourceLanguage::HLSL: reflection.debugInfo.encoding = ShaderEncoding::HLSL; break;
case SourceLanguage::Slang: reflection.debugInfo.encoding = ShaderEncoding::Slang; break;
case SourceLanguage::OpenCL_C:
case SourceLanguage::OpenCL_CPP:
case SourceLanguage::CPP_for_OpenCL:
case SourceLanguage::Unknown:
case SourceLanguage::Invalid:
case SourceLanguage::SYCL:
case SourceLanguage::HERO_C:
case SourceLanguage::NZSL:
case SourceLanguage::WGSL:
case SourceLanguage::Zig:
case SourceLanguage::Max: break;
}
for(size_t i = 0; i < sources.size(); i++)
{
reflection.debugInfo.files.push_back({sources[i].name, sources[i].contents});
}
switch(m_Generator)
{
case Generator::GlslangReferenceFrontEnd:
case Generator::ShadercoverGlslang:
reflection.debugInfo.compiler = reflection.debugInfo.encoding == ShaderEncoding::HLSL
? KnownShaderTool::glslangValidatorHLSL
: KnownShaderTool::glslangValidatorGLSL;
if(sourceAPI == GraphicsAPI::OpenGL &&
reflection.debugInfo.compiler == KnownShaderTool::glslangValidatorGLSL)
reflection.debugInfo.compiler = KnownShaderTool::glslangValidatorGLSL_OpenGL;
break;
case Generator::SPIRVToolsAssembler:
reflection.debugInfo.compiler = sourceAPI == GraphicsAPI::OpenGL
? KnownShaderTool::spirv_as_OpenGL
: KnownShaderTool::spirv_as;
break;
case Generator::spiregg: reflection.debugInfo.compiler = KnownShaderTool::dxcSPIRV; break;
case Generator::SlangCompiler:
reflection.debugInfo.compiler = KnownShaderTool::slangSPIRV;
break;
default: reflection.debugInfo.compiler = KnownShaderTool::Unknown; break;
}
if(!cmdline.empty())
reflection.debugInfo.compileFlags.flags = {{"@cmdline", cmdline}};
reflection.debugInfo.compileFlags.flags.push_back(
{"@spirver", StringFormat::Fmt("spirv%d.%d", m_MajorVersion, m_MinorVersion)});
reflection.debugInfo.entrySourceName = entryPoint;
{
auto it = funcToDebugFunc.find(entry->id);
if(it != funcToDebugFunc.end())
{
rdcstr debugEntryName = debugFuncName[it->second];
if(!debugEntryName.empty())
reflection.debugInfo.entrySourceName = debugEntryName;
reflection.debugInfo.entryLocation = debugFuncToLocation[it->second];
if(debugFuncToCmdLine.find(it->second) != debugFuncToCmdLine.end())
reflection.debugInfo.compileFlags.flags = {{"@cmdline", debugFuncToCmdLine[it->second]}};
if(debugFuncToBaseFile.find(it->second) != debugFuncToBaseFile.end())
reflection.debugInfo.editBaseFile = (int32_t)debugFuncToBaseFile[it->second];
}
}
PreprocessLineDirectives(reflection.debugInfo.files);
// we do a mini-preprocess of the files from the debug info to handle #line directives.
// This means that any lines that our source file declares to be in another filename via a #line
// get put in the right place for what the debug information hopefully matches.
// We also concatenate duplicate lines and display them all, to handle edge cases where #lines
// declare duplicates.
if(knownExtSet[ExtSet_ShaderDbg] != Id() && !reflection.debugInfo.files.empty())
{
reflection.debugInfo.compileFlags.flags.push_back({"preferSourceDebug", "1"});
reflection.debugInfo.sourceDebugInformation = true;
}
std::set<Id> usedIds;
std::map<Id, std::set<uint32_t>> usedStructChildren;
// for arrayed top level builtins like gl_MeshPrimitivesEXT[] there could be an access chain
// first with just the array index, then later the access to the builtin. This map tracks those
// first access chains so the second one can reference the original global
std::map<Id, Id> topLevelChildChain;
// build the static call tree from the entry point, and build a list of all IDs referenced
{
std::set<Id> processed;
rdcarray<Id> pending;
pending.push_back(entry->id);
while(!pending.empty())
{
Id func = pending.back();
pending.pop_back();
processed.insert(func);
ConstIter it(m_SPIRV, idOffsets[func]);
while(it.opcode() != Op::FunctionEnd)
{
OpDecoder::ForEachID(it, [&usedIds](Id id, bool result) { usedIds.insert(id); });
if(it.opcode() == Op::AccessChain || it.opcode() == Op::InBoundsAccessChain)
{
OpAccessChain access(it);
const DataType &pointeeType = dataTypes[dataTypes[idTypes[access.base]].InnerType()];
if(pointeeType.type == DataType::ArrayType)
{
const DataType &innerType = dataTypes[pointeeType.InnerType()];
if(innerType.type == DataType::StructType &&
(innerType.children[0].decorations.flags & rdcspv::Decorations::HasBuiltIn))
{
if(access.indexes.size() == 1)
{
topLevelChildChain[access.result] = access.base;
}
else if(access.indexes.size() == 2)
{
usedStructChildren[access.base].insert(
EvaluateConstant(access.indexes[1], specInfo).value.u32v[0]);
}
}
}
// save top-level children referenced in structs
else if(pointeeType.type == DataType::StructType)
{
rdcspv::Id globalId = access.base;
if(topLevelChildChain.find(access.base) != topLevelChildChain.end())
globalId = topLevelChildChain[access.base];
usedStructChildren[globalId].insert(
EvaluateConstant(access.indexes[0], specInfo).value.u32v[0]);
}
}
if(it.opcode() == Op::FunctionCall)
{
OpFunctionCall call(it);
if(processed.find(call.function) == processed.end())
pending.push_back(call.function);
}
it++;
}
}
}
if(m_MajorVersion > 1 || m_MinorVersion >= 4)
{
// from SPIR-V 1.4 onwards we can trust the entry point interface list to give us all used
// global variables. We still use the above heuristic so we can remove unused members of
// gl_PerVertex in structs.
usedIds.clear();
usedIds.insert(entry->usedIds.begin(), entry->usedIds.end());
}
else
{
// before that, still consider all entry interface used just not exclusively
usedIds.insert(entry->usedIds.begin(), entry->usedIds.end());
}
patchData.usedIds.reserve(usedIds.size());
for(Id id : usedIds)
patchData.usedIds.push_back(id);
// arrays of elements, which can be appended to in any order and then sorted
rdcarray<SigParameter> inputs;
rdcarray<SigParameter> outputs;
rdcarray<sortedcblock> cblocks;
rdcarray<sortedsamp> samplers;
rdcarray<sortedres> roresources, rwresources;
// for pointer types, mapping of inner type ID to index in list (assigned sequentially)
SparseIdMap<uint16_t> pointerTypes;
// $Globals gathering - for GL global values
ConstantBlock globalsblock;
// for mesh shaders, the task-mesh communication payload
ConstantBlock taskPayloadBlock;
// specialisation constant gathering
ConstantBlock specblock;
// declare pointerTypes for all declared physical pointer types first. This allows the debugger
// to easily match pointer types itself
for(auto it = dataTypes.begin(); it != dataTypes.end(); ++it)
{
if(it->second.type == DataType::PointerType &&
it->second.pointerType.storage == rdcspv::StorageClass::PhysicalStorageBuffer)
{
pointerTypes.insert(std::make_pair(it->second.InnerType(), (uint16_t)pointerTypes.size()));
}
}
for(const Variable &global : globals)
{
if(global.storage == StorageClass::Input || global.storage == StorageClass::Output)
{
// variable type must be a pointer of the same storage class
RDCASSERT(dataTypes[global.type].type == DataType::PointerType);
RDCASSERT(dataTypes[global.type].pointerType.storage == global.storage);
const DataType &baseType = dataTypes[dataTypes[global.type].InnerType()];
const bool isInput = (global.storage == StorageClass::Input);
rdcarray<SigParameter> &sigarray = (isInput ? inputs : outputs);
// try to use the instance/variable name
rdcstr name = strings[global.id];
// otherwise fall back to naming after the builtin or location
if(name.empty())
{
if(decorations[global.id].flags & Decorations::HasBuiltIn)
name = StringFormat::Fmt("_%s", ToStr(decorations[global.id].builtIn).c_str());
else if(decorations[global.id].flags & Decorations::HasLocation)
name = StringFormat::Fmt("_%s%u", isInput ? "input" : "output",
decorations[global.id].location);
else
name = StringFormat::Fmt("_sig%u", global.id.value());
for(const DecorationAndParamData &d : decorations[global.id].others)
{
if(d.value == Decoration::Component)
name += StringFormat::Fmt("_%u", d.component);
}
}
const bool used = usedIds.find(global.id) != usedIds.end();
// only include signature parameters that are explicitly used.
if(!used)
continue;
// we want to skip any members of the builtin interface block that are completely unused and
// just came along for the ride (usually with gl_Position, but maybe declared and still
// unused). This is meaningless in SPIR-V and just generates useless noise, but some compilers
// from GLSL can generate the whole gl_PerVertex as a literal translation from the implicit
// GLSL declaration.
//
// Some compilers generate global variables instead of members of a global struct. If this is
// a directly decorated builtin variable which is never used, skip it
if(IsStrippableBuiltin(
decorations[global.id].builtIn,
decorations[global.id].others.contains(rdcspv::Decoration::PerPrimitiveEXT)) &&
!used)
continue;
// move to the inner struct if this is an array of structs - e.g. for arrayed shader outputs
const DataType *structType = &baseType;
if(structType->type == DataType::ArrayType &&
dataTypes[structType->InnerType()].type == DataType::StructType)
structType = &dataTypes[structType->InnerType()];
// if this is a struct variable then either all members must be builtins, or none of them, as
// per the SPIR-V Decoration rules:
//
// "When applied to a structure-type member, all members of that structure type must also be
// decorated with BuiltIn. (No allowed mixing of built-in variables and non-built-in variables
// within a single structure.)"
//
// Some old compilers might generate gl_PerVertex with unused variables having no decoration,
// so to handle this case we treat a struct with any builtin members as if all are builtin -
// which is still legal.
if(structType->type == DataType::StructType)
{
// look to see if this struct contains a builtin member
bool hasBuiltins = false;
for(size_t i = 0; i < structType->children.size(); i++)
{
hasBuiltins = (structType->children[i].decorations.builtIn != BuiltIn::Invalid);
if(hasBuiltins)
break;
}
// if this is the builtin struct, explode the struct and call AddSignatureParameter for each
// member here, so we can skip unused children if we want
if(hasBuiltins)
{
const std::set<uint32_t> &usedchildren = usedStructChildren[global.id];
size_t oldSigSize = sigarray.size();
for(uint32_t i = 0; i < (uint32_t)structType->children.size(); i++)
{
// skip this member if it's in a builtin struct but has no builtin decoration
if(structType->children[i].decorations.builtIn == BuiltIn::Invalid)
continue;
// skip this member if it's unused and of a type that is commonly included 'by accident'
if(IsStrippableBuiltin(structType->children[i].decorations.builtIn,
structType->children[i].decorations.others.contains(
rdcspv::Decoration::PerPrimitiveEXT)) &&
usedchildren.find(i) == usedchildren.end())
continue;
rdcstr childname = name;
if(!structType->children[i].name.empty())
childname += "." + structType->children[i].name;
else
childname += StringFormat::Fmt("._child%zu", i);
SPIRVInterfaceAccess patch;
patch.accessChain = {i};
uint32_t dummy = 0;
AddSignatureParameter(isInput, stage, global.id, structType->id, dummy, patch,
childname, dataTypes[structType->children[i].type],
structType->children[i].decorations, sigarray, patchData, specInfo);
}
// apply stream decoration from a parent struct into newly-added members
for(const DecorationAndParamData &d : decorations[global.id].others)
{
if(d.value == Decoration::Stream)
{
for(size_t idx = oldSigSize; idx < sigarray.size(); idx++)
{
sigarray[idx].stream = d.stream;
}
}
}
// move on now, we've processed this global struct
continue;
}
}
uint32_t dummy = 0;
AddSignatureParameter(isInput, stage, global.id, Id(), dummy, {}, name, baseType,
decorations[global.id], sigarray, patchData, specInfo);
}
else if(global.storage == StorageClass::Uniform ||
global.storage == StorageClass::UniformConstant ||
global.storage == StorageClass::AtomicCounter ||
global.storage == StorageClass::StorageBuffer ||
global.storage == StorageClass::PushConstant ||
global.storage == StorageClass::TaskPayloadWorkgroupEXT)
{
// variable type must be a pointer of the same storage class
RDCASSERT(dataTypes[global.type].type == DataType::PointerType);
RDCASSERT(dataTypes[global.type].pointerType.storage == global.storage);
const DataType *varType = &dataTypes[dataTypes[global.type].InnerType()];
// if the outer type is an array, get the length and peel it off.
uint32_t arraySize = 1;
if(varType->type == DataType::ArrayType)
{
// runtime arrays have no length
if(varType->length != Id())
arraySize = EvaluateConstant(varType->length, specInfo).value.u32v[0];
else
arraySize = ~0U;
varType = &dataTypes[varType->InnerType()];
}
// new SSBOs are in the storage buffer class, previously they were in uniform with BufferBlock
// decoration
const bool ssbo = (global.storage == StorageClass::StorageBuffer) ||
(decorations[varType->id].flags & Decorations::BufferBlock);
const bool pushConst = (global.storage == StorageClass::PushConstant);
const bool atomicCounter = (global.storage == StorageClass::AtomicCounter);
const bool taskPayload = (global.storage == StorageClass::TaskPayloadWorkgroupEXT);
rdcspv::StorageClass effectiveStorage = global.storage;
if(ssbo)
effectiveStorage = StorageClass::StorageBuffer;
uint32_t bindset = 0;
if(!pushConst)
bindset = GetDescSet(decorations[global.id].set);
uint32_t bind = GetBinding(decorations[global.id].binding);
// On GL if we have a location and no binding, put that in as the bind. It is not used
// otherwise on GL as the bindings are dynamic. This should only happen for
// bare uniforms and not for texture/buffer type uniforms which should have a binding
if(sourceAPI == GraphicsAPI::OpenGL)
{
Decorations::Flags flags = Decorations::Flags(
decorations[global.id].flags & (Decorations::HasLocation | Decorations::HasBinding));
if(flags == Decorations::HasLocation)
{
bind = decorations[global.id].location;
}
else if(flags == Decorations::NoFlags)
{
bind = ~0U;
}
}
if(usedIds.find(global.id) == usedIds.end())
{
// ignore this variable that's not in the entry point's used interface
}
else if(atomicCounter)
{
// GL style atomic counter variable
RDCASSERT(sourceAPI == GraphicsAPI::OpenGL);
ShaderResource res;
res.isReadOnly = false;
res.isTexture = false;
res.name = strings[global.id];
if(res.name.empty())
res.name = varType->name;
if(res.name.empty())
res.name = StringFormat::Fmt("atomic%u", global.id.value());
res.textureType = TextureType::Buffer;
res.descriptorType = DescriptorType::ReadWriteBuffer;
res.variableType.columns = 1;
res.variableType.rows = 1;
res.variableType.baseType = VarType::UInt;
res.variableType.name = varType->name;
res.fixedBindSetOrSpace = 0;
res.fixedBindNumber = GetBinding(decorations[global.id].binding);
res.bindArraySize = arraySize;
rwresources.push_back(sortedres(global.id, res));
}
else if(varType->IsOpaqueType())
{
// on Vulkan should never have elements that have no binding declared but are used. On GL we
// should have gotten a location
// above, which will be rewritten later when looking up the pipeline state since it's
// mutable from action to action in theory.
RDCASSERT(bind != INVALID_BIND);
// opaque type - buffers, images, etc
ShaderResource res;
res.name = strings[global.id];
if(res.name.empty())
res.name = StringFormat::Fmt("res%u", global.id.value());
res.fixedBindSetOrSpace = bindset;
res.fixedBindNumber = bind;
res.bindArraySize = arraySize;
if(varType->type == DataType::SamplerType)
{
ShaderSampler samp;
samp.name = res.name;
samp.fixedBindSetOrSpace = bindset;
samp.fixedBindNumber = bind;
samp.bindArraySize = arraySize;
samplers.push_back(sortedsamp(global.id, samp));
}
else if(varType->type == DataType::AccelerationStructureType)
{
res.descriptorType = DescriptorType::AccelerationStructure;
res.variableType.baseType = VarType::ReadOnlyResource;
res.isTexture = false;
res.isReadOnly = true;
roresources.push_back(sortedres(global.id, res));
}
else
{
Id imageTypeId = varType->id;
if(varType->type == DataType::SampledImageType)
{
imageTypeId = sampledImageTypes[varType->id].baseId;
res.hasSampler = true;
}
const Image &imageType = imageTypes[imageTypeId];
if(imageType.ms)
res.textureType =
imageType.arrayed ? TextureType::Texture2DMSArray : TextureType::Texture2DMS;
else if(imageType.dim == rdcspv::Dim::_1D)
res.textureType =
imageType.arrayed ? TextureType::Texture1DArray : TextureType::Texture1D;
else if(imageType.dim == rdcspv::Dim::_2D)
res.textureType =
imageType.arrayed ? TextureType::Texture2DArray : TextureType::Texture2D;
else if(imageType.dim == rdcspv::Dim::Cube)
res.textureType =
imageType.arrayed ? TextureType::TextureCubeArray : TextureType::TextureCube;
else if(imageType.dim == rdcspv::Dim::_3D)
res.textureType = TextureType::Texture3D;
else if(imageType.dim == rdcspv::Dim::Rect)
res.textureType = TextureType::TextureRect;
else if(imageType.dim == rdcspv::Dim::Buffer)
res.textureType = TextureType::Buffer;
else if(imageType.dim == rdcspv::Dim::SubpassData)
res.textureType = TextureType::Texture2D;
res.isTexture = res.textureType != TextureType::Buffer;
res.isReadOnly = imageType.sampled != 2 || imageType.dim == rdcspv::Dim::SubpassData;
res.isInputAttachment = imageType.dim == rdcspv::Dim::SubpassData;
res.variableType.baseType = imageType.retType.Type();
if(res.isReadOnly)
{
res.descriptorType =
res.hasSampler ? DescriptorType::ImageSampler : DescriptorType::Image;
if(!res.isTexture)
res.descriptorType = DescriptorType::TypedBuffer;
roresources.push_back(sortedres(global.id, res));
}
else
{
res.descriptorType = DescriptorType::ReadWriteImage;
if(!res.isTexture)
res.descriptorType = DescriptorType::ReadWriteTypedBuffer;
rwresources.push_back(sortedres(global.id, res));
}
}
}
else
{
if(varType->type != DataType::StructType)
{
if(taskPayload)
{
if(!patchData.invalidTaskPayload)
{
RDCWARN(
"Unhandled case - non-struct task payload. Most likely DXC bug as only one task "
"payload variable allowed per entry point.");
taskPayloadBlock.name = "invalid";
taskPayloadBlock.bufferBacked = false;
patchData.invalidTaskPayload = true;
}
}
else
{
// global loose variable - add to $Globals block
RDCASSERT(varType->type == DataType::ScalarType || varType->type == DataType::VectorType ||
varType->type == DataType::MatrixType || varType->type == DataType::ArrayType);
RDCASSERT(sourceAPI == GraphicsAPI::OpenGL);
ShaderConstant constant;
MakeConstantBlockVariable(constant, pointerTypes, effectiveStorage, *varType,
strings[global.id], decorations[global.id], specInfo);
if(arraySize > 1)
constant.type.elements = arraySize;
else
constant.type.elements = 0;
constant.byteOffset = decorations[global.id].location;
globalsblock.variables.push_back(constant);
}
}
else if(taskPayload)
{
taskPayloadBlock.name = strings[global.id];
if(taskPayloadBlock.name.empty())
taskPayloadBlock.name = StringFormat::Fmt("payload%u", global.id.value());
taskPayloadBlock.bufferBacked = false;
MakeConstantBlockVariables(effectiveStorage, *varType, 0, 0, taskPayloadBlock.variables,
pointerTypes, specInfo);
CalculateScalarLayout(0, taskPayloadBlock.variables);
}
else
{
// on Vulkan should never have elements that have no binding declared but are used, unless
// it's push constants (which is handled elsewhere). On GL we should have gotten a
// location above, which will be rewritten later when looking up the pipeline state since
// it's mutable from action to action in theory.
RDCASSERT(pushConst || bind != INVALID_BIND);
if(ssbo)
{
ShaderResource res;
res.isReadOnly = false;
res.isTexture = false;
res.name = strings[global.id];
if(res.name.empty())
res.name = StringFormat::Fmt("ssbo%u", global.id.value());
res.textureType = TextureType::Buffer;
res.descriptorType = DescriptorType::ReadWriteBuffer;
res.fixedBindNumber = bind;
res.fixedBindSetOrSpace = bindset;
res.bindArraySize = arraySize;
res.variableType.columns = 0;
res.variableType.rows = 0;
res.variableType.baseType = VarType::Float;
res.variableType.name = varType->name;
MakeConstantBlockVariables(effectiveStorage, *varType, 0, 0, res.variableType.members,
pointerTypes, specInfo);
rwresources.push_back(sortedres(global.id, res));
}
else
{
ConstantBlock cblock;
cblock.name = strings[global.id];
if(cblock.name.empty())
cblock.name = StringFormat::Fmt("uniforms%u", global.id.value());
cblock.bufferBacked = !pushConst;
cblock.inlineDataBytes = pushConst;
cblock.fixedBindNumber = bind;
cblock.fixedBindSetOrSpace = bindset;
cblock.bindArraySize = arraySize;
MakeConstantBlockVariables(effectiveStorage, *varType, 0, 0, cblock.variables,
pointerTypes, specInfo);
if(!varType->children.empty())
cblock.byteSize = CalculateMinimumByteSize(cblock.variables);
else
cblock.byteSize = 0;
cblocks.push_back(sortedcblock(global.id, cblock));
}
}
}
}
else if(global.storage == StorageClass::Private ||
global.storage == StorageClass::CrossWorkgroup ||
global.storage == StorageClass::Workgroup)
{
// silently allow
}
else
{
RDCWARN("Unexpected storage class for global: %s", ToStr(global.storage).c_str());
}
}
for(auto it : constants)
{
const Constant &c = it.second;
if(decorations[c.id].flags & Decorations::HasSpecId)
{
rdcstr name = strings[c.id];
if(name.empty())
name = StringFormat::Fmt("specID%u", decorations[c.id].specID);
ShaderConstant spec;
MakeConstantBlockVariable(spec, pointerTypes, rdcspv::StorageClass::PushConstant,
dataTypes[c.type], name, decorations[c.id], specInfo);
spec.byteOffset = uint32_t(specblock.variables.size() * sizeof(uint64_t));
spec.defaultValue = c.value.value.u64v[0];
specblock.variables.push_back(spec);
patchData.specIDs.push_back(decorations[c.id].specID);
}
}
if(!specblock.variables.empty())
{
specblock.name = "Specialization Constants";
specblock.bufferBacked = false;
specblock.inlineDataBytes = true;
specblock.compileConstants = true;
specblock.byteSize = 0;
// set the binding number to some huge value to try to sort it to the end
specblock.fixedBindNumber = 0x8000000;
specblock.bindArraySize = 1;
cblocks.push_back(sortedcblock(Id(), specblock));
}
if(!globalsblock.variables.empty())
{
globalsblock.name = "$Globals";
globalsblock.bufferBacked = false;
globalsblock.inlineDataBytes = false;
globalsblock.byteSize = (uint32_t)globalsblock.variables.size();
globalsblock.fixedBindSetOrSpace = 0;
// set the binding number to some huge value to try to sort it to the end
globalsblock.fixedBindNumber = 0x8000001;
globalsblock.bindArraySize = 1;
cblocks.push_back(sortedcblock(Id(), globalsblock));
}
reflection.taskPayload = taskPayloadBlock;
// look for execution modes that affect the reflection and apply them
{
const EntryPoint &e = *entry;
if(e.executionModes.depthMode == ExecutionModes::DepthGreater)
{
for(SigParameter &sig : outputs)
{
if(sig.systemValue == ShaderBuiltin::DepthOutput)
sig.systemValue = ShaderBuiltin::DepthOutputGreaterEqual;
}
}
else if(e.executionModes.depthMode == ExecutionModes::DepthLess)
{
for(SigParameter &sig : outputs)
{
if(sig.systemValue == ShaderBuiltin::DepthOutput)
sig.systemValue = ShaderBuiltin::DepthOutputLessEqual;
}
}
reflection.outputTopology = e.executionModes.outTopo;
if(e.executionModes.others.contains(rdcspv::ExecutionMode::OutputPoints))
reflection.outputTopology = Topology::PointList;
else if(e.executionModes.others.contains(rdcspv::ExecutionMode::OutputLinesEXT))
reflection.outputTopology = Topology::LineList;
else if(e.executionModes.others.contains(rdcspv::ExecutionMode::OutputTrianglesEXT))
reflection.outputTopology = Topology::TriangleList;
}
for(auto it = extSets.begin(); it != extSets.end(); it++)
if(it->second == "NonSemantic.DebugPrintf")
patchData.usesPrintf = true;
// sort system value semantics to the start of the list
struct sig_param_sort
{
sig_param_sort(const rdcarray<SigParameter> &arr) : sigArray(arr) {}
const rdcarray<SigParameter> &sigArray;
bool operator()(const size_t idxA, const size_t idxB)
{
const SigParameter &a = sigArray[idxA];
const SigParameter &b = sigArray[idxB];
if(a.systemValue == b.systemValue)
{
if(a.regIndex != b.regIndex)
return a.regIndex < b.regIndex;
if(a.regChannelMask != b.regChannelMask)
return a.regChannelMask < b.regChannelMask;
return a.varName < b.varName;
}
if(a.systemValue == ShaderBuiltin::Undefined)
return false;
if(b.systemValue == ShaderBuiltin::Undefined)
return true;
return a.systemValue < b.systemValue;
}
};
rdcarray<size_t> indices;
{
indices.resize(inputs.size());
for(size_t i = 0; i < inputs.size(); i++)
indices[i] = i;
std::sort(indices.begin(), indices.end(), sig_param_sort(inputs));
reflection.inputSignature.reserve(inputs.size());
for(size_t i = 0; i < inputs.size(); i++)
reflection.inputSignature.push_back(inputs[indices[i]]);
rdcarray<SPIRVInterfaceAccess> inPatch = patchData.inputs;
for(size_t i = 0; i < inputs.size(); i++)
patchData.inputs[i] = inPatch[indices[i]];
}
{
indices.resize(outputs.size());
for(size_t i = 0; i < outputs.size(); i++)
indices[i] = i;
std::sort(indices.begin(), indices.end(), sig_param_sort(outputs));
reflection.outputSignature.reserve(outputs.size());
for(size_t i = 0; i < outputs.size(); i++)
reflection.outputSignature.push_back(outputs[indices[i]]);
rdcarray<SPIRVInterfaceAccess> outPatch = patchData.outputs;
for(size_t i = 0; i < outputs.size(); i++)
patchData.outputs[i] = outPatch[indices[i]];
}
size_t numInputs = 16;
for(size_t i = 0; i < reflection.inputSignature.size(); i++)
if(reflection.inputSignature[i].systemValue == ShaderBuiltin::Undefined)
numInputs = RDCMAX(numInputs, (size_t)reflection.inputSignature[i].regIndex + 1);
for(sortedcblock &cb : cblocks)
{
// sort the variables within each block because we want them in offset order but they don't have
// to be declared in offset order in the SPIR-V.
std::sort(cb.bindres.variables.begin(), cb.bindres.variables.end());
}
std::sort(cblocks.begin(), cblocks.end());
std::sort(samplers.begin(), samplers.end());
std::sort(roresources.begin(), roresources.end());
std::sort(rwresources.begin(), rwresources.end());
reflection.constantBlocks.resize(cblocks.size());
reflection.samplers.resize(samplers.size());
reflection.readOnlyResources.resize(roresources.size());
reflection.readWriteResources.resize(rwresources.size());
for(size_t i = 0; i < cblocks.size(); i++)
{
patchData.cblockInterface.push_back(cblocks[i].id);
reflection.constantBlocks[i] = cblocks[i].bindres;
// fix up any bind points marked with INVALID_BIND. They were sorted to the end
// but from here on we want to just be able to index with the bind point
// without any special casing.
if(reflection.constantBlocks[i].fixedBindNumber == INVALID_BIND)
reflection.constantBlocks[i].fixedBindNumber = 0;
}
for(size_t i = 0; i < samplers.size(); i++)
{
patchData.samplerInterface.push_back(samplers[i].id);
reflection.samplers[i] = samplers[i].bindres;
// fix up any bind points marked with INVALID_BIND. They were sorted to the end
// but from here on we want to just be able to index with the bind point
// without any special casing.
if(reflection.samplers[i].fixedBindNumber == INVALID_BIND)
reflection.samplers[i].fixedBindNumber = 0;
}
for(size_t i = 0; i < roresources.size(); i++)
{
patchData.roInterface.push_back(roresources[i].id);
reflection.readOnlyResources[i] = roresources[i].bindres;
// fix up any bind points marked with INVALID_BIND. They were sorted to the end
// but from here on we want to just be able to index with the bind point
// without any special casing.
if(reflection.readOnlyResources[i].fixedBindNumber == INVALID_BIND)
reflection.readOnlyResources[i].fixedBindNumber = 0;
}
for(size_t i = 0; i < rwresources.size(); i++)
{
patchData.rwInterface.push_back(rwresources[i].id);
reflection.readWriteResources[i] = rwresources[i].bindres;
// fix up any bind points marked with INVALID_BIND. They were sorted to the end
// but from here on we want to just be able to index with the bind point
// without any special casing.
if(reflection.readWriteResources[i].fixedBindNumber == INVALID_BIND)
reflection.readWriteResources[i].fixedBindNumber = 0;
}
// go through each pointer type and populate it. This may generate more pointer types so we repeat
// this until it converges. This is a bit redundant but not the end of the world
size_t numPointerTypes = 0;
do
{
numPointerTypes = pointerTypes.size();
rdcarray<Id> ids;
for(auto it = pointerTypes.begin(); it != pointerTypes.end(); ++it)
ids.push_back(it->first);
// generate a variable for each of the types. This will add to pointerTypes if it finds
// something new
for(Id id : ids)
{
ShaderConstant dummy;
MakeConstantBlockVariable(dummy, pointerTypes, dataTypes[id].pointerType.storage,
dataTypes[id], rdcstr(), Decorations(), specInfo);
}
// continue if we generated some more
} while(pointerTypes.size() != numPointerTypes);
// populate the pointer types
reflection.pointerTypes.reserve(pointerTypes.size());
for(auto it = pointerTypes.begin(); it != pointerTypes.end(); ++it)
{
ShaderConstant dummy;
MakeConstantBlockVariable(dummy, pointerTypes, dataTypes[it->first].pointerType.storage,
dataTypes[it->first], rdcstr(), Decorations(), specInfo);
if(it->second >= reflection.pointerTypes.size())
reflection.pointerTypes.resize(it->second + 1);
reflection.pointerTypes[it->second] = dummy.type;
}
// shouldn't have changed in the above loop!
RDCASSERT(pointerTypes.size() == numPointerTypes);
}