Add unit test to test basic importing and drawing of AHardwareBuffers.
Bug: skia:
Change-Id: If601693109148a7d5c6e6855443a3401c36e5f01
Reviewed-on: https://skia-review.googlesource.com/153545
Commit-Queue: Greg Daniel <egdaniel@google.com>
Reviewed-by: Robert Phillips <robertphillips@google.com>
diff --git a/gn/tests.gni b/gn/tests.gni
index e829c70..88a7b6d 100644
--- a/gn/tests.gni
+++ b/gn/tests.gni
@@ -91,6 +91,7 @@
"$_tests/GpuLayerCacheTest.cpp",
"$_tests/GpuRectanizerTest.cpp",
"$_tests/GradientTest.cpp",
+ "$_tests/GrAHardwareBufferTest.cpp",
"$_tests/GrAllocatorTest.cpp",
"$_tests/GrCCPRTest.cpp",
"$_tests/GrContextAbandonTest.cpp",
diff --git a/src/gpu/GrCaps.cpp b/src/gpu/GrCaps.cpp
index c700335..4fe6416 100644
--- a/src/gpu/GrCaps.cpp
+++ b/src/gpu/GrCaps.cpp
@@ -58,6 +58,7 @@
fPreferClientSideDynamicBuffers = false;
fPreferFullscreenClears = false;
fMustClearUploadedBufferData = false;
+ fSupportsAHardwareBufferImages = false;
fSampleShadingSupport = false;
fFenceSyncSupport = false;
fCrossContextTextureSupport = false;
@@ -169,6 +170,7 @@
writer->appendBool("Prefer client-side dynamic buffers", fPreferClientSideDynamicBuffers);
writer->appendBool("Prefer fullscreen clears", fPreferFullscreenClears);
writer->appendBool("Must clear buffer memory", fMustClearUploadedBufferData);
+ writer->appendBool("Supports importing AHardwareBuffers", fSupportsAHardwareBufferImages);
writer->appendBool("Sample shading support", fSampleShadingSupport);
writer->appendBool("Fence sync support", fFenceSyncSupport);
writer->appendBool("Cross context texture support", fCrossContextTextureSupport);
diff --git a/src/gpu/GrCaps.h b/src/gpu/GrCaps.h
index 9e18c38..d70254b 100644
--- a/src/gpu/GrCaps.h
+++ b/src/gpu/GrCaps.h
@@ -228,6 +228,12 @@
is not initialized (even if not read by draw calls). */
bool mustClearUploadedBufferData() const { return fMustClearUploadedBufferData; }
+ /** Returns true if the given backend supports importing AHardwareBuffers via the
+ * GrAHardwarebufferImageGenerator. This will only ever be supported on Android devices with API
+ * level >= 26.
+ * */
+ bool supportsAHardwareBufferImages() const { return fSupportsAHardwareBufferImages; }
+
bool wireframeMode() const { return fWireframeMode; }
bool sampleShadingSupport() const { return fSampleShadingSupport; }
@@ -315,6 +321,7 @@
bool fPreferClientSideDynamicBuffers : 1;
bool fPreferFullscreenClears : 1;
bool fMustClearUploadedBufferData : 1;
+ bool fSupportsAHardwareBufferImages : 1;
// Driver workaround
bool fBlacklistCoverageCounting : 1;
diff --git a/src/gpu/GrResourceCache.cpp b/src/gpu/GrResourceCache.cpp
index 37f255d..e6f5994 100644
--- a/src/gpu/GrResourceCache.cpp
+++ b/src/gpu/GrResourceCache.cpp
@@ -169,6 +169,11 @@
void GrResourceCache::abandonAll() {
AutoValidate av(this);
+ for (int i = 0; i < fResourcesWaitingForFreeMsg.count(); ++i) {
+ fResourcesWaitingForFreeMsg[i]->abandon();
+ }
+ fResourcesWaitingForFreeMsg.reset();
+
while (fNonpurgeableResources.count()) {
GrGpuResource* back = *(fNonpurgeableResources.end() - 1);
SkASSERT(!back->wasDestroyed());
@@ -189,6 +194,7 @@
SkASSERT(!fBudgetedCount);
SkASSERT(!fBudgetedBytes);
SkASSERT(!fPurgeableBytes);
+ SkASSERT(!fResourcesWaitingForFreeMsg.count());
}
void GrResourceCache::releaseAll() {
@@ -201,6 +207,7 @@
for (int i = 0; i < fResourcesWaitingForFreeMsg.count(); ++i) {
fResourcesWaitingForFreeMsg[i]->unref();
}
+ fResourcesWaitingForFreeMsg.reset();
SkASSERT(fProxyProvider); // better have called setProxyProvider
// We must remove the uniqueKeys from the proxies here. While they possess a uniqueKey
@@ -227,6 +234,7 @@
SkASSERT(!fBudgetedCount);
SkASSERT(!fBudgetedBytes);
SkASSERT(!fPurgeableBytes);
+ SkASSERT(!fResourcesWaitingForFreeMsg.count());
}
class GrResourceCache::AvailableForScratchUse {
@@ -586,9 +594,14 @@
for (int i = 0; i < msgs.count(); ++i) {
SkASSERT(msgs[i].fOwningUniqueID == fContextUniqueID);
int index = fResourcesWaitingForFreeMsg.find(msgs[i].fResource);
- SkASSERT(index != -1);
- fResourcesWaitingForFreeMsg.removeShuffle(index);
- msgs[i].fResource->unref();
+ // If we called release or abandon on the GrContext we will have already released our ref on
+ // the GrGpuResource. If then the message arrives before the actual GrContext gets destroyed
+ // we will try to process the message when we destroy the GrContext. This protects us from
+ // trying to unref the resource twice.
+ if (index != -1) {
+ fResourcesWaitingForFreeMsg.removeShuffle(index);
+ msgs[i].fResource->unref();
+ }
}
}
diff --git a/src/gpu/gl/GrGLCaps.cpp b/src/gpu/gl/GrGLCaps.cpp
index 55e85d9..f32afb0 100644
--- a/src/gpu/gl/GrGLCaps.cpp
+++ b/src/gpu/gl/GrGLCaps.cpp
@@ -291,6 +291,10 @@
fClearTextureSupport = true;
}
+#if defined(SK_BUILD_FOR_ANDROID) && __ANDROID_API__ >= 26
+ fSupportsAHardwareBufferImages = true;
+#endif
+
/**************************************************************************
* GrShaderCaps fields
**************************************************************************/
diff --git a/tests/GrAHardwareBufferTest.cpp b/tests/GrAHardwareBufferTest.cpp
new file mode 100644
index 0000000..2c698cb
--- /dev/null
+++ b/tests/GrAHardwareBufferTest.cpp
@@ -0,0 +1,192 @@
+/*
+ * Copyright 2018 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 "SkTypes.h"
+
+#if SK_SUPPORT_GPU && defined(SK_BUILD_FOR_ANDROID) && __ANDROID_API__ >= 26
+
+#include "GrAHardwareBufferImageGenerator.h"
+#include "GrContext.h"
+#include "GrContextFactory.h"
+#include "GrContextPriv.h"
+#include "GrGpu.h"
+#include "SkImage.h"
+#include "SkSurface.h"
+#include "Test.h"
+
+#include <android/hardware_buffer.h>
+#include <cinttypes>
+
+static const int DEV_W = 16, DEV_H = 16;
+
+static SkPMColor get_src_color(int x, int y) {
+ SkASSERT(x >= 0 && x < DEV_W);
+ SkASSERT(y >= 0 && y < DEV_H);
+
+ U8CPU r = x;
+ U8CPU g = y;
+ U8CPU b = 0xc;
+
+ U8CPU a = 0xff;
+ switch ((x+y) % 5) {
+ case 0:
+ a = 0xff;
+ break;
+ case 1:
+ a = 0x80;
+ break;
+ case 2:
+ a = 0xCC;
+ break;
+ case 4:
+ a = 0x01;
+ break;
+ case 3:
+ a = 0x00;
+ break;
+ }
+ a = 0xff;
+ return SkPremultiplyARGBInline(a, r, g, b);
+}
+
+static SkBitmap make_src_bitmap() {
+ static SkBitmap bmp;
+ if (bmp.isNull()) {
+ bmp.allocN32Pixels(DEV_W, DEV_H);
+ intptr_t pixels = reinterpret_cast<intptr_t>(bmp.getPixels());
+ for (int y = 0; y < DEV_H; ++y) {
+ for (int x = 0; x < DEV_W; ++x) {
+ SkPMColor* pixel = reinterpret_cast<SkPMColor*>(
+ pixels + y * bmp.rowBytes() + x * bmp.bytesPerPixel());
+ *pixel = get_src_color(x, y);
+ }
+ }
+ }
+ return bmp;
+}
+
+static bool check_read(skiatest::Reporter* reporter, const SkBitmap& expectedBitmap,
+ const SkBitmap& actualBitmap) {
+ bool result = true;
+ for (int y = 0; y < DEV_H && result; ++y) {
+ for (int x = 0; x < DEV_W && result; ++x) {
+ const uint32_t srcPixel = *expectedBitmap.getAddr32(x, y);
+ const uint32_t dstPixel = *actualBitmap.getAddr32(x, y);
+ if (srcPixel != dstPixel) {
+ ERRORF(reporter, "Expected readback pixel (%d, %d) value 0x%08x, got 0x%08x.",
+ x, y, srcPixel, dstPixel);
+ result = false;
+ }
+ }
+ }
+ return result;
+}
+
+static void cleanup_resources(AHardwareBuffer* buffer) {
+ if (buffer) {
+ AHardwareBuffer_release(buffer);
+ }
+}
+
+// Basic test to make sure we can import an AHardwareBuffer into an SkImage and draw it into a
+// surface.
+DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrAHardwareBuffer_BasicDrawTest,
+ reporter, context_info) {
+ GrContext* context = context_info.grContext();
+ if (!context->contextPriv().caps()->supportsAHardwareBufferImages()) {
+ return;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Setup SkBitmaps
+ ///////////////////////////////////////////////////////////////////////////
+
+ const SkBitmap srcBitmap = make_src_bitmap();
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Setup AHardwareBuffer
+ ///////////////////////////////////////////////////////////////////////////
+
+ AHardwareBuffer* buffer = nullptr;
+
+ AHardwareBuffer_Desc hwbDesc;
+ hwbDesc.width = DEV_W;
+ hwbDesc.height = DEV_H;
+ hwbDesc.layers = 1;
+ hwbDesc.usage = AHARDWAREBUFFER_USAGE_CPU_READ_NEVER |
+ AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN |
+ AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE;
+ hwbDesc.format = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM;
+ // The following three are not used in the allocate
+ hwbDesc.stride = 0;
+ hwbDesc.rfu0= 0;
+ hwbDesc.rfu1= 0;
+
+ if (int error = AHardwareBuffer_allocate(&hwbDesc, &buffer)) {
+ ERRORF(reporter, "Failed to allocated hardware buffer, error: %d", error);
+ cleanup_resources(buffer);
+ return;
+ }
+
+ // Get actual desc for allocated buffer so we know the stride for uploading cpu data.
+ AHardwareBuffer_describe(buffer, &hwbDesc);
+
+ uint32_t* bufferAddr;
+ if (AHardwareBuffer_lock(buffer, AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN, -1, nullptr,
+ reinterpret_cast<void**>(&bufferAddr))) {
+ ERRORF(reporter, "Failed to lock hardware buffer");
+ cleanup_resources(buffer);
+ return;
+ }
+
+ int bbp = srcBitmap.bytesPerPixel();
+ uint32_t* src = (uint32_t*)srcBitmap.getPixels();
+ uint32_t* dst = bufferAddr;
+ for (int y = 0; y < DEV_H; ++y) {
+ memcpy(dst, src, DEV_W * bbp);
+ src += DEV_W;
+ dst += hwbDesc.stride;
+ }
+ AHardwareBuffer_unlock(buffer, nullptr);
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Wrap AHardwareBuffer in SkImage
+ ///////////////////////////////////////////////////////////////////////////
+
+ sk_sp<SkImage> image = SkImage::MakeFromAHardwareBuffer(buffer);
+ REPORTER_ASSERT(reporter, image);
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Make a surface to draw into
+ ///////////////////////////////////////////////////////////////////////////
+
+ SkImageInfo imageInfo = SkImageInfo::Make(DEV_W, DEV_H, kRGBA_8888_SkColorType,
+ kPremul_SkAlphaType);
+ sk_sp<SkSurface> surface = SkSurface::MakeRenderTarget(context, SkBudgeted::kNo,
+ imageInfo);
+ REPORTER_ASSERT(reporter, surface);
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Draw the AHardwareBuffer SkImage into surface
+ ///////////////////////////////////////////////////////////////////////////
+
+ surface->getCanvas()->drawImage(image, 0, 0);
+
+ SkBitmap readbackBitmap;
+ readbackBitmap.allocN32Pixels(DEV_W, DEV_H);
+
+ REPORTER_ASSERT(reporter, surface->readPixels(readbackBitmap, 0, 0));
+ REPORTER_ASSERT(reporter, check_read(reporter, srcBitmap, readbackBitmap));
+
+ image.reset();
+
+ cleanup_resources(buffer);
+}
+
+#endif