1 /*
2 * Copyright 2016 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/vk/GrVkUniformHandler.h"
9
10 #include "include/core/SkString.h"
11 #include "include/core/SkTypes.h"
12 #include "include/gpu/ganesh/GrBackendSurface.h"
13 #include "include/gpu/ganesh/vk/GrVkBackendSurface.h"
14 #include "include/gpu/vk/VulkanTypes.h"
15 #include "src/core/SkSLTypeShared.h"
16 #include "src/gpu/ganesh/GrCaps.h"
17 #include "src/gpu/ganesh/GrUtil.h"
18 #include "src/gpu/ganesh/glsl/GrGLSLProgramBuilder.h"
19 #include "src/gpu/ganesh/vk/GrVkGpu.h"
20 #include "src/gpu/ganesh/vk/GrVkPipelineStateBuilder.h"
21 #include "src/gpu/ganesh/vk/GrVkResourceProvider.h"
22 #include "src/gpu/ganesh/vk/GrVkSampler.h"
23
24 #include <string.h>
25 #include <algorithm>
26 #include <utility>
27
28 class GrProcessor;
29 struct GrShaderCaps;
30
31 static constexpr int kDstInputAttachmentIndex = 0;
32
33 // To determine whether a current offset is aligned, we can just 'and' the lowest bits with the
34 // alignment mask. A value of 0 means aligned, any other value is how many bytes past alignment we
35 // are. This works since all alignments are powers of 2. The mask is always (alignment - 1).
36 // This alignment mask will give correct alignments for using the std430 block layout. If you want
37 // the std140 alignment, you can use this, but then make sure if you have an array type it is
38 // aligned to 16 bytes (i.e. has mask of 0xF).
39 // These are designated in the Vulkan spec, section 14.5.4 "Offset and Stride Assignment".
40 // https://www.khronos.org/registry/vulkan/specs/1.0-wsi_extensions/html/vkspec.html#interfaces-resources-layout
sksltype_to_alignment_mask(SkSLType type)41 static uint32_t sksltype_to_alignment_mask(SkSLType type) {
42 switch(type) {
43 case SkSLType::kShort: // fall through
44 case SkSLType::kUShort:
45 return 0x1;
46 case SkSLType::kShort2: // fall through
47 case SkSLType::kUShort2:
48 return 0x3;
49 case SkSLType::kShort3: // fall through
50 case SkSLType::kShort4:
51 case SkSLType::kUShort3:
52 case SkSLType::kUShort4:
53 return 0x7;
54 case SkSLType::kInt:
55 case SkSLType::kUInt:
56 return 0x3;
57 case SkSLType::kInt2:
58 case SkSLType::kUInt2:
59 return 0x7;
60 case SkSLType::kInt3:
61 case SkSLType::kUInt3:
62 case SkSLType::kInt4:
63 case SkSLType::kUInt4:
64 return 0xF;
65 case SkSLType::kHalf: // fall through
66 case SkSLType::kFloat:
67 return 0x3;
68 case SkSLType::kHalf2: // fall through
69 case SkSLType::kFloat2:
70 return 0x7;
71 case SkSLType::kHalf3: // fall through
72 case SkSLType::kFloat3:
73 return 0xF;
74 case SkSLType::kHalf4: // fall through
75 case SkSLType::kFloat4:
76 return 0xF;
77 case SkSLType::kHalf2x2: // fall through
78 case SkSLType::kFloat2x2:
79 return 0x7;
80 case SkSLType::kHalf3x3: // fall through
81 case SkSLType::kFloat3x3:
82 return 0xF;
83 case SkSLType::kHalf4x4: // fall through
84 case SkSLType::kFloat4x4:
85 return 0xF;
86
87 // This query is only valid for certain types.
88 case SkSLType::kVoid:
89 case SkSLType::kBool:
90 case SkSLType::kBool2:
91 case SkSLType::kBool3:
92 case SkSLType::kBool4:
93 case SkSLType::kTexture2DSampler:
94 case SkSLType::kTextureExternalSampler:
95 case SkSLType::kTexture2DRectSampler:
96 case SkSLType::kSampler:
97 case SkSLType::kTexture2D:
98 case SkSLType::kInput:
99 break;
100 }
101 SK_ABORT("Unexpected type");
102 }
103
104 /** Returns the size in bytes taken up in vulkanbuffers for SkSLTypes. */
sksltype_to_vk_size(SkSLType type,int layout)105 static inline uint32_t sksltype_to_vk_size(SkSLType type, int layout) {
106 switch(type) {
107 case SkSLType::kShort:
108 return sizeof(int16_t);
109 case SkSLType::kShort2:
110 return 2 * sizeof(int16_t);
111 case SkSLType::kShort3:
112 return 3 * sizeof(int16_t);
113 case SkSLType::kShort4:
114 return 4 * sizeof(int16_t);
115 case SkSLType::kUShort:
116 return sizeof(uint16_t);
117 case SkSLType::kUShort2:
118 return 2 * sizeof(uint16_t);
119 case SkSLType::kUShort3:
120 return 3 * sizeof(uint16_t);
121 case SkSLType::kUShort4:
122 return 4 * sizeof(uint16_t);
123 case SkSLType::kHalf: // fall through
124 case SkSLType::kFloat:
125 return sizeof(float);
126 case SkSLType::kHalf2: // fall through
127 case SkSLType::kFloat2:
128 return 2 * sizeof(float);
129 case SkSLType::kHalf3: // fall through
130 case SkSLType::kFloat3:
131 return 3 * sizeof(float);
132 case SkSLType::kHalf4: // fall through
133 case SkSLType::kFloat4:
134 return 4 * sizeof(float);
135 case SkSLType::kInt: // fall through
136 case SkSLType::kUInt:
137 return sizeof(int32_t);
138 case SkSLType::kInt2: // fall through
139 case SkSLType::kUInt2:
140 return 2 * sizeof(int32_t);
141 case SkSLType::kInt3: // fall through
142 case SkSLType::kUInt3:
143 return 3 * sizeof(int32_t);
144 case SkSLType::kInt4: // fall through
145 case SkSLType::kUInt4:
146 return 4 * sizeof(int32_t);
147 case SkSLType::kHalf2x2: // fall through
148 case SkSLType::kFloat2x2:
149 if (layout == GrVkUniformHandler::kStd430Layout) {
150 return 4 * sizeof(float);
151 } else {
152 return 8 * sizeof(float);
153 }
154 case SkSLType::kHalf3x3: // fall through
155 case SkSLType::kFloat3x3:
156 return 12 * sizeof(float);
157 case SkSLType::kHalf4x4: // fall through
158 case SkSLType::kFloat4x4:
159 return 16 * sizeof(float);
160
161 // This query is only valid for certain types.
162 case SkSLType::kVoid:
163 case SkSLType::kBool:
164 case SkSLType::kBool2:
165 case SkSLType::kBool3:
166 case SkSLType::kBool4:
167 case SkSLType::kTexture2DSampler:
168 case SkSLType::kTextureExternalSampler:
169 case SkSLType::kTexture2DRectSampler:
170 case SkSLType::kSampler:
171 case SkSLType::kTexture2D:
172 case SkSLType::kInput:
173 break;
174 }
175 SK_ABORT("Unexpected type");
176 }
177
178 // Given the current offset into the ubo data, calculate the offset for the uniform we're trying to
179 // add taking into consideration all alignment requirements. The uniformOffset is set to the offset
180 // for the new uniform, and currentOffset is updated to be the offset to the end of the new uniform.
get_aligned_offset(uint32_t * currentOffset,SkSLType type,int arrayCount,int layout)181 static uint32_t get_aligned_offset(uint32_t* currentOffset,
182 SkSLType type,
183 int arrayCount,
184 int layout) {
185 uint32_t alignmentMask = sksltype_to_alignment_mask(type);
186 // For std140 layout we must make arrays align to 16 bytes.
187 // TODO(skia:13380): make sure 2x3 and 3x2 matrices are handled properly once SkSLType adds
188 // support for non-square matrices
189 if (layout == GrVkUniformHandler::kStd140Layout &&
190 (arrayCount || type == SkSLType::kFloat2x2 || type == SkSLType::kHalf2x2)) {
191 alignmentMask = 0xF;
192 }
193 uint32_t offsetDiff = *currentOffset & alignmentMask;
194 if (offsetDiff != 0) {
195 offsetDiff = alignmentMask - offsetDiff + 1;
196 }
197 int32_t uniformOffset = *currentOffset + offsetDiff;
198 SkASSERT(sizeof(float) == 4);
199 if (arrayCount) {
200 // TODO: this shouldn't be necessary for std430
201 uint32_t elementSize = std::max<uint32_t>(16, sksltype_to_vk_size(type, layout));
202 SkASSERT(0 == (elementSize & 0xF));
203 *currentOffset = uniformOffset + elementSize * arrayCount;
204 } else {
205 *currentOffset = uniformOffset + sksltype_to_vk_size(type, layout);
206 }
207 return uniformOffset;
208 }
209
~GrVkUniformHandler()210 GrVkUniformHandler::~GrVkUniformHandler() {
211 for (VkUniformInfo& sampler : fSamplers.items()) {
212 if (sampler.fImmutableSampler) {
213 sampler.fImmutableSampler->unref();
214 sampler.fImmutableSampler = nullptr;
215 }
216 }
217 }
218
internalAddUniformArray(const GrProcessor * owner,uint32_t visibility,SkSLType type,const char * name,bool mangleName,int arrayCount,const char ** outName)219 GrGLSLUniformHandler::UniformHandle GrVkUniformHandler::internalAddUniformArray(
220 const GrProcessor* owner,
221 uint32_t visibility,
222 SkSLType type,
223 const char* name,
224 bool mangleName,
225 int arrayCount,
226 const char** outName) {
227 SkASSERT(name && strlen(name));
228 SkASSERT(SkSLTypeCanBeUniformValue(type));
229
230 // TODO this is a bit hacky, lets think of a better way. Basically we need to be able to use
231 // the uniform view matrix name in the GP, and the GP is immutable so it has to tell the PB
232 // exactly what name it wants to use for the uniform view matrix. If we prefix anythings, then
233 // the names will mismatch. I think the correct solution is to have all GPs which need the
234 // uniform view matrix, they should upload the view matrix in their setData along with regular
235 // uniforms.
236 char prefix = 'u';
237 if ('u' == name[0] || !strncmp(name, GR_NO_MANGLE_PREFIX, strlen(GR_NO_MANGLE_PREFIX))) {
238 prefix = '\0';
239 }
240 SkString resolvedName = fProgramBuilder->nameVariable(prefix, name, mangleName);
241
242 VkUniformInfo tempInfo;
243 tempInfo.fVariable = GrShaderVar{std::move(resolvedName),
244 type,
245 GrShaderVar::TypeModifier::None,
246 arrayCount};
247
248 tempInfo.fVisibility = visibility;
249 tempInfo.fOwner = owner;
250 tempInfo.fRawName = SkString(name);
251
252 for (int layout = 0; layout < kLayoutCount; ++layout) {
253 tempInfo.fOffsets[layout] = get_aligned_offset(&fCurrentOffsets[layout],
254 type,
255 arrayCount,
256 layout);
257 }
258
259 fUniforms.push_back(tempInfo);
260
261 if (outName) {
262 *outName = fUniforms.back().fVariable.c_str();
263 }
264
265 return GrGLSLUniformHandler::UniformHandle(fUniforms.count() - 1);
266 }
267
addSampler(const GrBackendFormat & backendFormat,GrSamplerState state,const skgpu::Swizzle & swizzle,const char * name,const GrShaderCaps * shaderCaps)268 GrGLSLUniformHandler::SamplerHandle GrVkUniformHandler::addSampler(
269 const GrBackendFormat& backendFormat,
270 GrSamplerState state,
271 const skgpu::Swizzle& swizzle,
272 const char* name,
273 const GrShaderCaps* shaderCaps) {
274 SkASSERT(name && strlen(name));
275
276 const char prefix = 'u';
277 SkString mangleName = fProgramBuilder->nameVariable(prefix, name, /*mangle=*/true);
278
279 SkString layoutQualifier;
280 layoutQualifier.appendf("vulkan, set=%d, binding=%d", kSamplerDescSet, fSamplers.count());
281
282 VkUniformInfo tempInfo;
283 tempInfo.fVariable =
284 GrShaderVar{std::move(mangleName),
285 SkSLCombinedSamplerTypeForTextureType(backendFormat.textureType()),
286 GrShaderVar::TypeModifier::None,
287 GrShaderVar::kNonArray,
288 std::move(layoutQualifier),
289 SkString()};
290
291 tempInfo.fVisibility = kFragment_GrShaderFlag;
292 tempInfo.fOwner = nullptr;
293 tempInfo.fRawName = SkString(name);
294 tempInfo.fOffsets[0] = 0;
295 tempInfo.fOffsets[1] = 0;
296
297 fSamplers.push_back(tempInfo);
298
299 // Check if we are dealing with an external texture and store the needed information if so.
300 auto ycbcrInfo = GrBackendFormats::GetVkYcbcrConversionInfo(backendFormat);
301 if (ycbcrInfo && ycbcrInfo->isValid()) {
302 GrVkGpu* gpu = static_cast<GrVkPipelineStateBuilder*>(fProgramBuilder)->gpu();
303 GrVkSampler* sampler = gpu->resourceProvider().findOrCreateCompatibleSampler(state,
304 *ycbcrInfo);
305 fSamplers.back().fImmutableSampler = sampler;
306 if (!sampler) {
307 return {};
308 }
309 }
310
311 fSamplerSwizzles.push_back(swizzle);
312 SkASSERT(fSamplerSwizzles.size() == fSamplers.count());
313 return GrGLSLUniformHandler::SamplerHandle(fSamplers.count() - 1);
314 }
315
addInputSampler(const skgpu::Swizzle & swizzle,const char * name)316 GrGLSLUniformHandler::SamplerHandle GrVkUniformHandler::addInputSampler(
317 const skgpu::Swizzle& swizzle, const char* name) {
318 SkASSERT(name && strlen(name));
319 SkASSERT(fInputUniform.fVariable.getType() == SkSLType::kVoid);
320
321 const char prefix = 'u';
322 SkString mangleName = fProgramBuilder->nameVariable(prefix, name, /*mangle=*/true);
323
324 auto layoutQualifier = SkStringPrintf("vulkan, input_attachment_index=%d, set=%d, binding=%d",
325 kDstInputAttachmentIndex,
326 kInputDescSet,
327 kInputBinding);
328
329 fInputUniform = {GrShaderVar{std::move(mangleName),
330 SkSLType::kInput,
331 GrShaderVar::TypeModifier::None,
332 GrShaderVar::kNonArray,
333 std::move(layoutQualifier),
334 SkString()},
335 kFragment_GrShaderFlag,
336 nullptr,
337 SkString(name)};
338 fInputSwizzle = swizzle;
339 return GrGLSLUniformHandler::SamplerHandle(0);
340 }
341
appendUniformDecls(GrShaderFlags visibility,SkString * out) const342 void GrVkUniformHandler::appendUniformDecls(GrShaderFlags visibility, SkString* out) const {
343 for (const VkUniformInfo& sampler : fSamplers.items()) {
344 SkASSERT(sampler.fVariable.getType() == SkSLType::kTexture2DSampler ||
345 sampler.fVariable.getType() == SkSLType::kTextureExternalSampler);
346 if (visibility == sampler.fVisibility) {
347 sampler.fVariable.appendDecl(fProgramBuilder->shaderCaps(), out);
348 out->append(";\n");
349 }
350 }
351 if (fInputUniform.fVariable.getType() == SkSLType::kInput) {
352 if (visibility == fInputUniform.fVisibility) {
353 SkASSERT(visibility == kFragment_GrShaderFlag);
354 fInputUniform.fVariable.appendDecl(fProgramBuilder->shaderCaps(), out);
355 out->append(";\n");
356 }
357 }
358
359 #ifdef SK_DEBUG
360 bool firstOffsetCheck = false;
361 for (const VkUniformInfo& localUniform : fUniforms.items()) {
362 if (!firstOffsetCheck) {
363 // Check to make sure we are starting our offset at 0 so the offset qualifier we
364 // set on each variable in the uniform block is valid.
365 SkASSERT(0 == localUniform.fOffsets[kStd140Layout] &&
366 0 == localUniform.fOffsets[kStd430Layout]);
367 firstOffsetCheck = true;
368 }
369 }
370 #endif
371
372 // At this point we determine whether we'll be using push constants based on the
373 // uniforms set so far. Later checks will use the internal bool we set here to
374 // keep things consistent.
375 this->determineIfUsePushConstants();
376 SkString uniformsString;
377 for (const VkUniformInfo& localUniform : fUniforms.items()) {
378 if (visibility & localUniform.fVisibility) {
379 if (SkSLTypeCanBeUniformValue(localUniform.fVariable.getType())) {
380 Layout layout = fUsePushConstants ? kStd430Layout : kStd140Layout;
381 uniformsString.appendf("layout(offset=%u) ", localUniform.fOffsets[layout]);
382 localUniform.fVariable.appendDecl(fProgramBuilder->shaderCaps(), &uniformsString);
383 uniformsString.append(";\n");
384 }
385 }
386 }
387
388 if (!uniformsString.isEmpty()) {
389 if (fUsePushConstants) {
390 out->append("layout (vulkan, push_constant) ");
391 } else {
392 out->appendf("layout (vulkan, set=%d, binding=%d) ",
393 kUniformBufferDescSet, kUniformBinding);
394 }
395 out->append("uniform uniformBuffer\n{\n");
396 out->appendf("%s\n};\n", uniformsString.c_str());
397 }
398 }
399
getRTFlipOffset() const400 uint32_t GrVkUniformHandler::getRTFlipOffset() const {
401 Layout layout = fUsePushConstants ? kStd430Layout : kStd140Layout;
402 uint32_t currentOffset = fCurrentOffsets[layout];
403 return get_aligned_offset(¤tOffset, SkSLType::kFloat2, 0, layout);
404 }
405
determineIfUsePushConstants() const406 void GrVkUniformHandler::determineIfUsePushConstants() const {
407 // We may insert a uniform for flipping origin-sensitive language features (e.g. sk_FragCoord).
408 // We won't know that for sure until then but we need to make this determination now,
409 // so assume we will need it.
410 static constexpr uint32_t kPad = 2*sizeof(float);
411 fUsePushConstants =
412 fCurrentOffsets[kStd430Layout] > 0 &&
413 fCurrentOffsets[kStd430Layout] + kPad <= fProgramBuilder->caps()->maxPushConstantsSize();
414 }
415