| /* | |
| * 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 |