// // 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 #include #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 &workerThreadPool, std::vector> &tasks, std::vector> *eventsOut) { eventsOut->reserve(tasks.size()); for (const std::shared_ptr &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 void UpdateInterfaceVariable(std::vector *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; 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(bufSize) - 1, logString.length()); memcpy(infoLog, logString.c_str(), index); } infoLog[index] = '\0'; } if (length) { *length = static_cast(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 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 ProgramBindings::getStableIterationMap() const { return std::map(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 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(const UsedUniform &variable) const; template int ProgramAliasedBindings::getBinding(const ProgramOutput &variable) const; template int ProgramAliasedBindings::getBinding( 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 ProgramAliasedBindings::getStableIterationMap() const { return std::map(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 &subTaskWorkerPool, ProgramState *state, std::shared_ptr &&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 &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> &&linkSubTasks, std::vector> &&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 mSubTaskWorkerPool; ProgramState &mState; std::shared_ptr mLinkTask; // Subtask and wait events std::vector> mSubTasks; std::vector> 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 &subTaskWorkerPool, const Caps &caps, const Limitations &limitations, const Version &clientVersion, bool isWebGL, Program *program, ProgramState *state, LinkingVariables *linkingVariables, ProgramLinkedResources *resources, std::shared_ptr &&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 &subTaskWorkerPool, Program *program, ProgramState *state, std::shared_ptr &&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 &linkTask, const std::shared_ptr &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 mLinkTask; std::shared_ptr 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> linkSubTasks; std::vector> 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> linkSubTasks; std::vector> 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(); // By default, set the link event as failing. If link succeeds, it will be replaced by the // appropriate event. mLinkingState->linkEvent = std::make_unique(angle::Result::Stop); InstallExecutable( context, std::make_shared(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 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(); } 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 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(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 linkTask; ANGLE_TRY(mProgram->link(context, &linkTask)); std::unique_ptr linkingState = std::make_unique(); // Prepare the main link job std::shared_ptr 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 mainLinkEvent = context->postCompileLinkTask(mainLinkTask, threadSafety, resultExpectancy); mLinkingState = std::move(linkingState); mLinkingState->linkingFromBinary = false; mLinkingState->linkEvent = std::make_unique(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(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( static_cast(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 = 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); // 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 *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 loadTask; ANGLE_TRY(mProgram->load(context, &stream, &loadTask, resultOut)); if (*resultOut == egl::CacheGetResult::Rejected) { return angle::Result::Continue; } std::unique_ptr loadEvent; if (loadTask) { std::shared_ptr mainLoadTask(new MainLoadTask( context->getLinkSubTaskThreadPool(), this, &mState, std::move(loadTask))); std::shared_ptr mainLoadEvent = context->getShaderCompileThreadPool()->postWorkerTask(mainLoadTask); loadEvent = std::make_unique(mainLoadTask, mainLoadEvent); } else { loadEvent = std::make_unique(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.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(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, "Insufficient buffer size", GL_INVALID_OPERATION); } if (binary) { char *ptr = reinterpret_cast(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::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.mBinaryRetrieveableHint; } int Program::getInfoLogLength() const { return static_cast(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 { 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) { 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 &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 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 are not compiled // successfully. // * The shaders do not use the same shader language version. // * contains objects to form a geometry shader, and // - 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 &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 &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 *unusedUniformsOutOrNull, GLuint *combinedImageUniformsOut) { // Initialize executable shader map. ShaderMap> 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(mState.mExecutable->getUniformLocations().size()); if (locationSize > caps.maxUniformLocations) { mState.mInfoLog << "Exceeded maximum uniform location size"; 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 &shaderAttributes = shaderVersion >= 300 ? vertexShader->allAttributes : vertexShader->activeAttributes; ASSERT(mState.mExecutable->mProgramInputs.empty()); mState.mExecutable->mProgramInputs.reserve(shaderAttributes.size()); GLuint maxAttribs = static_cast(caps.maxVertexAttributes); std::vector 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(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(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(VariableRegisterCount(attribute.getType())); unsigned int location = static_cast(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(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().major); stream.writeInt(context->getClientVersion().minor); } else { stream.writeInt(2); stream.writeInt(0); } // mSeparable must be before mExecutable->save(), since it uses the value. stream.writeBool(mState.mSeparable); stream.writeInt(mState.mTransformFeedbackBufferMode); stream.writeInt(mState.mTransformFeedbackVaryingNames.size()); for (const std::string &name : mState.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 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(); 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; } int majorVersion = stream.readInt(); int minorVersion = stream.readInt(); if (majorVersion != context->getClientMajorVersion() || minorVersion != context->getClientMinorVersion()) { mState.mInfoLog << "Cannot load program binaries across different ES context versions."; return false; } mState.mSeparable = stream.readBool(); mState.mTransformFeedbackBufferMode = stream.readInt(); mState.mTransformFeedbackVaryingNames.resize(stream.readInt()); 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(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(); 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.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 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()) { Shader *shader = getAttachedShader(shaderType); if (shader) { dumpStream << shader->getType() << ": " << GetShaderDumpFileName(shader->getSourceHash()) << std::endl; } } std::string dump = dumpStream.str(); size_t dumpHash = std::hash{}(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