blob: 2646bfbb3d8c9c240cf77a2188a87b1939d12a24 [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/DawnGraphicsPipeline.h"
#include "include/gpu/graphite/TextureInfo.h"
#include "include/gpu/graphite/dawn/DawnGraphiteTypes.h"
#include "include/private/base/SkTemplates.h"
#include "src/core/SkTraceEvent.h"
#include "src/gpu/SkSLToBackend.h"
#include "src/gpu/Swizzle.h"
#include "src/gpu/graphite/Attribute.h"
#include "src/gpu/graphite/ContextUtils.h"
#include "src/gpu/graphite/GraphicsPipelineDesc.h"
#include "src/gpu/graphite/Log.h"
#include "src/gpu/graphite/RenderPassDesc.h"
#include "src/gpu/graphite/RendererProvider.h"
#include "src/gpu/graphite/ShaderInfo.h"
#include "src/gpu/graphite/TextureInfoPriv.h"
#include "src/gpu/graphite/UniformManager.h"
#include "src/gpu/graphite/dawn/DawnCaps.h"
#include "src/gpu/graphite/dawn/DawnErrorChecker.h"
#include "src/gpu/graphite/dawn/DawnGraphiteUtils.h"
#include "src/gpu/graphite/dawn/DawnResourceProvider.h"
#include "src/gpu/graphite/dawn/DawnSampler.h"
#include "src/gpu/graphite/dawn/DawnSharedContext.h"
#include "src/sksl/SkSLProgramSettings.h"
#include "src/sksl/SkSLUtil.h"
#include "src/sksl/ir/SkSLProgram.h"
#include <atomic>
#include <vector>
namespace skgpu::graphite {
namespace {
inline wgpu::VertexFormat attribute_type_to_dawn(VertexAttribType type) {
switch (type) {
case VertexAttribType::kFloat:
return wgpu::VertexFormat::Float32;
case VertexAttribType::kFloat2:
return wgpu::VertexFormat::Float32x2;
case VertexAttribType::kFloat3:
return wgpu::VertexFormat::Float32x3;
case VertexAttribType::kFloat4:
return wgpu::VertexFormat::Float32x4;
case VertexAttribType::kHalf2:
return wgpu::VertexFormat::Float16x2;
case VertexAttribType::kHalf4:
return wgpu::VertexFormat::Float16x4;
case VertexAttribType::kInt2:
return wgpu::VertexFormat::Sint32x2;
case VertexAttribType::kInt3:
return wgpu::VertexFormat::Sint32x3;
case VertexAttribType::kInt4:
return wgpu::VertexFormat::Sint32x4;
case VertexAttribType::kUInt2:
return wgpu::VertexFormat::Uint32x2;
case VertexAttribType::kByte2:
return wgpu::VertexFormat::Sint8x2;
case VertexAttribType::kByte4:
return wgpu::VertexFormat::Sint8x4;
case VertexAttribType::kUByte2:
return wgpu::VertexFormat::Uint8x2;
case VertexAttribType::kUByte4:
return wgpu::VertexFormat::Uint8x4;
case VertexAttribType::kUByte4_norm:
return wgpu::VertexFormat::Unorm8x4;
case VertexAttribType::kShort2:
return wgpu::VertexFormat::Sint16x2;
case VertexAttribType::kShort4:
return wgpu::VertexFormat::Sint16x4;
case VertexAttribType::kUShort2:
return wgpu::VertexFormat::Uint16x2;
case VertexAttribType::kUShort2_norm:
return wgpu::VertexFormat::Unorm16x2;
case VertexAttribType::kInt:
return wgpu::VertexFormat::Sint32;
case VertexAttribType::kUInt:
return wgpu::VertexFormat::Uint32;
case VertexAttribType::kUShort4_norm:
return wgpu::VertexFormat::Unorm16x4;
case VertexAttribType::kHalf:
case VertexAttribType::kByte:
case VertexAttribType::kUByte:
case VertexAttribType::kUByte_norm:
case VertexAttribType::kUShort_norm:
// Not supported.
break;
}
SkUNREACHABLE;
}
wgpu::CompareFunction compare_op_to_dawn(CompareOp op) {
switch (op) {
case CompareOp::kAlways:
return wgpu::CompareFunction::Always;
case CompareOp::kNever:
return wgpu::CompareFunction::Never;
case CompareOp::kGreater:
return wgpu::CompareFunction::Greater;
case CompareOp::kGEqual:
return wgpu::CompareFunction::GreaterEqual;
case CompareOp::kLess:
return wgpu::CompareFunction::Less;
case CompareOp::kLEqual:
return wgpu::CompareFunction::LessEqual;
case CompareOp::kEqual:
return wgpu::CompareFunction::Equal;
case CompareOp::kNotEqual:
return wgpu::CompareFunction::NotEqual;
}
SkUNREACHABLE;
}
wgpu::StencilOperation stencil_op_to_dawn(StencilOp op) {
switch (op) {
case StencilOp::kKeep:
return wgpu::StencilOperation::Keep;
case StencilOp::kZero:
return wgpu::StencilOperation::Zero;
case StencilOp::kReplace:
return wgpu::StencilOperation::Replace;
case StencilOp::kInvert:
return wgpu::StencilOperation::Invert;
case StencilOp::kIncWrap:
return wgpu::StencilOperation::IncrementWrap;
case StencilOp::kDecWrap:
return wgpu::StencilOperation::DecrementWrap;
case StencilOp::kIncClamp:
return wgpu::StencilOperation::IncrementClamp;
case StencilOp::kDecClamp:
return wgpu::StencilOperation::DecrementClamp;
}
SkUNREACHABLE;
}
wgpu::StencilFaceState stencil_face_to_dawn(DepthStencilSettings::Face face) {
wgpu::StencilFaceState state;
state.compare = compare_op_to_dawn(face.fCompareOp);
state.failOp = stencil_op_to_dawn(face.fStencilFailOp);
state.depthFailOp = stencil_op_to_dawn(face.fDepthFailOp);
state.passOp = stencil_op_to_dawn(face.fDepthStencilPassOp);
return state;
}
size_t create_vertex_attributes(SkSpan<const Attribute> attrs,
int shaderLocationOffset,
std::vector<wgpu::VertexAttribute>* out) {
SkASSERT(out && out->empty());
out->resize(attrs.size());
size_t vertexAttributeOffset = 0;
int attributeIndex = 0;
for (const auto& attr : attrs) {
wgpu::VertexAttribute& vertexAttribute = (*out)[attributeIndex];
vertexAttribute.format = attribute_type_to_dawn(attr.cpuType());
vertexAttribute.offset = vertexAttributeOffset;
vertexAttribute.shaderLocation = shaderLocationOffset + attributeIndex;
vertexAttributeOffset += attr.sizeAlign4();
attributeIndex++;
}
return vertexAttributeOffset;
}
// TODO: share this w/ Ganesh dawn backend?
static wgpu::BlendFactor blend_coeff_to_dawn_blend(const DawnCaps& caps, skgpu::BlendCoeff coeff) {
#if defined(__EMSCRIPTEN__)
#define VALUE_IF_DSB_OR_ZERO(VALUE) wgpu::BlendFactor::Zero
#else
#define VALUE_IF_DSB_OR_ZERO(VALUE) \
((caps.shaderCaps()->fDualSourceBlendingSupport) ? (VALUE) : wgpu::BlendFactor::Zero)
#endif
switch (coeff) {
case skgpu::BlendCoeff::kZero:
return wgpu::BlendFactor::Zero;
case skgpu::BlendCoeff::kOne:
return wgpu::BlendFactor::One;
case skgpu::BlendCoeff::kSC:
return wgpu::BlendFactor::Src;
case skgpu::BlendCoeff::kISC:
return wgpu::BlendFactor::OneMinusSrc;
case skgpu::BlendCoeff::kDC:
return wgpu::BlendFactor::Dst;
case skgpu::BlendCoeff::kIDC:
return wgpu::BlendFactor::OneMinusDst;
case skgpu::BlendCoeff::kSA:
return wgpu::BlendFactor::SrcAlpha;
case skgpu::BlendCoeff::kISA:
return wgpu::BlendFactor::OneMinusSrcAlpha;
case skgpu::BlendCoeff::kDA:
return wgpu::BlendFactor::DstAlpha;
case skgpu::BlendCoeff::kIDA:
return wgpu::BlendFactor::OneMinusDstAlpha;
case skgpu::BlendCoeff::kConstC:
return wgpu::BlendFactor::Constant;
case skgpu::BlendCoeff::kIConstC:
return wgpu::BlendFactor::OneMinusConstant;
case skgpu::BlendCoeff::kS2C:
return VALUE_IF_DSB_OR_ZERO(wgpu::BlendFactor::Src1);
case skgpu::BlendCoeff::kIS2C:
return VALUE_IF_DSB_OR_ZERO(wgpu::BlendFactor::OneMinusSrc1);
case skgpu::BlendCoeff::kS2A:
return VALUE_IF_DSB_OR_ZERO(wgpu::BlendFactor::Src1Alpha);
case skgpu::BlendCoeff::kIS2A:
return VALUE_IF_DSB_OR_ZERO(wgpu::BlendFactor::OneMinusSrc1Alpha);
case skgpu::BlendCoeff::kIllegal:
return wgpu::BlendFactor::Zero;
}
SkUNREACHABLE;
#undef VALUE_IF_DSB_OR_ZERO
}
static wgpu::BlendFactor blend_coeff_to_dawn_blend_for_alpha(const DawnCaps& caps,
skgpu::BlendCoeff coeff) {
switch (coeff) {
// Force all srcColor used in alpha slot to alpha version.
case skgpu::BlendCoeff::kSC:
return wgpu::BlendFactor::SrcAlpha;
case skgpu::BlendCoeff::kISC:
return wgpu::BlendFactor::OneMinusSrcAlpha;
case skgpu::BlendCoeff::kDC:
return wgpu::BlendFactor::DstAlpha;
case skgpu::BlendCoeff::kIDC:
return wgpu::BlendFactor::OneMinusDstAlpha;
default:
return blend_coeff_to_dawn_blend(caps, coeff);
}
}
// TODO: share this w/ Ganesh Metal backend?
static wgpu::BlendOperation blend_equation_to_dawn_blend_op(skgpu::BlendEquation equation) {
static const wgpu::BlendOperation gTable[] = {
wgpu::BlendOperation::Add, // skgpu::BlendEquation::kAdd
wgpu::BlendOperation::Subtract, // skgpu::BlendEquation::kSubtract
wgpu::BlendOperation::ReverseSubtract, // skgpu::BlendEquation::kReverseSubtract
};
static_assert(std::size(gTable) == (int)skgpu::BlendEquation::kFirstAdvanced);
static_assert(0 == (int)skgpu::BlendEquation::kAdd);
static_assert(1 == (int)skgpu::BlendEquation::kSubtract);
static_assert(2 == (int)skgpu::BlendEquation::kReverseSubtract);
SkASSERT((unsigned)equation < skgpu::kBlendEquationCnt);
return gTable[(int)equation];
}
struct AsyncPipelineCreationBase {
AsyncPipelineCreationBase(const UniqueKey& key) : fKey(key) {}
wgpu::RenderPipeline fRenderPipeline;
std::atomic<bool> fFinished = false;
UniqueKey fKey; // for logging the wait to resolve a Pipeline future in dawnRenderPipeline
#if SK_HISTOGRAMS_ENABLED
// We need these three for the Graphite.PipelineCreationTimes.* histograms (cf.
// log_pipeline_creation)
skgpu::StdSteadyClock::time_point fStartTime;
bool fFromPrecompile;
bool fAsynchronous = false;
#endif
};
void log_pipeline_creation(const AsyncPipelineCreationBase* apcb) {
#if SK_HISTOGRAMS_ENABLED
[[maybe_unused]] static constexpr int kBucketCount = 100;
[[maybe_unused]] static constexpr int kOneSecInUS = 1000000;
SkASSERT(apcb->fFinished);
if (!apcb->fRenderPipeline) {
// A null fRenderPipeline means Pipeline creation failed
return; // TODO: log failures to their own UMA stat
}
[[maybe_unused]] auto micros_since = [](skgpu::StdSteadyClock::time_point start) {
skgpu::StdSteadyClock::duration elapsed = skgpu::StdSteadyClock::now() - start;
return std::chrono::duration_cast<std::chrono::microseconds>(elapsed).count();
};
if (apcb->fFromPrecompile) {
SkASSERT(!apcb->fAsynchronous); // precompile is done synchronously on a thread
SK_HISTOGRAM_CUSTOM_MICROSECONDS_TIMES(
"Graphite.PipelineCreationTimes.Precompile",
micros_since(apcb->fStartTime),
/* minUSec= */ 1,
/* maxUSec= */ kOneSecInUS,
kBucketCount);
} else if (apcb->fAsynchronous) {
SK_HISTOGRAM_CUSTOM_MICROSECONDS_TIMES(
"Graphite.PipelineCreationTimes.Asynchronous",
micros_since(apcb->fStartTime),
/* minUSec= */ 1,
/* maxUSec= */ kOneSecInUS,
kBucketCount);
} else {
SK_HISTOGRAM_CUSTOM_MICROSECONDS_TIMES(
"Graphite.PipelineCreationTimes.Synchronous",
micros_since(apcb->fStartTime),
/* minUSec= */ 1,
/* maxUSec= */ kOneSecInUS,
kBucketCount);
}
#endif // SK_HISTOGRAMS_ENABLED
}
} // anonymous namespace
#if defined(__EMSCRIPTEN__)
// For wasm, we don't use async compilation.
struct DawnGraphicsPipeline::AsyncPipelineCreation : public AsyncPipelineCreationBase {
AsyncPipelineCreation(const UniqueKey& key) : AsyncPipelineCreationBase(key) {}
};
#else
struct DawnGraphicsPipeline::AsyncPipelineCreation : public AsyncPipelineCreationBase {
AsyncPipelineCreation(const UniqueKey& key) : AsyncPipelineCreationBase(key) {}
wgpu::Future fFuture;
};
#endif
// static
sk_sp<DawnGraphicsPipeline> DawnGraphicsPipeline::Make(
const DawnSharedContext* sharedContext,
DawnResourceProvider* resourceProvider,
const RuntimeEffectDictionary* runtimeDict,
const UniqueKey& pipelineKey,
const GraphicsPipelineDesc& pipelineDesc,
const RenderPassDesc& renderPassDesc,
SkEnumBitMask<PipelineCreationFlags> pipelineCreationFlags,
uint32_t compilationID) {
const DawnCaps& caps = *static_cast<const DawnCaps*>(sharedContext->caps());
const auto& device = sharedContext->device();
SkSL::Program::Interface vsInterface, fsInterface;
SkSL::ProgramSettings settings;
settings.fSharpenTextures = true;
settings.fForceNoRTFlip = true;
ShaderErrorHandler* errorHandler = caps.shaderErrorHandler();
const RenderStep* step = sharedContext->rendererProvider()->lookup(pipelineDesc.renderStepID());
const bool useStorageBuffers = caps.storageBufferSupport();
SkSL::NativeShader vsCode, fsCode;
wgpu::ShaderModule fsModule, vsModule;
// Some steps just render depth buffer but not color buffer, so the fragment
// shader is null.
UniquePaintParamsID paintID = pipelineDesc.paintParamsID();
skia_private::TArray<SamplerDesc>* samplerDescArrPtr = nullptr;
#if !defined(__EMSCRIPTEN__)
skia_private::TArray<SamplerDesc> samplerDescArr {};
samplerDescArrPtr = &samplerDescArr;
#endif
std::unique_ptr<ShaderInfo> shaderInfo =
ShaderInfo::Make(&caps,
sharedContext->shaderCodeDictionary(),
runtimeDict,
step,
paintID,
useStorageBuffers,
renderPassDesc.fColorAttachment.fFormat,
renderPassDesc.fWriteSwizzle,
renderPassDesc.fDstReadStrategy,
samplerDescArrPtr);
const std::string& fsSkSL = shaderInfo->fragmentSkSL();
const BlendInfo& blendInfo = shaderInfo->blendInfo();
const int numTexturesAndSamplers = shaderInfo->numFragmentTexturesAndSamplers();
const bool hasFragmentSkSL = !fsSkSL.empty();
if (hasFragmentSkSL) {
if (!skgpu::SkSLToWGSL(caps.shaderCaps(),
fsSkSL,
SkSL::ProgramKind::kGraphiteFragment,
settings,
&fsCode,
&fsInterface,
errorHandler)) {
return {};
}
if (!DawnCompileWGSLShaderModule(sharedContext, shaderInfo->fsLabel().c_str(), fsCode,
&fsModule, errorHandler)) {
return {};
}
}
const std::string& vsSkSL = shaderInfo->vertexSkSL();
if (!skgpu::SkSLToWGSL(caps.shaderCaps(),
vsSkSL,
SkSL::ProgramKind::kGraphiteVertex,
settings,
&vsCode,
&vsInterface,
errorHandler)) {
return {};
}
if (!DawnCompileWGSLShaderModule(sharedContext, shaderInfo->vsLabel().c_str(), vsCode,
&vsModule, errorHandler)) {
return {};
}
std::string pipelineLabel =
GetPipelineLabel(sharedContext->shaderCodeDictionary(), renderPassDesc, step, paintID);
wgpu::RenderPipelineDescriptor descriptor;
// Always set the label for pipelines, dawn may need it for tracing.
descriptor.label = pipelineLabel.c_str();
// Fragment state
skgpu::BlendEquation equation = blendInfo.fEquation;
skgpu::BlendCoeff srcCoeff = blendInfo.fSrcBlend;
skgpu::BlendCoeff dstCoeff = blendInfo.fDstBlend;
bool blendOn = !skgpu::BlendShouldDisable(equation, srcCoeff, dstCoeff);
wgpu::BlendState blend;
if (blendOn) {
blend.color.operation = blend_equation_to_dawn_blend_op(equation);
blend.color.srcFactor = blend_coeff_to_dawn_blend(caps, srcCoeff);
blend.color.dstFactor = blend_coeff_to_dawn_blend(caps, dstCoeff);
blend.alpha.operation = blend_equation_to_dawn_blend_op(equation);
blend.alpha.srcFactor = blend_coeff_to_dawn_blend_for_alpha(caps, srcCoeff);
blend.alpha.dstFactor = blend_coeff_to_dawn_blend_for_alpha(caps, dstCoeff);
}
wgpu::ColorTargetState colorTarget;
colorTarget.format = TextureFormatToDawnFormat(renderPassDesc.fColorAttachment.fFormat);
colorTarget.blend = blendOn ? &blend : nullptr;
colorTarget.writeMask = blendInfo.fWritesColor && hasFragmentSkSL ? wgpu::ColorWriteMask::All
: wgpu::ColorWriteMask::None;
#if !defined(__EMSCRIPTEN__)
const bool loadMsaaFromResolve =
renderPassDesc.fColorResolveAttachment.fFormat != TextureFormat::kUnsupported &&
renderPassDesc.fColorResolveAttachment.fLoadOp == LoadOp::kLoad;
// Special case: a render pass loading resolve texture requires additional settings for the
// pipeline to make it compatible.
wgpu::ColorTargetStateExpandResolveTextureDawn pipelineMSAALoadResolveTextureDesc;
if (loadMsaaFromResolve && sharedContext->dawnCaps()->loadOpAffectsMSAAPipelines()) {
SkASSERT(device.HasFeature(wgpu::FeatureName::DawnLoadResolveTexture));
colorTarget.nextInChain = &pipelineMSAALoadResolveTextureDesc;
pipelineMSAALoadResolveTextureDesc.enabled = true;
}
#endif
wgpu::FragmentState fragment;
// Dawn doesn't allow having a color attachment but without fragment shader, so have to use a
// noop fragment shader, if fragment shader is null.
fragment.module = hasFragmentSkSL ? std::move(fsModule) : sharedContext->noopFragment();
fragment.entryPoint = "main";
fragment.constantCount = 0;
fragment.constants = nullptr;
fragment.targetCount = 1;
fragment.targets = &colorTarget;
descriptor.fragment = &fragment;
// Depth stencil state
const auto& depthStencilSettings = step->depthStencilSettings();
SkASSERT(depthStencilSettings.fDepthTestEnabled ||
depthStencilSettings.fDepthCompareOp == CompareOp::kAlways);
TextureFormat dsFormat = renderPassDesc.fDepthStencilAttachment.fFormat;
wgpu::DepthStencilState depthStencil;
if (dsFormat != TextureFormat::kUnsupported) {
SkASSERT(TextureFormatIsDepthOrStencil(dsFormat));
depthStencil.format = TextureFormatToDawnFormat(dsFormat);
if (depthStencilSettings.fDepthTestEnabled) {
depthStencil.depthWriteEnabled = depthStencilSettings.fDepthWriteEnabled;
}
depthStencil.depthCompare = compare_op_to_dawn(depthStencilSettings.fDepthCompareOp);
// Dawn validation fails if the stencil state is non-default and the
// format doesn't have the stencil aspect.
if (TextureFormatHasStencil(dsFormat) && depthStencilSettings.fStencilTestEnabled) {
depthStencil.stencilFront = stencil_face_to_dawn(depthStencilSettings.fFrontStencil);
depthStencil.stencilBack = stencil_face_to_dawn(depthStencilSettings.fBackStencil);
depthStencil.stencilReadMask = depthStencilSettings.fFrontStencil.fReadMask;
depthStencil.stencilWriteMask = depthStencilSettings.fFrontStencil.fWriteMask;
}
descriptor.depthStencil = &depthStencil;
}
// Determine the BindGroupLayouts that will be used to make up the pipeline layout.
BindGroupLayouts groupLayouts;
// If immutable samplers are used with this pipeline, they must be included in the pipeline
// layout and passed in to the pipline constructor for lifetime management.
skia_private::TArray<sk_sp<DawnSampler>> immutableSamplers;
{
SkASSERT(resourceProvider);
groupLayouts[0] = sharedContext->getUniformBuffersBindGroupLayout();
if (!groupLayouts[0]) {
return {};
}
bool hasFragmentSamplers = hasFragmentSkSL && numTexturesAndSamplers > 0;
if (hasFragmentSamplers) {
// Check if we can optimize for the common case of a single texture + 1 dynamic sampler
if (numTexturesAndSamplers == 2 &&
!(samplerDescArrPtr && samplerDescArrPtr->at(0).isImmutable())) {
groupLayouts[1] = sharedContext->getSingleTextureSamplerBindGroupLayout();
} else {
std::vector<wgpu::BindGroupLayoutEntry> entries(numTexturesAndSamplers);
#if !defined(__EMSCRIPTEN__)
// Static sampler layouts are passed into Dawn by address and therefore must stay
// alive until the BindGroupLayoutDescriptor is created. So, store them outside of
// the loop that iterates over each BindGroupLayoutEntry.
skia_private::TArray<wgpu::StaticSamplerBindingLayout> staticSamplerLayouts;
// Note that the number of samplers is equivalent to numTexturesAndSamplers / 2. So,
// a sampler's index within any container that only pertains to sampler information
// (as in, excludes textures) is equivalent to 1/2 of that sampler's binding index
// within the BindGroupLayout. Assert that we have analyzed the appropriate number
// of samplers by equating samplerDescArr size to sampler quantity.
SkASSERT(samplerDescArrPtr && samplerDescArr.size() == numTexturesAndSamplers / 2);
immutableSamplers.reset(samplerDescArr.size());
#endif
for (int i = 0; i < numTexturesAndSamplers;) {
entries[i].binding = i;
entries[i].visibility = wgpu::ShaderStage::Fragment;
#if !defined(__EMSCRIPTEN__)
// Index of sampler information = 1/2 of cumulative texture and sampler index.
// If we have a non-default-initialized SamplerDesc at that index,
// fetch an immutable sampler that matches that description to include in the
// pipeline layout.
const SamplerDesc& samplerDesc = samplerDescArr.at(i/2);
if (samplerDesc.isImmutable()) {
sk_sp<Sampler> immutableSampler =
resourceProvider->findOrCreateCompatibleSampler(samplerDesc);
if (!immutableSampler) {
SKGPU_LOG_E("Failed to find/create immutable sampler for pipeline");
return {};
}
sk_sp<DawnSampler> dawnImmutableSampler = sk_ref_sp<DawnSampler>(
static_cast<DawnSampler*>(immutableSampler.get()));
SkASSERT(dawnImmutableSampler);
wgpu::StaticSamplerBindingLayout& immutableSamplerBinding =
staticSamplerLayouts.emplace_back();
immutableSamplerBinding.sampler = dawnImmutableSampler->dawnSampler();
// Static samplers sample from the subsequent texture in the BindGroupLayout
immutableSamplerBinding.sampledTextureBinding = i + 1;
immutableSamplers[i/2] = std::move(dawnImmutableSampler);
entries[i].nextInChain = &immutableSamplerBinding;
} else {
#endif
entries[i].sampler.type = wgpu::SamplerBindingType::Filtering;
#if !defined(__EMSCRIPTEN__)
}
#endif
++i;
entries[i].binding = i;
entries[i].visibility = wgpu::ShaderStage::Fragment;
entries[i].texture.sampleType = wgpu::TextureSampleType::Float;
entries[i].texture.viewDimension = wgpu::TextureViewDimension::e2D;
entries[i].texture.multisampled = false;
++i;
}
wgpu::BindGroupLayoutDescriptor groupLayoutDesc;
if (sharedContext->caps()->setBackendLabels()) {
groupLayoutDesc.label = shaderInfo->vsLabel().c_str();
}
groupLayoutDesc.entryCount = entries.size();
groupLayoutDesc.entries = entries.data();
groupLayouts[1] = device.CreateBindGroupLayout(&groupLayoutDesc);
}
if (!groupLayouts[1]) {
return {};
}
}
wgpu::PipelineLayoutDescriptor layoutDesc;
if (sharedContext->caps()->setBackendLabels()) {
layoutDesc.label = shaderInfo->fsLabel().c_str();
}
layoutDesc.bindGroupLayoutCount =
hasFragmentSamplers ? groupLayouts.size() : groupLayouts.size() - 1;
layoutDesc.bindGroupLayouts = groupLayouts.data();
#if !defined(__EMSCRIPTEN__)
if (sharedContext->caps()
->resourceBindingRequirements()
.fUsePushConstantsForIntrinsicConstants) {
layoutDesc.immediateSize = kIntrinsicUniformSize;
}
#endif
auto layout = device.CreatePipelineLayout(&layoutDesc);
if (!layout) {
return {};
}
descriptor.layout = std::move(layout);
}
// Vertex state
std::array<wgpu::VertexBufferLayout, kNumVertexBuffers> vertexBufferLayouts;
// Static data buffer layout
std::vector<wgpu::VertexAttribute> staticDataAttributes;
{
auto arrayStride = create_vertex_attributes(step->staticAttributes(),
0,
&staticDataAttributes);
auto& layout = vertexBufferLayouts[kStaticDataBufferIndex];
if (arrayStride) {
layout.arrayStride = arrayStride;
layout.stepMode = wgpu::VertexStepMode::Vertex;
layout.attributeCount = staticDataAttributes.size();
layout.attributes = staticDataAttributes.data();
} else {
layout.arrayStride = 0;
#if defined(__EMSCRIPTEN__)
layout.stepMode = wgpu::VertexStepMode::VertexBufferNotUsed;
#else
layout.stepMode = wgpu::VertexStepMode::Undefined;
#endif
layout.attributeCount = 0;
layout.attributes = nullptr;
}
}
// Append data buffer layout
std::vector<wgpu::VertexAttribute> appendDataAttributes;
{
// Note: the shaderLocationOffset in this function call needs to be the staticAttributeSize
auto arrayStride = create_vertex_attributes(step->appendAttributes(),
step->staticAttributes().size(),
&appendDataAttributes);
auto& layout = vertexBufferLayouts[kAppendDataBufferIndex];
if (arrayStride) {
layout.arrayStride = arrayStride;
layout.stepMode = step->appendsVertices() ? wgpu::VertexStepMode::Vertex :
wgpu::VertexStepMode::Instance;
layout.attributeCount = appendDataAttributes.size();
layout.attributes = appendDataAttributes.data();
} else {
layout.arrayStride = 0;
#if defined(__EMSCRIPTEN__)
layout.stepMode = wgpu::VertexStepMode::VertexBufferNotUsed;
#else
layout.stepMode = wgpu::VertexStepMode::Undefined;
#endif
layout.attributeCount = 0;
layout.attributes = nullptr;
}
}
auto& vertex = descriptor.vertex;
vertex.module = std::move(vsModule);
vertex.entryPoint = "main";
vertex.constantCount = 0;
vertex.constants = nullptr;
vertex.bufferCount = vertexBufferLayouts.size();
vertex.buffers = vertexBufferLayouts.data();
// Other state
descriptor.primitive.frontFace = wgpu::FrontFace::CCW;
descriptor.primitive.cullMode = wgpu::CullMode::None;
switch (step->primitiveType()) {
case PrimitiveType::kTriangles:
descriptor.primitive.topology = wgpu::PrimitiveTopology::TriangleList;
break;
case PrimitiveType::kTriangleStrip:
descriptor.primitive.topology = wgpu::PrimitiveTopology::TriangleStrip;
descriptor.primitive.stripIndexFormat = wgpu::IndexFormat::Uint16;
break;
case PrimitiveType::kPoints:
descriptor.primitive.topology = wgpu::PrimitiveTopology::PointList;
break;
}
// Multisampled state
descriptor.multisample.count = renderPassDesc.fSampleCount;
descriptor.multisample.mask = 0xFFFFFFFF;
descriptor.multisample.alphaToCoverageEnabled = false;
const bool forPrecompilation =
SkToBool(pipelineCreationFlags & PipelineCreationFlags::kForPrecompilation);
// For Dawn, we want Precompilation to happen synchronously
const bool useAsync = caps.useAsyncPipelineCreation() && !forPrecompilation;
auto asyncCreation = std::make_unique<AsyncPipelineCreation>(pipelineKey);
#if SK_HISTOGRAMS_ENABLED
asyncCreation->fStartTime = skgpu::StdSteadyClock::now();
asyncCreation->fFromPrecompile = forPrecompilation;
asyncCreation->fAsynchronous = useAsync;
#endif
if (useAsync) {
#if defined(__EMSCRIPTEN__)
// We shouldn't use CreateRenderPipelineAsync in wasm.
SKGPU_LOG_F("CreateRenderPipelineAsync shouldn't be used in WASM");
#else
asyncCreation->fFuture = device.CreateRenderPipelineAsync(
&descriptor,
wgpu::CallbackMode::WaitAnyOnly,
[asyncCreationPtr = asyncCreation.get()](wgpu::CreatePipelineAsyncStatus status,
wgpu::RenderPipeline pipeline,
wgpu::StringView message) {
if (status != wgpu::CreatePipelineAsyncStatus::Success) {
SKGPU_LOG_E("Failed to create render pipeline (%d): %.*s",
static_cast<int>(status),
static_cast<int>(message.length),
message.data);
// invalidate AsyncPipelineCreation pointer to signal that this pipeline has
// failed.
asyncCreationPtr->fRenderPipeline = nullptr;
} else {
asyncCreationPtr->fRenderPipeline = std::move(pipeline);
}
asyncCreationPtr->fFinished = true;
log_pipeline_creation(asyncCreationPtr);
});
#endif
} else {
std::optional<DawnErrorChecker> errorChecker;
if (sharedContext->dawnCaps()->allowScopedErrorChecks()) {
errorChecker.emplace(sharedContext);
}
asyncCreation->fRenderPipeline = device.CreateRenderPipeline(&descriptor);
asyncCreation->fFinished = true;
if (errorChecker.has_value() && errorChecker->popErrorScopes() != DawnErrorType::kNoError) {
asyncCreation->fRenderPipeline = nullptr;
}
// Fail ASAP for synchronous pipeline creation so it affects the Recording snap instead of
// being detected later at insertRecording().
if (!asyncCreation->fRenderPipeline) {
return nullptr;
}
log_pipeline_creation(asyncCreation.get());
}
PipelineInfo pipelineInfo{ *shaderInfo, pipelineCreationFlags,
pipelineKey.hash(), compilationID };
#if defined(GPU_TEST_UTILS)
pipelineInfo.fNativeVertexShader = std::move(vsCode.fText);
pipelineInfo.fNativeFragmentShader = std::move(fsCode.fText);
#endif
return sk_sp<DawnGraphicsPipeline>(
new DawnGraphicsPipeline(sharedContext,
pipelineInfo,
std::move(asyncCreation),
std::move(groupLayouts),
step->primitiveType(),
depthStencilSettings.fStencilReferenceValue,
std::move(immutableSamplers)));
}
DawnGraphicsPipeline::DawnGraphicsPipeline(
const skgpu::graphite::SharedContext* sharedContext,
const PipelineInfo& pipelineInfo,
std::unique_ptr<AsyncPipelineCreation> asyncCreationInfo,
BindGroupLayouts groupLayouts,
PrimitiveType primitiveType,
uint32_t refValue,
skia_private::TArray<sk_sp<DawnSampler>> immutableSamplers)
: GraphicsPipeline(sharedContext, pipelineInfo)
, fAsyncPipelineCreation(std::move(asyncCreationInfo))
, fGroupLayouts(std::move(groupLayouts))
, fPrimitiveType(primitiveType)
, fStencilReferenceValue(refValue)
, fImmutableSamplers(std::move(immutableSamplers)) {}
DawnGraphicsPipeline::~DawnGraphicsPipeline() {
this->freeGpuData();
}
void DawnGraphicsPipeline::freeGpuData() {
// Wait for async creation to finish before we can destroy this object.
(void)this->dawnRenderPipeline();
fAsyncPipelineCreation = nullptr;
}
bool DawnGraphicsPipeline::didAsyncCompilationFail() const {
return fAsyncPipelineCreation &&
fAsyncPipelineCreation->fFinished &&
!fAsyncPipelineCreation->fRenderPipeline;
}
const wgpu::RenderPipeline& DawnGraphicsPipeline::dawnRenderPipeline() const {
if (!fAsyncPipelineCreation) {
static const wgpu::RenderPipeline kNullPipeline = nullptr;
return kNullPipeline;
}
if (fAsyncPipelineCreation->fFinished) {
return fAsyncPipelineCreation->fRenderPipeline;
}
#if defined(__EMSCRIPTEN__)
// We shouldn't use CreateRenderPipelineAsync in wasm.
SKGPU_LOG_F("CreateRenderPipelineAsync shouldn't be used in WASM");
#else
#if defined(SK_PIPELINE_LIFETIME_LOGGING)
TRACE_EVENT_INSTANT2("skia.gpu",
TRACE_STR_STATIC("WaitBeginN"),
TRACE_EVENT_SCOPE_THREAD,
"key", fAsyncPipelineCreation->fKey.hash(),
"compilationID", this->getPipelineInfo().fCompilationID);
#endif
wgpu::FutureWaitInfo waitInfo{};
waitInfo.future = fAsyncPipelineCreation->fFuture;
const auto& instance = static_cast<const DawnSharedContext*>(sharedContext())->instance();
[[maybe_unused]] auto status =
instance.WaitAny(1, &waitInfo, /*timeoutNS=*/std::numeric_limits<uint64_t>::max());
SkASSERT(status == wgpu::WaitStatus::Success);
SkASSERT(waitInfo.completed);
#if defined(SK_PIPELINE_LIFETIME_LOGGING)
TRACE_EVENT_INSTANT2("skia.gpu",
TRACE_STR_STATIC("WaitEndN"),
TRACE_EVENT_SCOPE_THREAD,
"key", fAsyncPipelineCreation->fKey.hash(),
"compilationID", this->getPipelineInfo().fCompilationID);
#endif
#endif
return fAsyncPipelineCreation->fRenderPipeline;
}
} // namespace skgpu::graphite