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