Commit 62742f9e by Shahbaz Youssefi Committed by Commit Bot

Vulkan: Optimize shader source macro replacement

@@ LAYOUT-xx @@ and @@ QUALIFIER-xx @@ macros are generated by the compiler when emitting Vulkan GLSL. These macros are replaced at link time in the Vulkan backend. Previously, this replacement was done through calls to angle::ReplaceSubstring, reiterating over the whole source on every replacement. This CL does a prepass on the input source and chunks it up in blocks. Search is optimized as only blocks of a certain type are string-compared (skipping large chunks of shader text). Replace is optimized as the whole shader is not shifted left or right on every replacement. Additionally, this CL modifies the layout macro to the following format: @@ LAYOUT-xx(extra, args) @@ This is used in a follow up CL to have the compiler provide additional layout qualifiers. Bug: angleproject:3220 Change-Id: I6367e781c3304d5f2e0a406e4fb4e6feb4c45f1d Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/1592070 Commit-Queue: Shahbaz Youssefi <syoussefi@chromium.org> Reviewed-by: 's avatarJamie Madill <jmadill@chromium.org> Reviewed-by: 's avatarYuly Novikov <ynovikov@chromium.org>
parent be394bad
......@@ -57,7 +57,7 @@ std::vector<std::string> SplitString(const std::string &input,
if (resultType == SPLIT_WANT_ALL || !piece.empty())
{
result.push_back(piece);
result.push_back(std::move(piece));
}
}
......@@ -101,6 +101,26 @@ std::string TrimString(const std::string &input, const std::string &trimChars)
return input.substr(begin, end - begin + 1);
}
std::string GetPrefix(const std::string &input, size_t offset, const char *delimiter)
{
size_t match = input.find(delimiter, offset);
if (match == std::string::npos)
{
return input.substr(offset);
}
return input.substr(offset, match - offset);
}
std::string GetPrefix(const std::string &input, size_t offset, char delimiter)
{
size_t match = input.find(delimiter, offset);
if (match == std::string::npos)
{
return input.substr(offset);
}
return input.substr(offset, match - offset);
}
bool HexStringToUInt(const std::string &input, unsigned int *uintOut)
{
unsigned int offset = 0;
......
......@@ -41,6 +41,10 @@ void SplitStringAlongWhitespace(const std::string &input, std::vector<std::strin
std::string TrimString(const std::string &input, const std::string &trimChars);
// Return the substring starting at offset and up to the first occurance of the |delimeter|.
std::string GetPrefix(const std::string &input, size_t offset, const char *delimiter);
std::string GetPrefix(const std::string &input, size_t offset, char delimiter);
bool HexStringToUInt(const std::string &input, unsigned int *uintOut);
bool ReadFileToString(const std::string &path, std::string *stringOut);
......
......@@ -63,7 +63,7 @@ void TOutputVulkanGLSL::writeLayoutQualifier(TIntermTyped *variable)
if (needsCustomLayout)
{
out << "@@ LAYOUT-" << symbol->getName() << " @@";
out << "@@ LAYOUT-" << symbol->getName() << "() @@";
}
else
{
......
......@@ -667,7 +667,7 @@ void TranslatorVulkan::translate(TIntermBlock *root,
if (defaultUniformCount > 0)
{
sink << "\nlayout(@@ DEFAULT-UNIFORMS-SET-BINDING @@) uniform defaultUniforms\n{\n";
sink << "\n@@ LAYOUT-defaultUniforms() @@ uniform defaultUniforms\n{\n";
DeclareDefaultUniformsTraverser defaultTraverser(&sink, getHashFunction(), &getNameMap());
root->traverse(&defaultTraverser);
......
......@@ -32,9 +32,12 @@ 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
......@@ -42,6 +45,16 @@ 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;
......@@ -75,43 +88,193 @@ void GetBuiltInResourcesFromCaps(const gl::Caps &caps, TBuiltInResource *outBuil
outBuiltInResources->maxVertexUniformVectors = caps.maxVertexUniformVectors;
}
void InsertLayoutSpecifierString(std::string *shaderString,
const std::string &variableName,
const std::string &layoutString)
class IntermediateShaderSource final : angle::NonCopyable
{
std::stringstream searchStringBuilder;
searchStringBuilder << kLayoutMarkerBegin << variableName << kMarkerEnd;
std::string searchString = searchStringBuilder.str();
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);
if (!layoutString.empty())
// 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())
{
angle::ReplaceSubstring(shaderString, searchString, "layout(" + layoutString + ")");
Token token = {TokenType::Text, std::move(text), ""};
mTokens.emplace_back(std::move(token));
}
else
}
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)
{
angle::ReplaceSubstring(shaderString, searchString, layoutString);
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 InsertQualifierSpecifierString(std::string *shaderString,
const std::string &variableName,
const std::string &replacementString)
void IntermediateShaderSource::insertQualifierSpecifier(const std::string &name,
const std::string &specifier)
{
std::stringstream searchStringBuilder;
searchStringBuilder << kQualifierMarkerBegin << variableName << kMarkerEnd;
std::string searchString = searchStringBuilder.str();
angle::ReplaceSubstring(shaderString, searchString, replacementString);
for (Token &block : mTokens)
{
if (block.type == TokenType::Qualifier && block.text == name)
{
block.type = TokenType::Text;
block.text = specifier;
break;
}
}
}
void EraseLayoutAndQualifierStrings(std::string *vertexSource,
std::string *fragmentSource,
const std::string &uniformName)
void IntermediateShaderSource::eraseLayoutAndQualifierSpecifiers(const std::string &name)
{
InsertLayoutSpecifierString(vertexSource, uniformName, "");
InsertLayoutSpecifierString(fragmentSource, uniformName, "");
for (Token &block : mTokens)
{
if ((block.type == TokenType::Layout || block.type == TokenType::Qualifier) &&
block.text == name)
{
block.type = TokenType::Text;
block.text = "";
}
}
}
InsertQualifierSpecifierString(vertexSource, uniformName, "");
InsertQualifierSpecifierString(fragmentSource, uniformName, "");
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)
......@@ -151,8 +314,8 @@ void GlslangWrapper::GetShaderSource(const gl::ProgramState &programState,
gl::Shader *glVertexShader = programState.getAttachedShader(gl::ShaderType::Vertex);
gl::Shader *glFragmentShader = programState.getAttachedShader(gl::ShaderType::Fragment);
std::string vertexSource = glVertexShader->getTranslatedSource();
std::string fragmentSource = glFragmentShader->getTranslatedSource();
IntermediateShaderSource vertexSource(glVertexShader->getTranslatedSource());
IntermediateShaderSource fragmentSource(glFragmentShader->getTranslatedSource());
// Parse attribute locations and replace them in the vertex shader.
// See corresponding code in OutputVulkanGLSL.cpp.
......@@ -163,8 +326,8 @@ void GlslangWrapper::GetShaderSource(const gl::ProgramState &programState,
ASSERT(attribute.active);
std::string locationString = "location = " + Str(attribute.location);
InsertLayoutSpecifierString(&vertexSource, attribute.name, locationString);
InsertQualifierSpecifierString(&vertexSource, attribute.name, "in");
vertexSource.insertLayoutSpecifier(attribute.name, locationString);
vertexSource.insertQualifierSpecifier(attribute.name, "in");
}
// The attributes in the programState could have been filled with active attributes only
......@@ -177,8 +340,7 @@ void GlslangWrapper::GetShaderSource(const gl::ProgramState &programState,
continue;
}
InsertLayoutSpecifierString(&vertexSource, attribute.name, "");
InsertQualifierSpecifierString(&vertexSource, attribute.name, "");
vertexSource.eraseLayoutAndQualifierSpecifiers(attribute.name);
}
// Parse output locations and replace them in the fragment shader.
......@@ -205,7 +367,7 @@ void GlslangWrapper::GetShaderSource(const gl::ProgramState &programState,
locationString = "location = 0";
}
InsertLayoutSpecifierString(&fragmentSource, outputVar.name, locationString);
fragmentSource.insertLayoutSpecifier(outputVar.name, locationString);
}
}
......@@ -240,34 +402,31 @@ void GlslangWrapper::GetShaderSource(const gl::ProgramState &programState,
// struct S { vec4 field; };
// out S varStruct;
//
// "varStruct" is found through `parentStructName`, with `varying->name` being "field". In
// such a case, use `parentStructName`.
// "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;
InsertLayoutSpecifierString(&vertexSource, name, locationString);
InsertLayoutSpecifierString(&fragmentSource, name, locationString);
vertexSource.insertLayoutSpecifier(name, locationString);
fragmentSource.insertLayoutSpecifier(name, locationString);
ASSERT(varying.interpolation == sh::INTERPOLATION_SMOOTH);
InsertQualifierSpecifierString(&vertexSource, name, "out");
InsertQualifierSpecifierString(&fragmentSource, name, "in");
vertexSource.insertQualifierSpecifier(name, "out");
fragmentSource.insertQualifierSpecifier(name, "in");
}
// Remove all the markers for unused varyings.
for (const std::string &varyingName : resources.varyingPacking.getInactiveVaryingNames())
{
EraseLayoutAndQualifierStrings(&vertexSource, &fragmentSource, varyingName);
vertexSource.eraseLayoutAndQualifierSpecifiers(varyingName);
fragmentSource.eraseLayoutAndQualifierSpecifiers(varyingName);
}
// Bind the default uniforms for vertex and fragment shaders.
// See corresponding code in OutputVulkanGLSL.cpp.
std::string uniformsSearchString("@@ DEFAULT-UNIFORMS-SET-BINDING @@");
std::string vertexDefaultUniformsBinding = "set = 0, binding = 0";
std::string fragmentDefaultUniformsBinding = "set = 0, binding = 1";
angle::ReplaceSubstring(&vertexSource, uniformsSearchString, vertexDefaultUniformsBinding);
angle::ReplaceSubstring(&fragmentSource, uniformsSearchString, fragmentDefaultUniformsBinding);
constexpr char kDefaultUniformsBlockName[] = "defaultUniforms";
vertexSource.insertLayoutSpecifier(kDefaultUniformsBlockName, "set = 0, binding = 0");
fragmentSource.insertLayoutSpecifier(kDefaultUniformsBlockName, "set = 0, binding = 1");
// Assign textures to a descriptor set and binding.
int textureCount = 0;
......@@ -284,15 +443,15 @@ void GlslangWrapper::GetShaderSource(const gl::ProgramState &programState,
samplerUniform.isActive(gl::ShaderType::Fragment));
if (samplerUniform.isActive(gl::ShaderType::Vertex))
{
InsertLayoutSpecifierString(&vertexSource, samplerName, setBindingString);
vertexSource.insertLayoutSpecifier(samplerName, setBindingString);
}
InsertQualifierSpecifierString(&vertexSource, samplerName, kUniformQualifier);
vertexSource.insertQualifierSpecifier(samplerName, kUniformQualifier);
if (samplerUniform.isActive(gl::ShaderType::Fragment))
{
InsertLayoutSpecifierString(&fragmentSource, samplerName, setBindingString);
fragmentSource.insertLayoutSpecifier(samplerName, setBindingString);
}
InsertQualifierSpecifierString(&fragmentSource, samplerName, kUniformQualifier);
fragmentSource.insertQualifierSpecifier(samplerName, kUniformQualifier);
textureCount++;
}
......@@ -314,26 +473,27 @@ void GlslangWrapper::GetShaderSource(const gl::ProgramState &programState,
std::string layoutString = layoutStringStream.str();
InsertLayoutSpecifierString(&vertexSource, uniformName, layoutString);
InsertLayoutSpecifierString(&fragmentSource, uniformName, layoutString);
vertexSource.insertLayoutSpecifier(uniformName, layoutString);
fragmentSource.insertLayoutSpecifier(uniformName, layoutString);
InsertQualifierSpecifierString(&vertexSource, uniformName, kUniformQualifier);
InsertQualifierSpecifierString(&fragmentSource, uniformName, kUniformQualifier);
vertexSource.insertQualifierSpecifier(uniformName, kUniformQualifier);
fragmentSource.insertQualifierSpecifier(uniformName, kUniformQualifier);
}
else
{
EraseLayoutAndQualifierStrings(&vertexSource, &fragmentSource, unusedUniform.name);
vertexSource.eraseLayoutAndQualifierSpecifiers(unusedUniform.name);
fragmentSource.eraseLayoutAndQualifierSpecifiers(unusedUniform.name);
}
}
// Substitute layout and qualifier strings for the driver uniforms block.
constexpr char kDriverBlockLayoutString[] = "set = 2, binding = 0";
constexpr char kDriverBlockName[] = "ANGLEUniforms";
InsertLayoutSpecifierString(&vertexSource, kDriverBlockName, kDriverBlockLayoutString);
InsertLayoutSpecifierString(&fragmentSource, kDriverBlockName, kDriverBlockLayoutString);
vertexSource.insertLayoutSpecifier(kDriverBlockName, kDriverBlockLayoutString);
fragmentSource.insertLayoutSpecifier(kDriverBlockName, kDriverBlockLayoutString);
InsertQualifierSpecifierString(&vertexSource, kDriverBlockName, kUniformQualifier);
InsertQualifierSpecifierString(&fragmentSource, kDriverBlockName, kUniformQualifier);
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.
......@@ -341,14 +501,14 @@ void GlslangWrapper::GetShaderSource(const gl::ProgramState &programState,
std::stringstream layoutStream;
layoutStream << "location = " << (resources.varyingPacking.getMaxSemanticIndex() + 1);
const std::string layout = layoutStream.str();
InsertLayoutSpecifierString(&vertexSource, kVaryingName, layout);
InsertLayoutSpecifierString(&fragmentSource, kVaryingName, layout);
vertexSource.insertLayoutSpecifier(kVaryingName, layout);
fragmentSource.insertLayoutSpecifier(kVaryingName, layout);
InsertQualifierSpecifierString(&vertexSource, kVaryingName, "out");
InsertQualifierSpecifierString(&fragmentSource, kVaryingName, "in");
vertexSource.insertQualifierSpecifier(kVaryingName, "out");
fragmentSource.insertQualifierSpecifier(kVaryingName, "in");
*vertexSourceOut = vertexSource;
*fragmentSourceOut = fragmentSource;
*vertexSourceOut = vertexSource.getShaderSource();
*fragmentSourceOut = fragmentSource.getShaderSource();
}
// static
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment