Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
DispatchGroup.cpp
Go to the documentation of this file.
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
9
22
23namespace skgpu::graphite {
24
26
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
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
76 if (fClearList.empty()) {
77 return nullptr;
78 }
79 return ClearBuffersTask::Make(std::move(fClearList));
80}
81
82const 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
89const Sampler* DispatchGroup::getSampler(size_t index) const {
90 SkASSERT(index < SkToSizeT(fSamplers.size()));
91 SkASSERT(fSamplers[index]);
92 return fSamplers[index].get();
93}
94
96
97Builder::Builder(Recorder* recorder) : fObj(new DispatchGroup()), fRecorder(recorder) {
98 SkASSERT(fRecorder);
99}
100
101bool Builder::appendStep(const ComputeStep* step, std::optional<WorkgroupSize> globalSize) {
102 return this->appendStepInternal(step,
103 globalSize ? *globalSize : step->calculateGlobalDispatchSize());
104}
105
107 return this->appendStepInternal(step, indirectBuffer);
108}
109
110bool 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;
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);
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
243void 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
255 SkASSERT(fObj);
257
258 fObj->fTextures.push_back(std::move(texture));
259 fOutputTable.fSharedSlots[slot] = TextureIndex{fObj->fTextures.size() - 1u};
260}
261
262std::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)
269void Builder::reset() {
270 fOutputTable.reset();
271 fObj.reset(new DispatchGroup);
272}
273#endif
274
276 SkASSERT(fObj);
277
279 if (const BufferView* slotValue = std::get_if<BufferView>(&fOutputTable.fSharedSlots[slot])) {
280 info = slotValue->fInfo;
281 }
282 return info;
283}
284
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
297DispatchResourceOptional Builder::allocateResource(const ComputeStep* step,
298 const ComputeStep::ResourceDesc& resource,
299 int resourceIdx) {
300 SkASSERT(step);
301 SkASSERT(fObj);
303 using ResourcePolicy = ComputeStep::ResourcePolicy;
304
305 DrawBufferManager* bufferMgr = fRecorder->priv().drawBufferManager();
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
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
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());
364
365 auto textureInfo = fRecorder->priv().caps()->getDefaultStorageTextureInfo(colorType);
367 fRecorder->priv().caps(), fRecorder->priv().resourceProvider(),
368 size, textureInfo, 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
static int step(int x, SkScalar min, SkScalar max)
Definition BlurTest.cpp:215
static void info(const char *fmt,...) SK_PRINTF_LIKE(1
Definition DM.cpp:213
#define SKGPU_LOG_W(fmt,...)
Definition Log.h:40
#define SK_ABORT(message,...)
Definition SkAssert.h:70
#define SkASSERT(cond)
Definition SkAssert.h:116
@ kUnknown_SkColorType
uninitialized
Definition SkColorType.h:20
static SkColorType colorType(AImageDecoder *decoder, const AImageDecoderHeaderInfo *headerInfo)
constexpr size_t SkToSizeT(S x)
Definition SkTo.h:31
const ResourceBindingRequirements & resourceBindingRequirements() const
Definition Caps.h:143
virtual TextureInfo getDefaultStorageTextureInfo(SkColorType) const =0
static sk_sp< ClearBuffersTask > Make(skia_private::TArray< ClearBufferInfo >)
void trackResource(sk_sp< Resource > resource)
void trackCommandBufferResource(sk_sp< Resource > resource)
bool appendStepIndirect(const ComputeStep *, BufferView indirectBuffer)
void assignSharedBuffer(BufferView buffer, unsigned int slot, ClearBuffer cleared=ClearBuffer::kNo)
sk_sp< TextureProxy > getSharedTextureResource(unsigned int slot) const
std::unique_ptr< DispatchGroup > finalize()
bool appendStep(const ComputeStep *, std::optional< WorkgroupSize > globalSize=std::nullopt)
void assignSharedTexture(sk_sp< TextureProxy > texture, unsigned int slot)
BindBufferInfo getSharedBufferResource(unsigned int slot) const
void addResourceRefs(CommandBuffer *) const
const Texture * getTexture(size_t index) const
bool prepareResources(ResourceProvider *)
const Sampler * getSampler(size_t index) const
std::pair< void *, BindBufferInfo > getStoragePointer(size_t requiredBytes)
std::pair< UniformWriter, BindBufferInfo > getUniformWriter(size_t requiredBytes)
BindBufferInfo getStorage(size_t requiredBytes, ClearBuffer cleared=ClearBuffer::kNo)
BindBufferInfo getIndirectStorage(size_t requiredBytes, ClearBuffer cleared=ClearBuffer::kNo)
const Caps * caps() const
ResourceProvider * resourceProvider()
DrawBufferManager * drawBufferManager()
sk_sp< ComputePipeline > findOrCreateComputePipeline(const ComputePipelineDesc &)
sk_sp< Sampler > findOrCreateCompatibleSampler(const SamplerDesc &)
bool isCompatible(const TextureInfo &that) const
static bool InstantiateIfNotLazy(ResourceProvider *, TextureProxy *)
static sk_sp< TextureProxy > Make(const Caps *, ResourceProvider *, SkISize dimensions, const TextureInfo &, skgpu::Budgeted)
const TextureInfo & textureInfo() const
static const uint8_t buffer[]
GAsyncResult * result
FlTexture * texture
it will be possible to load the file into Perfetto s trace viewer disable asset Prevents usage of any non test fonts unless they were explicitly Loaded via prefetched default font Indicates whether the embedding started a prefetch of the default font manager before creating the engine run In non interactive keep the shell running after the Dart script has completed enable serial On low power devices with low core running concurrent GC tasks on threads can cause them to contend with the UI thread which could potentially lead to jank This option turns off all concurrent GC activities domain network JSON encoded network policy per domain This overrides the DisallowInsecureConnections switch Embedder can specify whether to allow or disallow insecure connections at a domain level old gen heap size
Definition switches.h:259
std::variant< BufferView, TextureIndex, SamplerIndex > DispatchResource
constexpr int kMaxComputeDataFlowSlots
uint32_t BindingIndex
std::variant< std::monostate, BufferView, TextureIndex, SamplerIndex > DispatchResourceOptional
DispatchResourceOptional fSharedSlots[kMaxComputeDataFlowSlots]
skia_private::TArray< ComputeStep::WorkgroupBufferDesc > fWorkgroupBuffers
std::variant< WorkgroupSize, BufferView > fGlobalSizeOrIndirect
skia_private::TArray< ResourceBinding > fBindings