blob: 43ce76cc6fb3728f9b3251ede0eaedd569c7d747 [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/private/base/SkTemplates.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/UniformManager.h"
#include "src/gpu/graphite/dawn/DawnCaps.h"
#include "src/gpu/graphite/dawn/DawnErrorChecker.h"
#include "src/gpu/graphite/dawn/DawnGraphiteTypesPriv.h"
#include "src/gpu/graphite/dawn/DawnGraphiteUtilsPriv.h"
#include "src/gpu/graphite/dawn/DawnResourceProvider.h"
#include "src/gpu/graphite/dawn/DawnSharedContext.h"
#include "src/gpu/graphite/dawn/DawnUtilsPriv.h"
#include "src/sksl/SkSLProgramSettings.h"
#include "src/sksl/SkSLUtil.h"
#include "src/sksl/ir/SkSLProgram.h"
#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 {
wgpu::RenderPipeline fRenderPipeline;
bool fFinished = false;
};
} // anonymous namespace
#if defined(__EMSCRIPTEN__)
// For wasm, we don't use async compilation.
struct DawnGraphicsPipeline::AsyncPipelineCreation : public AsyncPipelineCreationBase {};
#else
struct DawnGraphicsPipeline::AsyncPipelineCreation : public AsyncPipelineCreationBase {
wgpu::Future fFuture;
};
#endif
#if !defined(__EMSCRIPTEN__)
using namespace ycbcrUtils;
// Fetches any immutable samplers and accumulates them into outImmutableSamplers. Returns false
// if there is a failure that triggers us to fail a draw. Acts as a no-op and returns true if a
// shader doesn't store any data (meaning immutable samplers are never used with the given shader).
bool gather_immutable_samplers(const SkSpan<uint32_t> samplerData,
DawnResourceProvider* resourceProvider,
skia_private::AutoTArray<sk_sp<DawnSampler>>& outImmutableSamplers) {
if (samplerData.empty()) {
return true;
}
// The quantity of int32s needed to represent immutable sampler data varies, so handle
// incrementing i within the loop. Sampler data can be anywhere from 1-3 uint32s depending upon
// whether a sampler is immutable or dynamic and whether it uses a known or external format.
// Since sampler data size can vary per-sampler, also track sampler count for indexing into
// outImmutableSamplers.
size_t samplerIdx = 0;
for (size_t i = 0; i < samplerData.size();) {
// A first sampler value of 0 indicates that an image shader uses no immutable samplers.
// Thus, we can push a nullptr on to immutableSamplers and continue iterating.
if (samplerData[i] == 0) {
i++;
samplerIdx++;
continue;
}
// If the data is non-zero, that means we are using an immutable sampler. Check
// whether it uses a known or external format to determine how many uint32s
// (samplerDataLength) we must consult to obtain all the data necessary to
// query the resource provider for a real sampler.
uint32_t immutableSamplerInfo = samplerData[i] >> SamplerDesc::kImmutableSamplerInfoShift;
SkASSERT(immutableSamplerInfo != 0);
bool usesExternalFormat =
static_cast<bool>(immutableSamplerInfo & ycbcrUtils::kUseExternalFormatMask);
const int samplerDataLength = usesExternalFormat ? SamplerDesc::kInt32sNeededExternalFormat
: SamplerDesc::kInt32sNeededKnownFormat;
// Gather samplerDataLength int32s from the data span. Use that to populate a
// SamplerDesc which enables us to query the resource provider for a real sampler.
SamplerDesc samplerDesc;
memcpy(&samplerDesc, samplerData.begin() + i, samplerDataLength * sizeof(uint32_t));
sk_sp<Sampler> immutableSampler =
resourceProvider->findOrCreateCompatibleSampler(samplerDesc);
if (!immutableSampler) {
SKGPU_LOG_E("Failed to find or create immutable sampler for pipeline");
return false;
}
sk_sp<DawnSampler> dawnImmutableSampler =
sk_ref_sp<DawnSampler>(static_cast<DawnSampler*>(immutableSampler.get()));
SkASSERT(dawnImmutableSampler);
outImmutableSamplers[samplerIdx++] = std::move(dawnImmutableSampler);
i += samplerDataLength;
}
// TODO(b/366220690): Once the root cause of b/366220690 is fixed, assert that we traverse the
// correct number of samplers (samplerIdx == outImmutableSamplers.size()).
return true;
}
#endif
// static
sk_sp<DawnGraphicsPipeline> DawnGraphicsPipeline::Make(const DawnSharedContext* sharedContext,
DawnResourceProvider* resourceProvider,
const RuntimeEffectDictionary* runtimeDict,
const GraphicsPipelineDesc& pipelineDesc,
const RenderPassDesc& renderPassDesc) {
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();
std::string 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();
FragSkSLInfo fsSkSLInfo = BuildFragmentSkSL(&caps,
sharedContext->shaderCodeDictionary(),
runtimeDict,
step,
paintID,
useStorageBuffers,
renderPassDesc.fWriteSwizzle);
std::string& fsSkSL = fsSkSLInfo.fSkSL;
const BlendInfo& blendInfo = fsSkSLInfo.fBlendInfo;
const bool localCoordsNeeded = fsSkSLInfo.fRequiresLocalCoords;
const int numTexturesAndSamplers = fsSkSLInfo.fNumTexturesAndSamplers;
bool hasFragmentSkSL = !fsSkSL.empty();
if (hasFragmentSkSL) {
if (!skgpu::SkSLToWGSL(caps.shaderCaps(),
fsSkSL,
SkSL::ProgramKind::kGraphiteFragment,
settings,
&fsCode,
&fsInterface,
errorHandler)) {
return {};
}
if (!DawnCompileWGSLShaderModule(sharedContext, fsSkSLInfo.fLabel.c_str(), fsCode,
&fsModule, errorHandler)) {
return {};
}
}
VertSkSLInfo vsSkSLInfo = BuildVertexSkSL(caps.resourceBindingRequirements(),
step,
useStorageBuffers,
localCoordsNeeded);
const std::string& vsSkSL = vsSkSLInfo.fSkSL;
if (!skgpu::SkSLToWGSL(caps.shaderCaps(),
vsSkSL,
SkSL::ProgramKind::kGraphiteVertex,
settings,
&vsCode,
&vsInterface,
errorHandler)) {
return {};
}
if (!DawnCompileWGSLShaderModule(sharedContext, vsSkSLInfo.fLabel.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 =
TextureInfos::GetDawnViewFormat(renderPassDesc.fColorAttachment.fTextureInfo);
colorTarget.blend = blendOn ? &blend : nullptr;
colorTarget.writeMask = blendInfo.fWritesColor && hasFragmentSkSL ? wgpu::ColorWriteMask::All
: wgpu::ColorWriteMask::None;
#if !defined(__EMSCRIPTEN__)
const bool loadMsaaFromResolve =
renderPassDesc.fColorResolveAttachment.fTextureInfo.isValid() &&
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()->resolveTextureLoadOp().has_value()) {
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.targetCount = 1;
fragment.targets = &colorTarget;
descriptor.fragment = &fragment;
// Depth stencil state
const auto& depthStencilSettings = step->depthStencilSettings();
SkASSERT(depthStencilSettings.fDepthTestEnabled ||
depthStencilSettings.fDepthCompareOp == CompareOp::kAlways);
wgpu::DepthStencilState depthStencil;
if (renderPassDesc.fDepthStencilAttachment.fTextureInfo.isValid()) {
wgpu::TextureFormat dsFormat = TextureInfos::GetDawnViewFormat(
renderPassDesc.fDepthStencilAttachment.fTextureInfo);
depthStencil.format =
DawnFormatIsDepthOrStencil(dsFormat) ? dsFormat : wgpu::TextureFormat::Undefined;
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 (DawnFormatIsStencil(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;
// The quantity of samplers = 1/2 the cumulative number of textures AND samplers. The count
// reported by the generated SkSL already includes any texture/sampler required for dst reads
// via texture copy so no additional logic is needed when preparing the BindGroupLayout.
const int numSamplers = numTexturesAndSamplers / 2;
// Determine and store any immutable samplers to be included in the pipeline layout. A sampler's
// binding index can be determined by multiplying its index within the immutableSamplers array
// by 2. Initialize all values to the default of nullptr, which acts as a spacer to indicate the
// usage of a "regular" dynamic sampler.
skia_private::AutoTArray<sk_sp<DawnSampler>> immutableSamplers(numSamplers);
{
SkASSERT(resourceProvider);
groupLayouts[0] = resourceProvider->getOrCreateUniformBuffersBindGroupLayout();
if (!groupLayouts[0]) {
return {};
}
bool hasFragmentSamplers = hasFragmentSkSL && numTexturesAndSamplers > 0;
if (hasFragmentSamplers) {
#if !defined(__EMSCRIPTEN__)
// fsSkSLInfo.fData contains SamplerDesc information of any immutable samplers used by
// this pipeline. Note that, for now, all data within fsSkSLInfo.fData is known to be
// SamplerDesc info of immutable samplers represented as uint32s. However, other
// snippets may one day utilize this fData to represent some other struct or info.
// b/347072931 tracks the effort to tie data to snippetIDs which would inform us of the
// expected data type.
if (!gather_immutable_samplers(
{fsSkSLInfo.fData}, resourceProvider, immutableSamplers)) {
return {};
}
#endif
// Optimize for the common case of a single texture + 1 dynamic sampler.
if (numTexturesAndSamplers == 2 && !immutableSamplers[0]) {
groupLayouts[1] =
resourceProvider->getOrCreateSingleTextureSamplerBindGroupLayout();
} else {
using BindGroupLayoutEntryList = std::vector<wgpu::BindGroupLayoutEntry>;
BindGroupLayoutEntryList entries(numTexturesAndSamplers);
#if !defined(__EMSCRIPTEN__)
// Static sampler layouts are passed in by address and therefore must stay valid
// until the BindGroupLayoutDescriptor is created, so store them outside of
// the loop that iterates over each BindGroupLayoutEntry.
std::vector<wgpu::StaticSamplerBindingLayout> staticSamplerLayouts;
#endif
for (int i = 0; i < numTexturesAndSamplers;) {
entries[i].binding = i;
entries[i].visibility = wgpu::ShaderStage::Fragment;
#if !defined(__EMSCRIPTEN__)
// When it's possible to use static samplers, check to see if we are using one
// for this entry. If so, add the sampler to the BindGroupLayoutEntry. Note that
// a sampler's index in immutableSamplers is equivalent to half of an entry's
// index within the entries container.
if (immutableSamplers[i / 2]) {
wgpu::StaticSamplerBindingLayout& immutableSamplerBinding =
staticSamplerLayouts.emplace_back();
immutableSamplerBinding.sampler = immutableSamplers[i / 2]->dawnSampler();
immutableSamplerBinding.sampledTextureBinding = i + 1;
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 = vsSkSLInfo.fLabel.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 = fsSkSLInfo.fLabel.c_str();
}
layoutDesc.bindGroupLayoutCount =
hasFragmentSamplers ? groupLayouts.size() : groupLayouts.size() - 1;
layoutDesc.bindGroupLayouts = groupLayouts.data();
auto layout = device.CreatePipelineLayout(&layoutDesc);
if (!layout) {
return {};
}
descriptor.layout = std::move(layout);
}
// Vertex state
std::array<wgpu::VertexBufferLayout, kNumVertexBuffers> vertexBufferLayouts;
// Vertex buffer layout
std::vector<wgpu::VertexAttribute> vertexAttributes;
{
auto arrayStride = create_vertex_attributes(step->vertexAttributes(),
0,
&vertexAttributes);
auto& layout = vertexBufferLayouts[kVertexBufferIndex];
if (arrayStride) {
layout.arrayStride = arrayStride;
layout.stepMode = wgpu::VertexStepMode::Vertex;
layout.attributeCount = vertexAttributes.size();
layout.attributes = vertexAttributes.data();
} else {
layout.arrayStride = 0;
layout.stepMode = wgpu::VertexStepMode::VertexBufferNotUsed;
layout.attributeCount = 0;
layout.attributes = nullptr;
}
}
// Instance buffer layout
std::vector<wgpu::VertexAttribute> instanceAttributes;
{
auto arrayStride = create_vertex_attributes(step->instanceAttributes(),
step->vertexAttributes().size(),
&instanceAttributes);
auto& layout = vertexBufferLayouts[kInstanceBufferIndex];
if (arrayStride) {
layout.arrayStride = arrayStride;
layout.stepMode = wgpu::VertexStepMode::Instance;
layout.attributeCount = instanceAttributes.size();
layout.attributes = instanceAttributes.data();
} else {
layout.arrayStride = 0;
layout.stepMode = wgpu::VertexStepMode::VertexBufferNotUsed;
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;
auto asyncCreation = std::make_unique<AsyncPipelineCreation>();
if (caps.useAsyncPipelineCreation()) {
#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,
char const* message) {
if (status != wgpu::CreatePipelineAsyncStatus::Success) {
SKGPU_LOG_E("Failed to create render pipeline (%d): %s",
static_cast<int>(status),
message);
// invalidate AsyncPipelineCreation pointer to signal that this pipeline has
// failed.
asyncCreationPtr->fRenderPipeline = nullptr;
} else {
asyncCreationPtr->fRenderPipeline = std::move(pipeline);
}
asyncCreationPtr->fFinished = true;
});
#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;
}
}
PipelineInfo pipelineInfo{vsSkSLInfo, fsSkSLInfo};
#if defined(GPU_TEST_UTILS)
pipelineInfo.fNativeVertexShader = std::move(vsCode);
pipelineInfo.fNativeFragmentShader = std::move(fsCode);
#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::AutoTArray<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;
}
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
wgpu::FutureWaitInfo waitInfo{};
waitInfo.future = fAsyncPipelineCreation->fFuture;
const auto& instance = static_cast<const DawnSharedContext*>(sharedContext())
->device()
.GetAdapter()
.GetInstance();
[[maybe_unused]] auto status =
instance.WaitAny(1, &waitInfo, /*timeoutNS=*/std::numeric_limits<uint64_t>::max());
SkASSERT(status == wgpu::WaitStatus::Success);
SkASSERT(waitInfo.completed);
#endif
return fAsyncPipelineCreation->fRenderPipeline;
}
} // namespace skgpu::graphite