797efcac71
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>
364 lines
15 KiB
C++
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);
|
|
}
|