blob: 17d9182d136562ade9e9915f7cd8ef4d12f4d233 [file] [log] [blame]
//
// 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/8661)
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();
}
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/4530: Enable program caching for separable programs
if (cache && !isSeparable())
{
std::lock_guard<std::mutex> 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);
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.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())
{
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);
// 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())
{
cacheProgramBinary(context);
}
}
void Program::waitForPostLinkTasks(const Context *context)
{
if (!mState.mExecutable->mPostLinkSubTasks.empty())
{
mState.mExecutable->waitForPostLinkTasks(context);
}
// Now that the subtasks are done, cache the binary (this was deferred in resolveLinkImpl).
cacheProgramBinary(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.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, "Insufficient buffer size", 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.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
{
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<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 &currentShader = 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/3571 and http://anglebug.com/3572
// Need to move logic of validating builtin varyings inside the for-loop above.
// This is because the built-in symbols `gl_ClipDistance` and `gl_CullDistance`
// can be redeclared in Geometry or Tessellation shaders as well.
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";
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().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<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;
}
int majorVersion = stream.readInt<int>();
int minorVersion = stream.readInt<int>();
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<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();
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::cacheProgramBinary(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<std::mutex> cacheLock(context->getProgramCacheMutex());
MemoryProgramCache *cache = context->getMemoryProgramCache();
// TODO: http://anglebug.com/4530: 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