skia2/tests/TransferPixelsTest.cpp
Greg Daniel 797efcac71 Add option to GrContext::flush for GrPrepareForExternalIORequests.
This will allow clients to specify a set of SkImages and SkSurfaces that they
want transitioned to a state which they can use for their own external IO.
Specifically for Vulkan this will move the surface and images back to the queues
in which they were originally wrapped or created with.

Bug: skia:8802
Change-Id: I6a76c4c4a333a8e752632d349899f5fd9921329d
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/210460
Commit-Queue: Greg Daniel <egdaniel@google.com>
Reviewed-by: Brian Salomon <bsalomon@google.com>
2019-05-09 18:50:06 +00:00

364 lines
15 KiB
C++

/*
* Copyright 2017 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
// This is a GPU-backend specific test. It relies on static intializers to work
#include "include/core/SkTypes.h"
#include "include/core/SkSurface.h"
#include "include/gpu/GrTexture.h"
#include "include/private/GrSurfaceProxy.h"
#include "src/gpu/GrContextPriv.h"
#include "src/gpu/GrGpu.h"
#include "src/gpu/GrResourceProvider.h"
#include "src/gpu/SkGr.h"
#include "tests/Test.h"
#include "tools/gpu/GrContextFactory.h"
using sk_gpu_test::GrContextFactory;
void fill_transfer_data(int left, int top, int width, int height, int bufferWidth,
GrColor* data) {
// build red-green gradient
for (int j = top; j < top + height; ++j) {
for (int i = left; i < left + width; ++i) {
unsigned int red = (unsigned int)(256.f*((i - left) / (float)width));
unsigned int green = (unsigned int)(256.f*((j - top) / (float)height));
data[i + j*bufferWidth] = GrColorPackRGBA(red - (red>>8),
green - (green>>8), 0xff, 0xff);
}
}
}
bool do_buffers_contain_same_values(const GrColor* bufferA,
const GrColor* bufferB,
int width,
int height,
size_t rowBytesA,
size_t rowBytesB,
bool swiz) {
for (int j = 0; j < height; ++j) {
for (int i = 0; i < width; ++i) {
auto colorA = bufferA[i];
if (swiz) {
colorA = GrColorPackRGBA(GrColorUnpackB(colorA), GrColorUnpackG(colorA),
GrColorUnpackR(colorA), GrColorUnpackA(colorA));
}
if (colorA != bufferB[i]) {
return false;
}
}
bufferA = reinterpret_cast<const GrColor*>(reinterpret_cast<const char*>(bufferA) +
rowBytesA);
bufferB = reinterpret_cast<const GrColor*>(reinterpret_cast<const char*>(bufferB) +
rowBytesB);
}
return true;
}
void basic_transfer_to_test(skiatest::Reporter* reporter, GrContext* context, GrColorType colorType,
bool renderTarget) {
if (GrCaps::kNone_MapFlags == context->priv().caps()->mapBufferFlags()) {
return;
}
auto resourceProvider = context->priv().resourceProvider();
GrGpu* gpu = context->priv().getGpu();
// set up the data
const int kTextureWidth = 16;
const int kTextureHeight = 16;
#ifdef SK_BUILD_FOR_IOS
// UNPACK_ROW_LENGTH is broken on iOS so rowBytes needs to match data width
const int kBufferWidth = GrBackendApi::kOpenGL == context->backend() ? 16 : 20;
#else
const int kBufferWidth = 20;
#endif
const int kBufferHeight = 16;
size_t rowBytes = kBufferWidth * sizeof(GrColor);
SkAutoTMalloc<GrColor> srcBuffer(kBufferWidth*kBufferHeight);
SkAutoTMalloc<GrColor> dstBuffer(kBufferWidth*kBufferHeight);
fill_transfer_data(0, 0, kTextureWidth, kTextureHeight, kBufferWidth, srcBuffer.get());
// create and fill transfer buffer
size_t size = rowBytes*kBufferHeight;
sk_sp<GrGpuBuffer> buffer(resourceProvider->createBuffer(size, GrGpuBufferType::kXferCpuToGpu,
kDynamic_GrAccessPattern));
if (!buffer) {
return;
}
void* data = buffer->map();
memcpy(data, srcBuffer.get(), size);
buffer->unmap();
for (auto srgbEncoding : {GrSRGBEncoded::kNo, GrSRGBEncoded::kYes}) {
// create texture
GrSurfaceDesc desc;
desc.fFlags = renderTarget ? kRenderTarget_GrSurfaceFlag : kNone_GrSurfaceFlags;
desc.fWidth = kTextureWidth;
desc.fHeight = kTextureHeight;
desc.fConfig = GrColorTypeToPixelConfig(colorType, srgbEncoding);
desc.fSampleCnt = 1;
if (kUnknown_GrPixelConfig == desc.fConfig) {
SkASSERT(GrSRGBEncoded::kYes == srgbEncoding);
continue;
}
if (!context->priv().caps()->isConfigTexturable(desc.fConfig) ||
(renderTarget && !context->priv().caps()->isConfigRenderable(desc.fConfig))) {
continue;
}
sk_sp<GrTexture> tex = resourceProvider->createTexture(
desc, SkBudgeted::kNo, GrResourceProvider::Flags::kNoPendingIO);
if (!tex) {
continue;
}
//////////////////////////
// transfer full data
bool result;
result = gpu->transferPixelsTo(tex.get(), 0, 0, kTextureWidth, kTextureHeight, colorType,
buffer.get(), 0, rowBytes);
REPORTER_ASSERT(reporter, result);
memset(dstBuffer.get(), 0xCDCD, size);
result = gpu->readPixels(tex.get(), 0, 0, kTextureWidth, kTextureHeight, colorType,
dstBuffer.get(), rowBytes);
if (result) {
REPORTER_ASSERT(reporter, do_buffers_contain_same_values(srcBuffer,
dstBuffer,
kTextureWidth,
kTextureHeight,
rowBytes,
rowBytes,
false));
}
//////////////////////////
// transfer partial data
#ifdef SK_BUILD_FOR_IOS
// UNPACK_ROW_LENGTH is broken on iOS so we can't do partial transfers
if (GrBackendApi::kOpenGL == context->backend()) {
continue;
}
#endif
const int kLeft = 2;
const int kTop = 10;
const int kWidth = 10;
const int kHeight = 2;
// change color of subrectangle
fill_transfer_data(kLeft, kTop, kWidth, kHeight, kBufferWidth, srcBuffer.get());
data = buffer->map();
memcpy(data, srcBuffer.get(), size);
buffer->unmap();
size_t offset = sizeof(GrColor) * (kTop * kBufferWidth + kLeft);
result = gpu->transferPixelsTo(tex.get(), kLeft, kTop, kWidth, kHeight, colorType,
buffer.get(), offset, rowBytes);
REPORTER_ASSERT(reporter, result);
memset(dstBuffer.get(), 0xCDCD, size);
result = gpu->readPixels(tex.get(), 0, 0, kTextureWidth, kTextureHeight, colorType,
dstBuffer.get(), rowBytes);
if (result) {
REPORTER_ASSERT(reporter, do_buffers_contain_same_values(srcBuffer,
dstBuffer,
kTextureWidth,
kTextureHeight,
rowBytes,
rowBytes,
false));
}
}
}
void basic_transfer_from_test(skiatest::Reporter* reporter, const sk_gpu_test::ContextInfo& ctxInfo,
GrColorType colorType, bool renderTarget) {
auto context = ctxInfo.grContext();
if (GrCaps::kNone_MapFlags == context->priv().caps()->mapBufferFlags()) {
return;
}
// On OpenGL ES it may not be possible to read back in to BGRA becagse GL_RGBA/GL_UNSIGNED_BYTE
// may be the only allowed format/type params to glReadPixels. So read back into GL_RGBA.
// TODO(bsalomon): Make this work in GrGLGpu.
auto readColorType = colorType;
if (GrColorType::kBGRA_8888 == colorType &&
ctxInfo.type() == sk_gpu_test::GrContextFactory::kGLES_ContextType) {
readColorType = GrColorType::kRGBA_8888;
}
auto resourceProvider = context->priv().resourceProvider();
GrGpu* gpu = context->priv().getGpu();
const int kTextureWidth = 16;
const int kTextureHeight = 16;
// We'll do a full texture read into the buffer followed by a partial read. These values
// describe the partial read subrect.
const int kPartialLeft = 2;
const int kPartialTop = 10;
const int kPartialWidth = 10;
const int kPartialHeight = 2;
size_t bpp = GrColorTypeBytesPerPixel(readColorType);
size_t fullBufferRowBytes = kTextureWidth * bpp;
size_t partialBufferRowBytes = kPartialWidth * bpp;
size_t offsetAlignment = context->priv().caps()->transferFromOffsetAlignment(readColorType);
SkASSERT(offsetAlignment);
size_t bufferSize = fullBufferRowBytes * kTextureHeight;
// Arbitrary starting offset for the partial read.
size_t partialReadOffset = GrSizeAlignUp(11, offsetAlignment);
bufferSize = SkTMax(bufferSize, partialReadOffset + partialBufferRowBytes * kPartialHeight);
sk_sp<GrGpuBuffer> buffer(resourceProvider->createBuffer(
bufferSize, GrGpuBufferType::kXferGpuToCpu, kDynamic_GrAccessPattern));
REPORTER_ASSERT(reporter, buffer);
if (!buffer) {
return;
}
int expectedTransferCnt = 0;
gpu->stats()->reset();
for (auto srgbEncoding : {GrSRGBEncoded::kNo, GrSRGBEncoded::kYes}) {
// create texture
GrSurfaceDesc desc;
desc.fFlags = renderTarget ? kRenderTarget_GrSurfaceFlag : kNone_GrSurfaceFlags;
desc.fWidth = kTextureWidth;
desc.fHeight = kTextureHeight;
desc.fConfig = GrColorTypeToPixelConfig(colorType, srgbEncoding);
desc.fSampleCnt = 1;
if (kUnknown_GrPixelConfig == desc.fConfig) {
SkASSERT(GrSRGBEncoded::kYes == srgbEncoding);
continue;
}
if (!context->priv().caps()->isConfigTexturable(desc.fConfig) ||
(renderTarget && !context->priv().caps()->isConfigRenderable(desc.fConfig))) {
continue;
}
SkAutoTMalloc<GrColor> textureData(kTextureWidth * kTextureHeight);
size_t textureDataRowBytes = kTextureWidth * sizeof(GrColor);
fill_transfer_data(0, 0, kTextureWidth, kTextureHeight, kTextureWidth, textureData.get());
GrMipLevel data;
data.fPixels = textureData.get();
data.fRowBytes = kTextureWidth * sizeof(GrColor);
sk_sp<GrTexture> tex = resourceProvider->createTexture(desc, SkBudgeted::kNo, &data, 1);
if (!tex) {
continue;
}
//////////////////////////
// transfer full data
bool result = gpu->transferPixelsFrom(tex.get(), 0, 0, kTextureWidth, kTextureHeight,
readColorType, buffer.get(), 0);
if (!result) {
ERRORF(reporter, "transferPixelsFrom failed.");
continue;
}
++expectedTransferCnt;
GrFlushInfo flushInfo;
flushInfo.fFlags = kSyncCpu_GrFlushFlag;
if (context->priv().caps()->mapBufferFlags() & GrCaps::kAsyncRead_MapFlag) {
gpu->finishFlush(nullptr, 0, SkSurface::BackendSurfaceAccess::kNoAccess, flushInfo,
GrPrepareForExternalIORequests());
}
const auto* map = reinterpret_cast<const GrColor*>(buffer->map());
REPORTER_ASSERT(reporter, map);
if (!map) {
continue;
}
REPORTER_ASSERT(reporter, do_buffers_contain_same_values(textureData.get(),
map,
kTextureWidth,
kTextureHeight,
textureDataRowBytes,
fullBufferRowBytes,
readColorType != colorType));
buffer->unmap();
///////////////////////
// Now test a partial read at an offset into the buffer.
result = gpu->transferPixelsFrom(tex.get(), kPartialLeft, kPartialTop, kPartialWidth,
kPartialHeight, readColorType, buffer.get(),
partialReadOffset);
if (!result) {
ERRORF(reporter, "transferPixelsFrom failed.");
continue;
}
++expectedTransferCnt;
if (context->priv().caps()->mapBufferFlags() & GrCaps::kAsyncRead_MapFlag) {
gpu->finishFlush(nullptr, 0, SkSurface::BackendSurfaceAccess::kNoAccess, flushInfo,
GrPrepareForExternalIORequests());
}
map = reinterpret_cast<const GrColor*>(buffer->map());
REPORTER_ASSERT(reporter, map);
if (!map) {
continue;
}
const GrColor* textureDataStart = reinterpret_cast<const GrColor*>(
reinterpret_cast<const char*>(textureData.get()) +
textureDataRowBytes * kPartialTop + sizeof(GrColor) * kPartialLeft);
const GrColor* bufferStart = reinterpret_cast<const GrColor*>(
reinterpret_cast<const char*>(map) + partialReadOffset);
REPORTER_ASSERT(reporter, do_buffers_contain_same_values(textureDataStart,
bufferStart,
kPartialWidth,
kPartialHeight,
textureDataRowBytes,
partialBufferRowBytes,
readColorType != colorType));
buffer->unmap();
}
#if GR_GPU_STATS
REPORTER_ASSERT(reporter, gpu->stats()->transfersFromSurface() == expectedTransferCnt);
#else
(void)expectedTransferCnt;
#endif
}
DEF_GPUTEST_FOR_RENDERING_CONTEXTS(TransferPixelsToTest, reporter, ctxInfo) {
if (!ctxInfo.grContext()->priv().caps()->transferBufferSupport()) {
return;
}
// RGBA
basic_transfer_to_test(reporter, ctxInfo.grContext(), GrColorType::kRGBA_8888, false);
basic_transfer_to_test(reporter, ctxInfo.grContext(), GrColorType::kRGBA_8888, true);
// BGRA
basic_transfer_to_test(reporter, ctxInfo.grContext(), GrColorType::kBGRA_8888, false);
basic_transfer_to_test(reporter, ctxInfo.grContext(), GrColorType::kBGRA_8888, true);
}
// TODO(bsalomon): Metal
DEF_GPUTEST_FOR_RENDERING_CONTEXTS(TransferPixelsFromTest, reporter, ctxInfo) {
if (!ctxInfo.grContext()->priv().caps()->transferBufferSupport()) {
return;
}
// RGBA
basic_transfer_from_test(reporter, ctxInfo, GrColorType::kRGBA_8888, false);
basic_transfer_from_test(reporter, ctxInfo, GrColorType::kRGBA_8888, true);
// BGRA
basic_transfer_from_test(reporter, ctxInfo, GrColorType::kBGRA_8888, false);
basic_transfer_from_test(reporter, ctxInfo, GrColorType::kBGRA_8888, true);
}