blob: a21b9c8950111c35d12a75e62656ace2991c0567 [file] [log] [blame]
/*
* Copyright 2022 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/dawn/DawnResourceProvider.h"
#include "include/core/SkString.h"
#include "include/gpu/graphite/BackendTexture.h"
#include "include/gpu/graphite/TextureInfo.h"
#include "include/gpu/graphite/dawn/DawnGraphiteTypes.h"
#include "include/private/base/SingleOwner.h"
#include "include/private/base/SkAlign.h"
#include "src/gpu/graphite/ComputePipeline.h"
#include "src/gpu/graphite/RenderPassDesc.h"
#include "src/gpu/graphite/dawn/DawnBuffer.h"
#include "src/gpu/graphite/dawn/DawnCommandBuffer.h"
#include "src/gpu/graphite/dawn/DawnComputePipeline.h"
#include "src/gpu/graphite/dawn/DawnErrorChecker.h"
#include "src/gpu/graphite/dawn/DawnGraphicsPipeline.h"
#include "src/gpu/graphite/dawn/DawnGraphiteUtils.h"
#include "src/gpu/graphite/dawn/DawnSampler.h"
#include "src/gpu/graphite/dawn/DawnSharedContext.h"
#include "src/gpu/graphite/dawn/DawnTexture.h"
#include "src/sksl/SkSLCompiler.h"
namespace skgpu::graphite {
namespace {
constexpr uint32_t kBufferBindingSizeAlignment = 16;
constexpr int kMaxNumberOfCachedBufferBindGroups = 1024;
constexpr int kMaxNumberOfCachedTextureBindGroups = 4096;
wgpu::ShaderModule create_shader_module(const wgpu::Device& device, const char* source) {
#if defined(__EMSCRIPTEN__)
wgpu::ShaderModuleWGSLDescriptor wgslDesc;
#else
wgpu::ShaderSourceWGSL wgslDesc;
#endif
wgslDesc.code = source;
wgpu::ShaderModuleDescriptor descriptor;
descriptor.nextInChain = &wgslDesc;
return device.CreateShaderModule(&descriptor);
}
wgpu::RenderPipeline create_blit_render_pipeline(const DawnSharedContext* sharedContext,
const char* label,
wgpu::ShaderModule shaderModule,
const char* vsEntryPoint,
const char* fsEntryPoint,
wgpu::TextureFormat renderPassColorFormat,
wgpu::TextureFormat renderPassDepthStencilFormat,
int numSamples) {
wgpu::RenderPipelineDescriptor descriptor;
descriptor.label = label;
descriptor.layout = nullptr;
wgpu::ColorTargetState colorTarget;
colorTarget.format = renderPassColorFormat;
colorTarget.blend = nullptr;
colorTarget.writeMask = wgpu::ColorWriteMask::All;
wgpu::DepthStencilState depthStencil;
if (renderPassDepthStencilFormat != wgpu::TextureFormat::Undefined) {
depthStencil.format = renderPassDepthStencilFormat;
depthStencil.depthWriteEnabled = false;
depthStencil.depthCompare = wgpu::CompareFunction::Always;
descriptor.depthStencil = &depthStencil;
}
wgpu::FragmentState fragment;
fragment.module = shaderModule;
fragment.entryPoint = fsEntryPoint;
fragment.constantCount = 0;
fragment.constants = nullptr;
fragment.targetCount = 1;
fragment.targets = &colorTarget;
descriptor.fragment = &fragment;
descriptor.vertex.module = shaderModule;
descriptor.vertex.entryPoint = vsEntryPoint;
descriptor.vertex.constantCount = 0;
descriptor.vertex.constants = nullptr;
descriptor.vertex.bufferCount = 0;
descriptor.vertex.buffers = nullptr;
descriptor.primitive.frontFace = wgpu::FrontFace::CCW;
descriptor.primitive.cullMode = wgpu::CullMode::None;
descriptor.primitive.topology = wgpu::PrimitiveTopology::TriangleStrip;
descriptor.primitive.stripIndexFormat = wgpu::IndexFormat::Undefined;
descriptor.multisample.count = numSamples;
descriptor.multisample.mask = 0xFFFFFFFF;
descriptor.multisample.alphaToCoverageEnabled = false;
std::optional<DawnErrorChecker> errorChecker;
if (sharedContext->dawnCaps()->allowScopedErrorChecks()) {
errorChecker.emplace(sharedContext);
}
auto pipeline = sharedContext->device().CreateRenderPipeline(&descriptor);
if (errorChecker.has_value() && errorChecker->popErrorScopes() != DawnErrorType::kNoError) {
return nullptr;
}
return pipeline;
}
template <size_t NumEntries>
using BindGroupKey = typename DawnResourceProvider::BindGroupKey<NumEntries>;
using UniformBindGroupKey = BindGroupKey<DawnResourceProvider::kNumUniformEntries>;
UniformBindGroupKey make_ubo_bind_group_key(
const std::array<std::pair<const DawnBuffer*, uint32_t>,
DawnResourceProvider::kNumUniformEntries>& boundBuffersAndSizes) {
UniformBindGroupKey uniqueKey;
{
// Each entry in the bind group needs 2 uint32_t in the key:
// - buffer's unique ID: 32 bits.
// - buffer's binding size: 32 bits.
// We need total of 4 entries in the uniform buffer bind group.
// Unused entries will be assigned zero values.
UniformBindGroupKey::Builder builder(&uniqueKey);
for (uint32_t i = 0; i < boundBuffersAndSizes.size(); ++i) {
const DawnBuffer* boundBuffer = boundBuffersAndSizes[i].first;
const uint32_t bindingSize = boundBuffersAndSizes[i].second;
if (boundBuffer) {
builder[2 * i] = boundBuffer->uniqueID().asUInt();
builder[2 * i + 1] = bindingSize;
} else {
builder[2 * i] = 0;
builder[2 * i + 1] = 0;
}
}
builder.finish();
}
return uniqueKey;
}
BindGroupKey<1> make_texture_bind_group_key(const DawnSampler* sampler,
const DawnTexture* texture) {
BindGroupKey<1> uniqueKey;
{
BindGroupKey<1>::Builder builder(&uniqueKey);
builder[0] = sampler->uniqueID().asUInt();
builder[1] = texture->uniqueID().asUInt();
builder.finish();
}
return uniqueKey;
}
} // namespace
// Wraps a Dawn buffer, and tracks the intrinsic blocks residing in this buffer.
class DawnResourceProvider::IntrinsicBuffer final {
public:
static constexpr int kNumSlots = 8;
IntrinsicBuffer(sk_sp<DawnBuffer> dawnBuffer) : fDawnBuffer(std::move(dawnBuffer)) {}
~IntrinsicBuffer() = default;
sk_sp<DawnBuffer> buffer() const { return fDawnBuffer; }
// Track that 'intrinsicValues' is stored in the buffer at the 'offset'.
void trackIntrinsic(UniformDataBlock intrinsicValues, uint32_t offset) {
fCachedIntrinsicValues.set(UniformDataBlock::Make(intrinsicValues, &fUniformData), offset);
}
// Find the offset of 'intrinsicValues' in the buffer. If not found, return nullptr.
uint32_t* findIntrinsic(UniformDataBlock intrinsicValues) const {
return fCachedIntrinsicValues.find(intrinsicValues);
}
int slotsUsed() const { return fCachedIntrinsicValues.count(); }
void updateAccessTime() {
fLastAccess = skgpu::StdSteadyClock::now();
}
skgpu::StdSteadyClock::time_point lastAccessTime() const {
return fLastAccess;
}
private:
skia_private::THashMap<UniformDataBlock, uint32_t, UniformDataBlock::Hash>
fCachedIntrinsicValues;
SkArenaAlloc fUniformData{0};
sk_sp<DawnBuffer> fDawnBuffer;
skgpu::StdSteadyClock::time_point fLastAccess;
SK_DECLARE_INTERNAL_LLIST_INTERFACE(IntrinsicBuffer);
};
// DawnResourceProvider::IntrinsicConstantsManager
// ----------------------------------------------------------------------------
/**
* Since Dawn does not currently provide push constants, this helper class manages rotating through
* buffers and writing each new occurrence of a set of intrinsic uniforms into the current buffer.
*/
class DawnResourceProvider::IntrinsicConstantsManager {
public:
explicit IntrinsicConstantsManager(DawnResourceProvider* resourceProvider)
: fResourceProvider(resourceProvider) {}
~IntrinsicConstantsManager() {
auto alwaysTrue = [](IntrinsicBuffer* buffer) { return true; };
this->purgeBuffersIf(alwaysTrue);
SkASSERT(fIntrinsicBuffersLRU.isEmpty());
}
// Find or create a bind buffer info for the given intrinsic values used in the given command
// buffer.
BindBufferInfo add(DawnCommandBuffer* cb, UniformDataBlock intrinsicValues);
void purgeResourcesNotUsedSince(StdSteadyClock::time_point purgeTime) {
auto bufferNotUsedSince = [purgeTime, this](IntrinsicBuffer* buffer) {
// We always keep the current buffer as it is likely to be used again soon.
return buffer != fCurrentBuffer && buffer->lastAccessTime() < purgeTime;
};
this->purgeBuffersIf(bufferNotUsedSince);
}
void freeGpuResources() { this->purgeResourcesNotUsedSince(skgpu::StdSteadyClock::now()); }
private:
// The max number of intrinsic buffers to keep around in the cache.
static constexpr uint32_t kMaxNumBuffers = 16;
// Traverse the intrinsic buffers and purge the ones that match the 'pred'.
template<typename T> void purgeBuffersIf(T pred);
DawnResourceProvider* const fResourceProvider;
// The current buffer being filled up, as well as the how much of it has been written to.
IntrinsicBuffer* fCurrentBuffer = nullptr;
// All cached intrinsic buffers, in LRU order.
SkTInternalLList<IntrinsicBuffer> fIntrinsicBuffersLRU;
// The number of intrinsic buffers currently in the cache.
uint32_t fNumBuffers = 0;
};
// Find or create a bind buffer info for the given intrinsic values used in the given command
// buffer.
BindBufferInfo DawnResourceProvider::IntrinsicConstantsManager::add(
DawnCommandBuffer* cb, UniformDataBlock intrinsicValues) {
using Iter = SkTInternalLList<IntrinsicBuffer>::Iter;
Iter iter;
auto* curr = iter.init(fIntrinsicBuffersLRU, Iter::kHead_IterStart);
uint32_t* offset = nullptr;
// Find the buffer that contains the given intrinsic values.
while (curr != nullptr) {
offset = curr->findIntrinsic(intrinsicValues);
if (offset != nullptr) {
break;
}
curr = iter.next();
}
// If we found the buffer, we can return the bind buffer info directly.
if (curr != nullptr && offset != nullptr) {
// Move the buffer to the head of the LRU list.
fIntrinsicBuffersLRU.remove(curr);
fIntrinsicBuffersLRU.addToHead(curr);
// Track the dawn buffer's usage by the command buffer.
cb->trackResource(curr->buffer());
curr->updateAccessTime();
return {curr->buffer().get(), *offset, SkTo<uint32_t>(intrinsicValues.size())};
}
// TODO: https://b.corp.google.com/issues/259267703
// Make updating intrinsic constants faster. Metal has setVertexBytes method to quickly send
// intrinsic constants to vertex shader without any buffer. But Dawn doesn't have similar
// capability. So we have to use WriteBuffer(), and this method is not allowed to be called when
// there is an active render pass.
SkASSERT(!cb->hasActivePassEncoder());
const Caps* caps = fResourceProvider->dawnSharedContext()->caps();
const uint32_t stride =
SkAlignTo(intrinsicValues.size(), caps->requiredUniformBufferAlignment());
// In any one of the following cases, we need to create a new buffer:
// (1) There is no current buffer.
// (2) The current buffer is full.
if (!fCurrentBuffer || fCurrentBuffer->slotsUsed() == IntrinsicBuffer::kNumSlots) {
// We can just replace the current buffer; any prior buffer was already tracked in the LRU
// list and the intrinsic constants were written directly to the Dawn queue.
DawnResourceProvider* resourceProvider = fResourceProvider;
auto dawnBuffer =
resourceProvider->findOrCreateDawnBuffer(stride * IntrinsicBuffer::kNumSlots,
BufferType::kUniform,
AccessPattern::kGpuOnly,
"IntrinsicConstantBuffer");
if (!dawnBuffer) {
// If we failed to create a GPU buffer to hold the intrinsic uniforms, we will fail the
// Recording being inserted, so return an empty bind info.
return {};
}
fCurrentBuffer = new IntrinsicBuffer(dawnBuffer);
fIntrinsicBuffersLRU.addToHead(fCurrentBuffer);
fNumBuffers++;
// If we have too many buffers, remove the least used one.
if (fNumBuffers > kMaxNumBuffers) {
auto* tail = fIntrinsicBuffersLRU.tail();
fIntrinsicBuffersLRU.remove(tail);
delete tail;
fNumBuffers--;
}
}
SkASSERT(fCurrentBuffer && fCurrentBuffer->slotsUsed() < IntrinsicBuffer::kNumSlots);
uint32_t newOffset = (fCurrentBuffer->slotsUsed()) * stride;
fResourceProvider->dawnSharedContext()->queue().WriteBuffer(
fCurrentBuffer->buffer()->dawnBuffer(),
newOffset,
intrinsicValues.data(),
intrinsicValues.size());
// Track the intrinsic values in the buffer.
fCurrentBuffer->trackIntrinsic(intrinsicValues, newOffset);
cb->trackResource(fCurrentBuffer->buffer());
fCurrentBuffer->updateAccessTime();
return {fCurrentBuffer->buffer().get(), newOffset, SkTo<uint32_t>(intrinsicValues.size())};
}
template <typename T> void DawnResourceProvider::IntrinsicConstantsManager::purgeBuffersIf(T pred) {
using Iter = SkTInternalLList<IntrinsicBuffer>::Iter;
Iter iter;
auto* curr = iter.init(fIntrinsicBuffersLRU, Iter::kHead_IterStart);
while (curr != nullptr) {
auto* next = iter.next();
if (pred(curr)) {
fIntrinsicBuffersLRU.remove(curr);
fNumBuffers--;
delete curr;
}
curr = next;
}
}
// DawnResourceProvider::IntrinsicConstantsManager
// ----------------------------------------------------------------------------
// DawnResourceProvider::BlitWithDrawEncoder
DawnResourceProvider::BlitWithDrawEncoder::BlitWithDrawEncoder(wgpu::RenderPipeline pipeline,
bool srcIsMSAA)
: fPipeline(std::move(pipeline)), fSrcIsMSAA(srcIsMSAA) {}
void DawnResourceProvider::BlitWithDrawEncoder::EncodeBlit(
const wgpu::Device& device,
const wgpu::RenderPassEncoder& renderEncoder,
const wgpu::TextureView& srcTextureView,
const SkIPoint& srcOffset,
const SkIRect& dstBounds) {
SkASSERT(fPipeline);
renderEncoder.SetPipeline(fPipeline);
// TODO(b/260368758): cache single texture's bind group creation.
wgpu::BindGroupEntry entry;
entry.binding = fSrcIsMSAA ? 1 : 0;
entry.textureView = srcTextureView;
wgpu::BindGroupDescriptor desc;
desc.layout = fPipeline.GetBindGroupLayout(0);
desc.entryCount = 1;
desc.entries = &entry;
auto bindGroup = device.CreateBindGroup(&desc);
renderEncoder.SetBindGroup(0, bindGroup);
renderEncoder.SetScissorRect(
dstBounds.left(), dstBounds.top(), dstBounds.width(), dstBounds.height());
renderEncoder.SetViewport(
dstBounds.left(), dstBounds.top(), dstBounds.width(), dstBounds.height(), 0, 1);
// In fragment shader, the sampling coords are calculated as:
// - x = fragPosition.x - dstX + srcX = fragPosition.x - (dxtX - srcX)
// - y = fragPosition.y - dstY + srcX = fragPosition.y - (dxtY - srcY)
int32_t deltaX = dstBounds.left() - srcOffset.x();
int32_t deltaY = dstBounds.top() - srcOffset.y();
// Since texture's sizes are never larger than 16 bits, we can encode the offsets's (x, y)
// in one single 32 bits instance index value. In future, once push constants are implemented in
// Dawn, we should use them instead.
SkASSERT(std::abs(deltaX) < std::numeric_limits<int16_t>::max());
SkASSERT(std::abs(deltaY) < std::numeric_limits<int16_t>::max());
int32_t baseInstance = (deltaX & 0xffff) | (deltaY << 16);
renderEncoder.Draw(/*vertexCount=*/3,
/*instanceCount=*/ 1,
/*firstVertex=*/0,
/*firstInstance=*/baseInstance);
}
// ----------------------------------------------------------------------------
DawnResourceProvider::DawnResourceProvider(SharedContext* sharedContext,
SingleOwner* singleOwner,
uint32_t recorderID,
size_t resourceBudget)
: ResourceProvider(sharedContext, singleOwner, recorderID, resourceBudget)
, fUniformBufferBindGroupCache(kMaxNumberOfCachedBufferBindGroups)
, fSingleTextureSamplerBindGroups(kMaxNumberOfCachedTextureBindGroups)
, fSingleOwner(singleOwner) {
fIntrinsicConstantsManager = std::make_unique<IntrinsicConstantsManager>(this);
// Only used for debug asserts so this avoids compile errors.
(void)fSingleOwner;
}
DawnResourceProvider::~DawnResourceProvider() = default;
DawnResourceProvider::BlitWithDrawEncoder DawnResourceProvider::findOrCreateBlitWithDrawEncoder(
const RenderPassDesc& renderPassDesc, int srcSampleCount) {
// Currently Dawn only supports one sample count > 1. So we can optimize the pipeline key by
// specifying whether the source has MSAA or not.
SkASSERT(srcSampleCount <= 1 ||
srcSampleCount == this->dawnSharedContext()->dawnCaps()->defaultMSAASamplesCount());
const bool srcIsMSAA = srcSampleCount > 1;
const uint32_t pipelineKey = this->dawnSharedContext()->dawnCaps()->getRenderPassDescKeyForPipeline(
renderPassDesc, srcIsMSAA);
wgpu::RenderPipeline pipeline = fBlitWithDrawPipelines[pipelineKey];
if (!pipeline) {
// Since texture's sizes are never larger than 16 bits, we can encode the offsets's (x, y)
// in one single 32 bits instance index value.
static constexpr char kShaderSrc[] =
"struct VertexOutput {"
"@builtin(position) position: vec4f,"
"@location(1) @interpolate(flat, either) srcOffset: vec2i,"
"};"
"var<private> fullscreenTriPositions : array<vec2<f32>, 3> = array<vec2<f32>, 3>("
"vec2(-1.0, -1.0), vec2(-1.0, 3.0), vec2(3.0, -1.0));"
"@vertex "
"fn VS(@builtin(vertex_index) vertexIndex : u32,"
"@builtin(instance_index) instanceIndex : u32)"
"-> VertexOutput {"
"var out: VertexOutput;"
"out.position = vec4(fullscreenTriPositions[vertexIndex], 1.0, 1.0);"
"var srcOffset = vec2u("
"bitcast<u32>(instanceIndex & 0xffff),"
"bitcast<u32>(instanceIndex >> 16)"
");"
// Sign extending from 16 bits to 32 bits
"let hasSignBit = (srcOffset & vec2u(0x8000)) != vec2u(0u);"
"srcOffset = select(srcOffset, srcOffset | vec2u(0xffff0000), hasSignBit);"
"out.srcOffset = bitcast<vec2i>(srcOffset);"
"return out;"
"}"
"fn getSamplingCoords(input: VertexOutput) -> vec2i {"
"var coords : vec2<i32> = vec2<i32>(i32(input.position.x), i32(input.position.y));"
"return coords - input.srcOffset;"
"}"
"@group(0) @binding(0) var colorMap: texture_2d<f32>;"
"@fragment "
"fn SampleFS(input: VertexOutput) -> @location(0) vec4<f32> {"
"let coords = getSamplingCoords(input);"
"return textureLoad(colorMap, coords, 0);"
"}"
"@group(0) @binding(1) var msColorMap: texture_multisampled_2d<f32>;"
"@fragment\n"
"fn SampleMSAAFS(input: VertexOutput) -> @location(0) vec4<f32> {"
"let coords = getSamplingCoords(input);"
"const sampleCount = %d;"
"var sum = vec4f(0.0);"
"for (var i: u32 = 0; i < sampleCount; i = i + 1) {"
"sum += textureLoad(msColorMap, coords, i);"
"}"
"return sum * (1.0 / f32(sampleCount));"
"}";
auto shaderModule = create_shader_module(
dawnSharedContext()->device(), SkStringPrintf(kShaderSrc, srcSampleCount).c_str());
pipeline = create_blit_render_pipeline(
dawnSharedContext(),
/*label=*/"BlitWithDraw",
std::move(shaderModule),
"VS",
srcIsMSAA ? "SampleMSAAFS": "SampleFS",
/*renderPassColorFormat=*/
TextureFormatToDawnFormat(renderPassDesc.fColorAttachment.fFormat),
/*renderPassDepthStencilFormat=*/
TextureFormatToDawnFormat(renderPassDesc.fDepthStencilAttachment.fFormat),
/*numSamples=*/renderPassDesc.fColorAttachment.fSampleCount);
if (pipeline) {
fBlitWithDrawPipelines.set(pipelineKey, pipeline);
}
}
return BlitWithDrawEncoder(std::move(pipeline), srcIsMSAA);
}
sk_sp<Texture> DawnResourceProvider::onCreateWrappedTexture(const BackendTexture& texture) {
// Convert to smart pointers. wgpu::Texture* constructor will increment the ref count.
wgpu::Texture dawnTexture = BackendTextures::GetDawnTexturePtr(texture);
wgpu::TextureView dawnTextureView = BackendTextures::GetDawnTextureViewPtr(texture);
SkASSERT(!dawnTexture || !dawnTextureView);
if (!dawnTexture && !dawnTextureView) {
return {};
}
if (dawnTexture) {
return DawnTexture::MakeWrapped(this->dawnSharedContext(),
texture.dimensions(),
texture.info(),
std::move(dawnTexture));
} else {
return DawnTexture::MakeWrapped(this->dawnSharedContext(),
texture.dimensions(),
texture.info(),
std::move(dawnTextureView));
}
}
sk_sp<DawnTexture> DawnResourceProvider::findOrCreateDiscardableMSAALoadTexture(
SkISize dimensions, const TextureInfo& msaaInfo) {
SkASSERT(msaaInfo.isValid());
// Derive the load texture's info from MSAA texture's info.
DawnTextureInfo dawnMsaaLoadTextureInfo = TextureInfoPriv::Get<DawnTextureInfo>(msaaInfo);
dawnMsaaLoadTextureInfo.fSampleCount = 1;
dawnMsaaLoadTextureInfo.fUsage |= wgpu::TextureUsage::TextureBinding;
#if !defined(__EMSCRIPTEN__)
// MSAA texture can be transient attachment (memoryless) but the load texture cannot be.
// This is because the load texture will need to have its content retained between two passes
// loading:
// - first pass: the resolve texture is blitted to the load texture.
// - 2nd pass: the actual render pass is started and the load texture is blitted to the MSAA
// texture.
dawnMsaaLoadTextureInfo.fUsage &= (~wgpu::TextureUsage::TransientAttachment);
#endif
auto texture = this->findOrCreateShareableTexture(
dimensions,
TextureInfos::MakeDawn(dawnMsaaLoadTextureInfo),
"DiscardableLoadMSAATexture");
return sk_sp<DawnTexture>(static_cast<DawnTexture*>(texture.release()));
}
sk_sp<ComputePipeline> DawnResourceProvider::createComputePipeline(
const ComputePipelineDesc& desc) {
return DawnComputePipeline::Make(this->dawnSharedContext(), desc);
}
sk_sp<Texture> DawnResourceProvider::createTexture(SkISize dimensions, const TextureInfo& info) {
return DawnTexture::Make(this->dawnSharedContext(), dimensions, info);
}
sk_sp<Buffer> DawnResourceProvider::createBuffer(size_t size,
BufferType type,
AccessPattern accessPattern) {
return DawnBuffer::Make(this->dawnSharedContext(), size, type, accessPattern);
}
sk_sp<Sampler> DawnResourceProvider::createSampler(const SamplerDesc& samplerDesc) {
return DawnSampler::Make(this->dawnSharedContext(), samplerDesc);
}
BackendTexture DawnResourceProvider::onCreateBackendTexture(SkISize dimensions,
const TextureInfo& info) {
wgpu::Texture texture = DawnTexture::MakeDawnTexture(this->dawnSharedContext(),
dimensions,
info);
if (!texture) {
return {};
}
return BackendTextures::MakeDawn(texture.MoveToCHandle());
}
void DawnResourceProvider::onDeleteBackendTexture(const BackendTexture& texture) {
SkASSERT(texture.isValid());
SkASSERT(texture.backend() == BackendApi::kDawn);
// Automatically release the pointers in wgpu::TextureView & wgpu::Texture's dtor.
// Acquire() won't increment the ref count.
wgpu::TextureView::Acquire(BackendTextures::GetDawnTextureViewPtr(texture));
// We need to explicitly call Destroy() here since since that is the recommended way to delete
// a Dawn texture predictably versus just dropping a ref and relying on garbage collection.
//
// Additionally this helps to work around an issue where Skia may have cached a BindGroup that
// references the underlying texture. Skia currently doesn't destroy BindGroups when its use of
// the texture goes away, thus a ref to the texture remains on the BindGroup and memory is never
// cleared up unless we call Destroy() here.
wgpu::Texture::Acquire(BackendTextures::GetDawnTexturePtr(texture)).Destroy();
}
DawnSharedContext* DawnResourceProvider::dawnSharedContext() const {
return static_cast<DawnSharedContext*>(fSharedContext);
}
sk_sp<DawnBuffer> DawnResourceProvider::findOrCreateDawnBuffer(size_t size,
BufferType type,
AccessPattern accessPattern,
std::string_view label) {
sk_sp<Buffer> buffer = this->findOrCreateNonShareableBuffer(
size, type, accessPattern, std::move(label));
DawnBuffer* ptr = static_cast<DawnBuffer*>(buffer.release());
return sk_sp<DawnBuffer>(ptr);
}
const wgpu::Buffer& DawnResourceProvider::getOrCreateNullBuffer() {
if (!fNullBuffer) {
wgpu::BufferDescriptor desc;
if (fSharedContext->caps()->setBackendLabels()) {
desc.label = "UnusedBufferSlot";
}
desc.usage = wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::Uniform |
wgpu::BufferUsage::Storage;
desc.size = kBufferBindingSizeAlignment;
desc.mappedAtCreation = false;
fNullBuffer = this->dawnSharedContext()->device().CreateBuffer(&desc);
SkASSERT(fNullBuffer);
}
return fNullBuffer;
}
const wgpu::BindGroup& DawnResourceProvider::findOrCreateUniformBuffersBindGroup(
const std::array<std::pair<const DawnBuffer*, uint32_t>, kNumUniformEntries>&
boundBuffersAndSizes) {
SKGPU_ASSERT_SINGLE_OWNER(fSingleOwner)
auto key = make_ubo_bind_group_key(boundBuffersAndSizes);
auto* existingBindGroup = fUniformBufferBindGroupCache.find(key);
if (existingBindGroup) {
// cache hit.
return *existingBindGroup;
}
// Translate to wgpu::BindGroupDescriptor
std::array<wgpu::BindGroupEntry, kNumUniformEntries> entries;
constexpr uint32_t kBindingIndices[] = {
DawnGraphicsPipeline::kIntrinsicUniformBufferIndex,
DawnGraphicsPipeline::kRenderStepUniformBufferIndex,
DawnGraphicsPipeline::kPaintUniformBufferIndex,
DawnGraphicsPipeline::kGradientBufferIndex,
};
for (uint32_t i = 0; i < boundBuffersAndSizes.size(); ++i) {
const DawnBuffer* boundBuffer = boundBuffersAndSizes[i].first;
const uint32_t bindingSize = boundBuffersAndSizes[i].second;
entries[i].binding = kBindingIndices[i];
entries[i].offset = 0;
if (boundBuffer) {
entries[i].buffer = boundBuffer->dawnBuffer();
entries[i].size = SkAlignTo(bindingSize, kBufferBindingSizeAlignment);
} else {
entries[i].buffer = this->getOrCreateNullBuffer();
entries[i].size = wgpu::kWholeSize;
}
}
wgpu::BindGroupDescriptor desc;
desc.layout = this->dawnSharedContext()->getUniformBuffersBindGroupLayout();
desc.entryCount = entries.size();
desc.entries = entries.data();
const auto& device = this->dawnSharedContext()->device();
auto bindGroup = device.CreateBindGroup(&desc);
return *fUniformBufferBindGroupCache.insert(key, bindGroup);
}
const wgpu::BindGroup& DawnResourceProvider::findOrCreateSingleTextureSamplerBindGroup(
const DawnSampler* sampler, const DawnTexture* texture) {
SKGPU_ASSERT_SINGLE_OWNER(fSingleOwner)
auto key = make_texture_bind_group_key(sampler, texture);
auto* existingBindGroup = fSingleTextureSamplerBindGroups.find(key);
if (existingBindGroup) {
// cache hit.
return *existingBindGroup;
}
std::array<wgpu::BindGroupEntry, 2> entries;
entries[0].binding = 0;
entries[0].sampler = sampler->dawnSampler();
entries[1].binding = 1;
entries[1].textureView = texture->sampleTextureView();
wgpu::BindGroupDescriptor desc;
desc.layout = this->dawnSharedContext()->getSingleTextureSamplerBindGroupLayout();
desc.entryCount = entries.size();
desc.entries = entries.data();
const auto& device = this->dawnSharedContext()->device();
auto bindGroup = device.CreateBindGroup(&desc);
return *fSingleTextureSamplerBindGroups.insert(key, bindGroup);
}
void DawnResourceProvider::onFreeGpuResources() {
SKGPU_ASSERT_SINGLE_OWNER(fSingleOwner)
fIntrinsicConstantsManager->freeGpuResources();
// The wgpu::Textures and wgpu::Buffers held by the BindGroups should be explicitly destroyed
// when the DawnTexture and DawnBuffer is destroyed, but removing the bind groups themselves
// helps reduce CPU memory periodically.
fSingleTextureSamplerBindGroups.reset();
fUniformBufferBindGroupCache.reset();
}
void DawnResourceProvider::onPurgeResourcesNotUsedSince(StdSteadyClock::time_point purgeTime) {
fIntrinsicConstantsManager->purgeResourcesNotUsedSince(purgeTime);
}
BindBufferInfo DawnResourceProvider::findOrCreateIntrinsicBindBufferInfo(
DawnCommandBuffer* cb, UniformDataBlock intrinsicValues) {
return fIntrinsicConstantsManager->add(cb, intrinsicValues);
}
DawnThreadSafeResourceProvider::DawnThreadSafeResourceProvider(
std::unique_ptr<ResourceProvider> resourceProvider)
: ThreadSafeResourceProvider(std::move(resourceProvider)) {}
} // namespace skgpu::graphite