Reland "Add SkSurface resolve function."

This is a reland of d921f21fbc

Original change's description:
> Add SkSurface resolve function.
>
> This will insert a resolve msaa call into the stream of commands for
> the SkSurface. This is mostly useful for cases when a client wraps the
> resolve texture but has Skia draw with MSAA, and the client wants to
> make sure Skia resolves to their wrapped texture.
>
> Bug: chromium:1292418
> Change-Id: I6eddae967136716b9215fcd96e7d77a2457efdf2
> Reviewed-on: https://skia-review.googlesource.com/c/skia/+/503340
> Reviewed-by: Robert Phillips <robertphillips@google.com>
> Commit-Queue: Greg Daniel <egdaniel@google.com>

Bug: chromium:1292418
Change-Id: I810b5618092c560f5bba900024b3b8c0c88baea9
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/503717
Reviewed-by: Robert Phillips <robertphillips@google.com>
Commit-Queue: Greg Daniel <egdaniel@google.com>
This commit is contained in:
Greg Daniel 2022-02-03 10:44:00 -05:00 committed by SkCQ
parent 005392cbc4
commit 2a4a0b7f1f
20 changed files with 253 additions and 20 deletions

View File

@ -7,6 +7,8 @@ Milestone 100
* Skia now requires C++17 and the corresponding standard library (or newer).
* Skia on iOS now requires iOS 11 to build; earlier versions of iOS do not support C++17.
* The skstd::string_view class has been replaced with the C++17 native std::string_view.
* Added SkSurface::resolveMSAA api to force Skia to resolve MSAA draws. Useful for when
Skia wraps a clients texture as the resolve target.
* * *

View File

