blob: d0677d63b9f66cff634260c2955545f26a47aece [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"
namespace rx
{
namespace
{
constexpr char kQualifierMarkerBegin[] = "@@ QUALIFIER-";
constexpr char kLayoutMarkerBegin[] = "@@ LAYOUT-";
constexpr char kMarkerEnd[] = " @@";
constexpr char kUniformQualifier[] = "uniform";
constexpr char kVersionDefine[] = "#version 450 core\n";
constexpr char kLineRasterDefine[] = R"(#version 450 core
#define ANGLE_ENABLE_LINE_SEGMENT_RASTERIZATION
)";
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;
}
void InsertLayoutSpecifierString(std::string *shaderString,
const std::string &variableName,
const std::string &layoutString)
{
std::stringstream searchStringBuilder;
searchStringBuilder << kLayoutMarkerBegin << variableName << kMarkerEnd;
std::string searchString = searchStringBuilder.str();
if (!layoutString.empty())
{
angle::ReplaceSubstring(shaderString, searchString, "layout(" + layoutString + ")");
}
else
{
angle::ReplaceSubstring(shaderString, searchString, layoutString);
}
}
void InsertQualifierSpecifierString(std::string *shaderString,
const std::string &variableName,
const std::string &replacementString)
{
std::stringstream searchStringBuilder;
searchStringBuilder << kQualifierMarkerBegin << variableName << kMarkerEnd;
std::string searchString = searchStringBuilder.str();
angle::ReplaceSubstring(shaderString, searchString, replacementString);
}
void EraseLayoutAndQualifierStrings(std::string *vertexSource,
std::string *fragmentSource,
const std::string &uniformName)
{
InsertLayoutSpecifierString(vertexSource, uniformName, "");
InsertLayoutSpecifierString(fragmentSource, uniformName, "");
InsertQualifierSpecifierString(vertexSource, uniformName, "");
InsertQualifierSpecifierString(fragmentSource, uniformName, "");
}
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);
std::string vertexSource = glVertexShader->getTranslatedSource();
std::string fragmentSource = glFragmentShader->getTranslatedSource();
// Parse attribute locations and replace them in the vertex shader.
// See corresponding code in OutputVulkanGLSL.cpp.
// TODO(jmadill): Also do the same for ESSL 3 fragment outputs.
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);
InsertLayoutSpecifierString(&vertexSource, attribute.name, locationString);
InsertQualifierSpecifierString(&vertexSource, 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;
}
InsertLayoutSpecifierString(&vertexSource, attribute.name, "");
InsertQualifierSpecifierString(&vertexSource, attribute.name, "");
}
// Assign varying locations.
for (const gl::PackedVaryingRegister &varyingReg : resources.varyingPacking.getRegisterList())
{
const auto &varying = *varyingReg.packedVarying;
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);
}
InsertLayoutSpecifierString(&vertexSource, varying.varying->name, locationString);
InsertLayoutSpecifierString(&fragmentSource, varying.varying->name, locationString);
ASSERT(varying.interpolation == sh::INTERPOLATION_SMOOTH);
InsertQualifierSpecifierString(&vertexSource, varying.varying->name, "out");
InsertQualifierSpecifierString(&fragmentSource, varying.varying->name, "in");
}
// Remove all the markers for unused varyings.
for (const std::string &varyingName : resources.varyingPacking.getInactiveVaryingNames())
{
EraseLayoutAndQualifierStrings(&vertexSource, &fragmentSource, 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);
// Assign textures to a descriptor set and binding.
int textureCount = 0;
const auto &uniforms = programState.getUniforms();
for (unsigned int uniformIndex : programState.getSamplerUniformRange())
{
const gl::LinkedUniform &samplerUniform = uniforms[uniformIndex];
std::string setBindingString = "set = 1, binding = " + Str(textureCount);
// 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))
{
InsertLayoutSpecifierString(&vertexSource, samplerName, setBindingString);
}
InsertQualifierSpecifierString(&vertexSource, samplerName, kUniformQualifier);
if (samplerUniform.isActive(gl::ShaderType::Fragment))
{
InsertLayoutSpecifierString(&fragmentSource, samplerName, setBindingString);
}
InsertQualifierSpecifierString(&fragmentSource, samplerName, kUniformQualifier);
textureCount++;
}
// Start the unused sampler bindings at something ridiculously high.
constexpr int kBaseUnusedSamplerBinding = 100;
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 << "set = 0, binding = " << unusedSamplerBinding++;
std::string layoutString = layoutStringStream.str();
InsertLayoutSpecifierString(&vertexSource, uniformName, layoutString);
InsertLayoutSpecifierString(&fragmentSource, uniformName, layoutString);
InsertQualifierSpecifierString(&vertexSource, uniformName, kUniformQualifier);
InsertQualifierSpecifierString(&fragmentSource, uniformName, kUniformQualifier);
}
else
{
EraseLayoutAndQualifierStrings(&vertexSource, &fragmentSource, 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);
InsertQualifierSpecifierString(&vertexSource, kDriverBlockName, kUniformQualifier);
InsertQualifierSpecifierString(&fragmentSource, 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();
InsertLayoutSpecifierString(&vertexSource, kVaryingName, layout);
InsertLayoutSpecifierString(&fragmentSource, kVaryingName, layout);
InsertQualifierSpecifierString(&vertexSource, kVaryingName, "out");
InsertQualifierSpecifierString(&fragmentSource, kVaryingName, "in");
*vertexSourceOut = vertexSource;
*fragmentSourceOut = fragmentSource;
}
// 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