xref: /aosp_15_r20/external/angle/src/libANGLE/renderer/vulkan/ProgramVk.cpp (revision 8975f5c5ed3d1c378011245431ada316dfb6f244)
1 //
2 // Copyright 2016 The ANGLE Project Authors. All rights reserved.
3 // Use of this source code is governed by a BSD-style license that can be
4 // found in the LICENSE file.
5 //
6 // ProgramVk.cpp:
7 //    Implements the class methods for ProgramVk.
8 //
9 
10 #include "libANGLE/renderer/vulkan/ProgramVk.h"
11 
12 #include "common/debug.h"
13 #include "common/utilities.h"
14 #include "libANGLE/Context.h"
15 #include "libANGLE/ProgramLinkedResources.h"
16 #include "libANGLE/renderer/renderer_utils.h"
17 #include "libANGLE/renderer/vulkan/BufferVk.h"
18 #include "libANGLE/renderer/vulkan/TextureVk.h"
19 
20 namespace rx
21 {
22 
23 namespace
24 {
25 // Identical to Std140 encoder in all aspects, except it ignores opaque uniform types.
26 class VulkanDefaultBlockEncoder : public sh::Std140BlockEncoder
27 {
28   public:
advanceOffset(GLenum type,const std::vector<unsigned int> & arraySizes,bool isRowMajorMatrix,int arrayStride,int matrixStride)29     void advanceOffset(GLenum type,
30                        const std::vector<unsigned int> &arraySizes,
31                        bool isRowMajorMatrix,
32                        int arrayStride,
33                        int matrixStride) override
34     {
35         if (gl::IsOpaqueType(type))
36         {
37             return;
38         }
39 
40         sh::Std140BlockEncoder::advanceOffset(type, arraySizes, isRowMajorMatrix, arrayStride,
41                                               matrixStride);
42     }
43 };
44 
45 class Std140BlockLayoutEncoderFactory : public gl::CustomBlockLayoutEncoderFactory
46 {
47   public:
makeEncoder()48     sh::BlockLayoutEncoder *makeEncoder() override { return new sh::Std140BlockEncoder(); }
49 };
50 
51 class LinkTaskVk final : public vk::Context, public LinkTask
52 {
53   public:
LinkTaskVk(vk::Renderer * renderer,PipelineLayoutCache & pipelineLayoutCache,DescriptorSetLayoutCache & descriptorSetLayoutCache,const gl::ProgramState & state,bool isGLES1,vk::PipelineRobustness pipelineRobustness,vk::PipelineProtectedAccess pipelineProtectedAccess)54     LinkTaskVk(vk::Renderer *renderer,
55                PipelineLayoutCache &pipelineLayoutCache,
56                DescriptorSetLayoutCache &descriptorSetLayoutCache,
57                const gl::ProgramState &state,
58                bool isGLES1,
59                vk::PipelineRobustness pipelineRobustness,
60                vk::PipelineProtectedAccess pipelineProtectedAccess)
61         : vk::Context(renderer),
62           mState(state),
63           mExecutable(&mState.getExecutable()),
64           mIsGLES1(isGLES1),
65           mPipelineRobustness(pipelineRobustness),
66           mPipelineProtectedAccess(pipelineProtectedAccess),
67           mPipelineLayoutCache(pipelineLayoutCache),
68           mDescriptorSetLayoutCache(descriptorSetLayoutCache)
69     {}
70     ~LinkTaskVk() override = default;
71 
link(const gl::ProgramLinkedResources & resources,const gl::ProgramMergedVaryings & mergedVaryings,std::vector<std::shared_ptr<LinkSubTask>> * linkSubTasksOut,std::vector<std::shared_ptr<LinkSubTask>> * postLinkSubTasksOut)72     void link(const gl::ProgramLinkedResources &resources,
73               const gl::ProgramMergedVaryings &mergedVaryings,
74               std::vector<std::shared_ptr<LinkSubTask>> *linkSubTasksOut,
75               std::vector<std::shared_ptr<LinkSubTask>> *postLinkSubTasksOut) override
76     {
77         ASSERT(linkSubTasksOut && linkSubTasksOut->empty());
78         ASSERT(postLinkSubTasksOut && postLinkSubTasksOut->empty());
79 
80         // In the Vulkan backend, the only subtasks are pipeline warm up, which is not required for
81         // link.  Running as a post-link task, the expensive warm up is run in a thread without
82         // holding up the link results.
83         angle::Result result = linkImpl(resources, mergedVaryings, postLinkSubTasksOut);
84         ASSERT((result == angle::Result::Continue) == (mErrorCode == VK_SUCCESS));
85     }
86 
handleError(VkResult result,const char * file,const char * function,unsigned int line)87     void handleError(VkResult result,
88                      const char *file,
89                      const char *function,
90                      unsigned int line) override
91     {
92         mErrorCode     = result;
93         mErrorFile     = file;
94         mErrorFunction = function;
95         mErrorLine     = line;
96     }
97 
getResult(const gl::Context * context,gl::InfoLog & infoLog)98     angle::Result getResult(const gl::Context *context, gl::InfoLog &infoLog) override
99     {
100         ContextVk *contextVk              = vk::GetImpl(context);
101         ProgramExecutableVk *executableVk = vk::GetImpl(mExecutable);
102 
103         ANGLE_TRY(executableVk->initializeDescriptorPools(contextVk,
104                                                           &contextVk->getDescriptorSetLayoutCache(),
105                                                           &contextVk->getMetaDescriptorPools()));
106 
107         // If the program uses framebuffer fetch and this is the first time this happens, switch the
108         // context to "framebuffer fetch mode".  In this mode, all render passes assume framebuffer
109         // fetch may be used, so they are prepared to accept a program that uses input attachments.
110         // This is done only when a program with framebuffer fetch is created to avoid potential
111         // performance impact on applications that don't use this extension.  If other contexts in
112         // the share group use this program, they will lazily switch to this mode.
113         //
114         // This is purely an optimization (to avoid creating and later releasing) non-framebuffer
115         // fetch render passes.  The optimization is unnecessary for and does not apply to dynamic
116         // rendering.
117         if (!contextVk->getFeatures().preferDynamicRendering.enabled &&
118             contextVk->getFeatures().permanentlySwitchToFramebufferFetchMode.enabled &&
119             mExecutable->usesColorFramebufferFetch())
120         {
121             ANGLE_TRY(contextVk->switchToColorFramebufferFetchMode(true));
122         }
123 
124         // Forward any errors
125         if (mErrorCode != VK_SUCCESS)
126         {
127             contextVk->handleError(mErrorCode, mErrorFile, mErrorFunction, mErrorLine);
128             return angle::Result::Stop;
129         }
130 
131         // Accumulate relevant perf counters
132         const angle::VulkanPerfCounters &from = getPerfCounters();
133         angle::VulkanPerfCounters &to         = contextVk->getPerfCounters();
134 
135         to.pipelineCreationCacheHits += from.pipelineCreationCacheHits;
136         to.pipelineCreationCacheMisses += from.pipelineCreationCacheMisses;
137         to.pipelineCreationTotalCacheHitsDurationNs +=
138             from.pipelineCreationTotalCacheHitsDurationNs;
139         to.pipelineCreationTotalCacheMissesDurationNs +=
140             from.pipelineCreationTotalCacheMissesDurationNs;
141 
142         return angle::Result::Continue;
143     }
144 
145   private:
146     angle::Result linkImpl(const gl::ProgramLinkedResources &resources,
147                            const gl::ProgramMergedVaryings &mergedVaryings,
148                            std::vector<std::shared_ptr<LinkSubTask>> *postLinkSubTasksOut);
149 
150     void linkResources(const gl::ProgramLinkedResources &resources);
151     angle::Result initDefaultUniformBlocks();
152     void generateUniformLayoutMapping(gl::ShaderMap<sh::BlockLayoutMap> *layoutMapOut,
153                                       gl::ShaderMap<size_t> *requiredBufferSizeOut);
154     void initDefaultUniformLayoutMapping(gl::ShaderMap<sh::BlockLayoutMap> *layoutMapOut);
155 
156     // The front-end ensures that the program is not accessed while linking, so it is safe to
157     // direclty access the state from a potentially parallel job.
158     const gl::ProgramState &mState;
159     const gl::ProgramExecutable *mExecutable;
160     const bool mIsGLES1;
161     const vk::PipelineRobustness mPipelineRobustness;
162     const vk::PipelineProtectedAccess mPipelineProtectedAccess;
163 
164     // Helpers that are interally thread-safe
165     PipelineLayoutCache &mPipelineLayoutCache;
166     DescriptorSetLayoutCache &mDescriptorSetLayoutCache;
167 
168     // Error handling
169     VkResult mErrorCode        = VK_SUCCESS;
170     const char *mErrorFile     = nullptr;
171     const char *mErrorFunction = nullptr;
172     unsigned int mErrorLine    = 0;
173 };
174 
linkImpl(const gl::ProgramLinkedResources & resources,const gl::ProgramMergedVaryings & mergedVaryings,std::vector<std::shared_ptr<LinkSubTask>> * postLinkSubTasksOut)175 angle::Result LinkTaskVk::linkImpl(const gl::ProgramLinkedResources &resources,
176                                    const gl::ProgramMergedVaryings &mergedVaryings,
177                                    std::vector<std::shared_ptr<LinkSubTask>> *postLinkSubTasksOut)
178 {
179     ANGLE_TRACE_EVENT0("gpu.angle", "LinkTaskVk::linkImpl");
180     ProgramExecutableVk *executableVk = vk::GetImpl(mExecutable);
181 
182     // Link resources before calling GetShaderSource to make sure they are ready for the set/binding
183     // assignment done in that function.
184     linkResources(resources);
185 
186     executableVk->clearVariableInfoMap();
187 
188     // Gather variable info and compiled SPIR-V binaries.
189     executableVk->assignAllSpvLocations(this, mState, resources);
190 
191     gl::ShaderMap<const angle::spirv::Blob *> spirvBlobs;
192     SpvGetShaderSpirvCode(mState, &spirvBlobs);
193 
194     if (getFeatures().varyingsRequireMatchingPrecisionInSpirv.enabled &&
195         getFeatures().enablePrecisionQualifiers.enabled)
196     {
197         executableVk->resolvePrecisionMismatch(mergedVaryings);
198     }
199 
200     // Compile the shaders.
201     ANGLE_TRY(executableVk->initShaders(this, mExecutable->getLinkedShaderStages(), spirvBlobs,
202                                         mIsGLES1));
203 
204     ANGLE_TRY(initDefaultUniformBlocks());
205 
206     ANGLE_TRY(executableVk->createPipelineLayout(this, &mPipelineLayoutCache,
207                                                  &mDescriptorSetLayoutCache, nullptr));
208 
209     // Warm up the pipeline cache by creating a few placeholder pipelines.  This is not done for
210     // separable programs, and is deferred to when the program pipeline is finalized.
211     //
212     // The cache warm up is skipped for GLES1 for two reasons:
213     //
214     // - Since GLES1 shaders are limited, the individual programs don't necessarily add new
215     //   pipelines, but rather it's draw time state that controls that.  Since the programs are
216     //   generated at draw time, it's just as well to let the pipelines be created using the
217     //   renderer's shared cache.
218     // - Individual GLES1 tests are long, and this adds a considerable overhead to those tests
219     if (!mState.isSeparable() && !mIsGLES1 && getFeatures().warmUpPipelineCacheAtLink.enabled)
220     {
221         ANGLE_TRY(executableVk->getPipelineCacheWarmUpTasks(
222             mRenderer, mPipelineRobustness, mPipelineProtectedAccess, postLinkSubTasksOut));
223     }
224 
225     return angle::Result::Continue;
226 }
227 
linkResources(const gl::ProgramLinkedResources & resources)228 void LinkTaskVk::linkResources(const gl::ProgramLinkedResources &resources)
229 {
230     Std140BlockLayoutEncoderFactory std140EncoderFactory;
231     gl::ProgramLinkedResourcesLinker linker(&std140EncoderFactory);
232 
233     linker.linkResources(mState, resources);
234 }
235 
initDefaultUniformBlocks()236 angle::Result LinkTaskVk::initDefaultUniformBlocks()
237 {
238     ProgramExecutableVk *executableVk = vk::GetImpl(mExecutable);
239 
240     // Process vertex and fragment uniforms into std140 packing.
241     gl::ShaderMap<sh::BlockLayoutMap> layoutMap;
242     gl::ShaderMap<size_t> requiredBufferSize;
243     requiredBufferSize.fill(0);
244 
245     generateUniformLayoutMapping(&layoutMap, &requiredBufferSize);
246     initDefaultUniformLayoutMapping(&layoutMap);
247 
248     // All uniform initializations are complete, now resize the buffers accordingly and return
249     return executableVk->resizeUniformBlockMemory(this, requiredBufferSize);
250 }
251 
InitDefaultUniformBlock(const std::vector<sh::ShaderVariable> & uniforms,sh::BlockLayoutMap * blockLayoutMapOut,size_t * blockSizeOut)252 void InitDefaultUniformBlock(const std::vector<sh::ShaderVariable> &uniforms,
253                              sh::BlockLayoutMap *blockLayoutMapOut,
254                              size_t *blockSizeOut)
255 {
256     if (uniforms.empty())
257     {
258         *blockSizeOut = 0;
259         return;
260     }
261 
262     VulkanDefaultBlockEncoder blockEncoder;
263     sh::GetActiveUniformBlockInfo(uniforms, "", &blockEncoder, blockLayoutMapOut);
264 
265     *blockSizeOut = blockEncoder.getCurrentOffset();
266     return;
267 }
268 
generateUniformLayoutMapping(gl::ShaderMap<sh::BlockLayoutMap> * layoutMapOut,gl::ShaderMap<size_t> * requiredBufferSizeOut)269 void LinkTaskVk::generateUniformLayoutMapping(gl::ShaderMap<sh::BlockLayoutMap> *layoutMapOut,
270                                               gl::ShaderMap<size_t> *requiredBufferSizeOut)
271 {
272     for (const gl::ShaderType shaderType : mExecutable->getLinkedShaderStages())
273     {
274         const gl::SharedCompiledShaderState &shader = mState.getAttachedShader(shaderType);
275 
276         if (shader)
277         {
278             const std::vector<sh::ShaderVariable> &uniforms = shader->uniforms;
279             InitDefaultUniformBlock(uniforms, &(*layoutMapOut)[shaderType],
280                                     &(*requiredBufferSizeOut)[shaderType]);
281         }
282     }
283 }
284 
initDefaultUniformLayoutMapping(gl::ShaderMap<sh::BlockLayoutMap> * layoutMapOut)285 void LinkTaskVk::initDefaultUniformLayoutMapping(gl::ShaderMap<sh::BlockLayoutMap> *layoutMapOut)
286 {
287     // Init the default block layout info.
288     ProgramExecutableVk *executableVk = vk::GetImpl(mExecutable);
289     const auto &uniforms              = mExecutable->getUniforms();
290 
291     for (const gl::VariableLocation &location : mExecutable->getUniformLocations())
292     {
293         gl::ShaderMap<sh::BlockMemberInfo> layoutInfo;
294 
295         if (location.used() && !location.ignored)
296         {
297             const auto &uniform = uniforms[location.index];
298             if (uniform.isInDefaultBlock() && !uniform.isSampler() && !uniform.isImage() &&
299                 !uniform.isFragmentInOut())
300             {
301                 std::string uniformName = mExecutable->getUniformNameByIndex(location.index);
302                 if (uniform.isArray())
303                 {
304                     // Gets the uniform name without the [0] at the end.
305                     uniformName = gl::StripLastArrayIndex(uniformName);
306                     ASSERT(uniformName.size() !=
307                            mExecutable->getUniformNameByIndex(location.index).size());
308                 }
309 
310                 bool found = false;
311 
312                 for (const gl::ShaderType shaderType : mExecutable->getLinkedShaderStages())
313                 {
314                     auto it = (*layoutMapOut)[shaderType].find(uniformName);
315                     if (it != (*layoutMapOut)[shaderType].end())
316                     {
317                         found                  = true;
318                         layoutInfo[shaderType] = it->second;
319                     }
320                 }
321 
322                 ASSERT(found);
323             }
324         }
325 
326         for (const gl::ShaderType shaderType : mExecutable->getLinkedShaderStages())
327         {
328             executableVk->getSharedDefaultUniformBlock(shaderType)
329                 ->uniformLayout.push_back(layoutInfo[shaderType]);
330         }
331     }
332 }
333 }  // anonymous namespace
334 
335 // ProgramVk implementation.
ProgramVk(const gl::ProgramState & state)336 ProgramVk::ProgramVk(const gl::ProgramState &state) : ProgramImpl(state) {}
337 
338 ProgramVk::~ProgramVk() = default;
339 
destroy(const gl::Context * context)340 void ProgramVk::destroy(const gl::Context *context)
341 {
342     ContextVk *contextVk = vk::GetImpl(context);
343     getExecutable()->reset(contextVk);
344 }
345 
load(const gl::Context * context,gl::BinaryInputStream * stream,std::shared_ptr<LinkTask> * loadTaskOut,egl::CacheGetResult * resultOut)346 angle::Result ProgramVk::load(const gl::Context *context,
347                               gl::BinaryInputStream *stream,
348                               std::shared_ptr<LinkTask> *loadTaskOut,
349                               egl::CacheGetResult *resultOut)
350 {
351     ContextVk *contextVk = vk::GetImpl(context);
352 
353     // TODO: parallelize program load.  http://anglebug.com/41488637
354     *loadTaskOut = {};
355 
356     return getExecutable()->load(contextVk, mState.isSeparable(), stream, resultOut);
357 }
358 
save(const gl::Context * context,gl::BinaryOutputStream * stream)359 void ProgramVk::save(const gl::Context *context, gl::BinaryOutputStream *stream)
360 {
361     ContextVk *contextVk = vk::GetImpl(context);
362     getExecutable()->save(contextVk, mState.isSeparable(), stream);
363 }
364 
setBinaryRetrievableHint(bool retrievable)365 void ProgramVk::setBinaryRetrievableHint(bool retrievable)
366 {
367     // Nothing to do here yet.
368 }
369 
setSeparable(bool separable)370 void ProgramVk::setSeparable(bool separable)
371 {
372     // Nothing to do here yet.
373 }
374 
link(const gl::Context * context,std::shared_ptr<LinkTask> * linkTaskOut)375 angle::Result ProgramVk::link(const gl::Context *context, std::shared_ptr<LinkTask> *linkTaskOut)
376 {
377     ContextVk *contextVk = vk::GetImpl(context);
378 
379     *linkTaskOut = std::shared_ptr<LinkTask>(new LinkTaskVk(
380         contextVk->getRenderer(), contextVk->getPipelineLayoutCache(),
381         contextVk->getDescriptorSetLayoutCache(), mState, context->getState().isGLES1(),
382         contextVk->pipelineRobustness(), contextVk->pipelineProtectedAccess()));
383 
384     return angle::Result::Continue;
385 }
386 
validate(const gl::Caps & caps)387 GLboolean ProgramVk::validate(const gl::Caps &caps)
388 {
389     // No-op. The spec is very vague about the behavior of validation.
390     return GL_TRUE;
391 }
392 
393 }  // namespace rx
394