blob: 67da1a0f3fbdedb24377c1aac53260d31c75686b [file] [log] [blame]
/*
* Copyright 2026 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/DrawListLayer.h"
#include "include/core/SkTypes.h"
#include "include/gpu/graphite/Recorder.h"
#include "src/core/SkTraceEvent.h"
#include "src/gpu/graphite/DrawPass.h"
#include "src/gpu/graphite/DrawWriter.h"
#include "src/gpu/graphite/KeyContext.h"
#include "src/gpu/graphite/RecorderPriv.h"
#include "src/gpu/graphite/Renderer.h"
#include "src/gpu/graphite/geom/Geometry.h"
namespace skgpu::graphite {
void DrawListLayer::reset(LoadOp loadOp, SkColor4f color) {
DrawListBase::reset(loadOp, color);
fStorage.reset();
fLayers.reset();
fDrawCount = 0;
fOrderCounter = CompressedPaintersOrder::First();
}
// Draws affected by depth only draws we call "clipped draws."
//
// Clipped draws must come *after* all depth only draws that affect them, and they must come *after*
// any preceding draws from the same renderstep. To accomodate this:
// 1) When recording the depth only draws, a pointer marking the latest layer inserted into is
// passed between each draw. If a later draws inserts after an earlier draw, the pointer is
// overwritten. This ensures that the pointer is always the *latest* layer.
// 2) How the pointer is used depends on the property of clipped draw:
// - Clipped draws which do not dependOnDst use this as the start of the traversal, then
// proceed FORWARDS until finding a suitable layer.
// - Clipped draws which do dependOnDst must stop when encountering any shading intersecting
// draw. Thus, forwards traversal becomes impractical because the draw must exhaustively
// search layers to the tail to ensure that there are no intersections. Instead,
// these draws must take the normal BACKWARDS traversal.
// 3) Each clipped draw updates the starting layer to the layer that it inserted into. Because
// the stopLayer is treated exclusively, a sucessor renderstep stops its traversal before
// the stopLayer, thus preserving the relative ordering between the draws. (Note: will be
// changed in future CL, so kind of stub comment)
//
// STENCIL stub comment: Removed by pilot draws in the future, so this will not be filled out.
void DrawListLayer::recordBackwards(int stepIndex,
bool isStencil,
bool dependsOnDst,
bool requiresBarrier,
const RenderStep* step,
const UniformDataCache::Index& uniformIndex,
const LayerKey& key,
const DrawParams* drawParams,
const Layer* stopLayer) {
// Child stencils get a fast path to their parent
if (isStencil) {
if (stepIndex > 0) {
SkASSERT(fStencilLayer);
SkASSERT(fStencilList);
SkASSERT(fStencilWrapper);
SingleDraw* draw = fStorage.make<SingleDraw>(drawParams, uniformIndex);
fStencilLayer->addStencil(&fStorage, fStencilWrapper, key, draw, step, &fStencilList);
return;
} else {
fStencilList = nullptr;
}
}
SkTInternalLList<Layer>::Iter iter;
Layer* targetLayer = nullptr;
BindingWrapper* targetMatch = nullptr;
// If we're an easy draw (!kIsStencil and !dependsOnDst), try the head first.
Layer* current;
if (!isStencil && !dependsOnDst) {
// A valid stopLayer will never be null, because the depth draw will always return the layer
// it drew into.
targetLayer = stopLayer ? stopLayer->fNext : fLayers.head();
if (targetLayer) {
targetMatch = targetLayer->searchBinding(key);
}
current = const_cast<Layer*>(stopLayer);
} else {
current = iter.init(fLayers, SkTInternalLList<Layer>::Iter::kTail_IterStart);
}
int limit = kMaxSearchLimit;
// When stopLayer == nullptr this is effectively while(current)
while (current != stopLayer && limit > 0) {
#if defined(__GNUC__) || defined(__clang__)
__builtin_prefetch(current->fPrev);
#endif
auto result = isStencil
? current->test<true>(drawParams->drawBounds(), key, requiresBarrier)
: current->test<false>(drawParams->drawBounds(), key, requiresBarrier);
if (result.first == BoundsTest::kIncompatibleOverlap) {
// If we need to read the dst, we cannot go earlier than this layer.
if (dependsOnDst) {
// TODO (thomsmit): Test performance of forward merging
break;
}
// If !dependsOnDst, we just keep searching backwards.
} else {
// Found a valid layer (Compatible or Disjoint)
targetLayer = current;
targetMatch = result.second;
// If it was compatible, we expect a match. If disjoint, match is nullptr.
if (result.first == BoundsTest::kCompatibleOverlap) {
break;
}
// If Disjoint, we can theoretically stay here, but we keep searching backwards
// to see if there is a 'Compatible' layer further back to batch with.
}
current = iter.prev();
limit--;
}
if (!targetLayer) {
fOrderCounter = fOrderCounter.next();
targetLayer = fStorage.make<Layer>(fOrderCounter);
fLayers.addToTail(targetLayer);
}
SkASSERT(targetLayer);
SingleDraw* draw = fStorage.make<SingleDraw>(drawParams, uniformIndex);
if (isStencil) {
targetLayer->addStencil(&fStorage, targetMatch, key, draw, step, &fStencilList);
fStencilLayer = targetLayer;
fStencilWrapper = targetMatch;
} else {
targetLayer->add(&fStorage, targetMatch, key, draw, step, !dependsOnDst);
}
}
void DrawListLayer::recordDepthOnly(int stepIndex,
bool isStencil,
bool dependsOnDst,
bool requiresBarrier,
const RenderStep* step,
const UniformDataCache::Index& uniformIndex,
const LayerKey& key,
const DrawParams* drawParams,
Layer** captureLayer) {
if (stepIndex > 0) {
SkASSERT(fParentDepthLayer);
DepthDraw* deferredDraw = fStorage.make<DepthDraw>(key, step, drawParams, uniformIndex);
fParentDepthLayer->addDepthOnlyDraw(&fStorage, deferredDraw, isStencil);
return;
}
SkTInternalLList<Layer>::Iter iter;
Layer* current = iter.init(fLayers, SkTInternalLList<Layer>::Iter::kTail_IterStart);
Layer* targetLayer = nullptr;
int limit = kMaxSearchLimit;
while (current && limit > 0) {
#if defined(__GNUC__) || defined(__clang__)
__builtin_prefetch(current->fPrev);
#endif
auto result = isStencil
? current->test<true>(drawParams->drawBounds(), key, requiresBarrier)
: current->test<false>(drawParams->drawBounds(), key,
requiresBarrier);
if (result.first == BoundsTest::kIncompatibleOverlap) {
// TODO (thomsmit): Test performance of forward merging
break;
} else {
targetLayer = current;
if (result.first == BoundsTest::kCompatibleOverlap) {
break;
}
}
current = iter.prev();
limit--;
}
if (!targetLayer) {
fOrderCounter = fOrderCounter.next();
targetLayer = fStorage.make<Layer>(fOrderCounter);
fLayers.addToTail(targetLayer);
}
SkASSERT(targetLayer);
DepthDraw* deferredDraw = fStorage.make<DepthDraw>(key, step, drawParams, uniformIndex);
targetLayer->addDepthOnlyDraw(&fStorage, deferredDraw, isStencil);
fParentDepthLayer = targetLayer;
if (!(*captureLayer) || targetLayer->fOrder > (*captureLayer)->fOrder) {
*captureLayer = targetLayer;
}
}
void DrawListLayer::recordForwards(int stepIndex,
bool isStencil,
bool dependsOnDst,
bool requiresBarrier,
const RenderStep* step,
const UniformDataCache::Index& uniformIndex,
const LayerKey& key,
const DrawParams* drawParams,
const Layer* startLayer) {
// Child stencils get a fast path to their parent
if (isStencil) {
if (stepIndex > 0) {
SkASSERT(fStencilLayer);
SkASSERT(fStencilList);
SkASSERT(fStencilWrapper);
SingleDraw* draw = fStorage.make<SingleDraw>(drawParams, uniformIndex);
fStencilLayer->addStencil(&fStorage, fStencilWrapper, key, draw, step, &fStencilList);
return;
} else {
fStencilList = nullptr;
}
}
Layer* current = const_cast<Layer*>(startLayer);
Layer* targetLayer = nullptr;
BindingWrapper* targetMatch = nullptr;
int limit = kMaxSearchLimit;
while (current && limit > 0) {
#if defined(__GNUC__) || defined(__clang__)
__builtin_prefetch(current->fNext);
#endif
auto result = isStencil
? current->test<true>(drawParams->drawBounds(), key, requiresBarrier)
: current->test<false>(drawParams->drawBounds(), key, requiresBarrier);
if (result.first != BoundsTest::kIncompatibleOverlap) {
targetLayer = current;
targetMatch = result.second;
break;
}
current = current->fNext;
limit--;
}
if (!targetLayer) {
fOrderCounter = fOrderCounter.next();
targetLayer = fStorage.make<Layer>(fOrderCounter);
// Note: addToTail produces visually correct images, but addAfter does not. Given that we
// explicitly do not allow dependsOnDst draws to take the forward walking path, it is not
// clear why this is happening. This should be remedied when we switch to the "pilot draw"
// style.
fLayers.addToTail(targetLayer);
}
SkASSERT(targetLayer);
SingleDraw* draw = fStorage.make<SingleDraw>(drawParams, uniformIndex);
if (isStencil) {
targetLayer->addStencil(&fStorage, targetMatch, key, draw, step, &fStencilList);
fStencilLayer = targetLayer;
fStencilWrapper = targetMatch;
} else {
bool notStartLayer = targetLayer != startLayer;
targetLayer->add(&fStorage, targetMatch, key, draw, step, !dependsOnDst && notStartLayer);
}
}
// Layer has dual purpose here:
// 1) (Producer) If recording a depth only draw, the pointer is set to the *latest* layer inserted.
// 2) (Consumer) If recording a clipped draw, the pointer is the latest layer inserted into across
// *all depth only draws* which affect this draw. Thus, it is the earliest possible layer that
// the clipped draw could be inserted into, so it is used as the starting point for a *forward*
// search.
std::pair<DrawParams*, Layer*> DrawListLayer::recordDraw(
const Renderer* renderer,
const Transform& localToDevice,
const Geometry& geometry,
const Clip& clip,
DrawOrder ordering,
UniquePaintParamsID paintID,
SkEnumBitMask<DstUsage> dstUsage,
BarrierType barrierBeforeDraws,
PipelineDataGatherer* gatherer,
const StrokeStyle* stroke,
const Layer* latestDepthLayer) {
SkASSERT(localToDevice.valid());
SkASSERT(!geometry.isEmpty() && !clip.drawBounds().isEmptyNegativeOrNaN());
// RENDERER not STEP because ATOMIC
bool isStencil = SkToBool(renderer->depthStencilFlags() & DepthStencilFlags::kStencil);
bool dependsOnDst = SkToBool(dstUsage & DstUsage::kDependsOnDst);
bool requiresBarrier = barrierBeforeDraws != BarrierType::kNone;
// Currently, the draw params are created once per record draw call, and the pointer is passed
// to each draw call. This is storage effecient but will still introduce some pointer chasing,
// because the params will likely no longer be on the same cache line for successor render
// steps. We should test whether it is faster for each step to hold a copy of the params except
// in the case of clipped draws (which must share a copy because they are mutated later).
DrawParams* drawParams = fStorage.make<DrawParams>(this->deduplicateTransform(localToDevice),
geometry,
clip,
ordering,
stroke,
barrierBeforeDraws);
Layer* stepLayer = nullptr;
fRenderStepCount += renderer->numRenderSteps();
for (int stepIndex = 0; stepIndex < renderer->numRenderSteps(); ++stepIndex) {
const RenderStep* const step = renderer->steps()[stepIndex];
gatherer->markOffsetAndAlign(step->performsShading(), step->uniformAlignment());
GraphicsPipelineCache::Index pipelineIndex = fPipelineCache.insert(
{step->renderStepID(),
step->performsShading() ? paintID : UniquePaintParamsID::Invalid()});
step->writeUniformsAndTextures(*drawParams, gatherer);
auto [combinedUniforms, combinedTextures] =
gatherer->endCombinedData(step->performsShading());
UniformDataCache::Index uniformIndex = combinedUniforms
? fUniformDataCache.insert(combinedUniforms)
: UniformDataCache::kInvalidIndex;
TextureDataCache::Index textureBindingIndex =
combinedTextures ? fTextureDataCache.insert(combinedTextures)
: TextureDataCache::kInvalidIndex;
if (paintID == UniquePaintParamsID::Invalid()) { // Invalid ID implies depth only draw
this->recordDepthOnly(stepIndex,
isStencil,
dependsOnDst,
requiresBarrier,
step,
uniformIndex,
LayerKey{pipelineIndex, textureBindingIndex},
drawParams,
&stepLayer);
} else {
if (latestDepthLayer && !dependsOnDst) {
this->recordForwards(stepIndex,
isStencil,
false,
requiresBarrier,
step,
uniformIndex,
LayerKey{pipelineIndex, textureBindingIndex},
drawParams,
latestDepthLayer);
} else {
this->recordBackwards(stepIndex,
isStencil,
dependsOnDst,
requiresBarrier,
step,
uniformIndex,
LayerKey{pipelineIndex, textureBindingIndex},
drawParams,
latestDepthLayer);
}
}
gatherer->rewindForRenderStep();
}
fDrawCount++;
fPassBounds.join(clip.drawBounds());
fRequiresMSAA |= renderer->requiresMSAA();
fDepthStencilFlags |= renderer->depthStencilFlags();
if (dstUsage & DstUsage::kDstReadRequired) {
// For paints that read from the dst, update the bounds. It may later be determined that the
// DstReadStrategy does not require them, but they are inexpensive to track.
fDstReadBounds.join(clip.drawBounds());
}
#if defined(SK_DEBUG)
if (geometry.isCoverageMaskShape()) {
fCoverageMaskShapeDrawCount++;
}
#endif
return {drawParams, stepLayer};
}
std::unique_ptr<DrawPass> DrawListLayer::snapDrawPass(Recorder* recorder,
sk_sp<TextureProxy> target,
const SkImageInfo& targetInfo,
const DstReadStrategy dstReadStrategy) {
TRACE_EVENT1("skia.gpu", TRACE_FUNC, "draw count", fDrawCount);
std::unique_ptr<DrawPass> drawPass(new DrawPass(target,
{fLoadOp, StoreOp::kStore},
fClearColor,
recorder->priv().refFloatStorageManager()));
DrawBufferManager* bufferMgr = recorder->priv().drawBufferManager();
DrawWriter drawWriter(&drawPass->fCommandList, bufferMgr);
GraphicsPipelineCache::Index lastPipeline = GraphicsPipelineCache::kInvalidIndex;
const SkIRect targetBounds = SkIRect::MakeSize(targetInfo.dimensions());
SkIRect lastScissor = targetBounds;
SkASSERT(drawPass->fTarget->isFullyLazy() ||
SkIRect::MakeSize(drawPass->fTarget->dimensions()).contains(lastScissor));
drawPass->fCommandList.setScissor(lastScissor);
const Caps* caps = recorder->priv().caps();
const bool useStorageBuffers = caps->storageBufferSupport();
UniformTracker uniformTracker(useStorageBuffers);
const bool rebindTexturesOnPipelineChange = dstReadStrategy == DstReadStrategy::kTextureCopy;
CompressedPaintersOrder priorDrawPaintOrder{};
// Accumulate rough pixel area touched by each pipeline
drawPass->fPipelineDrawAreas.push_back_n(fPipelineCache.count(), 0.f);
TextureTracker textureBindingTracker(&fTextureDataCache);
auto recordDraw = [&](const LayerKey& key,
const UniformDataCache::Index uniformIndex,
const RenderStep* renderStep,
const DrawParams& drawParams,
bool bindingsAreInvariant) -> bool {
SkASSERT(renderStep);
bool pipelineChange = false;
bool textureBindingsChange = false;
if (!bindingsAreInvariant) {
pipelineChange = key.fPipelineIndex != lastPipeline;
textureBindingsChange =
textureBindingTracker.setCurrentTextureBindings(key.fTextureIndex) ||
(rebindTexturesOnPipelineChange && pipelineChange &&
key.fTextureIndex != TextureDataCache::kInvalidIndex);
}
bool uniformBindingChange =
uniformTracker.writeUniforms(fUniformDataCache, bufferMgr, uniformIndex);
drawPass->fPipelineDrawAreas[key.fPipelineIndex] += drawParams.drawBounds().area();
std::optional<SkIRect> newScissor =
renderStep->getScissor(drawParams, lastScissor, targetBounds);
if (pipelineChange) {
drawWriter.newPipelineState(renderStep->primitiveType(),
renderStep->staticDataStride(),
renderStep->appendDataStride(),
renderStep->getRenderStateFlags(),
drawParams.barrierBeforeDraws());
} else if (uniformBindingChange || textureBindingsChange || newScissor.has_value()) {
drawWriter.newDynamicState();
} else if (drawParams.barrierBeforeDraws() != BarrierType::kNone) {
drawWriter.flush();
}
if (pipelineChange) {
drawPass->fCommandList.bindGraphicsPipeline(key.fPipelineIndex);
lastPipeline = key.fPipelineIndex;
}
if (uniformBindingChange) {
uniformTracker.bindUniforms(UniformSlot::kCombinedUniforms, &drawPass->fCommandList);
}
if (textureBindingsChange) {
textureBindingTracker.bindTextures(&drawPass->fCommandList);
}
if (newScissor.has_value()) {
drawPass->fCommandList.setScissor(*newScissor);
lastScissor = *newScissor;
}
uint32_t uniformSsboIndex = useStorageBuffers ? uniformTracker.ssboIndex() : 0;
renderStep->writeVertices(&drawWriter, drawParams, uniformSsboIndex);
if (bufferMgr->hasMappingFailed()) {
SKGPU_LOG_W("Failed to write necessary vertex/instance data for DrawPass, dropping!");
return false;
}
priorDrawPaintOrder = drawParams.order().paintOrder();
return true;
};
for (Layer* layer : fLayers) {
if (layer->fDepthInfo) {
const DepthDraw* current = layer->fDepthInfo->fDraws.head();
while (current) {
if (!recordDraw(current->fKey,
current->fUniformIndex,
current->fStep,
*current->fDrawParams,
false)) {
return nullptr;
}
current = current->fNext;
}
}
for (const BindingWrapper* binding : layer->fBindings) {
if (binding->fType == BindingListType::kSingle) {
const auto* singleList = static_cast<const SingleDrawList*>(binding);
SkASSERT(!singleList->fDraws.isEmpty());
const SingleDraw* current = singleList->fDraws.head();
if (!recordDraw(singleList->fKey,
current->fUniformIndex,
singleList->fStep,
*current->fDrawParams,
false)) {
return nullptr;
}
current = current->fNext;
while (current) {
if (!recordDraw(singleList->fKey,
current->fUniformIndex,
singleList->fStep,
*current->fDrawParams,
true)) {
return nullptr;
}
current = current->fNext;
}
} else {
const auto* stencilList = static_cast<const StencilDrawList*>(binding);
for (const StencilDraws* sd : stencilList->fStencilDraws) {
SkASSERT(sd && !sd->fDraws.isEmpty());
const SingleDraw* first = sd->fDraws.head();
if (!recordDraw(sd->fKey,
first->fUniformIndex,
sd->fStep,
*first->fDrawParams,
false)) {
return nullptr;
}
for (const SingleDraw* current = first->fNext; current;
current = current->fNext) {
if (!recordDraw(sd->fKey,
current->fUniformIndex,
sd->fStep,
*current->fDrawParams,
true)) {
return nullptr;
}
}
}
}
}
}
drawWriter.flush();
drawPass->fBounds = fPassBounds.roundOut().asSkIRect();
drawPass->fPipelineDescs = fPipelineCache.detach();
drawPass->fSampledTextures = fTextureDataCache.detachTextures();
TRACE_COUNTER1("skia.gpu", "# pipelines", drawPass->fPipelineDescs.size());
TRACE_COUNTER1("skia.gpu", "# textures", drawPass->fSampledTextures.size());
TRACE_COUNTER1("skia.gpu", "# commands", drawPass->fCommandList.count());
this->reset(LoadOp::kLoad);
return drawPass;
}
} // namespace skgpu::graphite