• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2014 Google Inc.
3  *
4  * Use of this source code is governed by a BSD-style license that can be
5  * found in the LICENSE file.
6  */
7 
8 #include "src/gpu/ganesh/gl/builders/GrGLProgramBuilder.h"
9 
10 #include "include/gpu/GrDirectContext.h"
11 #include "include/private/SkSLProgramKind.h"
12 #include "src/base/SkAutoMalloc.h"
13 #include "src/core/SkReadBuffer.h"
14 #include "src/core/SkTraceEvent.h"
15 #include "src/core/SkWriteBuffer.h"
16 #include "src/gpu/Swizzle.h"
17 #include "src/gpu/ganesh/GrAutoLocaleSetter.h"
18 #include "src/gpu/ganesh/GrDirectContextPriv.h"
19 #include "src/gpu/ganesh/GrFragmentProcessor.h"
20 #include "src/gpu/ganesh/GrGeometryProcessor.h"
21 #include "src/gpu/ganesh/GrPersistentCacheUtils.h"
22 #include "src/gpu/ganesh/GrProgramDesc.h"
23 #include "src/gpu/ganesh/GrShaderCaps.h"
24 #include "src/gpu/ganesh/GrXferProcessor.h"
25 #include "src/gpu/ganesh/gl/GrGLGpu.h"
26 #include "src/gpu/ganesh/gl/GrGLProgram.h"
27 #include "src/gpu/ganesh/gl/builders/GrGLProgramBuilder.h"
28 #include "src/sksl/SkSLProgramSettings.h"
29 #include "src/utils/SkShaderUtils.h"
30 
31 #include <memory>
32 #include "src/gpu/ganesh/gl/builders/GrGLShaderStringBuilder.h"
33 #include "src/gpu/ganesh/glsl/GrGLSLProgramDataManager.h"
34 
35 #define GL_CALL(X) GR_GL_CALL(this->gpu()->glInterface(), X)
36 #define GL_CALL_RET(R, X) GR_GL_CALL_RET(this->gpu()->glInterface(), R, X)
37 
cleanup_shaders(GrGLGpu * gpu,const SkTDArray<GrGLuint> & shaderIDs)38 static void cleanup_shaders(GrGLGpu* gpu, const SkTDArray<GrGLuint>& shaderIDs) {
39     for (int i = 0; i < shaderIDs.size(); ++i) {
40         GR_GL_CALL(gpu->glInterface(), DeleteShader(shaderIDs[i]));
41     }
42 }
43 
cleanup_program(GrGLGpu * gpu,GrGLuint programID,const SkTDArray<GrGLuint> & shaderIDs)44 static void cleanup_program(GrGLGpu* gpu, GrGLuint programID,
45                             const SkTDArray<GrGLuint>& shaderIDs) {
46     GR_GL_CALL(gpu->glInterface(), DeleteProgram(programID));
47     cleanup_shaders(gpu, shaderIDs);
48 }
49 
CreateProgram(GrDirectContext * dContext,const GrProgramDesc & desc,const GrProgramInfo & programInfo,const GrGLPrecompiledProgram * precompiledProgram)50 sk_sp<GrGLProgram> GrGLProgramBuilder::CreateProgram(
51                                                GrDirectContext* dContext,
52                                                const GrProgramDesc& desc,
53                                                const GrProgramInfo& programInfo,
54                                                const GrGLPrecompiledProgram* precompiledProgram) {
55     TRACE_EVENT0_ALWAYS("skia.shaders", "shader_compile");
56     GrAutoLocaleSetter als("C");
57 
58     GrGLGpu* glGpu = static_cast<GrGLGpu*>(dContext->priv().getGpu());
59 
60     // create a builder.  This will be handed off to effects so they can use it to add
61     // uniforms, varyings, textures, etc
62     GrGLProgramBuilder builder(glGpu, desc, programInfo);
63 
64     auto persistentCache = dContext->priv().getPersistentCache();
65     if (persistentCache && !precompiledProgram) {
66         sk_sp<SkData> key = SkData::MakeWithoutCopy(desc.asKey(), desc.keyLength());
67         builder.fCached = persistentCache->load(*key);
68         // the eventual end goal is to completely skip emitAndInstallProcs on a cache hit, but it's
69         // doing necessary setup in addition to generating the SkSL code. Currently we are only able
70         // to skip the SkSL->GLSL step on a cache hit.
71     }
72     if (!builder.emitAndInstallProcs()) {
73         return nullptr;
74     }
75     return builder.finalize(precompiledProgram);
76 }
77 
78 /////////////////////////////////////////////////////////////////////////////
79 
GrGLProgramBuilder(GrGLGpu * gpu,const GrProgramDesc & desc,const GrProgramInfo & programInfo)80 GrGLProgramBuilder::GrGLProgramBuilder(GrGLGpu* gpu,
81                                        const GrProgramDesc& desc,
82                                        const GrProgramInfo& programInfo)
83         : INHERITED(desc, programInfo)
84         , fGpu(gpu)
85         , fVaryingHandler(this)
86         , fUniformHandler(this)
87         , fVertexAttributeCnt(0)
88         , fInstanceAttributeCnt(0)
89         , fVertexStride(0)
90         , fInstanceStride(0) {}
91 
caps() const92 const GrCaps* GrGLProgramBuilder::caps() const {
93     return fGpu->caps();
94 }
95 
shaderCompiler() const96 SkSL::Compiler* GrGLProgramBuilder::shaderCompiler() const {
97     return fGpu->shaderCompiler();
98 }
99 
compileAndAttachShaders(const std::string & glsl,GrGLuint programId,GrGLenum type,SkTDArray<GrGLuint> * shaderIds,GrContextOptions::ShaderErrorHandler * errHandler)100 bool GrGLProgramBuilder::compileAndAttachShaders(const std::string& glsl,
101                                                  GrGLuint programId,
102                                                  GrGLenum type,
103                                                  SkTDArray<GrGLuint>* shaderIds,
104                                                  GrContextOptions::ShaderErrorHandler* errHandler) {
105     GrGLGpu* gpu = this->gpu();
106     GrGLuint shaderId = GrGLCompileAndAttachShader(gpu->glContext(),
107                                                    programId,
108                                                    type,
109                                                    glsl,
110                                                    gpu->pipelineBuilder()->stats(),
111                                                    errHandler);
112     if (!shaderId) {
113         return false;
114     }
115 
116     *shaderIds->append() = shaderId;
117     return true;
118 }
119 
computeCountsAndStrides(GrGLuint programID,const GrGeometryProcessor & geomProc,bool bindAttribLocations)120 void GrGLProgramBuilder::computeCountsAndStrides(GrGLuint programID,
121                                                  const GrGeometryProcessor& geomProc,
122                                                  bool bindAttribLocations) {
123     fVertexAttributeCnt = geomProc.numVertexAttributes();
124     fInstanceAttributeCnt = geomProc.numInstanceAttributes();
125     fAttributes = std::make_unique<GrGLProgram::Attribute[]>(
126             fVertexAttributeCnt + fInstanceAttributeCnt);
127     auto addAttr = [&](int i, const auto& a) {
128         fAttributes[i].fCPUType = a.cpuType();
129         fAttributes[i].fGPUType = a.gpuType();
130         fAttributes[i].fOffset = *a.offset();
131         fAttributes[i].fLocation = i;
132         if (bindAttribLocations) {
133             GL_CALL(BindAttribLocation(programID, i, a.name()));
134         }
135     };
136     fVertexStride = geomProc.vertexStride();
137     int i = 0;
138     for (auto attr : geomProc.vertexAttributes()) {
139         addAttr(i++, attr);
140     }
141     fInstanceStride = geomProc.instanceStride();
142     for (auto attr : geomProc.instanceAttributes()) {
143         addAttr(i++, attr);
144     }
145     SkASSERT(fInstanceStride == geomProc.instanceStride());
146 }
147 
addInputVars(const SkSL::Program::Inputs & inputs)148 void GrGLProgramBuilder::addInputVars(const SkSL::Program::Inputs& inputs) {
149     if (inputs.fUseFlipRTUniform) {
150         this->addRTFlipUniform(SKSL_RTFLIP_NAME);
151     }
152 }
153 
154 static constexpr SkFourByteTag kSKSL_Tag = SkSetFourByteTag('S', 'K', 'S', 'L');
155 static constexpr SkFourByteTag kGLSL_Tag = SkSetFourByteTag('G', 'L', 'S', 'L');
156 static constexpr SkFourByteTag kGLPB_Tag = SkSetFourByteTag('G', 'L', 'P', 'B');
157 
storeShaderInCache(const SkSL::Program::Inputs & inputs,GrGLuint programID,const std::string shaders[],bool isSkSL,SkSL::ProgramSettings * settings)158 void GrGLProgramBuilder::storeShaderInCache(const SkSL::Program::Inputs& inputs, GrGLuint programID,
159                                             const std::string shaders[], bool isSkSL,
160                                             SkSL::ProgramSettings* settings) {
161     if (!this->gpu()->getContext()->priv().getPersistentCache()) {
162         return;
163     }
164     sk_sp<SkData> key = SkData::MakeWithoutCopy(this->desc().asKey(), this->desc().keyLength());
165     SkString description = GrProgramDesc::Describe(fProgramInfo, *fGpu->caps());
166     if (fGpu->glCaps().programBinarySupport()) {
167         // binary cache
168         GrGLsizei length = 0;
169         GL_CALL(GetProgramiv(programID, GL_PROGRAM_BINARY_LENGTH, &length));
170         if (length > 0) {
171             SkBinaryWriteBuffer writer;
172             writer.writeInt(GrPersistentCacheUtils::GetCurrentVersion());
173             writer.writeUInt(kGLPB_Tag);
174 
175             writer.writePad32(&inputs, sizeof(inputs));
176 
177             SkAutoSMalloc<2048> binary(length);
178             GrGLenum binaryFormat;
179             GL_CALL(GetProgramBinary(programID, length, &length, &binaryFormat, binary.get()));
180 
181             writer.writeUInt(binaryFormat);
182             writer.writeInt(length);
183             writer.writePad32(binary.get(), length);
184 
185             auto data = writer.snapshotAsData();
186             this->gpu()->getContext()->priv().getPersistentCache()->store(*key, *data, description);
187         }
188     } else {
189         // source cache, plus metadata to allow for a complete precompile
190         GrPersistentCacheUtils::ShaderMetadata meta;
191         meta.fSettings = settings;
192         meta.fHasSecondaryColorOutput = fFS.hasSecondaryOutput();
193         for (auto attr : this->geometryProcessor().vertexAttributes()) {
194             meta.fAttributeNames.emplace_back(attr.name());
195         }
196         for (auto attr : this->geometryProcessor().instanceAttributes()) {
197             meta.fAttributeNames.emplace_back(attr.name());
198         }
199 
200         auto data = GrPersistentCacheUtils::PackCachedShaders(isSkSL ? kSKSL_Tag : kGLSL_Tag,
201                                                               shaders, &inputs, 1, &meta);
202         this->gpu()->getContext()->priv().getPersistentCache()->store(*key, *data, description);
203     }
204 }
205 
finalize(const GrGLPrecompiledProgram * precompiledProgram)206 sk_sp<GrGLProgram> GrGLProgramBuilder::finalize(const GrGLPrecompiledProgram* precompiledProgram) {
207     TRACE_EVENT0("skia.shaders", TRACE_FUNC);
208 
209     // verify we can get a program id
210     GrGLuint programID;
211     if (precompiledProgram) {
212         programID = precompiledProgram->fProgramID;
213     } else {
214         GL_CALL_RET(programID, CreateProgram());
215     }
216     if (0 == programID) {
217         return nullptr;
218     }
219 
220     if (this->gpu()->glCaps().programBinarySupport() &&
221         this->gpu()->glCaps().programParameterSupport() &&
222         this->gpu()->getContext()->priv().getPersistentCache() &&
223         !precompiledProgram) {
224         GL_CALL(ProgramParameteri(programID, GR_GL_PROGRAM_BINARY_RETRIEVABLE_HINT, GR_GL_TRUE));
225     }
226 
227     this->finalizeShaders();
228 
229     // compile shaders and bind attributes / uniforms
230     auto errorHandler = this->gpu()->getContext()->priv().getShaderErrorHandler();
231     const GrGeometryProcessor& geomProc = this->geometryProcessor();
232     SkSL::ProgramSettings settings;
233     settings.fSharpenTextures = true;
234     settings.fFragColorIsInOut = this->fragColorIsInOut();
235 
236     SkSL::Program::Inputs inputs;
237     SkTDArray<GrGLuint> shadersToDelete;
238 
239     bool cached = fCached.get() != nullptr;
240     bool usedProgramBinaries = false;
241     std::string glsl[kGrShaderTypeCount];
242     const std::string* sksl[kGrShaderTypeCount] = {
243         &fVS.fCompilerString,
244         &fFS.fCompilerString,
245     };
246     std::string cached_sksl[kGrShaderTypeCount];
247     if (precompiledProgram) {
248         // This is very similar to when we get program binaries. We even set that flag, as it's
249         // used to prevent other compile work later, and to force re-querying uniform locations.
250         this->addInputVars(precompiledProgram->fInputs);
251         this->computeCountsAndStrides(programID, geomProc, false);
252         usedProgramBinaries = true;
253     } else if (cached) {
254         TRACE_EVENT0_ALWAYS("skia.shaders", "cache_hit");
255         SkReadBuffer reader(fCached->data(), fCached->size());
256         SkFourByteTag shaderType = GrPersistentCacheUtils::GetType(&reader);
257 
258         switch (shaderType) {
259             case kGLPB_Tag: {
260                 // Program binary cache hit. We may opt not to use this if we don't trust program
261                 // binaries on this driver
262                 if (!fGpu->glCaps().programBinarySupport()) {
263                     cached = false;
264                     break;
265                 }
266                 reader.readPad32(&inputs, sizeof(inputs));
267                 GrGLenum binaryFormat = reader.readUInt();
268                 GrGLsizei length      = reader.readInt();
269                 const void* binary = reader.skip(length);
270                 if (!reader.isValid()) {
271                     break;
272                 }
273                 if (length <= 0 || !fGpu->glCaps().programBinaryFormatIsValid(binaryFormat)) {
274                     cached = false;
275                     break;
276                 }
277                 GL_CALL(ProgramBinary(programID, binaryFormat, const_cast<void*>(binary), length));
278                 // Pass nullptr for the error handler. We don't want to treat this as a compile
279                 // failure (we can still recover by compiling the program from source, below).
280                 // Clients won't be directly notified, but they can infer this from the trace
281                 // events, and from the traffic to the persistent cache.
282                 cached = GrGLCheckLinkStatus(fGpu, programID,
283                                              /*errorHandler=*/nullptr, nullptr, nullptr);
284                 if (cached) {
285                     this->addInputVars(inputs);
286                     this->computeCountsAndStrides(programID, geomProc, false);
287                 }
288                 usedProgramBinaries = cached;
289                 break;
290             }
291 
292             case kGLSL_Tag:
293                 // Source cache hit, we don't need to compile the SkSL->GLSL
294                 GrPersistentCacheUtils::UnpackCachedShaders(&reader, glsl, &inputs, 1);
295                 break;
296 
297             case kSKSL_Tag:
298                 // SkSL cache hit, this should only happen in tools overriding the generated SkSL
299                 if (GrPersistentCacheUtils::UnpackCachedShaders(&reader, cached_sksl, &inputs, 1)) {
300                     for (int i = 0; i < kGrShaderTypeCount; ++i) {
301                         sksl[i] = &cached_sksl[i];
302                     }
303                 }
304                 break;
305 
306             default:
307                 // We got something invalid, so pretend it wasn't there
308                 reader.validate(false);
309                 break;
310         }
311         if (!reader.isValid()) {
312             cached = false;
313         }
314     }
315     if (!usedProgramBinaries) {
316         TRACE_EVENT0_ALWAYS("skia.shaders", "cache_miss");
317         // Either a cache miss, or we got something other than binaries from the cache
318 
319         /*
320            Fragment Shader
321         */
322         if (glsl[kFragment_GrShaderType].empty()) {
323             // Don't have cached GLSL, need to compile SkSL->GLSL
324             if (fFS.fForceHighPrecision) {
325                 settings.fForceHighPrecision = true;
326             }
327             std::unique_ptr<SkSL::Program> fs = GrSkSLtoGLSL(this->gpu(),
328                                                              SkSL::ProgramKind::kFragment,
329                                                              *sksl[kFragment_GrShaderType],
330                                                              settings,
331                                                              &glsl[kFragment_GrShaderType],
332                                                              errorHandler);
333             if (!fs) {
334                 cleanup_program(fGpu, programID, shadersToDelete);
335                 return nullptr;
336             }
337             inputs = fs->fInputs;
338         }
339 
340         this->addInputVars(inputs);
341         if (!this->compileAndAttachShaders(glsl[kFragment_GrShaderType], programID,
342                                            GR_GL_FRAGMENT_SHADER, &shadersToDelete, errorHandler)) {
343             cleanup_program(fGpu, programID, shadersToDelete);
344             return nullptr;
345         }
346 
347         /*
348            Vertex Shader
349         */
350         if (glsl[kVertex_GrShaderType].empty()) {
351             // Don't have cached GLSL, need to compile SkSL->GLSL
352             std::unique_ptr<SkSL::Program> vs = GrSkSLtoGLSL(this->gpu(),
353                                                              SkSL::ProgramKind::kVertex,
354                                                              *sksl[kVertex_GrShaderType],
355                                                              settings,
356                                                              &glsl[kVertex_GrShaderType],
357                                                              errorHandler);
358             if (!vs) {
359                 cleanup_program(fGpu, programID, shadersToDelete);
360                 return nullptr;
361             }
362         }
363         if (!this->compileAndAttachShaders(glsl[kVertex_GrShaderType], programID,
364                                            GR_GL_VERTEX_SHADER, &shadersToDelete, errorHandler)) {
365             cleanup_program(fGpu, programID, shadersToDelete);
366             return nullptr;
367         }
368 
369         // This also binds vertex attribute locations.
370         this->computeCountsAndStrides(programID, geomProc, true);
371 
372         this->bindProgramResourceLocations(programID);
373 
374         {
375             TRACE_EVENT0_ALWAYS("skia.shaders", "driver_link_program");
376             GL_CALL(LinkProgram(programID));
377             if (!GrGLCheckLinkStatus(fGpu, programID, errorHandler, sksl, glsl)) {
378                 cleanup_program(fGpu, programID, shadersToDelete);
379                 return nullptr;
380             }
381         }
382     }
383     this->resolveProgramResourceLocations(programID, usedProgramBinaries);
384 
385     cleanup_shaders(fGpu, shadersToDelete);
386 
387     // We can't cache SkSL or GLSL if we were given a precompiled program, but there's not
388     // much point in doing so.
389     if (!cached && !precompiledProgram) {
390         bool isSkSL = false;
391         if (fGpu->getContext()->priv().options().fShaderCacheStrategy ==
392                 GrContextOptions::ShaderCacheStrategy::kSkSL) {
393             for (int i = 0; i < kGrShaderTypeCount; ++i) {
394                 glsl[i] = SkShaderUtils::PrettyPrint(*sksl[i]);
395             }
396             isSkSL = true;
397         }
398         this->storeShaderInCache(inputs, programID, glsl, isSkSL, &settings);
399     }
400     return this->createProgram(programID);
401 }
402 
bindProgramResourceLocations(GrGLuint programID)403 void GrGLProgramBuilder::bindProgramResourceLocations(GrGLuint programID) {
404     fUniformHandler.bindUniformLocations(programID, fGpu->glCaps());
405 
406     const GrGLCaps& caps = this->gpu()->glCaps();
407     if (caps.bindFragDataLocationSupport()) {
408         SkASSERT(caps.shaderCaps()->mustDeclareFragmentShaderOutput());
409         GL_CALL(BindFragDataLocation(programID, 0,
410                                      GrGLSLFragmentShaderBuilder::DeclaredColorOutputName()));
411         if (fFS.hasSecondaryOutput()) {
412             GL_CALL(BindFragDataLocationIndexed(programID, 0, 1,
413                                   GrGLSLFragmentShaderBuilder::DeclaredSecondaryColorOutputName()));
414         }
415     }
416 }
417 
resolveProgramResourceLocations(GrGLuint programID,bool force)418 void GrGLProgramBuilder::resolveProgramResourceLocations(GrGLuint programID, bool force) {
419     fUniformHandler.getUniformLocations(programID, fGpu->glCaps(), force);
420 }
421 
createProgram(GrGLuint programID)422 sk_sp<GrGLProgram> GrGLProgramBuilder::createProgram(GrGLuint programID) {
423     return GrGLProgram::Make(fGpu,
424                              fUniformHandles,
425                              programID,
426                              fUniformHandler.fUniforms,
427                              fUniformHandler.fSamplers,
428                              std::move(fGPImpl),
429                              std::move(fXPImpl),
430                              std::move(fFPImpls),
431                              std::move(fAttributes),
432                              fVertexAttributeCnt,
433                              fInstanceAttributeCnt,
434                              fVertexStride,
435                              fInstanceStride);
436 }
437 
PrecompileProgram(GrDirectContext * dContext,GrGLPrecompiledProgram * precompiledProgram,const SkData & cachedData)438 bool GrGLProgramBuilder::PrecompileProgram(GrDirectContext* dContext,
439                                            GrGLPrecompiledProgram* precompiledProgram,
440                                            const SkData& cachedData) {
441     SkReadBuffer reader(cachedData.data(), cachedData.size());
442     SkFourByteTag shaderType = GrPersistentCacheUtils::GetType(&reader);
443     if (shaderType != kSKSL_Tag) {
444         // TODO: Support GLSL, and maybe even program binaries, too?
445         return false;
446     }
447 
448     GrGLGpu* glGpu = static_cast<GrGLGpu*>(dContext->priv().getGpu());
449 
450     const GrGLInterface* gl = glGpu->glInterface();
451     auto errorHandler = dContext->priv().getShaderErrorHandler();
452 
453     SkSL::ProgramSettings settings;
454     settings.fSharpenTextures = true;
455     GrPersistentCacheUtils::ShaderMetadata meta;
456     meta.fSettings = &settings;
457 
458     std::string shaders[kGrShaderTypeCount];
459     SkSL::Program::Inputs inputs;
460     if (!GrPersistentCacheUtils::UnpackCachedShaders(&reader, shaders, &inputs, 1, &meta)) {
461         return false;
462     }
463 
464     GrGLuint programID;
465     GR_GL_CALL_RET(gl, programID, CreateProgram());
466     if (0 == programID) {
467         return false;
468     }
469 
470     SkTDArray<GrGLuint> shadersToDelete;
471 
472     auto compileShader = [&](SkSL::ProgramKind kind, const std::string& sksl, GrGLenum type) {
473         std::string glsl;
474         auto program = GrSkSLtoGLSL(glGpu, kind, sksl, settings, &glsl, errorHandler);
475         if (!program) {
476             return false;
477         }
478 
479         if (GrGLuint shaderID = GrGLCompileAndAttachShader(glGpu->glContext(), programID, type,
480                                                            glsl, glGpu->pipelineBuilder()->stats(),
481                                                            errorHandler)) {
482             shadersToDelete.push_back(shaderID);
483             return true;
484         } else {
485             return false;
486         }
487     };
488 
489     if (!compileShader(SkSL::ProgramKind::kFragment,
490                        shaders[kFragment_GrShaderType],
491                        GR_GL_FRAGMENT_SHADER) ||
492         !compileShader(SkSL::ProgramKind::kVertex,
493                        shaders[kVertex_GrShaderType],
494                        GR_GL_VERTEX_SHADER)) {
495         cleanup_program(glGpu, programID, shadersToDelete);
496         return false;
497     }
498 
499     for (int i = 0; i < meta.fAttributeNames.size(); ++i) {
500         GR_GL_CALL(glGpu->glInterface(), BindAttribLocation(programID, i,
501                                                           meta.fAttributeNames[i].c_str()));
502     }
503 
504     const GrGLCaps& caps = glGpu->glCaps();
505     if (caps.bindFragDataLocationSupport()) {
506         SkASSERT(caps.shaderCaps()->mustDeclareFragmentShaderOutput());
507         GR_GL_CALL(glGpu->glInterface(),
508                    BindFragDataLocation(programID, 0,
509                                         GrGLSLFragmentShaderBuilder::DeclaredColorOutputName()));
510 
511         if (meta.fHasSecondaryColorOutput) {
512             GR_GL_CALL(glGpu->glInterface(),
513                        BindFragDataLocationIndexed(programID, 0, 1,
514                                   GrGLSLFragmentShaderBuilder::DeclaredSecondaryColorOutputName()));
515         }
516     }
517 
518     GR_GL_CALL(glGpu->glInterface(), LinkProgram(programID));
519     GrGLint linked = GR_GL_INIT_ZERO;
520     GR_GL_CALL(glGpu->glInterface(), GetProgramiv(programID, GR_GL_LINK_STATUS, &linked));
521     if (!linked) {
522         cleanup_program(glGpu, programID, shadersToDelete);
523         return false;
524     }
525 
526     cleanup_shaders(glGpu, shadersToDelete);
527 
528     precompiledProgram->fProgramID = programID;
529     precompiledProgram->fInputs = inputs;
530     return true;
531 }
532