| // | |
| // 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. | |
| // | |
| // Shader.cpp: Implements the gl::Shader class and its derived classes | |
| // VertexShader and FragmentShader. Implements GL shader objects and related | |
| // functionality. [OpenGL ES 2.0.24] section 2.10 page 24 and section 3.8 page 84. | |
| #include "libANGLE/Shader.h" | |
| #include <functional> | |
| #include <sstream> | |
| #include "GLSLANG/ShaderLang.h" | |
| #include "common/angle_version_info.h" | |
| #include "common/string_utils.h" | |
| #include "common/system_utils.h" | |
| #include "common/utilities.h" | |
| #include "libANGLE/Caps.h" | |
| #include "libANGLE/Compiler.h" | |
| #include "libANGLE/Constants.h" | |
| #include "libANGLE/Context.h" | |
| #include "libANGLE/Display.h" | |
| #include "libANGLE/MemoryShaderCache.h" | |
| #include "libANGLE/Program.h" | |
| #include "libANGLE/ResourceManager.h" | |
| #include "libANGLE/renderer/GLImplFactory.h" | |
| #include "libANGLE/renderer/ShaderImpl.h" | |
| #include "libANGLE/trace.h" | |
| #include "platform/autogen/FrontendFeatures_autogen.h" | |
| namespace gl | |
| { | |
| namespace | |
| { | |
| constexpr uint32_t kShaderCacheIdentifier = 0x12345678; | |
| // Environment variable (and associated Android property) for the path to read and write shader | |
| // dumps | |
| constexpr char kShaderDumpPathVarName[] = "ANGLE_SHADER_DUMP_PATH"; | |
| constexpr char kEShaderDumpPathPropertyName[] = "debug.angle.shader_dump_path"; | |
| size_t ComputeShaderHash(const std::string &mergedSource) | |
| { | |
| return std::hash<std::string>{}(mergedSource); | |
| } | |
| std::string GetShaderDumpFilePath(size_t shaderHash, const char *suffix) | |
| { | |
| std::stringstream path; | |
| std::string shaderDumpDir = GetShaderDumpFileDirectory(); | |
| if (!shaderDumpDir.empty()) | |
| { | |
| path << shaderDumpDir << "/"; | |
| } | |
| path << shaderHash << "." << suffix; | |
| return path.str(); | |
| } | |
| class CompileTask final : public angle::Closure | |
| { | |
| public: | |
| // Translate and compile | |
| CompileTask(const angle::FrontendFeatures &frontendFeatures, | |
| ShHandle compilerHandle, | |
| ShShaderOutput outputType, | |
| const ShCompileOptions &options, | |
| const std::string &source, | |
| size_t sourceHash, | |
| const SharedCompiledShaderState &compiledState, | |
| size_t maxComputeWorkGroupInvocations, | |
| size_t maxComputeSharedMemory, | |
| std::shared_ptr<rx::ShaderTranslateTask> &&translateTask) | |
| : mFrontendFeatures(frontendFeatures), | |
| mMaxComputeWorkGroupInvocations(maxComputeWorkGroupInvocations), | |
| mMaxComputeSharedMemory(maxComputeSharedMemory), | |
| mCompilerHandle(compilerHandle), | |
| mOutputType(outputType), | |
| mOptions(options), | |
| mSource(source), | |
| mSourceHash(sourceHash), | |
| mCompiledState(compiledState), | |
| mTranslateTask(std::move(translateTask)) | |
| {} | |
| // Load from binary | |
| CompileTask(const angle::FrontendFeatures &frontendFeatures, | |
| const SharedCompiledShaderState &compiledState, | |
| std::shared_ptr<rx::ShaderTranslateTask> &&translateTask) | |
| : mFrontendFeatures(frontendFeatures), | |
| mCompiledState(compiledState), | |
| mTranslateTask(std::move(translateTask)) | |
| {} | |
| ~CompileTask() override = default; | |
| void operator()() override { mResult = compileImpl(); } | |
| angle::Result getResult() | |
| { | |
| // Note: this function is called from WaitCompileJobUnlocked(), and must therefore be | |
| // thread-safe if the linkJobIsThreadSafe feature is enabled. Without linkJobIsThreadSafe, | |
| // the call will end up done in the main thread, which is the case for the GL backend (which | |
| // happens to be the only backend that actually does anything in getResult). | |
| // | |
| // Consequently, this function must not _write_ to anything, e.g. by trying to cache the | |
| // result of |mTranslateTask->getResult()|. | |
| ANGLE_TRY(mResult); | |
| ANGLE_TRY(mTranslateTask->getResult(mInfoLog)); | |
| return angle::Result::Continue; | |
| } | |
| bool isCompilingInternally() { return mTranslateTask->isCompilingInternally(); } | |
| std::string &&getInfoLog() { return std::move(mInfoLog); } | |
| private: | |
| angle::Result compileImpl(); | |
| angle::Result postTranslate(); | |
| // Global constants that are safe to access by the worker thread | |
| const angle::FrontendFeatures &mFrontendFeatures; | |
| size_t mMaxComputeWorkGroupInvocations = 0; | |
| size_t mMaxComputeSharedMemory = 0; | |
| // Access to the compile information. Note that the compiler instance is kept alive until | |
| // resolveCompile. | |
| ShHandle mCompilerHandle = 0; | |
| ShShaderOutput mOutputType; | |
| ShCompileOptions mOptions; | |
| const std::string mSource; | |
| size_t mSourceHash = 0; | |
| SharedCompiledShaderState mCompiledState; | |
| std::shared_ptr<rx::ShaderTranslateTask> mTranslateTask; | |
| angle::Result mResult; | |
| std::string mInfoLog; | |
| }; | |
| class CompileEvent final | |
| { | |
| public: | |
| CompileEvent(const std::shared_ptr<CompileTask> &compileTask, | |
| const std::shared_ptr<angle::WaitableEvent> &waitEvent) | |
| : mCompileTask(compileTask), mWaitableEvent(waitEvent) | |
| {} | |
| ~CompileEvent() = default; | |
| angle::Result wait() | |
| { | |
| ANGLE_TRACE_EVENT0("gpu.angle", "CompileEvent::wait"); | |
| mWaitableEvent->wait(); | |
| return mCompileTask->getResult(); | |
| } | |
| bool isCompiling() | |
| { | |
| return !mWaitableEvent->isReady() || mCompileTask->isCompilingInternally(); | |
| } | |
| std::string &&getInfoLog() { return std::move(mCompileTask->getInfoLog()); } | |
| private: | |
| std::shared_ptr<CompileTask> mCompileTask; | |
| std::shared_ptr<angle::WaitableEvent> mWaitableEvent; | |
| }; | |
| angle::Result CompileTask::compileImpl() | |
| { | |
| if (mCompilerHandle) | |
| { | |
| // Compiling from source | |
| // Call the translator and get the info log | |
| bool result = mTranslateTask->translate(mCompilerHandle, mOptions, mSource); | |
| mInfoLog = sh::GetInfoLog(mCompilerHandle); | |
| if (!result) | |
| { | |
| return angle::Result::Stop; | |
| } | |
| // Process the translation results itself; gather compilation info, substitute the shader if | |
| // being overriden, etc. | |
| return postTranslate(); | |
| } | |
| else | |
| { | |
| // Loading from binary | |
| mTranslateTask->load(*mCompiledState.get()); | |
| return angle::Result::Continue; | |
| } | |
| } | |
| angle::Result CompileTask::postTranslate() | |
| { | |
| mCompiledState->buildCompiledShaderState(mCompilerHandle, mSource, mOutputType); | |
| ASSERT(!mCompiledState->translatedSource.empty() || !mCompiledState->compiledBinary.empty()); | |
| // Validation checks for compute shaders | |
| if (mCompiledState->shaderType == ShaderType::Compute && mCompiledState->localSize.isDeclared()) | |
| { | |
| angle::CheckedNumeric<size_t> checked_local_size_product(mCompiledState->localSize[0]); | |
| checked_local_size_product *= mCompiledState->localSize[1]; | |
| checked_local_size_product *= mCompiledState->localSize[2]; | |
| if (!checked_local_size_product.IsValid() || | |
| checked_local_size_product.ValueOrDie() > mMaxComputeWorkGroupInvocations) | |
| { | |
| mInfoLog += | |
| "\nThe total number of invocations within a work group exceeds " | |
| "MAX_COMPUTE_WORK_GROUP_INVOCATIONS."; | |
| return angle::Result::Stop; | |
| } | |
| } | |
| unsigned int sharedMemSize = sh::GetShaderSharedMemorySize(mCompilerHandle); | |
| if (sharedMemSize > mMaxComputeSharedMemory) | |
| { | |
| mInfoLog += "\nShared memory size exceeds GL_MAX_COMPUTE_SHARED_MEMORY_SIZE"; | |
| return angle::Result::Stop; | |
| } | |
| bool substitutedTranslatedShader = false; | |
| const char *suffix = "translated"; | |
| if (mFrontendFeatures.enableTranslatedShaderSubstitution.enabled) | |
| { | |
| // To support reading/writing compiled binaries (SPIR-V representation), need more file | |
| // input/output facilities, and figure out the byte ordering of writing the 32-bit words to | |
| // disk. | |
| if (!mCompiledState->compiledBinary.empty()) | |
| { | |
| INFO() << "Can not substitute compiled binary (SPIR-V) shaders yet"; | |
| } | |
| else | |
| { | |
| std::string substituteShaderPath = GetShaderDumpFilePath(mSourceHash, suffix); | |
| std::string substituteShader; | |
| if (angle::ReadFileToString(substituteShaderPath, &substituteShader)) | |
| { | |
| mCompiledState->translatedSource = std::move(substituteShader); | |
| substitutedTranslatedShader = true; | |
| INFO() << "Translated shader substitute found, loading from " | |
| << substituteShaderPath; | |
| } | |
| } | |
| } | |
| // Only dump translated shaders that have not been previously substituted. It would write the | |
| // same data back to the file. | |
| if (mFrontendFeatures.dumpTranslatedShaders.enabled && !substitutedTranslatedShader) | |
| { | |
| if (!mCompiledState->compiledBinary.empty()) | |
| { | |
| INFO() << "Can not dump compiled binary (SPIR-V) shaders yet"; | |
| } | |
| else | |
| { | |
| std::string dumpFile = GetShaderDumpFilePath(mSourceHash, suffix); | |
| const std::string &translatedSource = mCompiledState->translatedSource; | |
| writeFile(dumpFile.c_str(), translatedSource.c_str(), translatedSource.length()); | |
| INFO() << "Dumped translated source: " << dumpFile; | |
| } | |
| } | |
| #if defined(ANGLE_ENABLE_ASSERTS) | |
| if (!mCompiledState->compiledBinary.empty()) | |
| { | |
| // Suffix the translated shader with commented out un-translated shader. | |
| // Useful in diagnostics tools which capture the shader source. | |
| std::ostringstream shaderStream; | |
| shaderStream << "\n"; | |
| shaderStream << "// GLSL\n"; | |
| shaderStream << "//\n"; | |
| std::istringstream inputSourceStream(mSource); | |
| std::string line; | |
| while (std::getline(inputSourceStream, line)) | |
| { | |
| // Remove null characters from the source line | |
| line.erase(std::remove(line.begin(), line.end(), '\0'), line.end()); | |
| shaderStream << "// " << line; | |
| // glslang complains if a comment ends with backslash | |
| if (!line.empty() && line.back() == '\\') | |
| { | |
| shaderStream << "\\"; | |
| } | |
| shaderStream << std::endl; | |
| } | |
| mCompiledState->translatedSource += shaderStream.str(); | |
| } | |
| #endif // defined(ANGLE_ENABLE_ASSERTS) | |
| // Let the backend process the result of the compilation. For the GL backend, this means | |
| // kicking off compilation internally. Some of the other backends fill in their internal | |
| // "compiled state" at this point. | |
| mTranslateTask->postTranslate(mCompilerHandle, *mCompiledState.get()); | |
| return angle::Result::Continue; | |
| } | |
| template <typename T> | |
| void AppendHashValue(angle::base::SecureHashAlgorithm &hasher, T value) | |
| { | |
| static_assert(std::is_fundamental<T>::value || std::is_enum<T>::value); | |
| hasher.Update(&value, sizeof(T)); | |
| } | |
| angle::JobThreadSafety GetTranslateTaskThreadSafety(const Context *context) | |
| { | |
| // The GL backend relies on the driver's internal parallel compilation, and thus does not use a | |
| // thread to compile. A front-end feature selects whether the single-threaded pool must be | |
| // used. | |
| return context->getFrontendFeatures().compileJobIsThreadSafe.enabled | |
| ? angle::JobThreadSafety::Safe | |
| : angle::JobThreadSafety::Unsafe; | |
| } | |
| } // anonymous namespace | |
| const char *GetShaderTypeString(ShaderType type) | |
| { | |
| switch (type) | |
| { | |
| case ShaderType::Vertex: | |
| return "VERTEX"; | |
| case ShaderType::Fragment: | |
| return "FRAGMENT"; | |
| case ShaderType::Compute: | |
| return "COMPUTE"; | |
| case ShaderType::Geometry: | |
| return "GEOMETRY"; | |
| case ShaderType::TessControl: | |
| return "TESS_CONTROL"; | |
| case ShaderType::TessEvaluation: | |
| return "TESS_EVALUATION"; | |
| default: | |
| UNREACHABLE(); | |
| return ""; | |
| } | |
| } | |
| std::string GetShaderDumpFileDirectory() | |
| { | |
| // Check the environment variable for the path to save and read shader dump files. | |
| std::string environmentVariableDumpDir = | |
| angle::GetAndSetEnvironmentVarOrUnCachedAndroidProperty(kShaderDumpPathVarName, | |
| kEShaderDumpPathPropertyName); | |
| if (!environmentVariableDumpDir.empty() && environmentVariableDumpDir.compare("0") != 0) | |
| { | |
| return environmentVariableDumpDir; | |
| } | |
| // Fall back to the temp dir. If that doesn't exist, use the current working directory. | |
| return angle::GetTempDirectory().valueOr(""); | |
| } | |
| std::string GetShaderDumpFileName(size_t shaderHash) | |
| { | |
| std::stringstream name; | |
| name << shaderHash << ".essl"; | |
| return name.str(); | |
| } | |
| struct CompileJob | |
| { | |
| virtual ~CompileJob() = default; | |
| virtual bool wait() { return compileEvent->wait() == angle::Result::Continue; } | |
| std::unique_ptr<CompileEvent> compileEvent; | |
| ShCompilerInstance shCompilerInstance; | |
| }; | |
| struct CompileJobDone final : public CompileJob | |
| { | |
| CompileJobDone(bool compiledIn) : compiled(compiledIn) {} | |
| bool wait() override { return compiled; } | |
| bool compiled; | |
| }; | |
| ShaderState::ShaderState(ShaderType shaderType) | |
| : mCompiledState(std::make_shared<CompiledShaderState>(shaderType)) | |
| {} | |
| ShaderState::~ShaderState() {} | |
| Shader::Shader(ShaderProgramManager *manager, | |
| rx::GLImplFactory *implFactory, | |
| const gl::Limitations &rendererLimitations, | |
| ShaderType type, | |
| ShaderProgramID handle) | |
| : mState(type), | |
| mImplementation(implFactory->createShader(mState)), | |
| mRendererLimitations(rendererLimitations), | |
| mHandle(handle), | |
| mRefCount(0), | |
| mDeleteStatus(false), | |
| mResourceManager(manager) | |
| { | |
| ASSERT(mImplementation); | |
| mShaderHash = {0}; | |
| } | |
| void Shader::onDestroy(const gl::Context *context) | |
| { | |
| resolveCompile(context); | |
| mImplementation->onDestroy(context); | |
| mBoundCompiler.set(context, nullptr); | |
| mImplementation.reset(nullptr); | |
| delete this; | |
| } | |
| Shader::~Shader() | |
| { | |
| ASSERT(!mImplementation); | |
| } | |
| angle::Result Shader::setLabel(const Context *context, const std::string &label) | |
| { | |
| mState.mLabel = label; | |
| if (mImplementation) | |
| { | |
| return mImplementation->onLabelUpdate(context); | |
| } | |
| return angle::Result::Continue; | |
| } | |
| const std::string &Shader::getLabel() const | |
| { | |
| return mState.mLabel; | |
| } | |
| ShaderProgramID Shader::getHandle() const | |
| { | |
| return mHandle; | |
| } | |
| void Shader::setSource(const Context *context, | |
| GLsizei count, | |
| const char *const *string, | |
| const GLint *length) | |
| { | |
| std::string source = JoinShaderSources(count, string, length); | |
| // Compute the hash based on the original source before any substitutions | |
| size_t sourceHash = ComputeShaderHash(source); | |
| const angle::FrontendFeatures &frontendFeatures = context->getFrontendFeatures(); | |
| bool substitutedShader = false; | |
| const char *suffix = "essl"; | |
| if (frontendFeatures.enableShaderSubstitution.enabled) | |
| { | |
| std::string subsitutionShaderPath = GetShaderDumpFilePath(sourceHash, suffix); | |
| std::string substituteShader; | |
| if (angle::ReadFileToString(subsitutionShaderPath, &substituteShader)) | |
| { | |
| source = std::move(substituteShader); | |
| substitutedShader = true; | |
| INFO() << "Shader substitute found, loading from " << subsitutionShaderPath; | |
| } | |
| } | |
| // Only dump shaders that have not been previously substituted. It would write the same data | |
| // back to the file. | |
| if (frontendFeatures.dumpShaderSource.enabled && !substitutedShader) | |
| { | |
| std::string dumpFile = GetShaderDumpFilePath(sourceHash, suffix); | |
| writeFile(dumpFile.c_str(), source.c_str(), source.length()); | |
| INFO() << "Dumped shader source: " << dumpFile; | |
| } | |
| mState.mSource = std::move(source); | |
| mState.mSourceHash = sourceHash; | |
| } | |
| int Shader::getInfoLogLength(const Context *context) | |
| { | |
| resolveCompile(context); | |
| if (mInfoLog.empty()) | |
| { | |
| return 0; | |
| } | |
| return (static_cast<int>(mInfoLog.length()) + 1); | |
| } | |
| void Shader::getInfoLog(const Context *context, GLsizei bufSize, GLsizei *length, char *infoLog) | |
| { | |
| resolveCompile(context); | |
| int index = 0; | |
| if (bufSize > 0) | |
| { | |
| index = std::min(bufSize - 1, static_cast<GLsizei>(mInfoLog.length())); | |
| memcpy(infoLog, mInfoLog.c_str(), index); | |
| infoLog[index] = '\0'; | |
| } | |
| if (length) | |
| { | |
| *length = index; | |
| } | |
| } | |
| int Shader::getSourceLength() const | |
| { | |
| return mState.mSource.empty() ? 0 : (static_cast<int>(mState.mSource.length()) + 1); | |
| } | |
| int Shader::getTranslatedSourceLength(const Context *context) | |
| { | |
| resolveCompile(context); | |
| if (mState.mCompiledState->translatedSource.empty()) | |
| { | |
| return 0; | |
| } | |
| return static_cast<int>(mState.mCompiledState->translatedSource.length()) + 1; | |
| } | |
| int Shader::getTranslatedSourceWithDebugInfoLength(const Context *context) | |
| { | |
| resolveCompile(context); | |
| const std::string &debugInfo = mImplementation->getDebugInfo(); | |
| if (debugInfo.empty()) | |
| { | |
| return 0; | |
| } | |
| return (static_cast<int>(debugInfo.length()) + 1); | |
| } | |
| // static | |
| void Shader::GetSourceImpl(const std::string &source, | |
| GLsizei bufSize, | |
| GLsizei *length, | |
| char *buffer) | |
| { | |
| int index = 0; | |
| if (bufSize > 0) | |
| { | |
| index = std::min(bufSize - 1, static_cast<GLsizei>(source.length())); | |
| memcpy(buffer, source.c_str(), index); | |
| buffer[index] = '\0'; | |
| } | |
| if (length) | |
| { | |
| *length = index; | |
| } | |
| } | |
| void Shader::getSource(GLsizei bufSize, GLsizei *length, char *buffer) const | |
| { | |
| GetSourceImpl(mState.mSource, bufSize, length, buffer); | |
| } | |
| void Shader::getTranslatedSource(const Context *context, | |
| GLsizei bufSize, | |
| GLsizei *length, | |
| char *buffer) | |
| { | |
| GetSourceImpl(getTranslatedSource(context), bufSize, length, buffer); | |
| } | |
| const std::string &Shader::getTranslatedSource(const Context *context) | |
| { | |
| resolveCompile(context); | |
| return mState.mCompiledState->translatedSource; | |
| } | |
| size_t Shader::getSourceHash() const | |
| { | |
| return mState.mSourceHash; | |
| } | |
| void Shader::getTranslatedSourceWithDebugInfo(const Context *context, | |
| GLsizei bufSize, | |
| GLsizei *length, | |
| char *buffer) | |
| { | |
| resolveCompile(context); | |
| const std::string &debugInfo = mImplementation->getDebugInfo(); | |
| GetSourceImpl(debugInfo, bufSize, length, buffer); | |
| } | |
| void Shader::compile(const Context *context, angle::JobResultExpectancy resultExpectancy) | |
| { | |
| resolveCompile(context); | |
| // Create a new compiled shader state. If any programs are currently linking using this shader, | |
| // they would use the old compiled state, and this shader is free to recompile in the meantime. | |
| mState.mCompiledState = std::make_shared<CompiledShaderState>(mState.getShaderType()); | |
| mInfoLog.clear(); | |
| ShCompileOptions options = {}; | |
| options.objectCode = true; | |
| options.emulateGLDrawID = true; | |
| // Add default options to WebGL shaders to prevent unexpected behavior during | |
| // compilation. | |
| if (context->isWebGL()) | |
| { | |
| options.initGLPosition = true; | |
| options.limitCallStackDepth = true; | |
| options.limitExpressionComplexity = true; | |
| options.enforcePackingRestrictions = true; | |
| options.initSharedVariables = true; | |
| if (context->getFrontendFeatures().rejectWebglShadersWithUndefinedBehavior.enabled) | |
| { | |
| options.rejectWebglShadersWithUndefinedBehavior = true; | |
| } | |
| } | |
| else | |
| { | |
| // Per https://github.com/KhronosGroup/WebGL/pull/3278 gl_BaseVertex/gl_BaseInstance are | |
| // removed from WebGL | |
| options.emulateGLBaseVertexBaseInstance = true; | |
| } | |
| if (context->getFrontendFeatures().forceInitShaderVariables.enabled) | |
| { | |
| options.initOutputVariables = true; | |
| options.initializeUninitializedLocals = true; | |
| } | |
| #if defined(ANGLE_ENABLE_ASSERTS) | |
| options.validateAST = true; | |
| #endif | |
| if (context->getState().usesPassthroughShaders()) | |
| { | |
| options.skipAllValidationAndTransforms = true; | |
| } | |
| // Find a shader in Blob Cache | |
| Compiler *compiler = context->getCompiler(); | |
| setShaderKey(context, options, compiler->getShaderOutputType(), | |
| compiler->getBuiltInResources()); | |
| ASSERT(!mShaderHash.empty()); | |
| MemoryShaderCache *shaderCache = context->getMemoryShaderCache(); | |
| if (shaderCache != nullptr) | |
| { | |
| egl::CacheGetResult result = | |
| shaderCache->getShader(context, this, mShaderHash, resultExpectancy); | |
| switch (result) | |
| { | |
| case egl::CacheGetResult::Success: | |
| return; | |
| case egl::CacheGetResult::Rejected: | |
| // Reset the state | |
| mState.mCompiledState = | |
| std::make_shared<CompiledShaderState>(mState.getShaderType()); | |
| break; | |
| case egl::CacheGetResult::NotFound: | |
| default: | |
| break; | |
| } | |
| } | |
| mBoundCompiler.set(context, compiler); | |
| ASSERT(mBoundCompiler.get()); | |
| ShCompilerInstance compilerInstance = mBoundCompiler->getInstance(mState.getShaderType()); | |
| ShHandle compilerHandle = compilerInstance.getHandle(); | |
| ASSERT(compilerHandle); | |
| // Cache load failed, fall through normal compiling. | |
| mState.mCompileStatus = CompileStatus::COMPILE_REQUESTED; | |
| // Ask the backend to prepare the translate task | |
| std::shared_ptr<rx::ShaderTranslateTask> translateTask = | |
| mImplementation->compile(context, &options); | |
| // Prepare the complete compile task | |
| const size_t maxComputeWorkGroupInvocations = | |
| static_cast<size_t>(context->getCaps().maxComputeWorkGroupInvocations); | |
| const size_t maxComputeSharedMemory = context->getCaps().maxComputeSharedMemorySize; | |
| std::shared_ptr<CompileTask> compileTask( | |
| new CompileTask(context->getFrontendFeatures(), compilerInstance.getHandle(), | |
| compilerInstance.getShaderOutputType(), options, mState.mSource, | |
| mState.mSourceHash, mState.mCompiledState, maxComputeWorkGroupInvocations, | |
| maxComputeSharedMemory, std::move(translateTask))); | |
| // The GL backend relies on the driver's internal parallel compilation, and thus does not use a | |
| // thread to compile. A front-end feature selects whether the single-threaded pool must be | |
| // used. | |
| const angle::JobThreadSafety threadSafety = | |
| context->getFrontendFeatures().compileJobIsThreadSafe.enabled | |
| ? angle::JobThreadSafety::Safe | |
| : angle::JobThreadSafety::Unsafe; | |
| std::shared_ptr<angle::WaitableEvent> compileEvent = | |
| context->postCompileLinkTask(compileTask, threadSafety, resultExpectancy); | |
| mCompileJob = std::make_shared<CompileJob>(); | |
| mCompileJob->shCompilerInstance = std::move(compilerInstance); | |
| mCompileJob->compileEvent = std::make_unique<CompileEvent>(compileTask, compileEvent); | |
| } | |
| void Shader::resolveCompile(const Context *context) | |
| { | |
| if (!mState.compilePending()) | |
| { | |
| return; | |
| } | |
| ASSERT(mCompileJob.get()); | |
| mState.mCompileStatus = CompileStatus::IS_RESOLVING; | |
| const bool success = WaitCompileJobUnlocked(mCompileJob); | |
| mInfoLog = std::move(mCompileJob->compileEvent->getInfoLog()); | |
| mState.mCompileStatus = success ? CompileStatus::COMPILED : CompileStatus::NOT_COMPILED; | |
| if (mCompileJob->shCompilerInstance.getHandle()) | |
| { | |
| // Only save this shader to the cache if it was a compile from source (not load from binary) | |
| if (success) | |
| { | |
| MemoryShaderCache *shaderCache = context->getMemoryShaderCache(); | |
| if (shaderCache != nullptr) | |
| { | |
| // Save to the shader cache. | |
| if (shaderCache->putShader(context, mShaderHash, this) != angle::Result::Continue) | |
| { | |
| ANGLE_PERF_WARNING(context->getState().getDebug(), GL_DEBUG_SEVERITY_LOW, | |
| "Failed to save compiled shader to memory shader cache."); | |
| } | |
| } | |
| } | |
| mBoundCompiler->putInstance(std::move(mCompileJob->shCompilerInstance)); | |
| } | |
| mCompileJob.reset(); | |
| } | |
| void Shader::addRef() | |
| { | |
| mRefCount++; | |
| } | |
| void Shader::release(const Context *context) | |
| { | |
| mRefCount--; | |
| if (mRefCount == 0 && mDeleteStatus) | |
| { | |
| mResourceManager->deleteShader(context, mHandle); | |
| } | |
| } | |
| unsigned int Shader::getRefCount() const | |
| { | |
| return mRefCount; | |
| } | |
| bool Shader::isFlaggedForDeletion() const | |
| { | |
| return mDeleteStatus; | |
| } | |
| void Shader::flagForDeletion() | |
| { | |
| mDeleteStatus = true; | |
| } | |
| bool Shader::isCompiled(const Context *context) | |
| { | |
| resolveCompile(context); | |
| return mState.mCompileStatus == CompileStatus::COMPILED; | |
| } | |
| bool Shader::isCompleted() | |
| { | |
| return !mState.compilePending() || !mCompileJob->compileEvent->isCompiling(); | |
| } | |
| SharedCompileJob Shader::getCompileJob(SharedCompiledShaderState *compiledStateOut) | |
| { | |
| // mState.mCompiledState is the same as the one in the current compile job, because this call is | |
| // made during link which expects to pick up the currently compiled (or pending compilation) | |
| // state. | |
| *compiledStateOut = mState.mCompiledState; | |
| if (mCompileJob) | |
| { | |
| ASSERT(mState.compilePending()); | |
| return mCompileJob; | |
| } | |
| ASSERT(!mState.compilePending()); | |
| ASSERT(mState.mCompileStatus == CompileStatus::COMPILED || | |
| mState.mCompileStatus == CompileStatus::NOT_COMPILED); | |
| return std::make_shared<CompileJobDone>(mState.mCompileStatus == CompileStatus::COMPILED); | |
| } | |
| angle::Result Shader::serialize(const Context *context, angle::MemoryBuffer *binaryOut) const | |
| { | |
| BinaryOutputStream stream; | |
| stream.writeInt(kShaderCacheIdentifier); | |
| mState.mCompiledState->serialize(stream); | |
| ASSERT(binaryOut); | |
| if (!binaryOut->resize(stream.length())) | |
| { | |
| ANGLE_PERF_WARNING(context->getState().getDebug(), GL_DEBUG_SEVERITY_LOW, | |
| "Failed to allocate enough memory to serialize a shader. (%zu bytes)", | |
| stream.length()); | |
| return angle::Result::Stop; | |
| } | |
| memcpy(binaryOut->data(), stream.data(), stream.length()); | |
| return angle::Result::Continue; | |
| } | |
| bool Shader::deserialize(BinaryInputStream &stream) | |
| { | |
| mState.mCompiledState->deserialize(stream); | |
| if (stream.error()) | |
| { | |
| // Error while deserializing binary stream | |
| return false; | |
| } | |
| // Note: Currently, shader binaries are only supported on backends that don't happen to have any | |
| // additional state used at link time. If other backends implement this functionality, this | |
| // function should call into the backend object to deserialize their part. | |
| return true; | |
| } | |
| bool Shader::loadBinary(const Context *context, | |
| const void *binary, | |
| GLsizei length, | |
| angle::JobResultExpectancy resultExpectancy) | |
| { | |
| return loadBinaryImpl(context, binary, length, resultExpectancy, false); | |
| } | |
| bool Shader::loadShaderBinary(const Context *context, | |
| const void *binary, | |
| GLsizei length, | |
| angle::JobResultExpectancy resultExpectancy) | |
| { | |
| return loadBinaryImpl(context, binary, length, resultExpectancy, true); | |
| } | |
| bool Shader::loadBinaryImpl(const Context *context, | |
| const void *binary, | |
| GLsizei length, | |
| angle::JobResultExpectancy resultExpectancy, | |
| bool generatedWithOfflineCompiler) | |
| { | |
| BinaryInputStream stream(binary, length); | |
| mState.mCompiledState = std::make_shared<CompiledShaderState>(mState.getShaderType()); | |
| // Shader binaries generated with offline compiler have additional fields | |
| if (generatedWithOfflineCompiler) | |
| { | |
| // Load binary from a glShaderBinary call. | |
| // Validation layer should have already verified that the shader program version and shader | |
| // type match | |
| std::vector<uint8_t> commitString(angle::GetANGLEShaderProgramVersionHashSize(), 0); | |
| stream.readBytes(commitString.data(), commitString.size()); | |
| ASSERT(memcmp(commitString.data(), angle::GetANGLEShaderProgramVersion(), | |
| commitString.size()) == 0); | |
| gl::ShaderType shaderType; | |
| stream.readEnum(&shaderType); | |
| ASSERT(mState.getShaderType() == shaderType); | |
| // Get fields needed to generate the key for memory caches. | |
| ShShaderOutput outputType; | |
| stream.readEnum<ShShaderOutput>(&outputType); | |
| // Get the shader's source string. | |
| mState.mSource = stream.readString(); | |
| // In the absence of element-by-element serialize/deserialize functions, read | |
| // ShCompileOptions and ShBuiltInResources as raw binary blobs. | |
| ShCompileOptions compileOptions; | |
| stream.readBytes(reinterpret_cast<uint8_t *>(&compileOptions), sizeof(ShCompileOptions)); | |
| ShBuiltInResources resources; | |
| stream.readBytes(reinterpret_cast<uint8_t *>(&resources), sizeof(ShBuiltInResources)); | |
| setShaderKey(context, compileOptions, outputType, resources); | |
| } | |
| else | |
| { | |
| // Load binary from shader cache. | |
| if (stream.readInt<uint32_t>() != kShaderCacheIdentifier) | |
| { | |
| return false; | |
| } | |
| } | |
| if (!deserialize(stream)) | |
| { | |
| return false; | |
| } | |
| mState.mCompileStatus = CompileStatus::COMPILE_REQUESTED; | |
| // Ask the backend to prepare the translate task | |
| std::shared_ptr<rx::ShaderTranslateTask> translateTask = | |
| mImplementation->load(context, &stream); | |
| std::shared_ptr<CompileTask> compileTask(new CompileTask( | |
| context->getFrontendFeatures(), mState.mCompiledState, std::move(translateTask))); | |
| const angle::JobThreadSafety threadSafety = GetTranslateTaskThreadSafety(context); | |
| std::shared_ptr<angle::WaitableEvent> compileEvent = | |
| context->postCompileLinkTask(compileTask, threadSafety, resultExpectancy); | |
| mCompileJob = std::make_shared<CompileJob>(); | |
| mCompileJob->compileEvent = std::make_unique<CompileEvent>(compileTask, compileEvent); | |
| return true; | |
| } | |
| void Shader::setShaderKey(const Context *context, | |
| const ShCompileOptions &compileOptions, | |
| const ShShaderOutput &outputType, | |
| const ShBuiltInResources &resources) | |
| { | |
| // Compute shader key. | |
| angle::base::SecureHashAlgorithm hasher; | |
| hasher.Init(); | |
| // Start with the shader type and source. | |
| AppendHashValue(hasher, mState.getShaderType()); | |
| hasher.Update(mState.getSource().c_str(), mState.getSource().length()); | |
| // Include the shader program version hash. | |
| hasher.Update(angle::GetANGLEShaderProgramVersion(), | |
| angle::GetANGLEShaderProgramVersionHashSize()); | |
| AppendHashValue(hasher, Compiler::SelectShaderSpec(context->getState())); | |
| AppendHashValue(hasher, outputType); | |
| hasher.Update(reinterpret_cast<const uint8_t *>(&compileOptions), sizeof(compileOptions)); | |
| // Include the ShBuiltInResources, which represent the extensions and constants used by the | |
| // shader. | |
| hasher.Update(reinterpret_cast<const uint8_t *>(&resources), sizeof(resources)); | |
| // Call the secure SHA hashing function. | |
| hasher.Final(); | |
| memcpy(mShaderHash.data(), hasher.Digest(), angle::base::kSHA1Length); | |
| } | |
| bool WaitCompileJobUnlocked(const SharedCompileJob &compileJob) | |
| { | |
| // Simply wait for the job and return whether it succeeded. Do nothing more as this can be | |
| // called from multiple threads. Caching of the shader results and compiler clean up will be | |
| // done in resolveCompile() when the main thread happens to call it. | |
| return compileJob->wait(); | |
| } | |
| } // namespace gl |