blob: b1411fe6e743bba765cd605849ee62fc8fec7264 [file] [log] [blame]
//
// Copyright 2019 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// Utilities to map shader interface variables to Vulkan mappings, and transform the SPIR-V
// accordingly.
//
#include "libANGLE/renderer/vulkan/spv_utils.h"
#include <array>
#include <cctype>
#include <numeric>
#include "common/FixedVector.h"
#include "common/spirv/spirv_instruction_builder_autogen.h"
#include "common/spirv/spirv_instruction_parser_autogen.h"
#include "common/string_utils.h"
#include "common/utilities.h"
#include "libANGLE/Caps.h"
#include "libANGLE/ProgramLinkedResources.h"
#include "libANGLE/renderer/vulkan/ShaderInterfaceVariableInfoMap.h"
#include "libANGLE/renderer/vulkan/vk_cache_utils.h"
#include "libANGLE/trace.h"
namespace spirv = angle::spirv;
namespace rx
{
namespace
{
template <size_t N>
constexpr size_t ConstStrLen(const char (&)[N])
{
static_assert(N > 0, "C++ shouldn't allow N to be zero");
// The length of a string defined as a char array is the size of the array minus 1 (the
// terminating '\0').
return N - 1;
}
// Test if there are non-zero indices in the uniform name, returning false in that case. This
// happens for multi-dimensional arrays, where a uniform is created for every possible index of the
// array (except for the innermost dimension). When assigning decorations (set/binding/etc), only
// the indices corresponding to the first element of the array should be specified. This function
// is used to skip the other indices.
bool UniformNameIsIndexZero(const std::string &name)
{
size_t lastBracketClose = 0;
while (true)
{
size_t openBracket = name.find('[', lastBracketClose);
if (openBracket == std::string::npos)
{
break;
}
size_t closeBracket = name.find(']', openBracket);
// If the index between the brackets is not zero, ignore this uniform.
if (name.substr(openBracket + 1, closeBracket - openBracket - 1) != "0")
{
return false;
}
lastBracketClose = closeBracket;
}
return true;
}
template <typename OutputIter, typename ImplicitIter>
uint32_t CountExplicitOutputs(OutputIter outputsBegin,
OutputIter outputsEnd,
ImplicitIter implicitsBegin,
ImplicitIter implicitsEnd)
{
auto reduce = [implicitsBegin, implicitsEnd](uint32_t count, const gl::ProgramOutput &var) {
bool isExplicit = std::find(implicitsBegin, implicitsEnd, var.name) == implicitsEnd;
return count + isExplicit;
};
return std::accumulate(outputsBegin, outputsEnd, 0, reduce);
}
ShaderInterfaceVariableInfo *AddResourceInfoToAllStages(ShaderInterfaceVariableInfoMap *infoMap,
gl::ShaderType shaderType,
uint32_t varId,
uint32_t descriptorSet,
uint32_t binding)
{
gl::ShaderBitSet allStages;
allStages.set();
ShaderInterfaceVariableInfo &info = infoMap->add(shaderType, varId);
info.descriptorSet = descriptorSet;
info.binding = binding;
info.activeStages = allStages;
return &info;
}
ShaderInterfaceVariableInfo *AddResourceInfo(ShaderInterfaceVariableInfoMap *infoMap,
gl::ShaderBitSet stages,
gl::ShaderType shaderType,
uint32_t varId,
uint32_t descriptorSet,
uint32_t binding)
{
ShaderInterfaceVariableInfo &info = infoMap->add(shaderType, varId);
info.descriptorSet = descriptorSet;
info.binding = binding;
info.activeStages = stages;
return &info;
}
// Add location information for an in/out variable.
ShaderInterfaceVariableInfo *AddLocationInfo(ShaderInterfaceVariableInfoMap *infoMap,
gl::ShaderType shaderType,
uint32_t varId,
uint32_t location,
uint32_t component,
uint8_t attributeComponentCount,
uint8_t attributeLocationCount)
{
// The info map for this id may or may not exist already. This function merges the
// location/component information.
ShaderInterfaceVariableInfo &info = infoMap->addOrGet(shaderType, varId);
ASSERT(info.descriptorSet == ShaderInterfaceVariableInfo::kInvalid);
ASSERT(info.binding == ShaderInterfaceVariableInfo::kInvalid);
if (info.location != ShaderInterfaceVariableInfo::kInvalid)
{
ASSERT(info.location == location);
ASSERT(info.component == component);
}
ASSERT(info.component == ShaderInterfaceVariableInfo::kInvalid);
info.location = location;
info.component = component;
info.activeStages.set(shaderType);
info.attributeComponentCount = attributeComponentCount;
info.attributeLocationCount = attributeLocationCount;
return &info;
}
// Add location information for an in/out variable
void AddVaryingLocationInfo(ShaderInterfaceVariableInfoMap *infoMap,
const gl::VaryingInShaderRef &ref,
const uint32_t location,
const uint32_t component)
{
// Skip statically-unused varyings, they are already pruned by the translator
if (ref.varying->id != 0)
{
AddLocationInfo(infoMap, ref.stage, ref.varying->id, location, component, 0, 0);
}
}
// Modify an existing out variable and add transform feedback information.
void SetXfbInfo(ShaderInterfaceVariableInfoMap *infoMap,
gl::ShaderType shaderType,
uint32_t varId,
int fieldIndex,
uint32_t xfbBuffer,
uint32_t xfbOffset,
uint32_t xfbStride,
uint32_t arraySize,
uint32_t columnCount,
uint32_t rowCount,
uint32_t arrayIndex,
GLenum componentType)
{
XFBInterfaceVariableInfo *info = infoMap->getXFBMutable(shaderType, varId);
ASSERT(info != nullptr);
ShaderInterfaceVariableXfbInfo *xfb = &info->xfb;
if (fieldIndex >= 0)
{
if (info->fieldXfb.size() <= static_cast<size_t>(fieldIndex))
{
info->fieldXfb.resize(fieldIndex + 1);
}
xfb = &info->fieldXfb[fieldIndex];
}
ASSERT(xfb->pod.buffer == ShaderInterfaceVariableXfbInfo::kInvalid);
ASSERT(xfb->pod.offset == ShaderInterfaceVariableXfbInfo::kInvalid);
ASSERT(xfb->pod.stride == ShaderInterfaceVariableXfbInfo::kInvalid);
if (arrayIndex != ShaderInterfaceVariableXfbInfo::kInvalid)
{
xfb->arrayElements.emplace_back();
xfb = &xfb->arrayElements.back();
}
xfb->pod.buffer = xfbBuffer;
xfb->pod.offset = xfbOffset;
xfb->pod.stride = xfbStride;
xfb->pod.arraySize = arraySize;
xfb->pod.columnCount = columnCount;
xfb->pod.rowCount = rowCount;
xfb->pod.arrayIndex = arrayIndex;
xfb->pod.componentType = componentType;
}
void AssignTransformFeedbackEmulationBindings(gl::ShaderType shaderType,
const gl::ProgramExecutable &programExecutable,
bool isTransformFeedbackStage,
SpvProgramInterfaceInfo *programInterfaceInfo,
ShaderInterfaceVariableInfoMap *variableInfoMapOut)
{
size_t bufferCount = 0;
if (isTransformFeedbackStage)
{
ASSERT(!programExecutable.getLinkedTransformFeedbackVaryings().empty());
const bool isInterleaved =
programExecutable.getTransformFeedbackBufferMode() == GL_INTERLEAVED_ATTRIBS;
bufferCount =
isInterleaved ? 1 : programExecutable.getLinkedTransformFeedbackVaryings().size();
}
// Add entries for the transform feedback buffers to the info map, so they can have correct
// set/binding.
for (uint32_t bufferIndex = 0; bufferIndex < bufferCount; ++bufferIndex)
{
AddResourceInfo(variableInfoMapOut, gl::ShaderBitSet().set(shaderType), shaderType,
SpvGetXfbBufferBlockId(bufferIndex),
ToUnderlying(DescriptorSetIndex::UniformsAndXfb),
programInterfaceInfo->currentUniformBindingIndex);
++programInterfaceInfo->currentUniformBindingIndex;
}
// Remove inactive transform feedback buffers.
for (uint32_t bufferIndex = static_cast<uint32_t>(bufferCount);
bufferIndex < gl::IMPLEMENTATION_MAX_TRANSFORM_FEEDBACK_BUFFERS; ++bufferIndex)
{
variableInfoMapOut->add(shaderType, SpvGetXfbBufferBlockId(bufferIndex));
}
}
bool IsFirstRegisterOfVarying(const gl::PackedVaryingRegister &varyingReg,
bool allowFields,
uint32_t expectArrayIndex)
{
const gl::PackedVarying &varying = *varyingReg.packedVarying;
// In Vulkan GLSL, struct fields are not allowed to have location assignments. The varying of a
// struct type is thus given a location equal to the one assigned to its first field. With I/O
// blocks, transform feedback can capture an arbitrary field. In that case, we need to look at
// every field, not just the first one.
if (!allowFields && varying.isStructField() &&
(varying.fieldIndex > 0 || varying.secondaryFieldIndex > 0))
{
return false;
}
// Similarly, assign array varying locations to the assigned location of the first element.
// Transform feedback may capture array elements, so if a specific non-zero element is
// requested, accept that only.
if (varyingReg.varyingArrayIndex != expectArrayIndex ||
(varying.arrayIndex != GL_INVALID_INDEX && varying.arrayIndex != expectArrayIndex))
{
return false;
}
// Similarly, assign matrix varying locations to the assigned location of the first row.
if (varyingReg.varyingRowIndex != 0)
{
return false;
}
return true;
}
void AssignAttributeLocations(const gl::ProgramExecutable &programExecutable,
gl::ShaderType shaderType,
ShaderInterfaceVariableInfoMap *variableInfoMapOut)
{
const std::array<std::string, 2> implicitInputs = {"gl_VertexID", "gl_InstanceID"};
gl::AttributesMask isLocationAssigned;
bool hasAliasingAttributes = false;
// Assign attribute locations for the vertex shader.
for (const gl::ProgramInput &attribute : programExecutable.getProgramInputs())
{
ASSERT(attribute.isActive());
if (std::find(implicitInputs.begin(), implicitInputs.end(), attribute.name) !=
implicitInputs.end())
{
continue;
}
const uint8_t colCount = static_cast<uint8_t>(gl::VariableColumnCount(attribute.getType()));
const uint8_t rowCount = static_cast<uint8_t>(gl::VariableRowCount(attribute.getType()));
const bool isMatrix = colCount > 1 && rowCount > 1;
const uint8_t componentCount = isMatrix ? rowCount : colCount;
const uint8_t locationCount = isMatrix ? colCount : rowCount;
AddLocationInfo(variableInfoMapOut, shaderType, attribute.getId(), attribute.getLocation(),
ShaderInterfaceVariableInfo::kInvalid, componentCount, locationCount);
// Detect if there are aliasing attributes.
if (!hasAliasingAttributes &&
programExecutable.getLinkedShaderVersion(gl::ShaderType::Vertex) == 100)
{
for (uint8_t offset = 0; offset < locationCount; ++offset)
{
uint32_t location = attribute.getLocation() + offset;
// If there's aliasing, no need for futher processing.
if (isLocationAssigned.test(location))
{
hasAliasingAttributes = true;
break;
}
isLocationAssigned.set(location);
}
}
}
if (hasAliasingAttributes)
{
variableInfoMapOut->setHasAliasingAttributes();
}
}
void AssignSecondaryOutputLocations(const gl::ProgramExecutable &programExecutable,
ShaderInterfaceVariableInfoMap *variableInfoMapOut)
{
const auto &secondaryOutputLocations = programExecutable.getSecondaryOutputLocations();
const auto &outputVariables = programExecutable.getOutputVariables();
// Handle EXT_blend_func_extended secondary outputs (ones with index=1)
for (const gl::VariableLocation &outputLocation : secondaryOutputLocations)
{
if (outputLocation.arrayIndex == 0 && outputLocation.used() && !outputLocation.ignored)
{
const gl::ProgramOutput &outputVar = outputVariables[outputLocation.index];
uint32_t location = 0;
if (outputVar.pod.location != -1)
{
location = outputVar.pod.location;
}
ShaderInterfaceVariableInfo *info =
AddLocationInfo(variableInfoMapOut, gl::ShaderType::Fragment, outputVar.pod.id,
location, ShaderInterfaceVariableInfo::kInvalid, 0, 0);
// Index 1 is used to specify that the color be used as the second color input to
// the blend equation
info->index = 1;
ASSERT(!outputVar.isArray() || outputVar.getOutermostArraySize() == 1);
info->isArray = outputVar.isArray();
}
}
// Handle secondary outputs for ESSL version less than 3.00
if (programExecutable.hasLinkedShaderStage(gl::ShaderType::Fragment) &&
programExecutable.getLinkedShaderVersion(gl::ShaderType::Fragment) == 100)
{
const std::array<std::string, 2> secondaryFrag = {"gl_SecondaryFragColorEXT",
"gl_SecondaryFragDataEXT"};
for (const gl::ProgramOutput &outputVar : outputVariables)
{
if (std::find(secondaryFrag.begin(), secondaryFrag.end(), outputVar.name) !=
secondaryFrag.end())
{
ShaderInterfaceVariableInfo *info =
AddLocationInfo(variableInfoMapOut, gl::ShaderType::Fragment, outputVar.pod.id,
0, ShaderInterfaceVariableInfo::kInvalid, 0, 0);
info->index = 1;
ASSERT(!outputVar.isArray() || outputVar.getOutermostArraySize() == 1);
info->isArray = outputVar.isArray();
// SecondaryFragColor and SecondaryFragData cannot be present simultaneously.
break;
}
}
}
}
void AssignOutputLocations(const gl::ProgramExecutable &programExecutable,
const gl::ShaderType shaderType,
ShaderInterfaceVariableInfoMap *variableInfoMapOut)
{
// Assign output locations for the fragment shader.
ASSERT(shaderType == gl::ShaderType::Fragment);
const auto &outputLocations = programExecutable.getOutputLocations();
const auto &outputVariables = programExecutable.getOutputVariables();
const std::array<std::string, 3> implicitOutputs = {"gl_FragDepth", "gl_SampleMask",
"gl_FragStencilRefARB"};
for (const gl::VariableLocation &outputLocation : outputLocations)
{
if (outputLocation.arrayIndex == 0 && outputLocation.used() && !outputLocation.ignored)
{
const gl::ProgramOutput &outputVar = outputVariables[outputLocation.index];
uint32_t location = 0;
if (outputVar.pod.location != -1)
{
location = outputVar.pod.location;
}
else if (std::find(implicitOutputs.begin(), implicitOutputs.end(), outputVar.name) ==
implicitOutputs.end())
{
// If there is only one output, it is allowed not to have a location qualifier, in
// which case it defaults to 0. GLSL ES 3.00 spec, section 4.3.8.2.
ASSERT(CountExplicitOutputs(outputVariables.begin(), outputVariables.end(),
implicitOutputs.begin(), implicitOutputs.end()) == 1);
}
AddLocationInfo(variableInfoMapOut, shaderType, outputVar.pod.id, location,
ShaderInterfaceVariableInfo::kInvalid, 0, 0);
}
}
// Handle outputs for ESSL version less than 3.00
if (programExecutable.hasLinkedShaderStage(gl::ShaderType::Fragment) &&
programExecutable.getLinkedShaderVersion(gl::ShaderType::Fragment) == 100)
{
for (const gl::ProgramOutput &outputVar : outputVariables)
{
if (outputVar.name == "gl_FragColor" || outputVar.name == "gl_FragData")
{
AddLocationInfo(variableInfoMapOut, gl::ShaderType::Fragment, outputVar.pod.id, 0,
ShaderInterfaceVariableInfo::kInvalid, 0, 0);
}
}
}
AssignSecondaryOutputLocations(programExecutable, variableInfoMapOut);
}
void AssignVaryingLocations(const SpvSourceOptions &options,
const gl::VaryingPacking &varyingPacking,
const gl::ShaderType shaderType,
const gl::ShaderType frontShaderType,
SpvProgramInterfaceInfo *programInterfaceInfo,
ShaderInterfaceVariableInfoMap *variableInfoMapOut)
{
uint32_t locationsUsedForEmulation = programInterfaceInfo->locationsUsedForXfbExtension;
// Assign varying locations.
for (const gl::PackedVaryingRegister &varyingReg : varyingPacking.getRegisterList())
{
if (!IsFirstRegisterOfVarying(varyingReg, false, 0))
{
continue;
}
const gl::PackedVarying &varying = *varyingReg.packedVarying;
uint32_t location = varyingReg.registerRow + locationsUsedForEmulation;
uint32_t component = ShaderInterfaceVariableInfo::kInvalid;
if (varyingReg.registerColumn > 0)
{
ASSERT(!varying.varying().isStruct());
ASSERT(!gl::IsMatrixType(varying.varying().type));
component = varyingReg.registerColumn;
}
if (varying.frontVarying.varying && (varying.frontVarying.stage == shaderType))
{
AddVaryingLocationInfo(variableInfoMapOut, varying.frontVarying, location, component);
}
if (varying.backVarying.varying && (varying.backVarying.stage == shaderType))
{
AddVaryingLocationInfo(variableInfoMapOut, varying.backVarying, location, component);
}
}
// Add an entry for inactive varyings.
const gl::ShaderMap<std::vector<uint32_t>> &inactiveVaryingIds =
varyingPacking.getInactiveVaryingIds();
for (const uint32_t varyingId : inactiveVaryingIds[shaderType])
{
// If id is already in the map, it will automatically have marked all other stages inactive.
if (variableInfoMapOut->hasVariable(shaderType, varyingId))
{
continue;
}
// Otherwise, add an entry for it with all locations inactive.
ShaderInterfaceVariableInfo &info = variableInfoMapOut->addOrGet(shaderType, varyingId);
ASSERT(info.location == ShaderInterfaceVariableInfo::kInvalid);
}
// Add an entry for gl_PerVertex, for use with transform feedback capture of built-ins.
ShaderInterfaceVariableInfo &info =
variableInfoMapOut->addOrGet(shaderType, sh::vk::spirv::kIdOutputPerVertexBlock);
info.activeStages.set(shaderType);
}
// Calculates XFB layout qualifier arguments for each transform feedback varying. Stores calculated
// values for the SPIR-V transformation.
void AssignTransformFeedbackQualifiers(const gl::ProgramExecutable &programExecutable,
const gl::VaryingPacking &varyingPacking,
const gl::ShaderType shaderType,
bool usesExtension,
ShaderInterfaceVariableInfoMap *variableInfoMapOut)
{
const std::vector<gl::TransformFeedbackVarying> &tfVaryings =
programExecutable.getLinkedTransformFeedbackVaryings();
const std::vector<GLsizei> &varyingStrides = programExecutable.getTransformFeedbackStrides();
const bool isInterleaved =
programExecutable.getTransformFeedbackBufferMode() == GL_INTERLEAVED_ATTRIBS;
uint32_t currentOffset = 0;
uint32_t currentStride = 0;
uint32_t bufferIndex = 0;
for (uint32_t varyingIndex = 0; varyingIndex < tfVaryings.size(); ++varyingIndex)
{
if (isInterleaved)
{
bufferIndex = 0;
if (varyingIndex > 0)
{
const gl::TransformFeedbackVarying &prev = tfVaryings[varyingIndex - 1];
currentOffset += prev.size() * gl::VariableExternalSize(prev.type);
}
currentStride = varyingStrides[0];
}
else
{
bufferIndex = varyingIndex;
currentOffset = 0;
currentStride = varyingStrides[varyingIndex];
}
const gl::TransformFeedbackVarying &tfVarying = tfVaryings[varyingIndex];
const gl::UniformTypeInfo &uniformInfo = gl::GetUniformTypeInfo(tfVarying.type);
const uint32_t varyingSize =
tfVarying.isArray() ? tfVarying.size() : ShaderInterfaceVariableXfbInfo::kInvalid;
if (tfVarying.isBuiltIn())
{
if (usesExtension && tfVarying.name == "gl_Position")
{
// With the extension, gl_Position is captured via a special varying.
SetXfbInfo(variableInfoMapOut, shaderType, sh::vk::spirv::kIdXfbExtensionPosition,
-1, bufferIndex, currentOffset, currentStride, varyingSize,
uniformInfo.columnCount, uniformInfo.rowCount,
ShaderInterfaceVariableXfbInfo::kInvalid, uniformInfo.componentType);
}
else
{
// gl_PerVertex is always defined as:
//
// Field 0: gl_Position
// Field 1: gl_PointSize
// Field 2: gl_ClipDistance
// Field 3: gl_CullDistance
//
// With the extension, all fields except gl_Position can be captured directly by
// decorating gl_PerVertex fields.
int fieldIndex = -1;
constexpr int kPerVertexMemberCount = 4;
constexpr std::array<const char *, kPerVertexMemberCount> kPerVertexMembers = {
"gl_Position",
"gl_PointSize",
"gl_ClipDistance",
"gl_CullDistance",
};
for (int index = 0; index < kPerVertexMemberCount; ++index)
{
if (tfVarying.name == kPerVertexMembers[index])
{
fieldIndex = index;
break;
}
}
ASSERT(fieldIndex != -1);
ASSERT(!usesExtension || fieldIndex > 0);
SetXfbInfo(variableInfoMapOut, shaderType, sh::vk::spirv::kIdOutputPerVertexBlock,
fieldIndex, bufferIndex, currentOffset, currentStride, varyingSize,
uniformInfo.columnCount, uniformInfo.rowCount,
ShaderInterfaceVariableXfbInfo::kInvalid, uniformInfo.componentType);
}
continue;
}
// Note: capturing individual array elements using the Vulkan transform feedback extension
// is currently not supported due to limitations in the extension.
// ANGLE supports capturing the whole array.
// http://anglebug.com/4140
if (usesExtension && tfVarying.isArray() && tfVarying.arrayIndex != GL_INVALID_INDEX)
{
continue;
}
// Find the varying with this name. If a struct is captured, we would be iterating over its
// fields. This is done when the first field of the struct is visited. For I/O blocks on
// the other hand, we need to decorate the exact member that is captured (as whole-block
// capture is not supported).
const gl::PackedVarying *originalVarying = nullptr;
for (const gl::PackedVaryingRegister &varyingReg : varyingPacking.getRegisterList())
{
const uint32_t arrayIndex =
tfVarying.arrayIndex == GL_INVALID_INDEX ? 0 : tfVarying.arrayIndex;
if (!IsFirstRegisterOfVarying(varyingReg, tfVarying.isShaderIOBlock, arrayIndex))
{
continue;
}
const gl::PackedVarying *varying = varyingReg.packedVarying;
if (tfVarying.isShaderIOBlock)
{
if (varying->frontVarying.parentStructName == tfVarying.structOrBlockName)
{
size_t pos = tfVarying.name.find_first_of(".");
std::string fieldName =
pos == std::string::npos ? tfVarying.name : tfVarying.name.substr(pos + 1);
if (fieldName == varying->frontVarying.varying->name.c_str())
{
originalVarying = varying;
break;
}
}
}
else if (varying->frontVarying.varying->name == tfVarying.name)
{
originalVarying = varying;
break;
}
}
if (originalVarying)
{
const int fieldIndex = tfVarying.isShaderIOBlock ? originalVarying->fieldIndex : -1;
const uint32_t arrayIndex = tfVarying.arrayIndex == GL_INVALID_INDEX
? ShaderInterfaceVariableXfbInfo::kInvalid
: tfVarying.arrayIndex;
// Set xfb info for this varying. AssignVaryingLocations should have already added
// location information for these varyings.
SetXfbInfo(variableInfoMapOut, shaderType, originalVarying->frontVarying.varying->id,
fieldIndex, bufferIndex, currentOffset, currentStride, varyingSize,
uniformInfo.columnCount, uniformInfo.rowCount, arrayIndex,
uniformInfo.componentType);
}
}
}
void AssignUniformBindings(const SpvSourceOptions &options,
const gl::ProgramExecutable &programExecutable,
SpvProgramInterfaceInfo *programInterfaceInfo,
ShaderInterfaceVariableInfoMap *variableInfoMapOut)
{
for (const gl::ShaderType shaderType : programExecutable.getLinkedShaderStages())
{
AddResourceInfo(variableInfoMapOut, gl::ShaderBitSet().set(shaderType), shaderType,
sh::vk::spirv::kIdDefaultUniformsBlock,
ToUnderlying(DescriptorSetIndex::UniformsAndXfb),
programInterfaceInfo->currentUniformBindingIndex);
++programInterfaceInfo->currentUniformBindingIndex;
// Assign binding to the driver uniforms block
AddResourceInfoToAllStages(variableInfoMapOut, shaderType,
sh::vk::spirv::kIdDriverUniformsBlock,
ToUnderlying(DescriptorSetIndex::Internal), 0);
}
}
void AssignInputAttachmentBindings(const SpvSourceOptions &options,
const gl::ProgramExecutable &programExecutable,
SpvProgramInterfaceInfo *programInterfaceInfo,
ShaderInterfaceVariableInfoMap *variableInfoMapOut)
{
if (!programExecutable.hasLinkedShaderStage(gl::ShaderType::Fragment) ||
!programExecutable.usesFramebufferFetch())
{
return;
}
const uint32_t baseInputAttachmentBindingIndex =
programInterfaceInfo->currentShaderResourceBindingIndex;
const gl::ShaderBitSet activeShaders{gl::ShaderType::Fragment};
static_assert(gl::IMPLEMENTATION_MAX_DRAW_BUFFERS <= 8,
"sh::vk::spirv::ReservedIds supports max 8 draw buffers");
for (size_t index : programExecutable.getFragmentInoutIndices())
{
const uint32_t inputAttachmentBindingIndex =
baseInputAttachmentBindingIndex + static_cast<uint32_t>(index);
AddResourceInfo(variableInfoMapOut, activeShaders, gl::ShaderType::Fragment,
sh::vk::spirv::kIdInputAttachment0 + static_cast<uint32_t>(index),
ToUnderlying(DescriptorSetIndex::ShaderResource),
inputAttachmentBindingIndex);
}
// For input attachment uniform, the descriptor set binding indices are allocated as much as
// the maximum draw buffers.
programInterfaceInfo->currentShaderResourceBindingIndex += gl::IMPLEMENTATION_MAX_DRAW_BUFFERS;
}
void AssignInterfaceBlockBindings(const SpvSourceOptions &options,
const gl::ProgramExecutable &programExecutable,
const std::vector<gl::InterfaceBlock> &blocks,
SpvProgramInterfaceInfo *programInterfaceInfo,
ShaderInterfaceVariableInfoMap *variableInfoMapOut)
{
for (uint32_t blockIndex = 0; blockIndex < blocks.size(); ++blockIndex)
{
const gl::InterfaceBlock &block = blocks[blockIndex];
// TODO: http://anglebug.com/4523: All blocks should be active
const gl::ShaderBitSet activeShaders =
programExecutable.getLinkedShaderStages() & block.activeShaders();
if (activeShaders.none())
{
continue;
}
const bool isIndexZero = !block.pod.isArray || block.pod.arrayElement == 0;
if (!isIndexZero)
{
continue;
}
variableInfoMapOut->addResource(activeShaders, block.getIds(),
ToUnderlying(DescriptorSetIndex::ShaderResource),
programInterfaceInfo->currentShaderResourceBindingIndex++);
}
}
void AssignAtomicCounterBufferBindings(const SpvSourceOptions &options,
const gl::ProgramExecutable &programExecutable,
SpvProgramInterfaceInfo *programInterfaceInfo,
ShaderInterfaceVariableInfoMap *variableInfoMapOut)
{
const std::vector<gl::AtomicCounterBuffer> &buffers =
programExecutable.getAtomicCounterBuffers();
if (buffers.size() == 0)
{
return;
}
const gl::ShaderBitSet activeShaders = programExecutable.getLinkedShaderStages();
ASSERT(activeShaders.any());
gl::ShaderMap<uint32_t> ids = {};
for (const gl::ShaderType shaderType : activeShaders)
{
ids[shaderType] = sh::vk::spirv::kIdAtomicCounterBlock;
}
variableInfoMapOut->addResource(activeShaders, ids,
ToUnderlying(DescriptorSetIndex::ShaderResource),
programInterfaceInfo->currentShaderResourceBindingIndex++);
}
void AssignImageBindings(const SpvSourceOptions &options,
const gl::ProgramExecutable &programExecutable,
SpvProgramInterfaceInfo *programInterfaceInfo,
ShaderInterfaceVariableInfoMap *variableInfoMapOut)
{
const std::vector<gl::LinkedUniform> &uniforms = programExecutable.getUniforms();
const gl::RangeUI &imageUniformRange = programExecutable.getImageUniformRange();
for (unsigned int uniformIndex : imageUniformRange)
{
const gl::LinkedUniform &imageUniform = uniforms[uniformIndex];
// TODO: http://anglebug.com/4523: All uniforms should be active
const gl::ShaderBitSet activeShaders =
programExecutable.getLinkedShaderStages() & imageUniform.activeShaders();
if (activeShaders.none())
{
continue;
}
const bool isIndexZero =
UniformNameIsIndexZero(programExecutable.getUniformNameByIndex(uniformIndex));
if (!isIndexZero)
{
continue;
}
variableInfoMapOut->addResource(activeShaders, imageUniform.getIds(),
ToUnderlying(DescriptorSetIndex::ShaderResource),
programInterfaceInfo->currentShaderResourceBindingIndex++);
}
}
void AssignNonTextureBindings(const SpvSourceOptions &options,
const gl::ProgramExecutable &programExecutable,
SpvProgramInterfaceInfo *programInterfaceInfo,
ShaderInterfaceVariableInfoMap *variableInfoMapOut)
{
AssignInputAttachmentBindings(options, programExecutable, programInterfaceInfo,
variableInfoMapOut);
const std::vector<gl::InterfaceBlock> &uniformBlocks = programExecutable.getUniformBlocks();
AssignInterfaceBlockBindings(options, programExecutable, uniformBlocks, programInterfaceInfo,
variableInfoMapOut);
const std::vector<gl::InterfaceBlock> &storageBlocks =
programExecutable.getShaderStorageBlocks();
AssignInterfaceBlockBindings(options, programExecutable, storageBlocks, programInterfaceInfo,
variableInfoMapOut);
AssignAtomicCounterBufferBindings(options, programExecutable, programInterfaceInfo,
variableInfoMapOut);
AssignImageBindings(options, programExecutable, programInterfaceInfo, variableInfoMapOut);
}
void AssignTextureBindings(const SpvSourceOptions &options,
const gl::ProgramExecutable &programExecutable,
SpvProgramInterfaceInfo *programInterfaceInfo,
ShaderInterfaceVariableInfoMap *variableInfoMapOut)
{
// Assign textures to a descriptor set and binding.
const std::vector<gl::LinkedUniform> &uniforms = programExecutable.getUniforms();
const gl::RangeUI &samplerUniformRange = programExecutable.getSamplerUniformRange();
for (unsigned int uniformIndex : samplerUniformRange)
{
const gl::LinkedUniform &samplerUniform = uniforms[uniformIndex];
// TODO: http://anglebug.com/4523: All uniforms should be active
const gl::ShaderBitSet activeShaders =
programExecutable.getLinkedShaderStages() & samplerUniform.activeShaders();
if (activeShaders.none())
{
continue;
}
const bool isIndexZero =
UniformNameIsIndexZero(programExecutable.getUniformNameByIndex(uniformIndex));
if (!isIndexZero)
{
continue;
}
variableInfoMapOut->addResource(activeShaders, samplerUniform.getIds(),
ToUnderlying(DescriptorSetIndex::Texture),
programInterfaceInfo->currentTextureBindingIndex++);
}
}
bool IsNonSemanticInstruction(const uint32_t *instruction)
{
// To avoid parsing the numerous GLSL OpExtInst instructions, take a quick peek at the set and
// skip instructions that aren't non-semantic.
return instruction[3] == sh::vk::spirv::kIdNonSemanticInstructionSet;
}
// Base class for SPIR-V transformations.
class SpirvTransformerBase : angle::NonCopyable
{
public:
SpirvTransformerBase(const spirv::Blob &spirvBlobIn,
const ShaderInterfaceVariableInfoMap &variableInfoMap,
spirv::Blob *spirvBlobOut)
: mSpirvBlobIn(spirvBlobIn), mVariableInfoMap(variableInfoMap), mSpirvBlobOut(spirvBlobOut)
{
gl::ShaderBitSet allStages;
allStages.set();
mBuiltinVariableInfo.activeStages = allStages;
}
std::vector<const ShaderInterfaceVariableInfo *> &getVariableInfoByIdMap()
{
return mVariableInfoById;
}
static spirv::IdRef GetNewId(spirv::Blob *blob);
spirv::IdRef getNewId();
protected:
// Common utilities
void onTransformBegin();
const uint32_t *getCurrentInstruction(spv::Op *opCodeOut, uint32_t *wordCountOut) const;
void copyInstruction(const uint32_t *instruction, size_t wordCount);
// SPIR-V to transform:
const spirv::Blob &mSpirvBlobIn;
// Input shader variable info map:
const ShaderInterfaceVariableInfoMap &mVariableInfoMap;
// Transformed SPIR-V:
spirv::Blob *mSpirvBlobOut;
// Traversal state:
size_t mCurrentWord = 0;
bool mIsInFunctionSection = false;
// Transformation state:
// Shader variable info per id, if id is a shader variable.
std::vector<const ShaderInterfaceVariableInfo *> mVariableInfoById;
ShaderInterfaceVariableInfo mBuiltinVariableInfo;
};
void SpirvTransformerBase::onTransformBegin()
{
// The translator succeeded in outputting SPIR-V, so we assume it's valid.
ASSERT(mSpirvBlobIn.size() >= spirv::kHeaderIndexInstructions);
// Since SPIR-V comes from a local call to the translator, it necessarily has the same
// endianness as the running architecture, so no byte-swapping is necessary.
ASSERT(mSpirvBlobIn[spirv::kHeaderIndexMagic] == spv::MagicNumber);
// Make sure the transformer is not reused to avoid having to reinitialize it here.
ASSERT(mCurrentWord == 0);
ASSERT(mIsInFunctionSection == false);
// Make sure the spirv::Blob is not reused.
ASSERT(mSpirvBlobOut->empty());
// Copy the header to SPIR-V blob, we need that to be defined for SpirvTransformerBase::getNewId
// to work.
mSpirvBlobOut->assign(mSpirvBlobIn.begin(),
mSpirvBlobIn.begin() + spirv::kHeaderIndexInstructions);
mCurrentWord = spirv::kHeaderIndexInstructions;
}
const uint32_t *SpirvTransformerBase::getCurrentInstruction(spv::Op *opCodeOut,
uint32_t *wordCountOut) const
{
ASSERT(mCurrentWord < mSpirvBlobIn.size());
const uint32_t *instruction = &mSpirvBlobIn[mCurrentWord];
spirv::GetInstructionOpAndLength(instruction, opCodeOut, wordCountOut);
// The translator succeeded in outputting SPIR-V, so we assume it's valid.
ASSERT(mCurrentWord + *wordCountOut <= mSpirvBlobIn.size());
return instruction;
}
void SpirvTransformerBase::copyInstruction(const uint32_t *instruction, size_t wordCount)
{
mSpirvBlobOut->insert(mSpirvBlobOut->end(), instruction, instruction + wordCount);
}
spirv::IdRef SpirvTransformerBase::GetNewId(spirv::Blob *blob)
{
return spirv::IdRef((*blob)[spirv::kHeaderIndexIndexBound]++);
}
spirv::IdRef SpirvTransformerBase::getNewId()
{
return GetNewId(mSpirvBlobOut);
}
enum class TransformationState
{
Transformed,
Unchanged,
};
class SpirvNonSemanticInstructions final : angle::NonCopyable
{
public:
SpirvNonSemanticInstructions(bool isLastPass) : mIsLastPass(isLastPass) {}
// Returns whether this is a non-semantic instruction (as opposed to GLSL extended
// instructions). If it is non-semantic, returns the instruction code.
bool visitExtInst(const uint32_t *instruction, sh::vk::spirv::NonSemanticInstruction *instOut);
// Cleans up non-semantic instructions in the last SPIR-V pass.
TransformationState transformExtInst(const uint32_t *instruction);
bool hasSampleRateShading() const
{
return (mOverviewFlags & sh::vk::spirv::kOverviewHasSampleRateShadingMask) != 0;
}
bool hasSampleID() const
{
return (mOverviewFlags & sh::vk::spirv::kOverviewHasSampleIDMask) != 0;
}
bool hasOutputPerVertex() const
{
return (mOverviewFlags & sh::vk::spirv::kOverviewHasOutputPerVertexMask) != 0;
}
private:
// Whether this is the last SPIR-V pass. The non-semantics instructions are removed from the
// SPIR-V in the last pass.
const bool mIsLastPass;
uint32_t mOverviewFlags;
};
bool SpirvNonSemanticInstructions::visitExtInst(const uint32_t *instruction,
sh::vk::spirv::NonSemanticInstruction *instOut)
{
if (!IsNonSemanticInstruction(instruction))
{
return false;
}
spirv::IdResultType typeId;
spirv::IdResult id;
spirv::IdRef set;
spirv::LiteralExtInstInteger extInst;
spirv::ParseExtInst(instruction, &typeId, &id, &set, &extInst, nullptr);
ASSERT(set == sh::vk::spirv::kIdNonSemanticInstructionSet);
const uint32_t inst = extInst & sh::vk::spirv::kNonSemanticInstructionMask;
// Recover the additional overview flags placed in the instruction id.
if (inst == sh::vk::spirv::kNonSemanticOverview)
{
mOverviewFlags = extInst & ~sh::vk::spirv::kNonSemanticInstructionMask;
}
*instOut = static_cast<sh::vk::spirv::NonSemanticInstruction>(inst);
return true;
}
TransformationState SpirvNonSemanticInstructions::transformExtInst(const uint32_t *instruction)
{
return IsNonSemanticInstruction(instruction) && mIsLastPass ? TransformationState::Transformed
: TransformationState::Unchanged;
}
namespace ID
{
namespace
{
[[maybe_unused]] constexpr spirv::IdRef EntryPoint(sh::vk::spirv::kIdEntryPoint);
[[maybe_unused]] constexpr spirv::IdRef Void(sh::vk::spirv::kIdVoid);
[[maybe_unused]] constexpr spirv::IdRef Float(sh::vk::spirv::kIdFloat);
[[maybe_unused]] constexpr spirv::IdRef Vec2(sh::vk::spirv::kIdVec2);
[[maybe_unused]] constexpr spirv::IdRef Vec3(sh::vk::spirv::kIdVec3);
[[maybe_unused]] constexpr spirv::IdRef Vec4(sh::vk::spirv::kIdVec4);
[[maybe_unused]] constexpr spirv::IdRef Mat2(sh::vk::spirv::kIdMat2);
[[maybe_unused]] constexpr spirv::IdRef Mat3(sh::vk::spirv::kIdMat3);
[[maybe_unused]] constexpr spirv::IdRef Mat4(sh::vk::spirv::kIdMat4);
[[maybe_unused]] constexpr spirv::IdRef Int(sh::vk::spirv::kIdInt);
[[maybe_unused]] constexpr spirv::IdRef IVec4(sh::vk::spirv::kIdIVec4);
[[maybe_unused]] constexpr spirv::IdRef Uint(sh::vk::spirv::kIdUint);
[[maybe_unused]] constexpr spirv::IdRef IntZero(sh::vk::spirv::kIdIntZero);
[[maybe_unused]] constexpr spirv::IdRef IntOne(sh::vk::spirv::kIdIntOne);
[[maybe_unused]] constexpr spirv::IdRef IntTwo(sh::vk::spirv::kIdIntTwo);
[[maybe_unused]] constexpr spirv::IdRef IntThree(sh::vk::spirv::kIdIntThree);
[[maybe_unused]] constexpr spirv::IdRef IntInputTypePointer(sh::vk::spirv::kIdIntInputTypePointer);
[[maybe_unused]] constexpr spirv::IdRef Vec4OutputTypePointer(
sh::vk::spirv::kIdVec4OutputTypePointer);
[[maybe_unused]] constexpr spirv::IdRef IVec4FunctionTypePointer(
sh::vk::spirv::kIdIVec4FunctionTypePointer);
[[maybe_unused]] constexpr spirv::IdRef OutputPerVertexTypePointer(
sh::vk::spirv::kIdOutputPerVertexTypePointer);
[[maybe_unused]] constexpr spirv::IdRef TransformPositionFunction(
sh::vk::spirv::kIdTransformPositionFunction);
[[maybe_unused]] constexpr spirv::IdRef XfbEmulationGetOffsetsFunction(
sh::vk::spirv::kIdXfbEmulationGetOffsetsFunction);
[[maybe_unused]] constexpr spirv::IdRef SampleID(sh::vk::spirv::kIdSampleID);
[[maybe_unused]] constexpr spirv::IdRef InputPerVertexBlock(sh::vk::spirv::kIdInputPerVertexBlock);
[[maybe_unused]] constexpr spirv::IdRef OutputPerVertexBlock(
sh::vk::spirv::kIdOutputPerVertexBlock);
[[maybe_unused]] constexpr spirv::IdRef OutputPerVertexVar(sh::vk::spirv::kIdOutputPerVertexVar);
[[maybe_unused]] constexpr spirv::IdRef XfbExtensionPosition(
sh::vk::spirv::kIdXfbExtensionPosition);
[[maybe_unused]] constexpr spirv::IdRef XfbEmulationBufferBlockZero(
sh::vk::spirv::kIdXfbEmulationBufferBlockZero);
[[maybe_unused]] constexpr spirv::IdRef XfbEmulationBufferBlockOne(
sh::vk::spirv::kIdXfbEmulationBufferBlockOne);
[[maybe_unused]] constexpr spirv::IdRef XfbEmulationBufferBlockTwo(
sh::vk::spirv::kIdXfbEmulationBufferBlockTwo);
[[maybe_unused]] constexpr spirv::IdRef XfbEmulationBufferBlockThree(
sh::vk::spirv::kIdXfbEmulationBufferBlockThree);
} // anonymous namespace
} // namespace ID
// Helper class that trims input and output gl_PerVertex declarations to remove inactive builtins.
//
// gl_PerVertex is unique in that it's the only builtin of struct type. This struct is pruned
// by removing trailing inactive members. Note that intermediate stages, i.e. geometry and
// tessellation have two gl_PerVertex declarations, one for input and one for output.
class SpirvPerVertexTrimmer final : angle::NonCopyable
{
public:
SpirvPerVertexTrimmer(const SpvTransformOptions &options,
const ShaderInterfaceVariableInfoMap &variableInfoMap)
: mInputPerVertexMaxActiveMember{gl::PerVertexMember::Position},
mOutputPerVertexMaxActiveMember{gl::PerVertexMember::Position},
mInputPerVertexMaxActiveMemberIndex(0),
mOutputPerVertexMaxActiveMemberIndex(0)
{
const gl::PerVertexMemberBitSet inputPerVertexActiveMembers =
variableInfoMap.getInputPerVertexActiveMembers()[options.shaderType];
const gl::PerVertexMemberBitSet outputPerVertexActiveMembers =
variableInfoMap.getOutputPerVertexActiveMembers()[options.shaderType];
// Currently, this transformation does not trim inactive members in between two active
// members.
if (inputPerVertexActiveMembers.any())
{
mInputPerVertexMaxActiveMember = inputPerVertexActiveMembers.last();
}
if (outputPerVertexActiveMembers.any())
{
mOutputPerVertexMaxActiveMember = outputPerVertexActiveMembers.last();
}
}
void visitMemberDecorate(spirv::IdRef id,
spirv::LiteralInteger member,
spv::Decoration decoration,
const spirv::LiteralIntegerList &valueList);
TransformationState transformMemberDecorate(spirv::IdRef typeId,
spirv::LiteralInteger member,
spv::Decoration decoration);
TransformationState transformMemberName(spirv::IdRef id,
spirv::LiteralInteger member,
const spirv::LiteralString &name);
TransformationState transformTypeStruct(spirv::IdResult id,
spirv::IdRefList *memberList,
spirv::Blob *blobOut);
private:
bool isPerVertex(spirv::IdRef typeId) const
{
return typeId == ID::OutputPerVertexBlock || typeId == ID::InputPerVertexBlock;
}
uint32_t getPerVertexMaxActiveMember(spirv::IdRef typeId) const
{
ASSERT(isPerVertex(typeId));
return typeId == ID::OutputPerVertexBlock ? mOutputPerVertexMaxActiveMemberIndex
: mInputPerVertexMaxActiveMemberIndex;
}
gl::PerVertexMember mInputPerVertexMaxActiveMember;
gl::PerVertexMember mOutputPerVertexMaxActiveMember;
// If gl_ClipDistance and gl_CullDistance are not used, they are missing from gl_PerVertex. So
// the index of gl_CullDistance may not be the same as the value of
// gl::PerVertexMember::CullDistance.
//
// By looking at OpMemberDecorate %kIdInput/OutputPerVertexBlock <Index> BuiltIn <Member>, the
// <Index> corresponding to mInput/OutputPerVertexMaxActiveMember is discovered and kept in
// mInput/OutputPerVertexMaxActiveMemberIndex
uint32_t mInputPerVertexMaxActiveMemberIndex;
uint32_t mOutputPerVertexMaxActiveMemberIndex;
};
void SpirvPerVertexTrimmer::visitMemberDecorate(spirv::IdRef id,
spirv::LiteralInteger member,
spv::Decoration decoration,
const spirv::LiteralIntegerList &valueList)
{
if (decoration != spv::DecorationBuiltIn || !isPerVertex(id))
{
return;
}
// Map spv::BuiltIn to gl::PerVertexMember.
ASSERT(!valueList.empty());
const uint32_t builtIn = valueList[0];
gl::PerVertexMember perVertexMember = gl::PerVertexMember::Position;
switch (builtIn)
{
case spv::BuiltInPosition:
perVertexMember = gl::PerVertexMember::Position;
break;
case spv::BuiltInPointSize:
perVertexMember = gl::PerVertexMember::PointSize;
break;
case spv::BuiltInClipDistance:
perVertexMember = gl::PerVertexMember::ClipDistance;
break;
case spv::BuiltInCullDistance:
perVertexMember = gl::PerVertexMember::CullDistance;
break;
default:
UNREACHABLE();
}
if (id == ID::OutputPerVertexBlock && perVertexMember == mOutputPerVertexMaxActiveMember)
{
mOutputPerVertexMaxActiveMemberIndex = member;
}
else if (id == ID::InputPerVertexBlock && perVertexMember == mInputPerVertexMaxActiveMember)
{
mInputPerVertexMaxActiveMemberIndex = member;
}
}
TransformationState SpirvPerVertexTrimmer::transformMemberDecorate(spirv::IdRef typeId,
spirv::LiteralInteger member,
spv::Decoration decoration)
{
// Transform the following:
//
// - OpMemberDecorate %gl_PerVertex N BuiltIn B
// - OpMemberDecorate %gl_PerVertex N Invariant
// - OpMemberDecorate %gl_PerVertex N RelaxedPrecision
if (!isPerVertex(typeId) ||
(decoration != spv::DecorationBuiltIn && decoration != spv::DecorationInvariant &&
decoration != spv::DecorationRelaxedPrecision))
{
return TransformationState::Unchanged;
}
// Drop stripped fields.
return member > getPerVertexMaxActiveMember(typeId) ? TransformationState::Transformed
: TransformationState::Unchanged;
}
TransformationState SpirvPerVertexTrimmer::transformMemberName(spirv::IdRef id,
spirv::LiteralInteger member,
const spirv::LiteralString &name)
{
// Remove the instruction if it's a stripped member of gl_PerVertex.
return isPerVertex(id) && member > getPerVertexMaxActiveMember(id)
? TransformationState::Transformed
: TransformationState::Unchanged;
}
TransformationState SpirvPerVertexTrimmer::transformTypeStruct(spirv::IdResult id,
spirv::IdRefList *memberList,
spirv::Blob *blobOut)
{
if (!isPerVertex(id))
{
return TransformationState::Unchanged;
}
const uint32_t maxMembers = getPerVertexMaxActiveMember(id);
// Change the definition of the gl_PerVertex struct by stripping unused fields at the end.
const uint32_t memberCount = maxMembers + 1;
memberList->resize_down(memberCount);
spirv::WriteTypeStruct(blobOut, id, *memberList);
return TransformationState::Transformed;
}
// Helper class that removes inactive varyings and replaces them with Private variables.
class SpirvInactiveVaryingRemover final : angle::NonCopyable
{
public:
SpirvInactiveVaryingRemover() {}
void init(size_t indexCount);
TransformationState transformAccessChain(spirv::IdResultType typeId,
spirv::IdResult id,
spirv::IdRef baseId,
const spirv::IdRefList &indexList,
spirv::Blob *blobOut);
TransformationState transformDecorate(const ShaderInterfaceVariableInfo &info,
gl::ShaderType shaderType,
spirv::IdRef id,
spv::Decoration decoration,
const spirv::LiteralIntegerList &decorationValues,
spirv::Blob *blobOut);
TransformationState transformTypePointer(spirv::IdResult id,
spv::StorageClass storageClass,
spirv::IdRef typeId,
spirv::Blob *blobOut);
TransformationState transformVariable(spirv::IdResultType typeId,
spirv::IdResult id,
spv::StorageClass storageClass,
spirv::Blob *blobOut);
void modifyEntryPointInterfaceList(
const std::vector<const ShaderInterfaceVariableInfo *> &variableInfoById,
gl::ShaderType shaderType,
spirv::IdRefList *interfaceList);
bool isInactive(spirv::IdRef id) const { return mIsInactiveById[id]; }
spirv::IdRef getTransformedPrivateType(spirv::IdRef id) const
{
ASSERT(id < mTypePointerTransformedId.size());
return mTypePointerTransformedId[id];
}
private:
// Each OpTypePointer instruction that defines a type with the Output storage class is
// duplicated with a similar instruction but which defines a type with the Private storage
// class. If inactive varyings are encountered, its type is changed to the Private one. The
// following vector maps the Output type id to the corresponding Private one.
std::vector<spirv::IdRef> mTypePointerTransformedId;
// Whether a variable has been marked inactive.
std::vector<bool> mIsInactiveById;
};
void SpirvInactiveVaryingRemover::init(size_t indexBound)
{
// Allocate storage for Output type pointer map. At index i, this vector holds the identical
// type as %i except for its storage class turned to Private.
mTypePointerTransformedId.resize(indexBound);
mIsInactiveById.resize(indexBound, false);
}
TransformationState SpirvInactiveVaryingRemover::transformAccessChain(
spirv::IdResultType typeId,
spirv::IdResult id,
spirv::IdRef baseId,
const spirv::IdRefList &indexList,
spirv::Blob *blobOut)
{
// Modifiy the instruction to use the private type.
ASSERT(typeId < mTypePointerTransformedId.size());
ASSERT(mTypePointerTransformedId[typeId].valid());
spirv::WriteAccessChain(blobOut, mTypePointerTransformedId[typeId], id, baseId, indexList);
return TransformationState::Transformed;
}
TransformationState SpirvInactiveVaryingRemover::transformDecorate(
const ShaderInterfaceVariableInfo &info,
gl::ShaderType shaderType,
spirv::IdRef id,
spv::Decoration decoration,
const spirv::LiteralIntegerList &decorationValues,
spirv::Blob *blobOut)
{
// If it's an inactive varying, remove the decoration altogether.
return info.activeStages[shaderType] ? TransformationState::Unchanged
: TransformationState::Transformed;
}
void SpirvInactiveVaryingRemover::modifyEntryPointInterfaceList(
const std::vector<const ShaderInterfaceVariableInfo *> &variableInfoById,
gl::ShaderType shaderType,
spirv::IdRefList *interfaceList)
{
// Filter out inactive varyings from entry point interface declaration.
size_t writeIndex = 0;
for (size_t index = 0; index < interfaceList->size(); ++index)
{
spirv::IdRef id((*interfaceList)[index]);
const ShaderInterfaceVariableInfo *info = variableInfoById[id];
ASSERT(info);
if (!info->activeStages[shaderType])
{
continue;
}
(*interfaceList)[writeIndex] = id;
++writeIndex;
}
// Update the number of interface variables.
interfaceList->resize_down(writeIndex);
}
TransformationState SpirvInactiveVaryingRemover::transformTypePointer(
spirv::IdResult id,
spv::StorageClass storageClass,
spirv::IdRef typeId,
spirv::Blob *blobOut)
{
// If the storage class is output, this may be used to create a variable corresponding to an
// inactive varying, or if that varying is a struct, an Op*AccessChain retrieving a field of
// that inactive varying.
//
// SPIR-V specifies the storage class both on the type and the variable declaration. Otherwise
// it would have been sufficient to modify the OpVariable instruction. For simplicity, duplicate
// every "OpTypePointer Output" and "OpTypePointer Input" instruction except with the Private
// storage class, in case it may be necessary later.
// Cannot create a Private type declaration from builtins such as gl_PerVertex.
if (typeId == sh::vk::spirv::kIdInputPerVertexBlock ||
typeId == sh::vk::spirv::kIdOutputPerVertexBlock ||
typeId == sh::vk::spirv::kIdInputPerVertexBlockArray ||
typeId == sh::vk::spirv::kIdOutputPerVertexBlockArray)
{
return TransformationState::Unchanged;
}
if (storageClass != spv::StorageClassOutput && storageClass != spv::StorageClassInput)
{
return TransformationState::Unchanged;
}
const spirv::IdRef newPrivateTypeId(SpirvTransformerBase::GetNewId(blobOut));
// Write OpTypePointer for the new PrivateType.
spirv::WriteTypePointer(blobOut, newPrivateTypeId, spv::StorageClassPrivate, typeId);
// Remember the id of the replacement.
ASSERT(id < mTypePointerTransformedId.size());
mTypePointerTransformedId[id] = newPrivateTypeId;
// The original instruction should still be present as well. At this point, we don't know
// whether we will need the original or Private type.
return TransformationState::Unchanged;
}
TransformationState SpirvInactiveVaryingRemover::transformVariable(spirv::IdResultType typeId,
spirv::IdResult id,
spv::StorageClass storageClass,
spirv::Blob *blobOut)
{
ASSERT(storageClass == spv::StorageClassOutput || storageClass == spv::StorageClassInput);
ASSERT(typeId < mTypePointerTransformedId.size());
ASSERT(mTypePointerTransformedId[typeId].valid());
spirv::WriteVariable(blobOut, mTypePointerTransformedId[typeId], id, spv::StorageClassPrivate,
nullptr);
mIsInactiveById[id] = true;
return TransformationState::Transformed;
}
// Helper class that fixes varying precisions so they match between shader stages.
class SpirvVaryingPrecisionFixer final : angle::NonCopyable
{
public:
SpirvVaryingPrecisionFixer() {}
void init(size_t indexBound);
void visitTypePointer(spirv::IdResult id, spv::StorageClass storageClass, spirv::IdRef typeId);
void visitVariable(const ShaderInterfaceVariableInfo &info,
gl::ShaderType shaderType,
spirv::IdResultType typeId,
spirv::IdResult id,
spv::StorageClass storageClass,
spirv::Blob *blobOut);
TransformationState transformVariable(const ShaderInterfaceVariableInfo &info,
spirv::IdResultType typeId,
spirv::IdResult id,
spv::StorageClass storageClass,
spirv::Blob *blobOut);
void modifyEntryPointInterfaceList(spirv::IdRefList *interfaceList);
void addDecorate(spirv::IdRef replacedId, spirv::Blob *blobOut);
void writeInputPreamble(
const std::vector<const ShaderInterfaceVariableInfo *> &variableInfoById,
gl::ShaderType shaderType,
spirv::Blob *blobOut);
void writeOutputPrologue(
const std::vector<const ShaderInterfaceVariableInfo *> &variableInfoById,
gl::ShaderType shaderType,
spirv::Blob *blobOut);
bool isReplaced(spirv::IdRef id) const { return mFixedVaryingId[id].valid(); }
spirv::IdRef getReplacementId(spirv::IdRef id) const
{
return mFixedVaryingId[id].valid() ? mFixedVaryingId[id] : id;
}
private:
std::vector<spirv::IdRef> mTypePointerTypeId;
std::vector<spirv::IdRef> mFixedVaryingId;
std::vector<spirv::IdRef> mFixedVaryingTypeId;
};
void SpirvVaryingPrecisionFixer::init(size_t indexBound)
{
// Allocate storage for precision mismatch fix up.
mTypePointerTypeId.resize(indexBound);
mFixedVaryingId.resize(indexBound);
mFixedVaryingTypeId.resize(indexBound);
}
void SpirvVaryingPrecisionFixer::visitTypePointer(spirv::IdResult id,
spv::StorageClass storageClass,
spirv::IdRef typeId)
{
mTypePointerTypeId[id] = typeId;
}
void SpirvVaryingPrecisionFixer::visitVariable(const ShaderInterfaceVariableInfo &info,
gl::ShaderType shaderType,
spirv::IdResultType typeId,
spirv::IdResult id,
spv::StorageClass storageClass,
spirv::Blob *blobOut)
{
if (info.useRelaxedPrecision && info.activeStages[shaderType] && !mFixedVaryingId[id].valid())
{
mFixedVaryingId[id] = SpirvTransformerBase::GetNewId(blobOut);
mFixedVaryingTypeId[id] = typeId;
}
}
TransformationState SpirvVaryingPrecisionFixer::transformVariable(
const ShaderInterfaceVariableInfo &info,
spirv::IdResultType typeId,
spirv::IdResult id,
spv::StorageClass storageClass,
spirv::Blob *blobOut)
{
if (info.useRelaxedPrecision &&
(storageClass == spv::StorageClassOutput || storageClass == spv::StorageClassInput))
{
// Change existing OpVariable to use fixedVaryingId
ASSERT(mFixedVaryingId[id].valid());
spirv::WriteVariable(blobOut, typeId, mFixedVaryingId[id], storageClass, nullptr);
return TransformationState::Transformed;
}
return TransformationState::Unchanged;
}
void SpirvVaryingPrecisionFixer::writeInputPreamble(
const std::vector<const ShaderInterfaceVariableInfo *> &variableInfoById,
gl::ShaderType shaderType,
spirv::Blob *blobOut)
{
if (shaderType == gl::ShaderType::Vertex || shaderType == gl::ShaderType::Compute)
{
return;
}
// Copy from corrected varyings to temp global variables with original precision.
for (uint32_t idIndex = spirv::kMinValidId; idIndex < variableInfoById.size(); idIndex++)
{
const spirv::IdRef id(idIndex);
const ShaderInterfaceVariableInfo *info = variableInfoById[id];
if (info && info->useRelaxedPrecision && info->activeStages[shaderType] &&
info->varyingIsInput)
{
// This is an input varying, need to cast the mediump value that came from
// the previous stage into a highp value that the code wants to work with.
ASSERT(mFixedVaryingTypeId[id].valid());
// Build OpLoad instruction to load the mediump value into a temporary
const spirv::IdRef tempVar(SpirvTransformerBase::GetNewId(blobOut));
const spirv::IdRef tempVarType(mTypePointerTypeId[mFixedVaryingTypeId[id]]);
ASSERT(tempVarType.valid());
spirv::WriteLoad(blobOut, tempVarType, tempVar, mFixedVaryingId[id], nullptr);
// Build OpStore instruction to cast the mediump value to highp for use in
// the function
spirv::WriteStore(blobOut, id, tempVar, nullptr);
}
}
}
void SpirvVaryingPrecisionFixer::modifyEntryPointInterfaceList(spirv::IdRefList *interfaceList)
{
// Modify interface list if any ID was replaced due to varying precision mismatch.
for (size_t index = 0; index < interfaceList->size(); ++index)
{
(*interfaceList)[index] = getReplacementId((*interfaceList)[index]);
}
}
void SpirvVaryingPrecisionFixer::addDecorate(spirv::IdRef replacedId, spirv::Blob *blobOut)
{
spirv::WriteDecorate(blobOut, replacedId, spv::DecorationRelaxedPrecision, {});
}
void SpirvVaryingPrecisionFixer::writeOutputPrologue(
const std::vector<const ShaderInterfaceVariableInfo *> &variableInfoById,
gl::ShaderType shaderType,
spirv::Blob *blobOut)
{
if (shaderType == gl::ShaderType::Fragment || shaderType == gl::ShaderType::Compute)
{
return;
}
// Copy from temp global variables with original precision to corrected varyings.
for (uint32_t idIndex = spirv::kMinValidId; idIndex < variableInfoById.size(); idIndex++)
{
const spirv::IdRef id(idIndex);
const ShaderInterfaceVariableInfo *info = variableInfoById[id];
if (info && info->useRelaxedPrecision && info->activeStages[shaderType] &&
info->varyingIsOutput)
{
ASSERT(mFixedVaryingTypeId[id].valid());
// Build OpLoad instruction to load the highp value into a temporary
const spirv::IdRef tempVar(SpirvTransformerBase::GetNewId(blobOut));
const spirv::IdRef tempVarType(mTypePointerTypeId[mFixedVaryingTypeId[id]]);
ASSERT(tempVarType.valid());
spirv::WriteLoad(blobOut, tempVarType, tempVar, id, nullptr);
// Build OpStore instruction to cast the highp value to mediump for output
spirv::WriteStore(blobOut, mFixedVaryingId[id], tempVar, nullptr);
}
}
}
// Helper class that generates code for transform feedback
class SpirvTransformFeedbackCodeGenerator final : angle::NonCopyable
{
public:
SpirvTransformFeedbackCodeGenerator(bool isEmulated)
: mIsEmulated(isEmulated),
mHasTransformFeedbackOutput(false),
mIsPositionCapturedByTransformFeedbackExtension(false)
{}
void visitVariable(const ShaderInterfaceVariableInfo &info,
const XFBInterfaceVariableInfo &xfbInfo,
gl::ShaderType shaderType,
spirv::IdResultType typeId,
spirv::IdResult id,
spv::StorageClass storageClass);
TransformationState transformCapability(spv::Capability capability, spirv::Blob *blobOut);
TransformationState transformDecorate(const ShaderInterfaceVariableInfo *info,
gl::ShaderType shaderType,
spirv::IdRef id,
spv::Decoration decoration,
const spirv::LiteralIntegerList &decorationValues,
spirv::Blob *blobOut);
TransformationState transformMemberDecorate(const ShaderInterfaceVariableInfo *info,
gl::ShaderType shaderType,
spirv::IdRef id,
spirv::LiteralInteger member,
spv::Decoration decoration,
spirv::Blob *blobOut);
TransformationState transformName(spirv::IdRef id, spirv::LiteralString name);
TransformationState transformMemberName(spirv::IdRef id,
spirv::LiteralInteger member,
spirv::LiteralString name);
TransformationState transformTypeStruct(const ShaderInterfaceVariableInfo *info,
gl::ShaderType shaderType,
spirv::IdResult id,
const spirv::IdRefList &memberList,
spirv::Blob *blobOut);
TransformationState transformTypePointer(const ShaderInterfaceVariableInfo *info,
gl::ShaderType shaderType,
spirv::IdResult id,
spv::StorageClass storageClass,
spirv::IdRef typeId,
spirv::Blob *blobOut);
TransformationState transformVariable(const ShaderInterfaceVariableInfo &info,
const ShaderInterfaceVariableInfoMap &variableInfoMap,
gl::ShaderType shaderType,
spirv::IdResultType typeId,
spirv::IdResult id,
spv::StorageClass storageClass);
void writePendingDeclarations(
const std::vector<const ShaderInterfaceVariableInfo *> &variableInfoById,
spirv::Blob *blobOut);
void writeTransformFeedbackExtensionOutput(spirv::IdRef positionId, spirv::Blob *blobOut);
void writeTransformFeedbackEmulationOutput(
const SpirvInactiveVaryingRemover &inactiveVaryingRemover,
const SpirvVaryingPrecisionFixer &varyingPrecisionFixer,
const bool usePrecisionFixer,
spirv::Blob *blobOut);
void addExecutionMode(spirv::IdRef entryPointId, spirv::Blob *blobOut);
void addMemberDecorate(const XFBInterfaceVariableInfo &info,
spirv::IdRef id,
spirv::Blob *blobOut);
void addDecorate(const XFBInterfaceVariableInfo &xfbInfo,
spirv::IdRef id,
spirv::Blob *blobOut);
private:
void gatherXfbVaryings(const XFBInterfaceVariableInfo &info, spirv::IdRef id);
void visitXfbVarying(const ShaderInterfaceVariableXfbInfo &xfb,
spirv::IdRef baseId,
uint32_t fieldIndex);
TransformationState transformTypeHelper(const ShaderInterfaceVariableInfo *info,
gl::ShaderType shaderType,
spirv::IdResult id);
void writeIntConstant(uint32_t value, spirv::IdRef intId, spirv::Blob *blobOut);
void getVaryingTypeIds(GLenum componentType,
bool isPrivate,
spirv::IdRef *typeIdOut,
spirv::IdRef *typePtrOut);
void writeGetOffsetsCall(spirv::IdRef xfbOffsets, spirv::Blob *blobOut);
void writeComponentCapture(uint32_t bufferIndex,
spirv::IdRef xfbOffset,
spirv::IdRef varyingTypeId,
spirv::IdRef varyingTypePtr,
spirv::IdRef varyingBaseId,
const spirv::IdRefList &accessChainIndices,
GLenum componentType,
spirv::Blob *blobOut);
static constexpr size_t kXfbDecorationCount = 3;
static constexpr spv::Decoration kXfbDecorations[kXfbDecorationCount] = {
spv::DecorationXfbBuffer,
spv::DecorationXfbStride,
spv::DecorationOffset,
};
bool mIsEmulated;
bool mHasTransformFeedbackOutput;
// Ids needed to generate transform feedback support code.
bool mIsPositionCapturedByTransformFeedbackExtension;
gl::TransformFeedbackBuffersArray<spirv::IdRef> mBufferStrides;
spirv::IdRef mBufferStridesCompositeId;
// Type and constant ids:
//
// - mFloatOutputPointerId: id of OpTypePointer Output %kIdFloat
// - mIntOutputPointerId: id of OpTypePointer Output %kIdInt
// - mUintOutputPointerId: id of OpTypePointer Output %kIdUint
// - mFloatPrivatePointerId, mIntPrivatePointerId, mUintPrivatePointerId: identical to the
// above, but with the Private storage class. Used to load from varyings that have been
// replaced as part of precision mismatch fixup.
// - mFloatUniformPointerId: id of OpTypePointer Uniform %kIdFloat
//
// - mIntNIds[n]: id of OpConstant %kIdInt n
spirv::IdRef mFloatOutputPointerId;
spirv::IdRef mIntOutputPointerId;
spirv::IdRef mUintOutputPointerId;
spirv::IdRef mFloatPrivatePointerId;
spirv::IdRef mIntPrivatePointerId;
spirv::IdRef mUintPrivatePointerId;
spirv::IdRef mFloatUniformPointerId;
// Id of constants such as row, column and array index. Integers 0, 1, 2 and 3 are always
// defined by the compiler.
angle::FastVector<spirv::IdRef, 4> mIntNIds;
// For transform feedback emulation, the captured elements are gathered in a list and sorted.
// This allows the output generation code to always use offset += 1, thus relying on only one
// constant (1).
struct XfbVarying
{
// The varyings are sorted by info.offset.
const ShaderInterfaceVariableXfbInfo *info;
// Id of the base variable.
spirv::IdRef baseId;
// The field index, if a member of an I/O blocks
uint32_t fieldIndex;
};
gl::TransformFeedbackBuffersArray<std::vector<XfbVarying>> mXfbVaryings;
};
constexpr size_t SpirvTransformFeedbackCodeGenerator::kXfbDecorationCount;
constexpr spv::Decoration SpirvTransformFeedbackCodeGenerator::kXfbDecorations[kXfbDecorationCount];
void SpirvTransformFeedbackCodeGenerator::visitVariable(const ShaderInterfaceVariableInfo &info,
const XFBInterfaceVariableInfo &xfbInfo,
gl::ShaderType shaderType,
spirv::IdResultType typeId,
spirv::IdResult id,
spv::StorageClass storageClass)
{
if (mIsEmulated)
{
gatherXfbVaryings(xfbInfo, id);
return;
}
// Note if the variable is captured by transform feedback. In that case, the TransformFeedback
// capability needs to be added.
if ((xfbInfo.xfb.pod.buffer != ShaderInterfaceVariableInfo::kInvalid ||
!xfbInfo.fieldXfb.empty()) &&
info.activeStages[shaderType])
{
mHasTransformFeedbackOutput = true;
// If this is the special ANGLEXfbPosition variable, remember its id to be used for the
// ANGLEXfbPosition = gl_Position; assignment code generation.
if (id == ID::XfbExtensionPosition)
{
mIsPositionCapturedByTransformFeedbackExtension = true;
}
}
}
TransformationState SpirvTransformFeedbackCodeGenerator::transformCapability(
spv::Capability capability,
spirv::Blob *blobOut)
{
if (!mHasTransformFeedbackOutput || mIsEmulated)
{
return TransformationState::Unchanged;
}
// Transform feedback capability shouldn't have already been specified.
ASSERT(capability != spv::CapabilityTransformFeedback);
// Vulkan shaders have either Shader, Geometry or Tessellation capability. We find this
// capability, and add the TransformFeedback capability right before it.
if (capability != spv::CapabilityShader && capability != spv::CapabilityGeometry &&
capability != spv::CapabilityTessellation)
{
return TransformationState::Unchanged;
}
// Write the TransformFeedback capability declaration.
spirv::WriteCapability(blobOut, spv::CapabilityTransformFeedback);
// The original capability is retained.
return TransformationState::Unchanged;
}
TransformationState SpirvTransformFeedbackCodeGenerator::transformName(spirv::IdRef id,
spirv::LiteralString name)
{
// In the case of ANGLEXfbN, unconditionally remove the variable names. If transform
// feedback is not active, the corresponding variables will be removed.
switch (id)
{
case sh::vk::spirv::kIdXfbEmulationBufferBlockZero:
case sh::vk::spirv::kIdXfbEmulationBufferBlockOne:
case sh::vk::spirv::kIdXfbEmulationBufferBlockTwo:
case sh::vk::spirv::kIdXfbEmulationBufferBlockThree:
case sh::vk::spirv::kIdXfbEmulationBufferVarZero:
case sh::vk::spirv::kIdXfbEmulationBufferVarOne:
case sh::vk::spirv::kIdXfbEmulationBufferVarTwo:
case sh::vk::spirv::kIdXfbEmulationBufferVarThree:
return TransformationState::Transformed;
default:
return TransformationState::Unchanged;
}
}
TransformationState SpirvTransformFeedbackCodeGenerator::transformMemberName(
spirv::IdRef id,
spirv::LiteralInteger member,
spirv::LiteralString name)
{
switch (id)
{
case sh::vk::spirv::kIdXfbEmulationBufferBlockZero:
case sh::vk::spirv::kIdXfbEmulationBufferBlockOne:
case sh::vk::spirv::kIdXfbEmulationBufferBlockTwo:
case sh::vk::spirv::kIdXfbEmulationBufferBlockThree:
return TransformationState::Transformed;
default:
return TransformationState::Unchanged;
}
}
TransformationState SpirvTransformFeedbackCodeGenerator::transformTypeHelper(
const ShaderInterfaceVariableInfo *info,
gl::ShaderType shaderType,
spirv::IdResult id)
{
switch (id)
{
case sh::vk::spirv::kIdXfbEmulationBufferBlockZero:
case sh::vk::spirv::kIdXfbEmulationBufferBlockOne:
case sh::vk::spirv::kIdXfbEmulationBufferBlockTwo:
case sh::vk::spirv::kIdXfbEmulationBufferBlockThree:
ASSERT(info);
return info->activeStages[shaderType] ? TransformationState::Unchanged
: TransformationState::Transformed;
default:
return TransformationState::Unchanged;
}
}
TransformationState SpirvTransformFeedbackCodeGenerator::transformDecorate(
const ShaderInterfaceVariableInfo *info,
gl::ShaderType shaderType,
spirv::IdRef id,
spv::Decoration decoration,
const spirv::LiteralIntegerList &decorationValues,
spirv::Blob *blobOut)
{
return transformTypeHelper(info, shaderType, id);
}
TransformationState SpirvTransformFeedbackCodeGenerator::transformMemberDecorate(
const ShaderInterfaceVariableInfo *info,
gl::ShaderType shaderType,
spirv::IdRef id,
spirv::LiteralInteger member,
spv::Decoration decoration,
spirv::Blob *blobOut)
{
return transformTypeHelper(info, shaderType, id);
}
TransformationState SpirvTransformFeedbackCodeGenerator::transformTypeStruct(
const ShaderInterfaceVariableInfo *info,
gl::ShaderType shaderType,
spirv::IdResult id,
const spirv::IdRefList &memberList,
spirv::Blob *blobOut)
{
return transformTypeHelper(info, shaderType, id);
}
TransformationState SpirvTransformFeedbackCodeGenerator::transformTypePointer(
const ShaderInterfaceVariableInfo *info,
gl::ShaderType shaderType,
spirv::IdResult id,
spv::StorageClass storageClass,
spirv::IdRef typeId,
spirv::Blob *blobOut)
{
return transformTypeHelper(info, shaderType, typeId);
}
TransformationState SpirvTransformFeedbackCodeGenerator::transformVariable(
const ShaderInterfaceVariableInfo &info,
const ShaderInterfaceVariableInfoMap &variableInfoMap,
gl::ShaderType shaderType,
spirv::IdResultType typeId,
spirv::IdResult id,
spv::StorageClass storageClass)
{
// This function is currently called for inactive variables.
ASSERT(!info.activeStages[shaderType]);
if (shaderType == gl::ShaderType::Vertex && storageClass == spv::StorageClassUniform)
{
// The ANGLEXfbN variables are unconditionally generated and may be inactive. Remove these
// variables in that case.
ASSERT(&info == &variableInfoMap.getVariableById(shaderType, SpvGetXfbBufferBlockId(0)) ||
&info == &variableInfoMap.getVariableById(shaderType, SpvGetXfbBufferBlockId(1)) ||
&info == &variableInfoMap.getVariableById(shaderType, SpvGetXfbBufferBlockId(2)) ||
&info == &variableInfoMap.getVariableById(shaderType, SpvGetXfbBufferBlockId(3)));
// Drop the declaration.
return TransformationState::Transformed;
}
return TransformationState::Unchanged;
}
void SpirvTransformFeedbackCodeGenerator::gatherXfbVaryings(const XFBInterfaceVariableInfo &info,
spirv::IdRef id)
{
visitXfbVarying(info.xfb, id, ShaderInterfaceVariableXfbInfo::kInvalid);
for (size_t fieldIndex = 0; fieldIndex < info.fieldXfb.size(); ++fieldIndex)
{
visitXfbVarying(info.fieldXfb[fieldIndex], id, static_cast<uint32_t>(fieldIndex));
}
}
void SpirvTransformFeedbackCodeGenerator::visitXfbVarying(const ShaderInterfaceVariableXfbInfo &xfb,
spirv::IdRef baseId,
uint32_t fieldIndex)
{
for (const ShaderInterfaceVariableXfbInfo &arrayElement : xfb.arrayElements)
{
visitXfbVarying(arrayElement, baseId, fieldIndex);
}
if (xfb.pod.buffer == ShaderInterfaceVariableXfbInfo::kInvalid)
{
return;
}
// Varyings captured to the same buffer have the same stride.
ASSERT(mXfbVaryings[xfb.pod.buffer].empty() ||
mXfbVaryings[xfb.pod.buffer][0].info->pod.stride == xfb.pod.stride);
mXfbVaryings[xfb.pod.buffer].push_back({&xfb, baseId, fieldIndex});
}
void SpirvTransformFeedbackCodeGenerator::writeIntConstant(uint32_t value,
spirv::IdRef intId,
spirv::Blob *blobOut)
{
if (value == ShaderInterfaceVariableXfbInfo::kInvalid)
{
return;
}
if (mIntNIds.size() <= value)
{
// This member never resized down, so new elements can't have previous values.
mIntNIds.resize_maybe_value_reuse(value + 1);
}
else if (mIntNIds[value].valid())
{
return;
}
mIntNIds[value] = SpirvTransformerBase::GetNewId(blobOut);
spirv::WriteConstant(blobOut, ID::Int, mIntNIds[value],
spirv::LiteralContextDependentNumber(value));
}
void SpirvTransformFeedbackCodeGenerator::writePendingDeclarations(
const std::vector<const ShaderInterfaceVariableInfo *> &variableInfoById,
spirv::Blob *blobOut)
{
if (!mIsEmulated)
{
return;
}
mFloatOutputPointerId = SpirvTransformerBase::GetNewId(blobOut);
mFloatPrivatePointerId = SpirvTransformerBase::GetNewId(blobOut);
spirv::WriteTypePointer(blobOut, mFloatOutputPointerId, spv::StorageClassOutput, ID::Float);
spirv::WriteTypePointer(blobOut, mFloatPrivatePointerId, spv::StorageClassPrivate, ID::Float);
mIntOutputPointerId = SpirvTransformerBase::GetNewId(blobOut);
mIntPrivatePointerId = SpirvTransformerBase::GetNewId(blobOut);
spirv::WriteTypePointer(blobOut, mIntOutputPointerId, spv::StorageClassOutput, ID::Int);
spirv::WriteTypePointer(blobOut, mIntPrivatePointerId, spv::StorageClassPrivate, ID::Int);
mUintOutputPointerId = SpirvTransformerBase::GetNewId(blobOut);
mUintPrivatePointerId = SpirvTransformerBase::GetNewId(blobOut);
spirv::WriteTypePointer(blobOut, mUintOutputPointerId, spv::StorageClassOutput, ID::Uint);
spirv::WriteTypePointer(blobOut, mUintPrivatePointerId, spv::StorageClassPrivate, ID::Uint);
mFloatUniformPointerId = SpirvTransformerBase::GetNewId(blobOut);
spirv::WriteTypePointer(blobOut, mFloatUniformPointerId, spv::StorageClassUniform, ID::Float);
ASSERT(mIntNIds.empty());
// All new elements initialized later after the resize. Additionally mIntNIds was always empty
// before this resize, so previous value reuse is not possible.
mIntNIds.resize_maybe_value_reuse(4);
mIntNIds[0] = ID::IntZero;
mIntNIds[1] = ID::IntOne;
mIntNIds[2] = ID::IntTwo;
mIntNIds[3] = ID::IntThree;
spirv::IdRefList compositeIds;
for (const std::vector<XfbVarying> &varyings : mXfbVaryings)
{
if (varyings.empty())
{
compositeIds.push_back(ID::IntZero);
continue;
}
const ShaderInterfaceVariableXfbInfo *info0 = varyings[0].info;
// Define the buffer stride constant
ASSERT(info0->pod.stride % sizeof(float) == 0);
uint32_t stride = info0->pod.stride / sizeof(float);
mBufferStrides[info0->pod.buffer] = SpirvTransformerBase::GetNewId(blobOut);
spirv::WriteConstant(blobOut, ID::Int, mBufferStrides[info0->pod.buffer],
spirv::LiteralContextDependentNumber(stride));
compositeIds.push_back(mBufferStrides[info0->pod.buffer]);
// Define all the constants that would be necessary to load the components of the varying.
for (const XfbVarying &varying : varyings)
{
writeIntConstant(varying.fieldIndex, ID::Int, blobOut);
const ShaderInterfaceVariableXfbInfo *info = varying.info;
if (info->pod.arraySize == ShaderInterfaceVariableXfbInfo::kInvalid)
{
continue;
}
uint32_t arrayIndexStart =
varying.info->pod.arrayIndex != ShaderInterfaceVariableXfbInfo::kInvalid
? varying.info->pod.arrayIndex
: 0;
uint32_t arrayIndexEnd = arrayIndexStart + info->pod.arraySize;
for (uint32_t arrayIndex = arrayIndexStart; arrayIndex < arrayIndexEnd; ++arrayIndex)
{
writeIntConstant(arrayIndex, ID::Int, blobOut);
}
}
}
mBufferStridesCompositeId = SpirvTransformerBase::GetNewId(blobOut);
spirv::WriteConstantComposite(blobOut, ID::IVec4, mBufferStridesCompositeId, compositeIds);
}
void SpirvTransformFeedbackCodeGenerator::writeTransformFeedbackExtensionOutput(
spirv::IdRef positionId,
spirv::Blob *blobOut)
{
if (mIsEmulated)
{
return;
}
if (mIsPositionCapturedByTransformFeedbackExtension)
{
spirv::WriteStore(blobOut, ID::XfbExtensionPosition, positionId, nullptr);
}
}
class AccessChainIndexListAppend final : angle::NonCopyable
{
public:
AccessChainIndexListAppend(bool condition,
angle::FastVector<spirv::IdRef, 4> intNIds,
uint32_t index,
spirv::IdRefList *indexList)
: mCondition(condition), mIndexList(indexList)
{
if (mCondition)
{
mIndexList->push_back(intNIds[index]);
}
}
~AccessChainIndexListAppend()
{
if (mCondition)
{
mIndexList->pop_back();
}
}
private:
bool mCondition;
spirv::IdRefList *mIndexList;
};
void SpirvTransformFeedbackCodeGenerator::writeTransformFeedbackEmulationOutput(
const SpirvInactiveVaryingRemover &inactiveVaryingRemover,
const SpirvVaryingPrecisionFixer &varyingPrecisionFixer,
const bool usePrecisionFixer,
spirv::Blob *blobOut)
{
if (!mIsEmulated)
{
return;
}
// First, sort the varyings by offset, to simplify calculation of the output offset.
for (std::vector<XfbVarying> &varyings : mXfbVaryings)
{
std::sort(varyings.begin(), varyings.end(),
[](const XfbVarying &first, const XfbVarying &second) {
return first.info->pod.offset < second.info->pod.offset;
});
}
// The following code is generated for transform feedback emulation:
//
// ivec4 xfbOffsets = ANGLEGetXfbOffsets(ivec4(stride0, stride1, stride2, stride3));
// // For buffer N:
// int xfbOffset = xfbOffsets[N]
// ANGLEXfbN.xfbOut[xfbOffset] = tfVarying0.field[index][row][col]
// xfbOffset += 1;
// ANGLEXfbN.xfbOut[xfbOffset] = tfVarying0.field[index][row][col + 1]
// xfbOffset += 1;
// ...
//
// The following pieces of SPIR-V code are generated according to the above:
//
// - For the initial offsets calculation:
//
// %xfbOffsetsResult = OpFunctionCall %ivec4 %ANGLEGetXfbOffsets %stridesComposite
// %xfbOffsetsVar = OpVariable %kIdIVec4FunctionTypePointer Function
// OpStore %xfbOffsetsVar %xfbOffsetsResult
// %xfbOffsets = OpLoad %ivec4 %xfbOffsetsVar
//
// - Initial code for each buffer N:
//
// %xfbOffset = OpCompositeExtract %int %xfbOffsets N
//
// - For each varying being captured:
//
// // Load the component
// %componentPtr = OpAccessChain %floatOutputPtr %baseId %field %arrayIndex %row %col
// %component = OpLoad %float %componentPtr
// // Store in xfb output
// %xfbOutPtr = OpAccessChain %floatUniformPtr %xfbBufferN %int0 %xfbOffset
// OpStore %xfbOutPtr %component
// // Increment offset
// %xfbOffset = OpIAdd %int %xfbOffset %int1
//
// Note that if the varying being captured is integer, the first two instructions above would
// use the intger equivalent types, and the following instruction would bitcast it to float
// for storage:
//
// %asFloat = OpBitcast %float %component
//
spirv::IdRef xfbOffsets;
if (!mXfbVaryings.empty())
{
xfbOffsets = SpirvTransformerBase::GetNewId(blobOut);
// ivec4 xfbOffsets = ANGLEGetXfbOffsets(ivec4(stride0, stride1, stride2, stride3));
writeGetOffsetsCall(xfbOffsets, blobOut);
}
// Go over the buffers one by one and capture the varyings.
for (uint32_t bufferIndex = 0; bufferIndex < mXfbVaryings.size(); ++bufferIndex)
{
spirv::IdRef xfbOffset(SpirvTransformerBase::GetNewId(blobOut));
// Get the offset corresponding to this buffer:
//
// int xfbOffset = xfbOffsets[N]
spirv::WriteCompositeExtract(blobOut, ID::Int, xfbOffset, xfbOffsets,
{spirv::LiteralInteger(bufferIndex)});
// Track offsets for verification.
uint32_t offsetForVerification = 0;
// Go over the varyings of this buffer in order.
const std::vector<XfbVarying> &varyings = mXfbVaryings[bufferIndex];
for (size_t varyingIndex = 0; varyingIndex < varyings.size(); ++varyingIndex)
{
const XfbVarying &varying = varyings[varyingIndex];
const ShaderInterfaceVariableXfbInfo *info = varying.info;
ASSERT(info->pod.buffer == bufferIndex);
// Each component of the varying being captured is loaded one by one. This uses the
// OpAccessChain instruction that takes a chain of "indices" to end up with the
// component starting from the base variable. For example:
//
// var.member[3][2][0]
//
// where member is field number 4 in var and is a mat4, the access chain would be:
//
// 4 3 2 0
// ^ ^ ^ ^
// | | | |
// | | | row 0
// | | column 2
// | array element 3
// field 4
//
// The following tracks the access chain as the field, array elements, columns and rows
// are looped over.
spirv::IdRefList indexList;
AccessChainIndexListAppend appendField(
varying.fieldIndex != ShaderInterfaceVariableXfbInfo::kInvalid, mIntNIds,
varying.fieldIndex, &indexList);
// The varying being captured is either:
//
// - Not an array: In this case, no entry is added in the access chain
// - An element of the array
// - The whole array
//
uint32_t arrayIndexStart = 0;
uint32_t arrayIndexEnd = info->pod.arraySize;
const bool isArray = info->pod.arraySize != ShaderInterfaceVariableXfbInfo::kInvalid;
if (varying.info->pod.arrayIndex != ShaderInterfaceVariableXfbInfo::kInvalid)
{
// Capturing a single element.
arrayIndexStart = varying.info->pod.arrayIndex;
arrayIndexEnd = arrayIndexStart + 1;
}
else if (!isArray)
{
// Not an array.
arrayIndexEnd = 1;
}
// Sorting the varyings should have ensured that offsets are in order and that writing
// to the output buffer sequentially ends up using the correct offsets.
ASSERT(info->pod.offset == offsetForVerification);
offsetForVerification += (arrayIndexEnd - arrayIndexStart) * info->pod.rowCount *
info->pod.columnCount * sizeof(float);
// Determine the type of the component being captured. OpBitcast is used (the
// implementation of intBitsToFloat() and uintBitsToFloat() for non-float types).
spirv::IdRef varyingTypeId;
spirv::IdRef varyingTypePtr;
const bool isPrivate =
inactiveVaryingRemover.isInactive(varying.baseId) ||
(usePrecisionFixer && varyingPrecisionFixer.isReplaced(varying.baseId));
getVaryingTypeIds(info->pod.componentType, isPrivate, &varyingTypeId, &varyingTypePtr);
for (uint32_t arrayIndex = arrayIndexStart; arrayIndex < arrayIndexEnd; ++arrayIndex)
{
AccessChainIndexListAppend appendArrayIndex(isArray, mIntNIds, arrayIndex,
&indexList);
for (uint32_t col = 0; col < info->pod.columnCount; ++col)
{
AccessChainIndexListAppend appendColumn(info->pod.columnCount > 1, mIntNIds,
col, &indexList);
for (uint32_t row = 0; row < info->pod.rowCount; ++row)
{
AccessChainIndexListAppend appendRow(info->pod.rowCount > 1, mIntNIds, row,
&indexList);
// Generate the code to capture a single component of the varying:
//
// ANGLEXfbN.xfbOut[xfbOffset] = tfVarying0.field[index][row][col]
writeComponentCapture(bufferIndex, xfbOffset, varyingTypeId, varyingTypePtr,
varying.baseId, indexList, info->pod.componentType,
blobOut);
// Increment the offset:
//
// xfbOffset += 1;
//
// which translates to:
//
// %newOffsetId = OpIAdd %int %currentOffsetId %int1
spirv::IdRef nextOffset(SpirvTransformerBase::GetNewId(blobOut));
spirv::WriteIAdd(blobOut, ID::Int, nextOffset, xfbOffset, ID::IntOne);
xfbOffset = nextOffset;
}
}
}
}
}
}
void SpirvTransformFeedbackCodeGenerator::getVaryingTypeIds(GLenum componentType,
bool isPrivate,
spirv::IdRef *typeIdOut,
spirv::IdRef *typePtrOut)
{
switch (componentType)
{
case GL_INT:
*typeIdOut = ID::Int;
*typePtrOut = isPrivate ? mIntPrivatePointerId : mIntOutputPointerId;
break;
case GL_UNSIGNED_INT:
*typeIdOut = ID::Uint;
*typePtrOut = isPrivate ? mUintPrivatePointerId : mUintOutputPointerId;
break;
case GL_FLOAT:
*typeIdOut = ID::Float;
*typePtrOut = isPrivate ? mFloatPrivatePointerId : mFloatOutputPointerId;
break;
default:
UNREACHABLE();
}
ASSERT(typeIdOut->valid());
ASSERT(typePtrOut->valid());
}
void SpirvTransformFeedbackCodeGenerator::writeGetOffsetsCall(spirv::IdRef xfbOffsets,
spirv::Blob *blobOut)
{
const spirv::IdRef xfbOffsetsResult(SpirvTransformerBase::GetNewId(blobOut));
const spirv::IdRef xfbOffsetsVar(SpirvTransformerBase::GetNewId(blobOut));
// Generate code for the following:
//
// ivec4 xfbOffsets = ANGLEGetXfbOffsets(ivec4(stride0, stride1, stride2, stride3));
// Create a variable to hold the result.
spirv::WriteVariable(blobOut, ID::IVec4FunctionTypePointer, xfbOffsetsVar,
spv::StorageClassFunction, nullptr);
// Call a helper function generated by the translator to calculate the offsets for the current
// vertex.
spirv::WriteFunctionCall(blobOut, ID::IVec4, xfbOffsetsResult,
ID::XfbEmulationGetOffsetsFunction, {mBufferStridesCompositeId});
// Store the results.
spirv::WriteStore(blobOut, xfbOffsetsVar, xfbOffsetsResult, nullptr);
// Load from the variable for use in expressions.
spirv::WriteLoad(blobOut, ID::IVec4, xfbOffsets, xfbOffsetsVar, nullptr);
}
void SpirvTransformFeedbackCodeGenerator::writeComponentCapture(
uint32_t bufferIndex,
spirv::IdRef xfbOffset,
spirv::IdRef varyingTypeId,
spirv::IdRef varyingTypePtr,
spirv::IdRef varyingBaseId,
const spirv::IdRefList &accessChainIndices,
GLenum componentType,
spirv::Blob *blobOut)
{
spirv::IdRef component(SpirvTransformerBase::GetNewId(blobOut));
spirv::IdRef xfbOutPtr(SpirvTransformerBase::GetNewId(blobOut));
// Generate code for the following:
//
// ANGLEXfbN.xfbOut[xfbOffset] = tfVarying0.field[index][row][col]
// Load from the component traversing the base variable with the given indices. If there are no
// indices, the variable can be loaded directly.
spirv::IdRef loadPtr = varyingBaseId;
if (!accessChainIndices.empty())
{
loadPtr = SpirvTransformerBase::GetNewId(blobOut);
spirv::WriteAccessChain(blobOut, varyingTypePtr, loadPtr, varyingBaseId,
accessChainIndices);
}
spirv::WriteLoad(blobOut, varyingTypeId, component, loadPtr, nullptr);
// If the varying is int or uint, bitcast it to float to store in the float[] array used to
// capture transform feedback output.
spirv::IdRef asFloat = component;
if (componentType != GL_FLOAT)
{
asFloat = SpirvTransformerBase::GetNewId(blobOut);
spirv::WriteBitcast(blobOut, ID::Float, asFloat, component);
}
// Store into the transform feedback capture buffer at the current offset. Note that this
// buffer has only one field (xfbOut), hence ANGLEXfbN.xfbOut[xfbOffset] translates to ANGLEXfbN
// with access chain {0, xfbOffset}.
static_assert(gl::IMPLEMENTATION_MAX_TRANSFORM_FEEDBACK_BUFFERS == 4);
static_assert(sh::vk::spirv::kIdXfbEmulationBufferVarOne ==
sh::vk::spirv::kIdXfbEmulationBufferVarZero + 1);
static_assert(sh::vk::spirv::kIdXfbEmulationBufferVarTwo ==
sh::vk::spirv::kIdXfbEmulationBufferVarZero + 2);
static_assert(sh::vk::spirv::kIdXfbEmulationBufferVarThree ==
sh::vk::spirv::kIdXfbEmulationBufferVarZero + 3);
spirv::WriteAccessChain(blobOut, mFloatUniformPointerId, xfbOutPtr,
spirv::IdRef(sh::vk::spirv::kIdXfbEmulationBufferVarZero + bufferIndex),
{ID::IntZero, xfbOffset});
spirv::WriteStore(blobOut, xfbOutPtr, asFloat, nullptr);
}
void SpirvTransformFeedbackCodeGenerator::addExecutionMode(spirv::IdRef entryPointId,
spirv::Blob *blobOut)
{
if (mIsEmulated)
{
return;
}
if (mHasTransformFeedbackOutput)
{
spirv::WriteExecutionMode(blobOut, entryPointId, spv::ExecutionModeXfb, {});
}
}
void SpirvTransformFeedbackCodeGenerator::addMemberDecorate(const XFBInterfaceVariableInfo &info,
spirv::IdRef id,
spirv::Blob *blobOut)
{
if (mIsEmulated || info.fieldXfb.empty())
{
return;
}
for (uint32_t fieldIndex = 0; fieldIndex < info.fieldXfb.size(); ++fieldIndex)
{
const ShaderInterfaceVariableXfbInfo &xfb = info.fieldXfb[fieldIndex];
if (xfb.pod.buffer == ShaderInterfaceVariableXfbInfo::kInvalid)
{
continue;
}
ASSERT(xfb.pod.stride != ShaderInterfaceVariableXfbInfo::kInvalid);
ASSERT(xfb.pod.offset != ShaderInterfaceVariableXfbInfo::kInvalid);
const uint32_t xfbDecorationValues[kXfbDecorationCount] = {
xfb.pod.buffer,
xfb.pod.stride,
xfb.pod.offset,
};
// Generate the following three instructions:
//
// OpMemberDecorate %id fieldIndex XfbBuffer xfb.buffer
// OpMemberDecorate %id fieldIndex XfbStride xfb.stride
// OpMemberDecorate %id fieldIndex Offset xfb.offset
for (size_t i = 0; i < kXfbDecorationCount; ++i)
{
spirv::WriteMemberDecorate(blobOut, id, spirv::LiteralInteger(fieldIndex),
kXfbDecorations[i],
{spirv::LiteralInteger(xfbDecorationValues[i])});
}
}
}
void SpirvTransformFeedbackCodeGenerator::addDecorate(const XFBInterfaceVariableInfo &info,
spirv::IdRef id,
spirv::Blob *blobOut)
{
if (mIsEmulated || info.xfb.pod.buffer == ShaderInterfaceVariableXfbInfo::kInvalid)
{
return;
}
ASSERT(info.xfb.pod.stride != ShaderInterfaceVariableXfbInfo::kInvalid);
ASSERT(info.xfb.pod.offset != ShaderInterfaceVariableXfbInfo::kInvalid);
const uint32_t xfbDecorationValues[kXfbDecorationCount] = {
info.xfb.pod.buffer,
info.xfb.pod.stride,
info.xfb.pod.offset,
};
// Generate the following three instructions:
//
// OpDecorate %id XfbBuffer xfb.buffer
// OpDecorate %id XfbStride xfb.stride
// OpDecorate %id Offset xfb.offset
for (size_t i = 0; i < kXfbDecorationCount; ++i)
{
spirv::WriteDecorate(blobOut, id, kXfbDecorations[i],
{spirv::LiteralInteger(xfbDecorationValues[i])});
}
}
// Helper class that generates code for gl_Position transformations
class SpirvPositionTransformer final : angle::NonCopyable
{
public:
SpirvPositionTransformer(const SpvTransformOptions &options) : mOptions(options) {}
void writePositionTransformation(spirv::IdRef positionPointerId,
spirv::IdRef positionId,
spirv::Blob *blobOut);
private:
SpvTransformOptions mOptions;
};
void SpirvPositionTransformer::writePositionTransformation(spirv::IdRef positionPointerId,
spirv::IdRef positionId,
spirv::Blob *blobOut)
{
// Generate the following SPIR-V for prerotation and depth transformation:
//
// // Transform position based on uniforms by making a call to the ANGLETransformPosition
// // function that the translator has already provided.
// %transformed = OpFunctionCall %kIdVec4 %kIdTransformPositionFunction %position
//
// // Store the results back in gl_Position
// OpStore %PositionPointer %transformedPosition
//
const spirv::IdRef transformedPositionId(SpirvTransformerBase::GetNewId(blobOut));
spirv::WriteFunctionCall(blobOut, ID::Vec4, transformedPositionId,
ID::TransformPositionFunction, {positionId});
spirv::WriteStore(blobOut, positionPointerId, transformedPositionId, nullptr);
}
// A transformation to handle both the isMultisampledFramebufferFetch and enableSampleShading
// options. The common transformation between these two options is the addition of the
// SampleRateShading capability.
class SpirvMultisampleTransformer final : angle::NonCopyable
{
public:
SpirvMultisampleTransformer(const SpvTransformOptions &options)
: mOptions(options), mSampleIDDecorationsAdded(false), mAnyImageTypesModified(false)
{}
~SpirvMultisampleTransformer()
{
ASSERT(!mOptions.isMultisampledFramebufferFetch || mAnyImageTypesModified);
}
void init(size_t indexBound);
void visitDecorate(spirv::IdRef id,
spv::Decoration decoration,
const spirv::LiteralIntegerList &valueList);
void visitMemberDecorate(spirv::IdRef id,
spirv::LiteralInteger member,
spv::Decoration decoration);
void visitTypeStruct(spirv::IdResult id, const spirv::IdRefList &memberList);
void visitTypePointer(gl::ShaderType shaderType,
spirv::IdResult id,
spv::StorageClass storageClass,
spirv::IdRef typeId);
void visitVariable(gl::ShaderType shaderType,
spirv::IdResultType typeId,
spirv::IdResult id,
spv::StorageClass storageClass);
TransformationState transformCapability(const SpirvNonSemanticInstructions &nonSemantic,
spv::Capability capability,
spirv::Blob *blobOut);
TransformationState transformTypeImage(const uint32_t *instruction, spirv::Blob *blobOut);
void modifyEntryPointInterfaceList(const SpirvNonSemanticInstructions &nonSemantic,
spirv::IdRefList *interfaceList,
spirv::Blob *blobOut);
void writePendingDeclarations(
const SpirvNonSemanticInstructions &nonSemantic,
const std::vector<const ShaderInterfaceVariableInfo *> &variableInfoById,
spirv::Blob *blobOut);
TransformationState transformDecorate(const SpirvNonSemanticInstructions &nonSemantic,
const ShaderInterfaceVariableInfo &info,
gl::ShaderType shaderType,
spirv::IdRef id,
spv::Decoration &decoration,
spirv::Blob *blobOut);
TransformationState transformImageRead(const uint32_t *instruction, spirv::Blob *blobOut);
private:
void visitVarying(gl::ShaderType shaderType, spirv::IdRef id, spv::StorageClass storageClass);
bool skipSampleDecoration(spv::Decoration decoration);
SpvTransformOptions mOptions;
// Used to assert that the transformation is not unnecessarily run.
bool mSampleIDDecorationsAdded;
bool mAnyImageTypesModified;
struct VaryingInfo
{
// Whether any variable is a varying
bool isVarying = false;
// Whether any variable or its members are already sample-, centroid- or flat-qualified.
bool skipSampleDecoration = false;
std::vector<bool> skipMemberSampleDecoration;
};
std::vector<VaryingInfo> mVaryingInfoById;
};
void SpirvMultisampleTransformer::init(size_t indexBound)
{
mVaryingInfoById.resize(indexBound);
}
TransformationState SpirvMultisampleTransformer::transformImageRead(const uint32_t *instruction,
spirv::Blob *blobOut)
{
// Transform the following:
// %21 = OpImageRead %v4float %13 %20
// to
// %21 = OpImageRead %v4float %13 %20 Sample %17
// where
// %17 = OpLoad %int %gl_SampleID
if (!mOptions.isMultisampledFramebufferFetch)
{
return TransformationState::Unchanged;
}
spirv::IdResultType idResultType;
spirv::IdResult idResult;
spirv::IdRef image;
spirv::IdRef coordinate;
spv::ImageOperandsMask imageOperands;
spirv::IdRefList imageOperandIdsList;
spirv::ParseImageRead(instruction, &idResultType, &idResult, &image, &coordinate,
&imageOperands, &imageOperandIdsList);
ASSERT(ID::Int.valid());
spirv::IdRef builtInSampleIDOpLoad = SpirvTransformerBase::GetNewId(blobOut);
spirv::WriteLoad(blobOut, ID::Int, builtInSampleIDOpLoad, ID::SampleID, nullptr);
imageOperands = spv::ImageOperandsMask::ImageOperandsSampleMask;
imageOperandIdsList.push_back(builtInSampleIDOpLoad);
spirv::WriteImageRead(blobOut, idResultType, idResult, image, coordinate, &imageOperands,
imageOperandIdsList);
return TransformationState::Transformed;
}
void SpirvMultisampleTransformer::writePendingDeclarations(
const SpirvNonSemanticInstructions &nonSemantic,
const std::vector<const ShaderInterfaceVariableInfo *> &variableInfoById,
spirv::Blob *blobOut)
{
// Add following declarations if they are not available yet
// %int = OpTypeInt 32 1
// %_ptr_Input_int = OpTypePointer Input %int
// %gl_SampleID = OpVariable %_ptr_Input_int Input
if (!mOptions.isMultisampledFramebufferFetch)
{
return;
}
if (nonSemantic.hasSampleID())
{
return;
}
spirv::WriteVariable(blobOut, ID::IntInputTypePointer, ID::SampleID, spv::StorageClassInput,
nullptr);
}
TransformationState SpirvMultisampleTransformer::transformTypeImage(const uint32_t *instruction,
spirv::Blob *blobOut)
{
// Transform the following
// %10 = OpTypeImage %float SubpassData 0 0 0 2
// To
// %10 = OpTypeImage %float SubpassData 0 0 1 2
if (!mOptions.isMultisampledFramebufferFetch)
{
return TransformationState::Unchanged;
}
spirv::IdResult idResult;
spirv::IdRef sampledType;
spv::Dim dim;
spirv::LiteralInteger depth;
spirv::LiteralInteger arrayed;
spirv::LiteralInteger ms;
spirv::LiteralInteger sampled;
spv::ImageFormat imageFormat;
spv::AccessQualifier accessQualifier;
spirv::ParseTypeImage(instruction, &idResult, &sampledType, &dim, &depth, &arrayed, &ms,
&sampled, &imageFormat, &accessQualifier);
// Only transform input attachment image types.
if (dim != spv::DimSubpassData)
{
return TransformationState::Unchanged;
}
ms = spirv::LiteralInteger(1);
spirv::WriteTypeImage(blobOut, idResult, sampledType, dim, depth, arrayed, ms, sampled,
imageFormat, nullptr);
mAnyImageTypesModified = true;
return TransformationState::Transformed;
}
namespace
{
bool verifyEntryPointsContainsID(const spirv::IdRefList &interfaceList)
{
for (spirv::IdRef interfaceId : interfaceList)
{
if (interfaceId == ID::SampleID)
{
return true;
}
}
return false;
}
} // namespace
void SpirvMultisampleTransformer::modifyEntryPointInterfaceList(
const SpirvNonSemanticInstructions &nonSemantic,
spirv::IdRefList *interfaceList,
spirv::Blob *blobOut)
{
// Append %gl_sampleID to OpEntryPoint
// Transform the following
//
// OpEntryPoint Fragment %main "main" %_uo_color
//
// To
//
// OpEntryPoint Fragment %main "main" %_uo_color %gl_SampleID
if (!mOptions.isMultisampledFramebufferFetch)
{
return;
}
// Nothing to do if the shader had already declared SampleID
if (nonSemantic.hasSampleID())
{
ASSERT(verifyEntryPointsContainsID(*interfaceList));
return;
}
// Add the SampleID id to the interfaceList. The variable will later be decalred in
// writePendingDeclarations.
interfaceList->push_back(ID::SampleID);
return;
}
TransformationState SpirvMultisampleTransformer::transformCapability(
const SpirvNonSemanticInstructions &nonSemantic,
const spv::Capability capability,
spirv::Blob *blobOut)
{
// Add a new OpCapability line:
//
// OpCapability SampleRateShading
//
// right before the following instruction
//
// OpCapability InputAttachment
if (!mOptions.isMultisampledFramebufferFetch && !mOptions.enableSampleShading)
{
return TransformationState::Unchanged;
}
// Do not add the capability if the SPIR-V already has it
if (nonSemantic.hasSampleRateShading())
{
return TransformationState::Unchanged;
}
// Make sure no duplicates
ASSERT(capability != spv::CapabilitySampleRateShading);
// Make sure we only add the new line on top of "OpCapability Shader"
if (capability != spv::CapabilityShader)
{
return TransformationState::Unchanged;
}
spirv::WriteCapability(blobOut, spv::CapabilitySampleRateShading);
// Leave the original OpCapability untouched
return TransformationState::Unchanged;
}
TransformationState SpirvMultisampleTransformer::transformDecorate(
const SpirvNonSemanticInstructions &nonSemantic,
const ShaderInterfaceVariableInfo &info,
gl::ShaderType shaderType,
spirv::IdRef id,
spv::Decoration &decoration,
spirv::Blob *blobOut)
{
if (mOptions.isMultisampledFramebufferFetch &&
decoration == spv::DecorationInputAttachmentIndex && !nonSemantic.hasSampleID() &&
!mSampleIDDecorationsAdded)
{
// Add the following instructions if they are not available yet:
//
// OpDecorate %gl_SampleID RelaxedPrecision
// OpDecorate %gl_SampleID Flat
// OpDecorate %gl_SampleID BuiltIn SampleId
spirv::WriteDecorate(blobOut, ID::SampleID, spv::DecorationRelaxedPrecision, {});
spirv::WriteDecorate(blobOut, ID::SampleID, spv::DecorationFlat, {});
spirv::WriteDecorate(blobOut, ID::SampleID, spv::DecorationBuiltIn,
{spirv::LiteralInteger(spv::BuiltIn::BuiltInSampleId)});
mSampleIDDecorationsAdded = true;
}
if (mOptions.enableSampleShading && mVaryingInfoById[id].isVarying &&
!mVaryingInfoById[id].skipSampleDecoration)
{
if (decoration == spv::DecorationLocation && info.activeStages[shaderType])
{
// Add the following instructions when the Location decoration is met, if the varying is
// not already decorated with Sample:
//
// OpDecorate %id Sample
spirv::WriteDecorate(blobOut, id, spv::DecorationSample, {});
}
else if (decoration == spv::DecorationBlock)
{
// Add the following instructions when the Block decoration is met, for any member that
// is not already decorated with Sample:
//
// OpMemberDecorate %id member Sample
for (uint32_t member = 0;
member < mVaryingInfoById[id].skipMemberSampleDecoration.size(); ++member)
{
if (!mVaryingInfoById[id].skipMemberSampleDecoration[member])
{
spirv::WriteMemberDecorate(blobOut, id, spirv::LiteralInteger(member),
spv::DecorationSample, {});
}
}
}
}
return TransformationState::Unchanged;
}
bool SpirvMultisampleTransformer::skipSampleDecoration(spv::Decoration decoration)
{
// If a variable is already decorated with Sample, Patch or Centroid, it shouldn't be decorated
// with Sample. BuiltIns are also excluded.
return decoration == spv::DecorationPatch || decoration == spv::DecorationCentroid ||
decoration == spv::DecorationSample || decoration == spv::DecorationBuiltIn;
}
void SpirvMultisampleTransformer::visitDecorate(spirv::IdRef id,
spv::Decoration decoration,
const spirv::LiteralIntegerList &valueList)
{
if (mOptions.enableSampleShading)
{
// Determine whether the id is already decorated with Sample.
if (skipSampleDecoration(decoration))
{
mVaryingInfoById[id].skipSampleDecoration = true;
}
}
return;
}
void SpirvMultisampleTransformer::visitMemberDecorate(spirv::IdRef id,
spirv::LiteralInteger member,
spv::Decoration decoration)
{
if (!mOptions.enableSampleShading)
{
return;
}
if (mVaryingInfoById[id].skipMemberSampleDecoration.size() <= member)
{
mVaryingInfoById[id].skipMemberSampleDecoration.resize(member + 1, false);
}
// Determine whether the member is already decorated with Sample.
if (skipSampleDecoration(decoration))
{
mVaryingInfoById[id].skipMemberSampleDecoration[member] = true;
}
}
void SpirvMultisampleTransformer::visitVarying(gl::ShaderType shaderType,
spirv::IdRef id,
spv::StorageClass storageClass)
{
if (!mOptions.enableSampleShading)
{
return;
}
// Vertex input and fragment output variables are not varyings and don't need to be decorated
// with Sample.
if ((shaderType == gl::ShaderType::Fragment && storageClass == spv::StorageClassOutput) ||
(shaderType == gl::ShaderType::Vertex && storageClass == spv::StorageClassInput))
{
return;
}
const bool isVarying =
storageClass == spv::StorageClassInput || storageClass == spv::StorageClassOutput;
mVaryingInfoById[id].isVarying = isVarying;
}
void SpirvMultisampleTransformer::visitTypeStruct(spirv::IdResult id,
const spirv::IdRefList &memberList)
{
if (mOptions.enableSampleShading)
{
mVaryingInfoById[id].skipMemberSampleDecoration.resize(memberList.size(), false);
}
}
void SpirvMultisampleTransformer::visitTypePointer(gl::ShaderType shaderType,
spirv::IdResult id,
spv::StorageClass storageClass,
spirv::IdRef typeId)
{
// For I/O blocks, the Sample decoration should be specified on the members of the struct type.
// For that purpose, we consider the struct type as the varying instead.
visitVarying(shaderType, typeId, storageClass);
}
void SpirvMultisampleTransformer::visitVariable(gl::ShaderType shaderType,
spirv::IdResultType typeId,
spirv::IdResult id,
spv::StorageClass storageClass)
{
visitVarying(shaderType, id, storageClass);
}
// Helper class that flattens secondary fragment output arrays.
class SpirvSecondaryOutputTransformer final : angle::NonCopyable
{
public:
SpirvSecondaryOutputTransformer() {}
void init(size_t indexBound);
void visitTypeArray(spirv::IdResult id, spirv::IdRef typeId);
void visitTypePointer(spirv::IdResult id, spirv::IdRef typeId);
TransformationState transformAccessChain(spirv::IdResultType typeId,
spirv::IdResult id,
spirv::IdRef baseId,
const spirv::IdRefList &indexList,
spirv::Blob *blobOut);
TransformationState transformDecorate(spirv::IdRef id,
spv::Decoration decoration,
const spirv::LiteralIntegerList &decorationValues,
spirv::Blob *blobOut);
TransformationState transformVariable(spirv::IdResultType typeId,
spirv::IdResultType privateTypeId,
spirv::IdResult id,
spirv::Blob *blobOut);
void modifyEntryPointInterfaceList(
const std::vector<const ShaderInterfaceVariableInfo *> &variableInfoById,
spirv::IdRefList *interfaceList,
spirv::Blob *blobOut);
void writeOutputPrologue(
const std::vector<const ShaderInterfaceVariableInfo *> &variableInfoById,
spirv::Blob *blobOut);
static_assert(gl::IMPLEMENTATION_MAX_DUAL_SOURCE_DRAW_BUFFERS == 1,
"This transformer is incompatible with two or more dual-source draw buffers");
private:
void visitTypeHelper(spirv::IdResult id, spirv::IdRef typeId) { mTypeCache[id] = typeId; }
// This list is filled during visitTypePointer and visitTypeArray steps,
// to resolve the element type ID of the original output array variable.
std::vector<spirv::IdRef> mTypeCache;
spirv::IdRef mElementTypeId;
spirv::IdRef mArrayVariableId;
spirv::IdRef mReplacementVariableId;
spirv::IdRef mElementPointerTypeId;
};
void SpirvSecondaryOutputTransformer::init(size_t indexBound)
{
mTypeCache.resize(indexBound);
}
void SpirvSecondaryOutputTransformer::visitTypeArray(spirv::IdResult id, spirv::IdRef typeId)
{
visitTypeHelper(id, typeId);
}
void SpirvSecondaryOutputTransformer::visitTypePointer(spirv::IdResult id, spirv::IdRef typeId)
{
visitTypeHelper(id, typeId);
}
void SpirvSecondaryOutputTransformer::modifyEntryPointInterfaceList(
const std::vector<const ShaderInterfaceVariableInfo *> &variableInfoById,
spirv::IdRefList *interfaceList,
spirv::Blob *blobOut)
{
// Flatten a secondary output array (if any).
for (size_t index = 0; index < interfaceList->size(); ++index)
{
const spirv::IdRef id((*interfaceList)[index]);
const ShaderInterfaceVariableInfo *info = variableInfoById[id];
ASSERT(info);
if (info->index != 1 || !info->isArray)
{
continue;
}
mArrayVariableId = id;
mReplacementVariableId = SpirvTransformerBase::GetNewId(blobOut);
(*interfaceList)[index] = mReplacementVariableId;
break;
}
}
TransformationState SpirvSecondaryOutputTransformer::transformAccessChain(
spirv::IdResultType typeId,
spirv::IdResult id,
spirv::IdRef baseId,
const spirv::IdRefList &indexList,
spirv::Blob *blobOut)
{
if (baseId != mArrayVariableId)
{
return TransformationState::Unchanged;
}
ASSERT(typeId.valid());
spirv::WriteAccessChain(blobOut, typeId, id, baseId, indexList);
return TransformationState::Transformed;
}
TransformationState SpirvSecondaryOutputTransformer::transformDecorate(
spirv::IdRef id,
spv::Decoration decoration,
const spirv::LiteralIntegerList &decorationValues,
spirv::Blob *blobOut)
{
if (id != mArrayVariableId)
{
return TransformationState::Unchanged;
}
ASSERT(mReplacementVariableId.valid());
if (decoration == spv::DecorationLocation)
{
// Drop the Location decoration from the original variable and add
// it together with an Index decoration to the replacement variable.
spirv::WriteDecorate(blobOut, mReplacementVariableId, spv::DecorationLocation,
{spirv::LiteralInteger(0)});
spirv::WriteDecorate(blobOut, mReplacementVariableId, spv::DecorationIndex,
{spirv::LiteralInteger(1)});
}
else
{
// Apply other decorations, such as RelaxedPrecision, to both variables.
spirv::WriteDecorate(blobOut, id, decoration, decorationValues);
spirv::WriteDecorate(blobOut, mReplacementVariableId, decoration, decorationValues);
}
return TransformationState::Transformed;
}
TransformationState SpirvSecondaryOutputTransformer::transformVariable(
spirv::IdResultType typeId,
spirv::IdResultType privateTypeId,
spirv::IdResult id,
spirv::Blob *blobOut)
{
if (id != mArrayVariableId)
{
return TransformationState::Unchanged;
}
// Change the original variable to use private storage.
ASSERT(privateTypeId.valid());
spirv::WriteVariable(blobOut, privateTypeId, id, spv::StorageClassPrivate, nullptr);
ASSERT(!mElementTypeId.valid());
mElementTypeId = mTypeCache[mTypeCache[typeId]];
ASSERT(mElementTypeId.valid());
// Pointer type for accessing the array element value.
mElementPointerTypeId = SpirvTransformerBase::GetNewId(blobOut);
spirv::WriteTypePointer(blobOut, mElementPointerTypeId, spv::StorageClassPrivate,
mElementTypeId);
// Pointer type for the replacement output variable.
const spirv::IdRef outputPointerTypeId(SpirvTransformerBase::GetNewId(blobOut));
spirv::WriteTypePointer(blobOut, outputPointerTypeId, spv::StorageClassOutput, mElementTypeId);
ASSERT(mReplacementVariableId.valid());
spirv::WriteVariable(blobOut, outputPointerTypeId, mReplacementVariableId,
spv::StorageClassOutput, nullptr);
return TransformationState::Transformed;
}
void SpirvSecondaryOutputTransformer::writeOutputPrologue(
const std::vector<const ShaderInterfaceVariableInfo *> &variableInfoById,
spirv::Blob *blobOut)
{
if (mArrayVariableId.valid())
{
const spirv::IdRef accessChainId(SpirvTransformerBase::GetNewId(blobOut));
spirv::WriteAccessChain(blobOut, mElementPointerTypeId, accessChainId, mArrayVariableId,
{ID::IntZero});
ASSERT(mElementTypeId.valid());
const spirv::IdRef loadId(SpirvTransformerBase::GetNewId(blobOut));
spirv::WriteLoad(blobOut, mElementTypeId, loadId, accessChainId, nullptr);
ASSERT(mReplacementVariableId.valid());
spirv::WriteStore(blobOut, mReplacementVariableId, loadId, nullptr);
}
}
// A SPIR-V transformer. It walks the instructions and modifies them as necessary, for example to
// assign bindings or locations.
class SpirvTransformer final : public SpirvTransformerBase
{
public:
SpirvTransformer(const spirv::Blob &spirvBlobIn,
const SpvTransformOptions &options,
bool isLastPass,
const ShaderInterfaceVariableInfoMap &variableInfoMap,
spirv::Blob *spirvBlobOut)
: SpirvTransformerBase(spirvBlobIn, variableInfoMap, spirvBlobOut),
mOptions(options),
mOverviewFlags(0),
mNonSemanticInstructions(isLastPass),
mPerVertexTrimmer(options, variableInfoMap),
mXfbCodeGenerator(options.isTransformFeedbackEmulated),
mPositionTransformer(options),
mMultisampleTransformer(options)
{}
void transform();
private:
// A prepass to resolve interesting ids:
void resolveVariableIds();
// Transform instructions:
void transformInstruction();
// Instructions that are purely informational:
void visitDecorate(const uint32_t *instruction);
void visitMemberDecorate(const uint32_t *instruction);
void visitTypeArray(const uint32_t *instruction);
void visitTypePointer(const uint32_t *instruction);
void visitTypeStruct(const uint32_t *instruction);
void visitVariable(const uint32_t *instruction);
void visitCapability(const uint32_t *instruction);
bool visitExtInst(const uint32_t *instruction);
// Instructions that potentially need transformation. They return true if the instruction is
// transformed. If false is returned, the instruction should be copied as-is.
TransformationState transformAccessChain(const uint32_t *instruction);
TransformationState transformCapability(const uint32_t *instruction);
TransformationState transformEntryPoint(const uint32_t *instruction);
TransformationState transformExtension(const uint32_t *instruction);
TransformationState transformExtInstImport(const uint32_t *instruction);
TransformationState transformExtInst(const uint32_t *instruction);
TransformationState transformDecorate(const uint32_t *instruction);
TransformationState transformMemberDecorate(const uint32_t *instruction);
TransformationState transformName(const uint32_t *instruction);
TransformationState transformMemberName(const uint32_t *instruction);
TransformationState transformTypePointer(const uint32_t *instruction);
TransformationState transformTypeStruct(const uint32_t *instruction);
TransformationState transformVariable(const uint32_t *instruction);
TransformationState transformTypeImage(const uint32_t *instruction);
TransformationState transformImageRead(const uint32_t *instruction);
// Helpers:
void visitTypeHelper(spirv::IdResult id, spirv::IdRef typeId);
void writePendingDeclarations();
void writeInputPreamble();
void writeOutputPrologue();
// Special flags:
SpvTransformOptions mOptions;
// Traversal state:
spirv::IdRef mCurrentFunctionId;
// Transformation state:
uint32_t mOverviewFlags;
SpirvNonSemanticInstructions mNonSemanticInstructions;
SpirvPerVertexTrimmer mPerVertexTrimmer;
SpirvInactiveVaryingRemover mInactiveVaryingRemover;
SpirvVaryingPrecisionFixer mVaryingPrecisionFixer;
SpirvTransformFeedbackCodeGenerator mXfbCodeGenerator;
SpirvPositionTransformer mPositionTransformer;
SpirvMultisampleTransformer mMultisampleTransformer;
SpirvSecondaryOutputTransformer mSecondaryOutputTransformer;
};
void SpirvTransformer::transform()
{
onTransformBegin();
// First, find all necessary ids and associate them with the information required to transform
// their decorations. This is mostly derived from |mVariableInfoMap|, but may have additional
// mappings; for example |mVariableInfoMap| maps an interface block's type ID to info, but the
// transformer needs to discover the variable associated with that block and map it to the same
// info.
resolveVariableIds();
while (mCurrentWord < mSpirvBlobIn.size())
{
transformInstruction();
}
}
void SpirvTransformer::resolveVariableIds()
{
const size_t indexBound = mSpirvBlobIn[spirv::kHeaderIndexIndexBound];
mInactiveVaryingRemover.init(indexBound);
if (mOptions.useSpirvVaryingPrecisionFixer)
{
mVaryingPrecisionFixer.init(indexBound);
}
if (mOptions.isMultisampledFramebufferFetch || mOptions.enableSampleShading)
{
mMultisampleTransformer.init(indexBound);
}
if (mOptions.shaderType == gl::ShaderType::Fragment)
{
mSecondaryOutputTransformer.init(indexBound);
}
// Allocate storage for id-to-info map. If %i is an id in mVariableInfoMap, index i in this
// vector will hold a pointer to the ShaderInterfaceVariableInfo object associated with that
// name in mVariableInfoMap.
mVariableInfoById.resize(indexBound, nullptr);
// Pre-populate from mVariableInfoMap.
{
const ShaderInterfaceVariableInfoMap::VariableInfoArray &data = mVariableInfoMap.getData();
const ShaderInterfaceVariableInfoMap::IdToIndexMap &idToIndexMap =
mVariableInfoMap.getIdToIndexMap()[mOptions.shaderType];
for (uint32_t hashedId = 0; hashedId < idToIndexMap.size(); ++hashedId)
{
const uint32_t id = hashedId + sh::vk::spirv::kIdShaderVariablesBegin;
const VariableIndex &variableIndex = idToIndexMap.at(hashedId);
if (variableIndex.index == VariableIndex::kInvalid)
{
continue;
}
const ShaderInterfaceVariableInfo &info = data[variableIndex.index];
ASSERT(id < mVariableInfoById.size());
mVariableInfoById[id] = &info;
}
}
size_t currentWord = spirv::kHeaderIndexInstructions;
while (currentWord < mSpirvBlobIn.size())
{
const uint32_t *instruction = &mSpirvBlobIn[currentWord];
uint32_t wordCount;
spv::Op opCode;
spirv::GetInstructionOpAndLength(instruction, &opCode, &wordCount);
switch (opCode)
{
case spv::OpDecorate:
visitDecorate(instruction);
break;
case spv::OpMemberDecorate:
visitMemberDecorate(instruction);
break;
case spv::OpTypeArray:
visitTypeArray(instruction);
break;
case spv::OpTypePointer:
visitTypePointer(instruction);
break;
case spv::OpTypeStruct:
visitTypeStruct(instruction);
break;
case spv::OpVariable:
visitVariable(instruction);
break;
case spv::OpExtInst:
if (visitExtInst(instruction))
{
return;
}
break;
default:
break;
}
currentWord += wordCount;
}
UNREACHABLE();
}
void SpirvTransformer::transformInstruction()
{
uint32_t wordCount;
spv::Op opCode;
const uint32_t *instruction = getCurrentInstruction(&opCode, &wordCount);
if (opCode == spv::OpFunction)
{
spirv::IdResultType id;
spv::FunctionControlMask functionControl;
spirv::IdRef functionType;
spirv::ParseFunction(instruction, &id, &mCurrentFunctionId, &functionControl,
&functionType);
// SPIR-V is structured in sections. Function declarations come last. Only a few
// instructions such as Op*Access* or OpEmitVertex opcodes inside functions need to be
// inspected.
mIsInFunctionSection = true;
}
// Only look at interesting instructions.
TransformationState transformationState = TransformationState::Unchanged;
if (mIsInFunctionSection)
{
// Look at in-function opcodes.
switch (opCode)
{
case spv::OpExtInst:
transformationState = transformExtInst(instruction);
break;
case spv::OpAccessChain:
case spv::OpInBoundsAccessChain:
case spv::OpPtrAccessChain:
case spv::OpInBoundsPtrAccessChain:
transformationState = transformAccessChain(instruction);
break;
case spv::OpImageRead:
transformationState = transformImageRead(instruction);
break;
default:
break;
}
}
else
{
// Look at global declaration opcodes.
switch (opCode)
{
case spv::OpExtension:
transformationState = transformExtension(instruction);
break;
case spv::OpExtInstImport:
transformationState = transformExtInstImport(instruction);
break;
case spv::OpExtInst:
transformationState = transformExtInst(instruction);
break;
case spv::OpName:
transformationState = transformName(instruction);
break;
case spv::OpMemberName:
transformationState = transformMemberName(instruction);
break;
case spv::OpCapability:
transformationState = transformCapability(instruction);
break;
case spv::OpEntryPoint:
transformationState = transformEntryPoint(instruction);
break;
case spv::OpDecorate:
transformationState = transformDecorate(instruction);
break;
case spv::OpMemberDecorate:
transformationState = transformMemberDecorate(instruction);
break;
case spv::OpTypeImage:
transformationState = transformTypeImage(instruction);
break;
case spv::OpTypePointer:
transformationState = transformTypePointer(instruction);
break;
case spv::OpTypeStruct:
transformationState = transformTypeStruct(instruction);
break;
case spv::OpVariable:
transformationState = transformVariable(instruction);
break;
default:
break;
}
}
// If the instruction was not transformed, copy it to output as is.
if (transformationState == TransformationState::Unchanged)
{
copyInstruction(instruction, wordCount);
}
// Advance to next instruction.
mCurrentWord += wordCount;
}
// Called at the end of the declarations section. Any declarations that are necessary but weren't
// present in the original shader need to be done here.
void SpirvTransformer::writePendingDeclarations()
{
mMultisampleTransformer.writePendingDeclarations(mNonSemanticInstructions, mVariableInfoById,
mSpirvBlobOut);
// Pre-rotation and transformation of depth to Vulkan clip space require declarations that may
// not necessarily be in the shader. Transform feedback emulation additionally requires a few
// overlapping ids.
if (!mOptions.isLastPreFragmentStage)
{
return;
}
if (mOptions.isTransformFeedbackStage)
{
mXfbCodeGenerator.writePendingDeclarations(mVariableInfoById, mSpirvBlobOut);
}
}
// Called by transformInstruction to insert necessary instructions for casting varyings.
void SpirvTransformer::writeInputPreamble()
{
if (mOptions.useSpirvVaryingPrecisionFixer)
{
mVaryingPrecisionFixer.writeInputPreamble(mVariableInfoById, mOptions.shaderType,
mSpirvBlobOut);
}
}
// Called by transformInstruction to insert necessary instructions for casting varyings and
// modifying gl_Position.
void SpirvTransformer::writeOutputPrologue()
{
if (mOptions.useSpirvVaryingPrecisionFixer)
{
mVaryingPrecisionFixer.writeOutputPrologue(mVariableInfoById, mOptions.shaderType,
mSpirvBlobOut);
}
if (mOptions.shaderType == gl::ShaderType::Fragment)
{
mSecondaryOutputTransformer.writeOutputPrologue(mVariableInfoById, mSpirvBlobOut);
}
if (!mNonSemanticInstructions.hasOutputPerVertex())
{
return;
}
// Whether gl_Position should be transformed to account for pre-rotation and Vulkan clip space.
const bool transformPosition = mOptions.isLastPreFragmentStage;
const bool isXfbExtensionStage =
mOptions.isTransformFeedbackStage && !mOptions.isTransformFeedbackEmulated;
if (!transformPosition && !isXfbExtensionStage)
{
return;
}
// Load gl_Position with the following SPIR-V:
//
// // Create an access chain to gl_PerVertex.gl_Position, which is always at index 0.
// %PositionPointer = OpAccessChain %kIdVec4OutputTypePointer %kIdOutputPerVertexVar
// %kIdIntZero
// // Load gl_Position
// %Position = OpLoad %kIdVec4 %PositionPointer
//
const spirv::IdRef positionPointerId(getNewId());
const spirv::IdRef positionId(getNewId());
spirv::WriteAccessChain(mSpirvBlobOut, ID::Vec4OutputTypePointer, positionPointerId,
ID::OutputPerVertexVar, {ID::IntZero});
spirv::WriteLoad(mSpirvBlobOut, ID::Vec4, positionId, positionPointerId, nullptr);
// Write transform feedback output before modifying gl_Position.
if (isXfbExtensionStage)
{
mXfbCodeGenerator.writeTransformFeedbackExtensionOutput(positionId, mSpirvBlobOut);
}
if (transformPosition)
{
mPositionTransformer.writePositionTransformation(positionPointerId, positionId,
mSpirvBlobOut);
}
}
void SpirvTransformer::visitDecorate(const uint32_t *instruction)
{
spirv::IdRef id;
spv::Decoration decoration;
spirv::LiteralIntegerList valueList;
spirv::ParseDecorate(instruction, &id, &decoration, &valueList);
mMultisampleTransformer.visitDecorate(id, decoration, valueList);
}
void SpirvTransformer::visitMemberDecorate(const uint32_t *instruction)
{
spirv::IdRef typeId;
spirv::LiteralInteger member;
spv::Decoration decoration;
spirv::LiteralIntegerList valueList;
spirv::ParseMemberDecorate(instruction, &typeId, &member, &decoration, &valueList);
mPerVertexTrimmer.visitMemberDecorate(typeId, member, decoration, valueList);
mMultisampleTransformer.visitMemberDecorate(typeId, member, decoration);
}
void SpirvTransformer::visitTypeHelper(spirv::IdResult id, spirv::IdRef typeId)
{
// Carry forward the mapping of typeId->info to id->info. For interface block, it's the block
// id that is mapped to the info, so this is necessary to eventually be able to map the variable
// itself to the info.
mVariableInfoById[id] = mVariableInfoById[typeId];
}
void SpirvTransformer::visitTypeArray(const uint32_t *instruction)
{
spirv::IdResult id;
spirv::IdRef elementType;
spirv::IdRef length;
spirv::ParseTypeArray(instruction, &id, &elementType, &length);
visitTypeHelper(id, elementType);
if (mOptions.shaderType == gl::ShaderType::Fragment)
{
mSecondaryOutputTransformer.visitTypeArray(id, elementType);
}
}
void SpirvTransformer::visitTypePointer(const uint32_t *instruction)
{
spirv::IdResult id;
spv::StorageClass storageClass;
spirv::IdRef typeId;
spirv::ParseTypePointer(instruction, &id, &storageClass, &typeId);
visitTypeHelper(id, typeId);
if (mOptions.useSpirvVaryingPrecisionFixer)
{
mVaryingPrecisionFixer.visitTypePointer(id, storageClass, typeId);
}
mMultisampleTransformer.visitTypePointer(mOptions.shaderType, id, storageClass, typeId);
if (mOptions.shaderType == gl::ShaderType::Fragment)
{
mSecondaryOutputTransformer.visitTypePointer(id, typeId);
}
}
void SpirvTransformer::visitTypeStruct(const uint32_t *instruction)
{
spirv::IdResult id;
spirv::IdRefList memberList;
ParseTypeStruct(instruction, &id, &memberList);
mMultisampleTransformer.visitTypeStruct(id, memberList);
}
void SpirvTransformer::visitVariable(const uint32_t *instruction)
{
spirv::IdResultType typeId;
spirv::IdResult id;
spv::StorageClass storageClass;
spirv::ParseVariable(instruction, &typeId, &id, &storageClass, nullptr);
// If storage class indicates that this is not a shader interface variable, ignore it.
const bool isInterfaceBlockVariable =
storageClass == spv::StorageClassUniform || storageClass == spv::StorageClassStorageBuffer;
const bool isOpaqueUniform = storageClass == spv::StorageClassUniformConstant;
const bool isInOut =
storageClass == spv::StorageClassInput || storageClass == spv::StorageClassOutput;
if (!isInterfaceBlockVariable && !isOpaqueUniform && !isInOut)
{
return;
}
// If no info is already associated with this id, carry that forward from the type. This
// happens for interface blocks, where the id->info association is done on the type id.
ASSERT(mVariableInfoById[id] == nullptr || mVariableInfoById[typeId] == nullptr);
if (mVariableInfoById[id] == nullptr)
{
mVariableInfoById[id] = mVariableInfoById[typeId];
}
const ShaderInterfaceVariableInfo *info = mVariableInfoById[id];
// If this is an interface variable but no info is associated with it, it must be a built-in.
if (info == nullptr)
{
// Make all builtins point to this no-op info. Adding this entry allows us to ASSERT that
// every shader interface variable is processed during the SPIR-V transformation. This is
// done when iterating the ids provided by OpEntryPoint.
mVariableInfoById[id] = &mBuiltinVariableInfo;
return;
}
if (mOptions.useSpirvVaryingPrecisionFixer)
{
mVaryingPrecisionFixer.visitVariable(*info, mOptions.shaderType, typeId, id, storageClass,
mSpirvBlobOut);
}
if (mOptions.isTransformFeedbackStage && mVariableInfoById[id]->hasTransformFeedback)
{
const XFBInterfaceVariableInfo &xfbInfo =
mVariableInfoMap.getXFBDataForVariableInfo(mVariableInfoById[id]);
mXfbCodeGenerator.visitVariable(*info, xfbInfo, mOptions.shaderType, typeId, id,
storageClass);
}
mMultisampleTransformer.visitVariable(mOptions.shaderType, typeId, id, storageClass);
}
bool SpirvTransformer::visitExtInst(const uint32_t *instruction)
{
sh::vk::spirv::NonSemanticInstruction inst;
if (!mNonSemanticInstructions.visitExtInst(instruction, &inst))
{
return false;
}
switch (inst)
{
case sh::vk::spirv::kNonSemanticOverview:
// SPIR-V is structured in sections (SPIR-V 1.0 Section 2.4 Logical Layout of a Module).
// Names appear before decorations, which are followed by type+variables and finally
// functions. We are only interested in name and variable declarations (as well as type
// declarations for the sake of nameless interface blocks). Early out when the function
// declaration section is met.
//
// This non-semantic instruction marks the beginning of the functions section.
return true;
default:
UNREACHABLE();
}
return false;
}
TransformationState SpirvTransformer::transformDecorate(const uint32_t *instruction)
{
spirv::IdRef id;
spv::Decoration decoration;
spirv::LiteralIntegerList decorationValues;
spirv::ParseDecorate(instruction, &id, &decoration, &decorationValues);
ASSERT(id < mVariableInfoById.size());
const ShaderInterfaceVariableInfo *info = mVariableInfoById[id];
// If variable is not a shader interface variable that needs modification, there's nothing to
// do.
if (info == nullptr)
{
return TransformationState::Unchanged;
}
if (mOptions.shaderType == gl::ShaderType::Fragment)
{
// Handle decorations for the secondary fragment output array.
if (mSecondaryOutputTransformer.transformDecorate(id, decoration, decorationValues,
mSpirvBlobOut) ==
TransformationState::Transformed)
{
return TransformationState::Transformed;
}
}
mMultisampleTransformer.transformDecorate(mNonSemanticInstructions, *info, mOptions.shaderType,
id, decoration, mSpirvBlobOut);
if (mXfbCodeGenerator.transformDecorate(info, mOptions.shaderType, id, decoration,
decorationValues,
mSpirvBlobOut) == TransformationState::Transformed)
{
return TransformationState::Transformed;
}
if (mInactiveVaryingRemover.transformDecorate(*info, mOptions.shaderType, id, decoration,
decorationValues, mSpirvBlobOut) ==
TransformationState::Transformed)
{
return TransformationState::Transformed;
}
// If using relaxed precision, generate instructions for the replacement id instead.
if (mOptions.useSpirvVaryingPrecisionFixer)
{
id = mVaryingPrecisionFixer.getReplacementId(id);
}
uint32_t newDecorationValue = ShaderInterfaceVariableInfo::kInvalid;
switch (decoration)
{
case spv::DecorationLocation:
newDecorationValue = info->location;
break;
case spv::DecorationBinding:
newDecorationValue = info->binding;
break;
case spv::DecorationDescriptorSet:
newDecorationValue = info->descriptorSet;
break;
case spv::DecorationFlat:
case spv::DecorationNoPerspective:
case spv::DecorationCentroid:
case spv::DecorationSample:
if (mOptions.useSpirvVaryingPrecisionFixer && info->useRelaxedPrecision)
{
// Change the id to replacement variable
spirv::WriteDecorate(mSpirvBlobOut, id, decoration, decorationValues);
return TransformationState::Transformed;
}
break;
case spv::DecorationBlock:
// If this is the Block decoration of a shader I/O block, add the transform feedback
// decorations to its members right away.
if (mOptions.isTransformFeedbackStage && info->hasTransformFeedback)
{
const XFBInterfaceVariableInfo &xfbInfo =
mVariableInfoMap.getXFBDataForVariableInfo(info);
mXfbCodeGenerator.addMemberDecorate(xfbInfo, id, mSpirvBlobOut);
}
break;
case spv::DecorationInvariant:
spirv::WriteDecorate(mSpirvBlobOut, id, spv::DecorationInvariant, {});
return TransformationState::Transformed;
default:
break;
}
// If the decoration is not something we care about modifying, there's nothing to do.
if (newDecorationValue == ShaderInterfaceVariableInfo::kInvalid)
{
return TransformationState::Unchanged;
}
// Modify the decoration value.
ASSERT(decorationValues.size() == 1);
spirv::WriteDecorate(mSpirvBlobOut, id, decoration,
{spirv::LiteralInteger(newDecorationValue)});
// If there are decorations to be added, add them right after the Location decoration is
// encountered.
if (decoration != spv::DecorationLocation)
{
return TransformationState::Transformed;
}
// If any, the replacement variable is always reduced precision so add that decoration to
// fixedVaryingId.
if (mOptions.useSpirvVaryingPrecisionFixer && info->useRelaxedPrecision)
{
mVaryingPrecisionFixer.addDecorate(id, mSpirvBlobOut);
}
// Add component decoration, if any.
if (info->component != ShaderInterfaceVariableInfo::kInvalid)
{
spirv::WriteDecorate(mSpirvBlobOut, id, spv::DecorationComponent,
{spirv::LiteralInteger(info->component)});
}
// Add index decoration, if any.
if (info->index != ShaderInterfaceVariableInfo::kInvalid)
{
spirv::WriteDecorate(mSpirvBlobOut, id, spv::DecorationIndex,
{spirv::LiteralInteger(info->index)});
}
// Add Xfb decorations, if any.
if (mOptions.isTransformFeedbackStage && info->hasTransformFeedback)
{
const XFBInterfaceVariableInfo &xfbInfo = mVariableInfoMap.getXFBDataForVariableInfo(info);
mXfbCodeGenerator.addDecorate(xfbInfo, id, mSpirvBlobOut);
}
return TransformationState::Transformed;
}
TransformationState SpirvTransformer::transformMemberDecorate(const uint32_t *instruction)
{
spirv::IdRef typeId;
spirv::LiteralInteger member;
spv::Decoration decoration;
spirv::ParseMemberDecorate(instruction, &typeId, &member, &decoration, nullptr);
if (mPerVertexTrimmer.transformMemberDecorate(typeId, member, decoration) ==
TransformationState::Transformed)
{
return TransformationState::Transformed;
}
ASSERT(typeId < mVariableInfoById.size());
const ShaderInterfaceVariableInfo *info = mVariableInfoById[typeId];
return mXfbCodeGenerator.transformMemberDecorate(info, mOptions.shaderType, typeId, member,
decoration, mSpirvBlobOut);
}
TransformationState SpirvTransformer::transformCapability(const uint32_t *instruction)
{
spv::Capability capability;
spirv::ParseCapability(instruction, &capability);
TransformationState xfbTransformState =
mXfbCodeGenerator.transformCapability(capability, mSpirvBlobOut);
ASSERT(xfbTransformState == TransformationState::Unchanged);
TransformationState multiSampleTransformState = mMultisampleTransformer.transformCapability(
mNonSemanticInstructions, capability, mSpirvBlobOut);
ASSERT(multiSampleTransformState == TransformationState::Unchanged);
return TransformationState::Unchanged;
}
TransformationState SpirvTransformer::transformName(const uint32_t *instruction)
{
spirv::IdRef id;
spirv::LiteralString name;
spirv::ParseName(instruction, &id, &name);
return mXfbCodeGenerator.transformName(id, name);
}
TransformationState SpirvTransformer::transformMemberName(const uint32_t *instruction)
{
spirv::IdRef id;
spirv::LiteralInteger member;
spirv::LiteralString name;
spirv::ParseMemberName(instruction, &id, &member, &name);
if (mXfbCodeGenerator.transformMemberName(id, member, name) == TransformationState::Transformed)
{
return TransformationState::Transformed;
}
return mPerVertexTrimmer.transformMemberName(id, member, name);
}
TransformationState SpirvTransformer::transformEntryPoint(const uint32_t *instruction)
{
spv::ExecutionModel executionModel;
spirv::IdRef entryPointId;
spirv::LiteralString name;
spirv::IdRefList interfaceList;
spirv::ParseEntryPoint(instruction, &executionModel, &entryPointId, &name, &interfaceList);
// Should only have one EntryPoint
ASSERT(entryPointId == ID::EntryPoint);
mInactiveVaryingRemover.modifyEntryPointInterfaceList(mVariableInfoById, mOptions.shaderType,
&interfaceList);
if (mOptions.shaderType == gl::ShaderType::Fragment)
{
mSecondaryOutputTransformer.modifyEntryPointInterfaceList(mVariableInfoById, &interfaceList,
mSpirvBlobOut);
}
if (mOptions.useSpirvVaryingPrecisionFixer)
{
mVaryingPrecisionFixer.modifyEntryPointInterfaceList(&interfaceList);
}
mMultisampleTransformer.modifyEntryPointInterfaceList(mNonSemanticInstructions, &interfaceList,
mSpirvBlobOut);
// Write the entry point with the inactive interface variables removed.
spirv::WriteEntryPoint(mSpirvBlobOut, executionModel, ID::EntryPoint, name, interfaceList);
// Add an OpExecutionMode Xfb instruction if necessary.
mXfbCodeGenerator.addExecutionMode(ID::EntryPoint, mSpirvBlobOut);
return TransformationState::Transformed;
}
TransformationState SpirvTransformer::transformTypePointer(const uint32_t *instruction)
{
spirv::IdResult id;
spv::StorageClass storageClass;
spirv::IdRef typeId;
spirv::ParseTypePointer(instruction, &id, &storageClass, &typeId);
if (mInactiveVaryingRemover.transformTypePointer(id, storageClass, typeId, mSpirvBlobOut) ==
TransformationState::Transformed)
{
return TransformationState::Transformed;
}
ASSERT(id < mVariableInfoById.size());
const ShaderInterfaceVariableInfo *info = mVariableInfoById[id];
return mXfbCodeGenerator.transformTypePointer(info, mOptions.shaderType, id, storageClass,
typeId, mSpirvBlobOut);
}
TransformationState SpirvTransformer::transformExtension(const uint32_t *instruction)
{
// Drop the OpExtension "SPV_KHR_non_semantic_info" extension instruction.
// SPV_KHR_non_semantic_info is used purely as a means of communication between the compiler and
// the SPIR-V transformer, and is stripped away before the SPIR-V is handed off to the driver.
spirv::LiteralString name;
spirv::ParseExtension(instruction, &name);
return strcmp(name, "SPV_KHR_non_semantic_info") == 0 ? TransformationState::Transformed
: TransformationState::Unchanged;
}
TransformationState SpirvTransformer::transformExtInstImport(const uint32_t *instruction)
{
// Drop the OpExtInstImport "NonSemantic.ANGLE" instruction.
spirv::IdResult id;
spirv::LiteralString name;
ParseExtInstImport(instruction, &id, &name);
return id == sh::vk::spirv::kIdNonSemanticInstructionSet ? TransformationState::Transformed
: TransformationState::Unchanged;
}
TransformationState SpirvTransformer::transformExtInst(const uint32_t *instruction)
{
sh::vk::spirv::NonSemanticInstruction inst;
if (!mNonSemanticInstructions.visitExtInst(instruction, &inst))
{
return TransformationState::Unchanged;
}
switch (inst)
{
case sh::vk::spirv::kNonSemanticOverview:
// Declare anything that we need but didn't find there already.
writePendingDeclarations();
break;
case sh::vk::spirv::kNonSemanticEnter:
// If there are any precision mismatches that need to be handled, temporary global
// variables are created with the original precision. Initialize those variables from
// the varyings at the beginning of the shader.
writeInputPreamble();
break;
case sh::vk::spirv::kNonSemanticOutput:
// Generate gl_Position transformations and transform feedback capture (through
// extension) before return or EmitVertex(). Additionally, if there are any precision
// mismatches that need to be ahendled, write the temporary variables that hold varyings
// data. Copy a secondary fragment output value if it was declared as an array.
writeOutputPrologue();
break;
case sh::vk::spirv::kNonSemanticTransformFeedbackEmulation:
// Transform feedback emulation is written to a designated function. Allow its code to
// be generated if this is the right function.
if (mOptions.isTransformFeedbackStage)
{
mXfbCodeGenerator.writeTransformFeedbackEmulationOutput(
mInactiveVaryingRemover, mVaryingPrecisionFixer,
mOptions.useSpirvVaryingPrecisionFixer, mSpirvBlobOut);
}
break;
default:
UNREACHABLE();
break;
}
// Drop the instruction if this is the last pass
return mNonSemanticInstructions.transformExtInst(instruction);
}
TransformationState SpirvTransformer::transformTypeStruct(const uint32_t *instruction)
{
spirv::IdResult id;
spirv::IdRefList memberList;
ParseTypeStruct(instruction, &id, &memberList);
if (mPerVertexTrimmer.transformTypeStruct(id, &memberList, mSpirvBlobOut) ==
TransformationState::Transformed)
{
return TransformationState::Transformed;
}
ASSERT(id < mVariableInfoById.size());
const ShaderInterfaceVariableInfo *info = mVariableInfoById[id];
return mXfbCodeGenerator.transformTypeStruct(info, mOptions.shaderType, id, memberList,
mSpirvBlobOut);
}
TransformationState SpirvTransformer::transformVariable(const uint32_t *instruction)
{
spirv::IdResultType typeId;
spirv::IdResult id;
spv::StorageClass storageClass;
spirv::ParseVariable(instruction, &typeId, &id, &storageClass, nullptr);
const ShaderInterfaceVariableInfo *info = mVariableInfoById[id];
// If variable is not a shader interface variable that needs modification, there's nothing to
// do.
if (info == nullptr)
{
return TransformationState::Unchanged;
}
if (mOptions.shaderType == gl::ShaderType::Fragment && storageClass == spv::StorageClassOutput)
{
// If present, make the secondary fragment output array
// private and declare a non-array output instead.
if (mSecondaryOutputTransformer.transformVariable(
typeId, mInactiveVaryingRemover.getTransformedPrivateType(typeId), id,
mSpirvBlobOut) == TransformationState::Transformed)
{
return TransformationState::Transformed;
}
}
// Furthermore, if it's not an inactive varying output, there's nothing to do. Note that
// inactive varying inputs are already pruned by the translator.
// However, input or output storage class for interface block will not be pruned when a shader
// is compiled separately.
if (info->activeStages[mOptions.shaderType])
{
if (mOptions.useSpirvVaryingPrecisionFixer &&
mVaryingPrecisionFixer.transformVariable(
*info, typeId, id, storageClass, mSpirvBlobOut) == TransformationState::Transformed)
{
// Make original variable a private global
return mInactiveVaryingRemover.transformVariable(typeId, id, storageClass,
mSpirvBlobOut);
}
return TransformationState::Unchanged;
}
if (mXfbCodeGenerator.transformVariable(*info, mVariableInfoMap, mOptions.shaderType, typeId,
id, storageClass) == TransformationState::Transformed)
{
return TransformationState::Transformed;
}
// The variable is inactive. Output a modified variable declaration, where the type is the
// corresponding type with the Private storage class.
return mInactiveVaryingRemover.transformVariable(typeId, id, storageClass, mSpirvBlobOut);
}
TransformationState SpirvTransformer::transformTypeImage(const uint32_t *instruction)
{
return mMultisampleTransformer.transformTypeImage(instruction, mSpirvBlobOut);
}
TransformationState SpirvTransformer::transformImageRead(const uint32_t *instruction)
{
return mMultisampleTransformer.transformImageRead(instruction, mSpirvBlobOut);
}
TransformationState SpirvTransformer::transformAccessChain(const uint32_t *instruction)
{
spirv::IdResultType typeId;
spirv::IdResult id;
spirv::IdRef baseId;
spirv::IdRefList indexList;
spirv::ParseAccessChain(instruction, &typeId, &id, &baseId, &indexList);
// If not accessing an inactive output varying, nothing to do.
const ShaderInterfaceVariableInfo *info = mVariableInfoById[baseId];
if (info == nullptr)
{
return TransformationState::Unchanged;
}
if (mOptions.shaderType == gl::ShaderType::Fragment)
{
// Update the type used for accessing the secondary fragment output array.
if (mSecondaryOutputTransformer.transformAccessChain(
mInactiveVaryingRemover.getTransformedPrivateType(typeId), id, baseId, indexList,
mSpirvBlobOut) == TransformationState::Transformed)
{
return TransformationState::Transformed;
}
}
if (mOptions.useSpirvVaryingPrecisionFixer)
{
if (info->activeStages[mOptions.shaderType] && !info->useRelaxedPrecision)
{
return TransformationState::Unchanged;
}
}
else
{
if (info->activeStages[mOptions.shaderType])
{
return TransformationState::Unchanged;
}
}
return mInactiveVaryingRemover.transformAccessChain(typeId, id, baseId, indexList,
mSpirvBlobOut);
}
struct AliasingAttributeMap
{
// The SPIR-V id of the aliasing attribute with the most components. This attribute will be
// used to read from this location instead of every aliasing one.
spirv::IdRef attribute;
// SPIR-V ids of aliasing attributes.
std::vector<spirv::IdRef> aliasingAttributes;
};
void ValidateShaderInterfaceVariableIsAttribute(const ShaderInterfaceVariableInfo *info)
{
ASSERT(info);
ASSERT(info->activeStages[gl::ShaderType::Vertex]);
ASSERT(info->attributeComponentCount > 0);
ASSERT(info->attributeLocationCount > 0);
ASSERT(info->location != ShaderInterfaceVariableInfo::kInvalid);
}
void ValidateIsAliasingAttribute(const AliasingAttributeMap *aliasingMap, uint32_t id)
{
ASSERT(id != aliasingMap->attribute);
ASSERT(std::find(aliasingMap->aliasingAttributes.begin(), aliasingMap->aliasingAttributes.end(),
id) != aliasingMap->aliasingAttributes.end());
}
// A transformation that resolves vertex attribute aliases. Note that vertex attribute aliasing is
// only allowed in GLSL ES 100, where the attribute types can only be one of float, vec2, vec3,
// vec4, mat2, mat3, and mat4. Matrix attributes are handled by expanding them to multiple vector
// attributes, each occupying one location.
class SpirvVertexAttributeAliasingTransformer final : public SpirvTransformerBase
{
public:
SpirvVertexAttributeAliasingTransformer(
const spirv::Blob &spirvBlobIn,
const ShaderInterfaceVariableInfoMap &variableInfoMap,
std::vector<const ShaderInterfaceVariableInfo *> &&variableInfoById,
spirv::Blob *spirvBlobOut)
: SpirvTransformerBase(spirvBlobIn, variableInfoMap, spirvBlobOut),
mNonSemanticInstructions(true)
{
mVariableInfoById = std::move(variableInfoById);
}
void transform();
private:
// Preprocess aliasing attributes in preparation for their removal.
void preprocessAliasingAttributes();
// Transform instructions:
void transformInstruction();
// Helpers:
spirv::IdRef getAliasingAttributeReplacementId(spirv::IdRef aliasingId, uint32_t offset) const;
bool isMatrixAttribute(spirv::IdRef id) const;
// Instructions that are purely informational:
void visitTypePointer(const uint32_t *instruction);
// Instructions that potentially need transformation. They return true if the instruction is
// transformed. If false is returned, the instruction should be copied as-is.
TransformationState transformEntryPoint(const uint32_t *instruction);
TransformationState transformExtInst(const uint32_t *instruction);
TransformationState transformName(const uint32_t *instruction);
TransformationState transformDecorate(const uint32_t *instruction);
TransformationState transformVariable(const uint32_t *instruction);
TransformationState transformAccessChain(const uint32_t *instruction);
void transformLoadHelper(spirv::IdRef pointerId,
spirv::IdRef typeId,
spirv::IdRef replacementId,
spirv::IdRef resultId);
TransformationState transformLoad(const uint32_t *instruction);
void declareExpandedMatrixVectors();
void writeExpandedMatrixInitialization();
// Transformation state:
// Map of aliasing attributes per location.
gl::AttribArray<AliasingAttributeMap> mAliasingAttributeMap;
// For each id, this map indicates whether it refers to an aliasing attribute that needs to be
// removed.
std::vector<bool> mIsAliasingAttributeById;
// Matrix attributes are split into vectors, each occupying one location. The SPIR-V
// declaration would need to change from:
//
// %type = OpTypeMatrix %vectorType N
// %matrixType = OpTypePointer Input %type
// %matrix = OpVariable %matrixType Input
//
// to:
//
// %matrixType = OpTypePointer Private %type
// %matrix = OpVariable %matrixType Private
//
// %vecType = OpTypePointer Input %vectorType
//
// %vec0 = OpVariable %vecType Input
// ...
// %vecN-1 = OpVariable %vecType Input
//
// For each id %matrix (which corresponds to a matrix attribute), this map contains %vec0. The
// ids of the split vectors are consecutive, so %veci == %vec0 + i. %veciType is taken from
// mInputTypePointers.
std::vector<spirv::IdRef> mExpandedMatrixFirstVectorIdById;
// Id of attribute types; float and veci.
spirv::IdRef floatType(uint32_t componentCount)
{
static_assert(sh::vk::spirv::kIdVec2 == sh::vk::spirv::kIdFloat + 1);
static_assert(sh::vk::spirv::kIdVec3 == sh::vk::spirv::kIdFloat + 2);
static_assert(sh::vk::spirv::kIdVec4 == sh::vk::spirv::kIdFloat + 3);
ASSERT(componentCount <= 4);
return spirv::IdRef(sh::vk::spirv::kIdFloat + (componentCount - 1));
}
// Id of matrix attribute types. Note that only square matrices are possible as attributes in
// GLSL ES 1.00.
spirv::IdRef matrixType(uint32_t dimension)
{
static_assert(sh::vk::spirv::kIdMat3 == sh::vk::spirv::kIdMat2 + 1);
static_assert(sh::vk::spirv::kIdMat4 == sh::vk::spirv::kIdMat2 + 2);
ASSERT(dimension >= 2 && dimension <= 4);
return spirv::IdRef(sh::vk::spirv::kIdMat2 + (dimension - 2));
}
// Corresponding to floatType(), [i]: id of OpTypePointer Input %floatType(i). [0] is unused.
std::array<spirv::IdRef, 5> mInputTypePointers;
// Corresponding to floatType(), [i]: id of OpTypePointer Private %floatType(i). [0] is
// unused.
std::array<spirv::IdRef, 5> mPrivateFloatTypePointers;
// Corresponding to matrixType(), [i]: id of OpTypePointer Private %matrixType(i). [0] and
// [1] are unused.
std::array<spirv::IdRef, 5> mPrivateMatrixTypePointers;
SpirvNonSemanticInstructions mNonSemanticInstructions;
};
void SpirvVertexAttributeAliasingTransformer::transform()
{
onTransformBegin();
preprocessAliasingAttributes();
while (mCurrentWord < mSpirvBlobIn.size())
{
transformInstruction();
}
}
void SpirvVertexAttributeAliasingTransformer::preprocessAliasingAttributes()
{
const uint32_t indexBound = mSpirvBlobIn[spirv::kHeaderIndexIndexBound];
mVariableInfoById.resize(indexBound, nullptr);
mIsAliasingAttributeById.resize(indexBound, false);
mExpandedMatrixFirstVectorIdById.resize(indexBound);
// Go through attributes and find out which alias which.
for (uint32_t idIndex = spirv::kMinValidId; idIndex < indexBound; ++idIndex)
{
const spirv::IdRef id(idIndex);
const ShaderInterfaceVariableInfo *info = mVariableInfoById[id];
// Ignore non attribute ids.
if (info == nullptr || info->attributeComponentCount == 0)
{
continue;
}
ASSERT(info->activeStages[gl::ShaderType::Vertex]);
ASSERT(info->location != ShaderInterfaceVariableInfo::kInvalid);
const bool isMatrixAttribute = info->attributeLocationCount > 1;
for (uint32_t offset = 0; offset < info->attributeLocationCount; ++offset)
{
uint32_t location = info->location + offset;
ASSERT(location < mAliasingAttributeMap.size());
spirv::IdRef attributeId(id);
// If this is a matrix attribute, expand it to vectors.
if (isMatrixAttribute)
{
const spirv::IdRef matrixId(id);
// Get a new id for this location and associate it with the matrix.
attributeId = getNewId();
if (offset == 0)
{
mExpandedMatrixFirstVectorIdById[matrixId] = attributeId;
}
// The ids are consecutive.
ASSERT(attributeId == mExpandedMatrixFirstVectorIdById[matrixId] + offset);
mIsAliasingAttributeById.resize(attributeId + 1, false);
mVariableInfoById.resize(attributeId + 1, nullptr);
mVariableInfoById[attributeId] = info;
}
AliasingAttributeMap *aliasingMap = &mAliasingAttributeMap[location];
// If this is the first attribute in this location, remember it.
if (!aliasingMap->attribute.valid())
{
aliasingMap->attribute = attributeId;
continue;
}
// Otherwise, either add it to the list of aliasing attributes, or replace the main
// attribute (and add that to the list of aliasing attributes). The one with the
// largest number of components is used as the main attribute.
const ShaderInterfaceVariableInfo *curMainAttribute =
mVariableInfoById[aliasingMap->attribute];
ASSERT(curMainAttribute != nullptr && curMainAttribute->attributeComponentCount > 0);
spirv::IdRef aliasingId;
if (info->attributeComponentCount > curMainAttribute->attributeComponentCount)
{
aliasingId = aliasingMap->attribute;
aliasingMap->attribute = attributeId;
}
else
{
aliasingId = attributeId;
}
aliasingMap->aliasingAttributes.push_back(aliasingId);
ASSERT(!mIsAliasingAttributeById[aliasingId]);
mIsAliasingAttributeById[aliasingId] = true;
}
}
}
void SpirvVertexAttributeAliasingTransformer::transformInstruction()
{
uint32_t wordCount;
spv::Op opCode;
const uint32_t *instruction = getCurrentInstruction(&opCode, &wordCount);
if (opCode == spv::OpFunction)
{
// SPIR-V is structured in sections. Function declarations come last.
mIsInFunctionSection = true;
}
// Only look at interesting instructions.
TransformationState transformationState = TransformationState::Unchanged;
if (mIsInFunctionSection)
{
// Look at in-function opcodes.
switch (opCode)
{
case spv::OpExtInst:
transformationState = transformExtInst(instruction);
break;
case spv::OpAccessChain:
case spv::OpInBoundsAccessChain:
transformationState = transformAccessChain(instruction);
break;
case spv::OpLoad:
transformationState = transformLoad(instruction);
break;
default:
break;
}
}
else
{
// Look at global declaration opcodes.
switch (opCode)
{
// Informational instructions:
case spv::OpTypePointer:
visitTypePointer(instruction);
break;
// Instructions that may need transformation:
case spv::OpEntryPoint:
transformationState = transformEntryPoint(instruction);
break;
case spv::OpExtInst:
transformationState = transformExtInst(instruction);
break;
case spv::OpName:
transformationState = transformName(instruction);
break;
case spv::OpDecorate:
transformationState = transformDecorate(instruction);
break;
case spv::OpVariable:
transformationState = transformVariable(instruction);
break;
default:
break;
}
}
// If the instruction was not transformed, copy it to output as is.
if (transformationState == TransformationState::Unchanged)
{
copyInstruction(instruction, wordCount);
}
// Advance to next instruction.
mCurrentWord += wordCount;
}
spirv::IdRef SpirvVertexAttributeAliasingTransformer::getAliasingAttributeReplacementId(
spirv::IdRef aliasingId,
uint32_t offset) const
{
// Get variable info corresponding to the aliasing attribute.
const ShaderInterfaceVariableInfo *aliasingInfo = mVariableInfoById[aliasingId];
ValidateShaderInterfaceVariableIsAttribute(aliasingInfo);
// Find the replacement attribute.
const AliasingAttributeMap *aliasingMap =
&mAliasingAttributeMap[aliasingInfo->location + offset];
ValidateIsAliasingAttribute(aliasingMap, aliasingId);
const spirv::IdRef replacementId(aliasingMap->attribute);
ASSERT(replacementId.valid() && replacementId < mIsAliasingAttributeById.size());
ASSERT(!mIsAliasingAttributeById[replacementId]);
return replacementId;
}
bool SpirvVertexAttributeAliasingTransformer::isMatrixAttribute(spirv::IdRef id) const
{
return mExpandedMatrixFirstVectorIdById[id].valid();
}
void SpirvVertexAttributeAliasingTransformer::visitTypePointer(const uint32_t *instruction)
{
spirv::IdResult id;
spv::StorageClass storageClass;
spirv::IdRef typeId;
spirv::ParseTypePointer(instruction, &id, &storageClass, &typeId);
// Only interested in OpTypePointer Input %vecN, where %vecN is the id of OpTypeVector %f32 N,
// as well as OpTypePointer Private %matN, where %matN is the id of OpTypeMatrix %vecN N.
// This is only for matN types (as allowed by GLSL ES 1.00), so N >= 2.
if (storageClass == spv::StorageClassInput)
{
for (uint32_t n = 2; n <= 4; ++n)
{
if (typeId == floatType(n))
{
ASSERT(!mInputTypePointers[n].valid());
mInputTypePointers[n] = id;
break;
}
}
}
else if (storageClass == spv::StorageClassPrivate)
{
for (uint32_t n = 2; n <= 4; ++n)
{
// Note that Private types may not be unique, as the previous transformation can
// generate duplicates.
if (typeId == floatType(n))
{
mPrivateFloatTypePointers[n] = id;
break;
}
if (typeId == matrixType(n))
{
mPrivateMatrixTypePointers[n] = id;
break;
}
}
}
}
TransformationState SpirvVertexAttributeAliasingTransformer::transformEntryPoint(
const uint32_t *instruction)
{
// Remove aliasing attributes from the shader interface declaration.
spv::ExecutionModel executionModel;
spirv::IdRef entryPointId;
spirv::LiteralString name;
spirv::IdRefList interfaceList;
spirv::ParseEntryPoint(instruction, &executionModel, &entryPointId, &name, &interfaceList);
// Should only have one EntryPoint
ASSERT(entryPointId == ID::EntryPoint);
// As a first pass, filter out matrix attributes and append their replacement vectors.
size_t originalInterfaceListSize = interfaceList.size();
for (size_t index = 0; index < originalInterfaceListSize; ++index)
{
const spirv::IdRef matrixId(interfaceList[index]);
if (!mExpandedMatrixFirstVectorIdById[matrixId].valid())
{
continue;
}
const ShaderInterfaceVariableInfo *info = mVariableInfoById[matrixId];
ValidateShaderInterfaceVariableIsAttribute(info);
// Replace the matrix id with its first vector id.
const spirv::IdRef vec0Id(mExpandedMatrixFirstVectorIdById[matrixId]);
interfaceList[index] = vec0Id;
// Append the rest of the vectors to the entry point.
for (uint32_t offset = 1; offset < info->attributeLocationCount; ++offset)
{
const spirv::IdRef vecId(vec0Id + offset);
interfaceList.push_back(vecId);
}
}
// Filter out aliasing attributes from entry point interface declaration.
size_t writeIndex = 0;
for (size_t index = 0; index < interfaceList.size(); ++index)
{
const spirv::IdRef id(interfaceList[index]);
// If this is an attribute that's aliasing another one in the same location, remove it.
if (mIsAliasingAttributeById[id])
{
const ShaderInterfaceVariableInfo *info = mVariableInfoById[id];
ValidateShaderInterfaceVariableIsAttribute(info);
// The following assertion is only valid for non-matrix attributes.
if (info->attributeLocationCount == 1)
{
const AliasingAttributeMap *aliasingMap = &mAliasingAttributeMap[info->location];
ValidateIsAliasingAttribute(aliasingMap, id);
}
continue;
}
interfaceList[writeIndex] = id;
++writeIndex;
}
// Update the number of interface variables.
interfaceList.resize_down(writeIndex);
// Write the entry point with the aliasing attributes removed.
spirv::WriteEntryPoint(mSpirvBlobOut, executionModel, ID::EntryPoint, name, interfaceList);
return TransformationState::Transformed;
}
TransformationState SpirvVertexAttributeAliasingTransformer::transformExtInst(
const uint32_t *instruction)
{
sh::vk::spirv::NonSemanticInstruction inst;
if (!mNonSemanticInstructions.visitExtInst(instruction, &inst))
{
return TransformationState::Unchanged;
}
switch (inst)
{
case sh::vk::spirv::kNonSemanticOverview:
// Declare the expanded matrix variables
declareExpandedMatrixVectors();
break;
case sh::vk::spirv::kNonSemanticEnter:
// The matrix attribute declarations have been changed to have Private storage class,
// and they are initialized from the expanded (and potentially aliased) Input vectors.
// This is done at the beginning of the entry point.
writeExpandedMatrixInitialization();
break;
case sh::vk::spirv::kNonSemanticOutput:
case sh::vk::spirv::kNonSemanticTransformFeedbackEmulation:
// Unused by this transformation
break;
default:
UNREACHABLE();
break;
}
// Drop the instruction if this is the last pass
return mNonSemanticInstructions.transformExtInst(instruction);
}
TransformationState SpirvVertexAttributeAliasingTransformer::transformName(
const uint32_t *instruction)
{
spirv::IdRef id;
spirv::LiteralString name;
spirv::ParseName(instruction, &id, &name);
// If id is not that of an aliasing attribute, there's nothing to do.
ASSERT(id < mIsAliasingAttributeById.size());
if (!mIsAliasingAttributeById[id])
{
return TransformationState::Unchanged;
}
// Drop debug annotations for this id.
return TransformationState::Transformed;
}
TransformationState SpirvVertexAttributeAliasingTransformer::transformDecorate(
const uint32_t *instruction)
{
spirv::IdRef id;
spv::Decoration decoration;
spirv::ParseDecorate(instruction, &id, &decoration, nullptr);
if (isMatrixAttribute(id))
{
// If it's a matrix attribute, it's expanded to multiple vectors. Insert the Location
// decorations for these vectors here.
// Keep all decorations except for Location.
if (decoration != spv::DecorationLocation)
{
return TransformationState::Unchanged;
}
const ShaderInterfaceVariableInfo *info = mVariableInfoById[id];
ValidateShaderInterfaceVariableIsAttribute(info);
const spirv::IdRef vec0Id(mExpandedMatrixFirstVectorIdById[id]);
ASSERT(vec0Id.valid());
for (uint32_t offset = 0; offset < info->attributeLocationCount; ++offset)
{
const spirv::IdRef vecId(vec0Id + offset);
if (mIsAliasingAttributeById[vecId])
{
continue;
}
spirv::WriteDecorate(mSpirvBlobOut, vecId, decoration,
{spirv::LiteralInteger(info->location + offset)});
}
}
else
{
// If id is not that of an active attribute, there's nothing to do.
const ShaderInterfaceVariableInfo *info = mVariableInfoById[id];
if (info == nullptr || info->attributeComponentCount == 0 ||
!info->activeStages[gl::ShaderType::Vertex])
{
return TransformationState::Unchanged;
}
// Always drop RelaxedPrecision from input attributes. The temporary variable the attribute
// is loaded into has RelaxedPrecision and will implicitly convert.
if (decoration == spv::DecorationRelaxedPrecision)
{
return TransformationState::Transformed;
}
// If id is not that of an aliasing attribute, there's nothing else to do.
ASSERT(id < mIsAliasingAttributeById.size());
if (!mIsAliasingAttributeById[id])
{
return TransformationState::Unchanged;
}
}
// Drop every decoration for this id.
return TransformationState::Transformed;
}
TransformationState SpirvVertexAttributeAliasingTransformer::transformVariable(
const uint32_t *instruction)
{
spirv::IdResultType typeId;
spirv::IdResult id;
spv::StorageClass storageClass;
spirv::ParseVariable(instruction, &typeId, &id, &storageClass, nullptr);
if (!isMatrixAttribute(id))
{
// If id is not that of an aliasing attribute, there's nothing to do. Note that matrix
// declarations are always replaced.
ASSERT(id < mIsAliasingAttributeById.size());
if (!mIsAliasingAttributeById[id])
{
return TransformationState::Unchanged;
}
}
ASSERT(storageClass == spv::StorageClassInput);
// Drop the declaration.
return TransformationState::Transformed;
}
TransformationState SpirvVertexAttributeAliasingTransformer::transformAccessChain(
const uint32_t *instruction)
{
spirv::IdResultType typeId;
spirv::IdResult id;
spirv::IdRef baseId;
spirv::IdRefList indexList;
spirv::ParseAccessChain(instruction, &typeId, &id, &baseId, &indexList);
if (isMatrixAttribute(baseId))
{
// Write a modified OpAccessChain instruction. Only modification is that the %type is
// replaced with the Private version of it. If there is one %index, that would be a vector
// type, and if there are two %index'es, it's a float type.
spirv::IdRef replacementTypeId;
if (indexList.size() == 1)
{
// If indexed once, it uses a vector type.
const ShaderInterfaceVariableInfo *info = mVariableInfoById[baseId];
ValidateShaderInterfaceVariableIsAttribute(info);
const uint32_t componentCount = info->attributeComponentCount;
// %type must have been the Input vector type with the matrice's component size.
ASSERT(typeId == mInputTypePointers[componentCount]);
// Replace the type with the corresponding Private one.
replacementTypeId = mPrivateFloatTypePointers[componentCount];
}
else
{
// If indexed twice, it uses the float type.
ASSERT(indexList.size() == 2);
// Replace the type with the Private pointer to float32.
replacementTypeId = mPrivateFloatTypePointers[1];
}
spirv::WriteAccessChain(mSpirvBlobOut, replacementTypeId, id, baseId, indexList);
}
else
{
// If base id is not that of an aliasing attribute, there's nothing to do.
ASSERT(baseId < mIsAliasingAttributeById.size());
if (!mIsAliasingAttributeById[baseId])
{
return TransformationState::Unchanged;
}
// Find the replacement attribute for the aliasing one.
const spirv::IdRef replacementId(getAliasingAttributeReplacementId(baseId, 0));
// Get variable info corresponding to the replacement attribute.
const ShaderInterfaceVariableInfo *replacementInfo = mVariableInfoById[replacementId];
ValidateShaderInterfaceVariableIsAttribute(replacementInfo);
// Write a modified OpAccessChain instruction. Currently, the instruction is:
//
// %id = OpAccessChain %type %base %index
//
// This is modified to:
//
// %id = OpAccessChain %type %replacement %index
//
// Note that the replacement has at least as many components as the aliasing attribute,
// and both attributes start at component 0 (GLSL ES restriction). So, indexing the
// replacement attribute with the same index yields the same result and type.
spirv::WriteAccessChain(mSpirvBlobOut, typeId, id, replacementId, indexList);
}
return TransformationState::Transformed;
}
void SpirvVertexAttributeAliasingTransformer::transformLoadHelper(spirv::IdRef pointerId,
spirv::IdRef typeId,
spirv::IdRef replacementId,
spirv::IdRef resultId)
{
// Get variable info corresponding to the replacement attribute.
const ShaderInterfaceVariableInfo *replacementInfo = mVariableInfoById[replacementId];
ValidateShaderInterfaceVariableIsAttribute(replacementInfo);
// Currently, the instruction is:
//
// %id = OpLoad %type %pointer
//
// This is modified to:
//
// %newId = OpLoad %replacementType %replacement
//
const spirv::IdRef loadResultId(getNewId());
const spirv::IdRef replacementTypeId(floatType(replacementInfo->attributeComponentCount));
ASSERT(replacementTypeId.valid());
spirv::WriteLoad(mSpirvBlobOut, replacementTypeId, loadResultId, replacementId, nullptr);
// If swizzle is not necessary, assign %newId to %resultId.
const ShaderInterfaceVariableInfo *aliasingInfo = mVariableInfoById[pointerId];
if (aliasingInfo->attributeComponentCount == replacementInfo->attributeComponentCount)
{
spirv::WriteCopyObject(mSpirvBlobOut, typeId, resultId, loadResultId);
return;
}
// Take as many components from the replacement as the aliasing attribute wanted. This is done
// by either of the following instructions:
//
// - If aliasing attribute has only one component:
//
// %resultId = OpCompositeExtract %floatType %newId 0
//
// - If aliasing attribute has more than one component:
//
// %resultId = OpVectorShuffle %vecType %newId %newId 0 1 ...
//
ASSERT(aliasingInfo->attributeComponentCount < replacementInfo->attributeComponentCount);
ASSERT(floatType(aliasingInfo->attributeComponentCount) == typeId);
if (aliasingInfo->attributeComponentCount == 1)
{
spirv::WriteCompositeExtract(mSpirvBlobOut, typeId, resultId, loadResultId,
{spirv::LiteralInteger(0)});
}
else
{
spirv::LiteralIntegerList swizzle = {spirv::LiteralInteger(0), spirv::LiteralInteger(1),
spirv::LiteralInteger(2), spirv::LiteralInteger(3)};
swizzle.resize_down(aliasingInfo->attributeComponentCount);
spirv::WriteVectorShuffle(mSpirvBlobOut, typeId, resultId, loadResultId, loadResultId,
swizzle);
}
}
TransformationState SpirvVertexAttributeAliasingTransformer::transformLoad(
const uint32_t *instruction)
{
spirv::IdResultType typeId;
spirv::IdResult id;
spirv::IdRef pointerId;
ParseLoad(instruction, &typeId, &id, &pointerId, nullptr);
// Currently, the instruction is:
//
// %id = OpLoad %type %pointer
//
// If non-matrix, this is modifed to load from the aliasing vector instead if aliasing.
//
// If matrix, this is modified such that %type points to the Private version of it.
//
if (isMatrixAttribute(pointerId))
{
const ShaderInterfaceVariableInfo *info = mVariableInfoById[pointerId];
ValidateShaderInterfaceVariableIsAttribute(info);
const spirv::IdRef replacementTypeId(matrixType(info->attributeLocationCount));
spirv::WriteLoad(mSpirvBlobOut, replacementTypeId, id, pointerId, nullptr);
}
else
{
// If pointer id is not that of an aliasing attribute, there's nothing to do.
ASSERT(pointerId < mIsAliasingAttributeById.size());
if (!mIsAliasingAttributeById[pointerId])
{
return TransformationState::Unchanged;
}
// Find the replacement attribute for the aliasing one.
const spirv::IdRef replacementId(getAliasingAttributeReplacementId(pointerId, 0));
// Replace the load instruction by a load from the replacement id.
transformLoadHelper(pointerId, typeId, replacementId, id);
}
return TransformationState::Transformed;
}
void SpirvVertexAttributeAliasingTransformer::declareExpandedMatrixVectors()
{
// Go through matrix attributes and expand them.
for (uint32_t matrixIdIndex = spirv::kMinValidId;
matrixIdIndex < mExpandedMatrixFirstVectorIdById.size(); ++matrixIdIndex)
{
const spirv::IdRef matrixId(matrixIdIndex);
if (!mExpandedMatrixFirstVectorIdById[matrixId].valid())
{
continue;
}
const spirv::IdRef vec0Id(mExpandedMatrixFirstVectorIdById[matrixId]);
const ShaderInterfaceVariableInfo *info = mVariableInfoById[matrixId];
ValidateShaderInterfaceVariableIsAttribute(info);
// Need to generate the following:
//
// %privateType = OpTypePointer Private %matrixType
// %id = OpVariable %privateType Private
// %vecType = OpTypePointer %vecType Input
// %vec0 = OpVariable %vecType Input
// ...
// %vecN-1 = OpVariable %vecType Input
const uint32_t componentCount = info->attributeComponentCount;
const uint32_t locationCount = info->attributeLocationCount;
ASSERT(componentCount == locationCount);
// OpTypePointer Private %matrixType
spirv::IdRef privateType(mPrivateMatrixTypePointers[locationCount]);
if (!privateType.valid())
{
privateType = getNewId();
mPrivateMatrixTypePointers[locationCount] = privateType;
spirv::WriteTypePointer(mSpirvBlobOut, privateType, spv::StorageClassPrivate,
matrixType(locationCount));
}
// OpVariable %privateType Private
spirv::WriteVariable(mSpirvBlobOut, privateType, matrixId, spv::StorageClassPrivate,
nullptr);
// If the OpTypePointer is not declared for the vector type corresponding to each location,
// declare it now.
//
// %vecType = OpTypePointer %vecType Input
spirv::IdRef inputType(mInputTypePointers[componentCount]);
if (!inputType.valid())
{
inputType = getNewId();
mInputTypePointers[componentCount] = inputType;
spirv::WriteTypePointer(mSpirvBlobOut, inputType, spv::StorageClassInput,
floatType(componentCount));
}
// Declare a vector for each column of the matrix.
for (uint32_t offset = 0; offset < info->attributeLocationCount; ++offset)
{
const spirv::IdRef vecId(vec0Id + offset);
if (!mIsAliasingAttributeById[vecId])
{
spirv::WriteVariable(mSpirvBlobOut, inputType, vecId, spv::StorageClassInput,
nullptr);
}
}
}
// Additionally, declare OpTypePointer Private %floatType(i) in case needed (used in
// Op*AccessChain instructions, if any).
for (uint32_t n = 1; n <= 4; ++n)
{
if (!mPrivateFloatTypePointers[n].valid())
{
const spirv::IdRef privateType(getNewId());
mPrivateFloatTypePointers[n] = privateType;
spirv::WriteTypePointer(mSpirvBlobOut, privateType, spv::StorageClassPrivate,
floatType(n));
}
}
}
void SpirvVertexAttributeAliasingTransformer::writeExpandedMatrixInitialization()
{
// Go through matrix attributes and initialize them. Note that their declaration is replaced
// with a Private storage class, but otherwise has the same id.
for (uint32_t matrixIdIndex = spirv::kMinValidId;
matrixIdIndex < mExpandedMatrixFirstVectorIdById.size(); ++matrixIdIndex)
{
const spirv::IdRef matrixId(matrixIdIndex);
if (!mExpandedMatrixFirstVectorIdById[matrixId].valid())
{
continue;
}
const spirv::IdRef vec0Id(mExpandedMatrixFirstVectorIdById[matrixId]);
// For every matrix, need to generate the following:
//
// %vec0Id = OpLoad %vecType %vec0Pointer
// ...
// %vecN-1Id = OpLoad %vecType %vecN-1Pointer
// %mat = OpCompositeConstruct %matrixType %vec0 ... %vecN-1
// OpStore %matrixId %mat
const ShaderInterfaceVariableInfo *info = mVariableInfoById[matrixId];
ValidateShaderInterfaceVariableIsAttribute(info);
spirv::IdRefList vecLoadIds;
const uint32_t locationCount = info->attributeLocationCount;
for (uint32_t offset = 0; offset < locationCount; ++offset)
{
const spirv::IdRef vecId(vec0Id + offset);
// Load into temporary, potentially through an aliasing vector.
spirv::IdRef replacementId(vecId);
ASSERT(vecId < mIsAliasingAttributeById.size());
if (mIsAliasingAttributeById[vecId])
{
replacementId = getAliasingAttributeReplacementId(vecId, offset);
}
// Write a load instruction from the replacement id.
vecLoadIds.push_back(getNewId());
transformLoadHelper(matrixId, floatType(info->attributeComponentCount), replacementId,
vecLoadIds.back());
}
// Aggregate the vector loads into a matrix.
const spirv::IdRef compositeId(getNewId());
spirv::WriteCompositeConstruct(mSpirvBlobOut, matrixType(locationCount), compositeId,
vecLoadIds);
// Store it in the private variable.
spirv::WriteStore(mSpirvBlobOut, matrixId, compositeId, nullptr);
}
}
} // anonymous namespace
SpvSourceOptions SpvCreateSourceOptions(const angle::FeaturesVk &features)
{
SpvSourceOptions options;
options.supportsTransformFeedbackExtension =
features.supportsTransformFeedbackExtension.enabled;
options.supportsTransformFeedbackEmulation = features.emulateTransformFeedback.enabled;
options.enableTransformFeedbackEmulation = options.supportsTransformFeedbackEmulation;
return options;
}
uint32_t SpvGetXfbBufferBlockId(const uint32_t bufferIndex)
{
ASSERT(bufferIndex < 4);
static_assert(sh::vk::spirv::ReservedIds::kIdXfbEmulationBufferBlockOne ==
sh::vk::spirv::ReservedIds::kIdXfbEmulationBufferBlockZero + 1);
static_assert(sh::vk::spirv::ReservedIds::kIdXfbEmulationBufferBlockTwo ==
sh::vk::spirv::ReservedIds::kIdXfbEmulationBufferBlockZero + 2);
static_assert(sh::vk::spirv::ReservedIds::kIdXfbEmulationBufferBlockThree ==
sh::vk::spirv::ReservedIds::kIdXfbEmulationBufferBlockZero + 3);
return sh::vk::spirv::ReservedIds::kIdXfbEmulationBufferBlockZero + bufferIndex;
}
void SpvAssignLocations(const SpvSourceOptions &options,
const gl::ProgramExecutable &programExecutable,
const gl::ProgramVaryingPacking &varyingPacking,
const gl::ShaderType transformFeedbackStage,
SpvProgramInterfaceInfo *programInterfaceInfo,
ShaderInterfaceVariableInfoMap *variableInfoMapOut)
{
const gl::ShaderBitSet shaderStages = programExecutable.getLinkedShaderStages();
// Assign outputs to the fragment shader, if any.
if (shaderStages[gl::ShaderType::Fragment] &&
programExecutable.hasLinkedShaderStage(gl::ShaderType::Fragment))
{
AssignOutputLocations(programExecutable, gl::ShaderType::Fragment, variableInfoMapOut);
}
// Assign attributes to the vertex shader, if any.
if (shaderStages[gl::ShaderType::Vertex] &&
programExecutable.hasLinkedShaderStage(gl::ShaderType::Vertex))
{
AssignAttributeLocations(programExecutable, gl::ShaderType::Vertex, variableInfoMapOut);
if (options.supportsTransformFeedbackEmulation)
{
// If transform feedback emulation is not enabled, mark all transform feedback output
// buffers as inactive.
const bool isTransformFeedbackStage =
transformFeedbackStage == gl::ShaderType::Vertex &&
options.enableTransformFeedbackEmulation &&
!programExecutable.getLinkedTransformFeedbackVaryings().empty();
AssignTransformFeedbackEmulationBindings(gl::ShaderType::Vertex, programExecutable,
isTransformFeedbackStage, programInterfaceInfo,
variableInfoMapOut);
}
}
gl::ShaderType frontShaderType = gl::ShaderType::InvalidEnum;
for (const gl::ShaderType shaderType : shaderStages)
{
if (programExecutable.hasLinkedGraphicsShader())
{
const gl::VaryingPacking &inputPacking = varyingPacking.getInputPacking(shaderType);
const gl::VaryingPacking &outputPacking = varyingPacking.getOutputPacking(shaderType);
// Assign varying locations.
if (shaderType != gl::ShaderType::Vertex)
{
AssignVaryingLocations(options, inputPacking, shaderType, frontShaderType,
programInterfaceInfo, variableInfoMapOut);
// Record active members of in gl_PerVertex.
if (shaderType != gl::ShaderType::Fragment &&
frontShaderType != gl::ShaderType::InvalidEnum)
{
// If an output builtin is active in the previous stage, assume it's active in
// the input of the current stage as well.
const gl::ShaderMap<gl::PerVertexMemberBitSet> &outputPerVertexActiveMembers =
inputPacking.getOutputPerVertexActiveMembers();
variableInfoMapOut->setInputPerVertexActiveMembers(
shaderType, outputPerVertexActiveMembers[frontShaderType]);
}
}
if (shaderType != gl::ShaderType::Fragment)
{
AssignVaryingLocations(options, outputPacking, shaderType, frontShaderType,
programInterfaceInfo, variableInfoMapOut);
// Record active members of out gl_PerVertex.
const gl::ShaderMap<gl::PerVertexMemberBitSet> &outputPerVertexActiveMembers =
outputPacking.getOutputPerVertexActiveMembers();
variableInfoMapOut->setOutputPerVertexActiveMembers(
shaderType, outputPerVertexActiveMembers[shaderType]);
}
// Assign qualifiers to all varyings captured by transform feedback
if (!programExecutable.getLinkedTransformFeedbackVaryings().empty() &&
shaderType == programExecutable.getLinkedTransformFeedbackStage())
{
AssignTransformFeedbackQualifiers(programExecutable, outputPacking, shaderType,
options.supportsTransformFeedbackExtension,
variableInfoMapOut);
}
}
frontShaderType = shaderType;
}
AssignUniformBindings(options, programExecutable, programInterfaceInfo, variableInfoMapOut);
AssignTextureBindings(options, programExecutable, programInterfaceInfo, variableInfoMapOut);
AssignNonTextureBindings(options, programExecutable, programInterfaceInfo, variableInfoMapOut);
}
void SpvAssignTransformFeedbackLocations(gl::ShaderType shaderType,
const gl::ProgramExecutable &programExecutable,
bool isTransformFeedbackStage,
SpvProgramInterfaceInfo *programInterfaceInfo,
ShaderInterfaceVariableInfoMap *variableInfoMapOut)
{
// The only varying that requires additional resources is gl_Position, as it's indirectly
// captured through ANGLEXfbPosition.
const std::vector<gl::TransformFeedbackVarying> &tfVaryings =
programExecutable.getLinkedTransformFeedbackVaryings();
bool capturesPosition = false;
if (isTransformFeedbackStage)
{
for (uint32_t varyingIndex = 0; varyingIndex < tfVaryings.size(); ++varyingIndex)
{
const gl::TransformFeedbackVarying &tfVarying = tfVaryings[varyingIndex];
const std::string &tfVaryingName = tfVarying.name;
if (tfVaryingName == "gl_Position")
{
ASSERT(tfVarying.isBuiltIn());
capturesPosition = true;
break;
}
}
}
if (capturesPosition)
{
AddLocationInfo(variableInfoMapOut, shaderType, sh::vk::spirv::kIdXfbExtensionPosition,
programInterfaceInfo->locationsUsedForXfbExtension, 0, 0, 0);
++programInterfaceInfo->locationsUsedForXfbExtension;
}
else
{
// Make sure this varying is removed from the other stages, or if position is not captured
// at all.
variableInfoMapOut->add(shaderType, sh::vk::spirv::kIdXfbExtensionPosition);
}
}
void SpvGetShaderSpirvCode(const gl::ProgramState &programState,
gl::ShaderMap<const spirv::Blob *> *spirvBlobsOut)
{
for (const gl::ShaderType shaderType : gl::AllShaderTypes())
{
const gl::SharedCompiledShaderState &glShader = programState.getAttachedShader(shaderType);
(*spirvBlobsOut)[shaderType] = glShader ? &glShader->compiledBinary : nullptr;
}
}
void SpvAssignAllLocations(const SpvSourceOptions &options,
const gl::ProgramState &programState,
const gl::ProgramLinkedResources &resources,
ShaderInterfaceVariableInfoMap *variableInfoMapOut)
{
SpvProgramInterfaceInfo spvProgramInterfaceInfo = {};
const gl::ProgramExecutable &programExecutable = programState.getExecutable();
gl::ShaderType xfbStage = programState.getAttachedTransformFeedbackStage();
// This should be done before assigning varying location. Otherwise, We can encounter shader
// interface mismatching problem in case the transformFeedback stage is not Vertex stage.
for (const gl::ShaderType shaderType : programExecutable.getLinkedShaderStages())
{
// Assign location to varyings generated for transform feedback capture
const bool isXfbStage = shaderType == xfbStage &&
!programExecutable.getLinkedTransformFeedbackVaryings().empty();
if (options.supportsTransformFeedbackExtension &&
gl::ShaderTypeSupportsTransformFeedback(shaderType))
{
SpvAssignTransformFeedbackLocations(shaderType, programExecutable, isXfbStage,
&spvProgramInterfaceInfo, variableInfoMapOut);
}
}
SpvAssignLocations(options, programExecutable, resources.varyingPacking, xfbStage,
&spvProgramInterfaceInfo, variableInfoMapOut);
}
angle::Result SpvTransformSpirvCode(const SpvTransformOptions &options,
const ShaderInterfaceVariableInfoMap &variableInfoMap,
const spirv::Blob &initialSpirvBlob,
spirv::Blob *spirvBlobOut)
{
if (initialSpirvBlob.empty())
{
return angle::Result::Continue;
}
const bool hasAliasingAttributes =
options.shaderType == gl::ShaderType::Vertex && variableInfoMap.hasAliasingAttributes();
// Transform the SPIR-V code by assigning location/set/binding values.
SpirvTransformer transformer(initialSpirvBlob, options, !hasAliasingAttributes, variableInfoMap,
spirvBlobOut);
transformer.transform();
// If there are aliasing vertex attributes, transform the SPIR-V again to remove them.
if (hasAliasingAttributes)
{
spirv::Blob preTransformBlob = std::move(*spirvBlobOut);
SpirvVertexAttributeAliasingTransformer aliasingTransformer(
preTransformBlob, variableInfoMap, std::move(transformer.getVariableInfoByIdMap()),
spirvBlobOut);
aliasingTransformer.transform();
}
spirvBlobOut->shrink_to_fit();
if (options.validate)
{
ASSERT(spirv::Validate(*spirvBlobOut));
}
return angle::Result::Continue;
}
} // namespace rx