// // Copyright 2015 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. // // VaryingPacking: // Class which describes a mapping from varyings to registers, according // to the spec, or using custom packing algorithms. We also keep a register // allocation list for the D3D renderer. // #include "libANGLE/VaryingPacking.h" #include "common/utilities.h" #include "libANGLE/Program.h" #include "libANGLE/ProgramExecutable.h" #include "libANGLE/Shader.h" namespace gl { namespace { // true if varying x has a higher priority in packing than y bool ComparePackedVarying(const PackedVarying &x, const PackedVarying &y) { // If the PackedVarying 'x' or 'y' to be compared is an array element for transform feedback, // this clones an equivalent non-array shader variable 'vx' or 'vy' for actual comparison // instead. For I/O block arrays, the array index is used in the comparison. sh::ShaderVariable vx, vy; const sh::ShaderVariable *px, *py; px = &x.varying(); py = &y.varying(); if (x.isTransformFeedbackArrayElement()) { vx = *px; vx.arraySizes.clear(); px = &vx; } if (y.isTransformFeedbackArrayElement()) { vy = *py; vy.arraySizes.clear(); py = &vy; } // Make sure struct fields end up together. if (x.isStructField() != y.isStructField()) { return x.isStructField(); } if (x.isStructField()) { ASSERT(y.isStructField()); if (x.getParentStructName() != y.getParentStructName()) { return x.getParentStructName() < y.getParentStructName(); } } // For I/O block fields, order first by array index: if (!x.isTransformFeedbackArrayElement() && !y.isTransformFeedbackArrayElement()) { if (x.arrayIndex != y.arrayIndex) { return x.arrayIndex < y.arrayIndex; } } // Then order by field index if (x.fieldIndex != y.fieldIndex) { return x.fieldIndex < y.fieldIndex; } // Then order by secondary field index if (x.secondaryFieldIndex != y.secondaryFieldIndex) { return x.secondaryFieldIndex < y.secondaryFieldIndex; } // Otherwise order by variable return gl::CompareShaderVar(*px, *py); } bool InterfaceVariablesMatch(const sh::ShaderVariable &front, const sh::ShaderVariable &back) { // Matching ruels from 7.4.1 Shader Interface Matching from the GLES 3.2 spec: // - the two variables match in name, type, and qualification; or // - the two variables are declared with the same location qualifier and match in type and // qualification. Note that we use a more permissive check here thanks to front-end validation. if (back.location != -1 && back.location == front.location) { return true; } if (front.isShaderIOBlock != back.isShaderIOBlock) { return false; } // Compare names, or if shader I/O blocks, block names. const std::string &backName = back.isShaderIOBlock ? back.structOrBlockName : back.name; const std::string &frontName = front.isShaderIOBlock ? front.structOrBlockName : front.name; return backName == frontName; } GLint GetMaxShaderInputVectors(const Caps &caps, ShaderType shaderStage) { switch (shaderStage) { case ShaderType::TessControl: return caps.maxTessControlInputComponents / 4; case ShaderType::TessEvaluation: return caps.maxTessEvaluationInputComponents / 4; case ShaderType::Geometry: return caps.maxGeometryInputComponents / 4; case ShaderType::Fragment: return caps.maxFragmentInputComponents / 4; default: return std::numeric_limits::max(); } } GLint GetMaxShaderOutputVectors(const Caps &caps, ShaderType shaderStage) { switch (shaderStage) { case ShaderType::Vertex: return caps.maxVertexOutputComponents / 4; case ShaderType::TessControl: return caps.maxTessControlOutputComponents / 4; case ShaderType::TessEvaluation: return caps.maxTessEvaluationOutputComponents / 4; case ShaderType::Geometry: return caps.maxGeometryOutputComponents / 4; default: return std::numeric_limits::max(); } } bool ShouldSkipPackedVarying(const sh::ShaderVariable &varying, PackMode packMode) { // Don't pack gl_Position. Also don't count gl_PointSize for D3D9. return varying.name == "gl_Position" || (varying.name == "gl_PointSize" && packMode == PackMode::ANGLE_NON_CONFORMANT_D3D9); } std::vector StripVaryingArrayDimension(const sh::ShaderVariable *frontVarying, ShaderType frontShaderStage, const sh::ShaderVariable *backVarying, ShaderType backShaderStage, bool isStructField) { // "Geometry shader inputs, tessellation control shader inputs and outputs, and tessellation // evaluation inputs all have an additional level of arrayness relative to other shader inputs // and outputs. This outer array level is removed from the type before considering how many // locations the type consumes." if (backVarying && backVarying->isArray() && !backVarying->isPatch && !isStructField && (backShaderStage == ShaderType::Geometry || backShaderStage == ShaderType::TessEvaluation || backShaderStage == ShaderType::TessControl)) { std::vector arr = backVarying->arraySizes; arr.pop_back(); return arr; } if (frontVarying && frontVarying->isArray() && !frontVarying->isPatch && !isStructField && frontShaderStage == ShaderType::TessControl) { std::vector arr = frontVarying->arraySizes; arr.pop_back(); return arr; } return frontVarying ? frontVarying->arraySizes : backVarying->arraySizes; } } // anonymous namespace // Implementation of VaryingInShaderRef VaryingInShaderRef::VaryingInShaderRef(ShaderType stageIn, const sh::ShaderVariable *varyingIn) : varying(varyingIn), stage(stageIn) {} VaryingInShaderRef::~VaryingInShaderRef() = default; VaryingInShaderRef::VaryingInShaderRef(VaryingInShaderRef &&other) : varying(other.varying), stage(other.stage), parentStructName(std::move(other.parentStructName)), parentStructMappedName(std::move(other.parentStructMappedName)) {} VaryingInShaderRef &VaryingInShaderRef::operator=(VaryingInShaderRef &&other) { std::swap(varying, other.varying); std::swap(stage, other.stage); std::swap(parentStructName, other.parentStructName); std::swap(parentStructMappedName, other.parentStructMappedName); return *this; } // Implementation of PackedVarying PackedVarying::PackedVarying(VaryingInShaderRef &&frontVaryingIn, VaryingInShaderRef &&backVaryingIn, sh::InterpolationType interpolationIn) : PackedVarying(std::move(frontVaryingIn), std::move(backVaryingIn), interpolationIn, GL_INVALID_INDEX, 0, 0) {} PackedVarying::PackedVarying(VaryingInShaderRef &&frontVaryingIn, VaryingInShaderRef &&backVaryingIn, sh::InterpolationType interpolationIn, GLuint arrayIndexIn, GLuint fieldIndexIn, GLuint secondaryFieldIndexIn) : frontVarying(std::move(frontVaryingIn)), backVarying(std::move(backVaryingIn)), interpolation(interpolationIn), arrayIndex(arrayIndexIn), isTransformFeedback(false), fieldIndex(fieldIndexIn), secondaryFieldIndex(secondaryFieldIndexIn) {} PackedVarying::~PackedVarying() = default; PackedVarying::PackedVarying(PackedVarying &&other) : frontVarying(std::move(other.frontVarying)), backVarying(std::move(other.backVarying)), interpolation(other.interpolation), arrayIndex(other.arrayIndex), isTransformFeedback(other.isTransformFeedback), fieldIndex(other.fieldIndex), secondaryFieldIndex(other.secondaryFieldIndex) {} PackedVarying &PackedVarying::operator=(PackedVarying &&other) { std::swap(frontVarying, other.frontVarying); std::swap(backVarying, other.backVarying); std::swap(interpolation, other.interpolation); std::swap(arrayIndex, other.arrayIndex); std::swap(isTransformFeedback, other.isTransformFeedback); std::swap(fieldIndex, other.fieldIndex); std::swap(secondaryFieldIndex, other.secondaryFieldIndex); return *this; } unsigned int PackedVarying::getBasicTypeElementCount() const { // "Geometry shader inputs, tessellation control shader inputs and outputs, and tessellation // evaluation inputs all have an additional level of arrayness relative to other shader inputs // and outputs. This outer array level is removed from the type before considering how many // locations the type consumes." std::vector arr = StripVaryingArrayDimension(frontVarying.varying, frontVarying.stage, backVarying.varying, backVarying.stage, isStructField()); return arr.empty() ? 1u : arr.back(); } // Implementation of VaryingPacking VaryingPacking::VaryingPacking() = default; VaryingPacking::~VaryingPacking() = default; void VaryingPacking::reset() { clearRegisterMap(); mRegisterList.clear(); mPackedVaryings.clear(); for (std::vector &inactiveVaryingMappedNames : mInactiveVaryingMappedNames) { inactiveVaryingMappedNames.clear(); } for (std::vector &activeBuiltIns : mActiveOutputBuiltIns) { activeBuiltIns.clear(); } } void VaryingPacking::clearRegisterMap() { std::fill(mRegisterMap.begin(), mRegisterMap.end(), Register()); } // Packs varyings into generic varying registers, using the algorithm from // See [OpenGL ES Shading Language 1.00 rev. 17] appendix A section 7 page 111 // Also [OpenGL ES Shading Language 3.00 rev. 4] Section 11 page 119 // Returns false if unsuccessful. bool VaryingPacking::packVaryingIntoRegisterMap(PackMode packMode, const PackedVarying &packedVarying) { const sh::ShaderVariable &varying = packedVarying.varying(); // "Non - square matrices of type matCxR consume the same space as a square matrix of type matN // where N is the greater of C and R." // Here we are a bit more conservative and allow packing non-square matrices more tightly. // Make sure we use transposed matrix types to count registers correctly. ASSERT(!varying.isStruct()); GLenum transposedType = gl::TransposeMatrixType(varying.type); unsigned int varyingRows = gl::VariableRowCount(transposedType); unsigned int varyingColumns = gl::VariableColumnCount(transposedType); // Special pack mode for D3D9. Each varying takes a full register, no sharing. // TODO(jmadill): Implement more sophisticated component packing in D3D9. if (packMode == PackMode::ANGLE_NON_CONFORMANT_D3D9) { varyingColumns = 4; } // "Variables of type mat2 occupies 2 complete rows." // For non-WebGL contexts, we allow mat2 to occupy only two columns per row. else if (packMode == PackMode::WEBGL_STRICT && varying.type == GL_FLOAT_MAT2) { varyingColumns = 4; } // "Arrays of size N are assumed to take N times the size of the base type" // GLSL ES 3.10 section 4.3.6: Output variables cannot be arrays of arrays or arrays of // structures, so we may use getBasicTypeElementCount(). const unsigned int elementCount = packedVarying.getBasicTypeElementCount(); varyingRows *= (packedVarying.isTransformFeedbackArrayElement() ? 1 : elementCount); unsigned int maxVaryingVectors = static_cast(mRegisterMap.size()); // Fail if we are packing a single over-large varying. if (varyingRows > maxVaryingVectors) { return false; } // "For 2, 3 and 4 component variables packing is started using the 1st column of the 1st row. // Variables are then allocated to successive rows, aligning them to the 1st column." if (varyingColumns >= 2 && varyingColumns <= 4) { for (unsigned int row = 0; row <= maxVaryingVectors - varyingRows; ++row) { if (isRegisterRangeFree(row, 0, varyingRows, varyingColumns)) { insertVaryingIntoRegisterMap(row, 0, varyingColumns, packedVarying); return true; } } // "For 2 component variables, when there are no spare rows, the strategy is switched to // using the highest numbered row and the lowest numbered column where the variable will // fit." if (varyingColumns == 2) { for (unsigned int r = maxVaryingVectors - varyingRows + 1; r-- >= 1;) { if (isRegisterRangeFree(r, 2, varyingRows, 2)) { insertVaryingIntoRegisterMap(r, 2, varyingColumns, packedVarying); return true; } } } return false; } // "1 component variables have their own packing rule. They are packed in order of size, largest // first. Each variable is placed in the column that leaves the least amount of space in the // column and aligned to the lowest available rows within that column." ASSERT(varyingColumns == 1); unsigned int contiguousSpace[4] = {0}; unsigned int bestContiguousSpace[4] = {0}; unsigned int totalSpace[4] = {0}; for (unsigned int row = 0; row < maxVaryingVectors; ++row) { for (unsigned int column = 0; column < 4; ++column) { if (mRegisterMap[row][column]) { contiguousSpace[column] = 0; } else { contiguousSpace[column]++; totalSpace[column]++; if (contiguousSpace[column] > bestContiguousSpace[column]) { bestContiguousSpace[column] = contiguousSpace[column]; } } } } unsigned int bestColumn = 0; for (unsigned int column = 1; column < 4; ++column) { if (bestContiguousSpace[column] >= varyingRows && (bestContiguousSpace[bestColumn] < varyingRows || totalSpace[column] < totalSpace[bestColumn])) { bestColumn = column; } } if (bestContiguousSpace[bestColumn] >= varyingRows) { for (unsigned int row = 0; row < maxVaryingVectors; row++) { if (isRegisterRangeFree(row, bestColumn, varyingRows, 1)) { for (unsigned int arrayIndex = 0; arrayIndex < varyingRows; ++arrayIndex) { // If varyingRows > 1, it must be an array. PackedVaryingRegister registerInfo; registerInfo.packedVarying = &packedVarying; registerInfo.registerRow = row + arrayIndex; registerInfo.registerColumn = bestColumn; registerInfo.varyingArrayIndex = (packedVarying.isTransformFeedbackArrayElement() ? packedVarying.arrayIndex : arrayIndex); registerInfo.varyingRowIndex = 0; // Do not record register info for builtins. // TODO(jmadill): Clean this up. if (!varying.isBuiltIn()) { mRegisterList.push_back(registerInfo); } mRegisterMap[row + arrayIndex][bestColumn] = true; } break; } } return true; } return false; } bool VaryingPacking::isRegisterRangeFree(unsigned int registerRow, unsigned int registerColumn, unsigned int varyingRows, unsigned int varyingColumns) const { for (unsigned int row = 0; row < varyingRows; ++row) { ASSERT(registerRow + row < mRegisterMap.size()); for (unsigned int column = 0; column < varyingColumns; ++column) { ASSERT(registerColumn + column < 4); if (mRegisterMap[registerRow + row][registerColumn + column]) { return false; } } } return true; } void VaryingPacking::insertVaryingIntoRegisterMap(unsigned int registerRow, unsigned int registerColumn, unsigned int varyingColumns, const PackedVarying &packedVarying) { unsigned int varyingRows = 0; const sh::ShaderVariable &varying = packedVarying.varying(); ASSERT(!varying.isStruct()); GLenum transposedType = gl::TransposeMatrixType(varying.type); varyingRows = gl::VariableRowCount(transposedType); PackedVaryingRegister registerInfo; registerInfo.packedVarying = &packedVarying; registerInfo.registerColumn = registerColumn; // GLSL ES 3.10 section 4.3.6: Output variables cannot be arrays of arrays or arrays of // structures, so we may use getBasicTypeElementCount(). const unsigned int arrayElementCount = packedVarying.getBasicTypeElementCount(); for (unsigned int arrayElement = 0; arrayElement < arrayElementCount; ++arrayElement) { if (packedVarying.isTransformFeedbackArrayElement() && arrayElement != packedVarying.arrayIndex) { continue; } for (unsigned int varyingRow = 0; varyingRow < varyingRows; ++varyingRow) { registerInfo.registerRow = registerRow + (arrayElement * varyingRows) + varyingRow; registerInfo.varyingRowIndex = varyingRow; registerInfo.varyingArrayIndex = arrayElement; // Do not record register info for builtins. // TODO(jmadill): Clean this up. if (!varying.isBuiltIn()) { mRegisterList.push_back(registerInfo); } for (unsigned int columnIndex = 0; columnIndex < varyingColumns; ++columnIndex) { mRegisterMap[registerInfo.registerRow][registerColumn + columnIndex] = true; } } } } void VaryingPacking::collectUserVarying(const ProgramVaryingRef &ref, VaryingUniqueFullNames *uniqueFullNames) { const sh::ShaderVariable *input = ref.frontShader; const sh::ShaderVariable *output = ref.backShader; // Will get the vertex shader interpolation by default. sh::InterpolationType interpolation = input ? input->interpolation : output->interpolation; VaryingInShaderRef frontVarying(ref.frontShaderStage, input); VaryingInShaderRef backVarying(ref.backShaderStage, output); mPackedVaryings.emplace_back(std::move(frontVarying), std::move(backVarying), interpolation); if (input && !input->isBuiltIn()) { (*uniqueFullNames)[ref.frontShaderStage].insert( mPackedVaryings.back().fullName(ref.frontShaderStage)); } if (output && !output->isBuiltIn()) { (*uniqueFullNames)[ref.backShaderStage].insert( mPackedVaryings.back().fullName(ref.backShaderStage)); } } void VaryingPacking::collectUserVaryingField(const ProgramVaryingRef &ref, GLuint arrayIndex, GLuint fieldIndex, GLuint secondaryFieldIndex, VaryingUniqueFullNames *uniqueFullNames) { const sh::ShaderVariable *input = ref.frontShader; const sh::ShaderVariable *output = ref.backShader; // Will get the vertex shader interpolation by default. sh::InterpolationType interpolation = input ? input->interpolation : output->interpolation; const sh::ShaderVariable *frontField = input ? &input->fields[fieldIndex] : nullptr; const sh::ShaderVariable *backField = output ? &output->fields[fieldIndex] : nullptr; if (secondaryFieldIndex != GL_INVALID_INDEX) { frontField = frontField ? &frontField->fields[secondaryFieldIndex] : nullptr; backField = backField ? &backField->fields[secondaryFieldIndex] : nullptr; } VaryingInShaderRef frontVarying(ref.frontShaderStage, frontField); VaryingInShaderRef backVarying(ref.backShaderStage, backField); if (input) { if (frontField->isShaderIOBlock) { frontVarying.parentStructName = input->structOrBlockName; frontVarying.parentStructMappedName = input->mappedStructOrBlockName; } else { ASSERT(!frontField->isStruct() && !frontField->isArray()); frontVarying.parentStructName = input->name; frontVarying.parentStructMappedName = input->mappedName; } } if (output) { if (backField->isShaderIOBlock) { backVarying.parentStructName = output->structOrBlockName; backVarying.parentStructMappedName = output->mappedStructOrBlockName; } else { ASSERT(!backField->isStruct() && !backField->isArray()); backVarying.parentStructName = output->name; backVarying.parentStructMappedName = output->mappedName; } } mPackedVaryings.emplace_back(std::move(frontVarying), std::move(backVarying), interpolation, arrayIndex, fieldIndex, secondaryFieldIndex == GL_INVALID_INDEX ? 0 : secondaryFieldIndex); if (input) { (*uniqueFullNames)[ref.frontShaderStage].insert( mPackedVaryings.back().fullName(ref.frontShaderStage)); } if (output) { (*uniqueFullNames)[ref.backShaderStage].insert( mPackedVaryings.back().fullName(ref.backShaderStage)); } } void VaryingPacking::collectUserVaryingTF(const ProgramVaryingRef &ref, size_t subscript) { const sh::ShaderVariable *input = ref.frontShader; VaryingInShaderRef frontVarying(ref.frontShaderStage, input); VaryingInShaderRef backVarying(ref.backShaderStage, nullptr); mPackedVaryings.emplace_back(std::move(frontVarying), std::move(backVarying), input->interpolation); mPackedVaryings.back().arrayIndex = static_cast(subscript); mPackedVaryings.back().isTransformFeedback = true; } void VaryingPacking::collectUserVaryingFieldTF(const ProgramVaryingRef &ref, const sh::ShaderVariable &field, GLuint fieldIndex, GLuint secondaryFieldIndex) { const sh::ShaderVariable *input = ref.frontShader; const sh::ShaderVariable *frontField = &field; if (secondaryFieldIndex != GL_INVALID_INDEX) { frontField = &frontField->fields[secondaryFieldIndex]; } VaryingInShaderRef frontVarying(ref.frontShaderStage, frontField); VaryingInShaderRef backVarying(ref.backShaderStage, nullptr); if (frontField->isShaderIOBlock) { frontVarying.parentStructName = input->structOrBlockName; frontVarying.parentStructMappedName = input->mappedStructOrBlockName; } else { ASSERT(!frontField->isStruct() && !frontField->isArray()); frontVarying.parentStructName = input->name; frontVarying.parentStructMappedName = input->mappedName; } mPackedVaryings.emplace_back(std::move(frontVarying), std::move(backVarying), input->interpolation, GL_INVALID_INDEX, fieldIndex, secondaryFieldIndex == GL_INVALID_INDEX ? 0 : secondaryFieldIndex); } void VaryingPacking::collectVarying(const sh::ShaderVariable &varying, const ProgramVaryingRef &ref, PackMode packMode, VaryingUniqueFullNames *uniqueFullNames) { const sh::ShaderVariable *input = ref.frontShader; const sh::ShaderVariable *output = ref.backShader; if (varying.isStruct()) { std::vector arraySizes = StripVaryingArrayDimension( ref.frontShader, ref.frontShaderStage, ref.backShader, ref.backShaderStage, false); const bool isArray = !arraySizes.empty(); const GLuint arraySize = isArray ? arraySizes[0] : 1; for (GLuint arrayIndex = 0; arrayIndex < arraySize; ++arrayIndex) { const GLuint effectiveArrayIndex = isArray ? arrayIndex : GL_INVALID_INDEX; for (GLuint fieldIndex = 0; fieldIndex < varying.fields.size(); ++fieldIndex) { const sh::ShaderVariable &fieldVarying = varying.fields[fieldIndex]; if (ShouldSkipPackedVarying(fieldVarying, packMode)) { continue; } if (fieldVarying.isStruct()) { if (fieldVarying.isArray()) { unsigned int structFieldArraySize = fieldVarying.arraySizes[0]; for (unsigned int fieldArrayIndex = 0; fieldArrayIndex < structFieldArraySize; ++fieldArrayIndex) { for (GLuint nestedIndex = 0; nestedIndex < fieldVarying.fields.size(); nestedIndex++) { collectUserVaryingField(ref, effectiveArrayIndex, fieldIndex, nestedIndex, uniqueFullNames); } } } else { for (GLuint nestedIndex = 0; nestedIndex < fieldVarying.fields.size(); nestedIndex++) { collectUserVaryingField(ref, effectiveArrayIndex, fieldIndex, nestedIndex, uniqueFullNames); } } } else { collectUserVaryingField(ref, effectiveArrayIndex, fieldIndex, GL_INVALID_INDEX, uniqueFullNames); } } } if (input) { (*uniqueFullNames)[ref.frontShaderStage].insert(input->name); if (input->isShaderIOBlock) { (*uniqueFullNames)[ref.frontShaderStage].insert(input->structOrBlockName); } } if (output) { (*uniqueFullNames)[ref.backShaderStage].insert(output->name); } } else { collectUserVarying(ref, uniqueFullNames); } } void VaryingPacking::collectTFVarying(const std::string &tfVarying, const ProgramVaryingRef &ref, VaryingUniqueFullNames *uniqueFullNames) { const sh::ShaderVariable *input = ref.frontShader; std::vector subscripts; std::string baseName = ParseResourceName(tfVarying, &subscripts); // Already packed as active varying. if ((*uniqueFullNames)[ref.frontShaderStage].count(tfVarying) > 0 || (*uniqueFullNames)[ref.frontShaderStage].count(baseName) > 0 || (input->isShaderIOBlock && (*uniqueFullNames)[ref.frontShaderStage].count(input->structOrBlockName) > 0)) { return; } if (input->isStruct()) { GLuint fieldIndex = 0; const sh::ShaderVariable *field = input->findField(tfVarying, &fieldIndex); if (field != nullptr) { ASSERT(input->isShaderIOBlock || (!field->isStruct() && !field->isArray())); // If it's an I/O block whose member is being captured, pack every member of the // block. Currently, we pack either all or none of an I/O block. if (input->isShaderIOBlock) { for (fieldIndex = 0; fieldIndex < input->fields.size(); ++fieldIndex) { if (input->fields[fieldIndex].isStruct()) { for (GLuint nestedIndex = 0; nestedIndex < input->fields[fieldIndex].fields.size(); nestedIndex++) { collectUserVaryingFieldTF(ref, input->fields[fieldIndex], fieldIndex, nestedIndex); } } else { collectUserVaryingFieldTF(ref, input->fields[fieldIndex], fieldIndex, GL_INVALID_INDEX); } } (*uniqueFullNames)[ref.frontShaderStage].insert(input->structOrBlockName); } else { collectUserVaryingFieldTF(ref, *field, fieldIndex, GL_INVALID_INDEX); } (*uniqueFullNames)[ref.frontShaderStage].insert(tfVarying); (*uniqueFullNames)[ref.frontShaderStage].insert(input->name); } } // Array as a whole and array element conflict has already been checked in // linkValidateTransformFeedback. else if (baseName == input->name) { size_t subscript = GL_INVALID_INDEX; if (!subscripts.empty()) { subscript = subscripts.back(); } // only pack varyings that are not builtins. if (tfVarying.compare(0, 3, "gl_") != 0) { collectUserVaryingTF(ref, subscript); (*uniqueFullNames)[ref.frontShaderStage].insert(tfVarying); } } } bool VaryingPacking::collectAndPackUserVaryings(gl::InfoLog &infoLog, GLint maxVaryingVectors, PackMode packMode, ShaderType frontShaderStage, ShaderType backShaderStage, const ProgramMergedVaryings &mergedVaryings, const std::vector &tfVaryings, const bool isSeparableProgram) { VaryingUniqueFullNames uniqueFullNames; reset(); for (const ProgramVaryingRef &ref : mergedVaryings) { const sh::ShaderVariable *input = ref.frontShader; const sh::ShaderVariable *output = ref.backShader; if ((input && ref.frontShaderStage != frontShaderStage) || (output && ref.backShaderStage != backShaderStage)) { continue; } const bool isActiveBuiltInInput = input && input->isBuiltIn() && input->active; const bool isActiveBuiltInOutput = output && output->isBuiltIn() && output->active; // Keep track of output builtins that are used by the shader, such as gl_Position, // gl_PointSize etc. if (isActiveBuiltInInput) { mActiveOutputBuiltIns[ref.frontShaderStage].push_back(input->name); // Keep track of members of builtins, such as gl_out[].gl_Position, too. for (sh::ShaderVariable field : input->fields) { mActiveOutputBuiltIns[ref.frontShaderStage].push_back(field.name); } } // Only pack statically used varyings that have a matched input or output, plus special // builtins. Note that we pack all statically used user-defined varyings even if they are // not active. GLES specs are a bit vague on whether it's allowed to only pack active // varyings, though GLES 3.1 spec section 11.1.2.1 says that "device-dependent // optimizations" may be used to make vertex shader outputs fit. // // When separable programs are linked, varyings at the separable program's boundary are // treated as active. See section 7.4.1 in // https://www.khronos.org/registry/OpenGL/specs/es/3.2/es_spec_3.2.pdf bool matchedInputOutputStaticUse = (input && output && output->staticUse); bool activeBuiltIn = (isActiveBuiltInInput || isActiveBuiltInOutput); // Output variable in TCS can be read as input in another invocation by barrier. // See section 11.2.1.2.4 Tessellation Control Shader Execution Order in OpenGL ES 3.2. bool staticUseInTCS = (input && input->staticUse && ref.frontShaderStage == ShaderType::TessControl); // Separable program requirements bool separableActiveInput = (input && (input->active || !output)); bool separableActiveOutput = (output && (output->active || !input)); bool separableActiveVarying = (isSeparableProgram && (separableActiveInput || separableActiveOutput)); if (matchedInputOutputStaticUse || activeBuiltIn || separableActiveVarying || staticUseInTCS) { const sh::ShaderVariable *varying = output ? output : input; if (!ShouldSkipPackedVarying(*varying, packMode)) { collectVarying(*varying, ref, packMode, &uniqueFullNames); continue; } } // If the varying is not used in the input, we know it is inactive, unless it's a separable // program, in which case the input shader may not exist in this program. if (!input && !isSeparableProgram) { if (!output->isBuiltIn()) { mInactiveVaryingMappedNames[ref.backShaderStage].push_back(output->mappedName); if (output->isShaderIOBlock) { mInactiveVaryingMappedNames[ref.backShaderStage].push_back( output->mappedStructOrBlockName); } } continue; } // Keep Transform FB varyings in the merged list always. for (const std::string &tfVarying : tfVaryings) { collectTFVarying(tfVarying, ref, &uniqueFullNames); } if (input && !input->isBuiltIn() && uniqueFullNames[ref.frontShaderStage].count(input->name) == 0) { mInactiveVaryingMappedNames[ref.frontShaderStage].push_back(input->mappedName); if (input->isShaderIOBlock) { mInactiveVaryingMappedNames[ref.frontShaderStage].push_back( input->mappedStructOrBlockName); } } if (output && !output->isBuiltIn() && uniqueFullNames[ref.backShaderStage].count(output->name) == 0) { mInactiveVaryingMappedNames[ref.backShaderStage].push_back(output->mappedName); if (output->isShaderIOBlock) { mInactiveVaryingMappedNames[ref.backShaderStage].push_back( output->mappedStructOrBlockName); } } } std::sort(mPackedVaryings.begin(), mPackedVaryings.end(), ComparePackedVarying); return packUserVaryings(infoLog, maxVaryingVectors, packMode, mPackedVaryings); } // See comment on packVarying. bool VaryingPacking::packUserVaryings(gl::InfoLog &infoLog, GLint maxVaryingVectors, PackMode packMode, const std::vector &packedVaryings) { clearRegisterMap(); mRegisterMap.resize(maxVaryingVectors); // "Variables are packed into the registers one at a time so that they each occupy a contiguous // subrectangle. No splitting of variables is permitted." for (const PackedVarying &packedVarying : packedVaryings) { if (!packVaryingIntoRegisterMap(packMode, packedVarying)) { ShaderType eitherStage = packedVarying.frontVarying.varying ? packedVarying.frontVarying.stage : packedVarying.backVarying.stage; infoLog << "Could not pack varying " << packedVarying.fullName(eitherStage); // TODO(jmadill): Implement more sophisticated component packing in D3D9. if (packMode == PackMode::ANGLE_NON_CONFORMANT_D3D9) { infoLog << "Note: Additional non-conformant packing restrictions are enforced on " "D3D9."; } return false; } } // Sort the packed register list std::sort(mRegisterList.begin(), mRegisterList.end()); return true; } // ProgramVaryingPacking implementation. ProgramVaryingPacking::ProgramVaryingPacking() = default; ProgramVaryingPacking::~ProgramVaryingPacking() = default; const VaryingPacking &ProgramVaryingPacking::getInputPacking(ShaderType backShaderStage) const { ShaderType frontShaderStage = mBackToFrontStageMap[backShaderStage]; // If there's a missing shader stage, return the compute shader packing which is always empty. if (frontShaderStage == ShaderType::InvalidEnum) { ASSERT(mVaryingPackings[ShaderType::Compute].getMaxSemanticIndex() == 0); return mVaryingPackings[ShaderType::Compute]; } return mVaryingPackings[frontShaderStage]; } const VaryingPacking &ProgramVaryingPacking::getOutputPacking(ShaderType frontShaderStage) const { return mVaryingPackings[frontShaderStage]; } bool ProgramVaryingPacking::collectAndPackUserVaryings(InfoLog &infoLog, const Caps &caps, PackMode packMode, const ShaderBitSet &activeShadersMask, const ProgramMergedVaryings &mergedVaryings, const std::vector &tfVaryings, bool isSeparableProgram) { mBackToFrontStageMap.fill(ShaderType::InvalidEnum); ShaderBitSet activeShaders = activeShadersMask; ASSERT(activeShaders.any()); ShaderType frontShaderStage = activeShaders.first(); activeShaders[frontShaderStage] = false; // Special case for start-after-vertex. if (frontShaderStage != ShaderType::Vertex) { ShaderType emulatedFrontShaderStage = ShaderType::Vertex; ShaderType backShaderStage = frontShaderStage; if (!mVaryingPackings[emulatedFrontShaderStage].collectAndPackUserVaryings( infoLog, GetMaxShaderInputVectors(caps, backShaderStage), packMode, ShaderType::InvalidEnum, backShaderStage, mergedVaryings, tfVaryings, isSeparableProgram)) { return false; } mBackToFrontStageMap[backShaderStage] = emulatedFrontShaderStage; } // Process input/output shader pairs. for (ShaderType backShaderStage : activeShaders) { GLint maxVaryingVectors; if (frontShaderStage == ShaderType::Vertex && backShaderStage == ShaderType::Fragment) { maxVaryingVectors = caps.maxVaryingVectors; } else { GLint outputVaryingsMax = GetMaxShaderOutputVectors(caps, frontShaderStage); GLint inputVaryingsMax = GetMaxShaderInputVectors(caps, backShaderStage); maxVaryingVectors = std::min(inputVaryingsMax, outputVaryingsMax); } ASSERT(maxVaryingVectors > 0 && maxVaryingVectors < std::numeric_limits::max()); if (!mVaryingPackings[frontShaderStage].collectAndPackUserVaryings( infoLog, maxVaryingVectors, packMode, frontShaderStage, backShaderStage, mergedVaryings, tfVaryings, isSeparableProgram)) { return false; } mBackToFrontStageMap[backShaderStage] = frontShaderStage; frontShaderStage = backShaderStage; } // Special case for stop-before-fragment. if (frontShaderStage != ShaderType::Fragment) { if (!mVaryingPackings[frontShaderStage].collectAndPackUserVaryings( infoLog, GetMaxShaderOutputVectors(caps, frontShaderStage), packMode, frontShaderStage, ShaderType::InvalidEnum, mergedVaryings, tfVaryings, isSeparableProgram)) { return false; } ShaderType emulatedBackShaderStage = ShaderType::Fragment; mBackToFrontStageMap[emulatedBackShaderStage] = frontShaderStage; } return true; } ProgramMergedVaryings GetMergedVaryingsFromLinkingVariables( const LinkingVariables &linkingVariables) { ShaderType frontShaderType = ShaderType::InvalidEnum; ProgramMergedVaryings merged; for (ShaderType backShaderType : kAllGraphicsShaderTypes) { if (!linkingVariables.isShaderStageUsedBitset[backShaderType]) { continue; } const std::vector &backShaderOutputVaryings = linkingVariables.outputVaryings[backShaderType]; const std::vector &backShaderInputVaryings = linkingVariables.inputVaryings[backShaderType]; // Add outputs. These are always unmatched since we walk shader stages sequentially. for (const sh::ShaderVariable &frontVarying : backShaderOutputVaryings) { ProgramVaryingRef ref; ref.frontShader = &frontVarying; ref.frontShaderStage = backShaderType; merged.push_back(ref); } if (frontShaderType == ShaderType::InvalidEnum) { // If this is our first shader stage, and not a VS, we might have unmatched inputs. for (const sh::ShaderVariable &backVarying : backShaderInputVaryings) { ProgramVaryingRef ref; ref.backShader = &backVarying; ref.backShaderStage = backShaderType; merged.push_back(ref); } } else { // Match inputs with the prior shader stage outputs. for (const sh::ShaderVariable &backVarying : backShaderInputVaryings) { bool found = false; for (ProgramVaryingRef &ref : merged) { if (ref.frontShader && ref.frontShaderStage == frontShaderType && InterfaceVariablesMatch(*ref.frontShader, backVarying)) { ASSERT(ref.backShader == nullptr); ref.backShader = &backVarying; ref.backShaderStage = backShaderType; found = true; break; } } // Some outputs are never matched, e.g. some builtin variables. if (!found) { ProgramVaryingRef ref; ref.backShader = &backVarying; ref.backShaderStage = backShaderType; merged.push_back(ref); } } } // Save the current back shader to use as the next front shader. frontShaderType = backShaderType; } return merged; } } // namespace gl