// // 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 #include "libANGLE/Context.h" #include "libANGLE/ErrorStrings.h" #include "libANGLE/Program.h" #include "libANGLE/angletypes.h" #include "libANGLE/renderer/GLImplFactory.h" #include "libANGLE/renderer/ProgramPipelineImpl.h" namespace gl { ProgramPipelineState::ProgramPipelineState() : mLabel(), mIsCompute(false), mActiveShaderProgram(nullptr), mValid(false), mHasBeenBound(false), mExecutable(new ProgramExecutable()) { mExecutable->setProgramPipelineState(this); 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) { 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; } } void ProgramPipelineState::useProgramStages(const Context *context, GLbitfield stages, Program *shaderProgram) { if (stages == GL_ALL_SHADER_BITS) { for (const ShaderType shaderType : gl::AllShaderTypes()) { useProgramStage(context, shaderType, shaderProgram); } } else { if (stages & GL_VERTEX_SHADER_BIT) { useProgramStage(context, ShaderType::Vertex, shaderProgram); } if (stages & GL_FRAGMENT_SHADER_BIT) { useProgramStage(context, ShaderType::Fragment, shaderProgram); } if (stages & GL_COMPUTE_SHADER_BIT) { useProgramStage(context, ShaderType::Compute, shaderProgram); } } } bool ProgramPipelineState::usesShaderProgram(ShaderProgramID programId) const { for (const Program *program : mPrograms) { if (program && (program->id() == programId)) { return true; } } return false; } bool ProgramPipelineState::hasDefaultUniforms() const { for (const gl::ShaderType shaderType : mExecutable->getLinkedShaderStages()) { const Program *shaderProgram = getShaderProgram(shaderType); if (shaderProgram && shaderProgram->getState().hasDefaultUniforms()) { return true; } } return false; } bool ProgramPipelineState::hasTextures() const { for (const gl::ShaderType shaderType : mExecutable->getLinkedShaderStages()) { const Program *shaderProgram = getShaderProgram(shaderType); if (shaderProgram && shaderProgram->getState().hasTextures()) { return true; } } return false; } bool ProgramPipelineState::hasImages() const { for (const gl::ShaderType shaderType : mExecutable->getLinkedShaderStages()) { const Program *shaderProgram = getShaderProgram(shaderType); if (shaderProgram && shaderProgram->getState().hasImages()) { return true; } } return false; } ProgramPipeline::ProgramPipeline(rx::GLImplFactory *factory, ProgramPipelineID handle) : RefCountObject(factory->generateSerial(), handle), mProgramPipelineImpl(factory->createProgramPipeline(mState)) { ASSERT(mProgramPipelineImpl); } 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); } void ProgramPipeline::useProgramStages(const Context *context, GLbitfield stages, Program *shaderProgram) { mState.useProgramStages(context, stages, shaderProgram); updateLinkedShaderStages(); updateExecutable(); mDirtyBits.set(DIRTY_BIT_PROGRAM_STAGE); } 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; } void ProgramPipeline::updateTransformFeedbackMembers() { Program *vertexProgram = getShaderProgram(gl::ShaderType::Vertex); if (!vertexProgram) { return; } const ProgramExecutable &vertexExecutable = vertexProgram->getExecutable(); mState.mExecutable->mTransformFeedbackStrides = vertexExecutable.mTransformFeedbackStrides; mState.mExecutable->mLinkedTransformFeedbackVaryings = vertexExecutable.mLinkedTransformFeedbackVaryings; } void ProgramPipeline::updateExecutableTextures() { for (const ShaderType shaderType : mState.mExecutable->getLinkedShaderStages()) { const Program *program = getShaderProgram(shaderType); if (program) { mState.mExecutable->mActiveSamplersMask |= program->getExecutable().getActiveSamplersMask(); mState.mExecutable->mActiveImagesMask |= program->getExecutable().getActiveImagesMask(); // Updates mActiveSamplerRefCounts, mActiveSamplerTypes, and mActiveSamplerFormats mState.mExecutable->updateActiveSamplers(program->getState()); } } } void ProgramPipeline::updateHasBuffers() { // 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) { const ProgramExecutable &executable = shaderProgram->getExecutable(); if (executable.hasUniformBuffers()) { mState.mExecutable->mPipelineHasGraphicsUniformBuffers = true; } if (executable.hasStorageBuffers()) { mState.mExecutable->mPipelineHasGraphicsStorageBuffers = true; } if (executable.hasAtomicCounterBuffers()) { mState.mExecutable->mPipelineHasGraphicsAtomicCounterBuffers = true; } } } const Program *computeProgram = getShaderProgram(ShaderType::Compute); if (computeProgram) { const ProgramExecutable &executable = computeProgram->getExecutable(); if (executable.hasUniformBuffers()) { mState.mExecutable->mPipelineHasComputeUniformBuffers = true; } if (executable.hasStorageBuffers()) { mState.mExecutable->mPipelineHasComputeStorageBuffers = true; } if (executable.hasAtomicCounterBuffers()) { mState.mExecutable->mPipelineHasComputeAtomicCounterBuffers = true; } } } void ProgramPipeline::updateExecutable() { mState.mExecutable->reset(); // Vertex Shader ProgramExecutable properties updateExecutableAttributes(); updateTransformFeedbackMembers(); // All Shader ProgramExecutable properties updateExecutableTextures(); updateHasBuffers(); } ProgramMergedVaryings ProgramPipeline::getMergedVaryings() const { ASSERT(!mState.mExecutable->isCompute()); // Varyings are matched between pairs of consecutive stages, by location if assigned or // by name otherwise. Note that it's possible for one stage to specify location and the other // not: https://cvs.khronos.org/bugzilla/show_bug.cgi?id=16261 // Map stages to the previous active stage in the rendering pipeline. When looking at input // varyings of a stage, this is used to find the stage whose output varyings are being linked // with them. ShaderMap previousActiveStage; // Note that kAllGraphicsShaderTypes is sorted according to the rendering pipeline. ShaderType lastActiveStage = ShaderType::InvalidEnum; for (ShaderType shaderType : getExecutable().getLinkedShaderStages()) { previousActiveStage[shaderType] = lastActiveStage; const Program *program = getShaderProgram(shaderType); ASSERT(program); lastActiveStage = shaderType; } // First, go through output varyings and create two maps (one by name, one by location) for // faster lookup when matching input varyings. ShaderMap> outputVaryingNameToIndexShaderMap; ShaderMap> outputVaryingLocationToIndexShaderMap; ProgramMergedVaryings merged; // Gather output varyings. for (ShaderType shaderType : getExecutable().getLinkedShaderStages()) { const Program *program = getShaderProgram(shaderType); ASSERT(program); Shader *shader = program->getState().getAttachedShader(shaderType); ASSERT(shader); for (const sh::ShaderVariable &varying : shader->getOutputVaryings()) { merged.push_back({}); ProgramVaryingRef *ref = &merged.back(); ref->frontShader = &varying; ref->frontShaderStage = shaderType; // Always map by name. Even if location is provided in this stage, it may not be in the // paired stage. outputVaryingNameToIndexShaderMap[shaderType][varying.name] = merged.size() - 1; // If location is provided, also keep it in a map by location. if (varying.location != -1) { outputVaryingLocationToIndexShaderMap[shaderType][varying.location] = merged.size() - 1; } } } // Gather input varyings, and match them with output varyings of the previous stage. for (ShaderType shaderType : getExecutable().getLinkedShaderStages()) { const Program *program = getShaderProgram(shaderType); ASSERT(program); Shader *shader = program->getState().getAttachedShader(shaderType); ASSERT(shader); ShaderType previousStage = previousActiveStage[shaderType]; for (const sh::ShaderVariable &varying : shader->getInputVaryings()) { size_t mergedIndex = merged.size(); if (previousStage != ShaderType::InvalidEnum) { // If location is provided, see if we can match by location. if (varying.location != -1) { std::map outputVaryingLocationToIndex = outputVaryingLocationToIndexShaderMap[previousStage]; auto byLocationIter = outputVaryingLocationToIndex.find(varying.location); if (byLocationIter != outputVaryingLocationToIndex.end()) { mergedIndex = byLocationIter->second; } } // If not found, try to match by name. if (mergedIndex == merged.size()) { std::map outputVaryingNameToIndex = outputVaryingNameToIndexShaderMap[previousStage]; auto byNameIter = outputVaryingNameToIndex.find(varying.name); if (byNameIter != outputVaryingNameToIndex.end()) { mergedIndex = byNameIter->second; } } } // If no previous stage, or not matched by location or name, create a new entry for it. if (mergedIndex == merged.size()) { merged.push_back({}); mergedIndex = merged.size() - 1; } ProgramVaryingRef *ref = &merged[mergedIndex]; ref->backShader = &varying; ref->backShaderStage = shaderType; } } return merged; } // 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 (!getExecutable().isCompute()) { InfoLog &infoLog = mState.mExecutable->getInfoLog(); infoLog.reset(); const State &state = context->getState(); // Map the varyings to the register file gl::PackMode packMode = PackMode::ANGLE_RELAXED; if (state.getLimitations().noFlexibleVaryingPacking) { // D3D9 pack mode is strictly more strict than WebGL, so takes priority. packMode = PackMode::ANGLE_NON_CONFORMANT_D3D9; } else if (state.getExtensions().webglCompatibility) { // In WebGL, we use a slightly different handling for packing variables. packMode = PackMode::WEBGL_STRICT; } if (!linkVaryings(infoLog)) { return angle::Result::Stop; } if (!mState.mExecutable->linkValidateGlobalNames(infoLog)) { return angle::Result::Stop; } GLuint maxVaryingVectors = static_cast(context->getState().getCaps().maxVaryingVectors); VaryingPacking varyingPacking(maxVaryingVectors, packMode); const ProgramMergedVaryings &mergedVaryings = getMergedVaryings(); for (ShaderType shaderType : getExecutable().getLinkedShaderStages()) { Program *program = mState.mPrograms[shaderType]; ASSERT(program); program->getExecutable().getResources().varyingPacking.reset(); ANGLE_TRY( program->linkMergedVaryings(context, program->getExecutable(), mergedVaryings)); } } ANGLE_TRY(getImplementation()->link(context)); return angle::Result::Continue; } bool ProgramPipeline::linkVaryings(InfoLog &infoLog) const { ShaderType previousShaderType = ShaderType::InvalidEnum; for (ShaderType shaderType : getExecutable().getLinkedShaderStages()) { Program *program = getShaderProgram(shaderType); ASSERT(program); ProgramExecutable &executable = program->getExecutable(); if (previousShaderType != ShaderType::InvalidEnum) { Program *previousProgram = getShaderProgram(previousShaderType); ASSERT(previousProgram); ProgramExecutable &previousExecutable = previousProgram->getExecutable(); if (!Program::linkValidateShaderInterfaceMatching( previousExecutable.getLinkedOutputVaryings(previousShaderType), executable.getLinkedInputVaryings(shaderType), previousShaderType, shaderType, previousExecutable.getLinkedShaderVersion(previousShaderType), executable.getLinkedShaderVersion(shaderType), true, infoLog)) { return false; } } previousShaderType = shaderType; } Program *vertexProgram = mState.mPrograms[ShaderType::Vertex]; Program *fragmentProgram = mState.mPrograms[ShaderType::Fragment]; if (!vertexProgram || !fragmentProgram) { return false; } ProgramExecutable &vertexExecutable = vertexProgram->getExecutable(); ProgramExecutable &fragmentExecutable = fragmentProgram->getExecutable(); return Program::linkValidateBuiltInVaryings( vertexExecutable.getLinkedOutputVaryings(ShaderType::Vertex), fragmentExecutable.getLinkedInputVaryings(ShaderType::Fragment), vertexExecutable.getLinkedShaderVersion(ShaderType::Vertex), 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; } } } 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"; } } } } bool ProgramPipeline::validateSamplers(InfoLog *infoLog, const Caps &caps) { for (const ShaderType shaderType : gl::AllShaderTypes()) { Program *shaderProgram = mState.mPrograms[shaderType]; if (shaderProgram && !shaderProgram->validateSamplers(infoLog, caps)) { return false; } } return true; } angle::Result ProgramPipeline::syncState(const Context *context) { if (mDirtyBits.any()) { mDirtyBits.reset(); // If there's a Program bound, we still want to link the PPO so we don't // lose the dirty bit, but, we don't want to signal any errors if it fails // since the failure would be unrelated to drawing with the Program. bool goodResult = link(context) == angle::Result::Continue; if (!context->getState().getProgram()) { ANGLE_CHECK(const_cast(context), goodResult, "Program pipeline link failed", GL_INVALID_OPERATION); } } return angle::Result::Continue; } } // namespace gl