blob: e9ae96414a909290bd684678304346c6f67e2743 [file] [log] [blame] [edit]
/*
* Copyright 2025 Google LLC
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "tests/Test.h"
#include "include/core/SkCanvas.h"
#include "include/core/SkPaint.h"
#include "include/core/SkString.h"
#include "include/core/SkSurface.h"
#include "include/core/SkTraceMemoryDump.h"
#include "include/gpu/graphite/Context.h"
#include "include/gpu/graphite/Recorder.h"
#include "include/gpu/graphite/Recording.h"
#include "include/gpu/graphite/Surface.h"
#include "src/gpu/graphite/Caps.h"
#include "src/gpu/graphite/ContextPriv.h"
#include "src/gpu/graphite/ContextUtils.h"
#include "src/gpu/graphite/Renderer.h"
#include "src/gpu/graphite/Surface_Graphite.h"
#include "src/gpu/graphite/TextureFormat.h"
#include "src/gpu/graphite/TextureInfoPriv.h"
namespace skgpu::graphite {
/**
* Implementation of SkTraceMemoryDump which tracks the number of resources whose type values
* contain a provided string within a ResourceCache. Could easily be elevated to be shared if other
* tests could benefit from this utility, but for now is only used within this test.
*/
class ResourceTypeQuantifier : public SkTraceMemoryDump {
public:
ResourceTypeQuantifier() = delete;
~ResourceTypeQuantifier() override {}
ResourceTypeQuantifier(SkString resourceTypeSubstr) : fResourceTypeSubstr(resourceTypeSubstr) {}
// Override relevant methods
void dumpStringValue(const char* dumpName, const char* valueName, const char* value) override {
if (SkString("type").equals(valueName)) {
if (SkString(value).contains(fResourceTypeSubstr.c_str())) {
fNumResourcesWithTypeSubstr++;
}
}
}
LevelOfDetail getRequestedDetails() const override {
return SkTraceMemoryDump::kObjectsBreakdowns_LevelOfDetail;
}
bool shouldDumpSizelessObjects() const override { return true; }
// Define methods unique to this implementation
uint8_t numResourcesWithTypeSubstr() const { return fNumResourcesWithTypeSubstr; }
void resetCount() { fNumResourcesWithTypeSubstr = 0; }
// Override unneeded methods to be no-ops
void setMemoryBacking(const char* dumpName, const char* backingType,
const char* backingObjectId) override {}
void setDiscardableMemoryBacking(
const char* dumpName, const SkDiscardableMemory& discardableMemoryObject) override {}
void dumpNumericValue(const char* dumpName, const char* valueName, const char* units,
uint64_t value) override {}
private:
SkString fResourceTypeSubstr;
int fNumResourcesWithTypeSubstr = 0;
};
/**
* Unit test to ensure that the Vulkan backend shares renderpasses between draws that do and
* do not read from the dst texture as intended for optimization purposes. Could be expanded to test
* every blend mode that reads from the dst against all that do not, but testing just one should be
* sufficient to prove out that renderpasses can be shared between the different blend/draw types.
*/
DEF_GRAPHITE_TEST_FOR_VULKAN_CONTEXT(VulkanDstReadsShareRenderpass, reporter, context,
CtsEnforcement::kApiLevel_202504) {
// Set up a render target surface which we should be able to read from as an input attachment.
// Our Vulkan backend makes render targets usable as input attachments by default.
const Caps* caps = context->priv().caps();
std::unique_ptr<Recorder> recorder = context->makeRecorder();
SkImageInfo ii = SkImageInfo::Make(SkISize::Make(10, 10),
SkColorType::kRGBA_8888_SkColorType,
SkAlphaType::kPremul_SkAlphaType);
sk_sp<SkSurface> surface = SkSurfaces::RenderTarget(recorder.get(), ii);
SkCanvas* canvas = surface->getCanvas();
SharedContext* sharedContext = context->priv().sharedContext();
TextureFormat targetFormat = TextureInfoPriv::ViewFormat(
static_cast<const Surface*>(surface.get())->backingTextureProxy()->textureInfo());
// Select which blend modes to use for testing: one that we do expect to require a dst read on
// most hardware and one we do not. Assert that the dst read requirements of the modes align to
// expectations. For the sake of this test, assume Coverage::kNone.
static const SkBlendMode simpleBlendMode = SkBlendMode::kSrcATop;
static const SkBlendMode dstReadBlendMode = SkBlendMode::kLighten;
REPORTER_ASSERT(reporter,
CanUseHardwareBlending(caps, targetFormat, simpleBlendMode, Coverage::kNone));
// VK_EXT_blend_operation_advanced is supported on Nvidia hardware which does enable hardware
// blending for every Skia blend mode, so the following assertion is not universal.
// REPORTER_ASSERT(reporter, !CanUseHardwareBlending(caps, dstReadBlendMode, Coverage::kNone));
// Perform draw operations which do not read from the dst. Clear the canvas to initialize it to
// any color, drawing a color over it afterwards using the simple blend mode.
canvas->clear(SK_ColorMAGENTA);
canvas->drawColor(SK_ColorCYAN, simpleBlendMode);
std::unique_ptr<Recording> simpleBlendRecording = recorder->snap();
// Determine the number of renderpasses used for later comparison, asserting we have at least 1.
ResourceTypeQuantifier renderpassQuantifier { SkString("RenderPass") };
sharedContext->dumpMemoryStatistics(&renderpassQuantifier);
const int numRenderPassesNoDstRead = renderpassQuantifier.numResourcesWithTypeSubstr();
REPORTER_ASSERT(reporter, numRenderPassesNoDstRead > 0);
// Perform a draw using a blend mode which does require reading from the dst texture. Determine
// how many renderpasses are in the cache after this operation.
canvas->drawColor(SK_ColorYELLOW, dstReadBlendMode);
std::unique_ptr<Recording> dstReadRecording = recorder->snap();
renderpassQuantifier.resetCount();
sharedContext->dumpMemoryStatistics(&renderpassQuantifier);
const int numRenderPassesAfterDstRead = renderpassQuantifier.numResourcesWithTypeSubstr();
// Assert that no new renderpasses have been added to the cache, meaning that one was reused
// from the draw that did not require a dst read.
REPORTER_ASSERT(reporter,
numRenderPassesNoDstRead == numRenderPassesAfterDstRead,
"# renderpasses with no dst read: %d ; after dst read: %d",
numRenderPassesNoDstRead,
numRenderPassesAfterDstRead);
}
} // namespace skgpu::graphite