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, ...@@ -57,7 +57,7 @@ std::vector<std::string> SplitString(const std::string &input,
if (resultType == SPLIT_WANT_ALL || !piece.empty()) 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) ...@@ -101,6 +101,26 @@ std::string TrimString(const std::string &input, const std::string &trimChars)
return input.substr(begin, end - begin + 1); 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) bool HexStringToUInt(const std::string &input, unsigned int *uintOut)
{ {
unsigned int offset = 0; unsigned int offset = 0;
......
...@@ -41,6 +41,10 @@ void SplitStringAlongWhitespace(const std::string &input, std::vector<std::strin ...@@ -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); 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 HexStringToUInt(const std::string &input, unsigned int *uintOut);
bool ReadFileToString(const std::string &path, std::string *stringOut); bool ReadFileToString(const std::string &path, std::string *stringOut);
......
...@@ -63,7 +63,7 @@ void TOutputVulkanGLSL::writeLayoutQualifier(TIntermTyped *variable) ...@@ -63,7 +63,7 @@ void TOutputVulkanGLSL::writeLayoutQualifier(TIntermTyped *variable)
if (needsCustomLayout) if (needsCustomLayout)
{ {
out << "@@ LAYOUT-" << symbol->getName() << " @@"; out << "@@ LAYOUT-" << symbol->getName() << "() @@";
} }
else else
{ {
......
...@@ -667,7 +667,7 @@ void TranslatorVulkan::translate(TIntermBlock *root, ...@@ -667,7 +667,7 @@ void TranslatorVulkan::translate(TIntermBlock *root,
if (defaultUniformCount > 0) 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()); DeclareDefaultUniformsTraverser defaultTraverser(&sink, getHashFunction(), &getNameMap());
root->traverse(&defaultTraverser); root->traverse(&defaultTraverser);
......
...@@ -32,9 +32,12 @@ namespace rx ...@@ -32,9 +32,12 @@ namespace rx
{ {
namespace namespace
{ {
constexpr char kMarkerStart[] = "@@ ";
constexpr char kQualifierMarkerBegin[] = "@@ QUALIFIER-"; constexpr char kQualifierMarkerBegin[] = "@@ QUALIFIER-";
constexpr char kLayoutMarkerBegin[] = "@@ LAYOUT-"; constexpr char kLayoutMarkerBegin[] = "@@ LAYOUT-";
constexpr char kMarkerEnd[] = " @@"; constexpr char kMarkerEnd[] = " @@";
constexpr char kLayoutParamsBegin = '(';
constexpr char kLayoutParamsEnd = ')';
constexpr char kUniformQualifier[] = "uniform"; constexpr char kUniformQualifier[] = "uniform";
constexpr char kVersionDefine[] = "#version 450 core\n"; constexpr char kVersionDefine[] = "#version 450 core\n";
constexpr char kLineRasterDefine[] = R"(#version 450 core constexpr char kLineRasterDefine[] = R"(#version 450 core
...@@ -42,6 +45,16 @@ constexpr char kLineRasterDefine[] = R"(#version 450 core ...@@ -42,6 +45,16 @@ constexpr char kLineRasterDefine[] = R"(#version 450 core
#define ANGLE_ENABLE_LINE_SEGMENT_RASTERIZATION #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) void GetBuiltInResourcesFromCaps(const gl::Caps &caps, TBuiltInResource *outBuiltInResources)
{ {
outBuiltInResources->maxDrawBuffers = caps.maxDrawBuffers; outBuiltInResources->maxDrawBuffers = caps.maxDrawBuffers;
...@@ -75,43 +88,193 @@ void GetBuiltInResourcesFromCaps(const gl::Caps &caps, TBuiltInResource *outBuil ...@@ -75,43 +88,193 @@ void GetBuiltInResourcesFromCaps(const gl::Caps &caps, TBuiltInResource *outBuil
outBuiltInResources->maxVertexUniformVectors = caps.maxVertexUniformVectors; outBuiltInResources->maxVertexUniformVectors = caps.maxVertexUniformVectors;
} }
void InsertLayoutSpecifierString(std::string *shaderString, class IntermediateShaderSource final : angle::NonCopyable
const std::string &variableName,
const std::string &layoutString)
{ {
std::stringstream searchStringBuilder; public:
searchStringBuilder << kLayoutMarkerBegin << variableName << kMarkerEnd; IntermediateShaderSource(const std::string &source);
std::string searchString = searchStringBuilder.str();
// 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, void IntermediateShaderSource::insertQualifierSpecifier(const std::string &name,
const std::string &variableName, const std::string &specifier)
const std::string &replacementString)
{ {
std::stringstream searchStringBuilder; for (Token &block : mTokens)
searchStringBuilder << kQualifierMarkerBegin << variableName << kMarkerEnd; {
std::string searchString = searchStringBuilder.str(); if (block.type == TokenType::Qualifier && block.text == name)
angle::ReplaceSubstring(shaderString, searchString, replacementString); {
block.type = TokenType::Text;
block.text = specifier;
break;
}
}
} }
void EraseLayoutAndQualifierStrings(std::string *vertexSource, void IntermediateShaderSource::eraseLayoutAndQualifierSpecifiers(const std::string &name)
std::string *fragmentSource,
const std::string &uniformName)
{ {
InsertLayoutSpecifierString(vertexSource, uniformName, ""); for (Token &block : mTokens)
InsertLayoutSpecifierString(fragmentSource, uniformName, ""); {
if ((block.type == TokenType::Layout || block.type == TokenType::Qualifier) &&
block.text == name)
{
block.type = TokenType::Text;
block.text = "";
}
}
}
InsertQualifierSpecifierString(vertexSource, uniformName, ""); std::string IntermediateShaderSource::getShaderSource()
InsertQualifierSpecifierString(fragmentSource, uniformName, ""); {
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 GetMappedSamplerName(const std::string &originalName)
...@@ -151,8 +314,8 @@ void GlslangWrapper::GetShaderSource(const gl::ProgramState &programState, ...@@ -151,8 +314,8 @@ void GlslangWrapper::GetShaderSource(const gl::ProgramState &programState,
gl::Shader *glVertexShader = programState.getAttachedShader(gl::ShaderType::Vertex); gl::Shader *glVertexShader = programState.getAttachedShader(gl::ShaderType::Vertex);
gl::Shader *glFragmentShader = programState.getAttachedShader(gl::ShaderType::Fragment); gl::Shader *glFragmentShader = programState.getAttachedShader(gl::ShaderType::Fragment);
std::string vertexSource = glVertexShader->getTranslatedSource(); IntermediateShaderSource vertexSource(glVertexShader->getTranslatedSource());
std::string fragmentSource = glFragmentShader->getTranslatedSource(); IntermediateShaderSource fragmentSource(glFragmentShader->getTranslatedSource());
// Parse attribute locations and replace them in the vertex shader. // Parse attribute locations and replace them in the vertex shader.
// See corresponding code in OutputVulkanGLSL.cpp. // See corresponding code in OutputVulkanGLSL.cpp.
...@@ -163,8 +326,8 @@ void GlslangWrapper::GetShaderSource(const gl::ProgramState &programState, ...@@ -163,8 +326,8 @@ void GlslangWrapper::GetShaderSource(const gl::ProgramState &programState,
ASSERT(attribute.active); ASSERT(attribute.active);
std::string locationString = "location = " + Str(attribute.location); std::string locationString = "location = " + Str(attribute.location);
InsertLayoutSpecifierString(&vertexSource, attribute.name, locationString); vertexSource.insertLayoutSpecifier(attribute.name, locationString);
InsertQualifierSpecifierString(&vertexSource, attribute.name, "in"); vertexSource.insertQualifierSpecifier(attribute.name, "in");
} }
// The attributes in the programState could have been filled with active attributes only // The attributes in the programState could have been filled with active attributes only
...@@ -177,8 +340,7 @@ void GlslangWrapper::GetShaderSource(const gl::ProgramState &programState, ...@@ -177,8 +340,7 @@ void GlslangWrapper::GetShaderSource(const gl::ProgramState &programState,
continue; continue;
} }
InsertLayoutSpecifierString(&vertexSource, attribute.name, ""); vertexSource.eraseLayoutAndQualifierSpecifiers(attribute.name);
InsertQualifierSpecifierString(&vertexSource, attribute.name, "");
} }
// Parse output locations and replace them in the fragment shader. // Parse output locations and replace them in the fragment shader.
...@@ -205,7 +367,7 @@ void GlslangWrapper::GetShaderSource(const gl::ProgramState &programState, ...@@ -205,7 +367,7 @@ void GlslangWrapper::GetShaderSource(const gl::ProgramState &programState,
locationString = "location = 0"; locationString = "location = 0";
} }
InsertLayoutSpecifierString(&fragmentSource, outputVar.name, locationString); fragmentSource.insertLayoutSpecifier(outputVar.name, locationString);
} }
} }
...@@ -240,34 +402,31 @@ void GlslangWrapper::GetShaderSource(const gl::ProgramState &programState, ...@@ -240,34 +402,31 @@ void GlslangWrapper::GetShaderSource(const gl::ProgramState &programState,
// struct S { vec4 field; }; // struct S { vec4 field; };
// out S varStruct; // out S varStruct;
// //
// "varStruct" is found through `parentStructName`, with `varying->name` being "field". In // "varStruct" is found through |parentStructName|, with |varying->name| being "field". In
// such a case, use `parentStructName`. // such a case, use |parentStructName|.
const std::string &name = const std::string &name =
varying.isStructField() ? varying.parentStructName : varying.varying->name; varying.isStructField() ? varying.parentStructName : varying.varying->name;
InsertLayoutSpecifierString(&vertexSource, name, locationString); vertexSource.insertLayoutSpecifier(name, locationString);
InsertLayoutSpecifierString(&fragmentSource, name, locationString); fragmentSource.insertLayoutSpecifier(name, locationString);
ASSERT(varying.interpolation == sh::INTERPOLATION_SMOOTH); ASSERT(varying.interpolation == sh::INTERPOLATION_SMOOTH);
InsertQualifierSpecifierString(&vertexSource, name, "out"); vertexSource.insertQualifierSpecifier(name, "out");
InsertQualifierSpecifierString(&fragmentSource, name, "in"); fragmentSource.insertQualifierSpecifier(name, "in");
} }
// Remove all the markers for unused varyings. // Remove all the markers for unused varyings.
for (const std::string &varyingName : resources.varyingPacking.getInactiveVaryingNames()) 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. // Bind the default uniforms for vertex and fragment shaders.
// See corresponding code in OutputVulkanGLSL.cpp. // See corresponding code in OutputVulkanGLSL.cpp.
std::string uniformsSearchString("@@ DEFAULT-UNIFORMS-SET-BINDING @@"); constexpr char kDefaultUniformsBlockName[] = "defaultUniforms";
vertexSource.insertLayoutSpecifier(kDefaultUniformsBlockName, "set = 0, binding = 0");
std::string vertexDefaultUniformsBinding = "set = 0, binding = 0"; fragmentSource.insertLayoutSpecifier(kDefaultUniformsBlockName, "set = 0, binding = 1");
std::string fragmentDefaultUniformsBinding = "set = 0, binding = 1";
angle::ReplaceSubstring(&vertexSource, uniformsSearchString, vertexDefaultUniformsBinding);
angle::ReplaceSubstring(&fragmentSource, uniformsSearchString, fragmentDefaultUniformsBinding);
// Assign textures to a descriptor set and binding. // Assign textures to a descriptor set and binding.
int textureCount = 0; int textureCount = 0;
...@@ -284,15 +443,15 @@ void GlslangWrapper::GetShaderSource(const gl::ProgramState &programState, ...@@ -284,15 +443,15 @@ void GlslangWrapper::GetShaderSource(const gl::ProgramState &programState,
samplerUniform.isActive(gl::ShaderType::Fragment)); samplerUniform.isActive(gl::ShaderType::Fragment));
if (samplerUniform.isActive(gl::ShaderType::Vertex)) 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)) if (samplerUniform.isActive(gl::ShaderType::Fragment))
{ {
InsertLayoutSpecifierString(&fragmentSource, samplerName, setBindingString); fragmentSource.insertLayoutSpecifier(samplerName, setBindingString);
} }
InsertQualifierSpecifierString(&fragmentSource, samplerName, kUniformQualifier); fragmentSource.insertQualifierSpecifier(samplerName, kUniformQualifier);
textureCount++; textureCount++;
} }
...@@ -314,26 +473,27 @@ void GlslangWrapper::GetShaderSource(const gl::ProgramState &programState, ...@@ -314,26 +473,27 @@ void GlslangWrapper::GetShaderSource(const gl::ProgramState &programState,
std::string layoutString = layoutStringStream.str(); std::string layoutString = layoutStringStream.str();
InsertLayoutSpecifierString(&vertexSource, uniformName, layoutString); vertexSource.insertLayoutSpecifier(uniformName, layoutString);
InsertLayoutSpecifierString(&fragmentSource, uniformName, layoutString); fragmentSource.insertLayoutSpecifier(uniformName, layoutString);
InsertQualifierSpecifierString(&vertexSource, uniformName, kUniformQualifier); vertexSource.insertQualifierSpecifier(uniformName, kUniformQualifier);
InsertQualifierSpecifierString(&fragmentSource, uniformName, kUniformQualifier); fragmentSource.insertQualifierSpecifier(uniformName, kUniformQualifier);
} }
else else
{ {
EraseLayoutAndQualifierStrings(&vertexSource, &fragmentSource, unusedUniform.name); vertexSource.eraseLayoutAndQualifierSpecifiers(unusedUniform.name);
fragmentSource.eraseLayoutAndQualifierSpecifiers(unusedUniform.name);
} }
} }
// Substitute layout and qualifier strings for the driver uniforms block. // Substitute layout and qualifier strings for the driver uniforms block.
constexpr char kDriverBlockLayoutString[] = "set = 2, binding = 0"; constexpr char kDriverBlockLayoutString[] = "set = 2, binding = 0";
constexpr char kDriverBlockName[] = "ANGLEUniforms"; constexpr char kDriverBlockName[] = "ANGLEUniforms";
InsertLayoutSpecifierString(&vertexSource, kDriverBlockName, kDriverBlockLayoutString); vertexSource.insertLayoutSpecifier(kDriverBlockName, kDriverBlockLayoutString);
InsertLayoutSpecifierString(&fragmentSource, kDriverBlockName, kDriverBlockLayoutString); fragmentSource.insertLayoutSpecifier(kDriverBlockName, kDriverBlockLayoutString);
InsertQualifierSpecifierString(&vertexSource, kDriverBlockName, kUniformQualifier); vertexSource.insertQualifierSpecifier(kDriverBlockName, kUniformQualifier);
InsertQualifierSpecifierString(&fragmentSource, kDriverBlockName, kUniformQualifier); fragmentSource.insertQualifierSpecifier(kDriverBlockName, kUniformQualifier);
// Substitute layout and qualifier strings for the position varying. Use the first free // Substitute layout and qualifier strings for the position varying. Use the first free
// varying register after the packed varyings. // varying register after the packed varyings.
...@@ -341,14 +501,14 @@ void GlslangWrapper::GetShaderSource(const gl::ProgramState &programState, ...@@ -341,14 +501,14 @@ void GlslangWrapper::GetShaderSource(const gl::ProgramState &programState,
std::stringstream layoutStream; std::stringstream layoutStream;
layoutStream << "location = " << (resources.varyingPacking.getMaxSemanticIndex() + 1); layoutStream << "location = " << (resources.varyingPacking.getMaxSemanticIndex() + 1);
const std::string layout = layoutStream.str(); const std::string layout = layoutStream.str();
InsertLayoutSpecifierString(&vertexSource, kVaryingName, layout); vertexSource.insertLayoutSpecifier(kVaryingName, layout);
InsertLayoutSpecifierString(&fragmentSource, kVaryingName, layout); fragmentSource.insertLayoutSpecifier(kVaryingName, layout);
InsertQualifierSpecifierString(&vertexSource, kVaryingName, "out"); vertexSource.insertQualifierSpecifier(kVaryingName, "out");
InsertQualifierSpecifierString(&fragmentSource, kVaryingName, "in"); fragmentSource.insertQualifierSpecifier(kVaryingName, "in");
*vertexSourceOut = vertexSource; *vertexSourceOut = vertexSource.getShaderSource();
*fragmentSourceOut = fragmentSource; *fragmentSourceOut = fragmentSource.getShaderSource();
} }
// static // 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