| // | |
| // Copyright 2002 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. | |
| // | |
| // Program.cpp: Implements the gl::Program class. Implements GL program objects | |
| // and related functionality. [OpenGL ES 2.0.24] section 2.10.3 page 28. | |
| #include "libANGLE/Program.h" | |
| #include <algorithm> | |
| #include <utility> | |
| #include "common/angle_version_info.h" | |
| #include "common/bitset_utils.h" | |
| #include "common/debug.h" | |
| #include "common/platform.h" | |
| #include "common/platform_helpers.h" | |
| #include "common/string_utils.h" | |
| #include "common/utilities.h" | |
| #include "compiler/translator/blocklayout.h" | |
| #include "libANGLE/Context.h" | |
| #include "libANGLE/ErrorStrings.h" | |
| #include "libANGLE/MemoryProgramCache.h" | |
| #include "libANGLE/ProgramLinkedResources.h" | |
| #include "libANGLE/ResourceManager.h" | |
| #include "libANGLE/Uniform.h" | |
| #include "libANGLE/VaryingPacking.h" | |
| #include "libANGLE/Version.h" | |
| #include "libANGLE/capture/FrameCapture.h" | |
| #include "libANGLE/features.h" | |
| #include "libANGLE/histogram_macros.h" | |
| #include "libANGLE/queryconversions.h" | |
| #include "libANGLE/renderer/ContextImpl.h" | |
| #include "libANGLE/renderer/GLImplFactory.h" | |
| #include "libANGLE/renderer/ProgramImpl.h" | |
| #include "libANGLE/trace.h" | |
| #include "platform/PlatformMethods.h" | |
| #include "platform/autogen/FrontendFeatures_autogen.h" | |
| namespace gl | |
| { | |
| namespace | |
| { | |
| void InitUniformBlockLinker(const ProgramState &state, UniformBlockLinker *blockLinker) | |
| { | |
| for (ShaderType shaderType : AllShaderTypes()) | |
| { | |
| const SharedCompiledShaderState &shader = state.getAttachedShader(shaderType); | |
| if (shader) | |
| { | |
| blockLinker->addShaderBlocks(shaderType, &shader->uniformBlocks); | |
| } | |
| } | |
| } | |
| void InitShaderStorageBlockLinker(const ProgramState &state, ShaderStorageBlockLinker *blockLinker) | |
| { | |
| for (ShaderType shaderType : AllShaderTypes()) | |
| { | |
| const SharedCompiledShaderState &shader = state.getAttachedShader(shaderType); | |
| if (shader) | |
| { | |
| blockLinker->addShaderBlocks(shaderType, &shader->shaderStorageBlocks); | |
| } | |
| } | |
| } | |
| // Provides a mechanism to access the result of asynchronous linking. | |
| class LinkEvent : angle::NonCopyable | |
| { | |
| public: | |
| virtual ~LinkEvent() {} | |
| // Please be aware that these methods may be called under a gl::Context other | |
| // than the one where the LinkEvent was created. | |
| // | |
| // Waits until the linking is actually done. Returns true if the linking | |
| // succeeded, false otherwise. | |
| virtual angle::Result wait(const Context *context) = 0; | |
| // Peeks whether the linking is still ongoing. | |
| virtual bool isLinking() = 0; | |
| }; | |
| // Wraps an already done linking. | |
| class LinkEventDone final : public LinkEvent | |
| { | |
| public: | |
| LinkEventDone(angle::Result result) : mResult(result) {} | |
| angle::Result wait(const Context *context) override { return mResult; } | |
| bool isLinking() override { return false; } | |
| private: | |
| angle::Result mResult; | |
| }; | |
| void ScheduleSubTasks(const std::shared_ptr<angle::WorkerThreadPool> &workerThreadPool, | |
| std::vector<std::shared_ptr<rx::LinkSubTask>> &tasks, | |
| std::vector<std::shared_ptr<angle::WaitableEvent>> *eventsOut) | |
| { | |
| eventsOut->reserve(tasks.size()); | |
| for (const std::shared_ptr<rx::LinkSubTask> &subTask : tasks) | |
| { | |
| eventsOut->push_back(workerThreadPool->postWorkerTask(subTask)); | |
| } | |
| } | |
| } // anonymous namespace | |
| const char *GetLinkMismatchErrorString(LinkMismatchError linkError) | |
| { | |
| switch (linkError) | |
| { | |
| case LinkMismatchError::TYPE_MISMATCH: | |
| return "Type"; | |
| case LinkMismatchError::ARRAYNESS_MISMATCH: | |
| return "Array-ness"; | |
| case LinkMismatchError::ARRAY_SIZE_MISMATCH: | |
| return "Array size"; | |
| case LinkMismatchError::PRECISION_MISMATCH: | |
| return "Precision"; | |
| case LinkMismatchError::STRUCT_NAME_MISMATCH: | |
| return "Structure name"; | |
| case LinkMismatchError::FIELD_NUMBER_MISMATCH: | |
| return "Field number"; | |
| case LinkMismatchError::FIELD_NAME_MISMATCH: | |
| return "Field name"; | |
| case LinkMismatchError::INTERPOLATION_TYPE_MISMATCH: | |
| return "Interpolation type"; | |
| case LinkMismatchError::INVARIANCE_MISMATCH: | |
| return "Invariance"; | |
| case LinkMismatchError::BINDING_MISMATCH: | |
| return "Binding layout qualifier"; | |
| case LinkMismatchError::LOCATION_MISMATCH: | |
| return "Location layout qualifier"; | |
| case LinkMismatchError::OFFSET_MISMATCH: | |
| return "Offset layout qualifier"; | |
| case LinkMismatchError::INSTANCE_NAME_MISMATCH: | |
| return "Instance name qualifier"; | |
| case LinkMismatchError::FORMAT_MISMATCH: | |
| return "Format qualifier"; | |
| case LinkMismatchError::LAYOUT_QUALIFIER_MISMATCH: | |
| return "Layout qualifier"; | |
| case LinkMismatchError::MATRIX_PACKING_MISMATCH: | |
| return "Matrix Packing"; | |
| case LinkMismatchError::FIELD_LOCATION_MISMATCH: | |
| return "Field location"; | |
| case LinkMismatchError::FIELD_STRUCT_NAME_MISMATCH: | |
| return "Field structure name"; | |
| default: | |
| UNREACHABLE(); | |
| return ""; | |
| } | |
| } | |
| template <typename T> | |
| void UpdateInterfaceVariable(std::vector<T> *block, const sh::ShaderVariable &var) | |
| { | |
| if (!var.isStruct()) | |
| { | |
| block->emplace_back(var); | |
| block->back().resetEffectiveLocation(); | |
| } | |
| for (const sh::ShaderVariable &field : var.fields) | |
| { | |
| ASSERT(!var.name.empty() || var.isShaderIOBlock); | |
| // Shader I/O block naming is similar to UBOs and SSBOs: | |
| // | |
| // in Block | |
| // { | |
| // type field; // produces "field" | |
| // }; | |
| // | |
| // in Block2 | |
| // { | |
| // type field; // produces "Block2.field" | |
| // } block2; | |
| // | |
| const std::string &baseName = var.isShaderIOBlock ? var.structOrBlockName : var.name; | |
| const std::string prefix = var.name.empty() ? "" : baseName + "."; | |
| if (!field.isStruct()) | |
| { | |
| sh::ShaderVariable fieldCopy = field; | |
| fieldCopy.updateEffectiveLocation(var); | |
| fieldCopy.name = prefix + field.name; | |
| block->emplace_back(fieldCopy); | |
| } | |
| for (const sh::ShaderVariable &nested : field.fields) | |
| { | |
| sh::ShaderVariable nestedCopy = nested; | |
| nestedCopy.updateEffectiveLocation(field); | |
| nestedCopy.name = prefix + field.name + "." + nested.name; | |
| block->emplace_back(nestedCopy); | |
| } | |
| } | |
| } | |
| // Saves the linking context for later use in resolveLink(). | |
| struct Program::LinkingState | |
| { | |
| LinkingVariables linkingVariables; | |
| ProgramLinkedResources resources; | |
| std::unique_ptr<LinkEvent> linkEvent; | |
| bool linkingFromBinary; | |
| }; | |
| const char *const g_fakepath = "C:\\fakepath"; | |
| // InfoLog implementation. | |
| InfoLog::InfoLog() : mLazyStream(nullptr) {} | |
| InfoLog::~InfoLog() {} | |
| size_t InfoLog::getLength() const | |
| { | |
| if (!mLazyStream) | |
| { | |
| return 0; | |
| } | |
| const std::string &logString = mLazyStream->str(); | |
| return logString.empty() ? 0 : logString.length() + 1; | |
| } | |
| void InfoLog::getLog(GLsizei bufSize, GLsizei *length, char *infoLog) const | |
| { | |
| size_t index = 0; | |
| if (bufSize > 0) | |
| { | |
| const std::string logString(str()); | |
| if (!logString.empty()) | |
| { | |
| index = std::min(static_cast<size_t>(bufSize) - 1, logString.length()); | |
| memcpy(infoLog, logString.c_str(), index); | |
| } | |
| infoLog[index] = '\0'; | |
| } | |
| if (length) | |
| { | |
| *length = static_cast<GLsizei>(index); | |
| } | |
| } | |
| // append a sanitized message to the program info log. | |
| // The D3D compiler includes a fake file path in some of the warning or error | |
| // messages, so lets remove all occurrences of this fake file path from the log. | |
| void InfoLog::appendSanitized(const char *message) | |
| { | |
| ensureInitialized(); | |
| std::string msg(message); | |
| size_t found; | |
| do | |
| { | |
| found = msg.find(g_fakepath); | |
| if (found != std::string::npos) | |
| { | |
| msg.erase(found, strlen(g_fakepath)); | |
| } | |
| } while (found != std::string::npos); | |
| if (!msg.empty()) | |
| { | |
| *mLazyStream << message << std::endl; | |
| } | |
| } | |
| void InfoLog::reset() | |
| { | |
| if (mLazyStream) | |
| { | |
| mLazyStream.reset(nullptr); | |
| } | |
| } | |
| bool InfoLog::empty() const | |
| { | |
| if (!mLazyStream) | |
| { | |
| return true; | |
| } | |
| return mLazyStream->rdbuf()->in_avail() == 0; | |
| } | |
| void LogLinkMismatch(InfoLog &infoLog, | |
| const std::string &variableName, | |
| const char *variableType, | |
| LinkMismatchError linkError, | |
| const std::string &mismatchedStructOrBlockFieldName, | |
| ShaderType shaderType1, | |
| ShaderType shaderType2) | |
| { | |
| std::ostringstream stream; | |
| stream << GetLinkMismatchErrorString(linkError) << "s of " << variableType << " '" | |
| << variableName; | |
| if (!mismatchedStructOrBlockFieldName.empty()) | |
| { | |
| stream << "' member '" << variableName << "." << mismatchedStructOrBlockFieldName; | |
| } | |
| stream << "' differ between " << GetShaderTypeString(shaderType1) << " and " | |
| << GetShaderTypeString(shaderType2) << " shaders."; | |
| infoLog << stream.str(); | |
| } | |
| bool IsActiveInterfaceBlock(const sh::InterfaceBlock &interfaceBlock) | |
| { | |
| // Only 'packed' blocks are allowed to be considered inactive. | |
| return interfaceBlock.active || interfaceBlock.layout != sh::BLOCKLAYOUT_PACKED; | |
| } | |
| // VariableLocation implementation. | |
| VariableLocation::VariableLocation() : index(kUnused), arrayIndex(0), ignored(false) {} | |
| VariableLocation::VariableLocation(unsigned int arrayIndexIn, unsigned int index) | |
| : index(index), ignored(false) | |
| { | |
| ASSERT(arrayIndex != GL_INVALID_INDEX); | |
| SetBitField(arrayIndex, arrayIndexIn); | |
| } | |
| // ProgramBindings implementation. | |
| ProgramBindings::ProgramBindings() {} | |
| ProgramBindings::~ProgramBindings() {} | |
| void ProgramBindings::bindLocation(GLuint index, const std::string &name) | |
| { | |
| mBindings[name] = index; | |
| } | |
| int ProgramBindings::getBindingByName(const std::string &name) const | |
| { | |
| auto iter = mBindings.find(name); | |
| return (iter != mBindings.end()) ? iter->second : -1; | |
| } | |
| template <typename T> | |
| int ProgramBindings::getBinding(const T &variable) const | |
| { | |
| return getBindingByName(variable.name); | |
| } | |
| ProgramBindings::const_iterator ProgramBindings::begin() const | |
| { | |
| return mBindings.begin(); | |
| } | |
| ProgramBindings::const_iterator ProgramBindings::end() const | |
| { | |
| return mBindings.end(); | |
| } | |
| std::map<std::string, GLuint> ProgramBindings::getStableIterationMap() const | |
| { | |
| return std::map<std::string, GLuint>(mBindings.begin(), mBindings.end()); | |
| } | |
| // ProgramAliasedBindings implementation. | |
| ProgramAliasedBindings::ProgramAliasedBindings() {} | |
| ProgramAliasedBindings::~ProgramAliasedBindings() {} | |
| void ProgramAliasedBindings::bindLocation(GLuint index, const std::string &name) | |
| { | |
| mBindings[name] = ProgramBinding(index); | |
| // EXT_blend_func_extended spec: "If it specifies the base name of an array, | |
| // it identifies the resources associated with the first element of the array." | |
| // | |
| // Normalize array bindings so that "name" and "name[0]" map to the same entry. | |
| // If this binding is of the form "name[0]", then mark the "name" binding as | |
| // aliased but do not update it yet in case "name" is not actually an array. | |
| size_t nameLengthWithoutArrayIndex; | |
| unsigned int arrayIndex = ParseArrayIndex(name, &nameLengthWithoutArrayIndex); | |
| if (arrayIndex == 0) | |
| { | |
| std::string baseName = name.substr(0u, nameLengthWithoutArrayIndex); | |
| auto iter = mBindings.find(baseName); | |
| if (iter != mBindings.end()) | |
| { | |
| iter->second.aliased = true; | |
| } | |
| } | |
| } | |
| int ProgramAliasedBindings::getBindingByName(const std::string &name) const | |
| { | |
| auto iter = mBindings.find(name); | |
| return (iter != mBindings.end()) ? iter->second.location : -1; | |
| } | |
| int ProgramAliasedBindings::getBindingByLocation(GLuint location) const | |
| { | |
| for (const auto &iter : mBindings) | |
| { | |
| if (iter.second.location == location) | |
| { | |
| return iter.second.location; | |
| } | |
| } | |
| return -1; | |
| } | |
| template <typename T> | |
| int ProgramAliasedBindings::getBinding(const T &variable) const | |
| { | |
| const std::string &name = variable.name; | |
| // Check with the normalized array name if applicable. | |
| if (variable.isArray()) | |
| { | |
| size_t nameLengthWithoutArrayIndex; | |
| unsigned int arrayIndex = ParseArrayIndex(name, &nameLengthWithoutArrayIndex); | |
| if (arrayIndex == 0) | |
| { | |
| std::string baseName = name.substr(0u, nameLengthWithoutArrayIndex); | |
| auto iter = mBindings.find(baseName); | |
| // If "name" exists and is not aliased, that means it was modified more | |
| // recently than its "name[0]" form and should be used instead of that. | |
| if (iter != mBindings.end() && !iter->second.aliased) | |
| { | |
| return iter->second.location; | |
| } | |
| } | |
| else if (arrayIndex == GL_INVALID_INDEX) | |
| { | |
| auto iter = mBindings.find(variable.name); | |
| // If "name" exists and is not aliased, that means it was modified more | |
| // recently than its "name[0]" form and should be used instead of that. | |
| if (iter != mBindings.end() && !iter->second.aliased) | |
| { | |
| return iter->second.location; | |
| } | |
| // The base name was aliased, so use the name with the array notation. | |
| return getBindingByName(name + "[0]"); | |
| } | |
| } | |
| return getBindingByName(name); | |
| } | |
| template int ProgramAliasedBindings::getBinding<UsedUniform>(const UsedUniform &variable) const; | |
| template int ProgramAliasedBindings::getBinding<ProgramOutput>(const ProgramOutput &variable) const; | |
| template int ProgramAliasedBindings::getBinding<sh::ShaderVariable>( | |
| const sh::ShaderVariable &variable) const; | |
| ProgramAliasedBindings::const_iterator ProgramAliasedBindings::begin() const | |
| { | |
| return mBindings.begin(); | |
| } | |
| ProgramAliasedBindings::const_iterator ProgramAliasedBindings::end() const | |
| { | |
| return mBindings.end(); | |
| } | |
| std::map<std::string, ProgramBinding> ProgramAliasedBindings::getStableIterationMap() const | |
| { | |
| return std::map<std::string, ProgramBinding>(mBindings.begin(), mBindings.end()); | |
| } | |
| // ProgramState implementation. | |
| ProgramState::ProgramState(rx::GLImplFactory *factory) | |
| : mLabel(), | |
| mAttachedShaders{}, | |
| mTransformFeedbackBufferMode(GL_INTERLEAVED_ATTRIBS), | |
| mBinaryRetrieveableHint(false), | |
| mSeparable(false), | |
| mExecutable(new ProgramExecutable(factory, &mInfoLog)) | |
| {} | |
| ProgramState::~ProgramState() | |
| { | |
| ASSERT(!hasAnyAttachedShader()); | |
| } | |
| const std::string &ProgramState::getLabel() | |
| { | |
| return mLabel; | |
| } | |
| SharedCompiledShaderState ProgramState::getAttachedShader(ShaderType shaderType) const | |
| { | |
| ASSERT(shaderType != ShaderType::InvalidEnum); | |
| return mAttachedShaders[shaderType]; | |
| } | |
| bool ProgramState::hasAnyAttachedShader() const | |
| { | |
| for (const SharedCompiledShaderState &shader : mAttachedShaders) | |
| { | |
| if (shader) | |
| { | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| ShaderType ProgramState::getAttachedTransformFeedbackStage() const | |
| { | |
| if (mAttachedShaders[ShaderType::Geometry]) | |
| { | |
| return ShaderType::Geometry; | |
| } | |
| if (mAttachedShaders[ShaderType::TessEvaluation]) | |
| { | |
| return ShaderType::TessEvaluation; | |
| } | |
| return ShaderType::Vertex; | |
| } | |
| // The common portion of parallel link and load jobs | |
| class Program::MainLinkLoadTask : public angle::Closure | |
| { | |
| public: | |
| MainLinkLoadTask(const std::shared_ptr<angle::WorkerThreadPool> &subTaskWorkerPool, | |
| ProgramState *state, | |
| std::shared_ptr<rx::LinkTask> &&linkTask) | |
| : mSubTaskWorkerPool(subTaskWorkerPool), mState(*state), mLinkTask(std::move(linkTask)) | |
| { | |
| ASSERT(subTaskWorkerPool.get()); | |
| } | |
| ~MainLinkLoadTask() override = default; | |
| angle::Result getResult(const Context *context) | |
| { | |
| InfoLog &infoLog = mState.getExecutable().getInfoLog(); | |
| ANGLE_TRY(mResult); | |
| ANGLE_TRY(mLinkTask->getResult(context, infoLog)); | |
| for (const std::shared_ptr<rx::LinkSubTask> &task : mSubTasks) | |
| { | |
| ANGLE_TRY(task->getResult(context, infoLog)); | |
| } | |
| return angle::Result::Continue; | |
| } | |
| void waitSubTasks() { angle::WaitableEvent::WaitMany(&mSubTaskWaitableEvents); } | |
| bool areSubTasksLinking() | |
| { | |
| if (mLinkTask->isLinkingInternally()) | |
| { | |
| return true; | |
| } | |
| return !angle::WaitableEvent::AllReady(&mSubTaskWaitableEvents); | |
| } | |
| protected: | |
| void scheduleSubTasks(std::vector<std::shared_ptr<rx::LinkSubTask>> &&linkSubTasks, | |
| std::vector<std::shared_ptr<rx::LinkSubTask>> &&postLinkSubTasks) | |
| { | |
| // Only one of linkSubTasks or postLinkSubTasks should have tasks. This is because | |
| // currently, there is no support for ordering them. | |
| ASSERT(linkSubTasks.empty() || postLinkSubTasks.empty()); | |
| // Schedule link subtasks | |
| mSubTasks = std::move(linkSubTasks); | |
| ScheduleSubTasks(mSubTaskWorkerPool, mSubTasks, &mSubTaskWaitableEvents); | |
| // Schedule post-link subtasks | |
| mState.mExecutable->mPostLinkSubTasks = std::move(postLinkSubTasks); | |
| ScheduleSubTasks(mSubTaskWorkerPool, mState.mExecutable->mPostLinkSubTasks, | |
| &mState.mExecutable->mPostLinkSubTaskWaitableEvents); | |
| // No further use for worker pool. Release it earlier than the destructor (to avoid | |
| // situations such as http://anglebug.com/42267099) | |
| mSubTaskWorkerPool.reset(); | |
| } | |
| std::shared_ptr<angle::WorkerThreadPool> mSubTaskWorkerPool; | |
| ProgramState &mState; | |
| std::shared_ptr<rx::LinkTask> mLinkTask; | |
| // Subtask and wait events | |
| std::vector<std::shared_ptr<rx::LinkSubTask>> mSubTasks; | |
| std::vector<std::shared_ptr<angle::WaitableEvent>> mSubTaskWaitableEvents; | |
| // The result of the front-end portion of the link. The backend's result is retrieved via | |
| // mLinkTask->getResult(). The subtask results are retrieved via mSubTasks similarly. | |
| angle::Result mResult; | |
| }; | |
| class Program::MainLinkTask final : public Program::MainLinkLoadTask | |
| { | |
| public: | |
| MainLinkTask(const std::shared_ptr<angle::WorkerThreadPool> &subTaskWorkerPool, | |
| const Caps &caps, | |
| const Limitations &limitations, | |
| const Version &clientVersion, | |
| bool isWebGL, | |
| Program *program, | |
| ProgramState *state, | |
| LinkingVariables *linkingVariables, | |
| ProgramLinkedResources *resources, | |
| std::shared_ptr<rx::LinkTask> &&linkTask) | |
| : MainLinkLoadTask(subTaskWorkerPool, state, std::move(linkTask)), | |
| mCaps(caps), | |
| mLimitations(limitations), | |
| mClientVersion(clientVersion), | |
| mIsWebGL(isWebGL), | |
| mProgram(program), | |
| mLinkingVariables(linkingVariables), | |
| mResources(resources) | |
| {} | |
| ~MainLinkTask() override = default; | |
| void operator()() override { mResult = linkImpl(); } | |
| private: | |
| angle::Result linkImpl(); | |
| // State needed for link | |
| const Caps &mCaps; | |
| const Limitations &mLimitations; | |
| const Version mClientVersion; | |
| const bool mIsWebGL; | |
| Program *mProgram; | |
| LinkingVariables *mLinkingVariables; | |
| ProgramLinkedResources *mResources; | |
| }; | |
| class Program::MainLoadTask final : public Program::MainLinkLoadTask | |
| { | |
| public: | |
| MainLoadTask(const std::shared_ptr<angle::WorkerThreadPool> &subTaskWorkerPool, | |
| Program *program, | |
| ProgramState *state, | |
| std::shared_ptr<rx::LinkTask> &&loadTask) | |
| : MainLinkLoadTask(subTaskWorkerPool, state, std::move(loadTask)) | |
| {} | |
| ~MainLoadTask() override = default; | |
| void operator()() override { mResult = loadImpl(); } | |
| private: | |
| angle::Result loadImpl(); | |
| }; | |
| class Program::MainLinkLoadEvent final : public LinkEvent | |
| { | |
| public: | |
| MainLinkLoadEvent(const std::shared_ptr<MainLinkLoadTask> &linkTask, | |
| const std::shared_ptr<angle::WaitableEvent> &waitEvent) | |
| : mLinkTask(linkTask), mWaitableEvent(waitEvent) | |
| {} | |
| ~MainLinkLoadEvent() override {} | |
| angle::Result wait(const Context *context) override | |
| { | |
| ANGLE_TRACE_EVENT0("gpu.angle", "Program::MainLinkLoadEvent::wait"); | |
| mWaitableEvent->wait(); | |
| mLinkTask->waitSubTasks(); | |
| return mLinkTask->getResult(context); | |
| } | |
| bool isLinking() override | |
| { | |
| return !mWaitableEvent->isReady() || mLinkTask->areSubTasksLinking(); | |
| } | |
| private: | |
| std::shared_ptr<MainLinkLoadTask> mLinkTask; | |
| std::shared_ptr<angle::WaitableEvent> mWaitableEvent; | |
| }; | |
| angle::Result Program::MainLinkTask::linkImpl() | |
| { | |
| ProgramMergedVaryings mergedVaryings; | |
| // Do the front-end portion of the link. | |
| ANGLE_TRY(mProgram->linkJobImpl(mCaps, mLimitations, mClientVersion, mIsWebGL, | |
| mLinkingVariables, mResources, &mergedVaryings)); | |
| // Next, do the backend portion of the link. If there are any subtasks to be scheduled, they | |
| // are collected now. | |
| std::vector<std::shared_ptr<rx::LinkSubTask>> linkSubTasks; | |
| std::vector<std::shared_ptr<rx::LinkSubTask>> postLinkSubTasks; | |
| mLinkTask->link(*mResources, mergedVaryings, &linkSubTasks, &postLinkSubTasks); | |
| // Must be after backend's link to avoid misleading the linker about input/output variables. | |
| mState.updateProgramInterfaceInputs(); | |
| mState.updateProgramInterfaceOutputs(); | |
| // Schedule the subtasks | |
| scheduleSubTasks(std::move(linkSubTasks), std::move(postLinkSubTasks)); | |
| return angle::Result::Continue; | |
| } | |
| angle::Result Program::MainLoadTask::loadImpl() | |
| { | |
| std::vector<std::shared_ptr<rx::LinkSubTask>> linkSubTasks; | |
| std::vector<std::shared_ptr<rx::LinkSubTask>> postLinkSubTasks; | |
| mLinkTask->load(&linkSubTasks, &postLinkSubTasks); | |
| // Schedule the subtasks | |
| scheduleSubTasks(std::move(linkSubTasks), std::move(postLinkSubTasks)); | |
| return angle::Result::Continue; | |
| } | |
| Program::Program(rx::GLImplFactory *factory, ShaderProgramManager *manager, ShaderProgramID handle) | |
| : mSerial(factory->generateSerial()), | |
| mState(factory), | |
| mProgram(factory->createProgram(mState)), | |
| mValidated(false), | |
| mDeleteStatus(false), | |
| mIsBinaryCached(true), | |
| mLinked(false), | |
| mProgramHash{0}, | |
| mRefCount(0), | |
| mResourceManager(manager), | |
| mHandle(handle), | |
| mAttachedShaders{} | |
| { | |
| ASSERT(mProgram); | |
| unlink(); | |
| } | |
| Program::~Program() | |
| { | |
| ASSERT(!mProgram); | |
| } | |
| void Program::onDestroy(const Context *context) | |
| { | |
| resolveLink(context); | |
| waitForPostLinkTasks(context); | |
| for (ShaderType shaderType : AllShaderTypes()) | |
| { | |
| Shader *shader = getAttachedShader(shaderType); | |
| if (shader != nullptr) | |
| { | |
| shader->release(context); | |
| } | |
| mState.mShaderCompileJobs[shaderType].reset(); | |
| mState.mAttachedShaders[shaderType].reset(); | |
| mAttachedShaders[shaderType] = nullptr; | |
| } | |
| mProgram->destroy(context); | |
| UninstallExecutable(context, &mState.mExecutable); | |
| ASSERT(!mState.hasAnyAttachedShader()); | |
| SafeDelete(mProgram); | |
| mBinary.clear(); | |
| delete this; | |
| } | |
| ShaderProgramID Program::id() const | |
| { | |
| return mHandle; | |
| } | |
| angle::Result Program::setLabel(const Context *context, const std::string &label) | |
| { | |
| ASSERT(!mLinkingState); | |
| mState.mLabel = label; | |
| if (mProgram) | |
| { | |
| return mProgram->onLabelUpdate(context); | |
| } | |
| return angle::Result::Continue; | |
| } | |
| const std::string &Program::getLabel() const | |
| { | |
| ASSERT(!mLinkingState); | |
| return mState.mLabel; | |
| } | |
| void Program::attachShader(const Context *context, Shader *shader) | |
| { | |
| resolveLink(context); | |
| ShaderType shaderType = shader->getType(); | |
| ASSERT(shaderType != ShaderType::InvalidEnum); | |
| shader->addRef(); | |
| mAttachedShaders[shaderType] = shader; | |
| } | |
| void Program::detachShader(const Context *context, Shader *shader) | |
| { | |
| resolveLink(context); | |
| ShaderType shaderType = shader->getType(); | |
| ASSERT(shaderType != ShaderType::InvalidEnum); | |
| ASSERT(mAttachedShaders[shaderType] == shader); | |
| shader->release(context); | |
| mAttachedShaders[shaderType] = nullptr; | |
| mState.mShaderCompileJobs[shaderType].reset(); | |
| mState.mAttachedShaders[shaderType].reset(); | |
| } | |
| int Program::getAttachedShadersCount() const | |
| { | |
| ASSERT(!mLinkingState); | |
| int numAttachedShaders = 0; | |
| for (const Shader *shader : mAttachedShaders) | |
| { | |
| if (shader != nullptr) | |
| { | |
| ++numAttachedShaders; | |
| } | |
| } | |
| return numAttachedShaders; | |
| } | |
| Shader *Program::getAttachedShader(ShaderType shaderType) const | |
| { | |
| return mAttachedShaders[shaderType]; | |
| } | |
| void Program::bindAttributeLocation(const Context *context, GLuint index, const char *name) | |
| { | |
| ASSERT(!mLinkingState); | |
| mState.mAttributeBindings.bindLocation(index, name); | |
| } | |
| void Program::bindUniformLocation(const Context *context, | |
| UniformLocation location, | |
| const char *name) | |
| { | |
| ASSERT(!mLinkingState); | |
| mState.mUniformLocationBindings.bindLocation(location.value, name); | |
| } | |
| void Program::bindFragmentOutputLocation(const Context *context, GLuint index, const char *name) | |
| { | |
| ASSERT(!mLinkingState); | |
| mState.mFragmentOutputLocations.bindLocation(index, name); | |
| } | |
| void Program::bindFragmentOutputIndex(const Context *context, GLuint index, const char *name) | |
| { | |
| ASSERT(!mLinkingState); | |
| mState.mFragmentOutputIndexes.bindLocation(index, name); | |
| } | |
| void Program::makeNewExecutable(const Context *context) | |
| { | |
| ASSERT(!mLinkingState); | |
| waitForPostLinkTasks(context); | |
| // Unlink the program, but do not clear the validation-related caching yet, since we can still | |
| // use the previously linked program if linking the shaders fails. | |
| mLinked = false; | |
| mLinkingState = std::make_unique<LinkingState>(); | |
| // By default, set the link event as failing. If link succeeds, it will be replaced by the | |
| // appropriate event. | |
| mLinkingState->linkEvent = std::make_unique<LinkEventDone>(angle::Result::Stop); | |
| InstallExecutable( | |
| context, | |
| std::make_shared<ProgramExecutable>(context->getImplementation(), &mState.mInfoLog), | |
| &mState.mExecutable); | |
| onStateChange(angle::SubjectMessage::ProgramUnlinked); | |
| // If caching is disabled, consider it cached! | |
| mIsBinaryCached = context->getFrontendFeatures().disableProgramCaching.enabled; | |
| // Start with a clean slate every time a new executable is installed. Note that the executable | |
| // binary is not mutable; once linked it remains constant. When the program changes, a new | |
| // executable is installed in this function. | |
| mBinary.clear(); | |
| } | |
| void Program::setupExecutableForLink(const Context *context) | |
| { | |
| // Create a new executable to hold the result of the link. The previous executable may still be | |
| // referenced by the contexts the program is current on, and any program pipelines it may be | |
| // used in. Once link succeeds, the users of the program are notified to update their | |
| // executables. | |
| makeNewExecutable(context); | |
| // For every attached shader, get the compile job and compiled state. This is done at link time | |
| // (instead of earlier, such as attachShader time), because the shader could get recompiled | |
| // between attach and link. | |
| // | |
| // Additionally, make sure the backend is also able to cache the compiled state of its own | |
| // ShaderImpl objects. | |
| ShaderMap<rx::ShaderImpl *> shaderImpls = {}; | |
| for (ShaderType shaderType : AllShaderTypes()) | |
| { | |
| Shader *shader = mAttachedShaders[shaderType]; | |
| SharedCompileJob compileJob; | |
| SharedCompiledShaderState shaderCompiledState; | |
| if (shader != nullptr) | |
| { | |
| compileJob = shader->getCompileJob(&shaderCompiledState); | |
| shaderImpls[shaderType] = shader->getImplementation(); | |
| } | |
| mState.mShaderCompileJobs[shaderType] = std::move(compileJob); | |
| mState.mAttachedShaders[shaderType] = std::move(shaderCompiledState); | |
| } | |
| mProgram->prepareForLink(shaderImpls); | |
| const angle::FrontendFeatures &frontendFeatures = context->getFrontendFeatures(); | |
| if (frontendFeatures.dumpShaderSource.enabled) | |
| { | |
| dumpProgramInfo(context); | |
| } | |
| // Make sure the executable state is in sync with the program. | |
| // | |
| // The transform feedback buffer mode is duplicated in the executable as it is the only | |
| // link-input that is also needed at draw time. | |
| // | |
| // The transform feedback varying names are duplicated because the program pipeline link is not | |
| // currently able to use the link result of the program directly (and redoes the link, using | |
| // these names). | |
| // | |
| // The isSeparable state is duplicated for convenience; it is used when setting sampler/image | |
| // uniforms. | |
| mState.mExecutable->mPod.transformFeedbackBufferMode = mState.mTransformFeedbackBufferMode; | |
| mState.mExecutable->mTransformFeedbackVaryingNames = mState.mTransformFeedbackVaryingNames; | |
| mState.mExecutable->mPod.isSeparable = mState.mSeparable; | |
| mState.mInfoLog.reset(); | |
| } | |
| void Program::syncExecutableOnSuccessfulLink() | |
| { | |
| // Sync GL_PROGRAM_BINARY_RETRIEVABLE_HINT to the effective value when linking successfully. | |
| mState.mExecutable->mBinaryRetrieveableHint = mState.mBinaryRetrieveableHint; | |
| } | |
| angle::Result Program::link(const Context *context, angle::JobResultExpectancy resultExpectancy) | |
| { | |
| auto *platform = ANGLEPlatformCurrent(); | |
| double startTime = platform->currentTime(platform); | |
| setupExecutableForLink(context); | |
| mProgramHash = {0}; | |
| MemoryProgramCache *cache = (context->getFrontendFeatures().disableProgramCaching.enabled) | |
| ? nullptr | |
| : context->getMemoryProgramCache(); | |
| // TODO: http://anglebug.com/42263141: Enable program caching for separable programs | |
| if (cache && !isSeparable()) | |
| { | |
| std::lock_guard<angle::SimpleMutex> cacheLock(context->getProgramCacheMutex()); | |
| egl::CacheGetResult result = egl::CacheGetResult::NotFound; | |
| ANGLE_TRY(cache->getProgram(context, this, &mProgramHash, &result)); | |
| switch (result) | |
| { | |
| case egl::CacheGetResult::Success: | |
| { | |
| // No need to care about the compile jobs any more. | |
| mState.mShaderCompileJobs = {}; | |
| std::scoped_lock lock(mHistogramMutex); | |
| // Succeeded in loading the binaries in the front-end, back end may still be loading | |
| // asynchronously | |
| double delta = platform->currentTime(platform) - startTime; | |
| int us = static_cast<int>(delta * 1000'000.0); | |
| ANGLE_HISTOGRAM_COUNTS("GPU.ANGLE.ProgramCache.ProgramCacheHitTimeUS", us); | |
| return angle::Result::Continue; | |
| } | |
| case egl::CacheGetResult::Rejected: | |
| // If the program binary was found but rejected, the program executable may be in an | |
| // inconsistent half-loaded state. In that case, start over. | |
| mLinkingState.reset(); | |
| setupExecutableForLink(context); | |
| break; | |
| case egl::CacheGetResult::NotFound: | |
| default: | |
| break; | |
| } | |
| } | |
| const Caps &caps = context->getCaps(); | |
| const Limitations &limitations = context->getLimitations(); | |
| const Version &clientVersion = context->getClientVersion(); | |
| const bool isWebGL = context->isWebGL(); | |
| // Ask the backend to prepare the link task. | |
| std::shared_ptr<rx::LinkTask> linkTask; | |
| ANGLE_TRY(mProgram->link(context, &linkTask)); | |
| std::unique_ptr<LinkingState> linkingState = std::make_unique<LinkingState>(); | |
| // Prepare the main link job | |
| std::shared_ptr<MainLinkLoadTask> mainLinkTask(new MainLinkTask( | |
| context->getLinkSubTaskThreadPool(), caps, limitations, clientVersion, isWebGL, this, | |
| &mState, &linkingState->linkingVariables, &linkingState->resources, std::move(linkTask))); | |
| // While the subtasks are currently always thread-safe, the main task is not safe on all | |
| // backends. A front-end feature selects whether the single-threaded pool must be used. | |
| const angle::JobThreadSafety threadSafety = | |
| context->getFrontendFeatures().linkJobIsThreadSafe.enabled ? angle::JobThreadSafety::Safe | |
| : angle::JobThreadSafety::Unsafe; | |
| std::shared_ptr<angle::WaitableEvent> mainLinkEvent = | |
| context->postCompileLinkTask(mainLinkTask, threadSafety, resultExpectancy); | |
| mLinkingState = std::move(linkingState); | |
| mLinkingState->linkingFromBinary = false; | |
| mLinkingState->linkEvent = std::make_unique<MainLinkLoadEvent>(mainLinkTask, mainLinkEvent); | |
| return angle::Result::Continue; | |
| } | |
| angle::Result Program::linkJobImpl(const Caps &caps, | |
| const Limitations &limitations, | |
| const Version &clientVersion, | |
| bool isWebGL, | |
| LinkingVariables *linkingVariables, | |
| ProgramLinkedResources *resources, | |
| ProgramMergedVaryings *mergedVaryingsOut) | |
| { | |
| // Cache load failed, fall through to normal linking. | |
| unlink(); | |
| // Validate we have properly attached shaders after checking the cache. Since the input to the | |
| // shaders is part of the cache key, if there was a cache hit, the shaders would have linked | |
| // correctly. | |
| if (!linkValidateShaders()) | |
| { | |
| return angle::Result::Stop; | |
| } | |
| linkShaders(); | |
| linkingVariables->initForProgram(mState); | |
| resources->init( | |
| &mState.mExecutable->mUniformBlocks, &mState.mExecutable->mUniforms, | |
| &mState.mExecutable->mUniformNames, &mState.mExecutable->mUniformMappedNames, | |
| &mState.mExecutable->mShaderStorageBlocks, &mState.mExecutable->mBufferVariables, | |
| &mState.mExecutable->mAtomicCounterBuffers, &mState.mExecutable->mPixelLocalStorageFormats); | |
| updateLinkedShaderStages(); | |
| InitUniformBlockLinker(mState, &resources->uniformBlockLinker); | |
| InitShaderStorageBlockLinker(mState, &resources->shaderStorageBlockLinker); | |
| if (mState.mAttachedShaders[ShaderType::Compute]) | |
| { | |
| GLuint combinedImageUniforms = 0; | |
| if (!linkUniforms(caps, clientVersion, &resources->unusedUniforms, &combinedImageUniforms)) | |
| { | |
| return angle::Result::Stop; | |
| } | |
| GLuint combinedShaderStorageBlocks = 0u; | |
| if (!LinkValidateProgramInterfaceBlocks( | |
| caps, clientVersion, isWebGL, mState.mExecutable->getLinkedShaderStages(), | |
| *resources, mState.mInfoLog, &combinedShaderStorageBlocks)) | |
| { | |
| return angle::Result::Stop; | |
| } | |
| // [OpenGL ES 3.1] Chapter 8.22 Page 203: | |
| // A link error will be generated if the sum of the number of active image uniforms used in | |
| // all shaders, the number of active shader storage blocks, and the number of active | |
| // fragment shader outputs exceeds the implementation-dependent value of | |
| // MAX_COMBINED_SHADER_OUTPUT_RESOURCES. | |
| if (combinedImageUniforms + combinedShaderStorageBlocks > | |
| static_cast<GLuint>(caps.maxCombinedShaderOutputResources)) | |
| { | |
| mState.mInfoLog | |
| << "The sum of the number of active image uniforms, active shader storage blocks " | |
| "and active fragment shader outputs exceeds " | |
| "MAX_COMBINED_SHADER_OUTPUT_RESOURCES (" | |
| << caps.maxCombinedShaderOutputResources << ")"; | |
| return angle::Result::Stop; | |
| } | |
| } | |
| else | |
| { | |
| if (!linkAttributes(caps, limitations, isWebGL)) | |
| { | |
| return angle::Result::Stop; | |
| } | |
| if (!linkVaryings()) | |
| { | |
| return angle::Result::Stop; | |
| } | |
| GLuint combinedImageUniforms = 0; | |
| if (!linkUniforms(caps, clientVersion, &resources->unusedUniforms, &combinedImageUniforms)) | |
| { | |
| return angle::Result::Stop; | |
| } | |
| GLuint combinedShaderStorageBlocks = 0u; | |
| if (!LinkValidateProgramInterfaceBlocks( | |
| caps, clientVersion, isWebGL, mState.mExecutable->getLinkedShaderStages(), | |
| *resources, mState.mInfoLog, &combinedShaderStorageBlocks)) | |
| { | |
| return angle::Result::Stop; | |
| } | |
| if (!LinkValidateProgramGlobalNames(mState.mInfoLog, getExecutable(), *linkingVariables)) | |
| { | |
| return angle::Result::Stop; | |
| } | |
| const SharedCompiledShaderState &vertexShader = mState.mAttachedShaders[ShaderType::Vertex]; | |
| if (vertexShader) | |
| { | |
| mState.mExecutable->mPod.numViews = vertexShader->numViews; | |
| mState.mExecutable->mPod.hasClipDistance = | |
| vertexShader->metadataFlags.test(sh::MetadataFlags::HasClipDistance); | |
| mState.mExecutable->mPod.specConstUsageBits |= vertexShader->specConstUsageBits; | |
| } | |
| const SharedCompiledShaderState &fragmentShader = | |
| mState.mAttachedShaders[ShaderType::Fragment]; | |
| if (fragmentShader) | |
| { | |
| ASSERT(mState.mExecutable->mOutputVariables.empty()); | |
| mState.mExecutable->mOutputVariables.reserve( | |
| fragmentShader->activeOutputVariables.size()); | |
| for (const sh::ShaderVariable &shaderVariable : fragmentShader->activeOutputVariables) | |
| { | |
| mState.mExecutable->mOutputVariables.emplace_back(shaderVariable); | |
| } | |
| if (!mState.mExecutable->linkValidateOutputVariables( | |
| caps, clientVersion, combinedImageUniforms, combinedShaderStorageBlocks, | |
| fragmentShader->shaderVersion, mState.mFragmentOutputLocations, | |
| mState.mFragmentOutputIndexes)) | |
| { | |
| return angle::Result::Stop; | |
| } | |
| mState.mExecutable->mPod.hasDiscard = | |
| fragmentShader->metadataFlags.test(sh::MetadataFlags::HasDiscard); | |
| mState.mExecutable->mPod.enablesPerSampleShading = | |
| fragmentShader->metadataFlags.test(sh::MetadataFlags::EnablesPerSampleShading); | |
| mState.mExecutable->mPod.hasDepthInputAttachment = | |
| fragmentShader->metadataFlags.test(sh::MetadataFlags::HasDepthInputAttachment); | |
| mState.mExecutable->mPod.hasStencilInputAttachment = | |
| fragmentShader->metadataFlags.test(sh::MetadataFlags::HasStencilInputAttachment); | |
| mState.mExecutable->mPod.advancedBlendEquations = | |
| fragmentShader->advancedBlendEquations; | |
| mState.mExecutable->mPod.specConstUsageBits |= fragmentShader->specConstUsageBits; | |
| for (uint32_t index = 0; index < IMPLEMENTATION_MAX_DRAW_BUFFERS; ++index) | |
| { | |
| const sh::MetadataFlags flag = static_cast<sh::MetadataFlags>( | |
| static_cast<uint32_t>(sh::MetadataFlags::HasInputAttachment0) + index); | |
| if (fragmentShader->metadataFlags.test(flag)) | |
| { | |
| mState.mExecutable->mPod.fragmentInoutIndices.set(index); | |
| } | |
| } | |
| } | |
| *mergedVaryingsOut = GetMergedVaryingsFromLinkingVariables(*linkingVariables); | |
| if (!mState.mExecutable->linkMergedVaryings(caps, limitations, clientVersion, isWebGL, | |
| *mergedVaryingsOut, *linkingVariables, | |
| &resources->varyingPacking)) | |
| { | |
| return angle::Result::Stop; | |
| } | |
| } | |
| mState.mExecutable->saveLinkedStateInfo(mState); | |
| return angle::Result::Continue; | |
| } | |
| bool Program::isLinking() const | |
| { | |
| return mLinkingState.get() && mLinkingState->linkEvent && mLinkingState->linkEvent->isLinking(); | |
| } | |
| bool Program::isBinaryReady(const Context *context) | |
| { | |
| if (mState.mExecutable->mPostLinkSubTasks.empty()) | |
| { | |
| // Ensure the program binary is cached, even if the backend waits for post-link tasks | |
| // without the knowledge of the front-end. | |
| cacheProgramBinaryIfNotAlready(context); | |
| return true; | |
| } | |
| const bool allPostLinkTasksComplete = | |
| angle::WaitableEvent::AllReady(&mState.mExecutable->getPostLinkSubTaskWaitableEvents()); | |
| // Once the binary is ready, the |glGetProgramBinary| call will result in | |
| // |waitForPostLinkTasks| which in turn may internally cache the binary. However, for the sake | |
| // of blob cache tests, call |waitForPostLinkTasks| anyway if tasks are already complete. | |
| if (allPostLinkTasksComplete) | |
| { | |
| waitForPostLinkTasks(context); | |
| } | |
| return allPostLinkTasksComplete; | |
| } | |
| void Program::resolveLinkImpl(const Context *context) | |
| { | |
| ASSERT(mLinkingState.get()); | |
| angle::Result result = mLinkingState->linkEvent->wait(context); | |
| mLinked = result == angle::Result::Continue; | |
| std::unique_ptr<LinkingState> linkingState = std::move(mLinkingState); | |
| if (!mLinked) | |
| { | |
| // If the link fails, the spec allows program queries to either return empty results (all | |
| // zeros) or whatever parts of the link happened to have been done before the failure: | |
| // | |
| // > Implementations may return information on variables and interface blocks that would | |
| // > have been active had the program been linked successfully. In cases where the link | |
| // > failed because the program required too many resources, these commands may help | |
| // > applications determine why limits were exceeded. However, the information returned in | |
| // > this case is implementation-dependent and may be incomplete. | |
| // | |
| // The above means that it's ok for ANGLE to reset the executable here, but it *may* be | |
| // helpful to applications if it doesn't. We do reset it however, the info log should | |
| // already have enough debug information for the application. | |
| mState.mExecutable->reset(); | |
| return; | |
| } | |
| // According to GLES 3.0/3.1 spec for LinkProgram and UseProgram, | |
| // Only successfully linked program can replace the executables. | |
| ASSERT(mLinked); | |
| syncExecutableOnSuccessfulLink(); | |
| // In case of a successful link, it is no longer required for the attached shaders to hold on to | |
| // the memory they have used. Therefore, the shader compilations are resolved to save memory. | |
| for (Shader *shader : mAttachedShaders) | |
| { | |
| if (shader != nullptr) | |
| { | |
| shader->resolveCompile(context); | |
| } | |
| } | |
| // Mark implementation-specific unreferenced uniforms as ignored. | |
| std::vector<ImageBinding> *imageBindings = getExecutable().getImageBindings(); | |
| mProgram->markUnusedUniformLocations(&mState.mExecutable->mUniformLocations, | |
| &mState.mExecutable->mSamplerBindings, imageBindings); | |
| // Must be called after markUnusedUniformLocations. | |
| postResolveLink(context); | |
| // Notify observers that a new linked executable is available. If this program is current on a | |
| // context, the executable is reinstalled. If it is attached to a PPO, it is installed there | |
| // and the PPO is marked as needing to be linked again. | |
| onStateChange(angle::SubjectMessage::ProgramRelinked); | |
| // Cache the program if: | |
| // | |
| // - Not loading from binary, in which case the program is already in the cache. | |
| // - There are no post link tasks. If there are any, waitForPostLinkTasks will do this | |
| // instead. | |
| // * Note that serialize() calls waitForPostLinkTasks, so caching the binary here | |
| // effectively forces a wait for the post-link tasks. | |
| // | |
| if (!linkingState->linkingFromBinary && mState.mExecutable->mPostLinkSubTasks.empty()) | |
| { | |
| cacheProgramBinaryIfNotAlready(context); | |
| } | |
| } | |
| void Program::waitForPostLinkTasks(const Context *context) | |
| { | |
| // No-op if no tasks. | |
| mState.mExecutable->waitForPostLinkTasks(context); | |
| // Now that the subtasks are done, cache the binary (this was deferred in resolveLinkImpl). | |
| cacheProgramBinaryIfNotAlready(context); | |
| } | |
| void Program::updateLinkedShaderStages() | |
| { | |
| mState.mExecutable->resetLinkedShaderStages(); | |
| for (ShaderType shaderType : AllShaderTypes()) | |
| { | |
| if (mState.mAttachedShaders[shaderType]) | |
| { | |
| mState.mExecutable->setLinkedShaderStages(shaderType); | |
| } | |
| } | |
| } | |
| void ProgramState::updateActiveSamplers() | |
| { | |
| mExecutable->mActiveSamplerRefCounts.fill(0); | |
| mExecutable->updateActiveSamplers(*mExecutable); | |
| } | |
| void ProgramState::updateProgramInterfaceInputs() | |
| { | |
| const ShaderType firstAttachedShaderType = mExecutable->getFirstLinkedShaderStageType(); | |
| if (firstAttachedShaderType == ShaderType::Vertex) | |
| { | |
| // Vertex attributes are already what we need, so nothing to do | |
| return; | |
| } | |
| const SharedCompiledShaderState &shader = getAttachedShader(firstAttachedShaderType); | |
| ASSERT(shader); | |
| // Copy over each input varying, since the Shader could go away | |
| if (shader->shaderType == ShaderType::Compute) | |
| { | |
| for (const sh::ShaderVariable &attribute : shader->allAttributes) | |
| { | |
| // Compute Shaders have the following built-in input variables. | |
| // | |
| // in uvec3 gl_NumWorkGroups; | |
| // in uvec3 gl_WorkGroupID; | |
| // in uvec3 gl_LocalInvocationID; | |
| // in uvec3 gl_GlobalInvocationID; | |
| // in uint gl_LocalInvocationIndex; | |
| // They are all vecs or uints, so no special handling is required. | |
| mExecutable->mProgramInputs.emplace_back(attribute); | |
| } | |
| } | |
| else | |
| { | |
| for (const sh::ShaderVariable &varying : shader->inputVaryings) | |
| { | |
| UpdateInterfaceVariable(&mExecutable->mProgramInputs, varying); | |
| } | |
| } | |
| } | |
| void ProgramState::updateProgramInterfaceOutputs() | |
| { | |
| const ShaderType lastAttachedShaderType = mExecutable->getLastLinkedShaderStageType(); | |
| if (lastAttachedShaderType == ShaderType::Fragment) | |
| { | |
| // Fragment outputs are already what we need, so nothing to do | |
| return; | |
| } | |
| if (lastAttachedShaderType == ShaderType::Compute) | |
| { | |
| // If the program only contains a Compute Shader, then there are no user-defined outputs. | |
| return; | |
| } | |
| const SharedCompiledShaderState &shader = getAttachedShader(lastAttachedShaderType); | |
| ASSERT(shader); | |
| // Copy over each output varying, since the Shader could go away | |
| for (const sh::ShaderVariable &varying : shader->outputVaryings) | |
| { | |
| UpdateInterfaceVariable(&mExecutable->mOutputVariables, varying); | |
| } | |
| } | |
| // Returns the program object to an unlinked state, before re-linking, or at destruction | |
| void Program::unlink() | |
| { | |
| // There is always a new executable created on link, so the executable is already in a clean | |
| // state. | |
| mValidated = false; | |
| } | |
| angle::Result Program::setBinary(const Context *context, | |
| GLenum binaryFormat, | |
| const void *binary, | |
| GLsizei length) | |
| { | |
| ASSERT(binaryFormat == GL_PROGRAM_BINARY_ANGLE); | |
| makeNewExecutable(context); | |
| egl::CacheGetResult result = egl::CacheGetResult::NotFound; | |
| return loadBinary(context, binary, length, &result); | |
| } | |
| angle::Result Program::loadBinary(const Context *context, | |
| const void *binary, | |
| GLsizei length, | |
| egl::CacheGetResult *resultOut) | |
| { | |
| *resultOut = egl::CacheGetResult::Rejected; | |
| ASSERT(mLinkingState); | |
| unlink(); | |
| BinaryInputStream stream(binary, length); | |
| if (!deserialize(context, stream)) | |
| { | |
| return angle::Result::Continue; | |
| } | |
| // Currently we require the full shader text to compute the program hash. | |
| // We could also store the binary in the internal program cache. | |
| // Initialize the uniform block -> buffer index map based on serialized data. | |
| mState.mExecutable->initInterfaceBlockBindings(); | |
| // If load does not succeed, we know for sure that the binary is not compatible with the | |
| // backend. The loaded binary could have been read from the on-disk shader cache and be | |
| // corrupted or serialized with different revision and subsystem id than the currently loaded | |
| // backend. Returning to the caller results in link happening using the original shader | |
| // sources. | |
| std::shared_ptr<rx::LinkTask> loadTask; | |
| ANGLE_TRY(mProgram->load(context, &stream, &loadTask, resultOut)); | |
| if (*resultOut == egl::CacheGetResult::Rejected) | |
| { | |
| return angle::Result::Continue; | |
| } | |
| std::unique_ptr<LinkEvent> loadEvent; | |
| if (loadTask) | |
| { | |
| std::shared_ptr<MainLinkLoadTask> mainLoadTask(new MainLoadTask( | |
| context->getLinkSubTaskThreadPool(), this, &mState, std::move(loadTask))); | |
| std::shared_ptr<angle::WaitableEvent> mainLoadEvent = | |
| context->getShaderCompileThreadPool()->postWorkerTask(mainLoadTask); | |
| loadEvent = std::make_unique<MainLinkLoadEvent>(mainLoadTask, mainLoadEvent); | |
| } | |
| else | |
| { | |
| loadEvent = std::make_unique<LinkEventDone>(angle::Result::Continue); | |
| } | |
| mLinkingState->linkingFromBinary = true; | |
| mLinkingState->linkEvent = std::move(loadEvent); | |
| // Don't attempt to cache the binary that's just loaded | |
| mIsBinaryCached = true; | |
| *resultOut = egl::CacheGetResult::Success; | |
| return angle::Result::Continue; | |
| } | |
| angle::Result Program::getBinary(Context *context, | |
| GLenum *binaryFormat, | |
| void *binary, | |
| GLsizei bufSize, | |
| GLsizei *length) | |
| { | |
| if (!mState.mExecutable->mBinaryRetrieveableHint) | |
| { | |
| ANGLE_PERF_WARNING( | |
| context->getState().getDebug(), GL_DEBUG_SEVERITY_LOW, | |
| "Saving program binary without GL_PROGRAM_BINARY_RETRIEVABLE_HINT is suboptimal."); | |
| } | |
| ASSERT(!mLinkingState); | |
| if (binaryFormat) | |
| { | |
| *binaryFormat = GL_PROGRAM_BINARY_ANGLE; | |
| } | |
| // Serialize the program only if not already done. | |
| if (mBinary.empty()) | |
| { | |
| ANGLE_TRY(serialize(context)); | |
| } | |
| GLsizei streamLength = static_cast<GLsizei>(mBinary.size()); | |
| const uint8_t *streamState = mBinary.data(); | |
| if (streamLength > bufSize) | |
| { | |
| if (length) | |
| { | |
| *length = 0; | |
| } | |
| // TODO: This should be moved to the validation layer but computing the size of the binary | |
| // before saving it causes the save to happen twice. It may be possible to write the binary | |
| // to a separate buffer, validate sizes and then copy it. | |
| ANGLE_CHECK(context, false, err::kInsufficientBufferSize, GL_INVALID_OPERATION); | |
| } | |
| if (binary) | |
| { | |
| char *ptr = reinterpret_cast<char *>(binary); | |
| memcpy(ptr, streamState, streamLength); | |
| ptr += streamLength; | |
| ASSERT(ptr - streamLength == binary); | |
| // Once the binary is retrieved, assume the application will never need the binary and | |
| // release the memory. Note that implicit caching to blob cache is disabled when the | |
| // GL_PROGRAM_BINARY_RETRIEVABLE_HINT is set. If that hint is not set, serialization is | |
| // done twice, which is what the perf warning above is about! | |
| mBinary.clear(); | |
| } | |
| if (length) | |
| { | |
| *length = streamLength; | |
| } | |
| return angle::Result::Continue; | |
| } | |
| GLint Program::getBinaryLength(Context *context) | |
| { | |
| ASSERT(!mLinkingState); | |
| if (!mLinked) | |
| { | |
| return 0; | |
| } | |
| GLint length; | |
| angle::Result result = | |
| getBinary(context, nullptr, nullptr, std::numeric_limits<GLint>::max(), &length); | |
| if (result != angle::Result::Continue) | |
| { | |
| return 0; | |
| } | |
| return length; | |
| } | |
| void Program::setBinaryRetrievableHint(bool retrievable) | |
| { | |
| ASSERT(!mLinkingState); | |
| // TODO(jmadill) : replace with dirty bits | |
| mProgram->setBinaryRetrievableHint(retrievable); | |
| mState.mBinaryRetrieveableHint = retrievable; | |
| } | |
| bool Program::getBinaryRetrievableHint() const | |
| { | |
| ASSERT(!mLinkingState); | |
| return mState.mExecutable->mBinaryRetrieveableHint; | |
| } | |
| int Program::getInfoLogLength() const | |
| { | |
| return static_cast<int>(mState.mInfoLog.getLength()); | |
| } | |
| void Program::getInfoLog(GLsizei bufSize, GLsizei *length, char *infoLog) const | |
| { | |
| return mState.mInfoLog.getLog(bufSize, length, infoLog); | |
| } | |
| void Program::setSeparable(const Context *context, bool separable) | |
| { | |
| ASSERT(!mLinkingState); | |
| if (isSeparable() != separable) | |
| { | |
| mProgram->setSeparable(separable); | |
| mState.mSeparable = separable; | |
| } | |
| } | |
| void Program::deleteSelf(const Context *context) | |
| { | |
| ASSERT(mRefCount == 0 && mDeleteStatus); | |
| mResourceManager->deleteProgram(context, mHandle); | |
| } | |
| unsigned int Program::getRefCount() const | |
| { | |
| return mRefCount; | |
| } | |
| void Program::getAttachedShaders(GLsizei maxCount, GLsizei *count, ShaderProgramID *shaders) const | |
| { | |
| ASSERT(shaders != nullptr); | |
| int total = 0; | |
| for (const Shader *shader : mAttachedShaders) | |
| { | |
| if (shader != nullptr && total < maxCount) | |
| { | |
| shaders[total] = shader->getHandle(); | |
| ++total; | |
| } | |
| } | |
| if (count) | |
| { | |
| *count = total; | |
| } | |
| } | |
| void Program::flagForDeletion() | |
| { | |
| ASSERT(!mLinkingState); | |
| mDeleteStatus = true; | |
| } | |
| bool Program::isFlaggedForDeletion() const | |
| { | |
| ASSERT(!mLinkingState); | |
| return mDeleteStatus; | |
| } | |
| void Program::validate(const Caps &caps) | |
| { | |
| ASSERT(!mLinkingState); | |
| mState.mInfoLog.reset(); | |
| if (mLinked) | |
| { | |
| // According GLES 3.2 11.1.3.11 Validation: | |
| // ValidateProgram will check for all the conditions described in this section: | |
| // Now only check this condition: | |
| // Any two active samplers in the set of active program objects are of different | |
| // types, but refer to the same texture image unit. | |
| // TODO should check other conditions in future. | |
| if (getExecutable().validateSamplers(caps) == false) | |
| { | |
| mValidated = false; | |
| mState.mInfoLog << err::kTextureTypeConflict; | |
| return; | |
| } | |
| mValidated = ConvertToBool(mProgram->validate(caps)); | |
| } | |
| else | |
| { | |
| mState.mInfoLog << "Program has not been successfully linked."; | |
| } | |
| } | |
| bool Program::isValidated() const | |
| { | |
| ASSERT(!mLinkingState); | |
| return mValidated; | |
| } | |
| void Program::bindUniformBlock(UniformBlockIndex uniformBlockIndex, GLuint uniformBlockBinding) | |
| { | |
| ASSERT(!mLinkingState); | |
| mState.mExecutable->remapUniformBlockBinding(uniformBlockIndex, uniformBlockBinding); | |
| mProgram->onUniformBlockBinding(uniformBlockIndex); | |
| onStateChange( | |
| angle::ProgramUniformBlockBindingUpdatedMessageFromIndex(uniformBlockIndex.value)); | |
| } | |
| void Program::setTransformFeedbackVaryings(const Context *context, | |
| GLsizei count, | |
| const GLchar *const *varyings, | |
| GLenum bufferMode) | |
| { | |
| ASSERT(!mLinkingState); | |
| mState.mTransformFeedbackVaryingNames.resize(count); | |
| for (GLsizei i = 0; i < count; i++) | |
| { | |
| mState.mTransformFeedbackVaryingNames[i] = varyings[i]; | |
| } | |
| mState.mTransformFeedbackBufferMode = bufferMode; | |
| } | |
| bool Program::linkValidateShaders() | |
| { | |
| // Wait for attached shaders to finish compilation. At this point, they need to be checked | |
| // whether they successfully compiled. This information is cached so that all compile jobs can | |
| // be waited on and their corresponding objects released before the actual check. | |
| // | |
| // Note that this function is called from the link job, and is therefore not protected by any | |
| // locks. | |
| ShaderBitSet successfullyCompiledShaders; | |
| for (ShaderType shaderType : AllShaderTypes()) | |
| { | |
| const SharedCompileJob &compileJob = mState.mShaderCompileJobs[shaderType]; | |
| if (compileJob) | |
| { | |
| const bool success = WaitCompileJobUnlocked(compileJob); | |
| successfullyCompiledShaders.set(shaderType, success); | |
| } | |
| } | |
| mState.mShaderCompileJobs = {}; | |
| const ShaderMap<SharedCompiledShaderState> &shaders = mState.mAttachedShaders; | |
| bool isComputeShaderAttached = shaders[ShaderType::Compute].get() != nullptr; | |
| bool isGraphicsShaderAttached = shaders[ShaderType::Vertex].get() != nullptr || | |
| shaders[ShaderType::TessControl].get() != nullptr || | |
| shaders[ShaderType::TessEvaluation].get() != nullptr || | |
| shaders[ShaderType::Geometry].get() != nullptr || | |
| shaders[ShaderType::Fragment].get() != nullptr; | |
| // Check whether we both have a compute and non-compute shaders attached. | |
| // If there are of both types attached, then linking should fail. | |
| // OpenGL ES 3.10, 7.3 Program Objects, under LinkProgram | |
| if (isComputeShaderAttached && isGraphicsShaderAttached) | |
| { | |
| mState.mInfoLog << "Both compute and graphics shaders are attached to the same program."; | |
| return false; | |
| } | |
| Optional<int> version; | |
| for (ShaderType shaderType : kAllGraphicsShaderTypes) | |
| { | |
| const SharedCompiledShaderState &shader = shaders[shaderType]; | |
| ASSERT(!shader || shader->shaderType == shaderType); | |
| if (!shader) | |
| { | |
| continue; | |
| } | |
| if (!successfullyCompiledShaders.test(shaderType)) | |
| { | |
| mState.mInfoLog << ShaderTypeToString(shaderType) << " shader is not compiled."; | |
| return false; | |
| } | |
| if (!version.valid()) | |
| { | |
| version = shader->shaderVersion; | |
| } | |
| else if (version != shader->shaderVersion) | |
| { | |
| mState.mInfoLog << ShaderTypeToString(shaderType) | |
| << " shader version does not match other shader versions."; | |
| return false; | |
| } | |
| } | |
| if (isComputeShaderAttached) | |
| { | |
| ASSERT(shaders[ShaderType::Compute]->shaderType == ShaderType::Compute); | |
| // GLSL ES 3.10, 4.4.1.1 Compute Shader Inputs | |
| // If the work group size is not specified, a link time error should occur. | |
| if (!shaders[ShaderType::Compute]->localSize.isDeclared()) | |
| { | |
| mState.mInfoLog << "Work group size is not specified."; | |
| return false; | |
| } | |
| } | |
| else | |
| { | |
| if (!isGraphicsShaderAttached) | |
| { | |
| mState.mInfoLog << "No compiled shaders."; | |
| return false; | |
| } | |
| bool hasVertex = shaders[ShaderType::Vertex].get() != nullptr; | |
| bool hasFragment = shaders[ShaderType::Fragment].get() != nullptr; | |
| if (!isSeparable() && (!hasVertex || !hasFragment)) | |
| { | |
| mState.mInfoLog | |
| << "The program must contain objects to form both a vertex and fragment shader."; | |
| return false; | |
| } | |
| bool hasTessControl = shaders[ShaderType::TessControl].get() != nullptr; | |
| bool hasTessEvaluation = shaders[ShaderType::TessEvaluation].get() != nullptr; | |
| if (!isSeparable() && (hasTessControl != hasTessEvaluation)) | |
| { | |
| mState.mInfoLog | |
| << "Tessellation control and evaluation shaders must be specified together."; | |
| return false; | |
| } | |
| const SharedCompiledShaderState &geometryShader = shaders[ShaderType::Geometry]; | |
| if (geometryShader) | |
| { | |
| // [GL_EXT_geometry_shader] Chapter 7 | |
| // Linking can fail for a variety of reasons as specified in the OpenGL ES Shading | |
| // Language Specification, as well as any of the following reasons: | |
| // * One or more of the shader objects attached to <program> are not compiled | |
| // successfully. | |
| // * The shaders do not use the same shader language version. | |
| // * <program> contains objects to form a geometry shader, and | |
| // - <program> is not separable and contains no objects to form a vertex shader; or | |
| // - the input primitive type, output primitive type, or maximum output vertex count | |
| // is not specified in the compiled geometry shader object. | |
| if (!geometryShader->hasValidGeometryShaderInputPrimitiveType()) | |
| { | |
| mState.mInfoLog << "Input primitive type is not specified in the geometry shader."; | |
| return false; | |
| } | |
| if (!geometryShader->hasValidGeometryShaderOutputPrimitiveType()) | |
| { | |
| mState.mInfoLog << "Output primitive type is not specified in the geometry shader."; | |
| return false; | |
| } | |
| if (!geometryShader->hasValidGeometryShaderMaxVertices()) | |
| { | |
| mState.mInfoLog << "'max_vertices' is not specified in the geometry shader."; | |
| return false; | |
| } | |
| } | |
| const SharedCompiledShaderState &tessControlShader = shaders[ShaderType::TessControl]; | |
| if (tessControlShader) | |
| { | |
| int tcsShaderVertices = tessControlShader->tessControlShaderVertices; | |
| if (tcsShaderVertices == 0) | |
| { | |
| // In tessellation control shader, output vertices should be specified at least | |
| // once. | |
| // > GLSL ES Version 3.20.6 spec: | |
| // > 4.4.2. Output Layout Qualifiers | |
| // > Tessellation Control Outputs | |
| // > ... | |
| // > There must be at least one layout qualifier specifying an output patch vertex | |
| // > count in any program containing a tessellation control shader. | |
| mState.mInfoLog << "In Tessellation Control Shader, at least one layout qualifier " | |
| "specifying an output patch vertex count must exist."; | |
| return false; | |
| } | |
| } | |
| const SharedCompiledShaderState &tessEvaluationShader = shaders[ShaderType::TessEvaluation]; | |
| if (tessEvaluationShader) | |
| { | |
| GLenum tesPrimitiveMode = tessEvaluationShader->tessGenMode; | |
| if (tesPrimitiveMode == 0) | |
| { | |
| // In tessellation evaluation shader, a primitive mode should be specified at least | |
| // once. | |
| // > GLSL ES Version 3.20.6 spec: | |
| // > 4.4.1. Input Layout Qualifiers | |
| // > Tessellation Evaluation Inputs | |
| // > ... | |
| // > The tessellation evaluation shader object in a program must declare a primitive | |
| // > mode in its input layout. Declaring vertex spacing, ordering, or point mode | |
| // > identifiers is optional. | |
| mState.mInfoLog | |
| << "The Tessellation Evaluation Shader object in a program must declare a " | |
| "primitive mode in its input layout."; | |
| return false; | |
| } | |
| } | |
| } | |
| return true; | |
| } | |
| // Assumes linkValidateShaders() has validated the shaders and caches some values from the shaders. | |
| void Program::linkShaders() | |
| { | |
| const ShaderMap<SharedCompiledShaderState> &shaders = mState.mAttachedShaders; | |
| const bool isComputeShaderAttached = shaders[ShaderType::Compute].get() != nullptr; | |
| if (isComputeShaderAttached) | |
| { | |
| mState.mExecutable->mPod.computeShaderLocalSize = shaders[ShaderType::Compute]->localSize; | |
| } | |
| else | |
| { | |
| const SharedCompiledShaderState &geometryShader = shaders[ShaderType::Geometry]; | |
| if (geometryShader) | |
| { | |
| mState.mExecutable->mPod.geometryShaderInputPrimitiveType = | |
| geometryShader->geometryShaderInputPrimitiveType; | |
| mState.mExecutable->mPod.geometryShaderOutputPrimitiveType = | |
| geometryShader->geometryShaderOutputPrimitiveType; | |
| mState.mExecutable->mPod.geometryShaderMaxVertices = | |
| geometryShader->geometryShaderMaxVertices; | |
| mState.mExecutable->mPod.geometryShaderInvocations = | |
| geometryShader->geometryShaderInvocations; | |
| } | |
| const SharedCompiledShaderState &tessControlShader = shaders[ShaderType::TessControl]; | |
| if (tessControlShader) | |
| { | |
| int tcsShaderVertices = tessControlShader->tessControlShaderVertices; | |
| mState.mExecutable->mPod.tessControlShaderVertices = tcsShaderVertices; | |
| } | |
| const SharedCompiledShaderState &tessEvaluationShader = shaders[ShaderType::TessEvaluation]; | |
| if (tessEvaluationShader) | |
| { | |
| GLenum tesPrimitiveMode = tessEvaluationShader->tessGenMode; | |
| mState.mExecutable->mPod.tessGenMode = tesPrimitiveMode; | |
| mState.mExecutable->mPod.tessGenSpacing = tessEvaluationShader->tessGenSpacing; | |
| mState.mExecutable->mPod.tessGenVertexOrder = tessEvaluationShader->tessGenVertexOrder; | |
| mState.mExecutable->mPod.tessGenPointMode = tessEvaluationShader->tessGenPointMode; | |
| } | |
| } | |
| } | |
| bool Program::linkVaryings() | |
| { | |
| ShaderType previousShaderType = ShaderType::InvalidEnum; | |
| for (ShaderType shaderType : kAllGraphicsShaderTypes) | |
| { | |
| const SharedCompiledShaderState ¤tShader = mState.mAttachedShaders[shaderType]; | |
| if (!currentShader) | |
| { | |
| continue; | |
| } | |
| if (previousShaderType != ShaderType::InvalidEnum) | |
| { | |
| const SharedCompiledShaderState &previousShader = | |
| mState.mAttachedShaders[previousShaderType]; | |
| const std::vector<sh::ShaderVariable> &outputVaryings = previousShader->outputVaryings; | |
| if (!LinkValidateShaderInterfaceMatching( | |
| outputVaryings, currentShader->inputVaryings, previousShaderType, | |
| currentShader->shaderType, previousShader->shaderVersion, | |
| currentShader->shaderVersion, isSeparable(), mState.mInfoLog)) | |
| { | |
| return false; | |
| } | |
| } | |
| previousShaderType = currentShader->shaderType; | |
| } | |
| // TODO: http://anglebug.com/42262233 and http://anglebug.com/42262234 | |
| // 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. | |
| const SharedCompiledShaderState &vertexShader = mState.mAttachedShaders[ShaderType::Vertex]; | |
| const SharedCompiledShaderState &fragmentShader = mState.mAttachedShaders[ShaderType::Fragment]; | |
| if (vertexShader && fragmentShader && | |
| !LinkValidateBuiltInVaryings(vertexShader->outputVaryings, fragmentShader->inputVaryings, | |
| vertexShader->shaderType, fragmentShader->shaderType, | |
| vertexShader->shaderVersion, fragmentShader->shaderVersion, | |
| mState.mInfoLog)) | |
| { | |
| return false; | |
| } | |
| return true; | |
| } | |
| bool Program::linkUniforms(const Caps &caps, | |
| const Version &clientVersion, | |
| std::vector<UnusedUniform> *unusedUniformsOutOrNull, | |
| GLuint *combinedImageUniformsOut) | |
| { | |
| // Initialize executable shader map. | |
| ShaderMap<std::vector<sh::ShaderVariable>> shaderUniforms; | |
| for (const SharedCompiledShaderState &shader : mState.mAttachedShaders) | |
| { | |
| if (shader) | |
| { | |
| shaderUniforms[shader->shaderType] = shader->uniforms; | |
| } | |
| } | |
| if (!mState.mExecutable->linkUniforms(caps, shaderUniforms, mState.mUniformLocationBindings, | |
| combinedImageUniformsOut, unusedUniformsOutOrNull)) | |
| { | |
| return false; | |
| } | |
| if (clientVersion >= Version(3, 1)) | |
| { | |
| GLint locationSize = static_cast<GLint>(mState.mExecutable->getUniformLocations().size()); | |
| if (locationSize > caps.maxUniformLocations) | |
| { | |
| mState.mInfoLog | |
| << "Exceeded maximum uniform location size: number of uniform locations = " | |
| << locationSize << ", max uniform locations = " << caps.maxUniformLocations; | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| // Assigns locations to all attributes (except built-ins) from the bindings and program locations. | |
| bool Program::linkAttributes(const Caps &caps, | |
| const Limitations &limitations, | |
| bool webglCompatibility) | |
| { | |
| int shaderVersion = -1; | |
| unsigned int usedLocations = 0; | |
| const SharedCompiledShaderState &vertexShader = mState.getAttachedShader(ShaderType::Vertex); | |
| if (!vertexShader) | |
| { | |
| // No vertex shader, so no attributes, so nothing to do | |
| return true; | |
| } | |
| // In GLSL ES 3.00.6, aliasing checks should be done with all declared attributes - | |
| // see GLSL ES 3.00.6 section 12.46. Inactive attributes will be pruned after | |
| // aliasing checks. | |
| // In GLSL ES 1.00.17 we only do aliasing checks for active attributes. | |
| shaderVersion = vertexShader->shaderVersion; | |
| const std::vector<sh::ShaderVariable> &shaderAttributes = | |
| shaderVersion >= 300 ? vertexShader->allAttributes : vertexShader->activeAttributes; | |
| ASSERT(mState.mExecutable->mProgramInputs.empty()); | |
| mState.mExecutable->mProgramInputs.reserve(shaderAttributes.size()); | |
| GLuint maxAttribs = static_cast<GLuint>(caps.maxVertexAttributes); | |
| std::vector<ProgramInput *> usedAttribMap(maxAttribs, nullptr); | |
| for (const sh::ShaderVariable &shaderAttribute : shaderAttributes) | |
| { | |
| // GLSL ES 3.10 January 2016 section 4.3.4: Vertex shader inputs can't be arrays or | |
| // structures, so we don't need to worry about adjusting their names or generating entries | |
| // for each member/element (unlike uniforms for example). | |
| ASSERT(!shaderAttribute.isArray() && !shaderAttribute.isStruct()); | |
| mState.mExecutable->mProgramInputs.emplace_back(shaderAttribute); | |
| // Assign locations to attributes that have a binding location and check for attribute | |
| // aliasing. | |
| ProgramInput &attribute = mState.mExecutable->mProgramInputs.back(); | |
| int bindingLocation = mState.mAttributeBindings.getBinding(attribute); | |
| if (attribute.getLocation() == -1 && bindingLocation != -1) | |
| { | |
| attribute.setLocation(bindingLocation); | |
| } | |
| if (attribute.getLocation() != -1) | |
| { | |
| // Location is set by glBindAttribLocation or by location layout qualifier | |
| const int regs = VariableRegisterCount(attribute.getType()); | |
| if (static_cast<GLuint>(regs + attribute.getLocation()) > maxAttribs) | |
| { | |
| mState.mInfoLog << "Attribute (" << attribute.name << ") at location " | |
| << attribute.getLocation() << " is too big to fit"; | |
| return false; | |
| } | |
| for (int reg = 0; reg < regs; reg++) | |
| { | |
| const int regLocation = attribute.getLocation() + reg; | |
| ProgramInput *linkedAttribute = usedAttribMap[regLocation]; | |
| // In GLSL ES 3.00.6 and in WebGL, attribute aliasing produces a link error. | |
| // In non-WebGL GLSL ES 1.00.17, attribute aliasing is allowed with some | |
| // restrictions - see GLSL ES 1.00.17 section 2.10.4, but ANGLE currently has a bug. | |
| // In D3D 9 and 11, aliasing is not supported, so check a limitation. | |
| if (linkedAttribute) | |
| { | |
| if (shaderVersion >= 300 || webglCompatibility || | |
| limitations.noVertexAttributeAliasing) | |
| { | |
| mState.mInfoLog << "Attribute '" << attribute.name | |
| << "' aliases attribute '" << linkedAttribute->name | |
| << "' at location " << regLocation; | |
| return false; | |
| } | |
| } | |
| else | |
| { | |
| usedAttribMap[regLocation] = &attribute; | |
| } | |
| usedLocations |= 1 << regLocation; | |
| } | |
| } | |
| } | |
| // Assign locations to attributes that don't have a binding location. | |
| for (ProgramInput &attribute : mState.mExecutable->mProgramInputs) | |
| { | |
| // Not set by glBindAttribLocation or by location layout qualifier | |
| if (attribute.getLocation() == -1) | |
| { | |
| int regs = VariableRegisterCount(attribute.getType()); | |
| int availableIndex = AllocateFirstFreeBits(&usedLocations, regs, maxAttribs); | |
| if (availableIndex == -1 || static_cast<GLuint>(availableIndex + regs) > maxAttribs) | |
| { | |
| mState.mInfoLog << "Too many attributes (" << attribute.name << ")"; | |
| return false; | |
| } | |
| attribute.setLocation(availableIndex); | |
| } | |
| } | |
| ASSERT(mState.mExecutable->mPod.attributesTypeMask.none()); | |
| ASSERT(mState.mExecutable->mPod.attributesMask.none()); | |
| // Prune inactive attributes. This step is only needed on shaderVersion >= 300 since on earlier | |
| // shader versions we're only processing active attributes to begin with. | |
| if (shaderVersion >= 300) | |
| { | |
| for (auto attributeIter = mState.mExecutable->getProgramInputs().begin(); | |
| attributeIter != mState.mExecutable->getProgramInputs().end();) | |
| { | |
| if (attributeIter->isActive()) | |
| { | |
| ++attributeIter; | |
| } | |
| else | |
| { | |
| attributeIter = mState.mExecutable->mProgramInputs.erase(attributeIter); | |
| } | |
| } | |
| } | |
| for (const ProgramInput &attribute : mState.mExecutable->getProgramInputs()) | |
| { | |
| ASSERT(attribute.isActive()); | |
| ASSERT(attribute.getLocation() != -1); | |
| unsigned int regs = static_cast<unsigned int>(VariableRegisterCount(attribute.getType())); | |
| unsigned int location = static_cast<unsigned int>(attribute.getLocation()); | |
| for (unsigned int r = 0; r < regs; r++) | |
| { | |
| // Built-in active program inputs don't have a bound attribute. | |
| if (!attribute.isBuiltIn()) | |
| { | |
| mState.mExecutable->mPod.activeAttribLocationsMask.set(location); | |
| mState.mExecutable->mPod.maxActiveAttribLocation = | |
| std::max(mState.mExecutable->mPod.maxActiveAttribLocation, location + 1); | |
| ComponentType componentType = | |
| GLenumToComponentType(VariableComponentType(attribute.getType())); | |
| SetComponentTypeMask(componentType, location, | |
| &mState.mExecutable->mPod.attributesTypeMask); | |
| mState.mExecutable->mPod.attributesMask.set(location); | |
| location++; | |
| } | |
| } | |
| } | |
| return true; | |
| } | |
| angle::Result Program::serialize(const Context *context) | |
| { | |
| // In typical applications, the binary should already be empty here. However, in unusual | |
| // situations this may not be true. In particular, if the application doesn't set | |
| // GL_PROGRAM_BINARY_RETRIEVABLE_HINT, gets the program length but doesn't get the binary, the | |
| // cached binary remains until the program is destroyed or the program is bound (both causing | |
| // |waitForPostLinkTasks()| to cache the program in the blob cache). | |
| if (!mBinary.empty()) | |
| { | |
| return angle::Result::Continue; | |
| } | |
| BinaryOutputStream stream; | |
| stream.writeBytes( | |
| reinterpret_cast<const unsigned char *>(angle::GetANGLEShaderProgramVersion()), | |
| angle::GetANGLEShaderProgramVersionHashSize()); | |
| stream.writeBool(angle::Is64Bit()); | |
| stream.writeInt(angle::GetANGLESHVersion()); | |
| stream.writeString(context->getRendererString()); | |
| // nullptr context is supported when computing binary length. | |
| if (context) | |
| { | |
| stream.writeInt(context->getClientVersion().getMajor()); | |
| stream.writeInt(context->getClientVersion().getMinor()); | |
| } | |
| else | |
| { | |
| stream.writeInt(2); | |
| stream.writeInt(0); | |
| } | |
| // mSeparable must be before mExecutable->save(), since it uses the value. | |
| stream.writeBool(mState.mExecutable->mPod.isSeparable); | |
| stream.writeInt(mState.mExecutable->mPod.transformFeedbackBufferMode); | |
| stream.writeInt(mState.mExecutable->mTransformFeedbackVaryingNames.size()); | |
| for (const std::string &name : mState.mExecutable->mTransformFeedbackVaryingNames) | |
| { | |
| stream.writeString(name); | |
| } | |
| mState.mExecutable->save(&stream); | |
| // Warn the app layer if saving a binary with unsupported transform feedback. | |
| if (!mState.mExecutable->getLinkedTransformFeedbackVaryings().empty() && | |
| context->getFrontendFeatures().disableProgramCachingForTransformFeedback.enabled) | |
| { | |
| ANGLE_PERF_WARNING(context->getState().getDebug(), GL_DEBUG_SEVERITY_LOW, | |
| "Saving program binary with transform feedback, which is not supported " | |
| "on this driver."); | |
| } | |
| if (context->getShareGroup()->getFrameCaptureShared()->enabled()) | |
| { | |
| // Serialize the source for each stage for re-use during capture | |
| for (ShaderType shaderType : mState.mExecutable->getLinkedShaderStages()) | |
| { | |
| Shader *shader = getAttachedShader(shaderType); | |
| if (shader) | |
| { | |
| stream.writeString(shader->getSourceString()); | |
| } | |
| else | |
| { | |
| // If we don't have an attached shader, which would occur if this program was | |
| // created via glProgramBinary, pull from our cached copy | |
| const angle::ProgramSources &cachedLinkedSources = | |
| context->getShareGroup()->getFrameCaptureShared()->getProgramSources(id()); | |
| const std::string &cachedSourceString = cachedLinkedSources[shaderType]; | |
| ASSERT(!cachedSourceString.empty()); | |
| stream.writeString(cachedSourceString.c_str()); | |
| } | |
| } | |
| } | |
| mProgram->save(context, &stream); | |
| ASSERT(mState.mExecutable->mPostLinkSubTasks.empty()); | |
| if (!mBinary.resize(stream.length())) | |
| { | |
| ANGLE_PERF_WARNING(context->getState().getDebug(), GL_DEBUG_SEVERITY_LOW, | |
| "Failed to allocate enough memory to serialize a program. (%zu bytes)", | |
| stream.length()); | |
| return angle::Result::Stop; | |
| } | |
| memcpy(mBinary.data(), stream.data(), stream.length()); | |
| return angle::Result::Continue; | |
| } | |
| bool Program::deserialize(const Context *context, BinaryInputStream &stream) | |
| { | |
| std::vector<uint8_t> angleShaderProgramVersionString( | |
| angle::GetANGLEShaderProgramVersionHashSize(), 0); | |
| stream.readBytes(angleShaderProgramVersionString.data(), | |
| angleShaderProgramVersionString.size()); | |
| if (memcmp(angleShaderProgramVersionString.data(), angle::GetANGLEShaderProgramVersion(), | |
| angleShaderProgramVersionString.size()) != 0) | |
| { | |
| mState.mInfoLog << "Invalid program binary version."; | |
| return false; | |
| } | |
| bool binaryIs64Bit = stream.readBool(); | |
| if (binaryIs64Bit != angle::Is64Bit()) | |
| { | |
| mState.mInfoLog << "cannot load program binaries across CPU architectures."; | |
| return false; | |
| } | |
| int angleSHVersion = stream.readInt<int>(); | |
| if (angleSHVersion != angle::GetANGLESHVersion()) | |
| { | |
| mState.mInfoLog << "cannot load program binaries across different angle sh version."; | |
| return false; | |
| } | |
| std::string rendererString = stream.readString(); | |
| if (rendererString != context->getRendererString()) | |
| { | |
| mState.mInfoLog << "Cannot load program binary due to changed renderer string."; | |
| return false; | |
| } | |
| const uint32_t majorVersion = stream.readInt<int>(); | |
| const uint32_t minorVersion = stream.readInt<int>(); | |
| if (majorVersion != context->getClientVersion().getMajor() || | |
| minorVersion != context->getClientVersion().getMinor()) | |
| { | |
| mState.mInfoLog << "Cannot load program binaries across different ES context versions."; | |
| return false; | |
| } | |
| mState.mSeparable = stream.readBool(); | |
| mState.mTransformFeedbackBufferMode = stream.readInt<GLenum>(); | |
| mState.mTransformFeedbackVaryingNames.resize(stream.readInt<size_t>()); | |
| for (std::string &name : mState.mTransformFeedbackVaryingNames) | |
| { | |
| name = stream.readString(); | |
| } | |
| // mSeparable must be before mExecutable->load(), since it uses the value. This state is | |
| // duplicated in the executable for convenience. | |
| mState.mExecutable->mPod.isSeparable = mState.mSeparable; | |
| mState.mExecutable->load(&stream); | |
| static_assert(static_cast<unsigned long>(ShaderType::EnumCount) <= sizeof(unsigned long) * 8, | |
| "Too many shader types"); | |
| // Reject programs that use transform feedback varyings if the hardware cannot support them. | |
| if (mState.mExecutable->getLinkedTransformFeedbackVaryings().size() > 0 && | |
| context->getFrontendFeatures().disableProgramCachingForTransformFeedback.enabled) | |
| { | |
| mState.mInfoLog << "Current driver does not support transform feedback in binary programs."; | |
| return false; | |
| } | |
| if (!mState.mAttachedShaders[ShaderType::Compute]) | |
| { | |
| mState.mExecutable->updateTransformFeedbackStrides(); | |
| mState.mExecutable->mTransformFeedbackVaryingNames = mState.mTransformFeedbackVaryingNames; | |
| } | |
| if (context->getShareGroup()->getFrameCaptureShared()->enabled()) | |
| { | |
| // Extract the source for each stage from the program binary | |
| angle::ProgramSources sources; | |
| for (ShaderType shaderType : mState.mExecutable->getLinkedShaderStages()) | |
| { | |
| std::string shaderSource = stream.readString(); | |
| ASSERT(shaderSource.length() > 0); | |
| sources[shaderType] = std::move(shaderSource); | |
| } | |
| // Store it for use during mid-execution capture | |
| context->getShareGroup()->getFrameCaptureShared()->setProgramSources(id(), | |
| std::move(sources)); | |
| } | |
| return true; | |
| } | |
| void Program::postResolveLink(const Context *context) | |
| { | |
| mState.updateActiveSamplers(); | |
| mState.mExecutable->mActiveImageShaderBits.fill({}); | |
| mState.mExecutable->updateActiveImages(getExecutable()); | |
| mState.mExecutable->initInterfaceBlockBindings(); | |
| mState.mExecutable->setUniformValuesFromBindingQualifiers(); | |
| // Update active uniform and storage buffer block indices mask | |
| mState.mExecutable->updateActiveUniformBufferBlocks(); | |
| mState.mExecutable->updateActiveStorageBufferBlocks(); | |
| if (context->getExtensions().multiDrawANGLE) | |
| { | |
| mState.mExecutable->mPod.drawIDLocation = | |
| mState.mExecutable->getUniformLocation("gl_DrawID").value; | |
| } | |
| if (context->getExtensions().baseVertexBaseInstanceShaderBuiltinANGLE) | |
| { | |
| mState.mExecutable->mPod.baseVertexLocation = | |
| mState.mExecutable->getUniformLocation("gl_BaseVertex").value; | |
| mState.mExecutable->mPod.baseInstanceLocation = | |
| mState.mExecutable->getUniformLocation("gl_BaseInstance").value; | |
| } | |
| } | |
| void Program::cacheProgramBinaryIfNotAlready(const Context *context) | |
| { | |
| // If program caching is disabled, we already consider the binary cached. | |
| ASSERT(!context->getFrontendFeatures().disableProgramCaching.enabled || mIsBinaryCached); | |
| if (!mLinked || mIsBinaryCached || mState.mExecutable->mBinaryRetrieveableHint) | |
| { | |
| // Program caching is disabled, the program is yet to be linked, it's already cached, or the | |
| // application has specified that it prefers to cache the program binary itself. | |
| return; | |
| } | |
| // No post-link tasks should be pending. | |
| ASSERT(mState.mExecutable->mPostLinkSubTasks.empty()); | |
| // Save to the program cache. | |
| std::lock_guard<angle::SimpleMutex> cacheLock(context->getProgramCacheMutex()); | |
| MemoryProgramCache *cache = context->getMemoryProgramCache(); | |
| // TODO: http://anglebug.com/42263141: Enable program caching for separable programs | |
| if (cache && !isSeparable() && | |
| (mState.mExecutable->mLinkedTransformFeedbackVaryings.empty() || | |
| !context->getFrontendFeatures().disableProgramCachingForTransformFeedback.enabled)) | |
| { | |
| if (cache->putProgram(mProgramHash, context, this) == angle::Result::Stop) | |
| { | |
| // Don't fail linking if putting the program binary into the cache fails, the program is | |
| // still usable. | |
| ANGLE_PERF_WARNING(context->getState().getDebug(), GL_DEBUG_SEVERITY_LOW, | |
| "Failed to save linked program to memory program cache."); | |
| } | |
| // Drop the binary; the application didn't specify that it wants to retrieve the binary. If | |
| // it did, we wouldn't be implicitly caching it. | |
| mBinary.clear(); | |
| } | |
| mIsBinaryCached = true; | |
| } | |
| void Program::dumpProgramInfo(const Context *context) const | |
| { | |
| std::stringstream dumpStream; | |
| for (ShaderType shaderType : angle::AllEnums<ShaderType>()) | |
| { | |
| Shader *shader = getAttachedShader(shaderType); | |
| if (shader) | |
| { | |
| dumpStream << shader->getType() << ": " | |
| << GetShaderDumpFileName(shader->getSourceHash()) << std::endl; | |
| } | |
| } | |
| std::string dump = dumpStream.str(); | |
| size_t dumpHash = std::hash<std::string>{}(dump); | |
| std::stringstream pathStream; | |
| std::string shaderDumpDir = GetShaderDumpFileDirectory(); | |
| if (!shaderDumpDir.empty()) | |
| { | |
| pathStream << shaderDumpDir << "/"; | |
| } | |
| pathStream << dumpHash << ".program"; | |
| std::string path = pathStream.str(); | |
| writeFile(path.c_str(), dump.c_str(), dump.length()); | |
| INFO() << "Dumped program: " << path; | |
| } | |
| } // namespace gl |