summaryrefslogtreecommitdiff
path: root/chromium/components/viz/display_compositor/yuv_readback_unittest.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/components/viz/display_compositor/yuv_readback_unittest.cc')
-rw-r--r--chromium/components/viz/display_compositor/yuv_readback_unittest.cc558
1 files changed, 558 insertions, 0 deletions
diff --git a/chromium/components/viz/display_compositor/yuv_readback_unittest.cc b/chromium/components/viz/display_compositor/yuv_readback_unittest.cc
new file mode 100644
index 00000000000..ef6d83bd758
--- /dev/null
+++ b/chromium/components/viz/display_compositor/yuv_readback_unittest.cc
@@ -0,0 +1,558 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/json/json_reader.h"
+#include "base/memory/ref_counted_memory.h"
+#include "base/run_loop.h"
+#include "base/strings/stringprintf.h"
+#include "base/test/launcher/unit_test_launcher.h"
+#include "base/test/scoped_task_environment.h"
+#include "base/test/test_suite.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/trace_event/trace_event.h"
+#include "components/viz/display_compositor/gl_helper.h"
+#include "gpu/command_buffer/client/gles2_implementation.h"
+#include "gpu/command_buffer/client/shared_memory_limits.h"
+#include "gpu/ipc/common/surface_handle.h"
+#include "gpu/ipc/gl_in_process_context.h"
+#include "media/base/video_frame.h"
+#include "media/base/video_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "ui/gl/gl_implementation.h"
+
+namespace viz {
+
+namespace {
+int kYUVReadbackSizes[] = {2, 4, 14};
+}
+
+class YUVReadbackTest : public testing::Test {
+ protected:
+ void SetUp() override {
+ gpu::gles2::ContextCreationAttribHelper attributes;
+ attributes.alpha_size = 8;
+ attributes.depth_size = 24;
+ attributes.red_size = 8;
+ attributes.green_size = 8;
+ attributes.blue_size = 8;
+ attributes.stencil_size = 8;
+ attributes.samples = 4;
+ attributes.sample_buffers = 1;
+ attributes.bind_generates_resource = false;
+
+ context_.reset(
+ gpu::GLInProcessContext::Create(nullptr, /* service */
+ nullptr, /* surface */
+ true, /* offscreen */
+ gpu::kNullSurfaceHandle, /* window */
+ nullptr, /* share_context */
+ attributes, gpu::SharedMemoryLimits(),
+ nullptr, /* gpu_memory_buffer_manager */
+ nullptr, /* image_factory */
+ base::ThreadTaskRunnerHandle::Get()));
+ gl_ = context_->GetImplementation();
+ gpu::ContextSupport* support = context_->GetImplementation();
+
+ helper_.reset(new GLHelper(gl_, support));
+ }
+
+ void TearDown() override {
+ helper_.reset(NULL);
+ context_.reset(NULL);
+ }
+
+ void StartTracing(const std::string& filter) {
+ base::trace_event::TraceLog::GetInstance()->SetEnabled(
+ base::trace_event::TraceConfig(filter,
+ base::trace_event::RECORD_UNTIL_FULL),
+ base::trace_event::TraceLog::RECORDING_MODE);
+ }
+
+ static void TraceDataCB(
+ const base::Callback<void()>& callback,
+ std::string* output,
+ const scoped_refptr<base::RefCountedString>& json_events_str,
+ bool has_more_events) {
+ if (output->size() > 1 && !json_events_str->data().empty()) {
+ output->append(",");
+ }
+ output->append(json_events_str->data());
+ if (!has_more_events) {
+ callback.Run();
+ }
+ }
+
+ // End tracing, return tracing data in a simple map
+ // of event name->counts.
+ void EndTracing(std::map<std::string, int>* event_counts) {
+ std::string json_data = "[";
+ base::trace_event::TraceLog::GetInstance()->SetDisabled();
+ base::RunLoop run_loop;
+ base::trace_event::TraceLog::GetInstance()->Flush(
+ base::Bind(&YUVReadbackTest::TraceDataCB, run_loop.QuitClosure(),
+ base::Unretained(&json_data)));
+ run_loop.Run();
+ json_data.append("]");
+
+ std::string error_msg;
+ std::unique_ptr<base::Value> trace_data =
+ base::JSONReader::ReadAndReturnError(json_data, 0, NULL, &error_msg);
+ CHECK(trace_data) << "JSON parsing failed (" << error_msg
+ << ") JSON data:" << std::endl
+ << json_data;
+
+ base::ListValue* list;
+ CHECK(trace_data->GetAsList(&list));
+ for (size_t i = 0; i < list->GetSize(); i++) {
+ base::Value* item = NULL;
+ if (list->Get(i, &item)) {
+ base::DictionaryValue* dict;
+ CHECK(item->GetAsDictionary(&dict));
+ std::string name;
+ CHECK(dict->GetString("name", &name));
+ std::string trace_type;
+ CHECK(dict->GetString("ph", &trace_type));
+ // Count all except END traces, as they come in BEGIN/END pairs.
+ if (trace_type != "E" && trace_type != "e")
+ (*event_counts)[name]++;
+ VLOG(1) << "trace name: " << name;
+ }
+ }
+ }
+
+ // Look up a single channel value. Works for 4-channel and single channel
+ // bitmaps. Clamp x/y.
+ int Channel(SkBitmap* pixels, int x, int y, int c) {
+ if (pixels->bytesPerPixel() == 4) {
+ uint32_t* data =
+ pixels->getAddr32(std::max(0, std::min(x, pixels->width() - 1)),
+ std::max(0, std::min(y, pixels->height() - 1)));
+ return (*data) >> (c * 8) & 0xff;
+ } else {
+ DCHECK_EQ(pixels->bytesPerPixel(), 1);
+ DCHECK_EQ(c, 0);
+ return *pixels->getAddr8(std::max(0, std::min(x, pixels->width() - 1)),
+ std::max(0, std::min(y, pixels->height() - 1)));
+ }
+ }
+
+ // Set a single channel value. Works for 4-channel and single channel
+ // bitmaps. Clamp x/y.
+ void SetChannel(SkBitmap* pixels, int x, int y, int c, int v) {
+ DCHECK_GE(x, 0);
+ DCHECK_GE(y, 0);
+ DCHECK_LT(x, pixels->width());
+ DCHECK_LT(y, pixels->height());
+ if (pixels->bytesPerPixel() == 4) {
+ uint32_t* data = pixels->getAddr32(x, y);
+ v = std::max(0, std::min(v, 255));
+ *data = (*data & ~(0xffu << (c * 8))) | (v << (c * 8));
+ } else {
+ DCHECK_EQ(pixels->bytesPerPixel(), 1);
+ DCHECK_EQ(c, 0);
+ uint8_t* data = pixels->getAddr8(x, y);
+ v = std::max(0, std::min(v, 255));
+ *data = v;
+ }
+ }
+
+ // Print all the R, G, B or A values from an SkBitmap in a
+ // human-readable format.
+ void PrintChannel(SkBitmap* pixels, int c) {
+ for (int y = 0; y < pixels->height(); y++) {
+ std::string formatted;
+ for (int x = 0; x < pixels->width(); x++) {
+ formatted.append(base::StringPrintf("%3d, ", Channel(pixels, x, y, c)));
+ }
+ LOG(ERROR) << formatted;
+ }
+ }
+
+ // Get a single R, G, B or A value as a float.
+ float ChannelAsFloat(SkBitmap* pixels, int x, int y, int c) {
+ return Channel(pixels, x, y, c) / 255.0;
+ }
+
+ // Works like a GL_LINEAR lookup on an SkBitmap.
+ float Bilinear(SkBitmap* pixels, float x, float y, int c) {
+ x -= 0.5;
+ y -= 0.5;
+ int base_x = static_cast<int>(floorf(x));
+ int base_y = static_cast<int>(floorf(y));
+ x -= base_x;
+ y -= base_y;
+ return (ChannelAsFloat(pixels, base_x, base_y, c) * (1 - x) * (1 - y) +
+ ChannelAsFloat(pixels, base_x + 1, base_y, c) * x * (1 - y) +
+ ChannelAsFloat(pixels, base_x, base_y + 1, c) * (1 - x) * y +
+ ChannelAsFloat(pixels, base_x + 1, base_y + 1, c) * x * y);
+ }
+
+ void FlipSKBitmap(SkBitmap* bitmap) {
+ int bpp = bitmap->bytesPerPixel();
+ DCHECK(bpp == 4 || bpp == 1);
+ int top_line = 0;
+ int bottom_line = bitmap->height() - 1;
+ while (top_line < bottom_line) {
+ for (int x = 0; x < bitmap->width(); x++) {
+ bpp == 4 ? std::swap(*bitmap->getAddr32(x, top_line),
+ *bitmap->getAddr32(x, bottom_line))
+ : std::swap(*bitmap->getAddr8(x, top_line),
+ *bitmap->getAddr8(x, bottom_line));
+ }
+ top_line++;
+ bottom_line--;
+ }
+ }
+
+ // Note: Left/Right means Top/Bottom when used for Y dimension.
+ enum Margin {
+ MarginLeft,
+ MarginMiddle,
+ MarginRight,
+ MarginInvalid,
+ };
+
+ static Margin NextMargin(Margin m) {
+ switch (m) {
+ case MarginLeft:
+ return MarginMiddle;
+ case MarginMiddle:
+ return MarginRight;
+ case MarginRight:
+ return MarginInvalid;
+ default:
+ return MarginInvalid;
+ }
+ }
+
+ int compute_margin(int insize, int outsize, Margin m) {
+ int available = outsize - insize;
+ switch (m) {
+ default:
+ EXPECT_TRUE(false) << "This should not happen.";
+ return 0;
+ case MarginLeft:
+ return 0;
+ case MarginMiddle:
+ return (available / 2) & ~1;
+ case MarginRight:
+ return available;
+ }
+ }
+
+ // Convert 0.0 - 1.0 to 0 - 255
+ int float_to_byte(float v) {
+ int ret = static_cast<int>(floorf(v * 255.0f + 0.5f));
+ if (ret < 0) {
+ return 0;
+ }
+ if (ret > 255) {
+ return 255;
+ }
+ return ret;
+ }
+
+ static void callcallback(const base::Callback<void()>& callback,
+ bool result) {
+ callback.Run();
+ }
+
+ void PrintPlane(unsigned char* plane, int xsize, int stride, int ysize) {
+ for (int y = 0; y < ysize; y++) {
+ std::string formatted;
+ for (int x = 0; x < xsize; x++) {
+ formatted.append(base::StringPrintf("%3d, ", plane[y * stride + x]));
+ }
+ LOG(ERROR) << formatted << " (" << (plane + y * stride) << ")";
+ }
+ }
+
+ // Compare two planes make sure that each component of each pixel
+ // is no more than |maxdiff| apart.
+ void ComparePlane(unsigned char* truth,
+ int truth_stride,
+ unsigned char* other,
+ int other_stride,
+ int maxdiff,
+ int xsize,
+ int ysize,
+ SkBitmap* source,
+ std::string message) {
+ for (int x = 0; x < xsize; x++) {
+ for (int y = 0; y < ysize; y++) {
+ int a = other[y * other_stride + x];
+ int b = truth[y * truth_stride + x];
+ EXPECT_NEAR(a, b, maxdiff)
+ << " x=" << x << " y=" << y << " " << message;
+ if (std::abs(a - b) > maxdiff) {
+ LOG(ERROR) << "-------expected--------";
+ PrintPlane(truth, xsize, truth_stride, ysize);
+ LOG(ERROR) << "-------actual--------";
+ PrintPlane(other, xsize, other_stride, ysize);
+ if (source) {
+ LOG(ERROR) << "-------before yuv conversion: red--------";
+ PrintChannel(source, 0);
+ LOG(ERROR) << "-------before yuv conversion: green------";
+ PrintChannel(source, 1);
+ LOG(ERROR) << "-------before yuv conversion: blue-------";
+ PrintChannel(source, 2);
+ }
+ return;
+ }
+ }
+ }
+ }
+
+ // YUV readback test. Create a test pattern, convert to YUV
+ // with reference implementation and compare to what gl_helper
+ // returns.
+ void TestYUVReadback(int xsize,
+ int ysize,
+ int output_xsize,
+ int output_ysize,
+ int xmargin,
+ int ymargin,
+ int test_pattern,
+ bool flip,
+ bool use_mrt,
+ GLHelper::ScalerQuality quality) {
+ GLuint src_texture;
+ gl_->GenTextures(1, &src_texture);
+ SkBitmap input_pixels;
+ input_pixels.allocN32Pixels(xsize, ysize);
+
+ for (int x = 0; x < xsize; ++x) {
+ for (int y = 0; y < ysize; ++y) {
+ switch (test_pattern) {
+ case 0: // Smooth test pattern
+ SetChannel(&input_pixels, x, y, 0, x * 10);
+ SetChannel(&input_pixels, x, y, 1, y * 10);
+ SetChannel(&input_pixels, x, y, 2, (x + y) * 10);
+ SetChannel(&input_pixels, x, y, 3, 255);
+ break;
+ case 1: // Small blocks
+ SetChannel(&input_pixels, x, y, 0, x & 1 ? 255 : 0);
+ SetChannel(&input_pixels, x, y, 1, y & 1 ? 255 : 0);
+ SetChannel(&input_pixels, x, y, 2, (x + y) & 1 ? 255 : 0);
+ SetChannel(&input_pixels, x, y, 3, 255);
+ break;
+ case 2: // Medium blocks
+ SetChannel(&input_pixels, x, y, 0, 10 + x / 2 * 50);
+ SetChannel(&input_pixels, x, y, 1, 10 + y / 3 * 50);
+ SetChannel(&input_pixels, x, y, 2, (x + y) / 5 * 50 + 5);
+ SetChannel(&input_pixels, x, y, 3, 255);
+ break;
+ }
+ }
+ }
+
+ gl_->BindTexture(GL_TEXTURE_2D, src_texture);
+ gl_->TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, xsize, ysize, 0, GL_RGBA,
+ GL_UNSIGNED_BYTE, input_pixels.getPixels());
+
+ gpu::Mailbox mailbox;
+ gl_->GenMailboxCHROMIUM(mailbox.name);
+ EXPECT_FALSE(mailbox.IsZero());
+ gl_->ProduceTextureCHROMIUM(GL_TEXTURE_2D, mailbox.name);
+ const GLuint64 fence_sync = gl_->InsertFenceSyncCHROMIUM();
+ gl_->ShallowFlushCHROMIUM();
+
+ gpu::SyncToken sync_token;
+ gl_->GenSyncTokenCHROMIUM(fence_sync, sync_token.GetData());
+
+ std::string message = base::StringPrintf(
+ "input size: %dx%d "
+ "output size: %dx%d "
+ "margin: %dx%d "
+ "pattern: %d %s %s",
+ xsize, ysize, output_xsize, output_ysize, xmargin, ymargin,
+ test_pattern, flip ? "flip" : "noflip", flip ? "mrt" : "nomrt");
+ std::unique_ptr<ReadbackYUVInterface> yuv_reader(
+ helper_->CreateReadbackPipelineYUV(
+ quality, gfx::Size(xsize, ysize), gfx::Rect(0, 0, xsize, ysize),
+ gfx::Size(xsize, ysize), flip, use_mrt));
+
+ scoped_refptr<media::VideoFrame> output_frame =
+ media::VideoFrame::CreateFrame(
+ media::PIXEL_FORMAT_YV12,
+ // The coded size of the output frame is rounded up to the next
+ // 16-byte boundary. This tests that the readback is being
+ // positioned inside the frame's visible region, and not dependent
+ // on its coded size.
+ gfx::Size((output_xsize + 15) & ~15, (output_ysize + 15) & ~15),
+ gfx::Rect(0, 0, output_xsize, output_ysize),
+ gfx::Size(output_xsize, output_ysize),
+ base::TimeDelta::FromSeconds(0));
+ scoped_refptr<media::VideoFrame> truth_frame =
+ media::VideoFrame::CreateFrame(
+ media::PIXEL_FORMAT_YV12, gfx::Size(output_xsize, output_ysize),
+ gfx::Rect(0, 0, output_xsize, output_ysize),
+ gfx::Size(output_xsize, output_ysize),
+ base::TimeDelta::FromSeconds(0));
+
+ base::RunLoop run_loop;
+ yuv_reader->ReadbackYUV(mailbox, sync_token, output_frame->visible_rect(),
+ output_frame->stride(media::VideoFrame::kYPlane),
+ output_frame->data(media::VideoFrame::kYPlane),
+ output_frame->stride(media::VideoFrame::kUPlane),
+ output_frame->data(media::VideoFrame::kUPlane),
+ output_frame->stride(media::VideoFrame::kVPlane),
+ output_frame->data(media::VideoFrame::kVPlane),
+ gfx::Point(xmargin, ymargin),
+ base::Bind(&callcallback, run_loop.QuitClosure()));
+
+ const gfx::Rect paste_rect(gfx::Point(xmargin, ymargin),
+ gfx::Size(xsize, ysize));
+ media::LetterboxYUV(output_frame.get(), paste_rect);
+ run_loop.Run();
+
+ if (flip) {
+ FlipSKBitmap(&input_pixels);
+ }
+
+ unsigned char* Y = truth_frame->visible_data(media::VideoFrame::kYPlane);
+ unsigned char* U = truth_frame->visible_data(media::VideoFrame::kUPlane);
+ unsigned char* V = truth_frame->visible_data(media::VideoFrame::kVPlane);
+ int32_t y_stride = truth_frame->stride(media::VideoFrame::kYPlane);
+ int32_t u_stride = truth_frame->stride(media::VideoFrame::kUPlane);
+ int32_t v_stride = truth_frame->stride(media::VideoFrame::kVPlane);
+ memset(Y, 0x00, y_stride * output_ysize);
+ memset(U, 0x80, u_stride * output_ysize / 2);
+ memset(V, 0x80, v_stride * output_ysize / 2);
+
+ const float kRGBtoYColorWeights[] = {0.257f, 0.504f, 0.098f, 0.0625f};
+ const float kRGBtoUColorWeights[] = {-0.148f, -0.291f, 0.439f, 0.5f};
+ const float kRGBtoVColorWeights[] = {0.439f, -0.368f, -0.071f, 0.5f};
+
+ for (int y = 0; y < ysize; y++) {
+ for (int x = 0; x < xsize; x++) {
+ Y[(y + ymargin) * y_stride + x + xmargin] = float_to_byte(
+ ChannelAsFloat(&input_pixels, x, y, 0) * kRGBtoYColorWeights[0] +
+ ChannelAsFloat(&input_pixels, x, y, 1) * kRGBtoYColorWeights[1] +
+ ChannelAsFloat(&input_pixels, x, y, 2) * kRGBtoYColorWeights[2] +
+ kRGBtoYColorWeights[3]);
+ }
+ }
+
+ for (int y = 0; y < ysize / 2; y++) {
+ for (int x = 0; x < xsize / 2; x++) {
+ U[(y + ymargin / 2) * u_stride + x + xmargin / 2] =
+ float_to_byte(Bilinear(&input_pixels, x * 2 + 1.0, y * 2 + 1.0, 0) *
+ kRGBtoUColorWeights[0] +
+ Bilinear(&input_pixels, x * 2 + 1.0, y * 2 + 1.0, 1) *
+ kRGBtoUColorWeights[1] +
+ Bilinear(&input_pixels, x * 2 + 1.0, y * 2 + 1.0, 2) *
+ kRGBtoUColorWeights[2] +
+ kRGBtoUColorWeights[3]);
+ V[(y + ymargin / 2) * v_stride + x + xmargin / 2] =
+ float_to_byte(Bilinear(&input_pixels, x * 2 + 1.0, y * 2 + 1.0, 0) *
+ kRGBtoVColorWeights[0] +
+ Bilinear(&input_pixels, x * 2 + 1.0, y * 2 + 1.0, 1) *
+ kRGBtoVColorWeights[1] +
+ Bilinear(&input_pixels, x * 2 + 1.0, y * 2 + 1.0, 2) *
+ kRGBtoVColorWeights[2] +
+ kRGBtoVColorWeights[3]);
+ }
+ }
+
+ ComparePlane(
+ Y, y_stride, output_frame->visible_data(media::VideoFrame::kYPlane),
+ output_frame->stride(media::VideoFrame::kYPlane), 2, output_xsize,
+ output_ysize, &input_pixels, message + " Y plane");
+ ComparePlane(
+ U, u_stride, output_frame->visible_data(media::VideoFrame::kUPlane),
+ output_frame->stride(media::VideoFrame::kUPlane), 2, output_xsize / 2,
+ output_ysize / 2, &input_pixels, message + " U plane");
+ ComparePlane(
+ V, v_stride, output_frame->visible_data(media::VideoFrame::kVPlane),
+ output_frame->stride(media::VideoFrame::kVPlane), 2, output_xsize / 2,
+ output_ysize / 2, &input_pixels, message + " V plane");
+
+ gl_->DeleteTextures(1, &src_texture);
+ }
+
+ std::unique_ptr<gpu::GLInProcessContext> context_;
+ gpu::gles2::GLES2Interface* gl_;
+ std::unique_ptr<GLHelper> helper_;
+ gl::DisableNullDrawGLBindings enable_pixel_output_;
+ base::test::ScopedTaskEnvironment scoped_task_environment_;
+};
+
+TEST_F(YUVReadbackTest, YUVReadbackOptTest) {
+ // This test uses the gpu.service/gpu_decoder tracing events to detect how
+ // many scaling passes are actually performed by the YUV readback pipeline.
+ StartTracing(TRACE_DISABLED_BY_DEFAULT(
+ "gpu.service") "," TRACE_DISABLED_BY_DEFAULT("gpu_decoder"));
+
+ TestYUVReadback(800, 400, 800, 400, 0, 0, 1, false, true,
+ GLHelper::SCALER_QUALITY_FAST);
+
+ std::map<std::string, int> event_counts;
+ EndTracing(&event_counts);
+ int draw_buffer_calls = event_counts["kDrawBuffersEXTImmediate"];
+ int draw_arrays_calls = event_counts["kDrawArrays"];
+ VLOG(1) << "Draw buffer calls: " << draw_buffer_calls;
+ VLOG(1) << "DrawArrays calls: " << draw_arrays_calls;
+
+ if (draw_buffer_calls) {
+ // When using MRT, the YUV readback code should only
+ // execute two draw arrays, and scaling should be integrated
+ // into those two calls since we are using the FAST scalign
+ // quality.
+ EXPECT_EQ(2, draw_arrays_calls);
+ } else {
+ // When not using MRT, there are three passes for the YUV,
+ // and one for the scaling.
+ EXPECT_EQ(4, draw_arrays_calls);
+ }
+}
+
+class YUVReadbackPixelTest
+ : public YUVReadbackTest,
+ public ::testing::WithParamInterface<
+ std::tr1::tuple<bool, bool, unsigned int, unsigned int>> {};
+
+TEST_P(YUVReadbackPixelTest, Test) {
+ bool flip = std::tr1::get<0>(GetParam());
+ bool use_mrt = std::tr1::get<1>(GetParam());
+ unsigned int x = std::tr1::get<2>(GetParam());
+ unsigned int y = std::tr1::get<3>(GetParam());
+
+ for (unsigned int ox = x; ox < arraysize(kYUVReadbackSizes); ox++) {
+ for (unsigned int oy = y; oy < arraysize(kYUVReadbackSizes); oy++) {
+ // If output is a subsection of the destination frame, (letterbox)
+ // then try different variations of where the subsection goes.
+ for (Margin xm = x < ox ? MarginLeft : MarginRight; xm <= MarginRight;
+ xm = NextMargin(xm)) {
+ for (Margin ym = y < oy ? MarginLeft : MarginRight; ym <= MarginRight;
+ ym = NextMargin(ym)) {
+ for (int pattern = 0; pattern < 3; pattern++) {
+ TestYUVReadback(
+ kYUVReadbackSizes[x], kYUVReadbackSizes[y],
+ kYUVReadbackSizes[ox], kYUVReadbackSizes[oy],
+ compute_margin(kYUVReadbackSizes[x], kYUVReadbackSizes[ox], xm),
+ compute_margin(kYUVReadbackSizes[y], kYUVReadbackSizes[oy], ym),
+ pattern, flip, use_mrt, GLHelper::SCALER_QUALITY_GOOD);
+ if (HasFailure()) {
+ return;
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+// First argument is intentionally empty.
+INSTANTIATE_TEST_CASE_P(
+ ,
+ YUVReadbackPixelTest,
+ ::testing::Combine(
+ ::testing::Bool(),
+ ::testing::Bool(),
+ ::testing::Range<unsigned int>(0, arraysize(kYUVReadbackSizes)),
+ ::testing::Range<unsigned int>(0, arraysize(kYUVReadbackSizes))));
+
+} // namespace viz