614cdab25d
Previous code tried to aggregate some checks, but therefore had several subtle bugs. Make each format check independent. It's easier to check whether it's correct by looking up the specs/extensions referenced and tweak it if not. Also removes some "workarounds" that were just working around our own bugs in extension/version detection. Also adds some more WebGL format functionality. Also uses glTexStorage2D for formats that previously did not check for support. Use glTexStorage2D on Android/NVIDIA to work around N9 and Shield TV issues with glTexImage2D. Bug: chromium:1003481 Change-Id: Ie3295b1ca76a6068f1426e77783d9dc07698c56a Reviewed-on: https://skia-review.googlesource.com/c/skia/+/243816 Commit-Queue: Brian Salomon <bsalomon@google.com> Reviewed-by: Greg Daniel <egdaniel@google.com>
845 lines
42 KiB
C++
845 lines
42 KiB
C++
/*
|
|
* Copyright 2013 Google Inc.
|
|
*
|
|
* Use of this source code is governed by a BSD-style license that can be
|
|
* found in the LICENSE file.
|
|
*/
|
|
|
|
#include <set>
|
|
#include "include/core/SkSurface.h"
|
|
#include "include/gpu/GrContext.h"
|
|
#include "include/gpu/GrTexture.h"
|
|
#include "src/core/SkAutoPixmapStorage.h"
|
|
#include "src/core/SkMipMap.h"
|
|
#include "src/gpu/GrClip.h"
|
|
#include "src/gpu/GrContextPriv.h"
|
|
#include "src/gpu/GrDataUtils.h"
|
|
#include "src/gpu/GrGpu.h"
|
|
#include "src/gpu/GrProxyProvider.h"
|
|
#include "src/gpu/GrRenderTarget.h"
|
|
#include "src/gpu/GrResourceProvider.h"
|
|
#include "src/gpu/GrTexturePriv.h"
|
|
#include "tests/Test.h"
|
|
#include "tests/TestUtils.h"
|
|
|
|
// Tests that GrSurface::asTexture(), GrSurface::asRenderTarget(), and static upcasting of texture
|
|
// and render targets to GrSurface all work as expected.
|
|
DEF_GPUTEST_FOR_MOCK_CONTEXT(GrSurface, reporter, ctxInfo) {
|
|
GrContext* context = ctxInfo.grContext();
|
|
auto resourceProvider = context->priv().resourceProvider();
|
|
|
|
GrSurfaceDesc desc;
|
|
desc.fWidth = 256;
|
|
desc.fHeight = 256;
|
|
desc.fConfig = kRGBA_8888_GrPixelConfig;
|
|
auto format = context->priv().caps()->getDefaultBackendFormat(GrColorType::kRGBA_8888,
|
|
GrRenderable::kYes);
|
|
sk_sp<GrSurface> texRT1 =
|
|
resourceProvider->createTexture(desc, format, GrRenderable::kYes, 1, GrMipMapped::kNo,
|
|
SkBudgeted::kNo, GrProtected::kNo);
|
|
|
|
REPORTER_ASSERT(reporter, texRT1.get() == texRT1->asRenderTarget());
|
|
REPORTER_ASSERT(reporter, texRT1.get() == texRT1->asTexture());
|
|
REPORTER_ASSERT(reporter, static_cast<GrSurface*>(texRT1->asRenderTarget()) ==
|
|
texRT1->asTexture());
|
|
REPORTER_ASSERT(reporter, texRT1->asRenderTarget() ==
|
|
static_cast<GrSurface*>(texRT1->asTexture()));
|
|
REPORTER_ASSERT(reporter, static_cast<GrSurface*>(texRT1->asRenderTarget()) ==
|
|
static_cast<GrSurface*>(texRT1->asTexture()));
|
|
|
|
sk_sp<GrTexture> tex1 =
|
|
resourceProvider->createTexture(desc, format, GrRenderable::kNo, 1, GrMipMapped::kNo,
|
|
SkBudgeted::kNo, GrProtected::kNo);
|
|
REPORTER_ASSERT(reporter, nullptr == tex1->asRenderTarget());
|
|
REPORTER_ASSERT(reporter, tex1.get() == tex1->asTexture());
|
|
REPORTER_ASSERT(reporter, static_cast<GrSurface*>(tex1.get()) == tex1->asTexture());
|
|
|
|
GrBackendTexture backendTex = context->createBackendTexture(
|
|
256, 256, kRGBA_8888_SkColorType,
|
|
SkColors::kTransparent, GrMipMapped::kNo, GrRenderable::kNo, GrProtected::kNo);
|
|
|
|
sk_sp<GrSurface> texRT2 = resourceProvider->wrapRenderableBackendTexture(
|
|
backendTex, 1, GrColorType::kRGBA_8888, kBorrow_GrWrapOwnership, GrWrapCacheable::kNo);
|
|
|
|
REPORTER_ASSERT(reporter, texRT2.get() == texRT2->asRenderTarget());
|
|
REPORTER_ASSERT(reporter, texRT2.get() == texRT2->asTexture());
|
|
REPORTER_ASSERT(reporter, static_cast<GrSurface*>(texRT2->asRenderTarget()) ==
|
|
texRT2->asTexture());
|
|
REPORTER_ASSERT(reporter, texRT2->asRenderTarget() ==
|
|
static_cast<GrSurface*>(texRT2->asTexture()));
|
|
REPORTER_ASSERT(reporter, static_cast<GrSurface*>(texRT2->asRenderTarget()) ==
|
|
static_cast<GrSurface*>(texRT2->asTexture()));
|
|
|
|
context->deleteBackendTexture(backendTex);
|
|
}
|
|
|
|
// This test checks that the isFormatTexturable and isFormatRenderable are
|
|
// consistent with createTexture's result.
|
|
DEF_GPUTEST_FOR_ALL_CONTEXTS(GrSurfaceRenderability, reporter, ctxInfo) {
|
|
GrContext* context = ctxInfo.grContext();
|
|
GrProxyProvider* proxyProvider = context->priv().proxyProvider();
|
|
GrResourceProvider* resourceProvider = context->priv().resourceProvider();
|
|
const GrCaps* caps = context->priv().caps();
|
|
|
|
// TODO: Should only need format here but need to determine compression type from format
|
|
// without config.
|
|
auto createTexture = [](int width, int height, GrColorType colorType,
|
|
const GrBackendFormat& format, GrRenderable renderable,
|
|
GrResourceProvider* rp) -> sk_sp<GrTexture> {
|
|
GrPixelConfig config = rp->caps()->getConfigFromBackendFormat(format, colorType);
|
|
bool compressed = rp->caps()->isFormatCompressed(format);
|
|
if (compressed) {
|
|
if (renderable == GrRenderable::kYes) {
|
|
return nullptr;
|
|
}
|
|
SkImage::CompressionType type;
|
|
switch (config) {
|
|
case kRGB_ETC1_GrPixelConfig:
|
|
type = SkImage::kETC1_CompressionType;
|
|
break;
|
|
default:
|
|
SK_ABORT("Unexpected config");
|
|
}
|
|
// Only supported compression type right now.
|
|
SkASSERT(config == kRGB_ETC1_GrPixelConfig);
|
|
auto size = GrCompressedDataSize(type, width, height);
|
|
auto data = SkData::MakeUninitialized(size);
|
|
SkColor4f color = {0, 0, 0, 0};
|
|
GrFillInCompressedData(type, width, height, (char*)data->writable_data(), color);
|
|
return rp->createCompressedTexture(width, height, format,
|
|
SkImage::kETC1_CompressionType,
|
|
SkBudgeted::kNo, data.get());
|
|
} else {
|
|
GrSurfaceDesc desc;
|
|
desc.fWidth = width;
|
|
desc.fHeight = height;
|
|
desc.fConfig = config;
|
|
return rp->createTexture(desc, format, renderable, 1, GrMipMapped::kNo, SkBudgeted::kNo,
|
|
GrProtected::kNo);
|
|
}
|
|
};
|
|
|
|
static constexpr int kW = 64;
|
|
static constexpr int kH = 64;
|
|
|
|
const std::vector<GrCaps::TestFormatColorTypeCombination>& combos =
|
|
caps->getTestingCombinations();
|
|
|
|
for (auto combo : combos) {
|
|
|
|
SkASSERT(combo.fColorType != GrColorType::kUnknown);
|
|
SkASSERT(combo.fFormat.isValid());
|
|
|
|
// Right now Vulkan has two backend formats that support ABGR_4444 (R4G4B4A4 and B4G4R4A4).
|
|
// Until we can create textures directly from the backend format this yields some
|
|
// ambiguity in what is actually supported and which textures can be created.
|
|
if (ctxInfo.backend() == kVulkan_GrBackend && combo.fColorType == GrColorType::kABGR_4444) {
|
|
continue;
|
|
}
|
|
|
|
for (GrSurfaceOrigin origin : { kTopLeft_GrSurfaceOrigin, kBottomLeft_GrSurfaceOrigin }) {
|
|
GrSurfaceDesc desc;
|
|
desc.fWidth = kW;
|
|
desc.fHeight = kH;
|
|
desc.fConfig = caps->getConfigFromBackendFormat(combo.fFormat, combo.fColorType);
|
|
SkASSERT(desc.fConfig != kUnknown_GrPixelConfig);
|
|
|
|
// Check if 'isFormatTexturable' agrees with 'createTexture' and that the mipmap
|
|
// support check is working
|
|
{
|
|
|
|
bool compressed = caps->isFormatCompressed(combo.fFormat);
|
|
bool isTexturable;
|
|
if (compressed) {
|
|
isTexturable = caps->isFormatTexturable(combo.fFormat);
|
|
} else {
|
|
isTexturable = caps->isFormatTexturableAndUploadable(combo.fColorType,
|
|
combo.fFormat);
|
|
}
|
|
|
|
sk_sp<GrSurface> tex = createTexture(kW, kH, combo.fColorType, combo.fFormat,
|
|
GrRenderable::kNo, resourceProvider);
|
|
REPORTER_ASSERT(reporter, SkToBool(tex) == isTexturable,
|
|
"ct:%s format:%s, tex:%d, isTexturable:%d",
|
|
GrColorTypeToStr(combo.fColorType),
|
|
combo.fFormat.toStr().c_str(),
|
|
SkToBool(tex), isTexturable);
|
|
|
|
// Check that the lack of mipmap support blocks the creation of mipmapped
|
|
// proxies
|
|
bool expectedMipMapability = isTexturable && caps->mipMapSupport() &&
|
|
!caps->isFormatCompressed(combo.fFormat);
|
|
|
|
sk_sp<GrTextureProxy> proxy = proxyProvider->createProxy(
|
|
combo.fFormat, desc, GrRenderable::kNo, 1, origin, GrMipMapped::kYes,
|
|
SkBackingFit::kExact, SkBudgeted::kNo, GrProtected::kNo);
|
|
REPORTER_ASSERT(reporter, SkToBool(proxy.get()) == expectedMipMapability,
|
|
"ct:%s format:%s, tex:%d, expectedMipMapability:%d",
|
|
GrColorTypeToStr(combo.fColorType),
|
|
combo.fFormat.toStr().c_str(),
|
|
SkToBool(proxy.get()), expectedMipMapability);
|
|
}
|
|
|
|
// Check if 'isFormatAsColorTypeRenderable' agrees with 'createTexture' (w/o MSAA)
|
|
{
|
|
bool isRenderable = caps->isFormatRenderable(combo.fFormat, 1);
|
|
|
|
sk_sp<GrSurface> tex = resourceProvider->createTexture(
|
|
desc, combo.fFormat, GrRenderable::kYes, 1, GrMipMapped::kNo,
|
|
SkBudgeted::kNo, GrProtected::kNo);
|
|
REPORTER_ASSERT(reporter, SkToBool(tex) == isRenderable,
|
|
"ct:%s format:%s, tex:%d, isRenderable:%d",
|
|
GrColorTypeToStr(combo.fColorType),
|
|
combo.fFormat.toStr().c_str(),
|
|
SkToBool(tex), isRenderable);
|
|
}
|
|
|
|
// Check if 'isFormatAsColorTypeRenderable' agrees with 'createTexture' w/ MSAA
|
|
{
|
|
bool isRenderable = caps->isFormatRenderable(combo.fFormat, 2);
|
|
|
|
sk_sp<GrSurface> tex = resourceProvider->createTexture(
|
|
desc, combo.fFormat, GrRenderable::kYes, 2, GrMipMapped::kNo,
|
|
SkBudgeted::kNo, GrProtected::kNo);
|
|
REPORTER_ASSERT(reporter, SkToBool(tex) == isRenderable,
|
|
"ct:%s format:%s, tex:%d, isRenderable:%d",
|
|
GrColorTypeToStr(combo.fColorType),
|
|
combo.fFormat.toStr().c_str(),
|
|
SkToBool(tex), isRenderable);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#include "src/gpu/GrDrawingManager.h"
|
|
#include "src/gpu/GrSurfaceProxy.h"
|
|
#include "src/gpu/GrTextureContext.h"
|
|
|
|
// For each context, set it to always clear the textures and then run through all the
|
|
// supported formats checking that the textures are actually cleared
|
|
DEF_GPUTEST(InitialTextureClear, reporter, baseOptions) {
|
|
GrContextOptions options = baseOptions;
|
|
options.fClearAllTextures = true;
|
|
|
|
static constexpr int kSize = 100;
|
|
static constexpr SkColor kClearColor = 0xABABABAB;
|
|
|
|
const SkImageInfo info = SkImageInfo::Make(kSize, kSize, kRGBA_8888_SkColorType,
|
|
kPremul_SkAlphaType);
|
|
|
|
SkAutoPixmapStorage readback;
|
|
readback.alloc(info);
|
|
|
|
GrSurfaceDesc desc;
|
|
desc.fWidth = desc.fHeight = kSize;
|
|
|
|
for (int ct = 0; ct < sk_gpu_test::GrContextFactory::kContextTypeCnt; ++ct) {
|
|
sk_gpu_test::GrContextFactory factory(options);
|
|
auto contextType = static_cast<sk_gpu_test::GrContextFactory::ContextType>(ct);
|
|
if (!sk_gpu_test::GrContextFactory::IsRenderingContext(contextType)) {
|
|
continue;
|
|
}
|
|
auto context = factory.get(contextType);
|
|
if (!context) {
|
|
continue;
|
|
}
|
|
|
|
GrProxyProvider* proxyProvider = context->priv().proxyProvider();
|
|
const GrCaps* caps = context->priv().caps();
|
|
|
|
const std::vector<GrCaps::TestFormatColorTypeCombination>& combos =
|
|
caps->getTestingCombinations();
|
|
|
|
for (auto combo : combos) {
|
|
|
|
SkASSERT(combo.fColorType != GrColorType::kUnknown);
|
|
SkASSERT(combo.fFormat.isValid());
|
|
|
|
if (!caps->isFormatTexturableAndUploadable(combo.fColorType, combo.fFormat)) {
|
|
continue;
|
|
}
|
|
|
|
{
|
|
GrPixelConfig config = caps->getConfigFromBackendFormat(combo.fFormat,
|
|
combo.fColorType);
|
|
SkASSERT(config != kUnknown_GrPixelConfig);
|
|
|
|
desc.fConfig = config;
|
|
}
|
|
|
|
auto checkColor = [reporter](const GrCaps::TestFormatColorTypeCombination& combo,
|
|
uint32_t readColor) {
|
|
// We expect that if there is no alpha in the src color type and we read it to a
|
|
// color type with alpha that we will get one for alpha rather than zero. We used to
|
|
// require this but the Intel Iris 6100 on Win 10 test bot doesn't put one in the
|
|
// alpha channel when reading back from GL_RG16 or GL_RG16F. So now we allow either.
|
|
uint32_t components = GrColorTypeComponentFlags(combo.fColorType);
|
|
bool allowAlphaOne = !(components & kAlpha_SkColorTypeComponentFlag);
|
|
if (allowAlphaOne) {
|
|
if (readColor != 0x00000000 && readColor != 0xFF000000) {
|
|
ERRORF(reporter,
|
|
"Failed on ct %s format %s 0x%08x is not 0x00000000 or 0xFF000000",
|
|
GrColorTypeToStr(combo.fColorType), combo.fFormat.toStr().c_str(),
|
|
readColor);
|
|
return false;
|
|
}
|
|
} else {
|
|
if (readColor) {
|
|
ERRORF(reporter, "Failed on ct %s format %s 0x%08x != 0x00000000",
|
|
GrColorTypeToStr(combo.fColorType), combo.fFormat.toStr().c_str(),
|
|
readColor);
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
};
|
|
|
|
for (auto renderable : {GrRenderable::kNo, GrRenderable::kYes}) {
|
|
if (renderable == GrRenderable::kYes &&
|
|
!caps->isFormatAsColorTypeRenderable(combo.fColorType, combo.fFormat)) {
|
|
continue;
|
|
}
|
|
|
|
for (auto fit : {SkBackingFit::kApprox, SkBackingFit::kExact}) {
|
|
|
|
// Does directly allocating a texture clear it?
|
|
{
|
|
auto proxy = proxyProvider->testingOnly_createInstantiatedProxy(
|
|
{kSize, kSize}, combo.fColorType, combo.fFormat, renderable, 1,
|
|
kTopLeft_GrSurfaceOrigin, fit, SkBudgeted::kYes, GrProtected::kNo);
|
|
if (proxy) {
|
|
auto texCtx = context->priv().makeWrappedSurfaceContext(
|
|
std::move(proxy), combo.fColorType, kPremul_SkAlphaType);
|
|
|
|
readback.erase(kClearColor);
|
|
if (texCtx->readPixels(readback.info(), readback.writable_addr(),
|
|
readback.rowBytes(), {0, 0})) {
|
|
for (int i = 0; i < kSize * kSize; ++i) {
|
|
if (!checkColor(combo, readback.addr32()[i])) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
context->priv().testingOnly_purgeAllUnlockedResources();
|
|
}
|
|
|
|
// Try creating the texture as a deferred proxy.
|
|
{
|
|
std::unique_ptr<GrSurfaceContext> surfCtx;
|
|
if (renderable == GrRenderable::kYes) {
|
|
surfCtx = context->priv().makeDeferredRenderTargetContext(
|
|
fit, desc.fWidth, desc.fHeight, combo.fColorType, nullptr,
|
|
1, GrMipMapped::kNo, kTopLeft_GrSurfaceOrigin, nullptr);
|
|
} else {
|
|
surfCtx = context->priv().makeDeferredTextureContext(
|
|
fit, desc.fWidth, desc.fHeight, combo.fColorType,
|
|
kUnknown_SkAlphaType, nullptr, GrMipMapped::kNo,
|
|
kTopLeft_GrSurfaceOrigin);
|
|
}
|
|
if (!surfCtx) {
|
|
continue;
|
|
}
|
|
|
|
readback.erase(kClearColor);
|
|
if (surfCtx->readPixels(readback.info(), readback.writable_addr(),
|
|
readback.rowBytes(), {0, 0})) {
|
|
for (int i = 0; i < kSize * kSize; ++i) {
|
|
if (!checkColor(combo, readback.addr32()[i])) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
context->priv().testingOnly_purgeAllUnlockedResources();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
DEF_GPUTEST_FOR_RENDERING_CONTEXTS(ReadOnlyTexture, reporter, context_info) {
|
|
auto fillPixels = [](SkPixmap* p, const std::function<uint32_t(int x, int y)>& f) {
|
|
for (int y = 0; y < p->height(); ++y) {
|
|
for (int x = 0; x < p->width(); ++x) {
|
|
*p->writable_addr32(x, y) = f(x, y);
|
|
}
|
|
}
|
|
};
|
|
|
|
auto comparePixels = [](const SkPixmap& p1, const SkPixmap& p2, skiatest::Reporter* reporter) {
|
|
SkASSERT(p1.info() == p2.info());
|
|
for (int y = 0; y < p1.height(); ++y) {
|
|
for (int x = 0; x < p1.width(); ++x) {
|
|
REPORTER_ASSERT(reporter, p1.getColor(x, y) == p2.getColor(x, y));
|
|
if (p1.getColor(x, y) != p2.getColor(x, y)) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
static constexpr int kSize = 100;
|
|
SkImageInfo ii = SkImageInfo::Make(kSize, kSize, kRGBA_8888_SkColorType, kPremul_SkAlphaType);
|
|
SkAutoPixmapStorage srcPixmap;
|
|
srcPixmap.alloc(ii);
|
|
fillPixels(&srcPixmap,
|
|
[](int x, int y) {
|
|
return (0xFFU << 24) | (x << 16) | (y << 8) | uint8_t((x * y) & 0xFF);
|
|
});
|
|
|
|
GrContext* context = context_info.grContext();
|
|
GrProxyProvider* proxyProvider = context->priv().proxyProvider();
|
|
|
|
// We test both kRW in addition to kRead mostly to ensure that the calls are structured such
|
|
// that they'd succeed if the texture wasn't kRead. We want to be sure we're failing with
|
|
// kRead for the right reason.
|
|
for (auto ioType : {kRead_GrIOType, kRW_GrIOType}) {
|
|
auto backendTex = context->priv().createBackendTexture(&srcPixmap, 1,
|
|
GrRenderable::kYes,
|
|
GrProtected::kNo);
|
|
|
|
auto proxy = proxyProvider->wrapBackendTexture(backendTex, GrColorType::kRGBA_8888,
|
|
kTopLeft_GrSurfaceOrigin,
|
|
kBorrow_GrWrapOwnership,
|
|
GrWrapCacheable::kNo, ioType);
|
|
auto surfContext = context->priv().makeWrappedSurfaceContext(proxy, GrColorType::kRGBA_8888,
|
|
kPremul_SkAlphaType);
|
|
|
|
// Read pixels should work with a read-only texture.
|
|
{
|
|
SkAutoPixmapStorage read;
|
|
read.alloc(srcPixmap.info());
|
|
auto readResult = surfContext->readPixels(srcPixmap.info(), read.writable_addr(),
|
|
0, { 0, 0 });
|
|
REPORTER_ASSERT(reporter, readResult);
|
|
if (readResult) {
|
|
comparePixels(srcPixmap, read, reporter);
|
|
}
|
|
}
|
|
|
|
// Write pixels should not work with a read-only texture.
|
|
SkAutoPixmapStorage write;
|
|
write.alloc(srcPixmap.info());
|
|
fillPixels(&write, [&srcPixmap](int x, int y) { return ~*srcPixmap.addr32(); });
|
|
auto writeResult = surfContext->writePixels(srcPixmap.info(), write.addr(), 0, {0, 0});
|
|
REPORTER_ASSERT(reporter, writeResult == (ioType == kRW_GrIOType));
|
|
// Try the low level write.
|
|
context->flush();
|
|
auto gpuWriteResult = context->priv().getGpu()->writePixels(
|
|
proxy->peekTexture(), 0, 0, kSize, kSize, GrColorType::kRGBA_8888,
|
|
GrColorType::kRGBA_8888, write.addr32(),
|
|
kSize * GrColorTypeBytesPerPixel(GrColorType::kRGBA_8888));
|
|
REPORTER_ASSERT(reporter, gpuWriteResult == (ioType == kRW_GrIOType));
|
|
|
|
// Copies should not work with a read-only texture
|
|
auto copySrc =
|
|
proxyProvider->createTextureProxy(SkImage::MakeFromRaster(write, nullptr, nullptr),
|
|
1, SkBudgeted::kYes, SkBackingFit::kExact);
|
|
REPORTER_ASSERT(reporter, copySrc);
|
|
auto copyResult = surfContext->testCopy(copySrc.get());
|
|
REPORTER_ASSERT(reporter, copyResult == (ioType == kRW_GrIOType));
|
|
// Try the low level copy.
|
|
context->flush();
|
|
auto gpuCopyResult = context->priv().getGpu()->copySurface(
|
|
proxy->peekTexture(), copySrc->peekTexture(), SkIRect::MakeWH(kSize, kSize),
|
|
{0, 0});
|
|
REPORTER_ASSERT(reporter, gpuCopyResult == (ioType == kRW_GrIOType));
|
|
|
|
// Mip regen should not work with a read only texture.
|
|
if (context->priv().caps()->mipMapSupport()) {
|
|
delete_backend_texture(context, backendTex);
|
|
backendTex = context->createBackendTexture(
|
|
kSize, kSize, kRGBA_8888_SkColorType,
|
|
SkColors::kTransparent, GrMipMapped::kYes, GrRenderable::kYes,
|
|
GrProtected::kNo);
|
|
proxy = proxyProvider->wrapBackendTexture(backendTex, GrColorType::kRGBA_8888,
|
|
kTopLeft_GrSurfaceOrigin,
|
|
kBorrow_GrWrapOwnership, GrWrapCacheable::kNo,
|
|
ioType);
|
|
context->flush();
|
|
proxy->peekTexture()->texturePriv().markMipMapsDirty(); // avoids assert in GrGpu.
|
|
auto regenResult =
|
|
context->priv().getGpu()->regenerateMipMapLevels(proxy->peekTexture());
|
|
REPORTER_ASSERT(reporter, regenResult == (ioType == kRW_GrIOType));
|
|
}
|
|
delete_backend_texture(context, backendTex);
|
|
}
|
|
}
|
|
|
|
static const int kSurfSize = 10;
|
|
|
|
static sk_sp<GrTexture> make_wrapped_texture(GrContext* context, GrRenderable renderable) {
|
|
auto backendTexture = context->createBackendTexture(
|
|
kSurfSize, kSurfSize, kRGBA_8888_SkColorType, SkColors::kTransparent, GrMipMapped::kNo,
|
|
renderable, GrProtected::kNo);
|
|
sk_sp<GrTexture> texture;
|
|
if (GrRenderable::kYes == renderable) {
|
|
texture = context->priv().resourceProvider()->wrapRenderableBackendTexture(
|
|
backendTexture, 1, GrColorType::kRGBA_8888, kBorrow_GrWrapOwnership,
|
|
GrWrapCacheable::kNo);
|
|
} else {
|
|
texture = context->priv().resourceProvider()->wrapBackendTexture(
|
|
backendTexture, GrColorType::kRGBA_8888, kBorrow_GrWrapOwnership,
|
|
GrWrapCacheable::kNo, kRW_GrIOType);
|
|
}
|
|
// Add a release proc that deletes the GrBackendTexture.
|
|
struct ReleaseContext {
|
|
GrContext* fContext;
|
|
GrBackendTexture fBackendTexture;
|
|
};
|
|
auto release = [](void* rc) {
|
|
auto releaseContext = static_cast<ReleaseContext*>(rc);
|
|
auto context = releaseContext->fContext;
|
|
context->deleteBackendTexture(releaseContext->fBackendTexture);
|
|
delete releaseContext;
|
|
};
|
|
texture->setRelease(release, new ReleaseContext{context, backendTexture});
|
|
return texture;
|
|
}
|
|
|
|
static sk_sp<GrTexture> make_normal_texture(GrContext* context, GrRenderable renderable) {
|
|
GrSurfaceDesc desc;
|
|
desc.fConfig = kRGBA_8888_GrPixelConfig;
|
|
desc.fWidth = desc.fHeight = kSurfSize;
|
|
auto format =
|
|
context->priv().caps()->getDefaultBackendFormat(GrColorType::kRGBA_8888, renderable);
|
|
return context->priv().resourceProvider()->createTexture(
|
|
desc, format, renderable, 1, GrMipMapped::kNo, SkBudgeted::kNo, GrProtected::kNo);
|
|
}
|
|
|
|
DEF_GPUTEST(TextureIdleProcTest, reporter, options) {
|
|
// Various ways of making textures.
|
|
auto makeWrapped = [](GrContext* context) {
|
|
return make_wrapped_texture(context, GrRenderable::kNo);
|
|
};
|
|
auto makeWrappedRenderable = [](GrContext* context) {
|
|
return make_wrapped_texture(context, GrRenderable::kYes);
|
|
};
|
|
auto makeNormal = [](GrContext* context) {
|
|
return make_normal_texture(context, GrRenderable::kNo);
|
|
};
|
|
auto makeRenderable = [](GrContext* context) {
|
|
return make_normal_texture(context, GrRenderable::kYes);
|
|
};
|
|
|
|
std::function<sk_sp<GrTexture>(GrContext*)> makers[] = {makeWrapped, makeWrappedRenderable,
|
|
makeNormal, makeRenderable};
|
|
|
|
// Add a unique key, or not.
|
|
auto addKey = [](GrTexture* texture) {
|
|
static uint32_t gN = 0;
|
|
static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain();
|
|
GrUniqueKey key;
|
|
GrUniqueKey::Builder builder(&key, kDomain, 1);
|
|
builder[0] = gN++;
|
|
builder.finish();
|
|
texture->resourcePriv().setUniqueKey(key);
|
|
};
|
|
auto dontAddKey = [](GrTexture* texture) {};
|
|
std::function<void(GrTexture*)> keyAdders[] = {addKey, dontAddKey};
|
|
|
|
for (const auto& m : makers) {
|
|
for (const auto& keyAdder : keyAdders) {
|
|
for (int type = 0; type < sk_gpu_test::GrContextFactory::kContextTypeCnt; ++type) {
|
|
sk_gpu_test::GrContextFactory factory;
|
|
auto contextType = static_cast<sk_gpu_test::GrContextFactory::ContextType>(type);
|
|
GrContext* context = factory.get(contextType);
|
|
if (!context) {
|
|
continue;
|
|
}
|
|
|
|
// The callback we add simply adds an integer to a set.
|
|
std::set<int> idleIDs;
|
|
struct Context {
|
|
std::set<int>* fIdleIDs;
|
|
int fNum;
|
|
};
|
|
auto proc = [](void* context) {
|
|
static_cast<Context*>(context)->fIdleIDs->insert(
|
|
static_cast<Context*>(context)->fNum);
|
|
delete static_cast<Context*>(context);
|
|
};
|
|
|
|
// Makes a texture, possibly adds a key, and sets the callback.
|
|
auto make = [&m, &keyAdder, &proc, &idleIDs](GrContext* context, int num) {
|
|
sk_sp<GrTexture> texture = m(context);
|
|
texture->addIdleProc(proc, new Context{&idleIDs, num},
|
|
GrTexture::IdleState::kFinished);
|
|
keyAdder(texture.get());
|
|
return texture;
|
|
};
|
|
|
|
auto texture = make(context, 1);
|
|
REPORTER_ASSERT(reporter, idleIDs.find(1) == idleIDs.end());
|
|
auto renderable = GrRenderable(SkToBool(texture->asRenderTarget()));
|
|
auto backendFormat = texture->backendFormat();
|
|
texture.reset();
|
|
REPORTER_ASSERT(reporter, idleIDs.find(1) != idleIDs.end());
|
|
|
|
texture = make(context, 2);
|
|
int w = texture->width();
|
|
int h = texture->height();
|
|
SkImageInfo info =
|
|
SkImageInfo::Make(w, h, kRGBA_8888_SkColorType, kPremul_SkAlphaType);
|
|
auto rt = SkSurface::MakeRenderTarget(context, SkBudgeted::kNo, info, 0, nullptr);
|
|
auto rtc = rt->getCanvas()->internal_private_accessTopLayerRenderTargetContext();
|
|
auto singleUseLazyCB = [&texture](GrResourceProvider* rp) {
|
|
auto mode = GrSurfaceProxy::LazyInstantiationKeyMode::kSynced;
|
|
if (texture->getUniqueKey().isValid()) {
|
|
mode = GrSurfaceProxy::LazyInstantiationKeyMode::kUnsynced;
|
|
}
|
|
return GrSurfaceProxy::LazyCallbackResult{std::move(texture), true, mode};
|
|
};
|
|
GrSurfaceDesc desc;
|
|
desc.fWidth = w;
|
|
desc.fHeight = h;
|
|
desc.fConfig = kRGBA_8888_GrPixelConfig;
|
|
SkBudgeted budgeted;
|
|
if (texture->resourcePriv().budgetedType() == GrBudgetedType::kBudgeted) {
|
|
budgeted = SkBudgeted::kYes;
|
|
} else {
|
|
budgeted = SkBudgeted::kNo;
|
|
}
|
|
auto proxy = context->priv().proxyProvider()->createLazyProxy(
|
|
singleUseLazyCB, backendFormat, desc, renderable, 1,
|
|
GrSurfaceOrigin::kTopLeft_GrSurfaceOrigin, GrMipMapped::kNo,
|
|
GrMipMapsStatus::kNotAllocated, GrInternalSurfaceFlags ::kNone,
|
|
SkBackingFit::kExact, budgeted, GrProtected::kNo,
|
|
GrSurfaceProxy::UseAllocator::kYes);
|
|
rtc->drawTexture(GrNoClip(), proxy, GrSamplerState::Filter::kNearest,
|
|
SkBlendMode::kSrcOver, SkPMColor4f(), SkRect::MakeWH(w, h),
|
|
SkRect::MakeWH(w, h), GrAA::kNo, GrQuadAAFlags::kNone,
|
|
SkCanvas::kFast_SrcRectConstraint, SkMatrix::I(), nullptr);
|
|
// We still have the proxy, which should remain instantiated, thereby keeping the
|
|
// texture not purgeable.
|
|
REPORTER_ASSERT(reporter, idleIDs.find(2) == idleIDs.end());
|
|
context->flush();
|
|
REPORTER_ASSERT(reporter, idleIDs.find(2) == idleIDs.end());
|
|
context->priv().getGpu()->testingOnly_flushGpuAndSync();
|
|
REPORTER_ASSERT(reporter, idleIDs.find(2) == idleIDs.end());
|
|
|
|
// This time we move the proxy into the draw.
|
|
rtc->drawTexture(GrNoClip(), std::move(proxy), GrSamplerState::Filter::kNearest,
|
|
SkBlendMode::kSrcOver, SkPMColor4f(), SkRect::MakeWH(w, h),
|
|
SkRect::MakeWH(w, h), GrAA::kNo, GrQuadAAFlags::kNone,
|
|
SkCanvas::kFast_SrcRectConstraint, SkMatrix::I(), nullptr);
|
|
REPORTER_ASSERT(reporter, idleIDs.find(2) == idleIDs.end());
|
|
context->flush();
|
|
context->priv().getGpu()->testingOnly_flushGpuAndSync();
|
|
// Now that the draw is fully consumed by the GPU, the texture should be idle.
|
|
REPORTER_ASSERT(reporter, idleIDs.find(2) != idleIDs.end());
|
|
|
|
// Make sure we make the call during various shutdown scenarios where the texture
|
|
// might persist after context is destroyed, abandoned, etc. We test three
|
|
// variations of each scenario. One where the texture is just created. Another,
|
|
// where the texture has been used in a draw and then the context is flushed. And
|
|
// one where the the texture was drawn but the context is not flushed.
|
|
// In each scenario we test holding a ref beyond the context shutdown and not.
|
|
|
|
// These tests are difficult to get working with Vulkan. See http://skbug.com/8705
|
|
// and http://skbug.com/8275
|
|
GrBackendApi api = sk_gpu_test::GrContextFactory::ContextTypeBackend(contextType);
|
|
if (api == GrBackendApi::kVulkan) {
|
|
continue;
|
|
}
|
|
int id = 3;
|
|
enum class DrawType {
|
|
kNoDraw,
|
|
kDraw,
|
|
kDrawAndFlush,
|
|
};
|
|
for (auto drawType :
|
|
{DrawType::kNoDraw, DrawType::kDraw, DrawType::kDrawAndFlush}) {
|
|
for (bool unrefFirst : {false, true}) {
|
|
auto possiblyDrawAndFlush = [&context, &texture, drawType, unrefFirst, w,
|
|
h] {
|
|
if (drawType == DrawType::kNoDraw) {
|
|
return;
|
|
}
|
|
SkImageInfo info = SkImageInfo::Make(w, h, kRGBA_8888_SkColorType,
|
|
kPremul_SkAlphaType);
|
|
auto rt = SkSurface::MakeRenderTarget(context, SkBudgeted::kNo, info, 0,
|
|
nullptr);
|
|
auto rtc = rt->getCanvas()
|
|
->internal_private_accessTopLayerRenderTargetContext();
|
|
auto proxy = context->priv().proxyProvider()->testingOnly_createWrapped(
|
|
texture, GrColorType::kRGBA_8888, kTopLeft_GrSurfaceOrigin);
|
|
rtc->drawTexture(
|
|
GrNoClip(), proxy, GrSamplerState::Filter::kNearest,
|
|
SkBlendMode::kSrcOver, SkPMColor4f(), SkRect::MakeWH(w, h),
|
|
SkRect::MakeWH(w, h), GrAA::kNo, GrQuadAAFlags::kNone,
|
|
SkCanvas::kFast_SrcRectConstraint, SkMatrix::I(), nullptr);
|
|
if (drawType == DrawType::kDrawAndFlush) {
|
|
context->flush();
|
|
}
|
|
if (unrefFirst) {
|
|
texture.reset();
|
|
}
|
|
};
|
|
texture = make(context, id);
|
|
possiblyDrawAndFlush();
|
|
context->abandonContext();
|
|
texture.reset();
|
|
REPORTER_ASSERT(reporter, idleIDs.find(id) != idleIDs.end());
|
|
factory.destroyContexts();
|
|
context = factory.get(contextType);
|
|
++id;
|
|
|
|
// Similar to previous, but reset the texture after the context was
|
|
// abandoned and then destroyed.
|
|
texture = make(context, id);
|
|
possiblyDrawAndFlush();
|
|
context->abandonContext();
|
|
factory.destroyContexts();
|
|
texture.reset();
|
|
REPORTER_ASSERT(reporter, idleIDs.find(id) != idleIDs.end());
|
|
context = factory.get(contextType);
|
|
id++;
|
|
|
|
texture = make(context, id);
|
|
possiblyDrawAndFlush();
|
|
factory.destroyContexts();
|
|
texture.reset();
|
|
REPORTER_ASSERT(reporter, idleIDs.find(id) != idleIDs.end());
|
|
context = factory.get(contextType);
|
|
id++;
|
|
|
|
texture = make(context, id);
|
|
possiblyDrawAndFlush();
|
|
factory.releaseResourcesAndAbandonContexts();
|
|
texture.reset();
|
|
REPORTER_ASSERT(reporter, idleIDs.find(id) != idleIDs.end());
|
|
context = factory.get(contextType);
|
|
id++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Tests an idle proc that unrefs another resource down to zero.
|
|
DEF_GPUTEST_FOR_ALL_CONTEXTS(TextureIdleProcCacheManipulationTest, reporter, contextInfo) {
|
|
GrContext* context = contextInfo.grContext();
|
|
|
|
// idle proc that releases another texture.
|
|
auto idleProc = [](void* texture) { reinterpret_cast<GrTexture*>(texture)->unref(); };
|
|
|
|
for (const auto& idleMaker : {make_wrapped_texture, make_normal_texture}) {
|
|
for (const auto& otherMaker : {make_wrapped_texture, make_normal_texture}) {
|
|
for (auto idleState :
|
|
{GrTexture::IdleState::kFlushed, GrTexture::IdleState::kFinished}) {
|
|
auto idleTexture = idleMaker(context, GrRenderable::kNo);
|
|
auto otherTexture = otherMaker(context, GrRenderable::kNo);
|
|
otherTexture->ref();
|
|
idleTexture->addIdleProc(idleProc, otherTexture.get(), idleState);
|
|
otherTexture.reset();
|
|
idleTexture.reset();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Similar to above but more complicated. This flushes the context from the idle proc.
|
|
// crbug.com/933526.
|
|
DEF_GPUTEST_FOR_ALL_CONTEXTS(TextureIdleProcFlushTest, reporter, contextInfo) {
|
|
GrContext* context = contextInfo.grContext();
|
|
|
|
// idle proc that flushes the context.
|
|
auto idleProc = [](void* context) { reinterpret_cast<GrContext*>(context)->flush(); };
|
|
|
|
for (const auto& idleMaker : {make_wrapped_texture, make_normal_texture}) {
|
|
for (auto idleState : {GrTexture::IdleState::kFlushed, GrTexture::IdleState::kFinished}) {
|
|
auto idleTexture = idleMaker(context, GrRenderable::kNo);
|
|
idleTexture->addIdleProc(idleProc, context, idleState);
|
|
auto info = SkImageInfo::Make(10, 10, kRGBA_8888_SkColorType, kPremul_SkAlphaType);
|
|
auto surf = SkSurface::MakeRenderTarget(context, SkBudgeted::kNo, info, 1, nullptr);
|
|
// We'll draw two images to the canvas. One is a normal texture-backed image. The other
|
|
// is a wrapped-texture backed image.
|
|
surf->getCanvas()->clear(SK_ColorWHITE);
|
|
auto img1 = surf->makeImageSnapshot();
|
|
|
|
GrBackendTexture backendTexture;
|
|
|
|
if (!create_backend_texture(context, &backendTexture, info, SkColors::kBlack,
|
|
GrMipMapped::kNo, GrRenderable::kNo)) {
|
|
REPORTER_ASSERT(reporter, false);
|
|
continue;
|
|
}
|
|
|
|
auto img2 = SkImage::MakeFromTexture(context, backendTexture, kTopLeft_GrSurfaceOrigin,
|
|
info.colorType(), info.alphaType(), nullptr);
|
|
surf->getCanvas()->drawImage(std::move(img1), 0, 0);
|
|
surf->getCanvas()->drawImage(std::move(img2), 1, 1);
|
|
idleTexture.reset();
|
|
|
|
delete_backend_texture(context, backendTexture);
|
|
}
|
|
}
|
|
}
|
|
|
|
DEF_GPUTEST_FOR_ALL_CONTEXTS(TextureIdleProcRerefTest, reporter, contextInfo) {
|
|
GrContext* context = contextInfo.grContext();
|
|
// idle proc that refs the texture
|
|
auto idleProc = [](void* texture) { reinterpret_cast<GrTexture*>(texture)->ref(); };
|
|
// release proc to check whether the texture was released or not.
|
|
auto releaseProc = [](void* isReleased) { *reinterpret_cast<bool*>(isReleased) = true; };
|
|
for (auto idleState : {GrTexture::IdleState::kFlushed, GrTexture::IdleState::kFinished}) {
|
|
bool isReleased = false;
|
|
auto idleTexture = make_normal_texture(context, GrRenderable::kNo);
|
|
// This test assumes the texture won't be cached (or else the release proc doesn't get
|
|
// called).
|
|
idleTexture->resourcePriv().removeScratchKey();
|
|
context->flush();
|
|
idleTexture->addIdleProc(idleProc, idleTexture.get(), idleState);
|
|
idleTexture->setRelease(releaseProc, &isReleased);
|
|
auto* raw = idleTexture.get();
|
|
idleTexture.reset();
|
|
REPORTER_ASSERT(reporter, !isReleased);
|
|
raw->unref();
|
|
REPORTER_ASSERT(reporter, isReleased);
|
|
}
|
|
}
|
|
|
|
DEF_GPUTEST_FOR_ALL_CONTEXTS(TextureIdleStateTest, reporter, contextInfo) {
|
|
GrContext* context = contextInfo.grContext();
|
|
for (const auto& idleMaker : {make_wrapped_texture, make_normal_texture}) {
|
|
auto idleTexture = idleMaker(context, GrRenderable::kNo);
|
|
|
|
uint32_t flags = 0;
|
|
static constexpr uint32_t kFlushFlag = 0x1;
|
|
static constexpr uint32_t kFinishFlag = 0x2;
|
|
auto flushProc = [](void* flags) { *static_cast<uint32_t*>(flags) |= kFlushFlag; };
|
|
auto finishProc = [](void* flags) { *static_cast<uint32_t*>(flags) |= kFinishFlag; };
|
|
idleTexture->addIdleProc(flushProc, &flags, GrTexture::IdleState::kFlushed);
|
|
idleTexture->addIdleProc(finishProc, &flags, GrTexture::IdleState::kFinished);
|
|
|
|
// Insert a copy from idleTexture to another texture so that we have some queued IO on
|
|
// idleTexture.
|
|
SkImageInfo info = SkImageInfo::Make(kSurfSize, kSurfSize, kRGBA_8888_SkColorType,
|
|
kPremul_SkAlphaType);
|
|
auto rt = SkSurface::MakeRenderTarget(context, SkBudgeted::kNo, info, 0, nullptr);
|
|
auto rtc = rt->getCanvas()->internal_private_accessTopLayerRenderTargetContext();
|
|
auto proxy = context->priv().proxyProvider()->testingOnly_createWrapped(
|
|
std::move(idleTexture), GrColorType::kRGBA_8888, rtc->asSurfaceProxy()->origin());
|
|
context->flush();
|
|
SkAssertResult(rtc->testCopy(proxy.get()));
|
|
proxy.reset();
|
|
REPORTER_ASSERT(reporter, flags == 0);
|
|
|
|
// After a flush we expect idleTexture to have reached the kFlushed state on all backends.
|
|
// On "managed" backends we expect it to reach kFinished as well. On Vulkan, the only
|
|
// current "unmanaged" backend, we *may* need a sync to reach kFinished.
|
|
context->flush();
|
|
if (contextInfo.backend() == kVulkan_GrBackend) {
|
|
REPORTER_ASSERT(reporter, flags & kFlushFlag);
|
|
} else {
|
|
REPORTER_ASSERT(reporter, flags == (kFlushFlag | kFinishFlag));
|
|
}
|
|
context->priv().getGpu()->testingOnly_flushGpuAndSync();
|
|
REPORTER_ASSERT(reporter, flags == (kFlushFlag | kFinishFlag));
|
|
}
|
|
}
|