blob: ec4457b27f3a5ef7b27abf3eced172d6506f0e8b [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.
//
// Wrapper for Khronos glslang compiler.
//
#include "libANGLE/renderer/glslang_wrapper_utils.h"
// glslang has issues with some specific warnings.
ANGLE_DISABLE_EXTRA_SEMI_WARNING
ANGLE_DISABLE_SHADOWING_WARNING
ANGLE_DISABLE_SUGGEST_OVERRIDE_WARNINGS
// glslang's version of ShaderLang.h, not to be confused with ANGLE's.
#include <glslang/Public/ShaderLang.h>
// Other glslang includes.
#include <SPIRV/GlslangToSpv.h>
#include <StandAlone/ResourceLimits.h>
ANGLE_REENABLE_SUGGEST_OVERRIDE_WARNINGS
ANGLE_REENABLE_SHADOWING_WARNING
ANGLE_REENABLE_EXTRA_SEMI_WARNING
// SPIR-V headers include for AST transformation.
#include <spirv/unified1/spirv.hpp>
// SPIR-V tools include for AST validation.
#include <spirv-tools/libspirv.hpp>
#include <array>
#include <numeric>
#include "common/FixedVector.h"
#include "common/string_utils.h"
#include "common/utilities.h"
#include "libANGLE/Caps.h"
#include "libANGLE/ProgramLinkedResources.h"
#include "libANGLE/trace.h"
#define ANGLE_GLSLANG_CHECK(CALLBACK, TEST, ERR) \
do \
{ \
if (ANGLE_UNLIKELY(!(TEST))) \
{ \
return CALLBACK(ERR); \
} \
\
} while (0)
// Enable this for debug logging of pre-transform SPIR-V:
#if !defined(ANGLE_DEBUG_SPIRV_TRANSFORMER)
# define ANGLE_DEBUG_SPIRV_TRANSFORMER 0
#endif // !defined(ANGLE_DEBUG_SPIRV_TRANSFORMER)
namespace rx
{
namespace
{
constexpr char kXfbOutMarker[] = "@@ XFB-OUT @@;";
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;
}
void GetBuiltInResourcesFromCaps(const gl::Caps &caps, TBuiltInResource *outBuiltInResources)
{
outBuiltInResources->maxDrawBuffers = caps.maxDrawBuffers;
outBuiltInResources->maxAtomicCounterBindings = caps.maxAtomicCounterBufferBindings;
outBuiltInResources->maxAtomicCounterBufferSize = caps.maxAtomicCounterBufferSize;
outBuiltInResources->maxClipPlanes = caps.maxClipPlanes;
outBuiltInResources->maxCombinedAtomicCounterBuffers = caps.maxCombinedAtomicCounterBuffers;
outBuiltInResources->maxCombinedAtomicCounters = caps.maxCombinedAtomicCounters;
outBuiltInResources->maxCombinedImageUniforms = caps.maxCombinedImageUniforms;
outBuiltInResources->maxCombinedTextureImageUnits = caps.maxCombinedTextureImageUnits;
outBuiltInResources->maxCombinedShaderOutputResources = caps.maxCombinedShaderOutputResources;
outBuiltInResources->maxComputeWorkGroupCountX = caps.maxComputeWorkGroupCount[0];
outBuiltInResources->maxComputeWorkGroupCountY = caps.maxComputeWorkGroupCount[1];
outBuiltInResources->maxComputeWorkGroupCountZ = caps.maxComputeWorkGroupCount[2];
outBuiltInResources->maxComputeWorkGroupSizeX = caps.maxComputeWorkGroupSize[0];
outBuiltInResources->maxComputeWorkGroupSizeY = caps.maxComputeWorkGroupSize[1];
outBuiltInResources->maxComputeWorkGroupSizeZ = caps.maxComputeWorkGroupSize[2];
outBuiltInResources->minProgramTexelOffset = caps.minProgramTexelOffset;
outBuiltInResources->maxFragmentUniformVectors = caps.maxFragmentUniformVectors;
outBuiltInResources->maxFragmentInputComponents = caps.maxFragmentInputComponents;
outBuiltInResources->maxGeometryInputComponents = caps.maxGeometryInputComponents;
outBuiltInResources->maxGeometryOutputComponents = caps.maxGeometryOutputComponents;
outBuiltInResources->maxGeometryOutputVertices = caps.maxGeometryOutputVertices;
outBuiltInResources->maxGeometryTotalOutputComponents = caps.maxGeometryTotalOutputComponents;
outBuiltInResources->maxLights = caps.maxLights;
outBuiltInResources->maxProgramTexelOffset = caps.maxProgramTexelOffset;
outBuiltInResources->maxVaryingComponents = caps.maxVaryingComponents;
outBuiltInResources->maxVaryingVectors = caps.maxVaryingVectors;
outBuiltInResources->maxVertexAttribs = caps.maxVertexAttributes;
outBuiltInResources->maxVertexOutputComponents = caps.maxVertexOutputComponents;
outBuiltInResources->maxVertexUniformVectors = caps.maxVertexUniformVectors;
outBuiltInResources->maxClipDistances = caps.maxClipDistances;
outBuiltInResources->maxSamples = caps.maxSamples;
}
// Run at startup to warm up glslang's internals to avoid hitches on first shader compile.
void GlslangWarmup()
{
ANGLE_TRACE_EVENT0("gpu.angle,startup", "GlslangWarmup");
EShMessages messages = static_cast<EShMessages>(EShMsgSpvRules | EShMsgVulkanRules);
// EShMessages messages = EShMsgDefault;
const TBuiltInResource builtInResources(glslang::DefaultTBuiltInResource);
glslang::TShader warmUpShader(EShLangVertex);
const char *kShaderString = R"(#version 450 core
void main(){}
)";
const int kShaderLength = static_cast<int>(strlen(kShaderString));
warmUpShader.setStringsWithLengths(&kShaderString, &kShaderLength, 1);
warmUpShader.setEntryPoint("main");
bool result = warmUpShader.parse(&builtInResources, 450, ECoreProfile, false, false, messages);
ASSERT(result);
}
bool IsRotationIdentity(SurfaceRotation rotation)
{
return rotation == SurfaceRotation::Identity || rotation == SurfaceRotation::FlippedIdentity;
}
// 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.
//
// If useOldRewriteStructSamplers, there are multiple samplers extracted out of struct arrays
// though, so the above only applies to the sampler array defined in the struct.
bool UniformNameIsIndexZero(const std::string &name, bool excludeCheckForOwningStructArrays)
{
size_t lastBracketClose = 0;
if (excludeCheckForOwningStructArrays)
{
size_t lastDot = name.find_last_of('.');
if (lastDot != std::string::npos)
{
lastBracketClose = lastDot;
}
}
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;
}
bool MappedSamplerNameNeedsUserDefinedPrefix(const std::string &originalName)
{
return originalName.find('.') == std::string::npos;
}
template <typename OutputIter, typename ImplicitIter>
uint32_t CountExplicitOutputs(OutputIter outputsBegin,
OutputIter outputsEnd,
ImplicitIter implicitsBegin,
ImplicitIter implicitsEnd)
{
auto reduce = [implicitsBegin, implicitsEnd](uint32_t count, const sh::ShaderVariable &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,
const std::string &varName,
uint32_t descriptorSet,
uint32_t binding)
{
gl::ShaderBitSet allStages;
allStages.set();
ShaderInterfaceVariableInfo &info = infoMap->add(shaderType, varName);
info.descriptorSet = descriptorSet;
info.binding = binding;
info.activeStages = allStages;
return &info;
}
ShaderInterfaceVariableInfo *AddResourceInfo(ShaderInterfaceVariableInfoMap *infoMap,
gl::ShaderType shaderType,
const std::string &varName,
uint32_t descriptorSet,
uint32_t binding)
{
gl::ShaderBitSet stages;
stages.set(shaderType);
ShaderInterfaceVariableInfo &info = infoMap->add(shaderType, varName);
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,
const std::string &varName,
uint32_t location,
uint32_t component,
uint8_t attributeComponentCount,
uint8_t attributeLocationCount)
{
// The info map for this name may or may not exist already. This function merges the
// location/component information.
ShaderInterfaceVariableInfo &info = infoMap->addOrGet(shaderType, varName);
ASSERT(info.descriptorSet == ShaderInterfaceVariableInfo::kInvalid);
ASSERT(info.binding == ShaderInterfaceVariableInfo::kInvalid);
ASSERT(info.location == ShaderInterfaceVariableInfo::kInvalid);
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 bool isStructField,
const uint32_t location,
const uint32_t component)
{
const std::string &name = isStructField ? ref.parentStructMappedName : ref.varying->mappedName;
AddLocationInfo(infoMap, ref.stage, name, location, component, 0, 0);
}
// Modify an existing out variable and add transform feedback information.
ShaderInterfaceVariableInfo *SetXfbInfo(ShaderInterfaceVariableInfoMap *infoMap,
gl::ShaderType shaderType,
const std::string &varName,
int fieldIndex,
uint32_t xfbBuffer,
uint32_t xfbOffset,
uint32_t xfbStride)
{
ShaderInterfaceVariableInfo &info = infoMap->get(shaderType, varName);
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->buffer == ShaderInterfaceVariableXfbInfo::kInvalid);
ASSERT(xfb->offset == ShaderInterfaceVariableXfbInfo::kInvalid);
ASSERT(xfb->stride == ShaderInterfaceVariableXfbInfo::kInvalid);
xfb->buffer = xfbBuffer;
xfb->offset = xfbOffset;
xfb->stride = xfbStride;
return &info;
}
std::string SubstituteTransformFeedbackMarkers(const std::string &originalSource,
const std::string &xfbOut)
{
const size_t xfbOutMarkerStart = originalSource.find(kXfbOutMarker);
const size_t xfbOutMarkerEnd = xfbOutMarkerStart + ConstStrLen(kXfbOutMarker);
// The shader is the following form:
//
// ..part1..
// @@ XFB-OUT @@;
// ..part2..
//
// Construct the string by concatenating these three pieces, replacing the marker with the given
// value.
std::string result;
result.append(&originalSource[0], &originalSource[xfbOutMarkerStart]);
result.append(xfbOut);
result.append(&originalSource[xfbOutMarkerEnd], &originalSource[originalSource.size()]);
return result;
}
void GenerateTransformFeedbackVaryingOutput(const gl::TransformFeedbackVarying &varying,
const gl::UniformTypeInfo &info,
size_t offset,
const std::string &bufferIndex,
std::ostringstream *xfbOut)
{
const size_t arrayIndexStart = varying.arrayIndex == GL_INVALID_INDEX ? 0 : varying.arrayIndex;
const size_t arrayIndexEnd = arrayIndexStart + varying.size();
for (size_t arrayIndex = arrayIndexStart; arrayIndex < arrayIndexEnd; ++arrayIndex)
{
for (int col = 0; col < info.columnCount; ++col)
{
for (int row = 0; row < info.rowCount; ++row)
{
*xfbOut << sh::vk::kXfbEmulationBufferName << bufferIndex << "."
<< sh::vk::kXfbEmulationBufferFieldName << "[xfbOffsets[" << bufferIndex
<< "] + " << offset << "] = " << info.glslAsFloat << "("
<< varying.mappedName;
if (varying.isArray())
{
*xfbOut << "[" << arrayIndex << "]";
}
if (info.columnCount > 1)
{
*xfbOut << "[" << col << "]";
}
if (info.rowCount > 1)
{
*xfbOut << "[" << row << "]";
}
*xfbOut << ");\n";
++offset;
}
}
}
}
void AssignTransformFeedbackEmulationBindings(gl::ShaderType shaderType,
const gl::ProgramState &programState,
bool isTransformFeedbackStage,
GlslangProgramInterfaceInfo *programInterfaceInfo,
ShaderInterfaceVariableInfoMap *variableInfoMapOut)
{
size_t bufferCount = 0;
if (isTransformFeedbackStage)
{
ASSERT(!programState.getLinkedTransformFeedbackVaryings().empty());
const bool isInterleaved =
programState.getTransformFeedbackBufferMode() == GL_INTERLEAVED_ATTRIBS;
bufferCount = isInterleaved ? 1 : programState.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, shaderType, GetXfbBufferName(bufferIndex),
programInterfaceInfo->uniformsAndXfbDescriptorSetIndex,
programInterfaceInfo->currentUniformBindingIndex);
++programInterfaceInfo->currentUniformBindingIndex;
}
// Remove inactive transform feedback buffers.
for (uint32_t bufferIndex = bufferCount;
bufferIndex < gl::IMPLEMENTATION_MAX_TRANSFORM_FEEDBACK_BUFFERS; ++bufferIndex)
{
variableInfoMapOut->add(shaderType, GetXfbBufferName(bufferIndex));
}
}
void AssignTransformFeedbackExtensionLocations(gl::ShaderType shaderType,
const gl::ProgramState &programState,
bool isTransformFeedbackStage,
GlslangProgramInterfaceInfo *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 =
programState.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.mappedName;
if (tfVaryingName == "gl_Position")
{
ASSERT(tfVarying.isBuiltIn());
capturesPosition = true;
break;
}
}
}
if (capturesPosition)
{
AddLocationInfo(variableInfoMapOut, shaderType, sh::vk::kXfbExtensionPositionOutName,
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::kXfbExtensionPositionOutName);
}
}
void GenerateTransformFeedbackEmulationOutputs(const GlslangSourceOptions &options,
gl::ShaderType shaderType,
const gl::ProgramState &programState,
GlslangProgramInterfaceInfo *programInterfaceInfo,
std::string *vertexShader,
ShaderInterfaceVariableInfoMap *variableInfoMapOut)
{
const std::vector<gl::TransformFeedbackVarying> &varyings =
programState.getLinkedTransformFeedbackVaryings();
const std::vector<GLsizei> &bufferStrides = programState.getTransformFeedbackStrides();
const bool isInterleaved =
programState.getTransformFeedbackBufferMode() == GL_INTERLEAVED_ATTRIBS;
const size_t bufferCount = isInterleaved ? 1 : varyings.size();
ASSERT(bufferCount > 0);
const std::string xfbSet = Str(programInterfaceInfo->uniformsAndXfbDescriptorSetIndex);
const std::string driverUniforms = std::string(sh::vk::kDriverUniformsVarName);
std::ostringstream xfbOut;
xfbOut << "if (" << driverUniforms << ".xfbActiveUnpaused != 0)\n{\nivec4 xfbOffsets = "
<< sh::vk::kXfbEmulationGetOffsetsFunctionName << "(ivec4(";
for (size_t bufferIndex = 0; bufferIndex < bufferCount; ++bufferIndex)
{
if (bufferIndex > 0)
{
xfbOut << ", ";
}
ASSERT(bufferStrides[bufferIndex] % 4 == 0);
xfbOut << bufferStrides[bufferIndex] / 4;
}
for (size_t bufferIndex = bufferCount; bufferIndex < 4; ++bufferIndex)
{
xfbOut << ", 0";
}
xfbOut << "));\n";
size_t outputOffset = 0;
for (size_t varyingIndex = 0; varyingIndex < varyings.size(); ++varyingIndex)
{
const size_t bufferIndex = isInterleaved ? 0 : varyingIndex;
const gl::TransformFeedbackVarying &varying = varyings[varyingIndex];
// For every varying, output to the respective buffer packed. If interleaved, the output is
// always to the same buffer, but at different offsets.
const gl::UniformTypeInfo &info = gl::GetUniformTypeInfo(varying.type);
GenerateTransformFeedbackVaryingOutput(varying, info, outputOffset, Str(bufferIndex),
&xfbOut);
if (isInterleaved)
{
outputOffset += info.columnCount * info.rowCount * varying.size();
}
}
xfbOut << "}\n";
*vertexShader = SubstituteTransformFeedbackMarkers(*vertexShader, xfbOut.str());
}
bool IsFirstRegisterOfVarying(const gl::PackedVaryingRegister &varyingReg, bool allowFields)
{
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.
if (varyingReg.varyingArrayIndex != 0 ||
(varying.arrayIndex != GL_INVALID_INDEX && varying.arrayIndex != 0))
{
return false;
}
// Similarly, assign matrix varying locations to the assigned location of the first row.
if (varyingReg.varyingRowIndex != 0)
{
return false;
}
return true;
}
// Calculates XFB layout qualifier arguments for each tranform feedback varying. Stores calculated
// values for the SPIR-V transformation.
void GenerateTransformFeedbackExtensionOutputs(const gl::ProgramState &programState,
std::string *xfbShaderSource)
{
const std::vector<gl::TransformFeedbackVarying> &tfVaryings =
programState.getLinkedTransformFeedbackVaryings();
std::string xfbOut;
for (uint32_t varyingIndex = 0; varyingIndex < tfVaryings.size(); ++varyingIndex)
{
const gl::TransformFeedbackVarying &tfVarying = tfVaryings[varyingIndex];
const std::string &tfVaryingName = tfVarying.mappedName;
if (tfVaryingName == "gl_Position")
{
ASSERT(tfVarying.isBuiltIn());
// Add initialization code for the position varying.
xfbOut = sh::vk::kXfbExtensionPositionOutName;
xfbOut += " = " + tfVaryingName + ";\n";
break;
}
}
*xfbShaderSource = SubstituteTransformFeedbackMarkers(*xfbShaderSource, xfbOut);
}
void AssignAttributeLocations(const gl::ProgramExecutable &programExecutable,
gl::ShaderType shaderType,
ShaderInterfaceVariableInfoMap *variableInfoMapOut)
{
// Assign attribute locations for the vertex shader.
for (const sh::ShaderVariable &attribute : programExecutable.getProgramInputs())
{
ASSERT(attribute.active);
const uint8_t colCount = static_cast<uint8_t>(gl::VariableColumnCount(attribute.type));
const uint8_t rowCount = static_cast<uint8_t>(gl::VariableRowCount(attribute.type));
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.mappedName, attribute.location,
ShaderInterfaceVariableInfo::kInvalid, componentCount, locationCount);
}
}
void AssignOutputLocations(const gl::ProgramExecutable &programExecutable,
const gl::ShaderType shaderType,
ShaderInterfaceVariableInfoMap *variableInfoMapOut)
{
// Assign output locations for the fragment shader.
ASSERT(shaderType == gl::ShaderType::Fragment);
// TODO(syoussefi): Add support for EXT_blend_func_extended. http://anglebug.com/3385
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 sh::ShaderVariable &outputVar = outputVariables[outputLocation.index];
uint32_t location = 0;
if (outputVar.location != -1)
{
location = outputVar.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.mappedName, location,
ShaderInterfaceVariableInfo::kInvalid, 0, 0);
}
}
// When no fragment output is specified by the shader, the translator outputs webgl_FragColor or
// webgl_FragData. Add an entry for these. Even though the translator is already assigning
// location 0 to these entries, adding an entry for them here 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.
AddLocationInfo(variableInfoMapOut, shaderType, "webgl_FragColor", 0, 0, 0, 0);
AddLocationInfo(variableInfoMapOut, shaderType, "webgl_FragData", 0, 0, 0, 0);
}
void AssignVaryingLocations(const GlslangSourceOptions &options,
const gl::VaryingPacking &varyingPacking,
const gl::ShaderType shaderType,
const gl::ShaderType frontShaderType,
GlslangProgramInterfaceInfo *programInterfaceInfo,
ShaderInterfaceVariableInfoMap *variableInfoMapOut)
{
uint32_t locationsUsedForEmulation = programInterfaceInfo->locationsUsedForXfbExtension;
// Substitute layout and qualifier strings for the position varying added for line raster
// emulation.
if (options.emulateBresenhamLines)
{
uint32_t lineRasterEmulationPositionLocation = locationsUsedForEmulation++;
AddLocationInfo(variableInfoMapOut, shaderType, sh::vk::kLineRasterEmulationPosition,
lineRasterEmulationPositionLocation, ShaderInterfaceVariableInfo::kInvalid,
0, 0);
}
// Assign varying locations.
for (const gl::PackedVaryingRegister &varyingReg : varyingPacking.getRegisterList())
{
if (!IsFirstRegisterOfVarying(varyingReg, false))
{
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;
}
// In the following:
//
// struct S { vec4 field; };
// out S varStruct;
//
// "_uvarStruct" is found through |parentStructMappedName|, with |varying->mappedName|
// being "_ufield". In such a case, use |parentStructMappedName|.
if (varying.frontVarying.varying && (varying.frontVarying.stage == shaderType))
{
AddVaryingLocationInfo(variableInfoMapOut, varying.frontVarying,
varying.isStructField(), location, component);
}
if (varying.backVarying.varying && (varying.backVarying.stage == shaderType))
{
AddVaryingLocationInfo(variableInfoMapOut, varying.backVarying, varying.isStructField(),
location, component);
}
}
// Add an entry for inactive varyings.
const gl::ShaderMap<std::vector<std::string>> &inactiveVaryingMappedNames =
varyingPacking.getInactiveVaryingMappedNames();
for (const std::string &varyingName : inactiveVaryingMappedNames[shaderType])
{
ASSERT(!gl::IsBuiltInName(varyingName));
// If name is already in the map, it will automatically have marked all other stages
// inactive.
if (variableInfoMapOut->contains(shaderType, varyingName))
{
continue;
}
// Otherwise, add an entry for it with all locations inactive.
ShaderInterfaceVariableInfo &info = variableInfoMapOut->addOrGet(shaderType, varyingName);
ASSERT(info.location == ShaderInterfaceVariableInfo::kInvalid);
}
// Add an entry for active builtins varyings. This will allow inactive builtins, such as
// gl_PointSize, gl_ClipDistance etc to be removed.
const gl::ShaderMap<std::vector<std::string>> &activeOutputBuiltIns =
varyingPacking.getActiveOutputBuiltInNames();
for (const std::string &builtInName : activeOutputBuiltIns[shaderType])
{
ASSERT(gl::IsBuiltInName(builtInName));
ShaderInterfaceVariableInfo &info = variableInfoMapOut->addOrGet(shaderType, builtInName);
info.activeStages.set(shaderType);
info.varyingIsOutput = true;
}
// If an output builtin is active in the previous stage, assume it's active in the input of the
// current stage as well.
if (frontShaderType != gl::ShaderType::InvalidEnum)
{
for (const std::string &builtInName : activeOutputBuiltIns[frontShaderType])
{
ASSERT(gl::IsBuiltInName(builtInName));
ShaderInterfaceVariableInfo &info =
variableInfoMapOut->addOrGet(shaderType, builtInName);
info.activeStages.set(shaderType);
info.varyingIsInput = true;
}
}
// Add an entry for gl_PerVertex, for use with transform feedback capture of built-ins.
ShaderInterfaceVariableInfo &info = variableInfoMapOut->addOrGet(shaderType, "gl_PerVertex");
info.activeStages.set(shaderType);
}
// Calculates XFB layout qualifier arguments for each tranform feedback varying. Stores calculated
// values for the SPIR-V transformation.
void AssignTransformFeedbackExtensionQualifiers(const gl::ProgramExecutable &programExecutable,
const gl::VaryingPacking &varyingPacking,
const gl::ShaderType shaderType,
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];
if (tfVarying.isBuiltIn())
{
if (tfVarying.name == "gl_Position")
{
SetXfbInfo(variableInfoMapOut, shaderType, sh::vk::kXfbExtensionPositionOutName, -1,
bufferIndex, currentOffset, currentStride);
}
else
{
// gl_PerVertex is always defined as:
//
// Field 0: gl_Position
// Field 1: gl_PointSize
// Field 2: gl_ClipDistance
// Field 3: gl_CullDistance
//
// 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 = 1; index < kPerVertexMemberCount; ++index)
{
if (tfVarying.name == kPerVertexMembers[index])
{
fieldIndex = index;
break;
}
}
ASSERT(fieldIndex != -1);
SetXfbInfo(variableInfoMapOut, shaderType, "gl_PerVertex", fieldIndex, bufferIndex,
currentOffset, currentStride);
}
}
else if (!tfVarying.isArray() || tfVarying.arrayIndex == GL_INVALID_INDEX)
{
// Note: capturing individual array elements using the Vulkan transform feedback
// extension is not supported, and is unlikely to be ever supported (on the contrary, it
// may be removed from the GLES spec). http://anglebug.com/4140
// ANGLE should support capturing the whole array.
// Find the varying with this name. If a struct is captured, we would be iterating over
// its fields, and the name of the varying is found through parentStructMappedName.
// This should only be done for the first field of the struct. 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())
{
if (!IsFirstRegisterOfVarying(varyingReg, tfVarying.isShaderIOBlock))
{
continue;
}
const gl::PackedVarying *varying = varyingReg.packedVarying;
if (tfVarying.isShaderIOBlock)
{
if (varying->frontVarying.parentStructName == tfVarying.structName)
{
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 std::string &mappedName =
originalVarying->isStructField()
? originalVarying->frontVarying.parentStructMappedName
: originalVarying->frontVarying.varying->mappedName;
const int fieldIndex = tfVarying.isShaderIOBlock ? originalVarying->fieldIndex : -1;
// Set xfb info for this varying. AssignVaryingLocations should have already added
// location information for these varyings.
SetXfbInfo(variableInfoMapOut, shaderType, mappedName, fieldIndex, bufferIndex,
currentOffset, currentStride);
}
}
}
}
void AssignUniformBindings(const GlslangSourceOptions &options,
const gl::ProgramExecutable &programExecutable,
const gl::ShaderType shaderType,
GlslangProgramInterfaceInfo *programInterfaceInfo,
ShaderInterfaceVariableInfoMap *variableInfoMapOut)
{
if (programExecutable.hasLinkedShaderStage(shaderType))
{
AddResourceInfo(variableInfoMapOut, shaderType, kDefaultUniformNames[shaderType],
programInterfaceInfo->uniformsAndXfbDescriptorSetIndex,
programInterfaceInfo->currentUniformBindingIndex);
++programInterfaceInfo->currentUniformBindingIndex;
// Assign binding to the driver uniforms block
AddResourceInfoToAllStages(variableInfoMapOut, shaderType, sh::vk::kDriverUniformsBlockName,
programInterfaceInfo->driverUniformsDescriptorSetIndex, 0);
}
}
// TODO: http://anglebug.com/4512: Need to combine descriptor set bindings across
// shader stages.
void AssignInterfaceBlockBindings(const GlslangSourceOptions &options,
const gl::ProgramExecutable &programExecutable,
const std::vector<gl::InterfaceBlock> &blocks,
const gl::ShaderType shaderType,
GlslangProgramInterfaceInfo *programInterfaceInfo,
ShaderInterfaceVariableInfoMap *variableInfoMapOut)
{
for (const gl::InterfaceBlock &block : blocks)
{
if (!block.isArray || block.arrayElement == 0)
{
// TODO: http://anglebug.com/4523: All blocks should be active
if (programExecutable.hasLinkedShaderStage(shaderType) && block.isActive(shaderType))
{
AddResourceInfo(variableInfoMapOut, shaderType, block.mappedName,
programInterfaceInfo->shaderResourceDescriptorSetIndex,
programInterfaceInfo->currentShaderResourceBindingIndex);
++programInterfaceInfo->currentShaderResourceBindingIndex;
}
}
}
}
// TODO: http://anglebug.com/4512: Need to combine descriptor set bindings across
// shader stages.
void AssignAtomicCounterBufferBindings(const GlslangSourceOptions &options,
const gl::ProgramExecutable &programExecutable,
const std::vector<gl::AtomicCounterBuffer> &buffers,
const gl::ShaderType shaderType,
GlslangProgramInterfaceInfo *programInterfaceInfo,
ShaderInterfaceVariableInfoMap *variableInfoMapOut)
{
if (buffers.size() == 0)
{
return;
}
if (programExecutable.hasLinkedShaderStage(shaderType))
{
AddResourceInfo(variableInfoMapOut, shaderType, sh::vk::kAtomicCountersBlockName,
programInterfaceInfo->shaderResourceDescriptorSetIndex,
programInterfaceInfo->currentShaderResourceBindingIndex);
++programInterfaceInfo->currentShaderResourceBindingIndex;
}
}
// TODO: http://anglebug.com/4512: Need to combine descriptor set bindings across
// shader stages.
void AssignImageBindings(const GlslangSourceOptions &options,
const gl::ProgramExecutable &programExecutable,
const std::vector<gl::LinkedUniform> &uniforms,
const gl::RangeUI &imageUniformRange,
const gl::ShaderType shaderType,
GlslangProgramInterfaceInfo *programInterfaceInfo,
ShaderInterfaceVariableInfoMap *variableInfoMapOut)
{
for (unsigned int uniformIndex : imageUniformRange)
{
const gl::LinkedUniform &imageUniform = uniforms[uniformIndex];
std::string name = imageUniform.mappedName;
if (GetImageNameWithoutIndices(&name))
{
if (programExecutable.hasLinkedShaderStage(shaderType))
{
AddResourceInfo(variableInfoMapOut, shaderType, name,
programInterfaceInfo->shaderResourceDescriptorSetIndex,
programInterfaceInfo->currentShaderResourceBindingIndex);
++programInterfaceInfo->currentShaderResourceBindingIndex;
}
}
}
}
void AssignNonTextureBindings(const GlslangSourceOptions &options,
const gl::ProgramExecutable &programExecutable,
const gl::ShaderType shaderType,
GlslangProgramInterfaceInfo *programInterfaceInfo,
ShaderInterfaceVariableInfoMap *variableInfoMapOut)
{
const std::vector<gl::InterfaceBlock> &uniformBlocks = programExecutable.getUniformBlocks();
AssignInterfaceBlockBindings(options, programExecutable, uniformBlocks, shaderType,
programInterfaceInfo, variableInfoMapOut);
const std::vector<gl::InterfaceBlock> &storageBlocks =
programExecutable.getShaderStorageBlocks();
AssignInterfaceBlockBindings(options, programExecutable, storageBlocks, shaderType,
programInterfaceInfo, variableInfoMapOut);
const std::vector<gl::AtomicCounterBuffer> &atomicCounterBuffers =
programExecutable.getAtomicCounterBuffers();
AssignAtomicCounterBufferBindings(options, programExecutable, atomicCounterBuffers, shaderType,
programInterfaceInfo, variableInfoMapOut);
const std::vector<gl::LinkedUniform> &uniforms = programExecutable.getUniforms();
const gl::RangeUI &imageUniformRange = programExecutable.getImageUniformRange();
AssignImageBindings(options, programExecutable, uniforms, imageUniformRange, shaderType,
programInterfaceInfo, variableInfoMapOut);
}
// TODO: http://anglebug.com/4512: Need to combine descriptor set bindings across
// shader stages.
void AssignTextureBindings(const GlslangSourceOptions &options,
const gl::ProgramExecutable &programExecutable,
const gl::ShaderType shaderType,
GlslangProgramInterfaceInfo *programInterfaceInfo,
ShaderInterfaceVariableInfoMap *variableInfoMapOut)
{
// Assign textures to a descriptor set and binding.
const std::vector<gl::LinkedUniform> &uniforms = programExecutable.getUniforms();
for (unsigned int uniformIndex : programExecutable.getSamplerUniformRange())
{
const gl::LinkedUniform &samplerUniform = uniforms[uniformIndex];
if (!options.useOldRewriteStructSamplers &&
gl::SamplerNameContainsNonZeroArrayElement(samplerUniform.name))
{
continue;
}
if (UniformNameIsIndexZero(samplerUniform.name, options.useOldRewriteStructSamplers))
{
// Samplers in structs are extracted and renamed.
const std::string samplerName = options.useOldRewriteStructSamplers
? GetMappedSamplerNameOld(samplerUniform.name)
: GlslangGetMappedSamplerName(samplerUniform.name);
// TODO: http://anglebug.com/4523: All uniforms should be active
if (programExecutable.hasLinkedShaderStage(shaderType) &&
samplerUniform.isActive(shaderType))
{
AddResourceInfo(variableInfoMapOut, shaderType, samplerName,
programInterfaceInfo->textureDescriptorSetIndex,
programInterfaceInfo->currentTextureBindingIndex);
++programInterfaceInfo->currentTextureBindingIndex;
}
}
}
}
constexpr gl::ShaderMap<EShLanguage> kShLanguageMap = {
{gl::ShaderType::Vertex, EShLangVertex},
{gl::ShaderType::Geometry, EShLangGeometry},
{gl::ShaderType::Fragment, EShLangFragment},
{gl::ShaderType::Compute, EShLangCompute},
};
angle::Result CompileShader(const GlslangErrorCallback &callback,
const TBuiltInResource &builtInResources,
gl::ShaderType shaderType,
const std::string &shaderSource,
glslang::TShader *shader,
glslang::TProgram *program)
{
// Enable SPIR-V and Vulkan rules when parsing GLSL
constexpr EShMessages messages = static_cast<EShMessages>(EShMsgSpvRules | EShMsgVulkanRules);
ANGLE_TRACE_EVENT0("gpu.angle", "Glslang CompileShader TShader::parse");
const char *shaderString = shaderSource.c_str();
int shaderLength = static_cast<int>(shaderSource.size());
shader->setStringsWithLengths(&shaderString, &shaderLength, 1);
shader->setEntryPoint("main");
bool result = shader->parse(&builtInResources, 450, ECoreProfile, false, false, messages);
if (!result)
{
ERR() << "Internal error parsing Vulkan shader corresponding to " << shaderType << ":\n"
<< shader->getInfoLog() << "\n"
<< shader->getInfoDebugLog() << "\n";
ANGLE_GLSLANG_CHECK(callback, false, GlslangError::InvalidShader);
}
program->addShader(shader);
return angle::Result::Continue;
}
angle::Result LinkProgram(const GlslangErrorCallback &callback, glslang::TProgram *program)
{
// Enable SPIR-V and Vulkan rules
constexpr EShMessages messages = static_cast<EShMessages>(EShMsgSpvRules | EShMsgVulkanRules);
bool linkResult = program->link(messages);
if (!linkResult)
{
ERR() << "Internal error linking Vulkan shaders:\n" << program->getInfoLog() << "\n";
ANGLE_GLSLANG_CHECK(callback, false, GlslangError::InvalidShader);
}
return angle::Result::Continue;
}
#if defined(ANGLE_ENABLE_ASSERTS)
void ValidateSpirvMessage(spv_message_level_t level,
const char *source,
const spv_position_t &position,
const char *message)
{
WARN() << "Level" << level << ": " << message;
}
bool ValidateSpirv(const std::vector<uint32_t> &spirvBlob)
{
spvtools::SpirvTools spirvTools(SPV_ENV_VULKAN_1_1);
spirvTools.SetMessageConsumer(ValidateSpirvMessage);
bool result = spirvTools.Validate(spirvBlob);
if (!result)
{
std::string readableSpirv;
spirvTools.Disassemble(spirvBlob, &readableSpirv, 0);
WARN() << "Invalid SPIR-V:\n" << readableSpirv;
}
return result;
}
#else // ANGLE_ENABLE_ASSERTS
bool ValidateSpirv(const std::vector<uint32_t> &spirvBlob)
{
// Placeholder implementation since this is only used inside an ASSERT().
// Return false to indicate an error in case this is ever accidentally used somewhere else.
return false;
}
#endif // ANGLE_ENABLE_ASSERTS
// SPIR-V 1.0 Table 2: Instruction Physical Layout
uint32_t GetSpirvInstructionLength(const uint32_t *instruction)
{
return instruction[0] >> 16;
}
uint32_t GetSpirvInstructionOp(const uint32_t *instruction)
{
constexpr uint32_t kOpMask = 0xFFFFu;
return instruction[0] & kOpMask;
}
void SetSpirvInstructionLength(uint32_t *instruction, size_t length)
{
ASSERT(length < 0xFFFFu);
constexpr uint32_t kLengthMask = 0xFFFF0000u;
instruction[0] &= ~kLengthMask;
instruction[0] |= length << 16;
}
void SetSpirvInstructionOp(uint32_t *instruction, uint32_t op)
{
constexpr uint32_t kOpMask = 0xFFFFu;
instruction[0] &= ~kOpMask;
instruction[0] |= op;
}
// Base class for SPIR-V transformations.
class SpirvTransformerBase : angle::NonCopyable
{
public:
SpirvTransformerBase(const std::vector<uint32_t> &spirvBlobIn,
const ShaderInterfaceVariableInfoMap &variableInfoMap,
SpirvBlob *spirvBlobOut)
: mSpirvBlobIn(spirvBlobIn), mVariableInfoMap(variableInfoMap), mSpirvBlobOut(spirvBlobOut)
{
gl::ShaderBitSet allStages;
allStages.set();
mBuiltinVariableInfo.activeStages = allStages;
}
std::vector<const ShaderInterfaceVariableInfo *> &getVariableInfoByIdMap()
{
return mVariableInfoById;
}
protected:
// SPIR-V 1.0 Table 1: First Words of Physical Layout
enum HeaderIndex
{
kHeaderIndexMagic = 0,
kHeaderIndexVersion = 1,
kHeaderIndexGenerator = 2,
kHeaderIndexIndexBound = 3,
kHeaderIndexSchema = 4,
kHeaderIndexInstructions = 5,
};
// Common utilities
void onTransformBegin();
const uint32_t *getCurrentInstruction(uint32_t *opCodeOut, uint32_t *wordCountOut) const;
size_t copyInstruction(const uint32_t *instruction, size_t wordCount);
uint32_t getNewId();
// Instruction generators:
void writeAccessChain(uint32_t id, uint32_t typeId, uint32_t baseId, uint32_t indexId);
void writeCopyObject(uint32_t id, uint32_t typeId, uint32_t operandId);
void writeCompositeConstruct(uint32_t id,
uint32_t typeId,
const angle::FixedVector<uint32_t, 4> &constituents);
void writeCompositeExtract(uint32_t id, uint32_t typeId, uint32_t compositeId, uint32_t field);
void writeConstant(uint32_t id, uint32_t typeId, uint32_t value);
void writeFAdd(uint32_t id, uint32_t typeId, uint32_t operand1, uint32_t operand2);
void writeFMul(uint32_t id, uint32_t typeId, uint32_t operand1, uint32_t operand2);
void writeFNegate(uint32_t id, uint32_t typeId, uint32_t operand);
void writeLoad(uint32_t id, uint32_t typeId, uint32_t pointerId);
void writeMemberDecorate(uint32_t typeId, uint32_t member, uint32_t decoration, uint32_t value);
void writeStore(uint32_t pointerId, uint32_t objectId);
void writeTypeFloat(uint32_t id, uint32_t width);
void writeTypeInt(uint32_t id, uint32_t width, uint32_t signedness);
void writeTypePointer(uint32_t id, uint32_t storageClass, uint32_t typeId);
void writeTypeVector(uint32_t id, uint32_t componentTypeId, uint32_t componentCount);
void writeVariable(uint32_t id, uint32_t typeId, uint32_t storageClass);
void writeVectorShuffle(uint32_t id,
uint32_t typeId,
uint32_t vec1Id,
uint32_t vec2Id,
const angle::FixedVector<uint32_t, 4> &fields);
// SPIR-V to transform:
const std::vector<uint32_t> &mSpirvBlobIn;
// Input shader variable info map:
const ShaderInterfaceVariableInfoMap &mVariableInfoMap;
// Transformed SPIR-V:
SpirvBlob *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()
{
// Glslang succeeded in outputting SPIR-V, so we assume it's valid.
ASSERT(mSpirvBlobIn.size() >= kHeaderIndexInstructions);
// Since SPIR-V comes from a local call to glslang, it necessarily has the same endianness as
// the running architecture, so no byte-swapping is necessary.
ASSERT(mSpirvBlobIn[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 SpirvBlob is not reused.
ASSERT(mSpirvBlobOut->empty());
// Copy the header to SpirvBlob, we need that to be defined for SpirvTransformerBase::getNewId
// to work.
mSpirvBlobOut->assign(mSpirvBlobIn.begin(), mSpirvBlobIn.begin() + kHeaderIndexInstructions);
mCurrentWord = kHeaderIndexInstructions;
}
const uint32_t *SpirvTransformerBase::getCurrentInstruction(uint32_t *opCodeOut,
uint32_t *wordCountOut) const
{
ASSERT(mCurrentWord < mSpirvBlobIn.size());
const uint32_t *instruction = &mSpirvBlobIn[mCurrentWord];
*wordCountOut = GetSpirvInstructionLength(instruction);
*opCodeOut = GetSpirvInstructionOp(instruction);
// Since glslang succeeded in producing SPIR-V, we assume it to be valid.
ASSERT(mCurrentWord + *wordCountOut <= mSpirvBlobIn.size());
return instruction;
}
size_t SpirvTransformerBase::copyInstruction(const uint32_t *instruction, size_t wordCount)
{
size_t instructionOffset = mSpirvBlobOut->size();
mSpirvBlobOut->insert(mSpirvBlobOut->end(), instruction, instruction + wordCount);
return instructionOffset;
}
uint32_t SpirvTransformerBase::getNewId()
{
return (*mSpirvBlobOut)[kHeaderIndexIndexBound]++;
}
void SpirvTransformerBase::writeAccessChain(uint32_t id,
uint32_t typeId,
uint32_t baseId,
uint32_t indexId)
{
// SPIR-V 1.0 Section 3.32 Instructions, OpAccessChain
constexpr size_t kTypeIdIndex = 1;
constexpr size_t kIdIndex = 2;
constexpr size_t kBaseIdIndex = 3;
constexpr size_t kIndexIdIndex = 4;
constexpr size_t kAccessChainInstructionLength = 5;
std::array<uint32_t, kAccessChainInstructionLength> accessChain = {};
// Fill the fields.
SetSpirvInstructionOp(accessChain.data(), spv::OpAccessChain);
SetSpirvInstructionLength(accessChain.data(), kAccessChainInstructionLength);
accessChain[kTypeIdIndex] = typeId;
accessChain[kIdIndex] = id;
accessChain[kBaseIdIndex] = baseId;
accessChain[kIndexIdIndex] = indexId;
copyInstruction(accessChain.data(), kAccessChainInstructionLength);
}
void SpirvTransformerBase::writeCopyObject(uint32_t id, uint32_t typeId, uint32_t operandId)
{
// SPIR-V 1.0 Section 3.32 Instructions, OpCopyObject
constexpr size_t kTypeIdIndex = 1;
constexpr size_t kIdIndex = 2;
constexpr size_t kOperandIdIndex = 3;
constexpr size_t kCopyObjectInstructionLength = 4;
std::array<uint32_t, kCopyObjectInstructionLength> copyObject = {};
// Fill the fields.
SetSpirvInstructionOp(copyObject.data(), spv::OpCopyObject);
SetSpirvInstructionLength(copyObject.data(), kCopyObjectInstructionLength);
copyObject[kTypeIdIndex] = typeId;
copyObject[kIdIndex] = id;
copyObject[kOperandIdIndex] = operandId;
copyInstruction(copyObject.data(), kCopyObjectInstructionLength);
}
void SpirvTransformerBase::writeCompositeConstruct(
uint32_t id,
uint32_t typeId,
const angle::FixedVector<uint32_t, 4> &constituents)
{
// SPIR-V 1.0 Section 3.32 Instructions, OpCompositeConstruct
constexpr size_t kTypeIdIndex = 1;
constexpr size_t kIdIndex = 2;
constexpr size_t kConstituentsIndexStart = 3;
constexpr size_t kConstituentsMaxCount = 4;
constexpr size_t kCompositeConstructInstructionBaseLength = 3;
ASSERT(kConstituentsMaxCount == constituents.max_size());
std::array<uint32_t, kCompositeConstructInstructionBaseLength + kConstituentsMaxCount>
compositeConstruct = {};
// Fill the fields.
SetSpirvInstructionOp(compositeConstruct.data(), spv::OpCompositeConstruct);
SetSpirvInstructionLength(compositeConstruct.data(),
kCompositeConstructInstructionBaseLength + constituents.size());
compositeConstruct[kTypeIdIndex] = typeId;
compositeConstruct[kIdIndex] = id;
for (size_t constituentIndex = 0; constituentIndex < constituents.size(); ++constituentIndex)
{
compositeConstruct[kConstituentsIndexStart + constituentIndex] =
constituents[constituentIndex];
}
copyInstruction(compositeConstruct.data(),
kCompositeConstructInstructionBaseLength + constituents.size());
}
void SpirvTransformerBase::writeCompositeExtract(uint32_t id,
uint32_t typeId,
uint32_t compositeId,
uint32_t field)
{
// SPIR-V 1.0 Section 3.32 Instructions, OpCompositeExtract
constexpr size_t kTypeIdIndex = 1;
constexpr size_t kIdIndex = 2;
constexpr size_t kCompositeIdIndex = 3;
constexpr size_t kFieldIndex = 4;
constexpr size_t kCompositeExtractInstructionLength = 5;
std::array<uint32_t, kCompositeExtractInstructionLength> compositeExtract = {};
// Fill the fields.
SetSpirvInstructionOp(compositeExtract.data(), spv::OpCompositeExtract);
SetSpirvInstructionLength(compositeExtract.data(), kCompositeExtractInstructionLength);
compositeExtract[kTypeIdIndex] = typeId;
compositeExtract[kIdIndex] = id;
compositeExtract[kCompositeIdIndex] = compositeId;
compositeExtract[kFieldIndex] = field;
copyInstruction(compositeExtract.data(), kCompositeExtractInstructionLength);
}
void SpirvTransformerBase::writeConstant(uint32_t id, uint32_t typeId, uint32_t value)
{
// SPIR-V 1.0 Section 3.32 Instructions, OpConstant
constexpr size_t kTypeIdIndex = 1;
constexpr size_t kIdIndex = 2;
constexpr size_t kValueIndex = 3;
constexpr size_t kConstantInstructionLength = 4;
std::array<uint32_t, kConstantInstructionLength> constant = {};
// Fill the fields.
SetSpirvInstructionOp(constant.data(), spv::OpConstant);
SetSpirvInstructionLength(constant.data(), kConstantInstructionLength);
constant[kTypeIdIndex] = typeId;
constant[kIdIndex] = id;
constant[kValueIndex] = value;
copyInstruction(constant.data(), kConstantInstructionLength);
}
void SpirvTransformerBase::writeFAdd(uint32_t id,
uint32_t typeId,
uint32_t operand1,
uint32_t operand2)
{
// SPIR-V 1.0 Section 3.32 Instructions, OpFAdd
constexpr size_t kTypeIdIndex = 1;
constexpr size_t kIdIndex = 2;
constexpr size_t kOperand1Index = 3;
constexpr size_t kOperand2Index = 4;
constexpr size_t kFAddInstructionLength = 5;
std::array<uint32_t, kFAddInstructionLength> fAdd = {};
// Fill the fields.
SetSpirvInstructionOp(fAdd.data(), spv::OpFAdd);
SetSpirvInstructionLength(fAdd.data(), kFAddInstructionLength);
fAdd[kTypeIdIndex] = typeId;
fAdd[kIdIndex] = id;
fAdd[kOperand1Index] = operand1;
fAdd[kOperand2Index] = operand2;
copyInstruction(fAdd.data(), kFAddInstructionLength);
}
void SpirvTransformerBase::writeFMul(uint32_t id,
uint32_t typeId,
uint32_t operand1,
uint32_t operand2)
{
// SPIR-V 1.0 Section 3.32 Instructions, OpFMul
constexpr size_t kTypeIdIndex = 1;
constexpr size_t kIdIndex = 2;
constexpr size_t kOperand1Index = 3;
constexpr size_t kOperand2Index = 4;
constexpr size_t kFMulInstructionLength = 5;
std::array<uint32_t, kFMulInstructionLength> fMul = {};
// Fill the fields.
SetSpirvInstructionOp(fMul.data(), spv::OpFMul);
SetSpirvInstructionLength(fMul.data(), kFMulInstructionLength);
fMul[kTypeIdIndex] = typeId;
fMul[kIdIndex] = id;
fMul[kOperand1Index] = operand1;
fMul[kOperand2Index] = operand2;
copyInstruction(fMul.data(), kFMulInstructionLength);
}
void SpirvTransformerBase::writeFNegate(uint32_t id, uint32_t typeId, uint32_t operand)
{
// SPIR-V 1.0 Section 3.32 Instructions, OpFNegate
constexpr size_t kTypeIdIndex = 1;
constexpr size_t kIdIndex = 2;
constexpr size_t kOperandIndex = 3;
constexpr size_t kFNegateInstructionLength = 4;
std::array<uint32_t, kFNegateInstructionLength> fNegate = {};
// Fill the fields.
SetSpirvInstructionOp(fNegate.data(), spv::OpFNegate);
SetSpirvInstructionLength(fNegate.data(), kFNegateInstructionLength);
fNegate[kTypeIdIndex] = typeId;
fNegate[kIdIndex] = id;
fNegate[kOperandIndex] = operand;
copyInstruction(fNegate.data(), kFNegateInstructionLength);
}
void SpirvTransformerBase::writeLoad(uint32_t id, uint32_t typeId, uint32_t pointerId)
{
// SPIR-V 1.0 Section 3.32 Instructions, OpLoad
constexpr size_t kTypeIndex = 1;
constexpr size_t kIdIndex = 2;
constexpr size_t kPointerIdIndex = 3;
constexpr size_t kOpLoadInstructionLength = 4;
std::array<uint32_t, kOpLoadInstructionLength> load = {};
SetSpirvInstructionOp(load.data(), spv::OpLoad);
SetSpirvInstructionLength(load.data(), kOpLoadInstructionLength);
load[kTypeIndex] = typeId;
load[kIdIndex] = id;
load[kPointerIdIndex] = pointerId;
copyInstruction(load.data(), kOpLoadInstructionLength);
}
void SpirvTransformerBase::writeMemberDecorate(uint32_t typeId,
uint32_t member,
uint32_t decoration,
uint32_t value)
{
// SPIR-V 1.0 Section 3.32 Instructions, OpMemberDecorate
constexpr size_t kTypeIdIndex = 1;
constexpr size_t kMemberIndex = 2;
constexpr size_t kDecorationIndex = 3;
constexpr size_t kValueIndex = 4;
constexpr size_t kOpMemberDecorateInstructionLength = 5;
std::array<uint32_t, kOpMemberDecorateInstructionLength> memberDecorate = {};
SetSpirvInstructionOp(memberDecorate.data(), spv::OpMemberDecorate);
SetSpirvInstructionLength(memberDecorate.data(), kOpMemberDecorateInstructionLength);
memberDecorate[kTypeIdIndex] = typeId;
memberDecorate[kMemberIndex] = member;
memberDecorate[kDecorationIndex] = decoration;
memberDecorate[kValueIndex] = value;
copyInstruction(memberDecorate.data(), kOpMemberDecorateInstructionLength);
}
void SpirvTransformerBase::writeStore(uint32_t pointerId, uint32_t objectId)
{
// SPIR-V 1.0 Section 3.32 Instructions, OpStore
constexpr size_t kPointerIdIndex = 1;
constexpr size_t kObjectIdIndex = 2;
constexpr size_t kStoreInstructionLength = 3;
std::array<uint32_t, kStoreInstructionLength> store = {};
// Fill the fields.
SetSpirvInstructionOp(store.data(), spv::OpStore);
SetSpirvInstructionLength(store.data(), kStoreInstructionLength);
store[kPointerIdIndex] = pointerId;
store[kObjectIdIndex] = objectId;
copyInstruction(store.data(), kStoreInstructionLength);
}
void SpirvTransformerBase::writeTypeFloat(uint32_t id, uint32_t width)
{
// SPIR-V 1.0 Section 3.32 Instructions, OpTypeFloat
constexpr size_t kIdIndex = 1;
constexpr size_t kWidthIndex = 2;
constexpr size_t kTypeFloatInstructionLength = 3;
std::array<uint32_t, kTypeFloatInstructionLength> typeFloat = {};
// Fill the fields.
SetSpirvInstructionOp(typeFloat.data(), spv::OpTypeFloat);
SetSpirvInstructionLength(typeFloat.data(), kTypeFloatInstructionLength);
typeFloat[kIdIndex] = id;
typeFloat[kWidthIndex] = width;
copyInstruction(typeFloat.data(), kTypeFloatInstructionLength);
}
void SpirvTransformerBase::writeTypeInt(uint32_t id, uint32_t width, uint32_t signedness)
{
// SPIR-V 1.0 Section 3.32 Instructions, OpTypeInt
constexpr size_t kIdIndex = 1;
constexpr size_t kWidthIndex = 2;
constexpr size_t kSignednessIndex = 3;
constexpr size_t kTypeIntInstructionLength = 4;
std::array<uint32_t, kTypeIntInstructionLength> typeInt = {};
// Fill the fields.
SetSpirvInstructionOp(typeInt.data(), spv::OpTypeInt);
SetSpirvInstructionLength(typeInt.data(), kTypeIntInstructionLength);
typeInt[kIdIndex] = id;
typeInt[kWidthIndex] = width;
typeInt[kSignednessIndex] = signedness;
copyInstruction(typeInt.data(), kTypeIntInstructionLength);
}
void SpirvTransformerBase::writeTypePointer(uint32_t id, uint32_t storageClass, uint32_t typeId)
{
// SPIR-V 1.0 Section 3.32 Instructions, OpTypePointer
constexpr size_t kIdIndex = 1;
constexpr size_t kStorageClassIndex = 2;
constexpr size_t kTypeIdIndex = 3;
constexpr size_t kTypePointerInstructionLength = 4;
std::array<uint32_t, kTypePointerInstructionLength> typePointer = {};
// Fill the fields.
SetSpirvInstructionOp(typePointer.data(), spv::OpTypePointer);
SetSpirvInstructionLength(typePointer.data(), kTypePointerInstructionLength);
typePointer[kIdIndex] = id;
typePointer[kStorageClassIndex] = storageClass;
typePointer[kTypeIdIndex] = typeId;
copyInstruction(typePointer.data(), kTypePointerInstructionLength);
}
void SpirvTransformerBase::writeTypeVector(uint32_t id,
uint32_t componentTypeId,
uint32_t componentCount)
{
// SPIR-V 1.0 Section 3.32 Instructions, OpTypeVector
constexpr size_t kIdIndex = 1;
constexpr size_t kComponentTypeIdIndex = 2;
constexpr size_t kComponentCountIndex = 3;
constexpr size_t kTypeVectorInstructionLength = 4;
std::array<uint32_t, kTypeVectorInstructionLength> typeVector = {};
// Fill the fields.
SetSpirvInstructionOp(typeVector.data(), spv::OpTypeVector);
SetSpirvInstructionLength(typeVector.data(), kTypeVectorInstructionLength);
typeVector[kIdIndex] = id;
typeVector[kComponentTypeIdIndex] = componentTypeId;
typeVector[kComponentCountIndex] = componentCount;
copyInstruction(typeVector.data(), kTypeVectorInstructionLength);
}
void SpirvTransformerBase::writeVariable(uint32_t id, uint32_t typeId, uint32_t storageClass)
{
// SPIR-V 1.0 Section 3.32 Instructions, OpVariable
constexpr size_t kTypeIdIndex = 1;
constexpr size_t kIdIndex = 2;
constexpr size_t kStorageClassIndex = 3;
constexpr size_t kVariableInstructionLength = 4;
std::array<uint32_t, kVariableInstructionLength> variable = {};
// Fill the fields.
SetSpirvInstructionOp(variable.data(), spv::OpVariable);
SetSpirvInstructionLength(variable.data(), kVariableInstructionLength);
variable[kTypeIdIndex] = typeId;
variable[kIdIndex] = id;
variable[kStorageClassIndex] = storageClass;
copyInstruction(variable.data(), kVariableInstructionLength);
}
void SpirvTransformerBase::writeVectorShuffle(uint32_t id,
uint32_t typeId,
uint32_t vec1Id,
uint32_t vec2Id,
const angle::FixedVector<uint32_t, 4> &fields)
{
// SPIR-V 1.0 Section 3.32 Instructions, OpVectorShuffle
constexpr size_t kTypeIdIndex = 1;
constexpr size_t kIdIndex = 2;
constexpr size_t kVec1IdIndex = 3;
constexpr size_t kVec2IdIndex = 4;
constexpr size_t kFieldsIndexStart = 5;
constexpr size_t kFieldsMaxCount = 4;
constexpr size_t kVectorShuffleInstructionBaseLength = 5;
ASSERT(kFieldsMaxCount == fields.max_size());
std::array<uint32_t, kVectorShuffleInstructionBaseLength + kFieldsMaxCount> vectorShuffle = {};
// Fill the fields.
SetSpirvInstructionOp(vectorShuffle.data(), spv::OpVectorShuffle);
SetSpirvInstructionLength(vectorShuffle.data(),
kVectorShuffleInstructionBaseLength + fields.size());
vectorShuffle[kTypeIdIndex] = typeId;
vectorShuffle[kIdIndex] = id;
vectorShuffle[kVec1IdIndex] = vec1Id;
vectorShuffle[kVec2IdIndex] = vec2Id;
for (size_t fieldIndex = 0; fieldIndex < fields.size(); ++fieldIndex)
{
vectorShuffle[kFieldsIndexStart + fieldIndex] = fields[fieldIndex];
}
copyInstruction(vectorShuffle.data(), kVectorShuffleInstructionBaseLength + fields.size());
}
// 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 std::vector<uint32_t> &spirvBlobIn,
GlslangSpirvOptions options,
const ShaderInterfaceVariableInfoMap &variableInfoMap,
SpirvBlob *spirvBlobOut)
: SpirvTransformerBase(spirvBlobIn, variableInfoMap, spirvBlobOut),
mOptions(options),
mHasTransformFeedbackOutput(false),
mOutputPerVertex{},
mInputPerVertex{}
{}
bool 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 visitName(const uint32_t *instruction);
void visitMemberName(const uint32_t *instruction);
void visitTypeHelper(const uint32_t *instruction, size_t idIndex, size_t typeIdIndex);
void visitTypeArray(const uint32_t *instruction);
void visitTypeFloat(const uint32_t *instruction);
void visitTypeInt(const uint32_t *instruction);
void visitTypePointer(const uint32_t *instruction);
void visitTypeVector(const uint32_t *instruction);
void visitVariable(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.
bool transformAccessChain(const uint32_t *instruction, size_t wordCount);
bool transformCapability(const uint32_t *instruction, size_t wordCount);
bool transformDebugInfo(const uint32_t *instruction, size_t wordCount);
bool transformEmitVertex(const uint32_t *instruction, size_t wordCount);
bool transformEntryPoint(const uint32_t *instruction, size_t wordCount);
bool transformDecorate(const uint32_t *instruction, size_t wordCount);
bool transformMemberDecorate(const uint32_t *instruction, size_t wordCount);
bool transformTypePointer(const uint32_t *instruction, size_t wordCount);
bool transformTypeStruct(const uint32_t *instruction, size_t wordCount);
bool transformReturn(const uint32_t *instruction, size_t wordCount);
bool transformVariable(const uint32_t *instruction, size_t wordCount);
bool transformExecutionMode(const uint32_t *instruction, size_t wordCount);
// Helpers:
void writePendingDeclarations();
void writeInputPreamble();
void writeOutputPrologue();
void preRotateXY(uint32_t xId, uint32_t yId, uint32_t *rotatedXIdOut, uint32_t *rotatedYIdOut);
void transformZToVulkanClipSpace(uint32_t zId, uint32_t wId, uint32_t *correctedZIdOut);
// Special flags:
GlslangSpirvOptions mOptions;
bool mHasTransformFeedbackOutput;
// Traversal state:
bool mInsertFunctionVariables = false;
uint32_t mEntryPointId = 0;
uint32_t mOpFunctionId = 0;
// Transformation state:
// Names associated with ids through OpName. The same name may be assigned to multiple ids, but
// not all names are interesting (for example function arguments). When the variable
// declaration is met (OpVariable), the variable info is matched with the corresponding id's
// name based on the Storage Class.
std::vector<const char *> mNamesById;
// Tracks whether a given type is an I/O block. I/O blocks are identified by their type name
// instead of variable name, but otherwise look like varyings of struct type (which are
// identified by their instance name). To disambiguate them, the `OpDecorate %N Block`
// instruction is used which decorates I/O block types.
std::vector<bool> mIsIOBlockById;
// 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.
struct TransformedIDs
{
uint32_t privateID;
uint32_t typeID;
};
std::vector<TransformedIDs> mTypePointerTransformedId;
std::vector<uint32_t> mFixedVaryingId;
std::vector<uint32_t> mFixedVaryingTypeId;
// gl_PerVertex is unique in that it's the only builtin of struct type. This struct is pruned
// by removing trailing inactive members. We therefore need to keep track of what's its type id
// as well as which is the last active member. Note that intermediate stages, i.e. geometry and
// tessellation have two gl_PerVertex declarations, one for input and one for output.
struct PerVertexData
{
uint32_t typeId;
uint32_t maxActiveMember;
};
PerVertexData mOutputPerVertex;
PerVertexData mInputPerVertex;
// A handful of ids that are used to generate gl_Position transformation code (for pre-rotation
// or depth correction). These IDs are used to load/store gl_Position and apply modifications
// and swizzles.
//
// - mFloatId: id of OpTypeFloat 32
// - mVec4Id: id of OpTypeVector %mFloatID 4
// - mVec4OutTypePointerId: id of OpTypePointer Output %mVec4ID
// - mIntId: id of OpTypeInt 32 1
// - mInt0Id: id of OpConstant %mIntID 0
// - mFloatHalfId: id of OpConstant %mFloatId 0.5f
// - mOutputPerVertexTypePointerId: id of OpTypePointer Output %mOutputPerVertex.typeId
// - mOutputPerVertexId: id of OpVariable %mOutputPerVertexTypePointerId Output
//
uint32_t mFloatId = 0;
uint32_t mVec4Id = 0;
uint32_t mVec4OutTypePointerId = 0;
uint32_t mIntId = 0;
uint32_t mInt0Id = 0;
uint32_t mFloatHalfId = 0;
uint32_t mOutputPerVertexTypePointerId = 0;
uint32_t mOutputPerVertexId = 0;
};
bool SpirvTransformer::transform()
{
onTransformBegin();
// First, find all necessary ids and associate them with the information required to transform
// their decorations.
resolveVariableIds();
while (mCurrentWord < mSpirvBlobIn.size())
{
transformInstruction();
}
return true;
}
void SpirvTransformer::resolveVariableIds()
{
const size_t indexBound = mSpirvBlobIn[kHeaderIndexIndexBound];
// Allocate storage for id-to-name map. Used to associate ShaderInterfaceVariableInfo with ids
// based on name, but only when it's determined that the name corresponds to a shader interface
// variable.
mNamesById.resize(indexBound, nullptr);
// Allocate storage for id-to-flag map. Used to disambiguate I/O blocks instances from varyings
// of struct type.
mIsIOBlockById.resize(indexBound, false);
// Allocate storage for id-to-info map. If %i is the id of a name 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);
// 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.
// Also store a FunctionID and TypeID for when we need to fix a precision mismatch
mTypePointerTransformedId.resize(indexBound, {0, 0});
mFixedVaryingId.resize(indexBound, {0});
mFixedVaryingTypeId.resize(indexBound, {0});
size_t currentWord = kHeaderIndexInstructions;
while (currentWord < mSpirvBlobIn.size())
{
const uint32_t *instruction = &mSpirvBlobIn[currentWord];
const uint32_t wordCount = GetSpirvInstructionLength(instruction);
const uint32_t opCode = GetSpirvInstructionOp(instruction);
switch (opCode)
{
case spv::OpDecorate:
visitDecorate(instruction);
break;
case spv::OpName:
visitName(instruction);
break;
case spv::OpMemberName:
visitMemberName(instruction);
break;
case spv::OpTypeArray:
visitTypeArray(instruction);
break;
case spv::OpTypeFloat:
visitTypeFloat(instruction);
break;
case spv::OpTypeInt:
visitTypeInt(instruction);
break;
case spv::OpTypePointer:
visitTypePointer(instruction);
break;
case spv::OpTypeVector:
visitTypeVector(instruction);
break;
case spv::OpVariable:
visitVariable(instruction);
break;
case spv::OpFunction:
// 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.
return;
default:
break;
}
currentWord += wordCount;
}
}
void SpirvTransformer::transformInstruction()
{
uint32_t wordCount;
uint32_t opCode;
const uint32_t *instruction = getCurrentInstruction(&opCode, &wordCount);
if (opCode == spv::OpFunction)
{
constexpr size_t kFunctionIdIndex = 2;
mOpFunctionId = instruction[kFunctionIdIndex];
// 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.
//
// If this is the first OpFunction instruction, this is also where the declaration section
// finishes, so we need to declare anything that we need but didn't find there already right
// now.
if (!mIsInFunctionSection)
{
writePendingDeclarations();
}
mIsInFunctionSection = true;
// Only write function variables for the EntryPoint function for non-compute shaders
mInsertFunctionVariables =
mOpFunctionId == mEntryPointId && mOptions.shaderType != gl::ShaderType::Compute;
}
// Only look at interesting instructions.
bool transformed = false;
if (mIsInFunctionSection)
{
// After we process an OpFunction instruction and any instructions that must come
// immediately after OpFunction we need to check if there are any precision mismatches that
// need to be handled. If so, output OpVariable for each variable that needed to change from
// a StorageClassOutput to a StorageClassFunction.
if (mInsertFunctionVariables && opCode != spv::OpFunction &&
opCode != spv::OpFunctionParameter && opCode != spv::OpLabel &&
opCode != spv::OpVariable)
{
writeInputPreamble();
mInsertFunctionVariables = false;
}
// Look at in-function opcodes.
switch (opCode)
{
case spv::OpAccessChain:
case spv::OpInBoundsAccessChain:
case spv::OpPtrAccessChain:
case spv::OpInBoundsPtrAccessChain:
transformed = transformAccessChain(instruction, wordCount);
break;
case spv::OpEmitVertex:
transformed = transformEmitVertex(instruction, wordCount);
break;
case spv::OpReturn:
transformed = transformReturn(instruction, wordCount);
break;
default:
break;
}
}
else
{
// Look at global declaration opcodes.
switch (opCode)
{
case spv::OpSourceContinued:
case spv::OpSource:
case spv::OpSourceExtension:
case spv::OpName:
case spv::OpMemberName:
case spv::OpString:
case spv::OpLine:
case spv::OpNoLine:
case spv::OpModuleProcessed:
transformed = transformDebugInfo(instruction, wordCount);
break;
case spv::OpCapability:
transformed = transformCapability(instruction, wordCount);
break;
case spv::OpEntryPoint:
transformed = transformEntryPoint(instruction, wordCount);
break;
case spv::OpDecorate:
transformed = transformDecorate(instruction, wordCount);
break;
case spv::OpMemberDecorate:
transformed = transformMemberDecorate(instruction, wordCount);
break;
case spv::OpTypePointer:
transformed = transformTypePointer(instruction, wordCount);
break;
case spv::OpTypeStruct:
transformed = transformTypeStruct(instruction, wordCount);
break;
case spv::OpVariable:
transformed = transformVariable(instruction, wordCount);
break;
case spv::OpExecutionMode:
transformed = transformExecutionMode(instruction, wordCount);
break;
default:
break;
}
}
// If the instruction was not transformed, copy it to output as is.
if (!transformed)
{
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()
{
// Pre-rotation and transformation of depth to Vulkan clip space require declarations that may
// not necessarily be in the shader.
if (IsRotationIdentity(mOptions.preRotation) && !mOptions.transformPositionToVulkanClipSpace)
{
return;
}
if (mFloatId == 0)
{
mFloatId = getNewId();
writeTypeFloat(mFloatId, 32);
}
if (mVec4Id == 0)
{
mVec4Id = getNewId();
writeTypeVector(mVec4Id, mFloatId, 4);
}
if (mVec4OutTypePointerId == 0)
{
mVec4OutTypePointerId = getNewId();
writeTypePointer(mVec4OutTypePointerId, spv::StorageClassOutput, mVec4Id);
}
if (mIntId == 0)
{
mIntId = getNewId();
writeTypeInt(mIntId, 32, 1);
}
ASSERT(mInt0Id == 0);
mInt0Id = getNewId();
writeConstant(mInt0Id, mIntId, 0);
constexpr uint32_t kFloatHalfAsUint = 0x3F00'0000;
ASSERT(mFloatHalfId == 0);
mFloatHalfId = getNewId();
writeConstant(mFloatHalfId, mFloatId, kFloatHalfAsUint);
}
// Called by transformInstruction to insert necessary instructions for casting varyings.
void SpirvTransformer::writeInputPreamble()
{
if (mOptions.shaderType == gl::ShaderType::Vertex ||
mOptions.shaderType == gl::ShaderType::Compute)
{
return;
}
// Copy from corrected varyings to temp global variables with original precision.
for (uint32_t id = 0; id < mVariableInfoById.size(); id++)
{
const ShaderInterfaceVariableInfo *info = mVariableInfoById[id];
if (info && info->useRelaxedPrecision && info->activeStages[mOptions.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] != 0);
// Build OpLoad instruction to load the mediump value into a temporary
uint32_t tempVar = getNewId();
uint32_t tempVarType = mTypePointerTransformedId[mFixedVaryingTypeId[id]].typeID;
ASSERT(tempVarType != 0);
writeLoad(tempVar, tempVarType, mFixedVaryingId[id]);
// Build OpStore instruction to cast the mediump value to highp for use in
// the function
writeStore(id, tempVar);
}
}
}
// Called by transformInstruction to insert necessary instructions for casting varyings and
// modifying gl_Position.
void SpirvTransformer::writeOutputPrologue()
{
if (mOptions.shaderType == gl::ShaderType::Fragment ||
mOptions.shaderType == gl::ShaderType::Compute)
{
return;
}
// Copy from temp global variables with original precision to corrected varyings.
for (uint32_t id = 0; id < mVariableInfoById.size(); id++)
{
const ShaderInterfaceVariableInfo *info = mVariableInfoById[id];
if (info && info->useRelaxedPrecision && info->activeStages[mOptions.shaderType] &&
info->varyingIsOutput)
{
ASSERT(mFixedVaryingTypeId[id] != 0);
// Build OpLoad instruction to load the highp value into a temporary
uint32_t tempVar = getNewId();
uint32_t tempVarType = mTypePointerTransformedId[mFixedVaryingTypeId[id]].typeID;
ASSERT(tempVarType != 0);
writeLoad(tempVar, tempVarType, id);
// Build OpStore instruction to cast the highp value to mediump for output
writeStore(mFixedVaryingId[id], tempVar);
}
}
// Transform gl_Position to account for prerotation and Vulkan clip space if necessary.
if (mOutputPerVertexId == 0 ||
(IsRotationIdentity(mOptions.preRotation) && !mOptions.transformPositionToVulkanClipSpace))
{
return;
}
// In GL the viewport transformation is slightly different - see the GL 2.0 spec section "2.12.1
// Controlling the Viewport". In Vulkan the corresponding spec section is currently "23.4.
// Coordinate Transformations". The following transformation needs to be done:
//
// z_vk = 0.5 * (w_gl + z_gl)
//
// where z_vk is the depth output of a Vulkan geometry-stage shader and z_gl is the same for GL.
// Generate the following SPIR-V for prerotation and depth transformation:
//
// // Create an access chain to output gl_PerVertex.gl_Position, which is always at index 0.
// %PositionPointer = OpAccessChain %mVec4OutTypePointerId %mOutputPerVertexId %mInt0Id
// // Load gl_Position
// %Position = OpLoad %mVec4Id %PositionPointer
// // Create gl_Position.x and gl_Position.y for transformation, as well as gl_Position.z
// // and gl_Position.w for later.
// %x = OpCompositeExtract %mFloatId %Position 0
// %y = OpCompositeExtract %mFloatId %Position 1
// %z = OpCompositeExtract %mFloatId %Position 2
// %w = OpCompositeExtract %mFloatId %Position 3
//
// // Transform %x and %y based on pre-rotation. This could include swapping the two ids
// // (in the transformer, no need to generate SPIR-V instructions for that), and/or
// // negating either component. To negate a component, the following instruction is used:
// (optional:) %negated = OpFNegate %mFloatId %component
//
// // Transform %z if necessary, based on the above formula.
// %zPlusW = OpFAdd %mFloatId %z %w
// %correctedZ = OpFMul %mFloatId %zPlusW %mFloatHalfId
//
// // Create the rotated gl_Position from the rotated x and y and corrected z components.
// %RotatedPosition = OpCompositeConstruct %mVec4Id %rotatedX %rotatedY %correctedZ %w
// // Store the results back in gl_Position
// OpStore %PositionPointer %RotatedPosition
//
const uint32_t positionPointerId = getNewId();
const uint32_t positionId = getNewId();
const uint32_t xId = getNewId();
const uint32_t yId = getNewId();
const uint32_t zId = getNewId();
const uint32_t wId = getNewId();
const uint32_t rotatedPositionId = getNewId();
writeAccessChain(positionPointerId, mVec4OutTypePointerId, mOutputPerVertexId, mInt0Id);
writeLoad(positionId, mVec4Id, positionPointerId);
writeCompositeExtract(xId, mFloatId, positionId, 0);
writeCompositeExtract(yId, mFloatId, positionId, 1);
writeCompositeExtract(zId, mFloatId, positionId, 2);
writeCompositeExtract(wId, mFloatId, positionId, 3);
uint32_t rotatedXId = 0;
uint32_t rotatedYId = 0;
preRotateXY(xId, yId, &rotatedXId, &rotatedYId);
uint32_t correctedZId = 0;
transformZToVulkanClipSpace(zId, wId, &correctedZId);
writeCompositeConstruct(rotatedPositionId, mVec4Id,
{rotatedXId, rotatedYId, correctedZId, wId});
writeStore(positionPointerId, rotatedPositionId);
}
void SpirvTransformer::preRotateXY(uint32_t xId,
uint32_t yId,
uint32_t *rotatedXIdOut,
uint32_t *rotatedYIdOut)
{
switch (mOptions.preRotation)
{
case SurfaceRotation::Identity:
case SurfaceRotation::FlippedIdentity:
// [ 1 0] [x]
// [ 0 1] * [y]
*rotatedXIdOut = xId;
*rotatedYIdOut = yId;
break;
case SurfaceRotation::Rotated90Degrees:
case SurfaceRotation::FlippedRotated90Degrees:
// [ 0 1] [x]
// [-1 0] * [y]
*rotatedXIdOut = yId;
*rotatedYIdOut = getNewId();
writeFNegate(*rotatedYIdOut, mFloatId, xId);
break;
case SurfaceRotation::Rotated180Degrees:
case SurfaceRotation::FlippedRotated180Degrees:
// [-1 0] [x]
// [ 0 -1] * [y]
*rotatedXIdOut = getNewId();
*rotatedYIdOut = getNewId();
writeFNegate(*rotatedXIdOut, mFloatId, xId);
writeFNegate(*rotatedYIdOut, mFloatId, yId);
break;
case SurfaceRotation::Rotated270Degrees:
case SurfaceRotation::FlippedRotated270Degrees:
// [ 0 -1] [x]
// [ 1 0] * [y]
*rotatedXIdOut = getNewId();
*rotatedYIdOut = xId;
writeFNegate(*rotatedXIdOut, mFloatId, yId);
break;
default:
UNREACHABLE();
}
}
void SpirvTransformer::transformZToVulkanClipSpace(uint32_t zId,
uint32_t wId,
uint32_t *correctedZIdOut)
{
if (!mOptions.transformPositionToVulkanClipSpace)
{
*correctedZIdOut = zId;
return;
}
const uint32_t zPlusWId = getNewId();
*correctedZIdOut = getNewId();
// %zPlusW = OpFAdd %mFloatId %z %w
writeFAdd(zPlusWId, mFloatId, zId, wId);
// %correctedZ = OpFMul %mFloatId %zPlusW %mFloatHalfId
writeFMul(*correctedZIdOut, mFloatId, zPlusWId, mFloatHalfId);
}
void SpirvTransformer::visitDecorate(const uint32_t *instruction)
{
// SPIR-V 1.0 Section 3.32 Instructions, OpDecorate
constexpr size_t kIdIndex = 1;
constexpr size_t kDecorationIndex = 2;
const uint32_t id = instruction[kIdIndex];
uint32_t decoration = instruction[kDecorationIndex];
if (decoration == spv::DecorationBlock)
{
mIsIOBlockById[id] = true;
// For I/O blocks, associate the type with the info, which is used to decorate its members
// with transform feedback if any.
const char *name = mNamesById[id];
ASSERT(name != nullptr);
const ShaderInterfaceVariableInfo &info = mVariableInfoMap.get(mOptions.shaderType, name);
mVariableInfoById[id] = &info;
}
}
void SpirvTransformer::visitName(const uint32_t *instruction)
{
// We currently don't have any big-endian devices in the list of supported platforms. Literal
// strings in SPIR-V are stored little-endian (SPIR-V 1.0 Section 2.2.1, Literal String), so if
// a big-endian device is to be supported, the string matching here should be specialized.
ASSERT(IsLittleEndian());
// SPIR-V 1.0 Section 3.32 Instructions, OpName
constexpr size_t kIdIndex = 1;
constexpr size_t kNameIndex = 2;
const uint32_t id = instruction[kIdIndex];
const char *name = reinterpret_cast<const char *>(&instruction[kNameIndex]);
// The names and ids are unique
ASSERT(id < mNamesById.size());
ASSERT(mNamesById[id] == nullptr);
mNamesById[id] = name;
}
void SpirvTransformer::visitMemberName(const uint32_t *instruction)
{
ASSERT(IsLittleEndian());
// SPIR-V 1.0 Section 3.32 Instructions, OpMemberName
constexpr size_t kIdIndex = 1;
constexpr size_t kMemberIndex = 2;
constexpr size_t kNameIndex = 3;
const uint32_t id = instruction[kIdIndex];
const uint32_t member = instruction[kMemberIndex];
const char *name = reinterpret_cast<const char *>(&instruction[kNameIndex]);
// The names and ids are unique
ASSERT(id < mNamesById.size());
ASSERT(mNamesById[id] != nullptr);
if (strcmp(mNamesById[id], "gl_PerVertex") != 0)
{
return;
}
if (!mVariableInfoMap.contains(mOptions.shaderType, name))
{
return;
}
const ShaderInterfaceVariableInfo &info = mVariableInfoMap.get(mOptions.shaderType, name);
// Assume output gl_PerVertex is encountered first. When the storage class of these types are
// determined, the variables can be swapped if this assumption was incorrect.
if (mOutputPerVertex.typeId == 0 || id == mOutputPerVertex.typeId)
{
mOutputPerVertex.typeId = id;
// Keep track of the range of members that are active.
if (info.varyingIsOutput && member > mOutputPerVertex.maxActiveMember)
{
mOutputPerVertex.maxActiveMember = member;
}
}
else if (mInputPerVertex.typeId == 0 || id == mInputPerVertex.typeId)
{
mInputPerVertex.typeId = id;
// Keep track of the range of members that are active.
if (info.varyingIsInput && member > mInputPerVertex.maxActiveMember)
{
mInputPerVertex.maxActiveMember = member;
}
}
else
{
UNREACHABLE();
}
}
void SpirvTransformer::visitTypeHelper(const uint32_t *instruction,
const size_t idIndex,
const size_t typeIdIndex)
{
const uint32_t id = instruction[idIndex];
const uint32_t typeId = instruction[typeIdIndex];
// Every type id is declared only once.
ASSERT(id < mNamesById.size());
ASSERT(mNamesById[id] == nullptr);
ASSERT(id < mIsIOBlockById.size());
ASSERT(!mIsIOBlockById[id]);
// Carry the name forward from the base type. This is only necessary for interface blocks,
// as the variable info is associated with the block name instead of the variable name (to
// support nameless interface blocks). When the variable declaration is met, either the
// type name or the variable name is used to associate with info based on the variable's
// storage class.
ASSERT(typeId < mNamesById.size());
mNamesById[id] = mNamesById[typeId];
// Similarly, carry forward the information regarding whether this type is an I/O block.
ASSERT(typeId < mIsIOBlockById.size());
mIsIOBlockById[id] = mIsIOBlockById[typeId];
}
void SpirvTransformer::visitTypeArray(const uint32_t *instruction)
{
// SPIR-V 1.0 Section 3.32 Instructions, OpTypeArray
constexpr size_t kIdIndex = 1;
constexpr size_t kElementTypeIdIndex = 2;
visitTypeHelper(instruction, kIdIndex, kElementTypeIdIndex);
}
void SpirvTransformer::visitTypeFloat(const uint32_t *instruction)
{
// SPIR-V 1.0 Section 3.32 Instructions, OpTypeFloat
constexpr size_t kIdIndex = 1;
constexpr size_t kWidthIndex = 2;
const uint32_t id = instruction[kIdIndex];
const uint32_t width = instruction[kWidthIndex];
// Only interested in OpTypeFloat 32.
if (width == 32)
{
ASSERT(mFloatId == 0);
mFloatId = id;
}
}
void SpirvTransformer::visitTypeInt(const uint32_t *instruction)
{
// SPIR-V 1.0 Section 3.32 Instructions, OpTypeInt
constexpr size_t kIdIndex = 1;
constexpr size_t kWidthIndex = 2;
constexpr size_t kSignednessIndex = 3;
const uint32_t id = instruction[kIdIndex];
const uint32_t width = instruction[kWidthIndex];
const uint32_t signedness = instruction[kSignednessIndex];
// Only interested in OpTypeInt 32 1.
if (width == 32 && signedness == 1)
{
ASSERT(mIntId == 0);
mIntId = id;
}
}
void SpirvTransformer::visitTypePointer(const uint32_t *instruction)
{
// SPIR-V 1.0 Section 3.32 Instructions, OpTypePointer
constexpr size_t kIdIndex = 1;
constexpr size_t kStorageClassIndex = 2;
constexpr size_t kTypeIdIndex = 3;
visitTypeHelper(instruction, kIdIndex, kTypeIdIndex);
const uint32_t id = instruction[kIdIndex];
const uint32_t typeId = instruction[kTypeIdIndex];
const uint32_t storageClass = instruction[kStorageClassIndex];
// Verify that the ids associated with input and output gl_PerVertex are correct.
if (typeId == mOutputPerVertex.typeId || typeId == mInputPerVertex.typeId)
{
// If assumption about the first gl_PerVertex encountered being Output is wrong, swap the
// two ids.
if ((typeId == mOutputPerVertex.typeId && storageClass == spv::StorageClassInput) ||
(typeId == mInputPerVertex.typeId && storageClass == spv::StorageClassOutput))
{
std::swap(mOutputPerVertex.typeId, mInputPerVertex.typeId);
}
// Remember type pointer of output gl_PerVertex for gl_Position transformations.
if (storageClass == spv::StorageClassOutput)
{
mOutputPerVertexTypePointerId = id;
}
}
// If OpTypePointer Output %mVec4ID was encountered, remember that. Otherwise we'll have to
// generate one.
if (typeId == mVec4Id && storageClass == spv::StorageClassOutput)
{
mVec4OutTypePointerId = id;
}
}
void SpirvTransformer::visitTypeVector(const uint32_t *instruction)
{
// SPIR-V 1.0 Section 3.32 Instructions, OpTypeVector
constexpr size_t kIdIndex = 1;
constexpr size_t kComponentIdIndex = 2;
constexpr size_t kComponentCountIndex = 3;
const uint32_t id = instruction[kIdIndex];
const uint32_t componentId = instruction[kComponentIdIndex];
const uint32_t componentCount = instruction[kComponentCountIndex];
// Only interested in OpTypeVector %mFloatId 4
if (componentId == mFloatId && componentCount == 4)
{
ASSERT(mVec4Id == 0);
mVec4Id = id;
}
}
void SpirvTransformer::visitVariable(const uint32_t *instruction)
{
// SPIR-V 1.0 Section 3.32 Instructions, OpVariable
constexpr size_t kTypeIdIndex = 1;
constexpr size_t kIdIndex = 2;
constexpr size_t kStorageClassIndex = 3;
// All resources that take set/binding should be transformed.
const uint32_t typeId = instruction[kTypeIdIndex];
const uint32_t id = instruction[kIdIndex];
const uint32_t storageClass = instruction[kStorageClassIndex];
ASSERT(typeId < mNamesById.size());
ASSERT(id < mNamesById.size());
ASSERT(typeId < mIsIOBlockById.size());
// 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;
}
// The ids are unique.
ASSERT(id < mVariableInfoById.size());
ASSERT(mVariableInfoById[id] == nullptr);
// For interface block variables, the name that's used to associate info is the block name
// rather than the variable name.
const bool isIOBlock = mIsIOBlockById[typeId];
const char *name = mNamesById[isInterfaceBlockVariable || isIOBlock ? typeId : id];
ASSERT(name != nullptr);
// Handle builtins, which all start with "gl_". The variable name could be an indication of a
// builtin variable (such as with gl_FragCoord). gl_PerVertex is the only builtin whose "type"
// name starts with gl_. However, gl_PerVertex has its own entry in the info map for its
// potential use with transform feedback.
const bool isNameBuiltin = isInOut && !isIOBlock && gl::IsBuiltInName(name);
if (isNameBuiltin)
{
// 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 (typeId == mOutputPerVertexTypePointerId)
{
// If this is the output gl_PerVertex variable, remember its id for gl_Position
// transformations.
ASSERT(storageClass == spv::StorageClassOutput && isIOBlock &&
strcmp(name, "gl_PerVertex") == 0);
mOutputPerVertexId = id;
}
// Every shader interface variable should have an associated data.
const ShaderInterfaceVariableInfo &info = mVariableInfoMap.get(mOptions.shaderType, name);
// Associate the id of this name with its info.
mVariableInfoById[id] = &info;
if (info.useRelaxedPrecision && info.activeStages[mOptions.shaderType] &&
mFixedVaryingId[id] == 0)
{
mFixedVaryingId[id] = getNewId();
mFixedVaryingTypeId[id] = typeId;
}
// Note if the variable is captured by transform feedback. In that case, the TransformFeedback
// capability needs to be added.
if (mOptions.isTransformFeedbackStage &&
(info.xfb.buffer != ShaderInterfaceVariableInfo::kInvalid || !info.fieldXfb.empty()) &&
info.activeStages[mOptions.shaderType])
{
mHasTransformFeedbackOutput = true;
}
}
bool SpirvTransformer::transformDecorate(const uint32_t *instruction, size_t wordCount)
{
// SPIR-V 1.0 Section 3.32 Instructions, OpDecorate
constexpr size_t kIdIndex = 1;
constexpr size_t kDecorationIndex = 2;
constexpr size_t kDecorationValueIndex = 3;
uint32_t id = instruction[kIdIndex];
uint32_t decoration = instruction[kDecorationIndex];
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 false;
}
// If it's an inactive varying, remove the decoration altogether.
if (!info->activeStages[mOptions.shaderType])
{
return true;
}
// If this is the Block decoration of a shader I/O block, add the transform feedback decorations
// to its members right away.
constexpr size_t kXfbDecorationCount = 3;
constexpr uint32_t kXfbDecorations[kXfbDecorationCount] = {
spv::DecorationXfbBuffer,
spv::DecorationXfbStride,
spv::DecorationOffset,
};
if (mOptions.isTransformFeedbackStage && decoration == spv::DecorationBlock &&
!info->fieldXfb.empty())
{
for (uint32_t fieldIndex = 0; fieldIndex < info->fieldXfb.size(); ++fieldIndex)
{
const ShaderInterfaceVariableXfbInfo &xfb = info->fieldXfb[fieldIndex];
if (xfb.buffer == ShaderInterfaceVariableXfbInfo::kInvalid)
{
continue;
}
ASSERT(xfb.stride != ShaderInterfaceVariableXfbInfo::kInvalid);
ASSERT(xfb.offset != ShaderInterfaceVariableXfbInfo::kInvalid);
const uint32_t xfbDecorationValues[kXfbDecorationCount] = {
xfb.buffer,
xfb.stride,
xfb.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)
{
writeMemberDecorate(id, fieldIndex, kXfbDecorations[i], xfbDecorationValues[i]);
}
}
return false;
}
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:
if (info->useRelaxedPrecision)
{
// Change the id to replacement variable
ASSERT(mFixedVaryingId[id] != 0);
const size_t instructionOffset = copyInstruction(instruction, wordCount);
(*mSpirvBlobOut)[instructionOffset + kIdIndex] = mFixedVaryingId[id];
return true;
}
break;
default:
break;
}
// If the decoration is not something we care about modifying, there's nothing to do.
if (newDecorationValue == ShaderInterfaceVariableInfo::kInvalid)
{
return false;
}
// Copy the decoration declaration and modify it.
const size_t instructionOffset = copyInstruction(instruction, wordCount);
(*mSpirvBlobOut)[instructionOffset + kDecorationValueIndex] = newDecorationValue;
// If there are decorations to be added, add them right after the Location decoration is
// encountered.
if (decoration != spv::DecorationLocation)
{
return true;
}
if (info->useRelaxedPrecision)
{
// Change the id of the location decoration to replacement variable
ASSERT(mFixedVaryingId[id] != 0);
(*mSpirvBlobOut)[instructionOffset + kIdIndex] = mFixedVaryingId[id];
// The replacement variable is always reduced precision so add that decoration to
// fixedVaryingId
constexpr size_t kInstDecorateRelaxedPrecisionWordCount = 3;
uint32_t inst[kInstDecorateRelaxedPrecisionWordCount];
SetSpirvInstructionLength(inst, kInstDecorateRelaxedPrecisionWordCount);
SetSpirvInstructionOp(inst, spv::OpDecorate);
inst[kIdIndex] = mFixedVaryingId[id];
inst[kDecorationIndex] = spv::DecorationRelaxedPrecision;
copyInstruction(inst, kInstDecorateRelaxedPrecisionWordCount);
}
// Add component decoration, if any.
if (info->component != ShaderInterfaceVariableInfo::kInvalid)
{
// Copy the location decoration declaration and modify it to contain the Component
// decoration.
const size_t instOffset = copyInstruction(instruction, wordCount);
(*mSpirvBlobOut)[instOffset + kDecorationIndex] = spv::DecorationComponent;
(*mSpirvBlobOut)[instOffset + kDecorationValueIndex] = info->component;
}
// Add Xfb decorations, if any.
if (mOptions.isTransformFeedbackStage &&
info->xfb.buffer != ShaderInterfaceVariableXfbInfo::kInvalid)
{
ASSERT(info->xfb.stride != ShaderInterfaceVariableXfbInfo::kInvalid);
ASSERT(info->xfb.offset != ShaderInterfaceVariableXfbInfo::kInvalid);
const uint32_t xfbDecorationValues[kXfbDecorationCount] = {
info->xfb.buffer,
info->xfb.stride,
info->xfb.offset,
};
// Copy the location decoration declaration three times, and modify them to contain the
// XfbBuffer, XfbStride and Offset decorations.
for (size_t i = 0; i < kXfbDecorationCount; ++i)
{
const size_t xfbInstructionOffset = copyInstruction(instruction, wordCount);
if (info->useRelaxedPrecision)
{
// Change the id to replacement variable
(*mSpirvBlobOut)[xfbInstructionOffset + kIdIndex] = mFixedVaryingId[id];
}
(*mSpirvBlobOut)[xfbInstructionOffset + kDecorationIndex] = kXfbDecorations[i];
(*mSpirvBlobOut)[xfbInstructionOffset + kDecorationValueIndex] = xfbDecorationValues[i];
}
}
return true;
}
bool SpirvTransformer::transformMemberDecorate(const uint32_t *instruction, size_t wordCount)
{
// SPIR-V 1.0 Section 3.32 Instructions, OpMemberDecorate
constexpr size_t kTypeIdIndex = 1;
constexpr size_t kMemberIndex = 2;
constexpr size_t kDecorationIndex = 3;
uint32_t typeId = instruction[kTypeIdIndex];
uint32_t member = instruction[kMemberIndex];
uint32_t decoration = instruction[kDecorationIndex];
// Transform only OpMemberDecorate %gl_PerVertex N BuiltIn B
if ((typeId != mOutputPerVertex.typeId && typeId != mInputPerVertex.typeId) ||
decoration != spv::DecorationBuiltIn)
{
return false;
}
// Drop stripped fields.
return typeId == mOutputPerVertex.typeId ? member > mOutputPerVertex.maxActiveMember
: member > mInputPerVertex.maxActiveMember;
}
bool SpirvTransformer::transformCapability(const uint32_t *instruction, size_t wordCount)
{
if (!mHasTransformFeedbackOutput)
{
return false;
}
// SPIR-V 1.0 Section 3.32 Instructions, OpCapability
constexpr size_t kCapabilityIndex = 1;
uint32_t capability = instruction[kCapabilityIndex];
// 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 after it.
if (capability != spv::CapabilityShader && capability != spv::CapabilityGeometry &&
capability != spv::CapabilityTessellation)
{
return false;
}
// Copy the original capability declaration.
copyInstruction(instruction, wordCount);
// Create the TransformFeedback capability declaration.
// SPIR-V 1.0 Section 3.32 Instructions, OpCapability
constexpr size_t kCapabilityInstructionLength = 2;
std::array<uint32_t, kCapabilityInstructionLength> newCapabilityDeclaration = {
instruction[0], // length+opcode is identical
};
// Fill the fields.
newCapabilityDeclaration[kCapabilityIndex] = spv::CapabilityTransformFeedback;
copyInstruction(newCapabilityDeclaration.data(), kCapabilityInstructionLength);
return true;
}
bool SpirvTransformer::transformDebugInfo(const uint32_t *instruction, size_t wordCount)
{
if (mOptions.removeDebugInfo)
{
// Strip debug info to reduce binary size.
return true;
}
// In the case of OpMemberName, unconditionally remove stripped gl_PerVertex members.
if (GetSpirvInstructionOp(instruction) == spv::OpMemberName)
{
// SPIR-V 1.0 Section 3.32 Instructions, OpMemberName
constexpr size_t kIdIndex = 1;
constexpr size_t kMemberIndex = 2;
const uint32_t id = instruction[kIdIndex];
const uint32_t member = instruction[kMemberIndex];
// Remove the instruction if it's a stripped member of gl_PerVertex.
return (id == mOutputPerVertex.typeId && member > mOutputPerVertex.maxActiveMember) ||
(id == mInputPerVertex.typeId && member > mInputPerVertex.maxActiveMember);
}
// In the case of ANGLEXfbN, unconditionally remove the variable names. If transform
// feedback is not active, the corresponding variables will be removed.
if (GetSpirvInstructionOp(instruction) == spv::OpName)
{
// SPIR-V 1.0 Section 3.32 Instructions, OpName
constexpr size_t kNameIndex = 2;
const char *name = reinterpret_cast<const char *>(&instruction[kNameIndex]);
if (angle::BeginsWith(name, sh::vk::kXfbEmulationBufferName))
{
return true;
}
}
return false;
}
bool SpirvTransformer::transformEmitVertex(const uint32_t *instruction, size_t wordCount)
{
// This is only possible in geometry shaders.
ASSERT(mOptions.shaderType == gl::ShaderType::Geometry);
// Write the temporary variables that hold varyings data before EmitVertex().
writeOutputPrologue();
return false;
}
bool SpirvTransformer::transformEntryPoint(const uint32_t *instruction, size_t wordCount)
{
// Remove inactive varyings from the shader interface declaration.
// SPIR-V 1.0 Section 3.32 Instructions, OpEntryPoint
constexpr size_t kEntryPointIdIndex = 2;
constexpr size_t kNameIndex = 3;
// Calculate the length of entry point name in words. Note that endianness of the string
// doesn't matter, since we are looking for the '\0' character and rounding up to the word size.
// This calculates (strlen(name)+1+3) / 4, which is equal to strlen(name)/4+1.
const size_t nameLength =
strlen(reinterpret_cast<const char *>(&instruction[kNameIndex])) / 4 + 1;
const uint32_t instructionLength = GetSpirvInstructionLength(instruction);
const size_t interfaceStart = kNameIndex + nameLength;
const size_t interfaceCount = instructionLength - interfaceStart;
// Should only have one EntryPoint
ASSERT(mEntryPointId == 0);
mEntryPointId = instruction[kEntryPointIdIndex];
// Create a copy of the entry point for modification.
std::vector<uint32_t> filteredEntryPoint(instruction, instruction + wordCount);
// Filter out inactive varyings from entry point interface declaration.
size_t writeIndex = interfaceStart;
for (size_t index = 0; index < interfaceCount; ++index)
{
uint32_t id = instruction[interfaceStart + index];
const ShaderInterfaceVariableInfo *info = mVariableInfoById[id];
ASSERT(info);
if (!info->activeStages[mOptions.shaderType])
{
continue;
}
// If ID is one we had to replace due to varying mismatch, use the fixed ID.
if (mFixedVaryingId[id] != 0)
{
id = mFixedVaryingId[id];
}
filteredEntryPoint[writeIndex] = id;
++writeIndex;
}
// Update the length of the instruction.
const size_t newLength = writeIndex;
SetSpirvInstructionLength(filteredEntryPoint.data(), newLength);
// Copy to output.
copyInstruction(filteredEntryPoint.data(), newLength);
// Add an OpExecutionMode Xfb instruction if necessary.
if (!mHasTransformFeedbackOutput)
{
return true;
}
// SPIR-V 1.0 Section 3.32 Instructions, OpExecutionMode
constexpr size_t kExecutionModeInstructionLength = 3;
constexpr size_t kExecutionModeIdIndex = 1;
constexpr size_t kExecutionModeExecutionModeIndex = 2;
std::array<uint32_t, kExecutionModeInstructionLength> newExecutionModeDeclaration = {};
// Fill the fields.
SetSpirvInstructionOp(newExecutionModeDeclaration.data(), spv::OpExecutionMode);
SetSpirvInstructionLength(newExecutionModeDeclaration.data(), kExecutionModeInstructionLength);
newExecutionModeDeclaration[kExecutionModeIdIndex] = instruction[kEntryPointIdIndex];
newExecutionModeDeclaration[kExecutionModeExecutionModeIndex] = spv::ExecutionModeXfb;
copyInstruction(newExecutionModeDeclaration.data(), kExecutionModeInstructionLength);
return true;
}
bool SpirvTransformer::transformTypePointer(const uint32_t *instruction, size_t wordCount)
{
// SPIR-V 1.0 Section 3.32 Instructions, OpTypePointer
constexpr size_t kIdIndex = 1;
constexpr size_t kStorageClassIndex = 2;
constexpr size_t kTypeIdIndex = 3;
const uint32_t id = instruction[kIdIndex];
const uint32_t storageClass = instruction[kStorageClassIndex];
const uint32_t typeId = instruction[kTypeIdIndex];
// 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, copy
// 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 (mNamesById[typeId] != nullptr && gl::IsBuiltInName(mNamesById[typeId]))
{
return false;
}
// Precision fixup needs this typeID
mTypePointerTransformedId[id].typeID = typeId;
if (storageClass != spv::StorageClassOutput && storageClass != spv::StorageClassInput)
{
return false;
}
// Insert OpTypePointer definition for new PrivateType.
const size_t instructionOffset = copyInstruction(instruction, wordCount);
const uint32_t newPrivateTypeId = getNewId();
(*mSpirvBlobOut)[instructionOffset + kIdIndex] = newPrivateTypeId;
(*mSpirvBlobOut)[instructionOffset + kStorageClassIndex] = spv::StorageClassPrivate;
// Remember the id of the replacement.
ASSERT(id < mTypePointerTransformedId.size());
mTypePointerTransformedId[id].privateID = newPrivateTypeId;
// The original instruction should still be present as well. At this point, we don't know
// whether we will need the Output or Private type.
return false;
}
bool SpirvTransformer::transformTypeStruct(const uint32_t *instruction, size_t wordCount)
{
// SPIR-V 1.0 Section 3.32 Instructions, OpTypeStruct
constexpr size_t kIdIndex = 1;
constexpr size_t kTypeStructInstructionBaseLength = 2;
const uint32_t id = instruction[kIdIndex];
if (id != mOutputPerVertex.typeId && id != mInputPerVertex.typeId)
{
return false;
}
const uint32_t maxMembers = id == mOutputPerVertex.typeId ? mOutputPerVertex.maxActiveMember
: mInputPerVertex.maxActiveMember;
// Change the definition of the gl_PerVertex struct by stripping unused fields at the end.
const uint32_t memberCount = maxMembers + 1;
const size_t newLength = kTypeStructInstructionBaseLength + memberCount;
ASSERT(wordCount >= newLength);
const size_t instructionOffset = copyInstruction(instruction, newLength);
SetSpirvInstructionLength(&(*mSpirvBlobOut)[instructionOffset], newLength);
return true;
}
bool SpirvTransformer::transformReturn(const uint32_t *instruction, size_t wordCount)
{
if (mOpFunctionId != mEntryPointId)
{
// We only need to process the precision info when returning from the entry point function
return false;
}
// For geometry shaders, this operations is done before every EmitVertex() instead.
// Additionally, this transformation (which affects output varyings) doesn't apply to fragment
// shaders.
if (mOptions.shaderType == gl::ShaderType::Geometry ||
mOptions.shaderType == gl::ShaderType::Fragment)
{
return false;
}
writeOutputPrologue();
return false;
}
bool SpirvTransformer::transformVariable(const uint32_t *instruction, size_t wordCount)
{
// SPIR-V 1.0 Section 3.32 Instructions, OpVariable
constexpr size_t kTypeIdIndex = 1;
constexpr size_t kIdIndex = 2;
constexpr size_t kStorageClassIndex = 3;
const uint32_t id = instruction[kIdIndex];
const uint32_t typeId = instruction[kTypeIdIndex];
const uint32_t storageClass = instruction[kStorageClassIndex];
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 false;
}
// 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 (info->useRelaxedPrecision &&
(storageClass == spv::StorageClassOutput || storageClass == spv::StorageClassInput))
{
// Change existing OpVariable to use fixedVaryingId
const size_t instructionOffset = copyInstruction(instruction, wordCount);
ASSERT(mFixedVaryingId[id] != 0);
(*mSpirvBlobOut)[instructionOffset + kIdIndex] = mFixedVaryingId[id];
// Make original variable a private global
ASSERT(mTypePointerTransformedId[typeId].privateID != 0);
writeVariable(id, mTypePointerTransformedId[typeId].privateID,
spv::StorageClassPrivate);
return true;
}
return false;
}
if (mOptions.shaderType == gl::ShaderType::Vertex && storageClass == spv::StorageClassUniform)
{
// Exceptionally, the ANGLEXfbN variables are unconditionally generated and may be inactive.
// Remove these variables in that case.
ASSERT(info == &mVariableInfoMap.get(mOptions.shaderType, GetXfbBufferName(0)) ||
info == &mVariableInfoMap.get(mOptions.shaderType, GetXfbBufferName(1)) ||
info == &mVariableInfoMap.get(mOptions.shaderType, GetXfbBufferName(2)) ||
info == &mVariableInfoMap.get(mOptions.shaderType, GetXfbBufferName(3)));
// Drop the declaration.
return true;
}
// Copy the declaration even though the variable is inactive for the separately compiled shader.
ASSERT(storageClass == spv::StorageClassOutput || storageClass == spv::StorageClassInput);
// Copy the variable declaration for modification. Change its type to the corresponding type
// with the Private storage class, as well as changing the storage class respecified in this
// instruction.
const size_t instructionOffset = copyInstruction(instruction, wordCount);
ASSERT(typeId < mTypePointerTransformedId.size());
ASSERT(mTypePointerTransformedId[typeId].privateID != 0);
(*mSpirvBlobOut)[instructionOffset + kTypeIdIndex] =
mTypePointerTransformedId[typeId].privateID;
(*mSpirvBlobOut)[instructionOffset + kStorageClassIndex] = spv::StorageClassPrivate;
return true;
}
bool SpirvTransformer::transformAccessChain(const uint32_t *instruction, size_t wordCount)
{
// SPIR-V 1.0 Section 3.32 Instructions, OpAccessChain, OpInBoundsAccessChain, OpPtrAccessChain,
// OpInBoundsPtrAccessChain
constexpr size_t kTypeIdIndex = 1;
constexpr size_t kBaseIdIndex = 3;
const uint32_t typeId = instruction[kTypeIdIndex];
const uint32_t baseId = instruction[kBaseIdIndex];
// If not accessing an inactive output varying, nothing to do.
const ShaderInterfaceVariableInfo *info = mVariableInfoById[baseId];
if (info == nullptr)
{
return false;
}
if (info->activeStages[mOptions.shaderType] && !info->useRelaxedPrecision)
{
return false;
}
// Copy the instruction for modification.
const size_t instructionOffset = copyInstruction(instruction, wordCount);
ASSERT(typeId < mTypePointerTransformedId.size());
ASSERT(mTypePointerTransformedId[typeId].privateID != 0);
(*mSpirvBlobOut)[instructionOffset + kTypeIdIndex] =
mTypePointerTransformedId[typeId].privateID;
return true;
}
bool SpirvTransformer::transformExecutionMode(const uint32_t *instruction, size_t wordCount)
{
// SPIR-V 1.0 Section 3.32 Instructions, OpExecutionMode
constexpr size_t kModeIndex = 2;
const uint32_t executionMode = instruction[kModeIndex];
if (executionMode == spv::ExecutionModeEarlyFragmentTests &&
mOptions.removeEarlyFragmentTestsOptimization)
{
// skip the copy
return true;
}
return false;
}
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.
uint32_t attribute = 0;
// SPIR-V ids of aliasing attributes.
std::vector<uint32_t> 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 std::vector<uint32_t> &spirvBlobIn,
const ShaderInterfaceVariableInfoMap &variableInfoMap,
std::vector<const ShaderInterfaceVariableInfo *> &&variableInfoById,
SpirvBlob *spirvBlobOut)
: SpirvTransformerBase(spirvBlobIn, variableInfoMap, spirvBlobOut)
{
mVariableInfoById = std::move(variableInfoById);
}
bool transform();
private:
// Preprocess aliasing attributes in preparation for their removal.
void preprocessAliasingAttributes();
// Transform instructions:
void transformInstruction();
// Helpers:
uint32_t getAliasingAttributeReplacementId(uint32_t aliasingId, uint32_t row) const;
bool isMatrixAttribute(uint32_t id) const;
// Instructions that are purely informational:
void visitTypeFloat(const uint32_t *instruction);
void visitTypeVector(const uint32_t *instruction);
void visitTypeMatrix(const uint32_t *instruction);
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.
bool transformEntryPoint(const uint32_t *instruction, size_t wordCount);
bool transformName(const uint32_t *instruction, size_t wordCount);
bool transformDecorate(const uint32_t *instruction, size_t wordCount);
bool transformVariable(const uint32_t *instruction, size_t wordCount);
bool transformAccessChain(const uint32_t *instruction, size_t wordCount);
void transformLoadHelper(const uint32_t *instruction,
size_t wordCount,
uint32_t pointerId,
uint32_t typeId,
uint32_t replacementId,
uint32_t resultId);
bool transformLoad(const uint32_t *instruction, size_t wordCount);
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<uint32_t> mExpandedMatrixFirstVectorIdById;
// Whether the expanded matrix OpVariables are generated.
bool mHaveMatricesExpanded = false;
// Whether initialization of the matrix attributes should be written at the beginning of the
// current function.
bool mWriteExpandedMatrixInitialization = false;
uint32_t mEntryPointId = 0;
// Id of attribute types; float and veci. This array is one-based, and [0] is unused.
//
// [1]: id of OpTypeFloat 32
// [N]: id of OpTypeVector %[1] N, N = {2, 3, 4}
//
// In other words, index of the array corresponds to the number of components in the type.
std::array<uint32_t, 5> mFloatTypes = {};
// Corresponding to mFloatTypes, [i]: id of OpMatrix %mFloatTypes[i] i. Note that only square
// matrices are possible as attributes in GLSL ES 1.00. [0] and [1] are unused.
std::array<uint32_t, 5> mMatrixTypes = {};
// Corresponding to mFloatTypes, [i]: id of OpTypePointer Input %mFloatTypes[i]. [0] is unused.
std::array<uint32_t, 5> mInputTypePointers = {};
// Corresponding to mFloatTypes, [i]: id of OpTypePointer Private %mFloatTypes[i]. [0] is
// unused.
std::array<uint32_t, 5> mPrivateFloatTypePointers = {};
// Corresponding to mMatrixTypes, [i]: id of OpTypePointer Private %mMatrixTypes[i]. [0] and
// [1] are unused.
std::array<uint32_t, 5> mPrivateMatrixTypePointers = {};
};
bool SpirvVertexAttributeAliasingTransformer::transform()
{
onTransformBegin();
preprocessAliasingAttributes();
while (mCurrentWord < mSpirvBlobIn.size())
{
transformInstruction();
}
return true;
}
void SpirvVertexAttributeAliasingTransformer::preprocessAliasingAttributes()
{
const size_t indexBound = mSpirvBlobIn[kHeaderIndexIndexBound];
mVariableInfoById.resize(indexBound, nullptr);
mIsAliasingAttributeById.resize(indexBound, false);
mExpandedMatrixFirstVectorIdById.resize(indexBound, 0);
// Go through attributes and find out which alias which.
for (size_t id = 0; id < indexBound; ++id)
{
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());
uint32_t attributeId = id;
// If this is a matrix attribute, expand it to vectors.
if (isMatrixAttribute)
{
uint32_t 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 == 0)
{
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);
uint32_t aliasingId;
if (info->attributeComponentCount > curMainAttribute->attributeComponentCount)
{
aliasingId = aliasingMap->attribute;
aliasingMap->attribute = attributeId;
}
else
{
aliasingId = attributeId;
}
aliasingMap->aliasingAttributes.push_back(aliasingId);
ASSERT(mIsAliasingAttributeById[aliasingId] == false);
mIsAliasingAttributeById[aliasingId] = true;
}
}
}
void SpirvVertexAttributeAliasingTransformer::transformInstruction()
{
uint32_t wordCount;
uint32_t opCode;
const uint32_t *instruction = getCurrentInstruction(&opCode, &wordCount);
if (opCode == spv::OpFunction)
{
// Declare the expanded matrix variables right before the first function declaration.
if (!mHaveMatricesExpanded)
{
declareExpandedMatrixVectors();
mHaveMatricesExpanded = true;
}
// SPIR-V is structured in sections. Function declarations come last.
mIsInFunctionSection = true;
// 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.
// SPIR-V 1.0 Section 3.32 Instructions, OpFunction
constexpr size_t kFunctionIdIndex = 2;
const uint32_t functionId = instruction[kFunctionIdIndex];
mWriteExpandedMatrixInitialization = functionId == mEntryPointId;
}
// Only look at interesting instructions.
bool transformed = false;
if (mIsInFunctionSection)
{
// Write expanded matrix initialization right after the entry point's OpFunction and any
// instruction that must come immediately after it.
if (mWriteExpandedMatrixInitialization && opCode != spv::OpFunction &&
opCode != spv::OpFunctionParameter && opCode != spv::OpLabel &&
opCode != spv::OpVariable)
{
writeExpandedMatrixInitialization();
mWriteExpandedMatrixInitialization = false;
}
// Look at in-function opcodes.
switch (opCode)
{
case spv::OpAccessChain:
case spv::OpInBoundsAccessChain:
transformed = transformAccessChain(instruction, wordCount);
break;
case spv::OpLoad:
transformed = transformLoad(instruction, wordCount);
break;
default:
break;
}
}
else
{
// Look at global declaration opcodes.
switch (opCode)
{
// Informational instructions:
case spv::OpTypeFloat:
visitTypeFloat(instruction);
break;
case spv::OpTypeVector:
visitTypeVector(instruction);
break;
case spv::OpTypeMatrix:
visitTypeMatrix(instruction);
break;
case spv::OpTypePointer:
visitTypePointer(instruction);
break;
// Instructions that may need transformation:
case spv::OpEntryPoint:
transformed = transformEntryPoint(instruction, wordCount);
break;
case spv::OpName:
transformed = transformName(instruction, wordCount);
break;
case spv::OpDecorate:
transformed = transformDecorate(instruction, wordCount);
break;
case spv::OpVariable:
transformed = transformVariable(instruction, wordCount);
break;
default:
break;
}
}
// If the instruction was not transformed, copy it to output as is.
if (!transformed)
{
copyInstruction(instruction, wordCount);
}
// Advance to next instruction.
mCurrentWord += wordCount;
}
uint32_t SpirvVertexAttributeAliasingTransformer::getAliasingAttributeReplacementId(
uint32_t 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 uint32_t replacementId = aliasingMap->attribute;
ASSERT(replacementId != 0 && replacementId < mIsAliasingAttributeById.size());
ASSERT(!mIsAliasingAttributeById[replacementId]);
return replacementId;
}
bool SpirvVertexAttributeAliasingTransformer::isMatrixAttribute(uint32_t id) const
{
return mExpandedMatrixFirstVectorIdById[id] != 0;
}
void SpirvVertexAttributeAliasingTransformer::visitTypeFloat(const uint32_t *instruction)
{
// SPIR-V 1.0 Section 3.32 Instructions, OpTypeFloat
constexpr size_t kIdIndex = 1;
constexpr size_t kWidthIndex = 2;
const uint32_t id = instruction[kIdIndex];
const uint32_t width = instruction[kWidthIndex];
// Only interested in OpTypeFloat 32.
if (width == 32)
{
ASSERT(mFloatTypes[1] == 0);
mFloatTypes[1] = id;
}
}
void SpirvVertexAttributeAliasingTransformer::visitTypeVector(const uint32_t *instruction)
{
// SPIR-V 1.0 Section 3.32 Instructions, OpTypeVector
constexpr size_t kIdIndex = 1;
constexpr size_t kComponentIdIndex = 2;
constexpr size_t kComponentCountIndex = 3;
const uint32_t id = instruction[kIdIndex];
const uint32_t componentId = instruction[kComponentIdIndex];
const uint32_t componentCount = instruction[kComponentCountIndex];
// Only interested in OpTypeVector %f32 N, where %f32 is the id of OpTypeFloat 32.
if (componentId == mFloatTypes[1])
{
ASSERT(componentCount >= 2 && componentCount <= 4);
ASSERT(mFloatTypes[componentCount] == 0);
mFloatTypes[componentCount] = id;
}
}
void SpirvVertexAttributeAliasingTransformer::visitTypeMatrix(const uint32_t *instruction)
{
// SPIR-V 1.0 Section 3.32 Instructions, OpTypeMatrix
constexpr size_t kIdIndex = 1;
constexpr size_t kColumnTypeIndex = 2;
constexpr size_t kColumnCountIndex = 3;
const uint32_t id = instruction[kIdIndex];
const uint32_t columnType = instruction[kColumnTypeIndex];
const uint32_t columnCount = instruction[kColumnCountIndex];
// Only interested in OpTypeMatrix %vecN, where %vecN is the id of OpTypeVector %f32 N.
// This is only for square matN types (as allowed by GLSL ES 1.00), so columnCount is the same
// as rowCount.
if (columnType == mFloatTypes[columnCount])
{
ASSERT(mMatrixTypes[columnCount] == 0);
mMatrixTypes[columnCount] = id;
}
}
void SpirvVertexAttributeAliasingTransformer::visitTypePointer(const uint32_t *instruction)
{
// SPIR-V 1.0 Section 3.32 Instructions, OpTypePointer
constexpr size_t kIdIndex = 1;
constexpr size_t kStorageClassIndex = 2;
constexpr size_t kTypeIdIndex = 3;
const uint32_t id = instruction[kIdIndex];
const uint32_t storageClass = instruction[kStorageClassIndex];
const uint32_t typeId = instruction[kTypeIdIndex];
// 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 (size_t n = 2; n < mFloatTypes.size(); ++n)
{
if (typeId == mFloatTypes[n])
{
ASSERT(mInputTypePointers[n] == 0);
mInputTypePointers[n] = id;
break;
}
}
}
else if (storageClass == spv::StorageClassPrivate)
{
ASSERT(mFloatTypes.size() == mMatrixTypes.size());
for (size_t n = 2; n < mMatrixTypes.size(); ++n)
{
// Note that Private types may not be unique, as the previous transformation can
// generate duplicates.
if (typeId == mFloatTypes[n])
{
mPrivateFloatTypePointers[n] = id;
break;
}
if (typeId == mMatrixTypes[n])
{
mPrivateMatrixTypePointers[n] = id;
break;
}
}
}
}
bool SpirvVertexAttributeAliasingTransformer::transformEntryPoint(const uint32_t *instruction,
size_t wordCount)
{
// Remove aliasing attributes from the shader interface declaration.
// SPIR-V 1.0 Section 3.32 Instructions, OpEntryPoint
constexpr size_t kEntryPointIdIndex = 2;
constexpr size_t kNameIndex = 3;
// See comment in SpirvTransformer::transformEntryPoint.
const size_t nameLength =
strlen(reinterpret_cast<const char *>(&instruction[kNameIndex])) / 4 + 1;
const uint32_t instructionLength = GetSpirvInstructionLength(instruction);
const size_t interfaceStart = kNameIndex + nameLength;
// Should only have one EntryPoint
ASSERT(mEntryPointId == 0);
mEntryPointId = instruction[kEntryPointIdIndex];
// Create a copy of the entry point for modification.
std::vector<uint32_t> filteredEntryPoint(instruction, instruction + wordCount);
// As a first pass, filter out matrix attributes and append their replacement vectors.
for (size_t index = interfaceStart; index < instructionLength; ++index)
{
uint32_t matrixId = instruction[index];
if (mExpandedMatrixFirstVectorIdById[matrixId] == 0)
{
continue;
}
const ShaderInterfaceVariableInfo *info = mVariableInfoById[matrixId];
ValidateShaderInterfaceVariableIsAttribute(info);
// Replace the matrix id with its first vector id.
uint32_t vec0Id = mExpandedMatrixFirstVectorIdById[matrixId];
filteredEntryPoint[index] = vec0Id;
// Append the rest of the vectors to the entry point.
for (uint32_t offset = 1; offset < info->attributeLocationCount; ++offset)
{
uint32_t vecId = vec0Id + offset;
filteredEntryPoint.push_back(vecId);
}
}
// Filter out aliasing attributes from entry point interface declaration.
size_t writeIndex = interfaceStart;
for (size_t index = interfaceStart; index < filteredEntryPoint.size(); ++index)
{
uint32_t id = filteredEntryPoint[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;
}
filteredEntryPoint[writeIndex] = id;
++writeIndex;
}
// Update the length of the instruction.
const size_t newLength = writeIndex;
SetSpirvInstructionLength(filteredEntryPoint.data(), newLength);
// Copy to output.
copyInstruction(filteredEntryPoint.data(), newLength);
return true;
}
bool SpirvVertexAttributeAliasingTransformer::transformName(const uint32_t *instruction,
size_t wordCount)
{
// SPIR-V 1.0 Section 3.32 Instructions, OpName
constexpr size_t kIdIndex = 1;
uint32_t id = instruction[kIdIndex];
// If id is not that of an aliasing attribute, there's nothing to do.
ASSERT(id < mIsAliasingAttributeById.size());
if (!mIsAliasingAttributeById[id])
{
return false;
}
// Drop debug annotations for this id.
return true;
}
bool SpirvVertexAttributeAliasingTransformer::transformDecorate(const uint32_t *instruction,
size_t wordCount)
{
// SPIR-V 1.0 Section 3.32 Instructions, OpDecorate
constexpr size_t kIdIndex = 1;
constexpr size_t kDecorationIndex = 2;
constexpr size_t kDecorationValueIndex = 3;
uint32_t id = instruction[kIdIndex];
uint32_t decoration = instruction[kDecorationIndex];
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 false;
}
const ShaderInterfaceVariableInfo *info = mVariableInfoById[id];
ValidateShaderInterfaceVariableIsAttribute(info);
uint32_t vec0Id = mExpandedMatrixFirstVectorIdById[id];
ASSERT(vec0Id != 0);
for (uint32_t offset = 0; offset < info->attributeLocationCount; ++offset)
{
uint32_t vecId = vec0Id + offset;
if (mIsAliasingAttributeById[vecId])
{
continue;
}
const size_t instOffset = copyInstruction(instruction, wordCount);
(*mSpirvBlobOut)[instOffset + kIdIndex] = vecId;
(*mSpirvBlobOut)[instOffset + kDecorationValueIndex] = 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 false;
}
// 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 true;
}
// If id is not that of an aliasing attribute, there's nothing else to do.
ASSERT(id < mIsAliasingAttributeById.size());
if (!mIsAliasingAttributeById[id])
{
return false;
}
}
// Drop every decoration for this id.
return true;
}
bool SpirvVertexAttributeAliasingTransformer::transformVariable(const uint32_t *instruction,
size_t wordCount)
{
// SPIR-V 1.0 Section 3.32 Instructions, OpVariable
constexpr size_t kIdIndex = 2;
constexpr size_t kStorageClassIndex = 3;
const uint32_t id = instruction[kIdIndex];
const uint32_t storageClass = instruction[kStorageClassIndex];
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 false;
}
}
ASSERT(storageClass == spv::StorageClassInput);
// Drop the declaration.
return true;
}
bool SpirvVertexAttributeAliasingTransformer::transformAccessChain(const uint32_t *instruction,
size_t wordCount)
{
// SPIR-V 1.0 Section 3.32 Instructions, OpAccessChain, OpInBoundsAccessChain
constexpr size_t kTypeIdIndex = 1;
constexpr size_t kBaseIdIndex = 3;
const uint32_t typeId = instruction[kTypeIdIndex];
const uint32_t baseId = instruction[kBaseIdIndex];
if (isMatrixAttribute(baseId))
{
// Copy the OpAccessChain instruction for modification. 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.
const size_t instOffset = copyInstruction(instruction, wordCount);
// SPIR-V 1.0 Section 3.32 Instructions, OpAccessChain, OpInBoundsAccessChain
constexpr size_t kAccessChainBaseLength = 4;
if (wordCount == kAccessChainBaseLength + 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.
(*mSpirvBlobOut)[instOffset + kTypeIdIndex] = mPrivateFloatTypePointers[componentCount];
}
else
{
// If indexed twice, it uses the float type.
ASSERT(wordCount == kAccessChainBaseLength + 2);
// Replace the type with the Private pointer to float32.
(*mSpirvBlobOut)[instOffset + kTypeIdIndex] = mPrivateFloatTypePointers[1];
}
}
else
{
// If base id is not that of an aliasing attribute, there's nothing to do.
ASSERT(baseId < mIsAliasingAttributeById.size());
if (!mIsAliasingAttributeById[baseId])
{
return false;
}
// Find the replacement attribute for the aliasing one.
const uint32_t replacementId = getAliasingAttributeReplacementId(baseId, 0);
// Get variable info corresponding to the replacement attribute.
const ShaderInterfaceVariableInfo *replacementInfo = mVariableInfoById[replacementId];
ValidateShaderInterfaceVariableIsAttribute(replacementInfo);
// Copy the OpAccessChain instruction for modification. 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.
const size_t instOffset = copyInstruction(instruction, wordCount);
(*mSpirvBlobOut)[instOffset + kBaseIdIndex] = replacementId;
}
return true;
}
void SpirvVertexAttributeAliasingTransformer::transformLoadHelper(const uint32_t *instruction,
size_t wordCount,
uint32_t pointerId,
uint32_t typeId,
uint32_t replacementId,
uint32_t resultId)
{
// SPIR-V 1.0 Section 3.32 Instructions, OpLoad
constexpr size_t kTypeIdIndex = 1;
constexpr size_t kIdIndex = 2;
constexpr size_t kPointerIdIndex = 3;
// Get variable info corresponding to the replacement attribute.
const ShaderInterfaceVariableInfo *replacementInfo = mVariableInfoById[replacementId];
ValidateShaderInterfaceVariableIsAttribute(replacementInfo);
// Copy the load instruction for modification. Currently, the instruction is:
//
// %id = OpLoad %type %pointer
//
// This is modified to:
//
// %newId = OpLoad %replacementType %replacement
//
const uint32_t loadResultId = getNewId();
const uint32_t replacementTypeId = mFloatTypes[replacementInfo->attributeComponentCount];
ASSERT(replacementTypeId != 0);
const size_t instOffset = copyInstruction(instruction, wordCount);
(*mSpirvBlobOut)[instOffset + kIdIndex] = loadResultId;
(*mSpirvBlobOut)[instOffset + kTypeIdIndex] = replacementTypeId;
(*mSpirvBlobOut)[instOffset + kPointerIdIndex] = replacementId;
// If swizzle is not necessary, assign %newId to %resultId.
const ShaderInterfaceVariableInfo *aliasingInfo = mVariableInfoById[pointerId];
if (aliasingInfo->attributeComponentCount == replacementInfo->attributeComponentCount)
{
writeCopyObject(resultId, typeId, 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(mFloatTypes[aliasingInfo->attributeComponentCount] == typeId);
if (aliasingInfo->attributeComponentCount == 1)
{
writeCompositeExtract(resultId, typeId, loadResultId, 0);
}
else
{
angle::FixedVector<uint32_t, 4> swizzle = {0, 1, 2, 3};
swizzle.resize(aliasingInfo->attributeComponentCount);
writeVectorShuffle(resultId, typeId, loadResultId, loadResultId, swizzle);
}
}
bool SpirvVertexAttributeAliasingTransformer::transformLoad(const uint32_t *instruction,
size_t wordCount)
{
// SPIR-V 1.0 Section 3.32 Instructions, OpLoad
constexpr size_t kTypeIdIndex = 1;
constexpr size_t kIdIndex = 2;
constexpr size_t kPointerIdIndex = 3;
const uint32_t id = instruction[kIdIndex];
const uint32_t typeId = instruction[kTypeIdIndex];
const uint32_t pointerId = instruction[kPointerIdIndex];
// 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 uint32_t replacementTypeId = mMatrixTypes[info->attributeLocationCount];
const size_t instOffset = copyInstruction(instruction, wordCount);
(*mSpirvBlobOut)[instOffset + kTypeIdIndex] = replacementTypeId;
}
else
{
// If pointer id is not that of an aliasing attribute, there's nothing to do.
ASSERT(pointerId < mIsAliasingAttributeById.size());
if (!mIsAliasingAttributeById[pointerId])
{
return false;
}
// Find the replacement attribute for the aliasing one.
const uint32_t replacementId = getAliasingAttributeReplacementId(pointerId, 0);
// Replace the load instruction by a load from the replacement id.
transformLoadHelper(instruction, wordCount, pointerId, typeId, replacementId, id);
}
return true;
}
void SpirvVertexAttributeAliasingTransformer::declareExpandedMatrixVectors()
{
// Go through matrix attributes and expand them.
for (size_t matrixId = 0; matrixId < mExpandedMatrixFirstVectorIdById.size(); ++matrixId)
{
uint32_t vec0Id = mExpandedMatrixFirstVectorIdById[matrixId];
if (vec0Id == 0)
{
continue;
}
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);
ASSERT(mMatrixTypes[locationCount] != 0);
// OpTypePointer Private %matrixType
uint32_t privateType = mPrivateMatrixTypePointers[locationCount];
if (privateType == 0)
{
privateType = getNewId();
mPrivateMatrixTypePointers[locationCount] = privateType;
writeTypePointer(privateType, spv::StorageClassPrivate, mMatrixTypes[locationCount]);
}
// OpVariable %privateType Private
writeVariable(matrixId, privateType, spv::StorageClassPrivate);
// If the OpTypePointer is not declared for the vector type corresponding to each location,
// declare it now.
//
// %vecType = OpTypePointer %vecType Input
uint32_t inputType = mInputTypePointers[componentCount];
if (inputType == 0)
{
inputType = getNewId();
mInputTypePointers[componentCount] = inputType;
writeTypePointer(inputType, spv::StorageClassInput, mFloatTypes[componentCount]);
}
// Declare a vector for each column of the matrix.
for (uint32_t offset = 0; offset < info->attributeLocationCount; ++offset)
{
uint32_t vecId = vec0Id + offset;
if (!mIsAliasingAttributeById[vecId])
{
writeVariable(vecId, inputType, spv::StorageClassInput);
}
}
}
// Additionally, declare OpTypePointer Private %mFloatTypes[i] in case needed (used in
// Op*AccessChain instructions, if any).
for (size_t n = 1; n < mFloatTypes.size(); ++n)
{
if (mFloatTypes[n] != 0 && mPrivateFloatTypePointers[n] == 0)
{
const uint32_t privateType = getNewId();
mPrivateFloatTypePointers[n] = privateType;
writeTypePointer(privateType, spv::StorageClassPrivate, mFloatTypes[n]);
}
}
}
void SpirvVertexAttributeAliasingTransformer::writeExpandedMatrixInitialization()
{
// SPIR-V 1.0 Section 3.32 Instructions, OpLoad
constexpr size_t kLoadInstructionLength = 4;
// A fake OpLoad instruction for transformLoadHelper to copy off of.
std::array<uint32_t, kLoadInstructionLength> loadInstruction = {};
SetSpirvInstructionOp(loadInstruction.data(), spv::OpLoad);
SetSpirvInstructionLength(loadInstruction.data(), kLoadInstructionLength);
// 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 (size_t matrixId = 0; matrixId < mExpandedMatrixFirstVectorIdById.size(); ++matrixId)
{
uint32_t vec0Id = mExpandedMatrixFirstVectorIdById[matrixId];
if (vec0Id == 0)
{
continue;
}
// 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);
angle::FixedVector<uint32_t, 4> vecLoadIds;
const uint32_t locationCount = info->attributeLocationCount;
for (uint32_t offset = 0; offset < locationCount; ++offset)
{
uint32_t vecId = vec0Id + offset;
// Load into temporary, potentially through an aliasing vector.
uint32_t 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(loadInstruction.data(), kLoadInstructionLength, matrixId,
mFloatTypes[info->attributeComponentCount], replacementId,
vecLoadIds.back());
}
// Aggregate the vector loads into a matrix.
ASSERT(mMatrixTypes[locationCount] != 0);
const uint32_t compositeId = getNewId();
writeCompositeConstruct(compositeId, mMatrixTypes[locationCount], vecLoadIds);
// Store it in the private variable.
writeStore(matrixId, compositeId);
}
}
bool HasAliasingAttributes(const ShaderInterfaceVariableInfoMap &variableInfoMap)
{
gl::AttributesMask isLocationAssigned;
for (const auto &infoIter : variableInfoMap.getIterator(gl::ShaderType::Vertex))
{
const ShaderInterfaceVariableInfo &info = infoIter.second;
// Ignore non attribute ids.
if (info.attributeComponentCount == 0)
{
continue;
}
ASSERT(info.activeStages[gl::ShaderType::Vertex]);
ASSERT(info.location != ShaderInterfaceVariableInfo::kInvalid);
ASSERT(info.attributeLocationCount > 0);
for (uint8_t offset = 0; offset < info.attributeLocationCount; ++offset)
{
uint32_t location = info.location + offset;
// If there's aliasing, return immediately.
if (isLocationAssigned.test(location))
{
return true;
}
isLocationAssigned.set(location);
}
}
return false;
}
} // anonymous namespace
// ShaderInterfaceVariableInfo implementation.
const uint32_t ShaderInterfaceVariableInfo::kInvalid;
ShaderInterfaceVariableInfo::ShaderInterfaceVariableInfo() {}
// ShaderInterfaceVariableInfoMap implementation.
ShaderInterfaceVariableInfoMap::ShaderInterfaceVariableInfoMap() = default;
ShaderInterfaceVariableInfoMap::~ShaderInterfaceVariableInfoMap() = default;
void ShaderInterfaceVariableInfoMap::clear()
{
for (VariableNameToInfoMap &shaderMap : mData)
{
shaderMap.clear();
}
}
bool ShaderInterfaceVariableInfoMap::contains(gl::ShaderType shaderType,
const std::string &variableName) const
{
return mData[shaderType].find(variableName) != mData[shaderType].end();
}
const ShaderInterfaceVariableInfo &ShaderInterfaceVariableInfoMap::get(
gl::ShaderType shaderType,
const std::string &variableName) const
{
auto it = mData[shaderType].find(variableName);
ASSERT(it != mData[shaderType].end());
return it->second;
}
ShaderInterfaceVariableInfo &ShaderInterfaceVariableInfoMap::get(gl::ShaderType shaderType,
const std::string &variableName)
{
auto it = mData[shaderType].find(variableName);
ASSERT(it != mData[shaderType].end());
return it->second;
}
ShaderInterfaceVariableInfo &ShaderInterfaceVariableInfoMap::add(gl::ShaderType shaderType,
const std::string &variableName)
{
ASSERT(!contains(shaderType, variableName));
return mData[shaderType][variableName];
}
ShaderInterfaceVariableInfo &ShaderInterfaceVariableInfoMap::addOrGet(
gl::ShaderType shaderType,
const std::string &variableName)
{
return mData[shaderType][variableName];
}
ShaderInterfaceVariableInfoMap::Iterator ShaderInterfaceVariableInfoMap::getIterator(
gl::ShaderType shaderType) const
{
return Iterator(mData[shaderType].begin(), mData[shaderType].end());
}
void GlslangInitialize()
{
int result = ShInitialize();
ASSERT(result != 0);
GlslangWarmup();
}
void GlslangRelease()
{
int result = ShFinalize();
ASSERT(result != 0);
}
// Strip indices from the name. If there are non-zero indices, return false to indicate that this
// image uniform doesn't require set/binding. That is done on index 0.
bool GetImageNameWithoutIndices(std::string *name)
{
if (name->back() != ']')
{
return true;
}
if (!UniformNameIsIndexZero(*name, false))
{
return false;
}
// Strip all indices
*name = name->substr(0, name->find('['));
return true;
}
std::string GetMappedSamplerNameOld(const std::string &originalName)
{
std::string samplerName = gl::ParseResourceName(originalName, nullptr);
// Samplers in structs are extracted.
std::replace(samplerName.begin(), samplerName.end(), '.', '_');
// Samplers in arrays of structs are also extracted.
std::replace(samplerName.begin(), samplerName.end(), '[', '_');
samplerName.erase(std::remove(samplerName.begin(), samplerName.end(), ']'), samplerName.end());
if (MappedSamplerNameNeedsUserDefinedPrefix(originalName))
{
samplerName = sh::kUserDefinedNamePrefix + samplerName;
}
return samplerName;
}
std::string GlslangGetMappedSamplerName(const std::string &originalName)
{
std::string samplerName = originalName;
// Samplers in structs are extracted.
std::replace(samplerName.begin(), samplerName.end(), '.', '_');
// Remove array elements
auto out = samplerName.begin();
for (auto in = samplerName.begin(); in != samplerName.end(); in++)
{
if (*in == '[')
{
while (*in != ']')
{
in++;
ASSERT(in != samplerName.end());
}
}
else
{
*out++ = *in;
}
}
samplerName.erase(out, samplerName.end());
if (MappedSamplerNameNeedsUserDefinedPrefix(originalName))
{
samplerName = sh::kUserDefinedNamePrefix + samplerName;
}
return samplerName;
}
std::string GetXfbBufferName(const uint32_t bufferIndex)
{
return sh::vk::kXfbEmulationBufferBlockName + Str(bufferIndex);
}
void GlslangGenTransformFeedbackEmulationOutputs(const GlslangSourceOptions &options,
const gl::ProgramState &programState,
GlslangProgramInterfaceInfo *programInterfaceInfo,
std::string *vertexShader,
ShaderInterfaceVariableInfoMap *variableInfoMapOut)
{
GenerateTransformFeedbackEmulationOutputs(options, gl::ShaderType::Vertex, programState,
programInterfaceInfo, vertexShader,
variableInfoMapOut);
}
void GlslangAssignLocations(const GlslangSourceOptions &options,
const gl::ProgramState &programState,
const gl::ProgramVaryingPacking &varyingPacking,
const gl::ShaderType shaderType,
const gl::ShaderType frontShaderType,
bool isTransformFeedbackStage,
GlslangProgramInterfaceInfo *programInterfaceInfo,
ShaderInterfaceVariableInfoMap *variableInfoMapOut)
{
const gl::ProgramExecutable &programExecutable = programState.getExecutable();
// Assign outputs to the fragment shader, if any.
if ((shaderType == gl::ShaderType::Fragment) &&
programExecutable.hasLinkedShaderStage(gl::ShaderType::Fragment))
{
AssignOutputLocations(programExecutable, gl::ShaderType::Fragment, variableInfoMapOut);
}
// Assign attributes to the vertex shader, if any.
if ((shaderType == gl::ShaderType::Vertex) &&
programExecutable.hasLinkedShaderStage(gl::ShaderType::Vertex))
{
AssignAttributeLocations(programExecutable, gl::ShaderType::Vertex, variableInfoMapOut);
}
if (!programExecutable.hasLinkedShaderStage(gl::ShaderType::Compute))
{
const gl::VaryingPacking &inputPacking = varyingPacking.getInputPacking(shaderType);
const gl::VaryingPacking &outputPacking = varyingPacking.getOutputPacking(shaderType);
// Assign location to varyings generated for transform feedback capture
if (options.supportsTransformFeedbackExtension &&
gl::ShaderTypeSupportsTransformFeedback(shaderType))
{
AssignTransformFeedbackExtensionLocations(shaderType, programState,
isTransformFeedbackStage,
programInterfaceInfo, variableInfoMapOut);
}
// Assign varying locations.
if (shaderType != gl::ShaderType::Vertex)
{
AssignVaryingLocations(options, inputPacking, shaderType, frontShaderType,
programInterfaceInfo, variableInfoMapOut);
}
if (shaderType != gl::ShaderType::Fragment)
{
AssignVaryingLocations(options, outputPacking, shaderType, frontShaderType,
programInterfaceInfo, variableInfoMapOut);
}
// Assign qualifiers to all varyings captured by transform feedback
if (!programExecutable.getLinkedTransformFeedbackVaryings().empty() &&
options.supportsTransformFeedbackExtension &&
(shaderType == programExecutable.getLinkedTransformFeedbackStage()))
{
AssignTransformFeedbackExtensionQualifiers(programExecutable, outputPacking, shaderType,
variableInfoMapOut);
}
}
AssignUniformBindings(options, programExecutable, shaderType, programInterfaceInfo,
variableInfoMapOut);
AssignTextureBindings(options, programExecutable, shaderType, programInterfaceInfo,
variableInfoMapOut);
AssignNonTextureBindings(options, programExecutable, shaderType, programInterfaceInfo,
variableInfoMapOut);
if (options.emulateTransformFeedback && gl::ShaderTypeSupportsTransformFeedback(shaderType))
{
AssignTransformFeedbackEmulationBindings(shaderType, programState, isTransformFeedbackStage,
programInterfaceInfo, variableInfoMapOut);
}
}
void GlslangGetShaderSource(const GlslangSourceOptions &options,
const gl::ProgramState &programState,
const gl::ProgramLinkedResources &resources,
GlslangProgramInterfaceInfo *programInterfaceInfo,
gl::ShaderMap<std::string> *shaderSourcesOut,
ShaderInterfaceVariableInfoMap *variableInfoMapOut)
{
for (const gl::ShaderType shaderType : gl::AllShaderTypes())
{
gl::Shader *glShader = programState.getAttachedShader(shaderType);
(*shaderSourcesOut)[shaderType] = glShader ? glShader->getTranslatedSource() : "";
}
gl::ShaderType xfbStage = programState.getAttachedTransformFeedbackStage();
std::string *xfbSource = &(*shaderSourcesOut)[xfbStage];
// Write transform feedback output code.
if (!xfbSource->empty())
{
if (!programState.getLinkedTransformFeedbackVaryings().empty())
{
if (options.supportsTransformFeedbackExtension)
{
GenerateTransformFeedbackExtensionOutputs(programState, xfbSource);
}
else if (options.emulateTransformFeedback)
{
ASSERT(xfbStage == gl::ShaderType::Vertex);
GenerateTransformFeedbackEmulationOutputs(options, xfbStage, programState,
programInterfaceInfo, xfbSource,
variableInfoMapOut);
}
else
{
*xfbSource = SubstituteTransformFeedbackMarkers(*xfbSource, "");
}
}
else
{
*xfbSource = SubstituteTransformFeedbackMarkers(*xfbSource, "");
}
}
std::string *tessEvalSources = &(*shaderSourcesOut)[gl::ShaderType::TessEvaluation];
if (xfbStage > gl::ShaderType::TessEvaluation && !tessEvalSources->empty())
{
*tessEvalSources = SubstituteTransformFeedbackMarkers(*tessEvalSources, "");
}
std::string *vertexSource = &(*shaderSourcesOut)[gl::ShaderType::Vertex];
if (xfbStage > gl::ShaderType::Vertex && !vertexSource->empty())
{
*vertexSource = SubstituteTransformFeedbackMarkers(*vertexSource, "");
}
gl::ShaderType frontShaderType = gl::ShaderType::InvalidEnum;
for (const gl::ShaderType shaderType : programState.getExecutable().getLinkedShaderStages())
{
const bool isXfbStage =
shaderType == xfbStage && !programState.getLinkedTransformFeedbackVaryings().empty();
GlslangAssignLocations(options, programState, resources.varyingPacking, shaderType,
frontShaderType, isXfbStage, programInterfaceInfo,
variableInfoMapOut);
frontShaderType = shaderType;
}
}
angle::Result GlslangTransformSpirvCode(const GlslangErrorCallback &callback,
const GlslangSpirvOptions &options,
const ShaderInterfaceVariableInfoMap &variableInfoMap,
const SpirvBlob &initialSpirvBlob,
SpirvBlob *spirvBlobOut)
{
if (initialSpirvBlob.empty())
{
return angle::Result::Continue;
}
#if defined(ANGLE_DEBUG_SPIRV_TRANSFORMER) && ANGLE_DEBUG_SPIRV_TRANSFORMER
spvtools::SpirvTools spirvTools(SPV_ENV_VULKAN_1_1);
spirvTools.SetMessageConsumer(ValidateSpirvMessage);
std::string readableSpirv;
spirvTools.Disassemble(initialSpirvBlob, &readableSpirv, 0);
fprintf(stderr, "%s\n", readableSpirv.c_str());
#endif // defined(ANGLE_DEBUG_SPIRV_TRANSFORMER) && ANGLE_DEBUG_SPIRV_TRANSFORMER
// Transform the SPIR-V code by assigning location/set/binding values.
SpirvTransformer transformer(initialSpirvBlob, options, variableInfoMap, spirvBlobOut);
ANGLE_GLSLANG_CHECK(callback, transformer.transform(), GlslangError::InvalidSpirv);
// If there are aliasing vertex attributes, transform the SPIR-V again to remove them.
if (options.shaderType == gl::ShaderType::Vertex && HasAliasingAttributes(variableInfoMap))
{
SpirvBlob preTransformBlob = std::move(*spirvBlobOut);
SpirvVertexAttributeAliasingTransformer aliasingTransformer(
preTransformBlob, variableInfoMap, std::move(transformer.getVariableInfoByIdMap()),
spirvBlobOut);
ANGLE_GLSLANG_CHECK(callback, aliasingTransformer.transform(), GlslangError::InvalidSpirv);
}
ASSERT(ValidateSpirv(*spirvBlobOut));
return angle::Result::Continue;
}
angle::Result GlslangGetShaderSpirvCode(const GlslangErrorCallback &callback,
const gl::ShaderBitSet &linkedShaderStages,
const gl::Caps &glCaps,
const gl::ShaderMap<std::string> &shaderSources,
gl::ShaderMap<SpirvBlob> *spirvBlobsOut)
{
TBuiltInResource builtInResources(glslang::DefaultTBuiltInResource);
GetBuiltInResourcesFromCaps(glCaps, &builtInResources);
glslang::TShader vertexShader(EShLangVertex);
glslang::TShader fragmentShader(EShLangFragment);
glslang::TShader geometryShader(EShLangGeometry);
glslang::TShader computeShader(EShLangCompute);
gl::ShaderMap<glslang::TShader *> shaders = {
{gl::ShaderType::Vertex, &vertexShader},
{gl::ShaderType::Fragment, &fragmentShader},
{gl::ShaderType::Geometry, &geometryShader},
{gl::ShaderType::Compute, &computeShader},
};
glslang::TProgram program;
for (const gl::ShaderType shaderType : linkedShaderStages)
{
if (shaderSources[shaderType].empty())
{
continue;
}
ANGLE_TRY(CompileShader(callback, builtInResources, shaderType, shaderSources[shaderType],
shaders[shaderType], &program));
}
ANGLE_TRY(LinkProgram(callback, &program));
for (const gl::ShaderType shaderType : linkedShaderStages)
{
if (shaderSources[shaderType].empty())
{
continue;
}
glslang::TIntermediate *intermediate = program.getIntermediate(kShLanguageMap[shaderType]);
glslang::GlslangToSpv(*intermediate, (*spirvBlobsOut)[shaderType]);
}
return angle::Result::Continue;
}
angle::Result GlslangCompileShaderOneOff(const GlslangErrorCallback &callback,
gl::ShaderType shaderType,
const std::string &shaderSource,
SpirvBlob *spirvBlobOut)
{
const TBuiltInResource builtInResources(glslang::DefaultTBuiltInResource);
glslang::TShader shader(kShLanguageMap[shaderType]);
glslang::TProgram program;
ANGLE_TRY(
CompileShader(callback, builtInResources, shaderType, shaderSource, &shader, &program));
ANGLE_TRY(LinkProgram(callback, &program));
glslang::TIntermediate *intermediate = program.getIntermediate(kShLanguageMap[shaderType]);
glslang::GlslangToSpv(*intermediate, *spirvBlobOut);
return angle::Result::Continue;
}
} // namespace rx