blob: 240671ae08a8f7038a1e8f6cc2df2b54f2417ceb [file] [log] [blame]
//
// Copyright (c) 2016 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.
//
// GlslangWrapper: Wrapper for Vulkan's glslang compiler.
//
#include "libANGLE/renderer/vulkan/GlslangWrapper.h"
// glslang has issues with some specific warnings.
ANGLE_DISABLE_EXTRA_SEMI_WARNING
// 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_EXTRA_SEMI_WARNING
#include <array>
#include "common/FixedVector.h"
#include "common/string_utils.h"
#include "common/utilities.h"
#include "libANGLE/Caps.h"
#include "libANGLE/ProgramLinkedResources.h"
#include "libANGLE/renderer/vulkan/vk_cache_utils.h"
namespace rx
{
namespace
{
constexpr char kMarkerStart[] = "@@ ";
constexpr char kQualifierMarkerBegin[] = "@@ QUALIFIER-";
constexpr char kLayoutMarkerBegin[] = "@@ LAYOUT-";
constexpr char kMarkerEnd[] = " @@";
constexpr char kLayoutParamsBegin = '(';
constexpr char kLayoutParamsEnd = ')';
constexpr char kUniformQualifier[] = "uniform";
constexpr char kVersionDefine[] = "#version 450 core\n";
constexpr char kLineRasterDefine[] = R"(#version 450 core
#define ANGLE_ENABLE_LINE_SEGMENT_RASTERIZATION
)";
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;
}
class IntermediateShaderSource final : angle::NonCopyable
{
public:
IntermediateShaderSource(const std::string &source);
// Find @@ LAYOUT-name(extra, args) @@ and replace it with:
//
// layout(specifier, extra, args)
//
// or if specifier is empty,
//
// layout(extra, args)
//
void insertLayoutSpecifier(const std::string &name, const std::string &specifier);
// Find @@ QUALIFIER-name @@ and replace it with |specifier|.
void insertQualifierSpecifier(const std::string &name, const std::string &specifier);
// Remove @@ LAYOUT-name(*) @@ and @@ QUALIFIER-name @@ altogether.
void eraseLayoutAndQualifierSpecifiers(const std::string &name);
// Replace @@ DEFAULT-UNIFORMS-SET-BINDING @@ with |specifier|.
void insertDefaultUniformsSpecifier(std::string &&specifier);
// Get the transformed shader source as one string.
std::string getShaderSource();
private:
enum class TokenType
{
// A piece of shader source code.
Text,
// Block corresponding to @@ QUALIFIER-abc @@
Qualifier,
// Block corresponding to @@ LAYOUT-abc(extra, args) @@
Layout,
};
struct Token
{
TokenType type;
// |text| contains some shader code if Text, or the id of macro ("abc" in examples above)
// being replaced if Qualifier or Layout.
std::string text;
// If Layout, this contains extra parameters passed in parentheses, if any.
std::string args;
};
void addTextBlock(std::string &&text);
void addLayoutBlock(std::string &&name, std::string &&args);
void addQualifierBlock(std::string &&name);
std::vector<Token> mTokens;
};
void IntermediateShaderSource::addTextBlock(std::string &&text)
{
if (!text.empty())
{
Token token = {TokenType::Text, std::move(text), ""};
mTokens.emplace_back(std::move(token));
}
}
void IntermediateShaderSource::addLayoutBlock(std::string &&name, std::string &&args)
{
ASSERT(!name.empty());
Token token = {TokenType::Layout, std::move(name), std::move(args)};
mTokens.emplace_back(std::move(token));
}
void IntermediateShaderSource::addQualifierBlock(std::string &&name)
{
ASSERT(!name.empty());
Token token = {TokenType::Qualifier, std::move(name), ""};
mTokens.emplace_back(std::move(token));
}
IntermediateShaderSource::IntermediateShaderSource(const std::string &source)
{
size_t cur = 0;
// Split the source into Text, Layout and Qualifier blocks for efficient macro expansion.
while (cur < source.length())
{
// Create a Text block for the code up to the first marker.
std::string text = angle::GetPrefix(source, cur, kMarkerStart);
cur += text.length();
addTextBlock(std::move(text));
if (cur >= source.length())
{
break;
}
if (source.compare(cur, ConstStrLen(kQualifierMarkerBegin), kQualifierMarkerBegin) == 0)
{
cur += ConstStrLen(kQualifierMarkerBegin);
// Get the id of the macro and add a qualifier block.
std::string name = angle::GetPrefix(source, cur, kMarkerEnd);
cur += name.length();
addQualifierBlock(std::move(name));
}
else if (source.compare(cur, ConstStrLen(kLayoutMarkerBegin), kLayoutMarkerBegin) == 0)
{
cur += ConstStrLen(kLayoutMarkerBegin);
// Get the id and arguments of the macro and add a layout block.
// There should always be an extra args list (even if empty, for simplicity).
std::string name = angle::GetPrefix(source, cur, kLayoutParamsBegin);
cur += name.length() + 1;
std::string args = angle::GetPrefix(source, cur, kLayoutParamsEnd);
cur += args.length() + 1;
addLayoutBlock(std::move(name), std::move(args));
}
else
{
// If reached here, @@ was met in the shader source itself which would have been a
// compile error.
UNREACHABLE();
}
// There should always be a closing marker at this point.
ASSERT(source.compare(cur, ConstStrLen(kMarkerEnd), kMarkerEnd) == 0);
// Continue from after the closing of this macro.
cur += ConstStrLen(kMarkerEnd);
}
}
void IntermediateShaderSource::insertLayoutSpecifier(const std::string &name,
const std::string &specifier)
{
for (Token &block : mTokens)
{
if (block.type == TokenType::Layout && block.text == name)
{
const char *separator = specifier.empty() || block.args.empty() ? "" : ", ";
block.type = TokenType::Text;
block.text = "layout(" + block.args + separator + specifier + ")";
break;
}
}
}
void IntermediateShaderSource::insertQualifierSpecifier(const std::string &name,
const std::string &specifier)
{
for (Token &block : mTokens)
{
if (block.type == TokenType::Qualifier && block.text == name)
{
block.type = TokenType::Text;
block.text = specifier;
break;
}
}
}
void IntermediateShaderSource::eraseLayoutAndQualifierSpecifiers(const std::string &name)
{
for (Token &block : mTokens)
{
if ((block.type == TokenType::Layout || block.type == TokenType::Qualifier) &&
block.text == name)
{
block.type = TokenType::Text;
block.text = "";
}
}
}
std::string IntermediateShaderSource::getShaderSource()
{
std::string shaderSource;
for (Token &block : mTokens)
{
// All blocks should have been replaced.
ASSERT(block.type == TokenType::Text);
shaderSource += block.text;
}
return shaderSource;
}
std::string GetMappedSamplerName(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());
return samplerName;
}
} // anonymous namespace
// static
void GlslangWrapper::Initialize()
{
int result = ShInitialize();
ASSERT(result != 0);
}
// static
void GlslangWrapper::Release()
{
int result = ShFinalize();
ASSERT(result != 0);
}
// static
void GlslangWrapper::GetShaderSource(const gl::ProgramState &programState,
const gl::ProgramLinkedResources &resources,
std::string *vertexSourceOut,
std::string *fragmentSourceOut)
{
gl::Shader *glVertexShader = programState.getAttachedShader(gl::ShaderType::Vertex);
gl::Shader *glFragmentShader = programState.getAttachedShader(gl::ShaderType::Fragment);
IntermediateShaderSource vertexSource(glVertexShader->getTranslatedSource());
IntermediateShaderSource fragmentSource(glFragmentShader->getTranslatedSource());
// Parse attribute locations and replace them in the vertex shader.
// See corresponding code in OutputVulkanGLSL.cpp.
for (const sh::Attribute &attribute : programState.getAttributes())
{
// Warning: If we endup supporting ES 3.0 shaders and up, Program::linkAttributes is going
// to bring us all attributes in this list instead of only the active ones.
ASSERT(attribute.active);
std::string locationString = "location = " + Str(attribute.location);
vertexSource.insertLayoutSpecifier(attribute.name, locationString);
vertexSource.insertQualifierSpecifier(attribute.name, "in");
}
// The attributes in the programState could have been filled with active attributes only
// depending on the shader version. If there is inactive attributes left, we have to remove
// their @@ QUALIFIER and @@ LAYOUT markers.
for (const sh::Attribute &attribute : glVertexShader->getAllAttributes())
{
if (attribute.active)
{
continue;
}
vertexSource.eraseLayoutAndQualifierSpecifiers(attribute.name);
}
// Parse output locations and replace them in the fragment shader.
// See corresponding code in OutputVulkanGLSL.cpp.
// TODO(syoussefi): Add support for EXT_blend_func_extended. http://anglebug.com/3385
const auto &outputLocations = programState.getOutputLocations();
const auto &outputVariables = programState.getOutputVariables();
for (const gl::VariableLocation &outputLocation : outputLocations)
{
if (outputLocation.arrayIndex == 0 && outputLocation.used() && !outputLocation.ignored)
{
const sh::OutputVariable &outputVar = outputVariables[outputLocation.index];
std::string locationString;
if (outputVar.location != -1)
{
locationString = "location = " + Str(outputVar.location);
}
else
{
// 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(outputVariables.size() == 1);
locationString = "location = 0";
}
fragmentSource.insertLayoutSpecifier(outputVar.name, locationString);
}
}
// Assign varying locations.
for (const gl::PackedVaryingRegister &varyingReg : resources.varyingPacking.getRegisterList())
{
const auto &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.
if (varying.isStructField() && varying.fieldIndex > 0)
{
continue;
}
// Similarly, assign array varying locations to the assigned location of the first element.
if (varying.isArrayElement() && varying.arrayIndex != 0)
{
continue;
}
std::string locationString = "location = " + Str(varyingReg.registerRow);
if (varyingReg.registerColumn > 0)
{
ASSERT(!varying.varying->isStruct());
ASSERT(!gl::IsMatrixType(varying.varying->type));
locationString += ", component = " + Str(varyingReg.registerColumn);
}
// In the following:
//
// struct S { vec4 field; };
// out S varStruct;
//
// "varStruct" is found through |parentStructName|, with |varying->name| being "field". In
// such a case, use |parentStructName|.
const std::string &name =
varying.isStructField() ? varying.parentStructName : varying.varying->name;
vertexSource.insertLayoutSpecifier(name, locationString);
fragmentSource.insertLayoutSpecifier(name, locationString);
ASSERT(varying.interpolation == sh::INTERPOLATION_SMOOTH);
vertexSource.insertQualifierSpecifier(name, "out");
fragmentSource.insertQualifierSpecifier(name, "in");
}
// Remove all the markers for unused varyings.
for (const std::string &varyingName : resources.varyingPacking.getInactiveVaryingNames())
{
vertexSource.eraseLayoutAndQualifierSpecifiers(varyingName);
fragmentSource.eraseLayoutAndQualifierSpecifiers(varyingName);
}
// Assign uniform locations
// Bind the default uniforms for vertex and fragment shaders.
// See corresponding code in OutputVulkanGLSL.cpp.
const std::string uniformsSearchString("@@ DEFAULT-UNIFORMS-SET-BINDING @@");
const std::string driverUniformsDescriptorSet =
"set = " + Str(kDriverUniformsDescriptorSetIndex);
const std::string uniformsDescriptorSet = "set = " + Str(kUniformsDescriptorSetIndex);
const std::string uniformBlocksDescriptorSet = "set = " + Str(kUniformBlockDescriptorSetIndex);
const std::string texturesDescriptorSet = "set = " + Str(kTextureDescriptorSetIndex);
std::string vertexDefaultUniformsBinding =
uniformsDescriptorSet + ", binding = " + Str(kVertexUniformsBindingIndex);
std::string fragmentDefaultUniformsBinding =
uniformsDescriptorSet + ", binding = " + Str(kFragmentUniformsBindingIndex);
constexpr char kDefaultUniformsBlockName[] = "defaultUniforms";
vertexSource.insertLayoutSpecifier(kDefaultUniformsBlockName, vertexDefaultUniformsBinding);
fragmentSource.insertLayoutSpecifier(kDefaultUniformsBlockName, fragmentDefaultUniformsBinding);
// Assign uniform blocks to a descriptor set and binding.
const auto &uniformBlocks = programState.getUniformBlocks();
uint32_t uniformBlockBinding = 0;
for (const gl::InterfaceBlock &uniformBlock : uniformBlocks)
{
const std::string setBindingString =
uniformBlocksDescriptorSet + ", binding = " + Str(uniformBlockBinding);
vertexSource.insertLayoutSpecifier(uniformBlock.name, setBindingString);
fragmentSource.insertLayoutSpecifier(uniformBlock.name, setBindingString);
vertexSource.insertQualifierSpecifier(uniformBlock.name, kUniformQualifier);
fragmentSource.insertQualifierSpecifier(uniformBlock.name, kUniformQualifier);
++uniformBlockBinding;
}
// Assign textures to a descriptor set and binding.
uint32_t textureBinding = 0;
const auto &uniforms = programState.getUniforms();
for (unsigned int uniformIndex : programState.getSamplerUniformRange())
{
const gl::LinkedUniform &samplerUniform = uniforms[uniformIndex];
const std::string setBindingString =
texturesDescriptorSet + ", binding = " + Str(textureBinding);
// Samplers in structs are extracted and renamed.
const std::string samplerName = GetMappedSamplerName(samplerUniform.name);
ASSERT(samplerUniform.isActive(gl::ShaderType::Vertex) ||
samplerUniform.isActive(gl::ShaderType::Fragment));
if (samplerUniform.isActive(gl::ShaderType::Vertex))
{
vertexSource.insertLayoutSpecifier(samplerName, setBindingString);
}
vertexSource.insertQualifierSpecifier(samplerName, kUniformQualifier);
if (samplerUniform.isActive(gl::ShaderType::Fragment))
{
fragmentSource.insertLayoutSpecifier(samplerName, setBindingString);
}
fragmentSource.insertQualifierSpecifier(samplerName, kUniformQualifier);
textureBinding++;
}
// Place the unused uniforms in the driver uniforms descriptor set, which has a fixed number of
// bindings. This avoids any possible index collision between uniform bindings set in the
// shader and the ones assigned here to the unused ones.
constexpr int kBaseUnusedSamplerBinding = kReservedDriverUniformBindingCount;
int unusedSamplerBinding = kBaseUnusedSamplerBinding;
for (const gl::UnusedUniform &unusedUniform : resources.unusedUniforms)
{
if (unusedUniform.isSampler)
{
// Samplers in structs are extracted and renamed.
std::string uniformName = GetMappedSamplerName(unusedUniform.name);
std::stringstream layoutStringStream;
layoutStringStream << driverUniformsDescriptorSet + ", binding = "
<< unusedSamplerBinding++;
std::string layoutString = layoutStringStream.str();
vertexSource.insertLayoutSpecifier(uniformName, layoutString);
fragmentSource.insertLayoutSpecifier(uniformName, layoutString);
vertexSource.insertQualifierSpecifier(uniformName, kUniformQualifier);
fragmentSource.insertQualifierSpecifier(uniformName, kUniformQualifier);
}
else
{
vertexSource.eraseLayoutAndQualifierSpecifiers(unusedUniform.name);
fragmentSource.eraseLayoutAndQualifierSpecifiers(unusedUniform.name);
}
}
// Substitute layout and qualifier strings for the driver uniforms block.
const std::string driverBlockLayoutString = driverUniformsDescriptorSet + ", binding = 0";
constexpr char kDriverBlockName[] = "ANGLEUniformBlock";
vertexSource.insertLayoutSpecifier(kDriverBlockName, driverBlockLayoutString);
fragmentSource.insertLayoutSpecifier(kDriverBlockName, driverBlockLayoutString);
vertexSource.insertQualifierSpecifier(kDriverBlockName, kUniformQualifier);
fragmentSource.insertQualifierSpecifier(kDriverBlockName, kUniformQualifier);
// Substitute layout and qualifier strings for the position varying. Use the first free
// varying register after the packed varyings.
constexpr char kVaryingName[] = "ANGLEPosition";
std::stringstream layoutStream;
layoutStream << "location = " << (resources.varyingPacking.getMaxSemanticIndex() + 1);
const std::string layout = layoutStream.str();
vertexSource.insertLayoutSpecifier(kVaryingName, layout);
fragmentSource.insertLayoutSpecifier(kVaryingName, layout);
vertexSource.insertQualifierSpecifier(kVaryingName, "out");
fragmentSource.insertQualifierSpecifier(kVaryingName, "in");
*vertexSourceOut = vertexSource.getShaderSource();
*fragmentSourceOut = fragmentSource.getShaderSource();
}
// static
angle::Result GlslangWrapper::GetShaderCode(vk::Context *context,
const gl::Caps &glCaps,
bool enableLineRasterEmulation,
const std::string &vertexSource,
const std::string &fragmentSource,
std::vector<uint32_t> *vertexCodeOut,
std::vector<uint32_t> *fragmentCodeOut)
{
if (enableLineRasterEmulation)
{
std::string patchedVertexSource = vertexSource;
std::string patchedFragmentSource = fragmentSource;
// #defines must come after the #version directive.
ANGLE_VK_CHECK(
context,
angle::ReplaceSubstring(&patchedVertexSource, kVersionDefine, kLineRasterDefine),
VK_ERROR_INVALID_SHADER_NV);
ANGLE_VK_CHECK(
context,
angle::ReplaceSubstring(&patchedFragmentSource, kVersionDefine, kLineRasterDefine),
VK_ERROR_INVALID_SHADER_NV);
return GetShaderCodeImpl(context, glCaps, patchedVertexSource, patchedFragmentSource,
vertexCodeOut, fragmentCodeOut);
}
else
{
return GetShaderCodeImpl(context, glCaps, vertexSource, fragmentSource, vertexCodeOut,
fragmentCodeOut);
}
}
// static
angle::Result GlslangWrapper::GetShaderCodeImpl(vk::Context *context,
const gl::Caps &glCaps,
const std::string &vertexSource,
const std::string &fragmentSource,
std::vector<uint32_t> *vertexCodeOut,
std::vector<uint32_t> *fragmentCodeOut)
{
std::array<const char *, 2> strings = {{vertexSource.c_str(), fragmentSource.c_str()}};
std::array<int, 2> lengths = {
{static_cast<int>(vertexSource.length()), static_cast<int>(fragmentSource.length())}};
// Enable SPIR-V and Vulkan rules when parsing GLSL
EShMessages messages = static_cast<EShMessages>(EShMsgSpvRules | EShMsgVulkanRules);
glslang::TShader vertexShader(EShLangVertex);
vertexShader.setStringsWithLengths(&strings[0], &lengths[0], 1);
vertexShader.setEntryPoint("main");
TBuiltInResource builtInResources(glslang::DefaultTBuiltInResource);
GetBuiltInResourcesFromCaps(glCaps, &builtInResources);
bool vertexResult =
vertexShader.parse(&builtInResources, 450, ECoreProfile, false, false, messages);
if (!vertexResult)
{
ERR() << "Internal error parsing Vulkan vertex shader:\n"
<< vertexShader.getInfoLog() << "\n"
<< vertexShader.getInfoDebugLog() << "\n";
ANGLE_VK_CHECK(context, false, VK_ERROR_INVALID_SHADER_NV);
}
glslang::TShader fragmentShader(EShLangFragment);
fragmentShader.setStringsWithLengths(&strings[1], &lengths[1], 1);
fragmentShader.setEntryPoint("main");
bool fragmentResult =
fragmentShader.parse(&builtInResources, 450, ECoreProfile, false, false, messages);
if (!fragmentResult)
{
ERR() << "Internal error parsing Vulkan fragment shader:\n"
<< fragmentShader.getInfoLog() << "\n"
<< fragmentShader.getInfoDebugLog() << "\n";
ANGLE_VK_CHECK(context, false, VK_ERROR_INVALID_SHADER_NV);
}
glslang::TProgram program;
program.addShader(&vertexShader);
program.addShader(&fragmentShader);
bool linkResult = program.link(messages);
if (!linkResult)
{
ERR() << "Internal error linking Vulkan shaders:\n" << program.getInfoLog() << "\n";
ANGLE_VK_CHECK(context, false, VK_ERROR_INVALID_SHADER_NV);
}
glslang::TIntermediate *vertexStage = program.getIntermediate(EShLangVertex);
glslang::TIntermediate *fragmentStage = program.getIntermediate(EShLangFragment);
glslang::GlslangToSpv(*vertexStage, *vertexCodeOut);
glslang::GlslangToSpv(*fragmentStage, *fragmentCodeOut);
return angle::Result::Continue;
}
} // namespace rx