TString TOutputGLSLBase::getTypeName(const TType& type) { TInfoSinkBase out; if (type.isMatrix()) { out << "mat"; out << type.getNominalSize(); } else if (type.isVector()) { switch (type.getBasicType()) { case EbtFloat: out << "vec"; break; case EbtInt: out << "ivec"; break; case EbtBool: out << "bvec"; break; default: UNREACHABLE(); break; } out << type.getNominalSize(); } else { if (type.getBasicType() == EbtStruct) out << hashName(type.getTypeName()); else out << type.getBasicString(); } return TString(out.c_str()); }
// Accumulate locations used for inputs, outputs, and uniforms, and check for collisions // as the accumulation is done. // // Returns < 0 if no collision, >= 0 if collision and the value returned is a colliding value. // // typeCollision is set to true if there is no direct collision, but the types in the same location // are different. // int TIntermediate::addUsedLocation(const TQualifier& qualifier, const TType& type, bool& typeCollision) { typeCollision = false; int set; if (qualifier.isPipeInput()) set = 0; else if (qualifier.isPipeOutput()) set = 1; else if (qualifier.storage == EvqUniform) set = 2; else if (qualifier.storage == EvqBuffer) set = 3; else return -1; int size; if (qualifier.isUniformOrBuffer()) { if (type.isArray()) size = type.getCumulativeArraySize(); else size = 1; } else { // Strip off the outer array dimension for those having an extra one. if (type.isArray() && qualifier.isArrayedIo(language)) { TType elementType(type, 0); size = computeTypeLocationSize(elementType); } else size = computeTypeLocationSize(type); } TRange locationRange(qualifier.layoutLocation, qualifier.layoutLocation + size - 1); TRange componentRange(0, 3); if (qualifier.hasComponent()) { componentRange.start = qualifier.layoutComponent; componentRange.last = componentRange.start + type.getVectorSize() - 1; } TIoRange range(locationRange, componentRange, type.getBasicType(), qualifier.hasIndex() ? qualifier.layoutIndex : 0); // check for collisions, except for vertex inputs on desktop if (! (profile != EEsProfile && language == EShLangVertex && qualifier.isPipeInput())) { for (size_t r = 0; r < usedIo[set].size(); ++r) { if (range.overlap(usedIo[set][r])) { // there is a collision; pick one return std::max(locationRange.start, usedIo[set][r].location.start); } else if (locationRange.overlap(usedIo[set][r].location) && type.getBasicType() != usedIo[set][r].basicType) { // aliased-type mismatch typeCollision = true; return std::max(locationRange.start, usedIo[set][r].location.start); } } } usedIo[set].push_back(range); return -1; // no collision }
bool TParseContext::parameterSamplerErrorCheck(int line, TQualifier qualifier, const TType& type) { if ((qualifier == EvqOut || qualifier == EvqInOut) && type.getBasicType() != EbtStruct && IsSampler(type.getBasicType())) { error(line, "samplers cannot be output parameters", type.getBasicString(), ""); return true; } return false; }
void UniformHLSL::outputHLSL4_0_FL9_3Sampler(TInfoSinkBase &out, const TType &type, const TName &name, const unsigned int registerIndex) { out << "uniform " << SamplerString(type.getBasicType()) << " sampler_" << DecorateUniform(name, type) << ArrayString(type) << " : register(s" << str(registerIndex) << ");\n"; out << "uniform " << TextureString(type.getBasicType()) << " texture_" << DecorateUniform(name, type) << ArrayString(type) << " : register(t" << str(registerIndex) << ");\n"; }
bool TParseContext::containsSampler(TType& type) { if (IsSampler(type.getBasicType())) return true; if (type.getBasicType() == EbtStruct) { TTypeList& structure = *type.getStruct(); for (unsigned int i = 0; i < structure.size(); ++i) { if (containsSampler(*structure[i].type)) return true; } } return false; }
TString TOutputGLSLBase::getTypeName(const TType &type) { if (type.getBasicType() == EbtStruct) return hashName(type.getStruct()->name()); else return type.getBuiltInTypeNameString(); }
void TOutputGLSLBase::writeVariableType(const TType& type) { TInfoSinkBase& out = objSink(); TQualifier qualifier = type.getQualifier(); // TODO(alokp): Validate qualifier for variable declarations. if ((qualifier != EvqTemporary) && (qualifier != EvqGlobal)) out << type.getQualifierString() << " "; // Declare the struct if we have not done so already. if ((type.getBasicType() == EbtStruct) && (mDeclaredStructs.find(type.getTypeName()) == mDeclaredStructs.end())) { out << "struct " << type.getTypeName() << "{\n"; const TTypeList* structure = type.getStruct(); ASSERT(structure != NULL); for (size_t i = 0; i < structure->size(); ++i) { const TType* fieldType = (*structure)[i].type; ASSERT(fieldType != NULL); if (writeVariablePrecision(fieldType->getPrecision())) out << " "; out << getTypeName(*fieldType) << " " << fieldType->getFieldName(); if (fieldType->isArray()) out << arrayBrackets(*fieldType); out << ";\n"; } out << "}"; mDeclaredStructs.insert(type.getTypeName()); } else { if (writeVariablePrecision(type.getPrecision())) out << " "; out << getTypeName(type); } }
TString TextureString(const TType &type) { switch (type.getBasicType()) { case EbtSampler2D: return "Texture2D"; case EbtSamplerCube: return "TextureCube"; case EbtSamplerExternalOES: return "Texture2D"; case EbtSampler2DArray: return "Texture2DArray"; case EbtSampler3D: return "Texture3D"; case EbtISampler2D: return "Texture2D<int4>"; case EbtISampler3D: return "Texture3D<int4>"; case EbtISamplerCube: return "Texture2DArray<int4>"; case EbtISampler2DArray: return "Texture2DArray<int4>"; case EbtUSampler2D: return "Texture2D<uint4>"; case EbtUSampler3D: return "Texture3D<uint4>"; case EbtUSamplerCube: return "Texture2DArray<uint4>"; case EbtUSampler2DArray: return "Texture2DArray<uint4>"; case EbtSampler2DShadow: return "Texture2D"; case EbtSamplerCubeShadow: return "TextureCube"; case EbtSampler2DArrayShadow: return "Texture2DArray"; default: UNREACHABLE(); } return "<unknown texture type>"; }
void TOutputGLSLBase::writeVariableType(const TType &type) { TInfoSinkBase &out = objSink(); TQualifier qualifier = type.getQualifier(); if (qualifier != EvqTemporary && qualifier != EvqGlobal) { out << type.getQualifierString() << " "; } // Declare the struct if we have not done so already. if (type.getBasicType() == EbtStruct && !structDeclared(type.getStruct())) { TStructure *structure = type.getStruct(); declareStruct(structure); if (!structure->name().empty()) { mDeclaredStructs.insert(structure->uniqueId()); } } else { if (writeVariablePrecision(type.getPrecision())) out << " "; out << getTypeName(type); } }
unsigned int UniformHLSL::assignUniformRegister(const TType &type, const TString &name, unsigned int *outRegisterCount) { unsigned int registerIndex = (IsSampler(type.getBasicType()) ? mSamplerRegister : mUniformRegister); const Uniform *uniform = findUniformByName(name); ASSERT(uniform); mUniformRegisterMap[uniform->name] = registerIndex; unsigned int registerCount = HLSLVariableRegisterCount(*uniform, mOutputType); if (gl::IsSamplerType(uniform->type)) { mSamplerRegister += registerCount; } else { mUniformRegister += registerCount; } if (outRegisterCount) { *outRegisterCount = registerCount; } return registerIndex; }
// Recursively figure out how many bytes of xfb buffer are used by the given type. // Return the size of type, in bytes. // Sets containsDouble to true if the type contains a double. // N.B. Caller must set containsDouble to false before calling. unsigned int TIntermediate::computeTypeXfbSize(const TType& type, bool& containsDouble) const { // "...if applied to an aggregate containing a double, the offset must also be a multiple of 8, // and the space taken in the buffer will be a multiple of 8. // ...within the qualified entity, subsequent components are each // assigned, in order, to the next available offset aligned to a multiple of // that component's size. Aggregate types are flattened down to the component // level to get this sequence of components." if (type.isArray()) { // TODO: perf: this can be flattened by using getCumulativeArraySize(), and a deref that discards all arrayness assert(type.isExplicitlySizedArray()); TType elementType(type, 0); return type.getOuterArraySize() * computeTypeXfbSize(elementType, containsDouble); } if (type.isStruct()) { unsigned int size = 0; bool structContainsDouble = false; for (int member = 0; member < (int)type.getStruct()->size(); ++member) { TType memberType(type, member); // "... if applied to // an aggregate containing a double, the offset must also be a multiple of 8, // and the space taken in the buffer will be a multiple of 8." bool memberContainsDouble = false; int memberSize = computeTypeXfbSize(memberType, memberContainsDouble); if (memberContainsDouble) { structContainsDouble = true; RoundToPow2(size, 8); } size += memberSize; } if (structContainsDouble) { containsDouble = true; RoundToPow2(size, 8); } return size; } int numComponents; if (type.isScalar()) numComponents = 1; else if (type.isVector()) numComponents = type.getVectorSize(); else if (type.isMatrix()) numComponents = type.getMatrixCols() * type.getMatrixRows(); else { assert(0); numComponents = 1; } if (type.getBasicType() == EbtDouble) { containsDouble = true; return 8 * numComponents; } else return 4 * numComponents; }
// Return the size and alignment of a scalar. // The size is returned in the 'size' parameter // Return value is the alignment of the type. int TIntermediate::getBaseAlignmentScalar(const TType& type, int& size) { switch (type.getBasicType()) { case EbtInt64: case EbtUint64: case EbtDouble: size = 8; return 8; default: size = 4; return 4; } }
TString DecorateUniform(const TString &string, const TType &type) { if (type.getBasicType() == EbtSamplerExternalOES) { return "ex_" + string; } return Decorate(string); }
TString DecorateUniform(const TName &name, const TType &type) { if (type.getBasicType() == EbtSamplerExternalOES) { return "ex_" + name.getString(); } return DecorateIfNeeded(name); }
int UniformHLSL::declareUniformAndAssignRegister(const TType &type, const TString &name) { int registerIndex = (IsSampler(type.getBasicType()) ? mSamplerRegister : mUniformRegister); declareUniformToList(type, name, registerIndex, &mActiveUniforms); unsigned int registerCount = HLSLVariableRegisterCount(mActiveUniforms.back(), mOutputType); if (IsSampler(type.getBasicType())) { mSamplerRegister += registerCount; } else { mUniformRegister += registerCount; } return registerIndex; }
TString SamplerString(const TType &type) { if (IsShadowSampler(type.getBasicType())) { return "SamplerComparisonState"; } else { return "SamplerState"; } }
void TOutputVulkanGLSL::writeVariableType(const TType &type, const TSymbol *symbol) { TType overrideType(type); // External textures are treated as 2D textures in the vulkan back-end if (type.getBasicType() == EbtSamplerExternalOES) { overrideType.setBasicType(EbtSampler2D); } TOutputGLSL::writeVariableType(overrideType, symbol); }
// Recursively figure out how many locations are used up by an input or output type. // Return the size of type, as measured by "locations". int TIntermediate::computeTypeLocationSize(const TType& type) const { // "If the declared input is an array of size n and each element takes m locations, it will be assigned m * n // consecutive locations..." if (type.isArray()) { // TODO: perf: this can be flattened by using getCumulativeArraySize(), and a deref that discards all arrayness TType elementType(type, 0); if (type.isImplicitlySizedArray()) { // TODO: are there valid cases of having an implicitly-sized array with a location? If so, running this code too early. return computeTypeLocationSize(elementType); } else return type.getOuterArraySize() * computeTypeLocationSize(elementType); } // "The locations consumed by block and structure members are determined by applying the rules above // recursively..." if (type.isStruct()) { int size = 0; for (int member = 0; member < (int)type.getStruct()->size(); ++member) { TType memberType(type, member); size += computeTypeLocationSize(memberType); } return size; } // ES: "If a shader input is any scalar or vector type, it will consume a single location." // Desktop: "If a vertex shader input is any scalar or vector type, it will consume a single location. If a non-vertex // shader input is a scalar or vector type other than dvec3 or dvec4, it will consume a single location, while // types dvec3 or dvec4 will consume two consecutive locations. Inputs of type double and dvec2 will // consume only a single location, in all stages." if (type.isScalar()) return 1; if (type.isVector()) { if (language == EShLangVertex && type.getQualifier().isPipeInput()) return 1; if (type.getBasicType() == EbtDouble && type.getVectorSize() > 2) return 2; else return 1; } // "If the declared input is an n x m single- or double-precision matrix, ... // The number of locations assigned for each matrix will be the same as // for an n-element array of m-component vectors..." if (type.isMatrix()) { TType columnType(type, 0); return type.getMatrixCols() * computeTypeLocationSize(columnType); } assert(0); return 1; }
void TOutputGLSLBase::writeVariableType(const TType &type) { TInfoSinkBase &out = objSink(); if (type.isInvariant()) { out << "invariant "; } TQualifier qualifier = type.getQualifier(); if (qualifier != EvqTemporary && qualifier != EvqGlobal) { if (IsGLSL130OrNewer(mOutput)) { switch (qualifier) { case EvqAttribute: out << "in "; break; case EvqVaryingIn: out << "in "; break; case EvqVaryingOut: out << "out "; break; default: out << type.getQualifierString() << " "; break; } } else { out << type.getQualifierString() << " "; } } // Declare the struct if we have not done so already. if (type.getBasicType() == EbtStruct && !structDeclared(type.getStruct())) { TStructure *structure = type.getStruct(); declareStruct(structure); if (!structure->name().empty()) { mDeclaredStructs.insert(structure->uniqueId()); } } else { if (writeVariablePrecision(type.getPrecision())) out << " "; out << getTypeName(type); } }
GLenum GLVariablePrecision(const TType &type) { if (type.getBasicType() == EbtFloat) { switch (type.getPrecision()) { case EbpHigh: return GL_HIGH_FLOAT; case EbpMedium: return GL_MEDIUM_FLOAT; case EbpLow: return GL_LOW_FLOAT; case EbpUndefined: // Should be defined as the default precision by the parser default: UNREACHABLE(); } } else if (type.getBasicType() == EbtInt || type.getBasicType() == EbtUInt) { switch (type.getPrecision()) { case EbpHigh: return GL_HIGH_INT; case EbpMedium: return GL_MEDIUM_INT; case EbpLow: return GL_LOW_INT; case EbpUndefined: // Should be defined as the default precision by the parser default: UNREACHABLE(); } } // Other types (boolean, sampler) don't have a precision return GL_NONE; }
TString ScalarizeVecAndMatConstructorArgs::createTempVariable(TIntermTyped *original) { TString tempVarName = "_webgl_tmp_"; if (original->isScalar()) { tempVarName += "scalar_"; } else if (original->isVector()) { tempVarName += "vec_"; } else { ASSERT(original->isMatrix()); tempVarName += "mat_"; } tempVarName += Str(mTempVarCount).c_str(); mTempVarCount++; ASSERT(original); TType type = original->getType(); type.setQualifier(EvqTemporary); if (mShaderType == GL_FRAGMENT_SHADER && type.getBasicType() == EbtFloat && type.getPrecision() == EbpUndefined) { // We use the highest available precision for the temporary variable // to avoid computing the actual precision using the rules defined // in GLSL ES 1.0 Section 4.5.2. type.setPrecision(mFragmentPrecisionHigh ? EbpHigh : EbpMedium); } TIntermBinary *init = new TIntermBinary(EOpInitialize); TIntermSymbol *symbolNode = new TIntermSymbol(-1, tempVarName, type); init->setLeft(symbolNode); init->setRight(original); init->setType(type); TIntermAggregate *decl = new TIntermAggregate(EOpDeclaration); decl->getSequence()->push_back(init); ASSERT(mSequenceStack.size() > 0); TIntermSequence &sequence = mSequenceStack.back(); sequence.push_back(decl); return tempVarName; }
unsigned int UniformHLSL::assignSamplerInStructUniformRegister(const TType &type, const TString &name, unsigned int *outRegisterCount) { // Sampler that is a field of a uniform structure. ASSERT(IsSampler(type.getBasicType())); unsigned int registerIndex = mSamplerRegister; mUniformRegisterMap[std::string(name.c_str())] = registerIndex; unsigned int registerCount = type.isArray() ? type.getArraySize() : 1u; mSamplerRegister += registerCount; if (outRegisterCount) { *outRegisterCount = registerCount; } return registerIndex; }
TString Std140PaddingHelper::postPaddingString(const TType &type, bool useHLSLRowMajorPacking) { if (!type.isMatrix() && !type.isArray() && type.getBasicType() != EbtStruct) { return ""; } int numComponents = 0; TStructure *structure = type.getStruct(); if (type.isMatrix()) { // This method can also be called from structureString, which does not use layout qualifiers. // Thus, use the method parameter for determining the matrix packing. // // Note HLSL row major packing corresponds to GL API column-major, and vice-versa, since we // wish to always transpose GL matrices to play well with HLSL's matrix array indexing. // const bool isRowMajorMatrix = !useHLSLRowMajorPacking; const GLenum glType = GLVariableType(type); numComponents = gl::MatrixComponentCount(glType, isRowMajorMatrix); } else if (structure) { const TString &structName = QualifiedStructNameString(*structure, useHLSLRowMajorPacking, true); numComponents = mStructElementIndexes->find(structName)->second; if (numComponents == 0) { return ""; } } else { const GLenum glType = GLVariableType(type); numComponents = gl::VariableComponentCount(glType); } TString padding; for (int paddingOffset = numComponents; paddingOffset < 4; paddingOffset++) { padding += " float pad_" + next() + ";\n"; } return padding; }
void OutputParameter(TIntermParameter* node, TIntermTraverser* it) { TOutputTraverser* oit = static_cast<TOutputTraverser*>(it); TInfoSink& out = oit->infoSink; OutputExtensionText(out, node); OutputTreeText(out, node, oit->depth); TType* t = node->getType(); if (t->getBasicType() != EbtStruct) { out.debug << "param '" << t->getFieldName() << "' (" << t->getCompleteString() << ")\n"; } else { out.debug << "param '" << t->getFieldName() << "' (" << t->getCompleteString() << " '" << t->getTypeName() << "')\n"; } }
void TOutputGLSLBase::writeVariableType(const TType& type) { TInfoSinkBase& out = objSink(); TQualifier qualifier = type.getQualifier(); // TODO(alokp): Validate qualifier for variable declarations. if ((qualifier != EvqTemporary) && (qualifier != EvqGlobal)) out << type.getQualifierString() << " "; // Declare the struct if we have not done so already. if ((type.getBasicType() == EbtStruct) && !structDeclared(type.getStruct())) { declareStruct(type.getStruct()); } else { if (writeVariablePrecision(type.getPrecision())) out << " "; out << getTypeName(type); } }
bool TParseContext::structNestingErrorCheck(TSourceLoc line, const TType& fieldType) { if (shaderSpec != SH_WEBGL_SPEC) { return false; } if (fieldType.getBasicType() != EbtStruct) { return false; } // We're already inside a structure definition at this point, so add // one to the field's struct nesting. if (1 + fieldType.getDeepestStructNesting() > kWebGLMaxStructNesting) { error(line, "", "", "Reference of struct type %s exceeds maximum struct nesting of %d", fieldType.getTypeName().c_str(), kWebGLMaxStructNesting); return true; } return false; }
// TODO(jmadill): This is not complete. void TOutputVulkanGLSL::writeLayoutQualifier(const TType &type) { TInfoSinkBase &out = objSink(); const TLayoutQualifier &layoutQualifier = type.getLayoutQualifier(); out << "layout("; if (type.getQualifier() == EvqAttribute || type.getQualifier() == EvqFragmentOut || type.getQualifier() == EvqVertexIn) { // TODO(jmadill): Multiple output locations. out << "location = " << "0"; } if (IsImage(type.getBasicType()) && layoutQualifier.imageInternalFormat != EiifUnspecified) { ASSERT(type.getQualifier() == EvqTemporary || type.getQualifier() == EvqUniform); out << getImageInternalFormatString(layoutQualifier.imageInternalFormat); } out << ") "; }
bool TParseContext::structNestingErrorCheck(TSourceLoc line, const TType& fieldType) { if (!isWebGLBasedSpec(shaderSpec)) { return false; } if (fieldType.getBasicType() != EbtStruct) { return false; } // We're already inside a structure definition at this point, so add // one to the field's struct nesting. if (1 + fieldType.getDeepestStructNesting() > kWebGLMaxStructNesting) { std::stringstream extraInfoStream; extraInfoStream << "Reference of struct type " << fieldType.getTypeName() << " exceeds maximum struct nesting of " << kWebGLMaxStructNesting; std::string extraInfo = extraInfoStream.str(); error(line, "", "", extraInfo.c_str()); return true; } return false; }
int Std140PaddingHelper::prePadding(const TType &type) { if (type.getBasicType() == EbtStruct || type.isMatrix() || type.isArray()) { // no padding needed, HLSL will align the field to a new register mElementIndex = 0; return 0; } const GLenum glType = GLVariableType(type); const int numComponents = gl::VariableComponentCount(glType); if (numComponents >= 4) { // no padding needed, HLSL will align the field to a new register mElementIndex = 0; return 0; } if (mElementIndex + numComponents > 4) { // no padding needed, HLSL will align the field to a new register mElementIndex = numComponents; return 0; } const int alignment = numComponents == 3 ? 4 : numComponents; const int paddingOffset = (mElementIndex % alignment); const int paddingCount = (paddingOffset != 0 ? (alignment - paddingOffset) : 0); mElementIndex += paddingCount; mElementIndex += numComponents; mElementIndex %= 4; return paddingCount; }
GLenum GLVariableType(const TType &type) { if (type.getBasicType() == EbtFloat) { if (type.isScalar()) { return GL_FLOAT; } else if (type.isVector()) { switch (type.getNominalSize()) { case 2: return GL_FLOAT_VEC2; case 3: return GL_FLOAT_VEC3; case 4: return GL_FLOAT_VEC4; default: UNREACHABLE(); } } else if (type.isMatrix()) { switch (type.getCols()) { case 2: switch (type.getRows()) { case 2: return GL_FLOAT_MAT2; case 3: return GL_FLOAT_MAT2x3; case 4: return GL_FLOAT_MAT2x4; default: UNREACHABLE(); } case 3: switch (type.getRows()) { case 2: return GL_FLOAT_MAT3x2; case 3: return GL_FLOAT_MAT3; case 4: return GL_FLOAT_MAT3x4; default: UNREACHABLE(); } case 4: switch (type.getRows()) { case 2: return GL_FLOAT_MAT4x2; case 3: return GL_FLOAT_MAT4x3; case 4: return GL_FLOAT_MAT4; default: UNREACHABLE(); } default: UNREACHABLE(); } } else UNREACHABLE(); } else if (type.getBasicType() == EbtInt) { if (type.isScalar()) { return GL_INT; } else if (type.isVector()) { switch (type.getNominalSize()) { case 2: return GL_INT_VEC2; case 3: return GL_INT_VEC3; case 4: return GL_INT_VEC4; default: UNREACHABLE(); } } else UNREACHABLE(); } else if (type.getBasicType() == EbtUInt) { if (type.isScalar()) { return GL_UNSIGNED_INT; } else if (type.isVector()) { switch (type.getNominalSize()) { case 2: return GL_UNSIGNED_INT_VEC2; case 3: return GL_UNSIGNED_INT_VEC3; case 4: return GL_UNSIGNED_INT_VEC4; default: UNREACHABLE(); } } else UNREACHABLE(); } else if (type.getBasicType() == EbtBool) { if (type.isScalar()) { return GL_BOOL; } else if (type.isVector()) { switch (type.getNominalSize()) { case 2: return GL_BOOL_VEC2; case 3: return GL_BOOL_VEC3; case 4: return GL_BOOL_VEC4; default: UNREACHABLE(); } } else UNREACHABLE(); } switch (type.getBasicType()) { case EbtSampler2D: return GL_SAMPLER_2D; case EbtSampler3D: return GL_SAMPLER_3D; case EbtSamplerCube: return GL_SAMPLER_CUBE; case EbtSamplerExternalOES: return GL_SAMPLER_EXTERNAL_OES; case EbtSampler2DRect: return GL_SAMPLER_2D_RECT_ARB; case EbtSampler2DArray: return GL_SAMPLER_2D_ARRAY; case EbtISampler2D: return GL_INT_SAMPLER_2D; case EbtISampler3D: return GL_INT_SAMPLER_3D; case EbtISamplerCube: return GL_INT_SAMPLER_CUBE; case EbtISampler2DArray: return GL_INT_SAMPLER_2D_ARRAY; case EbtUSampler2D: return GL_UNSIGNED_INT_SAMPLER_2D; case EbtUSampler3D: return GL_UNSIGNED_INT_SAMPLER_3D; case EbtUSamplerCube: return GL_UNSIGNED_INT_SAMPLER_CUBE; case EbtUSampler2DArray: return GL_UNSIGNED_INT_SAMPLER_2D_ARRAY; case EbtSampler2DShadow: return GL_SAMPLER_2D_SHADOW; case EbtSamplerCubeShadow: return GL_SAMPLER_CUBE_SHADOW; case EbtSampler2DArrayShadow: return GL_SAMPLER_2D_ARRAY_SHADOW; default: UNREACHABLE(); } return GL_NONE; }