|  | /* | 
|  | * Copyright 2024 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/RenderPassDesc.h" | 
|  |  | 
|  | #include "include/gpu/graphite/TextureInfo.h" | 
|  | #include "src/gpu/graphite/Caps.h" | 
|  | #include "src/gpu/graphite/TextureInfoPriv.h" | 
|  |  | 
|  | namespace skgpu::graphite { | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | const char* to_str(LoadOp op) { | 
|  | switch (op) { | 
|  | case LoadOp::kLoad:    return "load"; | 
|  | case LoadOp::kClear:   return "clear"; | 
|  | case LoadOp::kDiscard: return "discard"; | 
|  | } | 
|  |  | 
|  | SkUNREACHABLE; | 
|  | } | 
|  |  | 
|  | const char* to_str(StoreOp op) { | 
|  | switch (op) { | 
|  | case StoreOp::kStore:   return "store"; | 
|  | case StoreOp::kDiscard: return "discard"; | 
|  | } | 
|  |  | 
|  | SkUNREACHABLE; | 
|  | } | 
|  |  | 
|  | } // anonymous namespace | 
|  |  | 
|  | RenderPassDesc RenderPassDesc::Make(const Caps* caps, | 
|  | const TextureInfo& targetInfo, | 
|  | LoadOp loadOp, | 
|  | StoreOp storeOp, | 
|  | SkEnumBitMask<DepthStencilFlags> depthStencilFlags, | 
|  | const std::array<float, 4>& clearColor, | 
|  | bool requiresMSAA, | 
|  | Swizzle writeSwizzle, | 
|  | const DstReadStrategy dstReadStrategy) { | 
|  | // It doesn't make sense to have a storeOp for our main target not be store. Why are we doing | 
|  | // this DrawPass then | 
|  | SkASSERT(storeOp == StoreOp::kStore); | 
|  |  | 
|  | RenderPassDesc desc; | 
|  | desc.fClearColor = clearColor; | 
|  | // Depth and stencil is currently always cleared to 1.f or 0 if it's used. Depth is 1.0 and | 
|  | // counts down as painter's order increases due to HW preference for historic OpenGL defaults | 
|  | // of a fast hi-z clear value of 1.0 with depth test of lesser. | 
|  | desc.fClearDepth = 1.f; | 
|  | desc.fClearStencil = 0; | 
|  | desc.fWriteSwizzle = writeSwizzle; | 
|  | desc.fDstReadStrategy = dstReadStrategy; | 
|  |  | 
|  | TextureFormat colorFormat = TextureInfoPriv::ViewFormat(targetInfo); | 
|  | // The render pass's overall sample count will either be the target's sample count | 
|  | // (when single-sampling or already multisampled), or the default sample count (which will then | 
|  | // be either the implicit sample count for msaa-render-to-single-sample or the explicit sample | 
|  | // count of a separate color attachment). | 
|  | // | 
|  | // Higher-level logic should ensure the default MSAA sample count is supported if using either | 
|  | // msaa-render-to-single-sample or with separate attachments, and select non-MSAA techniques if | 
|  | // they weren't supported. Downgrade to single-sampled if we get here somehow anyways. | 
|  | const bool msaaRenderToSingleSampledSupport = | 
|  | caps->msaaTextureRenderToSingleSampledSupport(targetInfo); | 
|  | const uint8_t defaultSamples = caps->defaultMSAASamplesCount(); | 
|  | const bool canUseDefaultMSAA = msaaRenderToSingleSampledSupport || | 
|  | caps->isSampleCountSupported(colorFormat, defaultSamples); | 
|  | desc.fSampleCount = requiresMSAA && targetInfo.numSamples() == 1 | 
|  | ? (canUseDefaultMSAA ? defaultSamples : 1) | 
|  | : targetInfo.numSamples(); | 
|  |  | 
|  | // We need to handle MSAA with an extra color attachment if: | 
|  | const bool needsMSAAColorAttachment = | 
|  | desc.fSampleCount > 1 &&            // using MSAA for the render pass, | 
|  | targetInfo.numSamples() == 1 &&     // the target isn't already MSAA'ed, | 
|  | !msaaRenderToSingleSampledSupport;  // can't use an MSAA->single extension. | 
|  | if (needsMSAAColorAttachment) { | 
|  | // We set the color and resolve attachments up the same regardless of if the backend ends up | 
|  | // using msaaRenderToSingleSampledSupport() to skip explicitly creating the MSAA attachment. | 
|  | // The color attachment (and any depth/stencil attachment) will use `sampleCount` and the | 
|  | // resolve attachment will be single-sampled. | 
|  | desc.fColorAttachment = {colorFormat, | 
|  | loadOp != LoadOp::kClear ? LoadOp::kDiscard : LoadOp::kClear, | 
|  | StoreOp::kDiscard, | 
|  | desc.fSampleCount}; | 
|  | desc.fColorResolveAttachment = {colorFormat, | 
|  | loadOp != LoadOp::kLoad ? LoadOp::kDiscard : LoadOp::kLoad, | 
|  | storeOp, | 
|  | /*sampleCount=*/1}; | 
|  | } else { | 
|  | // The target will be the color attachment and skip configuring the resolve attachment. | 
|  | SkASSERT(desc.fColorResolveAttachment.fFormat == TextureFormat::kUnsupported); | 
|  | desc.fColorAttachment = {colorFormat, | 
|  | loadOp, | 
|  | storeOp, | 
|  | SkTo<uint8_t>(targetInfo.numSamples())}; | 
|  | } | 
|  |  | 
|  | if (depthStencilFlags != DepthStencilFlags::kNone) { | 
|  | TextureFormat dsFormat = caps->getDepthStencilFormat(depthStencilFlags); | 
|  | SkASSERT(dsFormat != TextureFormat::kUnsupported); | 
|  | // Depth and stencil values are currently always cleared and don't need to persist. | 
|  | // The sample count should always match the color attachment. | 
|  | desc.fDepthStencilAttachment = {dsFormat, | 
|  | LoadOp::kClear, | 
|  | StoreOp::kDiscard, | 
|  | desc.fColorAttachment.fSampleCount}; | 
|  | } else { | 
|  | SkASSERT(desc.fDepthStencilAttachment.fFormat == TextureFormat::kUnsupported); | 
|  | } | 
|  |  | 
|  | return desc; | 
|  | } | 
|  |  | 
|  | SkString RenderPassDesc::toString() const { | 
|  | return SkStringPrintf("RP(color: %s, resolve: %s, ds: %s, samples: %u, swizzle: %s, " | 
|  | "clear: c(%f,%f,%f,%f), d(%f), s(0x%02x), dst read: %u)", | 
|  | fColorAttachment.toString().c_str(), | 
|  | fColorResolveAttachment.toString().c_str(), | 
|  | fDepthStencilAttachment.toString().c_str(), | 
|  | fSampleCount, | 
|  | fWriteSwizzle.asString().c_str(), | 
|  | fClearColor[0], fClearColor[1], fClearColor[2], fClearColor[3], | 
|  | fClearDepth, | 
|  | fClearStencil, | 
|  | (unsigned)fDstReadStrategy); | 
|  | } | 
|  |  | 
|  | SkString RenderPassDesc::toPipelineLabel() const { | 
|  | // Given current policies, these assumptions should hold and mean the conciseness in the label | 
|  | // is still unambiguous. | 
|  | SkASSERT(fColorAttachment.fFormat != TextureFormat::kUnsupported); | 
|  | SkASSERT(fColorResolveAttachment.fFormat == TextureFormat::kUnsupported || | 
|  | fColorResolveAttachment.fFormat == fColorAttachment.fFormat); | 
|  | SkASSERT(fDepthStencilAttachment.fFormat == TextureFormat::kUnsupported || | 
|  | fDepthStencilAttachment.fSampleCount == fColorAttachment.fSampleCount); | 
|  | SkASSERT(fColorResolveAttachment.fFormat == TextureFormat::kUnsupported || | 
|  | fColorResolveAttachment.fSampleCount == 1); | 
|  | SkASSERT(fColorAttachment.fSampleCount == fSampleCount || | 
|  | (fColorAttachment.fSampleCount == 1 && fSampleCount > 1)); | 
|  |  | 
|  | const char* colorFormatStr = TextureFormatName(fColorAttachment.fFormat); | 
|  | const char* dsFormatStr = "{}"; | 
|  | if (fDepthStencilAttachment.fFormat != TextureFormat::kUnsupported) { | 
|  | dsFormatStr = TextureFormatName(fDepthStencilAttachment.fFormat); | 
|  | } | 
|  |  | 
|  | // This intentionally only includes the fixed state that impacts pipeline compilation. | 
|  | // We include the load op of the color attachment when there is a resolve attachment because | 
|  | // the load may trigger a different renderpass description. | 
|  | const char* colorLoadStr = ""; | 
|  | const bool loadMsaaFromResolve = | 
|  | fColorResolveAttachment.fFormat != TextureFormat::kUnsupported && | 
|  | fColorResolveAttachment.fLoadOp == LoadOp::kLoad; | 
|  |  | 
|  | // This should, technically, check Caps::loadOpAffectsMSAAPipelines before adding the extra | 
|  | // string. Only the Metal backend doesn't set that flag, however, so we just assume it is set | 
|  | // to reduce plumbing. Since the Metal backend doesn't differentiate its UniqueKeys wrt | 
|  | // resolve-loads, this can lead to instances where two Metal Pipeline labels will map to the | 
|  | // same UniqueKey (i.e., one with "w/ msaa load" and one without it). | 
|  | if (loadMsaaFromResolve /* && Caps::loadOpAffectsMSAAPipelines() */) { | 
|  | colorLoadStr = " w/ msaa load"; | 
|  | } | 
|  |  | 
|  | // There are three supported ways of achieving MSAA rendering that we distinguish compactly. | 
|  | // 1. Direct sampling w/ N samples (includes single sample) | 
|  | // 2. MSAA render to single-sampled extensions | 
|  | // 3. Explicit MSAA color attachment w/ resolve | 
|  | // Since we don't expect to be mixing case 2 and 3 on the same device, treating them the same | 
|  | // in the pipeline labels makes it more convenient when writing test expectations. | 
|  | SkString sampleCountStr; | 
|  | if (fColorResolveAttachment.fFormat == TextureFormat::kUnsupported && | 
|  | fSampleCount == fColorAttachment.fSampleCount) { | 
|  | // Case 1: "xN" | 
|  | sampleCountStr = SkStringPrintf("x%u", fSampleCount); | 
|  | } else { | 
|  | // Case 2 and 3: "xN->1" | 
|  | sampleCountStr = SkStringPrintf("x%u->1", fSampleCount); | 
|  | } | 
|  | // NOTE: This label does not differentiate between explicitly resolved MSAA color attachments | 
|  | // and MSAA-render-to-single-sample renderpasses. For a given set of Caps, we currently only | 
|  | // expect to generate one or the other variety. | 
|  | return SkStringPrintf("RP((%s+%s %s).%s%s)", | 
|  | colorFormatStr, | 
|  | dsFormatStr, | 
|  | sampleCountStr.c_str(), | 
|  | fWriteSwizzle.asString().c_str(), | 
|  | colorLoadStr); | 
|  | } | 
|  |  | 
|  | SkString AttachmentDesc::toString() const { | 
|  | if (fFormat == TextureFormat::kUnsupported) { | 
|  | return SkString("{}"); | 
|  | } else { | 
|  | return SkStringPrintf("{f: %s x%u, ops: %s->%s}", | 
|  | TextureFormatName(fFormat), | 
|  | fSampleCount, | 
|  | to_str(fLoadOp), | 
|  | to_str(fStoreOp)); | 
|  | } | 
|  | } | 
|  |  | 
|  | bool AttachmentDesc::isCompatible(const TextureInfo& texInfo) const { | 
|  | return fFormat == TextureInfoPriv::ViewFormat(texInfo) && | 
|  | fSampleCount == texInfo.numSamples(); | 
|  | } | 
|  |  | 
|  | } // namespace skgpu::graphite |