| // |
| // Copyright 2017 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. |
| // |
| |
| // ProgramPipeline.cpp: Implements the gl::ProgramPipeline class. |
| // Implements GL program pipeline objects and related functionality. |
| // [OpenGL ES 3.1] section 7.4 page 105. |
| |
| #include "libANGLE/ProgramPipeline.h" |
| |
| #include <algorithm> |
| |
| #include "libANGLE/Context.h" |
| #include "libANGLE/Program.h" |
| #include "libANGLE/angletypes.h" |
| #include "libANGLE/renderer/GLImplFactory.h" |
| #include "libANGLE/renderer/ProgramPipelineImpl.h" |
| |
| namespace gl |
| { |
| |
| enum SubjectIndexes : angle::SubjectIndex |
| { |
| kExecutableSubjectIndex = 0 |
| }; |
| |
| ProgramPipelineState::ProgramPipelineState() |
| : mLabel(), |
| mActiveShaderProgram(nullptr), |
| mValid(false), |
| mExecutable(new ProgramExecutable()), |
| mIsLinked(false) |
| { |
| for (const ShaderType shaderType : gl::AllShaderTypes()) |
| { |
| mPrograms[shaderType] = nullptr; |
| } |
| } |
| |
| ProgramPipelineState::~ProgramPipelineState() |
| { |
| SafeDelete(mExecutable); |
| } |
| |
| const std::string &ProgramPipelineState::getLabel() const |
| { |
| return mLabel; |
| } |
| |
| void ProgramPipelineState::activeShaderProgram(Program *shaderProgram) |
| { |
| mActiveShaderProgram = shaderProgram; |
| } |
| |
| void ProgramPipelineState::useProgramStage(const Context *context, |
| const ShaderType shaderType, |
| Program *shaderProgram, |
| angle::ObserverBinding *programObserverBindings) |
| { |
| Program *oldProgram = mPrograms[shaderType]; |
| if (oldProgram) |
| { |
| oldProgram->release(context); |
| } |
| |
| // If program refers to a program object with a valid shader attached for the indicated shader |
| // stage, glUseProgramStages installs the executable code for that stage in the indicated |
| // program pipeline object pipeline. |
| if (shaderProgram && (shaderProgram->id().value != 0) && |
| shaderProgram->getExecutable().hasLinkedShaderStage(shaderType)) |
| { |
| mPrograms[shaderType] = shaderProgram; |
| shaderProgram->addRef(); |
| } |
| else |
| { |
| // If program is zero, or refers to a program object with no valid shader executable for the |
| // given stage, it is as if the pipeline object has no programmable stage configured for the |
| // indicated shader stage. |
| mPrograms[shaderType] = nullptr; |
| } |
| |
| Program *program = mPrograms[shaderType]; |
| programObserverBindings->bind(program); |
| } |
| |
| void ProgramPipelineState::useProgramStages( |
| const Context *context, |
| GLbitfield stages, |
| Program *shaderProgram, |
| std::vector<angle::ObserverBinding> *programObserverBindings) |
| { |
| for (size_t singleShaderBit : angle::BitSet16<16>(static_cast<uint16_t>(stages))) |
| { |
| // Cast back to a bit after the iterator returns an index. |
| ShaderType shaderType = GetShaderTypeFromBitfield(angle::Bit<size_t>(singleShaderBit)); |
| if (shaderType == ShaderType::InvalidEnum) |
| { |
| break; |
| } |
| useProgramStage(context, shaderType, shaderProgram, |
| &programObserverBindings->at(static_cast<size_t>(shaderType))); |
| } |
| } |
| |
| bool ProgramPipelineState::usesShaderProgram(ShaderProgramID programId) const |
| { |
| for (const Program *program : mPrograms) |
| { |
| if (program && (program->id() == programId)) |
| { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| void ProgramPipelineState::updateExecutableTextures() |
| { |
| for (const ShaderType shaderType : mExecutable->getLinkedShaderStages()) |
| { |
| const Program *program = getShaderProgram(shaderType); |
| ASSERT(program); |
| mExecutable->setActiveTextureMask(mExecutable->getActiveSamplersMask() | |
| program->getExecutable().getActiveSamplersMask()); |
| mExecutable->setActiveImagesMask(mExecutable->getActiveImagesMask() | |
| program->getExecutable().getActiveImagesMask()); |
| // Updates mActiveSamplerRefCounts, mActiveSamplerTypes, and mActiveSamplerFormats |
| mExecutable->updateActiveSamplers(program->getState()); |
| } |
| } |
| |
| rx::SpecConstUsageBits ProgramPipelineState::getSpecConstUsageBits() const |
| { |
| rx::SpecConstUsageBits specConstUsageBits; |
| for (const ShaderType shaderType : mExecutable->getLinkedShaderStages()) |
| { |
| const Program *program = getShaderProgram(shaderType); |
| ASSERT(program); |
| specConstUsageBits |= program->getState().getSpecConstUsageBits(); |
| } |
| return specConstUsageBits; |
| } |
| |
| ProgramPipeline::ProgramPipeline(rx::GLImplFactory *factory, ProgramPipelineID handle) |
| : RefCountObject(factory->generateSerial(), handle), |
| mProgramPipelineImpl(factory->createProgramPipeline(mState)), |
| mExecutableObserverBinding(this, kExecutableSubjectIndex) |
| { |
| ASSERT(mProgramPipelineImpl); |
| |
| for (const ShaderType shaderType : gl::AllShaderTypes()) |
| { |
| mProgramObserverBindings.emplace_back(this, static_cast<angle::SubjectIndex>(shaderType)); |
| } |
| mExecutableObserverBinding.bind(mState.mExecutable); |
| } |
| |
| ProgramPipeline::~ProgramPipeline() |
| { |
| mProgramPipelineImpl.release(); |
| } |
| |
| void ProgramPipeline::onDestroy(const Context *context) |
| { |
| for (Program *program : mState.mPrograms) |
| { |
| if (program) |
| { |
| ASSERT(program->getRefCount()); |
| program->release(context); |
| } |
| } |
| |
| getImplementation()->destroy(context); |
| } |
| |
| void ProgramPipeline::setLabel(const Context *context, const std::string &label) |
| { |
| mState.mLabel = label; |
| } |
| |
| const std::string &ProgramPipeline::getLabel() const |
| { |
| return mState.mLabel; |
| } |
| |
| rx::ProgramPipelineImpl *ProgramPipeline::getImplementation() const |
| { |
| return mProgramPipelineImpl.get(); |
| } |
| |
| void ProgramPipeline::activeShaderProgram(Program *shaderProgram) |
| { |
| mState.activeShaderProgram(shaderProgram); |
| } |
| |
| angle::Result ProgramPipeline::useProgramStages(const Context *context, |
| GLbitfield stages, |
| Program *shaderProgram) |
| { |
| mState.useProgramStages(context, stages, shaderProgram, &mProgramObserverBindings); |
| updateLinkedShaderStages(); |
| |
| mState.mIsLinked = false; |
| |
| return link(context); |
| } |
| |
| void ProgramPipeline::updateLinkedShaderStages() |
| { |
| mState.mExecutable->resetLinkedShaderStages(); |
| |
| for (const ShaderType shaderType : gl::AllShaderTypes()) |
| { |
| Program *program = mState.mPrograms[shaderType]; |
| if (program) |
| { |
| mState.mExecutable->setLinkedShaderStages(shaderType); |
| } |
| } |
| |
| mState.mExecutable->updateCanDrawWith(); |
| } |
| |
| void ProgramPipeline::updateExecutableAttributes() |
| { |
| Program *vertexProgram = getShaderProgram(gl::ShaderType::Vertex); |
| |
| if (!vertexProgram) |
| { |
| return; |
| } |
| |
| const ProgramExecutable &vertexExecutable = vertexProgram->getExecutable(); |
| mState.mExecutable->mActiveAttribLocationsMask = vertexExecutable.mActiveAttribLocationsMask; |
| mState.mExecutable->mMaxActiveAttribLocation = vertexExecutable.mMaxActiveAttribLocation; |
| mState.mExecutable->mAttributesTypeMask = vertexExecutable.mAttributesTypeMask; |
| mState.mExecutable->mAttributesMask = vertexExecutable.mAttributesMask; |
| mState.mExecutable->mProgramInputs = vertexExecutable.mProgramInputs; |
| } |
| |
| void ProgramPipeline::updateTransformFeedbackMembers() |
| { |
| ShaderType lastVertexProcessingStage = |
| gl::GetLastPreFragmentStage(getExecutable().getLinkedShaderStages()); |
| if (lastVertexProcessingStage == ShaderType::InvalidEnum) |
| { |
| return; |
| } |
| |
| Program *shaderProgram = getShaderProgram(lastVertexProcessingStage); |
| ASSERT(shaderProgram); |
| |
| const ProgramExecutable &lastPreFragmentExecutable = shaderProgram->getExecutable(); |
| mState.mExecutable->mTransformFeedbackStrides = |
| lastPreFragmentExecutable.mTransformFeedbackStrides; |
| mState.mExecutable->mLinkedTransformFeedbackVaryings = |
| lastPreFragmentExecutable.mLinkedTransformFeedbackVaryings; |
| } |
| |
| void ProgramPipeline::updateShaderStorageBlocks() |
| { |
| mState.mExecutable->mShaderStorageBlocks.clear(); |
| |
| // Only copy the storage blocks from each Program in the PPO once, since each Program could |
| // contain multiple shader stages. |
| ShaderBitSet handledStages; |
| |
| for (const gl::ShaderType shaderType : gl::AllShaderTypes()) |
| { |
| const Program *shaderProgram = getShaderProgram(shaderType); |
| if (shaderProgram && !handledStages.test(shaderType)) |
| { |
| // Only add each Program's blocks once. |
| handledStages |= shaderProgram->getExecutable().getLinkedShaderStages(); |
| |
| for (const InterfaceBlock &block : |
| shaderProgram->getExecutable().getShaderStorageBlocks()) |
| { |
| mState.mExecutable->mShaderStorageBlocks.emplace_back(block); |
| } |
| } |
| } |
| } |
| |
| void ProgramPipeline::updateImageBindings() |
| { |
| mState.mExecutable->mImageBindings.clear(); |
| mState.mExecutable->mActiveImageShaderBits.fill({}); |
| |
| // Only copy the storage blocks from each Program in the PPO once, since each Program could |
| // contain multiple shader stages. |
| ShaderBitSet handledStages; |
| |
| for (const gl::ShaderType shaderType : gl::AllShaderTypes()) |
| { |
| const Program *shaderProgram = getShaderProgram(shaderType); |
| if (shaderProgram && !handledStages.test(shaderType)) |
| { |
| // Only add each Program's blocks once. |
| handledStages |= shaderProgram->getExecutable().getLinkedShaderStages(); |
| |
| for (const ImageBinding &imageBinding : shaderProgram->getState().getImageBindings()) |
| { |
| mState.mExecutable->mImageBindings.emplace_back(imageBinding); |
| } |
| |
| mState.mExecutable->updateActiveImages(shaderProgram->getExecutable()); |
| } |
| } |
| } |
| |
| void ProgramPipeline::updateExecutableGeometryProperties() |
| { |
| Program *geometryProgram = getShaderProgram(gl::ShaderType::Geometry); |
| |
| if (!geometryProgram) |
| { |
| return; |
| } |
| |
| const ProgramExecutable &geometryExecutable = geometryProgram->getExecutable(); |
| mState.mExecutable->mGeometryShaderInputPrimitiveType = |
| geometryExecutable.mGeometryShaderInputPrimitiveType; |
| mState.mExecutable->mGeometryShaderOutputPrimitiveType = |
| geometryExecutable.mGeometryShaderOutputPrimitiveType; |
| mState.mExecutable->mGeometryShaderInvocations = geometryExecutable.mGeometryShaderInvocations; |
| mState.mExecutable->mGeometryShaderMaxVertices = geometryExecutable.mGeometryShaderMaxVertices; |
| } |
| |
| void ProgramPipeline::updateExecutableTessellationProperties() |
| { |
| Program *tessControlProgram = getShaderProgram(gl::ShaderType::TessControl); |
| Program *tessEvalProgram = getShaderProgram(gl::ShaderType::TessEvaluation); |
| |
| if (tessControlProgram) |
| { |
| const ProgramExecutable &tessControlExecutable = tessControlProgram->getExecutable(); |
| mState.mExecutable->mTessControlShaderVertices = |
| tessControlExecutable.mTessControlShaderVertices; |
| } |
| |
| if (tessEvalProgram) |
| { |
| const ProgramExecutable &tessEvalExecutable = tessEvalProgram->getExecutable(); |
| mState.mExecutable->mTessGenMode = tessEvalExecutable.mTessGenMode; |
| mState.mExecutable->mTessGenSpacing = tessEvalExecutable.mTessGenSpacing; |
| mState.mExecutable->mTessGenVertexOrder = tessEvalExecutable.mTessGenVertexOrder; |
| mState.mExecutable->mTessGenPointMode = tessEvalExecutable.mTessGenPointMode; |
| } |
| } |
| |
| void ProgramPipeline::updateFragmentInoutRange() |
| { |
| Program *fragmentProgram = getShaderProgram(gl::ShaderType::Fragment); |
| |
| if (!fragmentProgram) |
| { |
| return; |
| } |
| |
| const ProgramExecutable &fragmentExecutable = fragmentProgram->getExecutable(); |
| mState.mExecutable->mFragmentInoutRange = fragmentExecutable.mFragmentInoutRange; |
| } |
| |
| void ProgramPipeline::updateLinkedVaryings() |
| { |
| // Need to check all of the shader stages, not just linked, so we handle Compute correctly. |
| for (const gl::ShaderType shaderType : kAllGraphicsShaderTypes) |
| { |
| const Program *shaderProgram = getShaderProgram(shaderType); |
| if (shaderProgram && shaderProgram->isLinked()) |
| { |
| const ProgramExecutable &executable = shaderProgram->getExecutable(); |
| mState.mExecutable->mLinkedOutputVaryings[shaderType] = |
| executable.getLinkedOutputVaryings(shaderType); |
| mState.mExecutable->mLinkedInputVaryings[shaderType] = |
| executable.getLinkedInputVaryings(shaderType); |
| } |
| } |
| |
| const Program *computeProgram = getShaderProgram(ShaderType::Compute); |
| if (computeProgram && computeProgram->isLinked()) |
| { |
| const ProgramExecutable &executable = computeProgram->getExecutable(); |
| mState.mExecutable->mLinkedOutputVaryings[ShaderType::Compute] = |
| executable.getLinkedOutputVaryings(ShaderType::Compute); |
| mState.mExecutable->mLinkedInputVaryings[ShaderType::Compute] = |
| executable.getLinkedInputVaryings(ShaderType::Compute); |
| } |
| } |
| |
| void ProgramPipeline::updateHasBooleans() |
| { |
| // Need to check all of the shader stages, not just linked, so we handle Compute correctly. |
| for (const gl::ShaderType shaderType : gl::AllShaderTypes()) |
| { |
| const Program *shaderProgram = getShaderProgram(shaderType); |
| if (shaderProgram) |
| { |
| const ProgramExecutable &executable = shaderProgram->getExecutable(); |
| |
| if (executable.hasUniformBuffers()) |
| { |
| mState.mExecutable->mPipelineHasUniformBuffers = true; |
| } |
| if (executable.hasStorageBuffers()) |
| { |
| mState.mExecutable->mPipelineHasStorageBuffers = true; |
| } |
| if (executable.hasAtomicCounterBuffers()) |
| { |
| mState.mExecutable->mPipelineHasAtomicCounterBuffers = true; |
| } |
| if (executable.hasDefaultUniforms()) |
| { |
| mState.mExecutable->mPipelineHasDefaultUniforms = true; |
| } |
| if (executable.hasTextures()) |
| { |
| mState.mExecutable->mPipelineHasTextures = true; |
| } |
| if (executable.hasImages()) |
| { |
| mState.mExecutable->mPipelineHasImages = true; |
| } |
| } |
| } |
| } |
| |
| void ProgramPipeline::updateExecutable() |
| { |
| // Vertex Shader ProgramExecutable properties |
| updateExecutableAttributes(); |
| updateTransformFeedbackMembers(); |
| updateShaderStorageBlocks(); |
| updateImageBindings(); |
| |
| // Geometry Shader ProgramExecutable properties |
| updateExecutableGeometryProperties(); |
| |
| // Tessellation Shaders ProgramExecutable properties |
| updateExecutableTessellationProperties(); |
| |
| // Fragment Shader ProgramExecutable properties |
| updateFragmentInoutRange(); |
| |
| // All Shader ProgramExecutable properties |
| mState.updateExecutableTextures(); |
| updateLinkedVaryings(); |
| |
| // Must be last, since it queries things updated by earlier functions |
| updateHasBooleans(); |
| } |
| |
| // The attached shaders are checked for linking errors by matching up their variables. |
| // Uniform, input and output variables get collected. |
| // The code gets compiled into binaries. |
| angle::Result ProgramPipeline::link(const Context *context) |
| { |
| if (mState.mIsLinked) |
| { |
| return angle::Result::Continue; |
| } |
| |
| ProgramMergedVaryings mergedVaryings; |
| ProgramVaryingPacking varyingPacking; |
| LinkingVariables linkingVariables(mState); |
| |
| mState.mExecutable->reset(); |
| |
| if (mState.mExecutable->hasLinkedShaderStage(gl::ShaderType::Vertex)) |
| { |
| InfoLog &infoLog = mState.mExecutable->getInfoLog(); |
| infoLog.reset(); |
| |
| if (!linkVaryings(infoLog)) |
| { |
| return angle::Result::Stop; |
| } |
| |
| if (!LinkValidateProgramGlobalNames(infoLog, getExecutable(), linkingVariables)) |
| { |
| return angle::Result::Stop; |
| } |
| |
| Program *fragmentShaderProgram = getShaderProgram(ShaderType::Fragment); |
| if (fragmentShaderProgram) |
| { |
| // We should also be validating image uniforms and SSBOs. |
| const int combinedImageUniforms = 0; |
| const int combinedShaderStorageBlocks = 0; |
| const ProgramExecutable &fragmentExecutable = fragmentShaderProgram->getExecutable(); |
| if (!mState.mExecutable->linkValidateOutputVariables( |
| context->getCaps(), context->getExtensions(), context->getClientVersion(), |
| combinedImageUniforms, combinedShaderStorageBlocks, |
| fragmentExecutable.getOutputVariables(), |
| fragmentExecutable.getLinkedShaderVersion(ShaderType::Fragment), |
| ProgramAliasedBindings(), ProgramAliasedBindings())) |
| { |
| return angle::Result::Continue; |
| } |
| } |
| mergedVaryings = GetMergedVaryingsFromLinkingVariables(linkingVariables); |
| // If separable program objects are in use, the set of attributes captured is taken |
| // from the program object active on the last vertex processing stage. |
| ShaderType lastVertexProcessingStage = |
| gl::GetLastPreFragmentStage(getExecutable().getLinkedShaderStages()); |
| if (lastVertexProcessingStage == ShaderType::InvalidEnum) |
| { |
| // If there is no active program for the vertex or fragment shader stages, the results |
| // of vertex and fragment shader execution will respectively be undefined. However, |
| // this is not an error. |
| return angle::Result::Continue; |
| } |
| |
| Program *tfProgram = getShaderProgram(lastVertexProcessingStage); |
| ASSERT(tfProgram); |
| |
| if (!tfProgram) |
| { |
| tfProgram = mState.mPrograms[ShaderType::Vertex]; |
| } |
| |
| const std::vector<std::string> &transformFeedbackVaryingNames = |
| tfProgram->getState().getTransformFeedbackVaryingNames(); |
| |
| if (!mState.mExecutable->linkMergedVaryings(context, mergedVaryings, |
| transformFeedbackVaryingNames, linkingVariables, |
| false, &varyingPacking)) |
| { |
| return angle::Result::Stop; |
| } |
| } |
| |
| if (mState.mExecutable->hasLinkedShaderStage(gl::ShaderType::Vertex) || |
| mState.mExecutable->hasLinkedShaderStage(gl::ShaderType::Compute)) |
| { |
| ANGLE_TRY(getImplementation()->link(context, mergedVaryings, varyingPacking)); |
| } |
| |
| mState.mExecutable->mActiveSamplerRefCounts.fill(0); |
| updateExecutable(); |
| |
| mState.mIsLinked = true; |
| onStateChange(angle::SubjectMessage::SubjectChanged); |
| |
| return angle::Result::Continue; |
| } |
| |
| bool ProgramPipeline::linkVaryings(InfoLog &infoLog) const |
| { |
| ShaderType previousShaderType = ShaderType::InvalidEnum; |
| for (ShaderType shaderType : kAllGraphicsShaderTypes) |
| { |
| Program *program = getShaderProgram(shaderType); |
| if (!program) |
| { |
| continue; |
| } |
| ProgramExecutable &executable = program->getExecutable(); |
| |
| if (previousShaderType != ShaderType::InvalidEnum) |
| { |
| Program *previousProgram = getShaderProgram(previousShaderType); |
| ASSERT(previousProgram); |
| const ProgramExecutable &previousExecutable = previousProgram->getExecutable(); |
| |
| if (!LinkValidateShaderInterfaceMatching( |
| previousExecutable.getLinkedOutputVaryings(previousShaderType), |
| executable.getLinkedInputVaryings(shaderType), previousShaderType, shaderType, |
| previousExecutable.getLinkedShaderVersion(previousShaderType), |
| executable.getLinkedShaderVersion(shaderType), true, infoLog)) |
| { |
| return false; |
| } |
| } |
| previousShaderType = shaderType; |
| } |
| |
| // TODO: http://anglebug.com/3571 and http://anglebug.com/3572 |
| // Need to move logic of validating builtin varyings inside the for-loop above. |
| // This is because the built-in symbols `gl_ClipDistance` and `gl_CullDistance` |
| // can be redeclared in Geometry or Tessellation shaders as well. |
| Program *vertexProgram = mState.mPrograms[ShaderType::Vertex]; |
| Program *fragmentProgram = mState.mPrograms[ShaderType::Fragment]; |
| if (!vertexProgram || !fragmentProgram) |
| { |
| return true; |
| } |
| ProgramExecutable &vertexExecutable = vertexProgram->getExecutable(); |
| ProgramExecutable &fragmentExecutable = fragmentProgram->getExecutable(); |
| return LinkValidateBuiltInVaryings( |
| vertexExecutable.getLinkedOutputVaryings(ShaderType::Vertex), |
| fragmentExecutable.getLinkedInputVaryings(ShaderType::Fragment), ShaderType::Vertex, |
| ShaderType::Fragment, vertexExecutable.getLinkedShaderVersion(ShaderType::Vertex), |
| fragmentExecutable.getLinkedShaderVersion(ShaderType::Fragment), infoLog); |
| } |
| |
| void ProgramPipeline::validate(const gl::Context *context) |
| { |
| const Caps &caps = context->getCaps(); |
| mState.mValid = true; |
| InfoLog &infoLog = mState.mExecutable->getInfoLog(); |
| infoLog.reset(); |
| |
| for (const ShaderType shaderType : mState.mExecutable->getLinkedShaderStages()) |
| { |
| Program *shaderProgram = mState.mPrograms[shaderType]; |
| if (shaderProgram) |
| { |
| shaderProgram->resolveLink(context); |
| shaderProgram->validate(caps); |
| std::string shaderInfoString = shaderProgram->getExecutable().getInfoLogString(); |
| if (shaderInfoString.length()) |
| { |
| mState.mValid = false; |
| infoLog << shaderInfoString << "\n"; |
| return; |
| } |
| if (!shaderProgram->isSeparable()) |
| { |
| mState.mValid = false; |
| infoLog << GetShaderTypeString(shaderType) << " is not marked separable." |
| << "\n"; |
| return; |
| } |
| } |
| } |
| |
| intptr_t drawStatesError = context->getStateCache().getBasicDrawStatesError(context); |
| if (drawStatesError) |
| { |
| mState.mValid = false; |
| const char *errorMessage = reinterpret_cast<const char *>(drawStatesError); |
| infoLog << errorMessage << "\n"; |
| return; |
| } |
| |
| if (!linkVaryings(infoLog)) |
| { |
| mState.mValid = false; |
| |
| for (const ShaderType shaderType : mState.mExecutable->getLinkedShaderStages()) |
| { |
| Program *shaderProgram = mState.mPrograms[shaderType]; |
| ASSERT(shaderProgram); |
| shaderProgram->validate(caps); |
| std::string shaderInfoString = shaderProgram->getExecutable().getInfoLogString(); |
| if (shaderInfoString.length()) |
| { |
| infoLog << shaderInfoString << "\n"; |
| } |
| } |
| } |
| } |
| |
| void ProgramPipeline::onSubjectStateChange(angle::SubjectIndex index, angle::SubjectMessage message) |
| { |
| switch (message) |
| { |
| case angle::SubjectMessage::ProgramTextureOrImageBindingChanged: |
| mState.mExecutable->mActiveSamplerRefCounts.fill(0); |
| mState.updateExecutableTextures(); |
| break; |
| |
| case angle::SubjectMessage::ProgramRelinked: |
| mState.mIsLinked = false; |
| onStateChange(angle::SubjectMessage::ProgramRelinked); |
| break; |
| case angle::SubjectMessage::SamplerUniformsUpdated: |
| mState.mExecutable->resetCachedValidateSamplersResult(); |
| break; |
| default: |
| UNREACHABLE(); |
| break; |
| } |
| } |
| } // namespace gl |