| /* | |
| * Copyright 2014 Google Inc. | |
| * | |
| * Use of this source code is governed by a BSD-style license that can be | |
| * found in the LICENSE file. | |
| */ | |
| #include "src/gpu/gl/builders/GrGLProgramBuilder.h" | |
| #include "include/gpu/GrContext.h" | |
| #include "src/core/SkATrace.h" | |
| #include "src/core/SkAutoMalloc.h" | |
| #include "src/core/SkReader32.h" | |
| #include "src/core/SkTraceEvent.h" | |
| #include "src/core/SkWriter32.h" | |
| #include "src/gpu/GrAutoLocaleSetter.h" | |
| #include "src/gpu/GrContextPriv.h" | |
| #include "src/gpu/GrCoordTransform.h" | |
| #include "src/gpu/GrPersistentCacheUtils.h" | |
| #include "src/gpu/GrProgramDesc.h" | |
| #include "src/gpu/GrShaderCaps.h" | |
| #include "src/gpu/GrShaderUtils.h" | |
| #include "src/gpu/GrSwizzle.h" | |
| #include "src/gpu/gl/GrGLGpu.h" | |
| #include "src/gpu/gl/GrGLProgram.h" | |
| #include "src/gpu/gl/builders/GrGLProgramBuilder.h" | |
| #include "src/gpu/gl/builders/GrGLShaderStringBuilder.h" | |
| #include "src/gpu/glsl/GrGLSLFragmentProcessor.h" | |
| #include "src/gpu/glsl/GrGLSLGeometryProcessor.h" | |
| #include "src/gpu/glsl/GrGLSLProgramDataManager.h" | |
| #include "src/gpu/glsl/GrGLSLXferProcessor.h" | |
| #define GL_CALL(X) GR_GL_CALL(this->gpu()->glInterface(), X) | |
| #define GL_CALL_RET(R, X) GR_GL_CALL_RET(this->gpu()->glInterface(), R, X) | |
| static void cleanup_shaders(GrGLGpu* gpu, const SkTDArray<GrGLuint>& shaderIDs) { | |
| for (int i = 0; i < shaderIDs.count(); ++i) { | |
| GR_GL_CALL(gpu->glInterface(), DeleteShader(shaderIDs[i])); | |
| } | |
| } | |
| static void cleanup_program(GrGLGpu* gpu, GrGLuint programID, | |
| const SkTDArray<GrGLuint>& shaderIDs) { | |
| GR_GL_CALL(gpu->glInterface(), DeleteProgram(programID)); | |
| cleanup_shaders(gpu, shaderIDs); | |
| } | |
| GrGLProgram* GrGLProgramBuilder::CreateProgram(GrRenderTarget* renderTarget, | |
| const GrProgramInfo& programInfo, | |
| GrProgramDesc* desc, | |
| GrGLGpu* gpu, | |
| const GrGLPrecompiledProgram* precompiledProgram) { | |
| ATRACE_ANDROID_FRAMEWORK("Shader Compile"); | |
| GrAutoLocaleSetter als("C"); | |
| // create a builder. This will be handed off to effects so they can use it to add | |
| // uniforms, varyings, textures, etc | |
| GrGLProgramBuilder builder(gpu, renderTarget, programInfo, desc); | |
| auto persistentCache = gpu->getContext()->priv().getPersistentCache(); | |
| if (persistentCache && !precompiledProgram) { | |
| sk_sp<SkData> key = SkData::MakeWithoutCopy(desc->asKey(), desc->keyLength()); | |
| builder.fCached = persistentCache->load(*key); | |
| // the eventual end goal is to completely skip emitAndInstallProcs on a cache hit, but it's | |
| // doing necessary setup in addition to generating the SkSL code. Currently we are only able | |
| // to skip the SkSL->GLSL step on a cache hit. | |
| } | |
| if (!builder.emitAndInstallProcs()) { | |
| return nullptr; | |
| } | |
| return builder.finalize(precompiledProgram); | |
| } | |
| ///////////////////////////////////////////////////////////////////////////// | |
| GrGLProgramBuilder::GrGLProgramBuilder(GrGLGpu* gpu, | |
| GrRenderTarget* renderTarget, | |
| const GrProgramInfo& programInfo, | |
| GrProgramDesc* desc) | |
| : INHERITED(renderTarget, programInfo, desc) | |
| , fGpu(gpu) | |
| , fVaryingHandler(this) | |
| , fUniformHandler(this) | |
| , fVertexAttributeCnt(0) | |
| , fInstanceAttributeCnt(0) | |
| , fVertexStride(0) | |
| , fInstanceStride(0) {} | |
| const GrCaps* GrGLProgramBuilder::caps() const { | |
| return fGpu->caps(); | |
| } | |
| bool GrGLProgramBuilder::compileAndAttachShaders(const SkSL::String& glsl, | |
| GrGLuint programId, | |
| GrGLenum type, | |
| SkTDArray<GrGLuint>* shaderIds, | |
| GrContextOptions::ShaderErrorHandler* errHandler) { | |
| GrGLGpu* gpu = this->gpu(); | |
| GrGLuint shaderId = GrGLCompileAndAttachShader(gpu->glContext(), | |
| programId, | |
| type, | |
| glsl, | |
| gpu->stats(), | |
| errHandler); | |
| if (!shaderId) { | |
| return false; | |
| } | |
| *shaderIds->append() = shaderId; | |
| return true; | |
| } | |
| void GrGLProgramBuilder::computeCountsAndStrides(GrGLuint programID, | |
| const GrPrimitiveProcessor& primProc, | |
| bool bindAttribLocations) { | |
| fVertexAttributeCnt = primProc.numVertexAttributes(); | |
| fInstanceAttributeCnt = primProc.numInstanceAttributes(); | |
| fAttributes.reset( | |
| new GrGLProgram::Attribute[fVertexAttributeCnt + fInstanceAttributeCnt]); | |
| auto addAttr = [&](int i, const auto& a, size_t* stride) { | |
| fAttributes[i].fCPUType = a.cpuType(); | |
| fAttributes[i].fGPUType = a.gpuType(); | |
| fAttributes[i].fOffset = *stride; | |
| *stride += a.sizeAlign4(); | |
| fAttributes[i].fLocation = i; | |
| if (bindAttribLocations) { | |
| GL_CALL(BindAttribLocation(programID, i, a.name())); | |
| } | |
| }; | |
| fVertexStride = 0; | |
| int i = 0; | |
| for (const auto& attr : primProc.vertexAttributes()) { | |
| addAttr(i++, attr, &fVertexStride); | |
| } | |
| SkASSERT(fVertexStride == primProc.vertexStride()); | |
| fInstanceStride = 0; | |
| for (const auto& attr : primProc.instanceAttributes()) { | |
| addAttr(i++, attr, &fInstanceStride); | |
| } | |
| SkASSERT(fInstanceStride == primProc.instanceStride()); | |
| } | |
| void GrGLProgramBuilder::addInputVars(const SkSL::Program::Inputs& inputs) { | |
| if (inputs.fRTWidth) { | |
| this->addRTWidthUniform(SKSL_RTWIDTH_NAME); | |
| } | |
| if (inputs.fRTHeight) { | |
| this->addRTHeightUniform(SKSL_RTHEIGHT_NAME); | |
| } | |
| } | |
| static constexpr SkFourByteTag kSKSL_Tag = SkSetFourByteTag('S', 'K', 'S', 'L'); | |
| static constexpr SkFourByteTag kGLSL_Tag = SkSetFourByteTag('G', 'L', 'S', 'L'); | |
| static constexpr SkFourByteTag kGLPB_Tag = SkSetFourByteTag('G', 'L', 'P', 'B'); | |
| void GrGLProgramBuilder::storeShaderInCache(const SkSL::Program::Inputs& inputs, GrGLuint programID, | |
| const SkSL::String shaders[], bool isSkSL, | |
| SkSL::Program::Settings* settings) { | |
| if (!this->gpu()->getContext()->priv().getPersistentCache()) { | |
| return; | |
| } | |
| sk_sp<SkData> key = SkData::MakeWithoutCopy(this->desc()->asKey(), this->desc()->keyLength()); | |
| if (fGpu->glCaps().programBinarySupport()) { | |
| // binary cache | |
| GrGLsizei length = 0; | |
| GL_CALL(GetProgramiv(programID, GL_PROGRAM_BINARY_LENGTH, &length)); | |
| if (length > 0) { | |
| SkWriter32 writer; | |
| writer.write32(kGLPB_Tag); | |
| writer.writePad(&inputs, sizeof(inputs)); | |
| writer.write32(length); | |
| void* binary = writer.reservePad(length); | |
| GrGLenum binaryFormat; | |
| GL_CALL(GetProgramBinary(programID, length, &length, &binaryFormat, binary)); | |
| writer.write32(binaryFormat); | |
| auto data = writer.snapshotAsData(); | |
| this->gpu()->getContext()->priv().getPersistentCache()->store(*key, *data); | |
| } | |
| } else { | |
| // source cache, plus metadata to allow for a complete precompile | |
| GrPersistentCacheUtils::ShaderMetadata meta; | |
| meta.fSettings = settings; | |
| meta.fHasCustomColorOutput = fFS.hasCustomColorOutput(); | |
| meta.fHasSecondaryColorOutput = fFS.hasSecondaryOutput(); | |
| for (const auto& attr : this->primitiveProcessor().vertexAttributes()) { | |
| meta.fAttributeNames.emplace_back(attr.name()); | |
| } | |
| for (const auto& attr : this->primitiveProcessor().instanceAttributes()) { | |
| meta.fAttributeNames.emplace_back(attr.name()); | |
| } | |
| auto data = GrPersistentCacheUtils::PackCachedShaders(isSkSL ? kSKSL_Tag : kGLSL_Tag, | |
| shaders, &inputs, 1, &meta); | |
| this->gpu()->getContext()->priv().getPersistentCache()->store(*key, *data); | |
| } | |
| } | |
| GrGLProgram* GrGLProgramBuilder::finalize(const GrGLPrecompiledProgram* precompiledProgram) { | |
| TRACE_EVENT0("skia.gpu", TRACE_FUNC); | |
| // verify we can get a program id | |
| GrGLuint programID; | |
| if (precompiledProgram) { | |
| programID = precompiledProgram->fProgramID; | |
| } else { | |
| GL_CALL_RET(programID, CreateProgram()); | |
| } | |
| if (0 == programID) { | |
| return nullptr; | |
| } | |
| if (this->gpu()->glCaps().programBinarySupport() && | |
| this->gpu()->glCaps().programParameterSupport() && | |
| this->gpu()->getContext()->priv().getPersistentCache() && | |
| !precompiledProgram) { | |
| GL_CALL(ProgramParameteri(programID, GR_GL_PROGRAM_BINARY_RETRIEVABLE_HINT, GR_GL_TRUE)); | |
| } | |
| this->finalizeShaders(); | |
| // compile shaders and bind attributes / uniforms | |
| auto errorHandler = this->gpu()->getContext()->priv().getShaderErrorHandler(); | |
| const GrPrimitiveProcessor& primProc = this->primitiveProcessor(); | |
| SkSL::Program::Settings settings; | |
| settings.fCaps = this->gpu()->glCaps().shaderCaps(); | |
| settings.fFlipY = this->origin() != kTopLeft_GrSurfaceOrigin; | |
| settings.fSharpenTextures = | |
| this->gpu()->getContext()->priv().options().fSharpenMipmappedTextures; | |
| settings.fFragColorIsInOut = this->fragColorIsInOut(); | |
| SkSL::Program::Inputs inputs; | |
| SkTDArray<GrGLuint> shadersToDelete; | |
| // Calling GetProgramiv is expensive in Chromium. Assume success in release builds. | |
| bool checkLinked = kChromium_GrGLDriver != fGpu->ctxInfo().driver(); | |
| #ifdef SK_DEBUG | |
| checkLinked = true; | |
| #endif | |
| bool cached = fCached.get() != nullptr; | |
| bool usedProgramBinaries = false; | |
| SkSL::String glsl[kGrShaderTypeCount]; | |
| SkSL::String* sksl[kGrShaderTypeCount] = { | |
| &fVS.fCompilerString, | |
| &fGS.fCompilerString, | |
| &fFS.fCompilerString, | |
| }; | |
| SkSL::String cached_sksl[kGrShaderTypeCount]; | |
| if (precompiledProgram) { | |
| // This is very similar to when we get program binaries. We even set that flag, as it's | |
| // used to prevent other compile work later, and to force re-querying uniform locations. | |
| this->addInputVars(precompiledProgram->fInputs); | |
| this->computeCountsAndStrides(programID, primProc, false); | |
| usedProgramBinaries = true; | |
| } else if (cached) { | |
| SkReader32 reader(fCached->data(), fCached->size()); | |
| SkFourByteTag shaderType = reader.readU32(); | |
| switch (shaderType) { | |
| case kGLPB_Tag: { | |
| // Program binary cache hit. We may opt not to use this if we don't trust program | |
| // binaries on this driver | |
| if (!fGpu->glCaps().programBinarySupport()) { | |
| cached = false; | |
| break; | |
| } | |
| reader.read(&inputs, sizeof(inputs)); | |
| GrGLsizei length = reader.readInt(); | |
| const void* binary = reader.skip(length); | |
| GrGLenum binaryFormat = reader.readU32(); | |
| GrGLClearErr(this->gpu()->glInterface()); | |
| GR_GL_CALL_NOERRCHECK(this->gpu()->glInterface(), | |
| ProgramBinary(programID, binaryFormat, | |
| const_cast<void*>(binary), length)); | |
| if (GR_GL_GET_ERROR(this->gpu()->glInterface()) == GR_GL_NO_ERROR) { | |
| if (checkLinked) { | |
| cached = this->checkLinkStatus(programID, errorHandler, nullptr, nullptr); | |
| } | |
| if (cached) { | |
| this->addInputVars(inputs); | |
| this->computeCountsAndStrides(programID, primProc, false); | |
| } | |
| } else { | |
| cached = false; | |
| } | |
| usedProgramBinaries = cached; | |
| break; | |
| } | |
| case kGLSL_Tag: | |
| // Source cache hit, we don't need to compile the SkSL->GLSL | |
| GrPersistentCacheUtils::UnpackCachedShaders(&reader, glsl, &inputs, 1); | |
| break; | |
| case kSKSL_Tag: | |
| // SkSL cache hit, this should only happen in tools overriding the generated SkSL | |
| GrPersistentCacheUtils::UnpackCachedShaders(&reader, cached_sksl, &inputs, 1); | |
| for (int i = 0; i < kGrShaderTypeCount; ++i) { | |
| sksl[i] = &cached_sksl[i]; | |
| } | |
| break; | |
| } | |
| } | |
| if (!usedProgramBinaries) { | |
| // Either a cache miss, or we got something other than binaries from the cache | |
| /* | |
| Fragment Shader | |
| */ | |
| if (glsl[kFragment_GrShaderType].empty()) { | |
| // Don't have cached GLSL, need to compile SkSL->GLSL | |
| if (fFS.fForceHighPrecision) { | |
| settings.fForceHighPrecision = true; | |
| } | |
| std::unique_ptr<SkSL::Program> fs = GrSkSLtoGLSL(gpu()->glContext(), | |
| SkSL::Program::kFragment_Kind, | |
| *sksl[kFragment_GrShaderType], | |
| settings, | |
| &glsl[kFragment_GrShaderType], | |
| errorHandler); | |
| if (!fs) { | |
| cleanup_program(fGpu, programID, shadersToDelete); | |
| return nullptr; | |
| } | |
| inputs = fs->fInputs; | |
| } | |
| this->addInputVars(inputs); | |
| if (!this->compileAndAttachShaders(glsl[kFragment_GrShaderType], programID, | |
| GR_GL_FRAGMENT_SHADER, &shadersToDelete, errorHandler)) { | |
| cleanup_program(fGpu, programID, shadersToDelete); | |
| return nullptr; | |
| } | |
| /* | |
| Vertex Shader | |
| */ | |
| if (glsl[kVertex_GrShaderType].empty()) { | |
| // Don't have cached GLSL, need to compile SkSL->GLSL | |
| std::unique_ptr<SkSL::Program> vs = GrSkSLtoGLSL(gpu()->glContext(), | |
| SkSL::Program::kVertex_Kind, | |
| *sksl[kVertex_GrShaderType], | |
| settings, | |
| &glsl[kVertex_GrShaderType], | |
| errorHandler); | |
| if (!vs) { | |
| cleanup_program(fGpu, programID, shadersToDelete); | |
| return nullptr; | |
| } | |
| } | |
| if (!this->compileAndAttachShaders(glsl[kVertex_GrShaderType], programID, | |
| GR_GL_VERTEX_SHADER, &shadersToDelete, errorHandler)) { | |
| cleanup_program(fGpu, programID, shadersToDelete); | |
| return nullptr; | |
| } | |
| // This also binds vertex attribute locations. NVPR doesn't really use vertices, | |
| // even though it requires a vertex shader in the program. | |
| if (!primProc.isPathRendering()) { | |
| this->computeCountsAndStrides(programID, primProc, true); | |
| } | |
| /* | |
| Geometry Shader | |
| */ | |
| if (primProc.willUseGeoShader()) { | |
| if (glsl[kGeometry_GrShaderType].empty()) { | |
| // Don't have cached GLSL, need to compile SkSL->GLSL | |
| std::unique_ptr<SkSL::Program> gs; | |
| gs = GrSkSLtoGLSL(gpu()->glContext(), | |
| SkSL::Program::kGeometry_Kind, | |
| *sksl[kGeometry_GrShaderType], | |
| settings, | |
| &glsl[kGeometry_GrShaderType], | |
| errorHandler); | |
| if (!gs) { | |
| cleanup_program(fGpu, programID, shadersToDelete); | |
| return nullptr; | |
| } | |
| } | |
| if (!this->compileAndAttachShaders(glsl[kGeometry_GrShaderType], programID, | |
| GR_GL_GEOMETRY_SHADER, &shadersToDelete, | |
| errorHandler)) { | |
| cleanup_program(fGpu, programID, shadersToDelete); | |
| return nullptr; | |
| } | |
| } | |
| this->bindProgramResourceLocations(programID); | |
| GL_CALL(LinkProgram(programID)); | |
| if (checkLinked) { | |
| if (!this->checkLinkStatus(programID, errorHandler, sksl, glsl)) { | |
| cleanup_program(fGpu, programID, shadersToDelete); | |
| return nullptr; | |
| } | |
| } | |
| } | |
| this->resolveProgramResourceLocations(programID, usedProgramBinaries); | |
| cleanup_shaders(fGpu, shadersToDelete); | |
| // With ANGLE, we can't cache path-rendering programs. We use ProgramPathFragmentInputGen, | |
| // and ANGLE's deserialized program state doesn't restore enough state to handle that. | |
| // The native NVIDIA drivers do, but this is such an edge case that it's easier to just | |
| // black-list caching these programs in all cases. See: anglebug.com/3619 | |
| // We also can't cache SkSL or GLSL if we were given a precompiled program, but there's not | |
| // much point in doing so. | |
| if (!cached && !primProc.isPathRendering() && !precompiledProgram) { | |
| bool isSkSL = false; | |
| if (fGpu->getContext()->priv().options().fShaderCacheStrategy == | |
| GrContextOptions::ShaderCacheStrategy::kSkSL) { | |
| for (int i = 0; i < kGrShaderTypeCount; ++i) { | |
| glsl[i] = GrShaderUtils::PrettyPrint(*sksl[i]); | |
| } | |
| isSkSL = true; | |
| } | |
| this->storeShaderInCache(inputs, programID, glsl, isSkSL, &settings); | |
| } | |
| return this->createProgram(programID); | |
| } | |
| void GrGLProgramBuilder::bindProgramResourceLocations(GrGLuint programID) { | |
| fUniformHandler.bindUniformLocations(programID, fGpu->glCaps()); | |
| const GrGLCaps& caps = this->gpu()->glCaps(); | |
| if (fFS.hasCustomColorOutput() && caps.bindFragDataLocationSupport()) { | |
| GL_CALL(BindFragDataLocation(programID, 0, | |
| GrGLSLFragmentShaderBuilder::DeclaredColorOutputName())); | |
| } | |
| if (fFS.hasSecondaryOutput() && caps.shaderCaps()->mustDeclareFragmentShaderOutput()) { | |
| GL_CALL(BindFragDataLocationIndexed(programID, 0, 1, | |
| GrGLSLFragmentShaderBuilder::DeclaredSecondaryColorOutputName())); | |
| } | |
| // handle NVPR separable varyings | |
| if (!fGpu->glCaps().shaderCaps()->pathRenderingSupport() || | |
| !fGpu->glPathRendering()->shouldBindFragmentInputs()) { | |
| return; | |
| } | |
| int count = fVaryingHandler.fPathProcVaryingInfos.count(); | |
| for (int i = 0; i < count; ++i) { | |
| GL_CALL(BindFragmentInputLocation(programID, i, | |
| fVaryingHandler.fPathProcVaryingInfos[i].fVariable.c_str())); | |
| fVaryingHandler.fPathProcVaryingInfos[i].fLocation = i; | |
| } | |
| } | |
| bool GrGLProgramBuilder::checkLinkStatus(GrGLuint programID, | |
| GrContextOptions::ShaderErrorHandler* errorHandler, | |
| SkSL::String* sksl[], const SkSL::String glsl[]) { | |
| GrGLint linked = GR_GL_INIT_ZERO; | |
| GL_CALL(GetProgramiv(programID, GR_GL_LINK_STATUS, &linked)); | |
| if (!linked) { | |
| SkSL::String allShaders; | |
| if (sksl) { | |
| allShaders.appendf("// Vertex SKSL\n%s\n", sksl[kVertex_GrShaderType]->c_str()); | |
| if (!sksl[kGeometry_GrShaderType]->empty()) { | |
| allShaders.appendf("// Geometry SKSL\n%s\n", sksl[kGeometry_GrShaderType]->c_str()); | |
| } | |
| allShaders.appendf("// Fragment SKSL\n%s\n", sksl[kFragment_GrShaderType]->c_str()); | |
| } | |
| if (glsl) { | |
| allShaders.appendf("// Vertex GLSL\n%s\n", glsl[kVertex_GrShaderType].c_str()); | |
| if (!glsl[kGeometry_GrShaderType].empty()) { | |
| allShaders.appendf("// Geometry GLSL\n%s\n", glsl[kGeometry_GrShaderType].c_str()); | |
| } | |
| allShaders.appendf("// Fragment GLSL\n%s\n", glsl[kFragment_GrShaderType].c_str()); | |
| } | |
| GrGLint infoLen = GR_GL_INIT_ZERO; | |
| GL_CALL(GetProgramiv(programID, GR_GL_INFO_LOG_LENGTH, &infoLen)); | |
| SkAutoMalloc log(sizeof(char)*(infoLen+1)); // outside if for debugger | |
| if (infoLen > 0) { | |
| // retrieve length even though we don't need it to workaround | |
| // bug in chrome cmd buffer param validation. | |
| GrGLsizei length = GR_GL_INIT_ZERO; | |
| GL_CALL(GetProgramInfoLog(programID, infoLen+1, &length, (char*)log.get())); | |
| } | |
| errorHandler->compileError(allShaders.c_str(), infoLen > 0 ? (const char*)log.get() : ""); | |
| } | |
| return SkToBool(linked); | |
| } | |
| void GrGLProgramBuilder::resolveProgramResourceLocations(GrGLuint programID, bool force) { | |
| fUniformHandler.getUniformLocations(programID, fGpu->glCaps(), force); | |
| // handle NVPR separable varyings | |
| if (!fGpu->glCaps().shaderCaps()->pathRenderingSupport() || | |
| fGpu->glPathRendering()->shouldBindFragmentInputs()) { | |
| return; | |
| } | |
| int count = fVaryingHandler.fPathProcVaryingInfos.count(); | |
| for (int i = 0; i < count; ++i) { | |
| GrGLint location; | |
| GL_CALL_RET(location, GetProgramResourceLocation( | |
| programID, | |
| GR_GL_FRAGMENT_INPUT, | |
| fVaryingHandler.fPathProcVaryingInfos[i].fVariable.c_str())); | |
| fVaryingHandler.fPathProcVaryingInfos[i].fLocation = location; | |
| } | |
| } | |
| GrGLProgram* GrGLProgramBuilder::createProgram(GrGLuint programID) { | |
| return new GrGLProgram(fGpu, | |
| fUniformHandles, | |
| programID, | |
| fUniformHandler.fUniforms, | |
| fUniformHandler.fSamplers, | |
| fVaryingHandler.fPathProcVaryingInfos, | |
| std::move(fGeometryProcessor), | |
| std::move(fXferProcessor), | |
| std::move(fFragmentProcessors), | |
| fFragmentProcessorCnt, | |
| std::move(fAttributes), | |
| fVertexAttributeCnt, | |
| fInstanceAttributeCnt, | |
| fVertexStride, | |
| fInstanceStride); | |
| } | |
| bool GrGLProgramBuilder::PrecompileProgram(GrGLPrecompiledProgram* precompiledProgram, | |
| GrGLGpu* gpu, | |
| const SkData& cachedData) { | |
| SkReader32 reader(cachedData.data(), cachedData.size()); | |
| SkFourByteTag shaderType = reader.readU32(); | |
| if (shaderType != kSKSL_Tag) { | |
| // TODO: Support GLSL, and maybe even program binaries, too? | |
| return false; | |
| } | |
| const GrGLInterface* gl = gpu->glInterface(); | |
| auto errorHandler = gpu->getContext()->priv().getShaderErrorHandler(); | |
| GrGLuint programID; | |
| GR_GL_CALL_RET(gl, programID, CreateProgram()); | |
| if (0 == programID) { | |
| return false; | |
| } | |
| SkTDArray<GrGLuint> shadersToDelete; | |
| SkSL::Program::Settings settings; | |
| const GrGLCaps& caps = gpu->glCaps(); | |
| settings.fCaps = caps.shaderCaps(); | |
| settings.fSharpenTextures = gpu->getContext()->priv().options().fSharpenMipmappedTextures; | |
| GrPersistentCacheUtils::ShaderMetadata meta; | |
| meta.fSettings = &settings; | |
| SkSL::String shaders[kGrShaderTypeCount]; | |
| SkSL::Program::Inputs inputs; | |
| GrPersistentCacheUtils::UnpackCachedShaders(&reader, shaders, &inputs, 1, &meta); | |
| auto compileShader = [&](SkSL::Program::Kind kind, const SkSL::String& sksl, GrGLenum type) { | |
| SkSL::String glsl; | |
| auto program = GrSkSLtoGLSL(gpu->glContext(), kind, sksl, settings, &glsl, errorHandler); | |
| if (!program) { | |
| return false; | |
| } | |
| if (GrGLuint shaderID = GrGLCompileAndAttachShader(gpu->glContext(), programID, type, glsl, | |
| gpu->stats(), errorHandler)) { | |
| shadersToDelete.push_back(shaderID); | |
| return true; | |
| } else { | |
| return false; | |
| } | |
| }; | |
| if (!compileShader(SkSL::Program::kFragment_Kind, | |
| shaders[kFragment_GrShaderType], | |
| GR_GL_FRAGMENT_SHADER) || | |
| !compileShader(SkSL::Program::kVertex_Kind, | |
| shaders[kVertex_GrShaderType], | |
| GR_GL_VERTEX_SHADER) || | |
| (!shaders[kGeometry_GrShaderType].empty() && | |
| !compileShader(SkSL::Program::kGeometry_Kind, | |
| shaders[kGeometry_GrShaderType], | |
| GR_GL_GEOMETRY_SHADER))) { | |
| cleanup_program(gpu, programID, shadersToDelete); | |
| return false; | |
| } | |
| for (int i = 0; i < meta.fAttributeNames.count(); ++i) { | |
| GR_GL_CALL(gpu->glInterface(), BindAttribLocation(programID, i, | |
| meta.fAttributeNames[i].c_str())); | |
| } | |
| if (meta.fHasCustomColorOutput && caps.bindFragDataLocationSupport()) { | |
| GR_GL_CALL(gpu->glInterface(), BindFragDataLocation(programID, 0, | |
| GrGLSLFragmentShaderBuilder::DeclaredColorOutputName())); | |
| } | |
| if (meta.fHasSecondaryColorOutput && caps.shaderCaps()->mustDeclareFragmentShaderOutput()) { | |
| GR_GL_CALL(gpu->glInterface(), BindFragDataLocationIndexed(programID, 0, 1, | |
| GrGLSLFragmentShaderBuilder::DeclaredSecondaryColorOutputName())); | |
| } | |
| GR_GL_CALL(gpu->glInterface(), LinkProgram(programID)); | |
| GrGLint linked = GR_GL_INIT_ZERO; | |
| GR_GL_CALL(gpu->glInterface(), GetProgramiv(programID, GR_GL_LINK_STATUS, &linked)); | |
| if (!linked) { | |
| cleanup_program(gpu, programID, shadersToDelete); | |
| return false; | |
| } | |
| cleanup_shaders(gpu, shadersToDelete); | |
| precompiledProgram->fProgramID = programID; | |
| precompiledProgram->fInputs = inputs; | |
| return true; | |
| } |