blob: 28293fdbdf1cad4a5c8c74d3890f1459aacf3fe8 [file] [log] [blame]
/*
* Copyright 2023 Google LLC
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "src/gpu/graphite/compute/DispatchGroup.h"
#include "include/gpu/graphite/Recorder.h"
#include "src/gpu/graphite/BufferManager.h"
#include "src/gpu/graphite/Caps.h"
#include "src/gpu/graphite/CommandBuffer.h"
#include "src/gpu/graphite/ComputePipeline.h"
#include "src/gpu/graphite/Log.h"
#include "src/gpu/graphite/PipelineData.h"
#include "src/gpu/graphite/RecorderPriv.h"
#include "src/gpu/graphite/ResourceProvider.h"
#include "src/gpu/graphite/Texture.h"
#include "src/gpu/graphite/UniformManager.h"
namespace skgpu::graphite {
DispatchGroup::~DispatchGroup() = default;
bool DispatchGroup::prepareResources(ResourceProvider* resourceProvider) {
fPipelines.reserve(fPipelines.size() + fPipelineDescs.size());
for (const ComputePipelineDesc& desc : fPipelineDescs) {
auto pipeline = resourceProvider->findOrCreateComputePipeline(desc);
if (!pipeline) {
SKGPU_LOG_W("Failed to create ComputePipeline for dispatch group. Dropping group!");
return false;
}
fPipelines.push_back(std::move(pipeline));
}
for (int i = 0; i < fTextures.size(); ++i) {
if (!fTextures[i]->textureInfo().isValid()) {
SKGPU_LOG_W("Failed to validate bound texture. Dropping dispatch group!");
return false;
}
if (!TextureProxy::InstantiateIfNotLazy(resourceProvider, fTextures[i].get())) {
SKGPU_LOG_W("Failed to instantiate bound texture. Dropping dispatch group!");
return false;
}
}
// The DispatchGroup may be long lived on a Recording and we no longer need ComputePipelineDescs
// once we've created pipelines.
fPipelineDescs.clear();
return true;
}
void DispatchGroup::addResourceRefs(CommandBuffer* commandBuffer) const {
for (int i = 0; i < fPipelines.size(); ++i) {
commandBuffer->trackResource(fPipelines[i]);
}
for (int i = 0; i < fTextures.size(); ++i) {
commandBuffer->trackResource(fTextures[i]->refTexture());
}
}
const Texture* DispatchGroup::getTexture(TextureIndex index) const {
SkASSERT(index < SkToSizeT(fTextures.size()));
SkASSERT(fTextures[index]);
SkASSERT(fTextures[index]->texture());
return fTextures[index]->texture();
}
using Builder = DispatchGroup::Builder;
Builder::Builder(Recorder* recorder) : fObj(new DispatchGroup()), fRecorder(recorder) {
SkASSERT(fRecorder);
}
bool Builder::appendStep(const ComputeStep* step,
const DrawParams& params,
int ssboIndex,
std::optional<WorkgroupSize> globalSize) {
SkASSERT(fObj);
SkASSERT(step);
Dispatch dispatch;
// Process the step's resources.
auto resources = step->resources();
dispatch.fBindings.reserve(resources.size());
// `nextIndex` matches the declaration order of resources as specified by the ComputeStep.
int nextIndex = 0;
// We assign buffer and texture indices from separate ranges. This is compatible with how
// Graphite assigns indices in all of its backends: on Metal these map directly to
// the `buffer()` and `texture()` index ranges; on Dawn/Vulkan these are allocated from separate
// bind groups/descriptor sets.
//
// TODO(armansito): This also happens to be compatible with the binding index reassignment
// scheme that the vello_shaders crate implements for WGSL->MSL translation (see
// https://github.com/linebender/vello/blob/main/crates/shaders/src/compile/msl.rs#L10). However
// Vello WGSL bindings are currently all assigned from the same bind group without separate
// assignments for textures and buffers. We'll need to figure out how to re-map these on
// Graphite's Dawn backend which should use the WGSL text directly.
int bufferIndex = 0;
int texIndex = 0;
for (const ComputeStep::ResourceDesc& r : resources) {
SkASSERT(r.fSlot == -1 || (r.fSlot >= 0 && r.fSlot < kMaxComputeDataFlowSlots));
int index = nextIndex++;
DispatchResourceOptional maybeResource;
using DataFlow = ComputeStep::DataFlow;
switch (r.fFlow) {
case DataFlow::kVertexOutput:
case DataFlow::kIndexOutput:
case DataFlow::kInstanceOutput:
case DataFlow::kIndirectDrawOutput: {
auto bufferInfo = this->allocateDrawBuffer(step, r, index, params);
if (bufferInfo) {
maybeResource = bufferInfo;
}
break;
}
case DataFlow::kPrivate:
maybeResource = this->allocateResource(step, r, ssboIndex, index, params);
break;
case DataFlow::kShared: {
// TODO: Support allocating a scratch texture
SkASSERT(r.fSlot >= 0);
// Allocate a new buffer only if the shared slot is empty.
DispatchResourceOptional* slot = &fOutputTable.fSharedSlots[r.fSlot];
if (std::holds_alternative<std::monostate>(*slot)) {
maybeResource = this->allocateResource(step, r, ssboIndex, index, params);
*slot = maybeResource;
} else {
SkDEBUGCODE(using Type = ComputeStep::ResourceType;)
SkASSERT((r.fType == Type::kStorageBuffer &&
std::holds_alternative<BindBufferInfo>(*slot)) ||
((r.fType == Type::kTexture || r.fType == Type::kStorageTexture) &&
std::holds_alternative<TextureIndex>(*slot)));
maybeResource = *slot;
}
break;
}
}
int bindingIndex = 0;
DispatchResource dispatchResource;
if (const BindBufferInfo* buffer = std::get_if<BindBufferInfo>(&maybeResource)) {
dispatchResource = *buffer;
bindingIndex = bufferIndex++;
} else if (const TextureIndex* texIdx = std::get_if<TextureIndex>(&maybeResource)) {
dispatchResource = *texIdx;
bindingIndex = texIndex++;
} else {
SKGPU_LOG_W("Failed to allocate resource for compute dispatch");
return false;
}
dispatch.fBindings.push_back({static_cast<BindingIndex>(bindingIndex), dispatchResource});
}
auto wgBufferDescs = step->workgroupBuffers();
if (!wgBufferDescs.empty()) {
dispatch.fWorkgroupBuffers.push_back_n(wgBufferDescs.size(), wgBufferDescs.data());
}
// We need to switch pipelines if this step uses a different pipeline from the previous step.
if (fObj->fPipelineDescs.empty() ||
fObj->fPipelineDescs.back().uniqueID() != step->uniqueID()) {
fObj->fPipelineDescs.push_back(ComputePipelineDesc(step));
}
dispatch.fPipelineIndex = fObj->fPipelineDescs.size() - 1;
dispatch.fParams.fGlobalDispatchSize =
globalSize ? *globalSize : step->calculateGlobalDispatchSize(params);
dispatch.fParams.fLocalDispatchSize = step->localDispatchSize();
fObj->fDispatchList.push_back(std::move(dispatch));
return true;
}
void Builder::assignSharedBuffer(BindBufferInfo buffer, unsigned int slot) {
SkASSERT(fObj);
SkASSERT(buffer);
fOutputTable.fSharedSlots[slot] = buffer;
}
void Builder::assignSharedTexture(sk_sp<TextureProxy> texture, unsigned int slot) {
SkASSERT(fObj);
SkASSERT(texture);
fObj->fTextures.push_back(std::move(texture));
fOutputTable.fSharedSlots[slot] = TextureIndex(fObj->fTextures.size() - 1);
}
std::unique_ptr<DispatchGroup> Builder::finalize() {
auto obj = std::move(fObj);
fOutputTable.reset();
return obj;
}
BindBufferInfo Builder::getSharedBufferResource(unsigned int slot) const {
SkASSERT(fObj);
BindBufferInfo info;
if (const BindBufferInfo* slotValue =
std::get_if<BindBufferInfo>(&fOutputTable.fSharedSlots[slot])) {
info = *slotValue;
}
return info;
}
sk_sp<TextureProxy> Builder::getSharedTextureResource(unsigned int slot) const {
SkASSERT(fObj);
const TextureIndex* idx = std::get_if<TextureIndex>(&fOutputTable.fSharedSlots[slot]);
if (!idx) {
return nullptr;
}
SkASSERT(*idx < SkToSizeT(fObj->fTextures.size()));
return fObj->fTextures[*idx];
}
BindBufferInfo Builder::allocateDrawBuffer(const ComputeStep* step,
const ComputeStep::ResourceDesc& resource,
int resourceIdx,
const DrawParams& params) {
SkASSERT(step);
SkASSERT(resource.fType == ComputeStep::ResourceType::kStorageBuffer);
size_t bufferSize = step->calculateBufferSize(params, resourceIdx, resource);
SkASSERT(bufferSize);
DrawBufferManager* bufferMgr = fRecorder->priv().drawBufferManager();
BindBufferInfo* slot = nullptr;
BindBufferInfo info;
using DataFlow = ComputeStep::DataFlow;
switch (resource.fFlow) {
case DataFlow::kVertexOutput:
slot = &fOutputTable.fVertexBuffer;
info = bufferMgr->getVertexStorage(bufferSize);
break;
case DataFlow::kIndexOutput:
slot = &fOutputTable.fIndexBuffer;
info = bufferMgr->getIndexStorage(bufferSize);
break;
case DataFlow::kInstanceOutput:
slot = &fOutputTable.fInstanceBuffer;
info = bufferMgr->getVertexStorage(bufferSize);
break;
case DataFlow::kIndirectDrawOutput:
slot = &fOutputTable.fIndirectDrawBuffer;
info = bufferMgr->getIndirectStorage(bufferSize);
break;
default:
SkASSERT(false);
break;
}
// Multiple ComputeSteps in a sequence are currently not allowed to output the same type of
// geometry (this is enforced during ComputeStep construction).
SkASSERT(*slot);
if (info) {
*slot = info;
}
return info;
}
DispatchResourceOptional Builder::allocateResource(const ComputeStep* step,
const ComputeStep::ResourceDesc& resource,
int ssboIdx,
int resourceIdx,
const DrawParams& params) {
SkASSERT(step);
using Type = ComputeStep::ResourceType;
using ResourcePolicy = ComputeStep::ResourcePolicy;
DrawBufferManager* bufferMgr = fRecorder->priv().drawBufferManager();
DispatchResourceOptional result;
switch (resource.fType) {
case Type::kStorageBuffer: {
size_t bufferSize = step->calculateBufferSize(params, resourceIdx, resource);
SkASSERT(bufferSize);
if (resource.fPolicy == ResourcePolicy::kMapped) {
auto [ptr, bufInfo] = bufferMgr->getStoragePointer(bufferSize);
if (ptr) {
step->prepareStorageBuffer(
params, ssboIdx, resourceIdx, resource, ptr, bufferSize);
result = bufInfo;
}
} else {
auto bufInfo = bufferMgr->getStorage(bufferSize,
resource.fPolicy == ResourcePolicy::kClear
? ClearBuffer::kYes
: ClearBuffer::kNo);
if (bufInfo) {
result = bufInfo;
}
}
break;
}
case Type::kUniformBuffer: {
SkASSERT(resource.fPolicy == ResourcePolicy::kMapped);
const auto& resourceReqs = fRecorder->priv().caps()->resourceBindingRequirements();
UniformManager uboMgr(resourceReqs.fUniformBufferLayout);
step->prepareUniformBuffer(params, resourceIdx, resource, &uboMgr);
auto dataBlock = uboMgr.finishUniformDataBlock();
SkASSERT(dataBlock.size());
auto [writer, bufInfo] = bufferMgr->getUniformWriter(dataBlock.size());
if (bufInfo) {
writer.write(dataBlock.data(), dataBlock.size());
result = bufInfo;
}
break;
}
case Type::kStorageTexture: {
auto [size, colorType] =
step->calculateTextureParameters(params, resourceIdx, resource);
SkASSERT(!size.isEmpty());
SkASSERT(colorType != kUnknown_SkColorType);
sk_sp<TextureProxy> texture = TextureProxy::MakeStorage(
fRecorder->priv().caps(), size, colorType, skgpu::Budgeted::kYes);
if (texture) {
fObj->fTextures.push_back(std::move(texture));
result = TextureIndex(fObj->fTextures.size() - 1);
}
break;
}
case Type::kTexture:
// This resource type is meant to be populated externally (e.g. by an upload or a render
// pass) and only sampled by a ComputeStep. It's not meaningful to allocate an internal
// texture for a DispatchGroup if none of the ComputeSteps will write to it.
//
// Instead of using internal allocation, this texture must be assigned explicitly to a
// slot by calling the Builder::assignSharedTexture() method.
SK_ABORT("a sampled texture must be externally assigned to a ComputeStep");
break;
}
return result;
}
} // namespace skgpu::graphite