blob: c0f3f2ce7af3decba1b4670c84ab4b9b9afa1008 [file] [log] [blame]
//
// Copyright 2024 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.
//
#include "compiler/translator/wgsl/OutputUniformBlocks.h"
#include "angle_gl.h"
#include "common/mathutil.h"
#include "common/utilities.h"
#include "compiler/translator/BaseTypes.h"
#include "compiler/translator/Common.h"
#include "compiler/translator/Compiler.h"
#include "compiler/translator/ImmutableString.h"
#include "compiler/translator/ImmutableStringBuilder.h"
#include "compiler/translator/InfoSink.h"
#include "compiler/translator/IntermNode.h"
#include "compiler/translator/SymbolUniqueId.h"
#include "compiler/translator/tree_util/IntermTraverse.h"
#include "compiler/translator/util.h"
#include "compiler/translator/wgsl/Utils.h"
namespace sh
{
namespace
{
// Traverses the AST and finds all structs that are used in the uniform address space (see the
// UniformBlockMetadata struct).
class FindUniformAddressSpaceStructs : public TIntermTraverser
{
public:
FindUniformAddressSpaceStructs(UniformBlockMetadata *uniformBlockMetadata)
: TIntermTraverser(true, false, false), mUniformBlockMetadata(uniformBlockMetadata)
{}
~FindUniformAddressSpaceStructs() override = default;
bool visitDeclaration(Visit visit, TIntermDeclaration *node) override
{
const TIntermSequence &sequence = *(node->getSequence());
TIntermTyped *variable = sequence.front()->getAsTyped();
const TType &type = variable->getType();
// TODO(anglebug.com/376553328): should eventually ASSERT that there are no default uniforms
// here.
if (type.getQualifier() == EvqUniform)
{
recordTypesUsedInUniformAddressSpace(&type);
}
return true;
}
private:
// Recurses through the tree of types referred to be `type` (which is used in the uniform
// address space) and fills in the `mUniformBlockMetadata` struct appropriately.
void recordTypesUsedInUniformAddressSpace(const TType *type)
{
if (type->isArray())
{
TType innerType = *type;
innerType.toArrayBaseType();
recordTypesUsedInUniformAddressSpace(&innerType);
}
else if (type->getStruct() != nullptr)
{
mUniformBlockMetadata->structsInUniformAddressSpace.insert(
type->getStruct()->uniqueId().get());
// Recurse into the types of the fields of this struct type.
for (TField *const field : type->getStruct()->fields())
{
recordTypesUsedInUniformAddressSpace(field->type());
}
}
}
UniformBlockMetadata *const mUniformBlockMetadata;
};
} // namespace
bool RecordUniformBlockMetadata(TIntermBlock *root, UniformBlockMetadata &outMetadata)
{
FindUniformAddressSpaceStructs traverser(&outMetadata);
root->traverse(&traverser);
return true;
}
bool OutputUniformWrapperStructsAndConversions(
TInfoSinkBase &output,
const WGSLGenerationMetadataForUniforms &wgslGenerationMetadataForUniforms)
{
auto generate16AlignedWrapperStruct = [&output](const TType &type) {
output << "struct " << MakeUniformWrapperStructName(&type) << "\n{\n";
output << " @align(16) " << kWrappedStructFieldName << " : ";
WriteWgslType(output, type, {});
output << "\n};\n";
};
bool generatedVec2WrapperStruct = false;
for (const TType &type : wgslGenerationMetadataForUniforms.arrayElementTypesInUniforms)
{
// Structs don't need wrapper structs.
ASSERT(type.getStruct() == nullptr);
// Multidimensional arrays not currently supported in uniforms
ASSERT(!type.isArray());
if (type.isVector() && type.getNominalSize() == 2)
{
generatedVec2WrapperStruct = true;
}
generate16AlignedWrapperStruct(type);
}
// matCx2 is represented as array<ANGLE_wrapped_vec2, C> so if there are matCx2s we need to
// generate an ANGLE_wrapped_vec2 struct.
if (!wgslGenerationMetadataForUniforms.outputMatCx2Conversion.empty() &&
!generatedVec2WrapperStruct)
{
generate16AlignedWrapperStruct(*new TType(TBasicType::EbtFloat, 2));
}
for (const TType &type :
wgslGenerationMetadataForUniforms.arrayElementTypesThatNeedUnwrappingConversions)
{
// Should be a subset of the types that have had wrapper structs generated above, otherwise
// it's impossible to unwrap them!
TType innerType = type;
innerType.toArrayElementType();
ASSERT(wgslGenerationMetadataForUniforms.arrayElementTypesInUniforms.count(innerType) != 0);
// This could take ptr<uniform, typeName>, with the unrestricted_pointer_parameters
// extension. This is probably fine.
output << "fn " << MakeUnwrappingArrayConversionFunctionName(&type) << "(wrappedArr : ";
WriteWgslType(output, type, {WgslAddressSpace::Uniform});
output << ") -> ";
WriteWgslType(output, type, {WgslAddressSpace::NonUniform});
output << "\n{\n";
output << " var retVal : ";
WriteWgslType(output, type, {WgslAddressSpace::NonUniform});
output << ";\n";
output << " for (var i : u32 = 0; i < " << type.getOutermostArraySize() << "; i++) {;\n";
output << " retVal[i] = wrappedArr[i]." << kWrappedStructFieldName << ";\n";
output << " }\n";
output << " return retVal;\n";
output << "}\n";
}
for (const TType &type : wgslGenerationMetadataForUniforms.outputMatCx2Conversion)
{
ASSERT(type.isMatrix() && type.getRows() == 2);
output << "fn " << MakeMatCx2ConversionFunctionName(&type) << "(mangledMatrix : ";
WriteWgslType(output, type, {WgslAddressSpace::Uniform});
output << ") -> ";
WriteWgslType(output, type, {WgslAddressSpace::NonUniform});
output << "\n{\n";
output << " var retVal : ";
WriteWgslType(output, type, {WgslAddressSpace::NonUniform});
output << ";\n";
if (type.isArray())
{
output << " for (var i : u32 = 0; i < " << type.getOutermostArraySize()
<< "; i++) {;\n";
output << " retVal[i] = ";
}
else
{
output << " retVal = ";
}
TType baseType = type;
baseType.toArrayBaseType();
WriteWgslType(output, baseType, {WgslAddressSpace::NonUniform});
output << "(";
for (uint8_t i = 0; i < type.getCols(); i++)
{
if (i != 0)
{
output << ", ";
}
// The mangled matrix is an array and the elements are wrapped vec2s, which can be
// passed directly to the matCx2 constructor.
output << "mangledMatrix" << (type.isArray() ? "[i]" : "") << "[" << static_cast<int>(i)
<< "]." << kWrappedStructFieldName;
}
output << ");\n";
if (type.isArray())
{
// Close the for loop.
output << " }\n";
}
output << " return retVal;\n";
output << "}\n";
}
return true;
}
ImmutableString MakeUnwrappingArrayConversionFunctionName(const TType *type)
{
ASSERT(type->getNumArraySizes() <= 1);
ImmutableString arrStr = type->isArray() ? BuildConcatenatedImmutableString(
"Array", type->getOutermostArraySize(), "_")
: kEmptyImmutableString;
return BuildConcatenatedImmutableString("ANGLE_Convert_", arrStr,
MakeUniformWrapperStructName(type), "_ElementsTo_",
type->getBuiltInTypeNameString(), "_Elements");
}
bool IsMatCx2(const TType *type)
{
return type->isMatrix() && type->getRows() == 2;
}
ImmutableString MakeMatCx2ConversionFunctionName(const TType *type)
{
ASSERT(type->getNumArraySizes() <= 1);
ImmutableString arrStr = type->isArray() ? BuildConcatenatedImmutableString(
"Array", type->getOutermostArraySize(), "_")
: kEmptyImmutableString;
return BuildConcatenatedImmutableString("ANGLE_Convert_", arrStr, "Mat", type->getCols(), "x2");
}
bool OutputUniformBlocksAndSamplers(TCompiler *compiler, TIntermBlock *root)
{
// TODO(anglebug.com/42267100): This should eventually just be handled the same way as a regular
// UBO, like in Vulkan which create a block out of the default uniforms with a traverser:
// https://source.chromium.org/chromium/chromium/src/+/main:third_party/angle/src/compiler/translator/spirv/TranslatorSPIRV.cpp;l=70;drc=451093bbaf7fe812bf67d27d760f3bb64c92830b
const std::vector<ShaderVariable> &basicUniforms = compiler->getUniforms();
TInfoSinkBase &output = compiler->getInfoSink().obj;
GlobalVars globalVars = FindGlobalVars(root);
// Only output a struct at all if there are going to be members.
bool outputStructHeader = false;
for (const ShaderVariable &shaderVar : basicUniforms)
{
if (gl::IsOpaqueType(shaderVar.type) || !shaderVar.active)
{
continue;
}
if (shaderVar.isBuiltIn())
{
// gl_DepthRange and also the GLSL 4.2 gl_NumSamples are uniforms.
// TODO(anglebug.com/42267100): put gl_DepthRange into default uniform block.
continue;
}
// TODO(anglebug.com/42267100): some types will NOT match std140 layout here, namely matCx2,
// bool, and arrays with stride less than 16.
// (this check does not cover the unsupported case where there is an array of structs of
// size < 16).
if (shaderVar.type == GL_BOOL)
{
return false;
}
// Some uniform variables might have been deleted, for example if they were structs that
// only contained samplers (which are pulled into separate default uniforms).
auto globalVarIter = globalVars.find(shaderVar.name);
if (globalVarIter == globalVars.end())
{
continue;
}
if (!outputStructHeader)
{
output << "struct ANGLE_DefaultUniformBlock {\n";
outputStructHeader = true;
}
output << " ";
output << shaderVar.name << " : ";
TIntermDeclaration *declNode = globalVarIter->second;
const TVariable *astVar = &ViewDeclaration(*declNode).symbol.variable();
WriteWgslType(output, astVar->getType(), {WgslAddressSpace::Uniform});
output << ",\n";
}
// TODO(anglebug.com/42267100): might need string replacement for @group(0) and @binding(0)
// annotations. All WGSL resources available to shaders share the same (group, binding) ID
// space.
if (outputStructHeader)
{
ASSERT(compiler->getShaderType() == GL_VERTEX_SHADER ||
compiler->getShaderType() == GL_FRAGMENT_SHADER);
const uint32_t bindingIndex = compiler->getShaderType() == GL_VERTEX_SHADER
? kDefaultVertexUniformBlockBinding
: kDefaultFragmentUniformBlockBinding;
output << "};\n\n"
<< "@group(" << kDefaultUniformBlockBindGroup << ") @binding(" << bindingIndex
<< ") var<uniform> " << kDefaultUniformBlockVarName << " : "
<< kDefaultUniformBlockVarType << ";\n";
}
for (const auto &globalVarIter : globalVars)
{
TIntermDeclaration *declNode = globalVarIter.second;
ASSERT(declNode);
const TIntermSymbol *declSymbol = &ViewDeclaration(*declNode).symbol;
const TType &declType = declSymbol->getType();
if (!declType.isSampler())
{
continue;
}
// Note that this may output ignored symbols.
output << kTextureSamplerBindingMarker << kAngleSamplerPrefix << declSymbol->getName()
<< " : ";
WriteWgslSamplerType(output, declType, WgslSamplerTypeConfig::Sampler);
output << ";\n";
output << kTextureSamplerBindingMarker << kAngleTexturePrefix << declSymbol->getName()
<< " : ";
WriteWgslSamplerType(output, declType, WgslSamplerTypeConfig::Texture);
output << ";\n";
}
return true;
}
std::string WGSLGetMappedSamplerName(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());
return samplerName;
}
} // namespace sh