blob: 74b5bf269c4fd57895cd2bec92aceeb852ae0c42 [file]
/*
* Copyright 2022 Google LLC
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "include/core/SkAlphaType.h"
#include "include/core/SkBitmap.h"
#include "include/core/SkCanvas.h"
#include "include/core/SkColor.h"
#include "include/core/SkColorSpace.h"
#include "include/core/SkColorType.h"
#include "include/core/SkImageInfo.h"
#include "include/core/SkMatrix.h"
#include "include/core/SkRect.h"
#include "include/core/SkRefCnt.h"
#include "include/core/SkSamplingOptions.h"
#include "include/core/SkSurface.h"
#include "include/core/SkSurfaceProps.h"
#include "include/core/SkTypes.h"
#include "include/gpu/GpuTypes.h"
#include "include/gpu/ganesh/GrBackendSurface.h"
#include "include/gpu/ganesh/GrContextOptions.h"
#include "include/gpu/ganesh/GrDirectContext.h"
#include "include/gpu/ganesh/GrTypes.h"
#include "include/gpu/ganesh/SkSurfaceGanesh.h"
#include "include/gpu/ganesh/mock/GrMockTypes.h"
#include "include/private/gpu/ganesh/GrTypesPriv.h"
#include "src/core/SkColorData.h"
#include "src/gpu/SkBackingFit.h"
#include "src/gpu/Swizzle.h"
#include "src/gpu/ganesh/GrCaps.h"
#include "src/gpu/ganesh/GrDirectContextPriv.h"
#include "src/gpu/ganesh/GrDrawingManager.h"
#include "src/gpu/ganesh/GrFragmentProcessor.h"
#include "src/gpu/ganesh/GrOnFlushResourceProvider.h"
#include "src/gpu/ganesh/GrPaint.h"
#include "src/gpu/ganesh/GrProxyProvider.h"
#include "src/gpu/ganesh/GrRenderTargetProxy.h"
#include "src/gpu/ganesh/GrSamplerState.h"
#include "src/gpu/ganesh/GrSurfaceProxy.h"
#include "src/gpu/ganesh/GrSurfaceProxyView.h"
#include "src/gpu/ganesh/GrTextureProxy.h"
#include "src/gpu/ganesh/GrTextureResolveRenderTask.h"
#include "src/gpu/ganesh/SurfaceContext.h"
#include "src/gpu/ganesh/SurfaceDrawContext.h"
#include "src/gpu/ganesh/effects/GrTextureEffect.h"
#include "src/gpu/ganesh/ops/OpsTask.h"
#include "tests/ComparePixels.h"
#include "tests/CtsEnforcement.h"
#include "tests/Test.h"
#include "tools/gpu/ManagedBackendTexture.h"
#include <functional>
#include <initializer_list>
#include <memory>
#include <utility>
struct GrContextOptions;
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 = SkSurfaces::WrapBackendTexture(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_GANESH_TEST_FOR_RENDERING_CONTEXTS(SurfaceResolveTest,
reporter,
ctxInfo,
CtsEnforcement::kApiLevel_T) {
auto dContext = ctxInfo.directContext();
SkImageInfo info = SkImageInfo::Make(8, 8, kRGBA_8888_SkColorType, kPremul_SkAlphaType);
auto managedTex = ManagedBackendTexture::MakeFromInfo(
dContext, info, skgpu::Mipmapped::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 = SkSurfaces::WrapBackendTexture(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);
dContext->flush(surface.get());
dContext->submit();
REPORTER_ASSERT(reporter, check_pixels(reporter, dContext, tex, info, SK_ColorRED));
// Next try doing a GrDirectContext::flush without the surface 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.
dContext->flush(surface.get());
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);
SkSurfaces::ResolveMSAA(surface);
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).
SkSurfaces::ResolveMSAA(surface);
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);
SkSurfaces::ResolveMSAA(surface);
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
dContext->flush(surface.get());
auto otherSurface = SkSurfaces::RenderTarget(dContext, skgpu::Budgeted::kYes, info);
REPORTER_ASSERT(reporter, otherSurface);
otherSurface->getCanvas()->clear(SK_ColorRED);
SkSurfaces::ResolveMSAA(surface);
otherSurface->getCanvas()->clear(SK_ColorBLUE);
dContext->flush();
dContext->submit();
// Make sure resolving a non-msaa surface doesn't trigger a resolve call. We'll hit an assert
// that the msaa is not dirty if it does.
REPORTER_ASSERT(reporter, otherSurface);
otherSurface->getCanvas()->clear(SK_ColorRED);
SkSurfaces::ResolveMSAA(otherSurface);
dContext->flush();
dContext->submit();
}
// This test comes from crbug.com/1355807 and crbug.com/1365578. The underlying issue was:
// * We would do a non-mipmapped draw of a proxy. This proxy would add a dependency from the ops
// task to the proxy's last render task, which was a copy task targetting the proxy.
// * We would do a mipmapped draw of the same proxy to the same ops task.
// GrRenderTask::addDependency would detect the pre-existing dependency and early out before
// adding the proxy to a resolve task.
// We also test the case where the first draw should add a MSAA resolve and the second draw should
// add a mipmap resolve.
DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS(NonmippedDrawBeforeMippedDraw,
reporter,
ctxInfo,
CtsEnforcement::kNever) {
using ResolveFlags = GrSurfaceProxy::ResolveFlags;
auto dc = ctxInfo.directContext();
if (!dc->priv().caps()->mipmapSupport()) {
return;
}
for (int sampleCount : {1, 4}) {
GrRenderable renderable = sampleCount > 1 ? GrRenderable::kYes : GrRenderable::kNo;
auto bef = dc->priv().caps()->getDefaultBackendFormat(GrColorType::kRGBA_8888, renderable);
if (sampleCount > 1) {
if (dc->priv().caps()->msaaResolvesAutomatically()) {
// MSAA won't add a resolve task.
continue;
}
sampleCount = dc->priv().caps()->getRenderTargetSampleCount(sampleCount, bef);
if (!sampleCount) {
continue;
}
}
// Create a mipmapped proxy
auto mmProxy = dc->priv().proxyProvider()->createProxy(bef,
{64, 64},
renderable,
sampleCount,
skgpu::Mipmapped::kYes,
SkBackingFit::kExact,
skgpu::Budgeted::kYes,
GrProtected::kNo,
"test MM Proxy");
GrSurfaceProxyView mmProxyView{mmProxy,
kBottomLeft_GrSurfaceOrigin,
skgpu::Swizzle::RGBA()};
if (sampleCount > 1) {
// Make sure MSAA surface needs a resolve by drawing to it. This also adds a last
// render task to the proxy.
auto drawContext = skgpu::ganesh::SurfaceDrawContext::Make(dc,
GrColorType::kRGBA_8888,
mmProxy,
nullptr,
kBottomLeft_GrSurfaceOrigin,
SkSurfaceProps{});
drawContext->fillWithFP(GrFragmentProcessor::MakeColor(SK_PMColor4fWHITE));
} else {
// Use a copy, as in the original bug, to dirty the mipmap status and also install
// a last render task on the proxy.
auto src = dc->priv().proxyProvider()->createProxy(bef,
{64, 64},
GrRenderable::kNo,
1,
skgpu::Mipmapped::kNo,
SkBackingFit::kExact,
skgpu::Budgeted::kYes,
GrProtected::kNo,
"testSrc");
skgpu::ganesh::SurfaceContext mmSC(
dc, mmProxyView, {GrColorType::kRGBA_8888, kPremul_SkAlphaType, nullptr});
mmSC.testCopy(src);
}
auto drawDst = skgpu::ganesh::SurfaceDrawContext::Make(dc,
GrColorType::kRGBA_8888,
nullptr,
SkBackingFit::kExact,
{8, 8},
SkSurfaceProps{},
"testDrawDst");
// Do a non-mipmapped draw from the mipmapped texture. This should add a dependency on the
// copy task recorded above. If the src texture is also multisampled this should record a
// msaa-only resolve.
{
auto te = GrTextureEffect::Make(
mmProxyView,
kPremul_SkAlphaType,
SkMatrix::I(),
GrSamplerState{SkFilterMode::kLinear, SkMipmapMode::kNone},
*dc->priv().caps());
GrPaint paint;
paint.setColorFragmentProcessor(std::move(te));
drawDst->drawRect(nullptr,
std::move(paint),
GrAA::kNo,
SkMatrix::Scale(1/8.f, 1/8.f),
SkRect::Make(mmProxy->dimensions()));
if (sampleCount > 1) {
const GrTextureResolveRenderTask* resolveTask =
drawDst->getOpsTask()->resolveTask();
if (!resolveTask) {
ERRORF(reporter, "No resolve task after drawing MSAA proxy");
return;
}
if (resolveTask->flagsForProxy(mmProxy) != ResolveFlags::kMSAA) {
ERRORF(reporter, "Expected resolve flags to be kMSAA");
return;
}
}
}
// Now do a mipmapped draw from the same texture. Ensure that even though we have a
// dependency on the copy task we still ensure that a resolve is recorded.
{
auto te = GrTextureEffect::Make(
mmProxyView,
kPremul_SkAlphaType,
SkMatrix::I(),
GrSamplerState{SkFilterMode::kLinear, SkMipmapMode::kLinear},
*dc->priv().caps());
GrPaint paint;
paint.setColorFragmentProcessor(std::move(te));
drawDst->drawRect(nullptr,
std::move(paint),
GrAA::kNo,
SkMatrix::Scale(1/8.f, 1/8.f),
SkRect::Make(mmProxy->dimensions()));
}
const GrTextureResolveRenderTask* resolveTask = drawDst->getOpsTask()->resolveTask();
if (!resolveTask) {
ERRORF(reporter, "No resolve task after drawing mip mapped proxy");
return;
}
ResolveFlags expectedFlags = GrSurfaceProxy::ResolveFlags::kMipMaps;
const char* expectedStr = "kMipMaps";
if (sampleCount > 1) {
expectedFlags |= GrSurfaceProxy::ResolveFlags::kMSAA;
expectedStr = "kMipMaps|kMSAA";
}
if (resolveTask->flagsForProxy(mmProxy) != expectedFlags) {
ERRORF(reporter, "Expected resolve flags to be %s", expectedStr);
return;
}
}
}
namespace {
// A preFlush callback that fails the first N flushes, simulating an allocation failure in an
// onFlush callback (e.g. atlas instantiation under memory pressure).
class FailingPreFlushCallback : public GrOnFlushCallbackObject {
public:
explicit FailingPreFlushCallback(int failCount) : fRemainingFailures(failCount) {}
bool preFlush(GrOnFlushResourceProvider*) override {
if (fRemainingFailures > 0) {
--fRemainingFailures;
return false;
}
return true;
}
bool retainOnFreeGpuResources() override { return true; }
private:
int fRemainingFailures;
};
} // namespace
// GrTextureResolveRenderTask::addProxy() optimistically marks a proxy's mipmaps clean / MSAA
// resolved at recording time. If the owning flush is dropped without executing render tasks
// (e.g., a preFlush callback or the resource allocator fails) the proxy must be re-dirtied so a
// later flush will record a new resolve.
DEF_GANESH_TEST(SurfaceResolveProxyStateAfterFailedFlush,
reporter,
/* options */,
CtsEnforcement::kNever) {
using ResolveFlags = GrSurfaceProxy::ResolveFlags;
using Enable = GrContextOptions::Enable;
using MipmapMode = GrSamplerState::MipmapMode;
for (auto reduceOpsTaskSplitting : {Enable::kYes, Enable::kNo}) {
for (int sampleCount : {1, 4}) {
GrMockOptions mockOptions;
mockOptions.fMipmapSupport = true;
mockOptions.fConfigOptions[(int)GrColorType::kRGBA_8888].fRenderability =
GrMockOptions::ConfigOptions::Renderability::kMSAA;
GrContextOptions ctxOptions;
ctxOptions.fReduceOpsTaskSplitting = reduceOpsTaskSplitting;
sk_sp<GrDirectContext> dContext = GrDirectContext::MakeMock(&mockOptions, ctxOptions);
if (!dContext) {
ERRORF(reporter, "could not create mock context");
continue;
}
GrDrawingManager* drawingManager = dContext->priv().drawingManager();
auto bef = dContext->priv().caps()->getDefaultBackendFormat(GrColorType::kRGBA_8888,
GrRenderable::kYes);
sk_sp<GrSurfaceProxy> proxy =
dContext->priv().proxyProvider()->createProxy(bef,
{64, 64},
GrRenderable::kYes,
sampleCount,
skgpu::Mipmapped::kYes,
SkBackingFit::kExact,
skgpu::Budgeted::kYes,
GrProtected::kNo,
"ResolveProxyStateTest");
if (!proxy) {
ERRORF(reporter, "could not create proxy");
continue;
}
GrTextureProxy* texProxy = proxy->asTextureProxy();
GrRenderTargetProxy* rtProxy = proxy->asRenderTargetProxy();
GrSurfaceProxyView proxyView{proxy,
kTopLeft_GrSurfaceOrigin,
skgpu::Swizzle::RGBA()};
const bool testMSAA = sampleCount > 1;
REPORTER_ASSERT(reporter, !testMSAA || proxy->requiresManualMSAAResolve());
auto recordDirtyingDrawAndMipmappedDependency = [&]() {
auto srcSDC =
skgpu::ganesh::SurfaceDrawContext::Make(dContext.get(),
GrColorType::kRGBA_8888,
proxy,
nullptr,
kTopLeft_GrSurfaceOrigin,
SkSurfaceProps{});
srcSDC->fillWithFP(GrFragmentProcessor::MakeColor(SK_PMColor4fWHITE));
auto dstSDC =
skgpu::ganesh::SurfaceDrawContext::Make(dContext.get(),
GrColorType::kRGBA_8888,
nullptr,
SkBackingFit::kExact,
{8, 8},
SkSurfaceProps{},
"ResolveProxyStateTestDst");
auto te = GrTextureEffect::Make(
proxyView,
kPremul_SkAlphaType,
SkMatrix::I(),
GrSamplerState{SkFilterMode::kLinear, MipmapMode::kLinear},
*dContext->priv().caps());
GrPaint paint;
paint.setColorFragmentProcessor(std::move(te));
dstSDC->drawRect(nullptr,
std::move(paint),
GrAA::kNo,
SkMatrix::Scale(1/8.f, 1/8.f),
SkRect::Make(proxy->dimensions()));
return dstSDC;
};
// Record a flush whose preFlush callback fails. The resolve task records the proxy
// as resolved/clean and is then discarded without executing.
{
FailingPreFlushCallback failCB(1);
dContext->priv().addOnFlushCallbackObject(&failCB);
auto dstSDC = recordDirtyingDrawAndMipmappedDependency();
ResolveFlags expectedFlags = ResolveFlags::kMipMaps;
if (testMSAA) {
expectedFlags |= ResolveFlags::kMSAA;
}
const GrTextureResolveRenderTask* resolveTask =
dstSDC->getOpsTask()->resolveTask();
REPORTER_ASSERT(reporter,
resolveTask &&
resolveTask->flagsForProxy(proxy) == expectedFlags);
REPORTER_ASSERT(reporter, !texProxy->mipmapsAreDirty());
REPORTER_ASSERT(reporter, !testMSAA || !rtProxy->isMSAADirty());
dContext->flush();
drawingManager->testingOnly_removeOnFlushCallbackObject(&failCB);
}
// The resolve never executed so the proxy must once again report dirty mips/MSAA.
REPORTER_ASSERT(reporter, texProxy->mipmapsAreDirty());
if (testMSAA) {
REPORTER_ASSERT(reporter, rtProxy->isMSAADirty());
REPORTER_ASSERT(reporter,
rtProxy->msaaDirtyRect() == SkIRect::MakeSize(proxy->dimensions()));
}
// A subsequent dependency must therefore record a new resolve task and the proxy
// should be clean once that flush succeeds.
{
auto dstSDC = recordDirtyingDrawAndMipmappedDependency();
REPORTER_ASSERT(reporter, dstSDC->getOpsTask()->resolveTask());
dContext->flush();
}
REPORTER_ASSERT(reporter, !texProxy->mipmapsAreDirty());
REPORTER_ASSERT(reporter, !testMSAA || !rtProxy->isMSAADirty());
}
}
}
// Directly read back 'tex' and return the first pixel color.
static bool read_backing_pixel(GrDirectContext* dContext,
const GrBackendTexture& tex,
const SkImageInfo& info,
SkColor* outPixel) {
sk_sp<SkSurface> reader = SkSurfaces::WrapBackendTexture(dContext,
tex,
kTopLeft_GrSurfaceOrigin,
/*sampleCnt=*/1,
kRGBA_8888_SkColorType,
nullptr,
nullptr);
if (!reader) {
return false;
}
SkBitmap bm;
bm.allocPixels(info);
if (!reader->readPixels(bm, 0, 0)) {
return false;
}
*outPixel = bm.getColor(0, 0);
return true;
}
// This test wraps a backend texture as an MSAA render target twice. The first wrap establishes a
// known baseline color in the single-sample texture via a *successful* flush. The second wrap
// creates a *fresh, never-written* MSAA color attachment (on GL a new renderbuffer, on Vulkan a
// scratch/new GrVkImage), records a draw, forces the flush to fail via a failing preFlush
// callback, and then reads back the single-sample texture. The read-back must not show the
// uninitialized MSAA attachment contents; if it does, resolve_and_mipmap() ran despite the failed
// flush and copied uninitialized GPU memory into the client-visible texture.
DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS(SurfaceResolveAfterFailedFlush,
reporter,
ctxInfo,
CtsEnforcement::kNever) {
auto dContext = ctxInfo.directContext();
const GrCaps* caps = dContext->priv().caps();
// Only meaningful on backends that require an explicit resolve of a persistent MSAA attachment.
if (caps->msaaResolvesAutomatically() || caps->preferDiscardableMSAAAttachment()) {
return;
}
SkImageInfo info = SkImageInfo::Make(8, 8, kRGBA_8888_SkColorType, kPremul_SkAlphaType);
auto managedTex = ManagedBackendTexture::MakeFromInfo(
dContext, info, skgpu::Mipmapped::kNo, GrRenderable::kYes);
if (!managedTex) {
return;
}
const GrBackendTexture& tex = managedTex->texture();
constexpr SkColor kBaseline = SK_ColorBLUE; // known content of the single-sample texture
constexpr SkColor kIntended = SK_ColorGREEN; // what the failed flush *would* have drawn
// 1. Wrap once and successfully draw kBaseline so the single-sample texture holds known bytes.
{
sk_sp<SkSurface> surface = SkSurfaces::WrapBackendTexture(dContext,
tex,
kTopLeft_GrSurfaceOrigin,
/*sampleCnt=*/4,
kRGBA_8888_SkColorType,
nullptr,
nullptr);
if (!surface) {
return; // MSAA=4 unsupported on this config.
}
surface->getCanvas()->clear(kBaseline);
dContext->flush(surface.get());
dContext->submit(GrSyncCpu::kYes);
}
// Ensure the second wrap below allocates a *fresh* MSAA attachment rather than recycling the
// one from the wrap above (whose contents would coincidentally match kBaseline).
dContext->purgeUnlockedResources(GrPurgeResourceOptions::kAllResources);
SkColor pixel = 0;
REPORTER_ASSERT(reporter, read_backing_pixel(dContext, tex, info, &pixel));
REPORTER_ASSERT(reporter, pixel == kBaseline,
"baseline flush failed to resolve, got 0x%x", pixel);
// 2. Re-wrap: this creates a *new* persistent MSAA color attachment that has never been
// written by any render pass in this test.
sk_sp<SkSurface> surface = SkSurfaces::WrapBackendTexture(dContext,
tex,
kTopLeft_GrSurfaceOrigin,
/*sampleCnt=*/4,
kRGBA_8888_SkColorType,
nullptr,
nullptr);
if (!surface) {
return;
}
// 3. Record a full-surface draw so OpsTask::onMakeClosed() returns kTargetDirty and
// markMSAADirty() is called during closeAllTasks(). Force the flush to fail.
FailingPreFlushCallback failCB(1);
dContext->priv().addOnFlushCallbackObject(&failCB);
surface->getCanvas()->clear(kIntended);
GrSemaphoresSubmitted result = dContext->flush(surface.get(), GrFlushInfo{});
dContext->submit(GrSyncCpu::kYes);
GrDrawingManager* drawingManager = dContext->priv().drawingManager();
drawingManager->testingOnly_removeOnFlushCallbackObject(&failCB);
REPORTER_ASSERT(reporter, result == GrSemaphoresSubmitted::kNo,
"expected flush to report semaphores not submitted");
// 4. Read back the single-sample backing texture.
REPORTER_ASSERT(reporter, read_backing_pixel(dContext, tex, info, &pixel));
// The recorded draw never executed, so kIntended should not appear.
REPORTER_ASSERT(reporter, pixel != kIntended,
"flush failed but draw, somehow, occurred (got 0x%x)", pixel);
// Since the flush failed the MSAA resolve step should not have occurred and the contents
// of the backend texture should have remained at 'kBaseline'.
REPORTER_ASSERT(reporter, pixel == kBaseline,
"Invalid resolve after a failed flush: expected 0x%x, actual 0x%x",
kBaseline, pixel);
}