| /* | |
| * Copyright 2021 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/DrawContext.h" | |
| #include "include/core/SkAlphaType.h" | |
| #include "include/core/SkColorType.h" | |
| #include "include/core/SkRect.h" | |
| #include "include/core/SkSize.h" | |
| #include "include/gpu/GpuTypes.h" | |
| #include "include/gpu/graphite/Recorder.h" | |
| #include "include/private/SkAssert.h" | |
| #include "include/private/SkEnumBitMask.h" | |
| #include "include/private/SkLog.h" | |
| #include "src/core/SkColorData.h" | |
| #include "src/core/SkTraceEvent.h" | |
| #include "src/gpu/SkBackingFit.h" | |
| #include "src/gpu/Swizzle.h" | |
| #include "src/gpu/graphite/AtlasProvider.h" | |
| #include "src/gpu/graphite/Caps.h" | |
| #include "src/gpu/graphite/ComputePathAtlas.h" | |
| #include "src/gpu/graphite/DrawList.h" | |
| #include "src/gpu/graphite/DrawListBase.h" | |
| #include "src/gpu/graphite/DrawListLayer.h" | |
| #include "src/gpu/graphite/DrawOrder.h" | |
| #include "src/gpu/graphite/DrawParams.h" | |
| #include "src/gpu/graphite/DrawPass.h" | |
| #include "src/gpu/graphite/Image_Graphite.h" | |
| #include "src/gpu/graphite/RecorderPriv.h" | |
| #include "src/gpu/graphite/RenderPassDesc.h" | |
| #include "src/gpu/graphite/ResourceTypes.h" | |
| #include "src/gpu/graphite/TextureFormat.h" | |
| #include "src/gpu/graphite/TextureInfoPriv.h" | |
| #include "src/gpu/graphite/TextureProxy.h" | |
| #include "src/gpu/graphite/TextureProxyView.h" | |
| #include "src/gpu/graphite/TextureUtils.h" | |
| #include "src/gpu/graphite/geom/Rect.h" | |
| #include "src/gpu/graphite/task/ComputeTask.h" | |
| #include "src/gpu/graphite/task/DrawTask.h" | |
| #include "src/gpu/graphite/task/RenderPassTask.h" | |
| #include "src/gpu/graphite/task/Task.h" | |
| #include "src/gpu/graphite/task/UploadTask.h" | |
| #include <cstdint> | |
| #include <utility> | |
| namespace skgpu::graphite { | |
| class PaintParams; | |
| class Renderer; | |
| enum class DepthStencilFlags : int; | |
| sk_sp<DrawContext> DrawContext::Make(const Caps* caps, | |
| sk_sp<TextureProxy> target, | |
| SkISize deviceSize, | |
| const SkColorInfo& colorInfo, | |
| const SkSurfaceProps& props, | |
| bool allowUnpremul) { | |
| if (!target) { | |
| return nullptr; | |
| } | |
| // We don't render to unknown or unpremul alphatypes unless allowUnpremul is explicitly enabled. | |
| if (colorInfo.alphaType() == kUnknown_SkAlphaType || | |
| (colorInfo.alphaType() == kUnpremul_SkAlphaType && !allowUnpremul)) { | |
| return nullptr; | |
| } | |
| if (!caps->isRenderable(target->textureInfo())) { | |
| return nullptr; | |
| } | |
| if (!AreColorTypeAndFormatCompatible(colorInfo.colorType(), target->format())) { | |
| return nullptr; | |
| } | |
| // Accept an approximate-fit texture, but make sure it's at least as large as the device's | |
| // logical size. | |
| SkASSERT(target->isFullyLazy() || (target->dimensions().width() >= deviceSize.width() && | |
| target->dimensions().height() >= deviceSize.height())); | |
| SkImageInfo imageInfo = SkImageInfo::Make(deviceSize, colorInfo); | |
| return sk_sp<DrawContext>(new DrawContext(caps, std::move(target), imageInfo, props)); | |
| } | |
| DrawContext::DrawContext(const Caps* caps, | |
| sk_sp<TextureProxy> target, | |
| const SkImageInfo& ii, | |
| const SkSurfaceProps& props) | |
| : fTarget{target, ReadSwizzleForColorType(ii.colorType(), target->format())} | |
| , fImageInfo(ii) | |
| , fSurfaceProps(props) | |
| , fIsTexturable(caps->isTexturable(fTarget.proxy()->textureInfo()) && | |
| !fTarget.proxy()->isFullyLazy()) | |
| , fDstReadStrategy(caps->getDstReadStrategy()) | |
| , fSupportsHardwareAdvancedBlend(caps->supportsHardwareAdvancedBlending()) | |
| , fAdvancedBlendsRequireBarrier(caps->blendEquationSupport() == | |
| Caps::BlendEquationSupport::kAdvancedNoncoherent) | |
| , fCurrentDrawTask(sk_make_sp<DrawTask>(fTarget.refProxy())) | |
| , fPendingDraws( | |
| caps->useDrawListLayer() | |
| ? std::unique_ptr<DrawListBase>(std::make_unique<DrawListLayer>( | |
| caps->storageBufferSupport())) | |
| : std::unique_ptr<DrawListBase>(std::make_unique<DrawList>())) | |
| , fPendingUploads(std::make_unique<UploadList>()) | |
| , fStorageContext(caps->resourceBindingRequirements(), | |
| caps->storageBufferSupport()) { | |
| // Must determine a valid strategy to use should a dst texture read be required. | |
| SkASSERT(fDstReadStrategy != DstReadStrategy::kNoneRequired); | |
| // TBD - Will probably want DrawLists (and its internal commands) to come from an arena | |
| // that the DC manages. | |
| } | |
| DrawContext::~DrawContext() = default; | |
| void DrawContext::clear(const SkColor4f& clearColor) { | |
| this->resetForClearOrDiscard(); | |
| fPendingDraws->reset(LoadOp::kClear, clearColor); | |
| } | |
| void DrawContext::discard() { | |
| this->resetForClearOrDiscard(); | |
| fPendingDraws->reset(LoadOp::kDiscard); | |
| } | |
| void DrawContext::resetForClearOrDiscard() { | |
| // Non-loading operations on a fully lazy target can corrupt data beyond the DrawContext's | |
| // region so should be avoided. | |
| SkASSERT(!fTarget.proxy()->isFullyLazy()); | |
| // NOTE: Eventually the current DrawTask should be reset, once there are no longer implicit | |
| // dependencies on atlas tasks between DrawContexts. When that's resolved, the only tasks in the | |
| // current DrawTask are those that directly impact the target, which becomes irrelevant with the | |
| // clear op overwriting it. For now, preserve the previous tasks that might include atlas | |
| // uploads that are not explicitly shared between DrawContexts. | |
| if (fComputePathAtlas) { | |
| fComputePathAtlas->reset(); | |
| } | |
| } | |
| bool DrawContext::readsTexture(const TextureProxy* texture) const { | |
| if (fPendingDraws->samplesTexture(texture)) { | |
| return true; | |
| } | |
| // visitProxies() before calling prepareResources() can revisit tasks in the general case | |
| // (e.g. processing everything in the root task list). In this case, the only tasks being | |
| // visited are pending tasks so their graph complexity should be minimal. | |
| bool notFound = fCurrentDrawTask->visitProxies( | |
| [texture](const TextureProxy* other) { | |
| // Return true to continue visiting, i.e. when we haven't found `texture` yet. | |
| return texture != other; | |
| }, /*readsOnly=*/true); | |
| return !notFound; // double negation means its found in a pending child task | |
| } | |
| std::pair<DrawParams*, Layer*> DrawContext::recordDraw( | |
| const Renderer* renderer, | |
| const Transform& localToDevice, | |
| const Geometry& geometry, | |
| const Clip& clip, | |
| DrawOrder ordering, | |
| UniquePaintParamsID paintID, | |
| SkEnumBitMask<DstUsage> dstUsage, | |
| PipelineDataGatherer* gatherer, | |
| const StrokeStyle* stroke, | |
| Layer* lastInsertion) { | |
| SkASSERTF(SkIRect::MakeSize(this->imageInfo().dimensions()).contains(clip.scissor()), | |
| "Image %dx%d, scissor %d,%d,%d,%d", | |
| this->imageInfo().width(), this->imageInfo().height(), | |
| clip.scissor().left(), clip.scissor().top(), | |
| clip.scissor().right(), clip.scissor().bottom()); | |
| // Determine whether a draw requies a barrier | |
| BarrierType barrierBeforeDraws = BarrierType::kNone; | |
| if (fDstReadStrategy == DstReadStrategy::kReadFromInput && | |
| (dstUsage & DstUsage::kDstReadRequired)) { | |
| barrierBeforeDraws = BarrierType::kReadDstFromInput; | |
| } | |
| if ((dstUsage & DstUsage::kAdvancedBlend) && | |
| fSupportsHardwareAdvancedBlend && fAdvancedBlendsRequireBarrier) { | |
| // A draw should only read from the dst OR use hardware for advanced blend modes. | |
| SkASSERT(!(dstUsage & DstUsage::kDstReadRequired)); | |
| barrierBeforeDraws = BarrierType::kAdvancedNoncoherentBlend; | |
| } | |
| return fPendingDraws->recordDraw(renderer, localToDevice, geometry, clip, ordering, paintID, | |
| dstUsage, barrierBeforeDraws, gatherer, &fStorageContext, | |
| stroke, lastInsertion); | |
| } | |
| bool DrawContext::recordUpload(Recorder* recorder, | |
| const UploadSource& source, | |
| std::unique_ptr<ConditionalUploadContext> condContext) { | |
| // Since this upload is inline with the DrawContext's tasks, we do not attempt to upload it | |
| // directly via the host, we want to keep it as a repeatable task. | |
| return fPendingUploads->recordUpload(recorder, source, std::move(condContext)); | |
| } | |
| void DrawContext::recordDependency(sk_sp<Task> task) { | |
| SkASSERT(task); | |
| // Adding `task` to the current DrawTask directly means that it will execute after any previous | |
| // dependent tasks and after any previous calls to flush(), but everything else that's being | |
| // collected on the DrawContext will execute after `task` once the next flush() is performed. | |
| fCurrentDrawTask->addTask(std::move(task)); | |
| } | |
| PathAtlas* DrawContext::getComputePathAtlas(Recorder* recorder) { | |
| if (!fComputePathAtlas) { | |
| fComputePathAtlas = | |
| recorder->priv().getOrCreateAtlasProvider()->createComputePathAtlas(recorder); | |
| } | |
| return fComputePathAtlas.get(); | |
| } | |
| void DrawContext::flush(Recorder* recorder) { | |
| if (fPendingUploads->size() > 0) { | |
| TRACE_EVENT_INSTANT1("skia.gpu", TRACE_FUNC, TRACE_EVENT_SCOPE_THREAD, | |
| "# uploads", fPendingUploads->size()); | |
| fCurrentDrawTask->addTask(UploadTask::Make(fPendingUploads.get())); | |
| // The UploadTask steals the collected upload instances, automatically resetting this list | |
| SkASSERT(fPendingUploads->size() == 0); | |
| } | |
| // Generate compute dispatches that render into the atlas texture used by pending draws. | |
| // TODO: Once compute atlas caching is implemented, DrawContext might not hold onto to this | |
| // at which point a recordDispatch() could be added and it stores a pending dispatches list that | |
| // much like how uploads are handled. In that case, Device would be responsible for triggering | |
| // the recording of dispatches, but that may happen naturally in AtlasProvider::recordUploads(). | |
| if (fComputePathAtlas) { | |
| ComputeTask::DispatchGroupList dispatches; | |
| if (fComputePathAtlas->recordDispatches(recorder, &dispatches)) { | |
| // For now this check is valid as all coverage mask draws involve dispatches | |
| SkASSERT(fPendingDraws->hasCoverageMaskDraws()); | |
| fCurrentDrawTask->addTask(ComputeTask::Make(std::move(dispatches))); | |
| } // else no pending compute work needed to be recorded | |
| fComputePathAtlas->reset(); | |
| } // else platform doesn't support compute or atlas was never initialized. | |
| if (!fPendingDraws->modifiesTarget()) { | |
| // Nothing will be rasterized to the target that warrants a RenderPassTask, but we preserve | |
| // any added uploads or compute tasks since those could also affect the target w/o | |
| // rasterizing anything directly. | |
| return; | |
| } | |
| // Extract certain properties from DrawList relevant for DrawTask construction before | |
| // relinquishing the pending draw list to the DrawPass constructor. | |
| SkIRect dstReadPixelBounds = fPendingDraws->dstReadBounds().makeRoundOut().asSkIRect(); | |
| const bool drawsRequireMSAA = fPendingDraws->drawsRequireMSAA(); | |
| SkEnumBitMask<DepthStencilFlags> dsFlags = fPendingDraws->depthStencilFlags(); | |
| // TODO(michaelludwig): Track whether or not draws within fPendingDraw require the depth for | |
| // occlusion or clipping (per-draw depth is already accumulated into `dsFlags`). For now, | |
| // always add kDepth. RenderPassDesc::Make() ignores kDepth when Caps::avoidDepthMode() is true. | |
| // Instead we should ensure that kDepth was never included at all. Only add depth when there | |
| // are draws; otherwise we'd create a depth attachment for must a clear. | |
| if (fPendingDraws->renderStepCount() > 0) { | |
| dsFlags |= DepthStencilFlags::kDepth; | |
| } | |
| // Determine the optimal dst read strategy for the drawpass given pending draw characteristics | |
| const DstReadStrategy drawPassDstReadStrategy = fPendingDraws->drawsReadDst() | |
| ? this->dstReadStrategy() | |
| : DstReadStrategy::kNoneRequired; | |
| // Convert the pending draws and load/store ops into a DrawPass that will be executed after | |
| // the collected uploads and compute dispatches. | |
| // TODO: At this point, there's only ever one DrawPass in a RenderPassTask to a target. When | |
| // subpasses are implemented, they will either be collected alongside fPendingDraws or added | |
| // to the RenderPassTask separately. | |
| std::unique_ptr<DrawPass> pass = fPendingDraws->snapDrawPass(recorder, | |
| &fStorageContext, | |
| this, | |
| fTarget.refProxy(), | |
| this->imageInfo(), | |
| drawPassDstReadStrategy); | |
| SkASSERT(!fPendingDraws->modifiesTarget()); // Should be drained into `pass`. | |
| if (pass) { | |
| SkASSERT(fTarget.proxy() == pass->target()); | |
| // If any paint used within the DrawPass reads from the dst texture (indicated by nonempty | |
| // dstReadPixelBounds) and the dstReadStrategy is kTextureCopy, then add a CopyTask. | |
| sk_sp<TextureProxy> dstCopy; | |
| if (!dstReadPixelBounds.isEmpty() && | |
| drawPassDstReadStrategy == DstReadStrategy::kTextureCopy) { | |
| TRACE_EVENT_INSTANT0("skia.gpu", "DrawPass requires dst copy", | |
| TRACE_EVENT_SCOPE_THREAD); | |
| sk_sp<Image> imageCopy = Image::Copy( | |
| recorder, | |
| this, | |
| fTarget, | |
| fImageInfo.colorInfo(), | |
| dstReadPixelBounds, | |
| Budgeted::kYes, | |
| Mipmapped::kNo, | |
| SkBackingFit::kApprox, | |
| "DstCopy"); | |
| if (!imageCopy) { | |
| SKIA_LOG_W("DrawContext::flush Image::Copy failed, draw pass dropped!"); | |
| return; | |
| } | |
| dstCopy = imageCopy->textureProxyView().refProxy(); | |
| SkASSERT(dstCopy); | |
| } | |
| const Caps* caps = recorder->priv().caps(); | |
| auto [loadOp, storeOp] = pass->ops(); | |
| TextureFormat format = TextureInfoPriv::ViewFormat(fTarget.proxy()->textureInfo()); | |
| auto writeSwizzle = WriteSwizzleForColorType(this->colorInfo().colorType(), format); | |
| if (!writeSwizzle.has_value()) { | |
| writeSwizzle = Swizzle::RGBA(); // Fall back to rgba in release builds | |
| SKIA_LOG_W("No valid write swizzle for color type %d with format %s", | |
| (int) this->colorInfo().colorType(), TextureFormatName(format)); | |
| } | |
| RenderPassDesc desc = RenderPassDesc::Make(caps, fTarget.proxy()->textureInfo(), | |
| loadOp, storeOp, | |
| dsFlags, | |
| pass->clearColor(), | |
| drawsRequireMSAA, | |
| *writeSwizzle, | |
| drawPassDstReadStrategy); | |
| RenderPassTask::DrawPassList passes; | |
| passes.emplace_back(std::move(pass)); | |
| fCurrentDrawTask->addTask(RenderPassTask::Make(std::move(passes), desc, fTarget.refProxy(), | |
| std::move(dstCopy), dstReadPixelBounds)); | |
| if (fTarget.mipmapped() == Mipmapped::kYes) { | |
| if (!GenerateMipmaps(recorder, this, fTarget.refProxy())) { | |
| SKIA_LOG_W("DrawContext::flush GenerateMipmaps failed, draw pass dropped!"); | |
| return; | |
| } | |
| } | |
| } | |
| // else pass creation failed, DrawPass will have logged why. Don't discard the previously | |
| // accumulated tasks, however, since they may represent operations on an atlas that other | |
| // DrawContexts now implicitly depend on. | |
| } | |
| sk_sp<Task> DrawContext::snapDrawTask() { | |
| if (!fCurrentDrawTask->hasTasks()) { | |
| return nullptr; | |
| } | |
| sk_sp<Task> snappedTask = std::move(fCurrentDrawTask); | |
| fCurrentDrawTask = sk_make_sp<DrawTask>(fTarget.refProxy()); | |
| return snappedTask; | |
| } | |
| } // namespace skgpu::graphite |