• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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