blob: ae4443f8ed42fe8252f1ba0b1c93e9119601fb9b [file] [log] [blame]
/*
* 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/base/SkAssert.h"
#include "src/base/SkEnumBitMask.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/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/Log.h"
#include "src/gpu/graphite/RecorderPriv.h"
#include "src/gpu/graphite/RenderPassDesc.h"
#include "src/gpu/graphite/ResourceTypes.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) {
if (!target) {
return nullptr;
}
// We don't render to unknown or unpremul alphatypes
if (colorInfo.alphaType() == kUnknown_SkAlphaType ||
colorInfo.alphaType() == kUnpremul_SkAlphaType) {
return nullptr;
}
if (!caps->isRenderable(target->textureInfo())) {
return nullptr;
}
if (!caps->areColorTypeAndTextureInfoCompatible(colorInfo.colorType(), target->textureInfo())) {
return nullptr;
}
// Accept an approximate-fit texture, but make sure it's at least as large as the device's
// logical size.
// TODO: validate that the alpha type is compatible with the target's info
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(std::move(target))
, fImageInfo(ii)
, fSurfaceProps(props)
, fDstReadStrategy(caps->getDstReadStrategy())
, fSupportsHardwareAdvancedBlend(caps->supportsHardwareAdvancedBlending())
, fAdvancedBlendsRequireBarrier(caps->blendEquationSupport() ==
Caps::BlendEquationSupport::kAdvancedNoncoherent)
, fCurrentDrawTask(sk_make_sp<DrawTask>(fTarget))
, fPendingDraws(std::make_unique<DrawList>())
, fPendingUploads(std::make_unique<UploadList>()) {
// Must determine a valid strategy to use should a dst texture read be required.
SkASSERT(fDstReadStrategy != DstReadStrategy::kNoneRequired);
if (!caps->isTexturable(fTarget->textureInfo())) {
fReadView = {}; // Presumably this DrawContext is rendering into a swap chain
} else {
Swizzle swizzle = caps->getReadSwizzle(ii.colorType(), fTarget->textureInfo());
fReadView = {fTarget, swizzle};
}
// 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->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();
}
}
void 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) {
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;
}
fPendingDraws->recordDraw(renderer, localToDevice, geometry, clip, ordering, paintID, dstUsage,
barrierBeforeDraws, gatherer, stroke);
}
bool DrawContext::recordUpload(Recorder* recorder,
sk_sp<TextureProxy> targetProxy,
const SkColorInfo& srcColorInfo,
const SkColorInfo& dstColorInfo,
const UploadSource& source,
const SkIRect& dstRect,
std::unique_ptr<ConditionalUploadContext> condContext) {
// Our caller should have clipped to the bounds of the surface already.
SkASSERT(targetProxy->isFullyLazy() ||
SkIRect::MakeSize(targetProxy->dimensions()).contains(dstRect));
SkASSERT(source.isValid());
return fPendingUploads->recordUpload(recorder,
std::move(targetProxy),
srcColorInfo,
dstColorInfo,
source,
dstRect,
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().atlasProvider()->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();
const SkEnumBitMask<DepthStencilFlags> dsFlags = fPendingDraws->depthStencilFlags();
// 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,
fTarget,
this->imageInfo(),
drawPassDstReadStrategy);
SkASSERT(!fPendingDraws->modifiesTarget()); // Should be drained into `pass`.
if (pass) {
SkASSERT(fTarget.get() == 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,
fReadView,
fImageInfo.colorInfo(),
dstReadPixelBounds,
Budgeted::kYes,
Mipmapped::kNo,
SkBackingFit::kApprox,
"DstCopy");
if (!imageCopy) {
SKGPU_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();
auto writeSwizzle = caps->getWriteSwizzle(this->colorInfo().colorType(),
fTarget->textureInfo());
RenderPassDesc desc = RenderPassDesc::Make(caps, fTarget->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,
std::move(dstCopy), dstReadPixelBounds));
if (fTarget->mipmapped() == Mipmapped::kYes) {
if (!GenerateMipmaps(recorder, this, fTarget, fImageInfo.colorInfo())) {
SKGPU_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);
return snappedTask;
}
} // namespace skgpu::graphite