@ -107,6 +107,7 @@ tests_sources = [
"$_tests/GrSlugTest.cpp",
"$_tests/GrStyledShapeTest.cpp",
"$_tests/GrSubmittedFlushTest.cpp",
"$_tests/GrSurfaceResolveTest.cpp",
"$_tests/GrSurfaceTest.cpp",
"$_tests/GrTextBlobTest.cpp",
"$_tests/GrTextureMipMapInvalidationTest.cpp",

View File

@ -910,6 +910,19 @@ public:
};
#if SK_SUPPORT_GPU
/** If a surface is GPU texture backed, is being drawn with MSAA, and there is a resolve
texture, this call will insert a resolve command into the stream of gpu commands. In order
for the resolve to actually have an effect, the work still needs to be flushed and submitted
to the GPU after recording the resolve command. If a resolve is not supported or the
SkSurface has no dirty work to resolve, then this call is a no-op.
This call is most useful when the SkSurface is created by wrapping a single sampled gpu
texture, but asking Skia to render with MSAA. If the client wants to use the wrapped texture
outside of Skia, the only way to trigger a resolve is either to call this command or use
SkSurface::flush.
*/
void resolveMSAA();
/** Issues pending SkSurface commands to the GPU-backed API objects and resolves any SkSurface
MSAA. A call to GrDirectContext::submit is always required to ensure work is actually sent
to the gpu. Some specific API details:

View File

@ -58,6 +58,7 @@ public:
const GrBackendSemaphore* waitSemaphores,
bool deleteSemaphoresAfterWait) = 0;
virtual void discard() = 0;
virtual void resolveMSAA() = 0;
virtual bool replaceBackingProxy(SkSurface::ContentChangeMode,
sk_sp<GrRenderTargetProxy>,

View File

@ -31,6 +31,7 @@ GrCaps::GrCaps(const GrContextOptions& options) {
fConservativeRasterSupport = false;
fWireframeSupport = false;
fMSAAResolvesAutomatically = false;
fPreferDiscardableMSAAAttachment = false;
fUsePrimitiveRestart = false;
fPreferClientSideDynamicBuffers = false;
fPreferFullscreenClears = false;

View File

@ -75,6 +75,13 @@ public:
// an MSAA-render-to-texture extension: Any render target we create internally will use the
// extension, and any wrapped render target is the client's responsibility.
bool msaaResolvesAutomatically() const { return fMSAAResolvesAutomatically; }
// If true then when doing MSAA draws, we will prefer to discard the msaa attachment on load
// and stores. The use of this feature for specific draws depends on the render target having a
// resolve attachment, and if we need to load previous data the resolve attachment must be
// usable as an input attachment/texture. Otherwise we will just write out and store the msaa
// attachment like normal.
// This flag is similar to enabling gl render to texture for msaa rendering.
bool preferDiscardableMSAAAttachment() const { return fPreferDiscardableMSAAAttachment; }
bool halfFloatVertexAttributeSupport() const { return fHalfFloatVertexAttributeSupport; }
// Primitive restart functionality is core in ES 3.0, but using it will cause slowdowns on some
@ -544,6 +551,7 @@ protected:
bool fConservativeRasterSupport : 1;
bool fWireframeSupport : 1;
bool fMSAAResolvesAutomatically : 1;
bool fPreferDiscardableMSAAAttachment : 1;
bool fUsePrimitiveRestart : 1;
bool fPreferClientSideDynamicBuffers : 1;
bool fPreferFullscreenClears : 1;

View File

@ -753,7 +753,8 @@ void GrDrawingManager::addAtlasTask(sk_sp<GrRenderTask> atlasTask,
}
#endif // SK_GPU_V1
GrTextureResolveRenderTask* GrDrawingManager::newTextureResolveRenderTask(const GrCaps& caps) {
GrTextureResolveRenderTask* GrDrawingManager::newTextureResolveRenderTaskBefore(
const GrCaps& caps) {
// Unlike in the "new opsTask" case, we do not want to close the active opsTask, nor (if we are
// in sorting and opsTask reduction mode) the render tasks that depend on any proxy's current
// state. This is because those opsTasks can still receive new ops and because if they refer to
@ -767,6 +768,34 @@ GrTextureResolveRenderTask* GrDrawingManager::newTextureResolveRenderTask(const
return static_cast<GrTextureResolveRenderTask*>(task);
}
void GrDrawingManager::newTextureResolveRenderTask(sk_sp<GrSurfaceProxy> proxy,
GrSurfaceProxy::ResolveFlags flags,
const GrCaps& caps) {
SkDEBUGCODE(this->validate());
SkASSERT(fContext);
GrRenderTask* lastTask = this->getLastRenderTask(proxy.get());
if ((!proxy->asRenderTargetProxy()->isMSAADirty() && (!lastTask || lastTask->isClosed())) ||
caps.msaaResolvesAutomatically()) {
SkDEBUGCODE(this->validate());
return;
}
this->closeActiveOpsTask();
auto resolveTask = sk_make_sp<GrTextureResolveRenderTask>();
// Add proxy also adds all the needed dependencies we need
resolveTask->addProxy(this, std::move(proxy), flags, caps);
auto task = this->appendTask(std::move(resolveTask));
task->makeClosed(fContext);
// We have closed the previous active oplist but since a new oplist isn't being added there
// shouldn't be an active one.
SkASSERT(!fActiveOpsTask);
SkDEBUGCODE(this->validate());
}
void GrDrawingManager::newWaitRenderTask(sk_sp<GrSurfaceProxy> proxy,
std::unique_ptr<std::unique_ptr<GrSemaphore>[]> semaphores,
int numSemaphores) {

View File

@ -63,9 +63,15 @@ public:
#endif
// Create a render task that can resolve MSAA and/or regenerate mipmap levels on proxies. This
// method will only add the new render task to the list. It is up to the caller to call
// addProxy() on the returned object.
GrTextureResolveRenderTask* newTextureResolveRenderTask(const GrCaps&);
// method will only add the new render task to the list. However, it adds the task before the
// last task in the list. It is up to the caller to call addProxy() on the returned object.
GrTextureResolveRenderTask* newTextureResolveRenderTaskBefore(const GrCaps&);
// Creates a render task that can resolve MSAA and/or regenerate mimap levels on the passed in
// proxy. The task is appended to the end of the current list of tasks.
void newTextureResolveRenderTask(sk_sp<GrSurfaceProxy> proxy,
GrSurfaceProxy::ResolveFlags,
const GrCaps&);
// Create a new render task that will cause the gpu to wait on semaphores before executing any
// more RenderTasks that target proxy. It is possible for this wait to also block additional

View File

@ -27,7 +27,7 @@ public:
GrTextureResolveRenderTask* newTextureResolveRenderTask(const GrCaps& caps) const {
SkASSERT(fDrawingManager);
return fDrawingManager->newTextureResolveRenderTask(caps);
return fDrawingManager->newTextureResolveRenderTaskBefore(caps);
}
private:

View File

@ -23,6 +23,8 @@ public:
*/
virtual void discard() = 0;
virtual void resolveMSAA() = 0;
/**
* Clear the rect of the render target to the given color.
* @param rect the rect to clear to

View File

@ -93,12 +93,6 @@ public:
// if true, MTLStoreActionStoreAndMultiplesampleResolve is available
bool storeAndMultisampleResolveSupport() const { return fStoreAndMultisampleResolveSupport; }
// If true when doing MSAA draws, we will prefer to discard the MSAA attachment on load
// and stores. The use of this feature for specific draws depends on the render target having a
// resolve attachment, and if we need to load previous data the resolve attachment must
// be readable in a shader. Otherwise we will just write out and store the MSAA attachment
// like normal.
bool preferDiscardableMSAAAttachment() const { return fPreferDiscardableMSAAAttachment; }
bool renderTargetSupportsDiscardableMSAA(const GrMtlRenderTarget*) const;
#if GR_TEST_UTILS
@ -200,7 +194,6 @@ private:
MTLPixelFormat fPreferredStencilFormat;
bool fStoreAndMultisampleResolveSupport : 1;
bool fPreferDiscardableMSAAAttachment : 1;
using INHERITED = GrCaps;
};

View File

@ -38,6 +38,10 @@ public:
fSurfaceDrawContext->discard();
}
void resolveMSAA() override {
fSurfaceDrawContext->resolveMSAA();
}
bool replaceBackingProxy(SkSurface::ContentChangeMode,
sk_sp<GrRenderTargetProxy>,
GrColorType,

View File

@ -11,6 +11,7 @@
#include "src/gpu/GrDstProxyView.h"
#include "src/gpu/GrImageContextPriv.h"
#include "src/gpu/GrProxyProvider.h"
#include "src/gpu/GrTextureResolveRenderTask.h"
#include "src/gpu/effects/GrTextureEffect.h"
#include "src/gpu/geometry/GrRect.h"
#include "src/gpu/ops/ClearOp.h"
@ -160,6 +161,19 @@ void SurfaceFillContext::discard() {
this->getOpsTask()->discard();
}
void SurfaceFillContext::resolveMSAA() {
ASSERT_SINGLE_OWNER
RETURN_IF_ABANDONED
SkDEBUGCODE(this->validate();)
GR_CREATE_TRACE_MARKER_CONTEXT("v1::SurfaceFillContext", "resolveMSAA", fContext);
AutoCheckFlush acf(this->drawingManager());
this->drawingManager()->newTextureResolveRenderTask(this->asSurfaceProxyRef(),
GrSurfaceProxy::ResolveFlags::kMSAA,
*this->caps());
}
void SurfaceFillContext::internalClear(const SkIRect* scissor,
std::array<float, 4> color,
bool upgradePartialToFull) {

View File

@ -38,6 +38,8 @@ public:
void discard() override;
void resolveMSAA() override;
void fillRectWithFP(const SkIRect& dstRect, std::unique_ptr<GrFragmentProcessor> fp) override;
bool blitTexture(GrSurfaceProxyView view,

View File

@ -253,13 +253,6 @@ public:
VkShaderStageFlags getPushConstantStageFlags() const;
// If true then when doing MSAA draws, we will prefer to discard the msaa attachment on load
// and stores. The use of this feature for specific draws depends on the render target having a
// resolve attachment, and if we need to load previous data the resolve attachment must be
// usable as an input attachment. Otherwise we will just write out and store the msaa attachment
// like normal.
// This flag is similar to enabling gl render to texture for msaa rendering.
bool preferDiscardableMSAAAttachment() const { return fPreferDiscardableMSAAAttachment; }
bool mustLoadFullImageWithDiscardableMSAA() const {
return fMustLoadFullImageWithDiscardableMSAA;
}
@ -418,7 +411,6 @@ private:
uint32_t fMaxInputAttachmentDescriptors = 0;
bool fPreferDiscardableMSAAAttachment = false;
bool fMustLoadFullImageWithDiscardableMSAA = false;
bool fSupportsDiscardableMSAAForDMSAA = true;
bool fSupportsMemorylessAttachments = false;

View File

@ -356,6 +356,10 @@ bool SkSurface::replaceBackendTexture(const GrBackendTexture& backendTexture,
releaseContext);
}
void SkSurface::resolveMSAA() {
asSB(this)->onResolveMSAA();
}
GrSemaphoresSubmitted SkSurface::flush(BackendSurfaceAccess access, const GrFlushInfo& flushInfo) {
return asSB(this)->onFlush(access, flushInfo, nullptr);
}

View File

@ -31,6 +31,8 @@ public:
TextureReleaseProc,
ReleaseContext);
virtual void onResolveMSAA() {}
/**
* Issue any pending surface IO to the current backend 3D API and resolve any surface MSAA.
* Inserts the requested number of semaphores for the gpu to signal when work is complete on the

View File

@ -208,6 +208,8 @@ bool SkSurface_Gpu::onCopyOnWrite(ContentChangeMode mode) {
void SkSurface_Gpu::onDiscard() { fDevice->discard(); }
void SkSurface_Gpu::onResolveMSAA() { fDevice->resolveMSAA(); }
GrSemaphoresSubmitted SkSurface_Gpu::onFlush(BackendSurfaceAccess access, const GrFlushInfo& info,
const GrBackendSurfaceMutableState* newState) {

View File

@ -46,6 +46,7 @@ public:
ReadPixelsContext context) override;
bool onCopyOnWrite(ContentChangeMode) override;
void onDiscard() override;
void onResolveMSAA() override;
GrSemaphoresSubmitted onFlush(BackendSurfaceAccess access, const GrFlushInfo& info,
const GrBackendSurfaceMutableState*) override;
bool onWait(int numSemaphores, const GrBackendSemaphore* waitSemaphores,

View File

@ -0,0 +1,156 @@
/*
* Copyright 2022 Google Inc.
*
* 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/SkBitmap.h"
#include "include/core/SkCanvas.h"
#include "include/core/SkSurface.h"
#include "src/gpu/GrCaps.h"
#include "src/gpu/GrDirectContextPriv.h"
#include "src/gpu/GrPixmap.h"
#include "tests/TestUtils.h"
#include "tools/gpu/ManagedBackendTexture.h"
using namespace sk_gpu_test;
bool check_pixels(skiatest::Reporter* reporter,
GrDirectContext* dContext,
const GrBackendTexture& tex,
const SkImageInfo& info,
SkColor expectedColor) {
// We have to do the readback of the backend texture wrapped in a different Skia surface than
// the one used in the main body of the test or else the readPixels call will trigger resolves
// itself.
sk_sp<SkSurface> surface = SkSurface::MakeFromBackendTexture(dContext,
tex,
kTopLeft_GrSurfaceOrigin,
/*sampleCnt=*/4,
kRGBA_8888_SkColorType,
nullptr, nullptr);
SkBitmap actual;
actual.allocPixels(info);
if (!surface->readPixels(actual, 0, 0)) {
return false;
}
SkBitmap expected;
expected.allocPixels(info);
SkCanvas tmp(expected);
tmp.clear(expectedColor);
expected.setImmutable();
const float tols[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
auto error = std::function<ComparePixmapsErrorReporter>(
[reporter](int x, int y, const float diffs[4]) {
SkASSERT(x >= 0 && y >= 0);
ERRORF(reporter, "mismatch at %d, %d (%f, %f, %f %f)",
x, y, diffs[0], diffs[1], diffs[2], diffs[3]);
});
return ComparePixels(expected.pixmap(), actual.pixmap(), tols, error);
}
DEF_GPUTEST_FOR_RENDERING_CONTEXTS(SurfaceResolveTest, reporter, ctxInfo) {
auto dContext = ctxInfo.directContext();
SkImageInfo info = SkImageInfo::Make(8, 8, kRGBA_8888_SkColorType, kPremul_SkAlphaType);
auto managedTex = ManagedBackendTexture::MakeFromInfo(dContext,
info,
GrMipmapped::kNo,
GrRenderable::kYes);
if (!managedTex) {
return;
}
auto tex = managedTex->texture();
// Wrap the backend surface but tell it rendering with MSAA so that the wrapped texture is the
// resolve.
sk_sp<SkSurface> surface = SkSurface::MakeFromBackendTexture(dContext,
tex,
kTopLeft_GrSurfaceOrigin,
/*sampleCnt=*/4,
kRGBA_8888_SkColorType,
nullptr, nullptr);
if (!surface) {
return;
}
const GrCaps* caps = dContext->priv().caps();
// In metal and vulkan if we prefer discardable msaa attachments we will also auto resolve. The
// GrBackendTexture and SkSurface are set up in a way that is compatible with discardable msaa
// for both backends.
bool autoResolves = caps->msaaResolvesAutomatically() ||
caps->preferDiscardableMSAAAttachment();
// First do a simple test where we clear the surface than flush with SkSurface::flush. This
// should trigger the resolve and the texture should have the correct data.
surface->getCanvas()->clear(SK_ColorRED);
surface->flush();
dContext->submit();
REPORTER_ASSERT(reporter, check_pixels(reporter, dContext, tex, info, SK_ColorRED));
// Next try doing a GrDirectContext::flush which will not trigger a resolve on gpus without
// automatic msaa resolves.
surface->getCanvas()->clear(SK_ColorBLUE);
dContext->flush();
dContext->submit();
if (autoResolves) {
REPORTER_ASSERT(reporter, check_pixels(reporter, dContext, tex, info, SK_ColorBLUE));
} else {
REPORTER_ASSERT(reporter, check_pixels(reporter, dContext, tex, info, SK_ColorRED));
}
// Now doing a surface flush (even without any queued up normal work) should still resolve the
// surface.
surface->flush();
dContext->submit();
REPORTER_ASSERT(reporter, check_pixels(reporter, dContext, tex, info, SK_ColorBLUE));
// Test using SkSurface::resolve with a GrDirectContext::flush
surface->getCanvas()->clear(SK_ColorRED);
surface->resolveMSAA();
dContext->flush();
dContext->submit();
REPORTER_ASSERT(reporter, check_pixels(reporter, dContext, tex, info, SK_ColorRED));
// Calling resolve again should cause no issues as it is a no-op (there is an assert in the
// resolve op that the surface's msaa is dirty, we shouldn't hit that assert).
surface->resolveMSAA();
dContext->flush();
dContext->submit();
REPORTER_ASSERT(reporter, check_pixels(reporter, dContext, tex, info, SK_ColorRED));
// Try resolving in the middle of draw calls. Non automatic resolve gpus should only see the
// results of the first draw.
surface->getCanvas()->clear(SK_ColorGREEN);
surface->resolveMSAA();
surface->getCanvas()->clear(SK_ColorBLUE);
dContext->flush();
dContext->submit();
if (autoResolves) {
REPORTER_ASSERT(reporter, check_pixels(reporter, dContext, tex, info, SK_ColorBLUE));
} else {
REPORTER_ASSERT(reporter, check_pixels(reporter, dContext, tex, info, SK_ColorGREEN));
}
// Test that a resolve between draws to a different surface doesn't cause the OpsTasks for that
// surface to be split. Fails if we hit validation asserts in GrDrawingManager.
// First clear out dirty msaa from previous test
surface->flush();
auto otherSurface = SkSurface::MakeRenderTarget(dContext, SkBudgeted::kYes, info);
REPORTER_ASSERT(reporter, otherSurface);
otherSurface->getCanvas()->clear(SK_ColorRED);
surface->resolveMSAA();
otherSurface->getCanvas()->clear(SK_ColorBLUE);
dContext->flush();
dContext->submit();
}