in src/gpu/graphite/dawn/DawnGraphicsPipeline.cpp [323:748]
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();
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();
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.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] = resourceProvider->getOrCreateUniformBuffersBindGroupLayout();
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] =
resourceProvider->getOrCreateSingleTextureSamplerBindGroupLayout();
} 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);
#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.push_back(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();
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);
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)));
}