1 /*
2 * Copyright 2023 Google LLC
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/graphite/compute/DispatchGroup.h"
9
10 #include "include/gpu/graphite/Recorder.h"
11 #include "src/gpu/graphite/BufferManager.h"
12 #include "src/gpu/graphite/Caps.h"
13 #include "src/gpu/graphite/CommandBuffer.h"
14 #include "src/gpu/graphite/ComputePipeline.h"
15 #include "src/gpu/graphite/Log.h"
16 #include "src/gpu/graphite/PipelineData.h"
17 #include "src/gpu/graphite/RecorderPriv.h"
18 #include "src/gpu/graphite/ResourceProvider.h"
19 #include "src/gpu/graphite/Texture.h"
20 #include "src/gpu/graphite/UniformManager.h"
21 #include "src/gpu/graphite/task/ClearBuffersTask.h"
22
23 namespace skgpu::graphite {
24
25 DispatchGroup::~DispatchGroup() = default;
26
prepareResources(ResourceProvider * resourceProvider)27 bool DispatchGroup::prepareResources(ResourceProvider* resourceProvider) {
28 fPipelines.reserve(fPipelines.size() + fPipelineDescs.size());
29 for (const ComputePipelineDesc& desc : fPipelineDescs) {
30 auto pipeline = resourceProvider->findOrCreateComputePipeline(desc);
31 if (!pipeline) {
32 SKGPU_LOG_W("Failed to create ComputePipeline for dispatch group. Dropping group!");
33 return false;
34 }
35 fPipelines.push_back(std::move(pipeline));
36 }
37
38 for (int i = 0; i < fTextures.size(); ++i) {
39 if (!fTextures[i]->textureInfo().isValid()) {
40 SKGPU_LOG_W("Failed to validate bound texture. Dropping dispatch group!");
41 return false;
42 }
43 if (!TextureProxy::InstantiateIfNotLazy(resourceProvider, fTextures[i].get())) {
44 SKGPU_LOG_W("Failed to instantiate bound texture. Dropping dispatch group!");
45 return false;
46 }
47 }
48
49 for (const SamplerDesc& desc : fSamplerDescs) {
50 sk_sp<Sampler> sampler = resourceProvider->findOrCreateCompatibleSampler(desc);
51 if (!sampler) {
52 SKGPU_LOG_W("Failed to create sampler. Dropping dispatch group!");
53 return false;
54 }
55 fSamplers.push_back(std::move(sampler));
56 }
57
58 // The DispatchGroup may be long lived on a Recording and we no longer need the descriptors
59 // once we've created pipelines.
60 fPipelineDescs.clear();
61 fSamplerDescs.clear();
62
63 return true;
64 }
65
addResourceRefs(CommandBuffer * commandBuffer) const66 void DispatchGroup::addResourceRefs(CommandBuffer* commandBuffer) const {
67 for (int i = 0; i < fPipelines.size(); ++i) {
68 commandBuffer->trackResource(fPipelines[i]);
69 }
70 for (int i = 0; i < fTextures.size(); ++i) {
71 commandBuffer->trackCommandBufferResource(fTextures[i]->refTexture());
72 }
73 }
74
snapChildTask()75 sk_sp<Task> DispatchGroup::snapChildTask() {
76 if (fClearList.empty()) {
77 return nullptr;
78 }
79 return ClearBuffersTask::Make(std::move(fClearList));
80 }
81
getTexture(size_t index) const82 const Texture* DispatchGroup::getTexture(size_t index) const {
83 SkASSERT(index < SkToSizeT(fTextures.size()));
84 SkASSERT(fTextures[index]);
85 SkASSERT(fTextures[index]->texture());
86 return fTextures[index]->texture();
87 }
88
getSampler(size_t index) const89 const Sampler* DispatchGroup::getSampler(size_t index) const {
90 SkASSERT(index < SkToSizeT(fSamplers.size()));
91 SkASSERT(fSamplers[index]);
92 return fSamplers[index].get();
93 }
94
95 using Builder = DispatchGroup::Builder;
96
Builder(Recorder * recorder)97 Builder::Builder(Recorder* recorder) : fObj(new DispatchGroup()), fRecorder(recorder) {
98 SkASSERT(fRecorder);
99 }
100
appendStep(const ComputeStep * step,std::optional<WorkgroupSize> globalSize)101 bool Builder::appendStep(const ComputeStep* step, std::optional<WorkgroupSize> globalSize) {
102 return this->appendStepInternal(step,
103 globalSize ? *globalSize : step->calculateGlobalDispatchSize());
104 }
105
appendStepIndirect(const ComputeStep * step,BufferView indirectBuffer)106 bool Builder::appendStepIndirect(const ComputeStep* step, BufferView indirectBuffer) {
107 return this->appendStepInternal(step, indirectBuffer);
108 }
109
appendStepInternal(const ComputeStep * step,const std::variant<WorkgroupSize,BufferView> & globalSizeOrIndirect)110 bool Builder::appendStepInternal(
111 const ComputeStep* step,
112 const std::variant<WorkgroupSize, BufferView>& globalSizeOrIndirect) {
113 SkASSERT(fObj);
114 SkASSERT(step);
115
116 Dispatch dispatch;
117
118 // Process the step's resources.
119 auto resources = step->resources();
120 dispatch.fBindings.reserve(resources.size());
121
122 // `nextIndex` matches the declaration order of resources as specified by the ComputeStep.
123 int nextIndex = 0;
124
125 // We assign buffer, texture, and sampler indices from separate ranges. This is compatible with
126 // how Graphite assigns indices on Metal, as these map directly to the buffer/texture/sampler
127 // index ranges. On Dawn/Vulkan buffers and textures/samplers are allocated from separate bind
128 // groups/descriptor sets but texture and sampler indices need to not overlap.
129 const auto& bindingReqs = fRecorder->priv().caps()->resourceBindingRequirements();
130 bool distinctRanges = bindingReqs.fDistinctIndexRanges;
131 bool separateSampler = bindingReqs.fSeparateTextureAndSamplerBinding;
132 int bufferOrGlobalIndex = 0;
133 int texIndex = 0;
134 // NOTE: SkSL Metal codegen always assigns the same binding index to a texture and its sampler.
135 // TODO: This could cause sampler indices to not be tightly packed if the sampler2D declaration
136 // comes after 1 or more storage texture declarations (which don't have samplers).
137 for (const ComputeStep::ResourceDesc& r : resources) {
138 SkASSERT(r.fSlot == -1 || (r.fSlot >= 0 && r.fSlot < kMaxComputeDataFlowSlots));
139 const int index = nextIndex++;
140
141 DispatchResourceOptional maybeResource;
142
143 using DataFlow = ComputeStep::DataFlow;
144 using Type = ComputeStep::ResourceType;
145 switch (r.fFlow) {
146 case DataFlow::kPrivate:
147 // A sampled or fetched-type readonly texture must either get assigned via
148 // `assignSharedTexture()` or internally allocated as a storage texture of a
149 // preceding step. Such a texture always has a data slot.
150 SkASSERT(r.fType != Type::kReadOnlyTexture);
151 SkASSERT(r.fType != Type::kSampledTexture);
152 maybeResource = this->allocateResource(step, r, index);
153 break;
154 case DataFlow::kShared: {
155 SkASSERT(r.fSlot >= 0);
156 // Allocate a new resource only if the shared slot is empty (except for a
157 // SampledTexture which needs its sampler to be allocated internally).
158 DispatchResourceOptional* slot = &fOutputTable.fSharedSlots[r.fSlot];
159 if (std::holds_alternative<std::monostate>(*slot)) {
160 SkASSERT(r.fType != Type::kReadOnlyTexture);
161 SkASSERT(r.fType != Type::kSampledTexture);
162 maybeResource = this->allocateResource(step, r, index);
163 *slot = maybeResource;
164 } else {
165 SkASSERT(((r.fType == Type::kUniformBuffer ||
166 r.fType == Type::kStorageBuffer ||
167 r.fType == Type::kReadOnlyStorageBuffer ||
168 r.fType == Type::kIndirectBuffer) &&
169 std::holds_alternative<BufferView>(*slot)) ||
170 ((r.fType == Type::kReadOnlyTexture ||
171 r.fType == Type::kSampledTexture ||
172 r.fType == Type::kWriteOnlyStorageTexture) &&
173 std::holds_alternative<TextureIndex>(*slot)));
174 #ifdef SK_DEBUG
175 // Ensure that the texture has the right format if it was assigned via
176 // `assignSharedTexture()`.
177 const TextureIndex* texIdx = std::get_if<TextureIndex>(slot);
178 if (texIdx && r.fType == Type::kWriteOnlyStorageTexture) {
179 const TextureProxy* t = fObj->fTextures[texIdx->fValue].get();
180 SkASSERT(t);
181 auto [_, colorType] = step->calculateTextureParameters(index, r);
182 SkASSERT(t->textureInfo().isCompatible(
183 fRecorder->priv().caps()->getDefaultStorageTextureInfo(colorType)));
184 }
185 #endif // SK_DEBUG
186
187 maybeResource = *slot;
188
189 if (r.fType == Type::kSampledTexture) {
190 // The shared slot holds the texture part of the sampled texture but we
191 // still need to allocate the sampler.
192 SkASSERT(std::holds_alternative<TextureIndex>(*slot));
193 auto samplerResource = this->allocateResource(step, r, index);
194 const SamplerIndex* samplerIdx =
195 std::get_if<SamplerIndex>(&samplerResource);
196 SkASSERT(samplerIdx);
197 int bindingIndex = distinctRanges ? texIndex
198 : separateSampler ? bufferOrGlobalIndex++
199 : bufferOrGlobalIndex;
200 dispatch.fBindings.push_back(
201 {static_cast<BindingIndex>(bindingIndex), *samplerIdx});
202 }
203 }
204 break;
205 }
206 }
207
208 int bindingIndex = 0;
209 DispatchResource dispatchResource;
210 if (const BufferView* buffer = std::get_if<BufferView>(&maybeResource)) {
211 dispatchResource = *buffer;
212 bindingIndex = bufferOrGlobalIndex++;
213 } else if (const TextureIndex* texIdx = std::get_if<TextureIndex>(&maybeResource)) {
214 dispatchResource = *texIdx;
215 bindingIndex = distinctRanges ? texIndex++ : bufferOrGlobalIndex++;
216 } else {
217 SKGPU_LOG_W("Failed to allocate resource for compute dispatch");
218 return false;
219 }
220 dispatch.fBindings.push_back({static_cast<BindingIndex>(bindingIndex), dispatchResource});
221 }
222
223 auto wgBufferDescs = step->workgroupBuffers();
224 if (!wgBufferDescs.empty()) {
225 dispatch.fWorkgroupBuffers.push_back_n(wgBufferDescs.size(), wgBufferDescs.data());
226 }
227
228 // We need to switch pipelines if this step uses a different pipeline from the previous step.
229 if (fObj->fPipelineDescs.empty() ||
230 fObj->fPipelineDescs.back().uniqueID() != step->uniqueID()) {
231 fObj->fPipelineDescs.push_back(ComputePipelineDesc(step));
232 }
233
234 dispatch.fPipelineIndex = fObj->fPipelineDescs.size() - 1;
235 dispatch.fLocalSize = step->localDispatchSize();
236 dispatch.fGlobalSizeOrIndirect = globalSizeOrIndirect;
237
238 fObj->fDispatchList.push_back(std::move(dispatch));
239
240 return true;
241 }
242
assignSharedBuffer(BufferView buffer,unsigned int slot,ClearBuffer cleared)243 void Builder::assignSharedBuffer(BufferView buffer, unsigned int slot, ClearBuffer cleared) {
244 SkASSERT(fObj);
245 SkASSERT(buffer.fInfo);
246 SkASSERT(buffer.fSize);
247
248 fOutputTable.fSharedSlots[slot] = buffer;
249 if (cleared == ClearBuffer::kYes) {
250 fObj->fClearList.push_back({buffer.fInfo.fBuffer, buffer.fInfo.fOffset, buffer.fSize});
251 }
252 }
253
assignSharedTexture(sk_sp<TextureProxy> texture,unsigned int slot)254 void Builder::assignSharedTexture(sk_sp<TextureProxy> texture, unsigned int slot) {
255 SkASSERT(fObj);
256 SkASSERT(texture);
257
258 fObj->fTextures.push_back(std::move(texture));
259 fOutputTable.fSharedSlots[slot] = TextureIndex{fObj->fTextures.size() - 1u};
260 }
261
finalize()262 std::unique_ptr<DispatchGroup> Builder::finalize() {
263 auto obj = std::move(fObj);
264 fOutputTable.reset();
265 return obj;
266 }
267
268 #if defined(GRAPHITE_TEST_UTILS)
reset()269 void Builder::reset() {
270 fOutputTable.reset();
271 fObj.reset(new DispatchGroup);
272 }
273 #endif
274
getSharedBufferResource(unsigned int slot) const275 BindBufferInfo Builder::getSharedBufferResource(unsigned int slot) const {
276 SkASSERT(fObj);
277
278 BindBufferInfo info;
279 if (const BufferView* slotValue = std::get_if<BufferView>(&fOutputTable.fSharedSlots[slot])) {
280 info = slotValue->fInfo;
281 }
282 return info;
283 }
284
getSharedTextureResource(unsigned int slot) const285 sk_sp<TextureProxy> Builder::getSharedTextureResource(unsigned int slot) const {
286 SkASSERT(fObj);
287
288 const TextureIndex* idx = std::get_if<TextureIndex>(&fOutputTable.fSharedSlots[slot]);
289 if (!idx) {
290 return nullptr;
291 }
292
293 SkASSERT(idx->fValue < SkToSizeT(fObj->fTextures.size()));
294 return fObj->fTextures[idx->fValue];
295 }
296
allocateResource(const ComputeStep * step,const ComputeStep::ResourceDesc & resource,int resourceIdx)297 DispatchResourceOptional Builder::allocateResource(const ComputeStep* step,
298 const ComputeStep::ResourceDesc& resource,
299 int resourceIdx) {
300 SkASSERT(step);
301 SkASSERT(fObj);
302 using Type = ComputeStep::ResourceType;
303 using ResourcePolicy = ComputeStep::ResourcePolicy;
304
305 DrawBufferManager* bufferMgr = fRecorder->priv().drawBufferManager();
306 DispatchResourceOptional result;
307 switch (resource.fType) {
308 case Type::kReadOnlyStorageBuffer:
309 case Type::kStorageBuffer: {
310 size_t bufferSize = step->calculateBufferSize(resourceIdx, resource);
311 SkASSERT(bufferSize);
312 if (resource.fPolicy == ResourcePolicy::kMapped) {
313 auto [ptr, bufInfo] = bufferMgr->getStoragePointer(bufferSize);
314 if (ptr) {
315 step->prepareStorageBuffer(resourceIdx, resource, ptr, bufferSize);
316 result = BufferView{bufInfo, bufferSize};
317 }
318 } else {
319 auto bufInfo = bufferMgr->getStorage(bufferSize,
320 resource.fPolicy == ResourcePolicy::kClear
321 ? ClearBuffer::kYes
322 : ClearBuffer::kNo);
323 if (bufInfo) {
324 result = BufferView{bufInfo, bufferSize};
325 }
326 }
327 break;
328 }
329 case Type::kIndirectBuffer: {
330 SkASSERT(resource.fPolicy != ResourcePolicy::kMapped);
331
332 size_t bufferSize = step->calculateBufferSize(resourceIdx, resource);
333 SkASSERT(bufferSize);
334 auto bufInfo = bufferMgr->getIndirectStorage(bufferSize,
335 resource.fPolicy == ResourcePolicy::kClear
336 ? ClearBuffer::kYes
337 : ClearBuffer::kNo);
338 if (bufInfo) {
339 result = BufferView{bufInfo, bufferSize};
340 }
341 break;
342 }
343 case Type::kUniformBuffer: {
344 SkASSERT(resource.fPolicy == ResourcePolicy::kMapped);
345
346 const auto& resourceReqs = fRecorder->priv().caps()->resourceBindingRequirements();
347 UniformManager uboMgr(resourceReqs.fUniformBufferLayout);
348 step->prepareUniformBuffer(resourceIdx, resource, &uboMgr);
349
350 auto dataBlock = uboMgr.finishUniformDataBlock();
351 SkASSERT(dataBlock.size());
352
353 auto [writer, bufInfo] = bufferMgr->getUniformWriter(dataBlock.size());
354 if (bufInfo) {
355 writer.write(dataBlock.data(), dataBlock.size());
356 result = BufferView{bufInfo, dataBlock.size()};
357 }
358 break;
359 }
360 case Type::kWriteOnlyStorageTexture: {
361 auto [size, colorType] = step->calculateTextureParameters(resourceIdx, resource);
362 SkASSERT(!size.isEmpty());
363 SkASSERT(colorType != kUnknown_SkColorType);
364
365 auto textureInfo = fRecorder->priv().caps()->getDefaultStorageTextureInfo(colorType);
366 sk_sp<TextureProxy> texture = TextureProxy::Make(
367 fRecorder->priv().caps(), fRecorder->priv().resourceProvider(),
368 size, textureInfo, "DispatchWriteOnlyStorageTexture", skgpu::Budgeted::kYes);
369 if (texture) {
370 fObj->fTextures.push_back(std::move(texture));
371 result = TextureIndex{fObj->fTextures.size() - 1u};
372 }
373 break;
374 }
375 case Type::kReadOnlyTexture:
376 // This resource type is meant to be populated externally (e.g. by an upload or a render
377 // pass) and only read/sampled by a ComputeStep. It's not meaningful to allocate an
378 // internal texture for a DispatchGroup if none of the ComputeSteps will write to it.
379 //
380 // Instead of using internal allocation, this texture must be assigned explicitly to a
381 // slot by calling the Builder::assignSharedTexture() method.
382 //
383 // Note: A ComputeStep is allowed to read/sample from a storage texture that a previous
384 // ComputeStep has written to.
385 SK_ABORT("a readonly texture must be externally assigned to a ComputeStep");
386 break;
387 case Type::kSampledTexture: {
388 fObj->fSamplerDescs.push_back(step->calculateSamplerParameters(resourceIdx, resource));
389 result = SamplerIndex{fObj->fSamplerDescs.size() - 1u};
390 break;
391 }
392 }
393 return result;
394 }
395
396 } // namespace skgpu::graphite
397