blob: 732e9a350e157f2700bdaa9ff145445473330239 [file] [log] [blame]
//
// Copyright 2020 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.
//
// ProgramExecutable.h: Collects the information and interfaces common to both Programs and
// ProgramPipelines in order to execute/draw with either.
#ifndef LIBANGLE_PROGRAMEXECUTABLE_H_
#define LIBANGLE_PROGRAMEXECUTABLE_H_
#include "common/BinaryStream.h"
#include "libANGLE/Caps.h"
#include "libANGLE/InfoLog.h"
#include "libANGLE/ProgramLinkedResources.h"
#include "libANGLE/Shader.h"
#include "libANGLE/Uniform.h"
#include "libANGLE/VaryingPacking.h"
#include "libANGLE/angletypes.h"
namespace gl
{
// This small structure encapsulates binding sampler uniforms to active GL textures.
ANGLE_ENABLE_STRUCT_PADDING_WARNINGS
struct SamplerBinding
{
SamplerBinding() = default;
SamplerBinding(TextureType textureTypeIn,
GLenum samplerTypeIn,
SamplerFormat formatIn,
uint16_t startIndex,
uint16_t elementCount)
: textureType(textureTypeIn),
format(formatIn),
textureUnitsStartIndex(startIndex),
textureUnitsCount(elementCount)
{
SetBitField(samplerType, samplerTypeIn);
}
~SamplerBinding() = default;
GLuint getTextureUnit(const std::vector<GLuint> &boundTextureUnits,
unsigned int arrayIndex) const
{
return boundTextureUnits[textureUnitsStartIndex + arrayIndex];
}
// Necessary for retrieving active textures from the GL state.
TextureType textureType;
SamplerFormat format;
uint16_t samplerType;
// [textureUnitsStartIndex, textureUnitsStartIndex+textureUnitsCount) Points to the subset in
// mSamplerBoundTextureUnits that stores the texture unit bound to this sampler. Cropped by the
// amount of unused elements reported by the driver.
uint16_t textureUnitsStartIndex;
uint16_t textureUnitsCount;
};
static_assert(std::is_trivially_copyable<SamplerBinding>(), "must be memcpy-able");
ANGLE_DISABLE_STRUCT_PADDING_WARNINGS
struct ImageBinding
{
ImageBinding();
ImageBinding(size_t count, TextureType textureTypeIn);
ImageBinding(GLuint imageUnit, size_t count, TextureType textureTypeIn);
ImageBinding(const ImageBinding &other);
~ImageBinding();
// Necessary for distinguishing between textures with images and texture buffers.
TextureType textureType;
// List of all textures bound.
// Cropped by the amount of unused elements reported by the driver.
std::vector<GLuint> boundImageUnits;
};
ANGLE_ENABLE_STRUCT_PADDING_WARNINGS
struct ProgramInput
{
ProgramInput();
ProgramInput(const sh::ShaderVariable &var);
ProgramInput(const ProgramInput &other);
ProgramInput &operator=(const ProgramInput &rhs);
GLenum getType() const { return basicDataTypeStruct.type; }
bool isBuiltIn() const { return basicDataTypeStruct.flagBits.isBuiltIn; }
bool isArray() const { return basicDataTypeStruct.flagBits.isArray; }
bool isActive() const { return basicDataTypeStruct.flagBits.active; }
bool isPatch() const { return basicDataTypeStruct.flagBits.isPatch; }
int getLocation() const { return basicDataTypeStruct.location; }
unsigned int getBasicTypeElementCount() const
{
return basicDataTypeStruct.basicTypeElementCount;
}
unsigned int getArraySizeProduct() const { return basicDataTypeStruct.arraySizeProduct; }
uint32_t getId() const { return basicDataTypeStruct.id; }
sh::InterpolationType getInterpolation() const
{
return static_cast<sh::InterpolationType>(basicDataTypeStruct.interpolation);
}
void setLocation(int location) { basicDataTypeStruct.location = location; }
void resetEffectiveLocation()
{
if (basicDataTypeStruct.flagBits.hasImplicitLocation)
{
basicDataTypeStruct.location = -1;
}
}
std::string name;
std::string mappedName;
// The struct bellow must only contain data of basic type so that entire struct can memcpy-able.
struct PODStruct
{
uint16_t type; // GLenum
uint16_t arraySizeProduct;
int location;
uint8_t interpolation; // sh::InterpolationType
union
{
struct
{
uint8_t active : 1;
uint8_t isPatch : 1;
uint8_t hasImplicitLocation : 1;
uint8_t isArray : 1;
uint8_t isBuiltIn : 1;
uint8_t padding : 3;
} flagBits;
uint8_t flagBitsAsUByte;
};
int16_t basicTypeElementCount;
uint32_t id;
} basicDataTypeStruct;
static_assert(std::is_trivially_copyable<PODStruct>(), "must be memcpy-able");
};
ANGLE_DISABLE_STRUCT_PADDING_WARNINGS
// A varying with transform feedback enabled. If it's an array, either the whole array or one of its
// elements specified by 'arrayIndex' can set to be enabled.
struct TransformFeedbackVarying : public sh::ShaderVariable
{
TransformFeedbackVarying() = default;
TransformFeedbackVarying(const sh::ShaderVariable &varyingIn, GLuint arrayIndexIn)
: sh::ShaderVariable(varyingIn), arrayIndex(arrayIndexIn)
{
ASSERT(!isArrayOfArrays());
}
TransformFeedbackVarying(const sh::ShaderVariable &field, const sh::ShaderVariable &parent)
: arrayIndex(GL_INVALID_INDEX)
{
sh::ShaderVariable *thisVar = this;
*thisVar = field;
interpolation = parent.interpolation;
isInvariant = parent.isInvariant;
ASSERT(parent.isShaderIOBlock || !parent.name.empty());
if (!parent.name.empty())
{
name = parent.name + "." + name;
mappedName = parent.mappedName + "." + mappedName;
}
structOrBlockName = parent.structOrBlockName;
mappedStructOrBlockName = parent.mappedStructOrBlockName;
}
std::string nameWithArrayIndex() const
{
std::stringstream fullNameStr;
fullNameStr << name;
if (arrayIndex != GL_INVALID_INDEX)
{
fullNameStr << "[" << arrayIndex << "]";
}
return fullNameStr.str();
}
GLsizei size() const
{
return (isArray() && arrayIndex == GL_INVALID_INDEX ? getOutermostArraySize() : 1);
}
GLuint arrayIndex;
};
class ProgramState;
class ProgramPipelineState;
class ProgramExecutable final : public angle::Subject
{
public:
ProgramExecutable();
ProgramExecutable(const ProgramExecutable &other);
~ProgramExecutable() override;
void reset(bool clearInfoLog);
void save(bool isSeparable, gl::BinaryOutputStream *stream) const;
void load(bool isSeparable, gl::BinaryInputStream *stream);
int getInfoLogLength() const;
InfoLog &getInfoLog() { return mInfoLog; }
void getInfoLog(GLsizei bufSize, GLsizei *length, char *infoLog) const;
std::string getInfoLogString() const;
void resetInfoLog() { mInfoLog.reset(); }
void resetLinkedShaderStages() { mPODStruct.linkedShaderStages.reset(); }
const ShaderBitSet getLinkedShaderStages() const { return mPODStruct.linkedShaderStages; }
void setLinkedShaderStages(ShaderType shaderType)
{
mPODStruct.linkedShaderStages.set(shaderType);
updateCanDrawWith();
}
bool hasLinkedShaderStage(ShaderType shaderType) const
{
ASSERT(shaderType != ShaderType::InvalidEnum);
return mPODStruct.linkedShaderStages[shaderType];
}
size_t getLinkedShaderStageCount() const { return mPODStruct.linkedShaderStages.count(); }
bool hasLinkedGraphicsShader() const
{
return mPODStruct.linkedShaderStages.any() &&
mPODStruct.linkedShaderStages != gl::ShaderBitSet{gl::ShaderType::Compute};
}
bool hasLinkedTessellationShader() const
{
return mPODStruct.linkedShaderStages[ShaderType::TessEvaluation];
}
ShaderType getLinkedTransformFeedbackStage() const
{
return GetLastPreFragmentStage(mPODStruct.linkedShaderStages);
}
const AttributesMask &getActiveAttribLocationsMask() const
{
return mPODStruct.activeAttribLocationsMask;
}
bool isAttribLocationActive(size_t attribLocation) const
{
ASSERT(attribLocation < mPODStruct.activeAttribLocationsMask.size());
return mPODStruct.activeAttribLocationsMask[attribLocation];
}
AttributesMask getNonBuiltinAttribLocationsMask() const { return mPODStruct.attributesMask; }
unsigned int getMaxActiveAttribLocation() const { return mPODStruct.maxActiveAttribLocation; }
ComponentTypeMask getAttributesTypeMask() const { return mPODStruct.attributesTypeMask; }
AttributesMask getAttributesMask() const { return mPODStruct.attributesMask; }
const ActiveTextureMask &getActiveSamplersMask() const { return mActiveSamplersMask; }
void setActiveTextureMask(ActiveTextureMask mask) { mActiveSamplersMask = mask; }
SamplerFormat getSamplerFormatForTextureUnitIndex(size_t textureUnitIndex) const
{
return mActiveSamplerFormats[textureUnitIndex];
}
const ShaderBitSet getSamplerShaderBitsForTextureUnitIndex(size_t textureUnitIndex) const
{
return mActiveSamplerShaderBits[textureUnitIndex];
}
const ActiveTextureMask &getActiveImagesMask() const { return mActiveImagesMask; }
void setActiveImagesMask(ActiveTextureMask mask) { mActiveImagesMask = mask; }
const ActiveTextureArray<ShaderBitSet> &getActiveImageShaderBits() const
{
return mActiveImageShaderBits;
}
const ActiveTextureMask &getActiveYUVSamplers() const { return mActiveSamplerYUV; }
const ActiveTextureArray<TextureType> &getActiveSamplerTypes() const
{
return mActiveSamplerTypes;
}
void setActive(size_t textureUnit,
const SamplerBinding &samplerBinding,
const gl::LinkedUniform &samplerUniform);
void setInactive(size_t textureUnit);
void hasSamplerTypeConflict(size_t textureUnit);
void hasSamplerFormatConflict(size_t textureUnit);
void updateActiveSamplers(const ProgramState &programState);
bool hasDefaultUniforms() const { return !getDefaultUniformRange().empty(); }
bool hasTextures() const { return !getSamplerBindings().empty(); }
bool hasUniformBuffers() const { return !mUniformBlocks.empty(); }
bool hasStorageBuffers() const { return !mShaderStorageBlocks.empty(); }
bool hasAtomicCounterBuffers() const { return !mAtomicCounterBuffers.empty(); }
bool hasImages() const { return !mImageBindings.empty(); }
bool hasTransformFeedbackOutput() const
{
return !getLinkedTransformFeedbackVaryings().empty();
}
bool usesFramebufferFetch() const { return (mPODStruct.fragmentInoutRange.length() > 0); }
// Count the number of uniform and storage buffer declarations, counting arrays as one.
size_t getTransformFeedbackBufferCount() const { return mTransformFeedbackStrides.size(); }
void updateCanDrawWith() { mPODStruct.canDrawWith = hasLinkedShaderStage(ShaderType::Vertex); }
bool hasVertexShader() const { return mPODStruct.canDrawWith; }
const std::vector<ProgramInput> &getProgramInputs() const { return mProgramInputs; }
const std::vector<sh::ShaderVariable> &getOutputVariables() const { return mOutputVariables; }
const std::vector<VariableLocation> &getOutputLocations() const { return mOutputLocations; }
const std::vector<VariableLocation> &getSecondaryOutputLocations() const
{
return mSecondaryOutputLocations;
}
const std::vector<LinkedUniform> &getUniforms() const { return mUniforms; }
const std::vector<std::string> &getUniformNames() const { return mUniformNames; }
const std::vector<std::string> &getUniformMappedNames() const { return mUniformMappedNames; }
const std::vector<InterfaceBlock> &getUniformBlocks() const { return mUniformBlocks; }
const UniformBlockBindingMask &getActiveUniformBlockBindings() const
{
return mPODStruct.activeUniformBlockBindings;
}
const std::vector<SamplerBinding> &getSamplerBindings() const { return mSamplerBindings; }
const std::vector<GLuint> &getSamplerBoundTextureUnits() const
{
return mSamplerBoundTextureUnits;
}
const std::vector<ImageBinding> &getImageBindings() const { return mImageBindings; }
std::vector<ImageBinding> *getImageBindings() { return &mImageBindings; }
const RangeUI &getDefaultUniformRange() const { return mPODStruct.defaultUniformRange; }
const RangeUI &getSamplerUniformRange() const { return mPODStruct.samplerUniformRange; }
const RangeUI &getImageUniformRange() const { return mPODStruct.imageUniformRange; }
const RangeUI &getAtomicCounterUniformRange() const
{
return mPODStruct.atomicCounterUniformRange;
}
const RangeUI &getFragmentInoutRange() const { return mPODStruct.fragmentInoutRange; }
bool hasClipDistance() const { return mPODStruct.hasClipDistance; }
bool hasDiscard() const { return mPODStruct.hasDiscard; }
bool enablesPerSampleShading() const { return mPODStruct.enablesPerSampleShading; }
BlendEquationBitSet getAdvancedBlendEquations() const
{
return mPODStruct.advancedBlendEquations;
}
const std::vector<TransformFeedbackVarying> &getLinkedTransformFeedbackVaryings() const
{
return mLinkedTransformFeedbackVaryings;
}
GLint getTransformFeedbackBufferMode() const { return mPODStruct.transformFeedbackBufferMode; }
GLuint getUniformBlockBinding(GLuint uniformBlockIndex) const
{
ASSERT(uniformBlockIndex < mUniformBlocks.size());
return mUniformBlocks[uniformBlockIndex].binding;
}
GLuint getShaderStorageBlockBinding(GLuint blockIndex) const
{
ASSERT(blockIndex < mShaderStorageBlocks.size());
return mShaderStorageBlocks[blockIndex].binding;
}
const std::vector<GLsizei> &getTransformFeedbackStrides() const
{
return mTransformFeedbackStrides;
}
const std::vector<AtomicCounterBuffer> &getAtomicCounterBuffers() const
{
return mAtomicCounterBuffers;
}
const std::vector<InterfaceBlock> &getShaderStorageBlocks() const
{
return mShaderStorageBlocks;
}
const LinkedUniform &getUniformByIndex(GLuint index) const
{
ASSERT(index < static_cast<size_t>(mUniforms.size()));
return mUniforms[index];
}
const std::string &getUniformNameByIndex(GLuint index) const
{
ASSERT(index < static_cast<size_t>(mUniforms.size()));
return mUniformNames[index];
}
ANGLE_INLINE GLuint getActiveUniformBlockCount() const
{
return static_cast<GLuint>(mUniformBlocks.size());
}
ANGLE_INLINE GLuint getActiveAtomicCounterBufferCount() const
{
return static_cast<GLuint>(mAtomicCounterBuffers.size());
}
ANGLE_INLINE GLuint getActiveShaderStorageBlockCount() const
{
size_t shaderStorageBlocksSize = mShaderStorageBlocks.size();
return static_cast<GLuint>(shaderStorageBlocksSize);
}
GLuint getUniformIndexFromImageIndex(GLuint imageIndex) const
{
ASSERT(imageIndex < mPODStruct.imageUniformRange.length());
return imageIndex + mPODStruct.imageUniformRange.low();
}
GLuint getUniformIndexFromSamplerIndex(GLuint samplerIndex) const
{
ASSERT(samplerIndex < mPODStruct.samplerUniformRange.length());
return samplerIndex + mPODStruct.samplerUniformRange.low();
}
void saveLinkedStateInfo(const ProgramState &state);
const std::vector<sh::ShaderVariable> &getLinkedOutputVaryings(ShaderType shaderType) const
{
return mLinkedOutputVaryings[shaderType];
}
const std::vector<sh::ShaderVariable> &getLinkedInputVaryings(ShaderType shaderType) const
{
return mLinkedInputVaryings[shaderType];
}
const std::vector<sh::ShaderVariable> &getLinkedUniforms(ShaderType shaderType) const
{
return mLinkedUniforms[shaderType];
}
const std::vector<sh::InterfaceBlock> &getLinkedUniformBlocks(ShaderType shaderType) const
{
return mLinkedUniformBlocks[shaderType];
}
int getLinkedShaderVersion(ShaderType shaderType) const
{
return mPODStruct.linkedShaderVersions[shaderType];
}
bool isYUVOutput() const { return mPODStruct.hasYUVOutput; }
PrimitiveMode getGeometryShaderInputPrimitiveType() const
{
return mPODStruct.geometryShaderInputPrimitiveType;
}
PrimitiveMode getGeometryShaderOutputPrimitiveType() const
{
return mPODStruct.geometryShaderOutputPrimitiveType;
}
int getGeometryShaderInvocations() const { return mPODStruct.geometryShaderInvocations; }
int getGeometryShaderMaxVertices() const { return mPODStruct.geometryShaderMaxVertices; }
GLenum getTessGenMode() const { return mPODStruct.tessGenMode; }
void resetCachedValidateSamplersResult() { mCachedValidateSamplersResult.reset(); }
bool validateSamplers(InfoLog *infoLog, const Caps &caps) const
{
// Use the cache if:
// - we aren't using an info log (which gives the full error).
// - The sample mapping hasn't changed and we've already validated.
if (infoLog == nullptr && mCachedValidateSamplersResult.valid())
{
return mCachedValidateSamplersResult.value();
}
return validateSamplersImpl(infoLog, caps);
}
ComponentTypeMask getFragmentOutputsTypeMask() const { return mPODStruct.drawBufferTypeMask; }
DrawBufferMask getActiveOutputVariablesMask() const
{
return mPODStruct.activeOutputVariablesMask;
}
bool linkUniforms(const Caps &caps,
const ShaderMap<std::vector<sh::ShaderVariable>> &shaderUniforms,
InfoLog &infoLog,
const ProgramAliasedBindings &uniformLocationBindings,
GLuint *combinedImageUniformsCount,
std::vector<UnusedUniform> *unusedUniforms,
std::vector<VariableLocation> *uniformLocationsOutOrNull);
void copyInputsFromProgram(const ProgramState &programState);
void copyShaderBuffersFromProgram(const ProgramState &programState, ShaderType shaderType);
void clearSamplerBindings();
void copySamplerBindingsFromProgram(const ProgramState &programState);
void copyImageBindingsFromProgram(const ProgramState &programState);
void copyOutputsFromProgram(const ProgramState &programState);
void copyUniformsFromProgramMap(const ShaderMap<Program *> &programs);
private:
friend class Program;
friend class ProgramPipeline;
friend class ProgramState;
void updateActiveImages(const ProgramExecutable &executable);
// Scans the sampler bindings for type conflicts with sampler 'textureUnitIndex'.
void setSamplerUniformTextureTypeAndFormat(size_t textureUnitIndex,
const std::vector<SamplerBinding> &samplerBindings,
const std::vector<GLuint> &boundTextureUnits);
bool linkMergedVaryings(const Caps &caps,
const Limitations &limitations,
const Version &clientVersion,
bool webglCompatibility,
const ProgramMergedVaryings &mergedVaryings,
const std::vector<std::string> &transformFeedbackVaryingNames,
const LinkingVariables &linkingVariables,
bool isSeparable,
ProgramVaryingPacking *varyingPacking);
bool linkValidateTransformFeedback(
const Caps &caps,
const Version &clientVersion,
const ProgramMergedVaryings &varyings,
ShaderType stage,
const std::vector<std::string> &transformFeedbackVaryingNames);
void gatherTransformFeedbackVaryings(
const ProgramMergedVaryings &varyings,
ShaderType stage,
const std::vector<std::string> &transformFeedbackVaryingNames);
void updateTransformFeedbackStrides();
bool validateSamplersImpl(InfoLog *infoLog, const Caps &caps) const;
bool linkValidateOutputVariables(const Caps &caps,
const Version &version,
GLuint combinedImageUniformsCount,
GLuint combinedShaderStorageBlocksCount,
const std::vector<sh::ShaderVariable> &outputVariables,
int fragmentShaderVersion,
const ProgramAliasedBindings &fragmentOutputLocations,
const ProgramAliasedBindings &fragmentOutputIndices);
bool gatherOutputTypes();
void linkSamplerAndImageBindings(GLuint *combinedImageUniformsCount);
bool linkAtomicCounterBuffers(const Caps &caps, InfoLog &infoLog);
InfoLog mInfoLog;
// This struct must only contains basic data types so that entire struct can be memcpy.
struct PODStruct
{
ShaderBitSet linkedShaderStages;
angle::BitSet<MAX_VERTEX_ATTRIBS> activeAttribLocationsMask;
unsigned int maxActiveAttribLocation;
ComponentTypeMask attributesTypeMask;
// attributesMask is identical to mActiveAttribLocationsMask with built-in attributes
// removed.
AttributesMask attributesMask;
DrawBufferMask activeOutputVariablesMask;
ComponentTypeMask drawBufferTypeMask;
RangeUI defaultUniformRange;
RangeUI samplerUniformRange;
RangeUI imageUniformRange;
RangeUI atomicCounterUniformRange;
RangeUI fragmentInoutRange;
bool hasClipDistance;
bool hasDiscard;
bool hasYUVOutput;
bool enablesPerSampleShading;
bool canDrawWith;
// KHR_blend_equation_advanced supported equation list
BlendEquationBitSet advancedBlendEquations;
// GL_EXT_geometry_shader.
PrimitiveMode geometryShaderInputPrimitiveType;
PrimitiveMode geometryShaderOutputPrimitiveType;
int geometryShaderInvocations;
int geometryShaderMaxVertices;
// GL_EXT_tessellation_shader
int tessControlShaderVertices;
GLenum tessGenMode;
GLenum tessGenSpacing;
GLenum tessGenVertexOrder;
GLenum tessGenPointMode;
GLenum transformFeedbackBufferMode;
// For faster iteration on the blocks currently being bound.
UniformBlockBindingMask activeUniformBlockBindings;
ShaderMap<int> linkedShaderVersions;
} mPODStruct;
static_assert(std::is_trivially_copyable<PODStruct>(), "must be memcpy-able");
// Cached mask of active samplers and sampler types.
ActiveTextureMask mActiveSamplersMask;
ActiveTextureArray<uint32_t> mActiveSamplerRefCounts;
ActiveTextureArray<TextureType> mActiveSamplerTypes;
ActiveTextureMask mActiveSamplerYUV;
ActiveTextureArray<SamplerFormat> mActiveSamplerFormats;
ActiveTextureArray<ShaderBitSet> mActiveSamplerShaderBits;
// Cached mask of active images.
ActiveTextureMask mActiveImagesMask;
ActiveTextureArray<ShaderBitSet> mActiveImageShaderBits;
// Names and mapped names of output variables that are arrays include [0] in the end, similarly
// to uniforms.
std::vector<sh::ShaderVariable> mOutputVariables;
std::vector<VariableLocation> mOutputLocations;
// EXT_blend_func_extended secondary outputs (ones with index 1)
std::vector<VariableLocation> mSecondaryOutputLocations;
// Vertex attributes, Fragment input varyings, etc.
std::vector<ProgramInput> mProgramInputs;
std::vector<TransformFeedbackVarying> mLinkedTransformFeedbackVaryings;
// The size of the data written to each transform feedback buffer per vertex.
std::vector<GLsizei> mTransformFeedbackStrides;
// Uniforms are sorted in order:
// 1. Non-opaque uniforms
// 2. Sampler uniforms
// 3. Image uniforms
// 4. Atomic counter uniforms
// 5. Subpass Input uniforms (Only for Vulkan)
// 6. Uniform block uniforms
// This makes opaque uniform validation easier, since we don't need a separate list.
// For generating the entries and naming them we follow the spec: GLES 3.1 November 2016 section
// 7.3.1.1 Naming Active Resources. There's a separate entry for each struct member and each
// inner array of an array of arrays. Names and mapped names of uniforms that are arrays include
// [0] in the end. This makes implementation of queries simpler.
std::vector<LinkedUniform> mUniforms;
std::vector<std::string> mUniformNames;
// Only used by GL and D3D backend
std::vector<std::string> mUniformMappedNames;
std::vector<InterfaceBlock> mUniformBlocks;
std::vector<AtomicCounterBuffer> mAtomicCounterBuffers;
std::vector<InterfaceBlock> mShaderStorageBlocks;
// An array of the samplers that are used by the program
std::vector<SamplerBinding> mSamplerBindings;
// List of all textures bound to all samplers. Each SamplerBinding will point to a subset in
// this vector.
std::vector<GLuint> mSamplerBoundTextureUnits;
// An array of the images that are used by the program
std::vector<ImageBinding> mImageBindings;
ShaderMap<std::vector<sh::ShaderVariable>> mLinkedOutputVaryings;
ShaderMap<std::vector<sh::ShaderVariable>> mLinkedInputVaryings;
ShaderMap<std::vector<sh::ShaderVariable>> mLinkedUniforms;
ShaderMap<std::vector<sh::InterfaceBlock>> mLinkedUniformBlocks;
// Cache for sampler validation
mutable Optional<bool> mCachedValidateSamplersResult;
};
} // namespace gl
#endif // LIBANGLE_PROGRAMEXECUTABLE_H_