blob: 9b23629ad2febaf344cabc17dbcc7a93375963c5 [file] [log] [blame]
// Copyright 2017 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 <vector>
#include "base/command_line.h"
#include "base/strings/stringprintf.h"
#include "base/threading/thread_task_runner_handle.h"
#include "cc/base/region.h"
#include "cc/layers/recording_source.h"
#include "cc/paint/display_item_list.h"
#include "cc/raster/raster_source.h"
#include "cc/test/pixel_test_utils.h"
#include "cc/test/test_in_process_context_provider.h"
#include "gpu/command_buffer/client/gles2_implementation.h"
#include "gpu/command_buffer/client/gles2_interface.h"
#include "gpu/command_buffer/client/raster_implementation_gles.h"
#include "gpu/command_buffer/client/shared_memory_limits.h"
#include "gpu/command_buffer/common/context_creation_attribs.h"
#include "gpu/config/gpu_switches.h"
#include "gpu/ipc/gl_in_process_context.h"
#include "gpu/skia_bindings/grcontext_for_gles2_interface.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkSurface.h"
#include "third_party/skia/include/gpu/GrBackendSurface.h"
#include "third_party/skia/include/gpu/GrContext.h"
#include "third_party/skia/include/gpu/GrTypes.h"
#include "ui/gfx/geometry/axis_transform2d.h"
#include "ui/gfx/geometry/rect_conversions.h"
#include "ui/gfx/skia_util.h"
#include "ui/gl/gl_implementation.h"
namespace cc {
namespace {
class NoOpImageProvider : public ImageProvider {
public:
~NoOpImageProvider() override = default;
ScopedDecodedDrawImage GetDecodedDrawImage(
const DrawImage& draw_image) override {
SkBitmap bitmap;
bitmap.allocPixelsFlags(SkImageInfo::MakeN32Premul(10, 10),
SkBitmap::kZeroPixels_AllocFlag);
sk_sp<SkImage> image = SkImage::MakeFromBitmap(bitmap);
return ScopedDecodedDrawImage(DecodedDrawImage(
image, SkSize::Make(10, 10), SkSize::Make(1, 1), kLow_SkFilterQuality));
}
};
class OopPixelTest : public testing::Test {
public:
void SetUp() override {
// Add an OOP rasterization command line flag so that we set
// |chromium_raster_transport| features flag.
// TODO(vmpstr): Is there a better way to do this?
if (!base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kEnableOOPRasterization)) {
base::CommandLine::ForCurrentProcess()->AppendSwitch(
switches::kEnableOOPRasterization);
}
// Setup a GL context for reading back pixels.
bool is_offscreen = true;
gpu::ContextCreationAttribs attribs;
attribs.alpha_size = -1;
attribs.depth_size = 24;
attribs.stencil_size = 8;
attribs.samples = 0;
attribs.sample_buffers = 0;
attribs.fail_if_major_perf_caveat = false;
attribs.bind_generates_resource = false;
context_ = gpu::GLInProcessContext::CreateWithoutInit();
auto result = context_->Initialize(
nullptr, nullptr, is_offscreen, gpu::kNullSurfaceHandle, nullptr,
attribs, gpu::SharedMemoryLimits(), &gpu_memory_buffer_manager_,
&image_factory_, nullptr, base::ThreadTaskRunnerHandle::Get());
ASSERT_EQ(result, gpu::ContextResult::kSuccess);
// Setup a second context with OOP rasterization enabled and a
// RasterInterface on top of it.
attribs.enable_oop_rasterization = true;
raster_context_ = gpu::GLInProcessContext::CreateWithoutInit();
result = raster_context_->Initialize(
nullptr, nullptr, is_offscreen, gpu::kNullSurfaceHandle, nullptr,
attribs, gpu::SharedMemoryLimits(), &gpu_memory_buffer_manager_,
&image_factory_, nullptr, base::ThreadTaskRunnerHandle::Get());
ASSERT_EQ(result, gpu::ContextResult::kSuccess);
ASSERT_TRUE(raster_context_->GetCapabilities().supports_oop_raster);
raster_implementation_ =
std::make_unique<gpu::raster::RasterImplementationGLES>(
raster_context_->GetImplementation(),
raster_context_->GetImplementation(),
raster_context_->GetCapabilities());
}
void TearDown() override {
raster_implementation_.reset();
raster_context_.reset();
context_.reset();
}
struct RasterOptions {
SkColor background_color = SK_ColorBLACK;
int msaa_sample_count = 0;
bool use_lcd_text = false;
bool use_distance_field_text = false;
GrPixelConfig pixel_config = kRGBA_8888_GrPixelConfig;
gfx::Rect bitmap_rect;
gfx::Rect playback_rect;
gfx::Vector2dF post_translate = {0.f, 0.f};
float post_scale = 1.f;
};
SkBitmap Raster(scoped_refptr<DisplayItemList> display_item_list,
const gfx::Size& playback_size) {
RasterOptions options;
options.bitmap_rect = gfx::Rect(playback_size);
options.playback_rect = gfx::Rect(playback_size);
return Raster(display_item_list, options);
}
SkBitmap Raster(scoped_refptr<DisplayItemList> display_item_list,
const RasterOptions& options) {
gpu::gles2::GLES2Interface* gl = context_->GetImplementation();
int width = options.bitmap_rect.width();
int height = options.bitmap_rect.height();
// Create and allocate a texture on the raster interface.
GLuint raster_texture_id;
raster_implementation_->GenTextures(1, &raster_texture_id);
raster_implementation_->BindTexture(GL_TEXTURE_2D, raster_texture_id);
raster_implementation_->TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height,
0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
raster_implementation_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
GL_LINEAR);
EXPECT_EQ(raster_implementation_->GetError(),
static_cast<unsigned>(GL_NO_ERROR));
// "Out of process" raster! \o/
raster_implementation_->BeginRasterCHROMIUM(
raster_texture_id, options.background_color, options.msaa_sample_count,
options.use_lcd_text, options.use_distance_field_text,
options.pixel_config);
raster_implementation_->RasterCHROMIUM(
display_item_list.get(), &image_provider_,
options.bitmap_rect.OffsetFromOrigin(), options.playback_rect,
options.post_translate, options.post_scale);
raster_implementation_->EndRasterCHROMIUM();
// Produce a mailbox and insert an ordering barrier (assumes the raster
// interface and gl are on the same scheduling group).
gpu::Mailbox mailbox;
raster_implementation_->GenMailboxCHROMIUM(mailbox.name);
raster_implementation_->ProduceTextureDirectCHROMIUM(raster_texture_id,
mailbox.name);
raster_implementation_->OrderingBarrierCHROMIUM();
EXPECT_EQ(raster_implementation_->GetError(),
static_cast<unsigned>(GL_NO_ERROR));
// Import the texture in gl, create an fbo and bind the texture to it.
GLuint gl_texture_id = gl->CreateAndConsumeTextureCHROMIUM(mailbox.name);
GLuint fbo_id;
gl->GenFramebuffers(1, &fbo_id);
gl->BindFramebuffer(GL_FRAMEBUFFER, fbo_id);
gl->FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
GL_TEXTURE_2D, gl_texture_id, 0);
// Read the data back.
std::unique_ptr<unsigned char[]> data(
new unsigned char[width * height * 4]);
gl->ReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, data.get());
gl->DeleteTextures(1, &gl_texture_id);
gl->DeleteFramebuffers(1, &fbo_id);
gl->OrderingBarrierCHROMIUM();
raster_implementation_->DeleteTextures(1, &raster_texture_id);
// Swizzle rgba->bgra if needed.
std::vector<SkPMColor> colors;
colors.reserve(width * height);
for (int h = 0; h < height; ++h) {
for (int w = 0; w < width; ++w) {
int i = (h * width + w) * 4;
colors.push_back(SkPreMultiplyARGB(data[i + 3], data[i + 0],
data[i + 1], data[i + 2]));
}
}
SkBitmap bitmap;
bitmap.allocN32Pixels(width, height);
SkPixmap pixmap(SkImageInfo::MakeN32Premul(options.bitmap_rect.width(),
options.bitmap_rect.height()),
colors.data(),
options.bitmap_rect.width() * sizeof(SkColor));
bitmap.writePixels(pixmap);
return bitmap;
}
SkBitmap RasterExpectedBitmap(
scoped_refptr<DisplayItemList> display_item_list,
const gfx::Size& playback_size) {
RasterOptions options;
options.bitmap_rect = gfx::Rect(playback_size);
options.playback_rect = gfx::Rect(playback_size);
return RasterExpectedBitmap(display_item_list, options);
}
SkBitmap RasterExpectedBitmap(
scoped_refptr<DisplayItemList> display_item_list,
const RasterOptions& options) {
// Generate bitmap via the "in process" raster path. This verifies
// that the preamble setup in RasterSource::PlaybackToCanvas matches
// the same setup done in GLES2Implementation::RasterCHROMIUM.
RecordingSource recording;
recording.UpdateDisplayItemList(display_item_list, 0u, 1.f);
recording.SetBackgroundColor(options.background_color);
Region fake_invalidation;
gfx::Rect layer_rect(
gfx::Size(options.bitmap_rect.right(), options.bitmap_rect.bottom()));
recording.UpdateAndExpandInvalidation(&fake_invalidation, layer_rect.size(),
layer_rect);
auto raster_source = recording.CreateRasterSource();
RasterSource::PlaybackSettings settings;
settings.use_lcd_text = options.use_lcd_text;
// OOP raster does not support the complicated debug color clearing from
// RasterSource::ClearCanvasForPlayback, so disable it for consistency.
settings.clear_canvas_before_raster = false;
// TODO(enne): add a fake image provider here.
uint32_t flags = options.use_distance_field_text
? SkSurfaceProps::kUseDistanceFieldFonts_Flag
: 0;
SkSurfaceProps surface_props(flags, kUnknown_SkPixelGeometry);
if (options.use_lcd_text) {
surface_props =
SkSurfaceProps(flags, SkSurfaceProps::kLegacyFontHost_InitType);
}
gpu::Capabilities capabilities;
gpu::gles2::GLES2Interface* gl = context_->GetImplementation();
size_t max_resource_cache_bytes;
size_t max_glyph_cache_texture_bytes;
skia_bindings::GrContextForGLES2Interface::DefaultCacheLimitsForTests(
&max_resource_cache_bytes, &max_glyph_cache_texture_bytes);
skia_bindings::GrContextForGLES2Interface scoped_grcontext(
gl, capabilities, max_resource_cache_bytes,
max_glyph_cache_texture_bytes);
SkImageInfo image_info = SkImageInfo::MakeN32Premul(
options.bitmap_rect.width(), options.bitmap_rect.height());
auto surface = SkSurface::MakeRenderTarget(scoped_grcontext.get(),
SkBudgeted::kYes, image_info);
SkCanvas* canvas = surface->getCanvas();
canvas->drawColor(options.background_color);
gfx::AxisTransform2d raster_transform(options.post_scale,
options.post_translate);
// TODO(enne): add a target colorspace to BeginRasterCHROMIUM etc.
gfx::ColorSpace target_color_space;
raster_source->PlaybackToCanvas(canvas, target_color_space,
options.bitmap_rect, options.playback_rect,
raster_transform, settings);
surface->prepareForExternalIO();
EXPECT_EQ(gl->GetError(), static_cast<unsigned>(GL_NO_ERROR));
SkBitmap bitmap;
SkImageInfo info = SkImageInfo::Make(
options.bitmap_rect.width(), options.bitmap_rect.height(),
SkColorType::kBGRA_8888_SkColorType, SkAlphaType::kPremul_SkAlphaType);
bitmap.allocPixels(info, options.bitmap_rect.width() * 4);
bool success = surface->readPixels(bitmap, 0, 0);
CHECK(success);
EXPECT_EQ(gl->GetError(), static_cast<unsigned>(GL_NO_ERROR));
return bitmap;
}
void ExpectEquals(SkBitmap actual, SkBitmap expected) {
EXPECT_EQ(actual.dimensions(), expected.dimensions());
auto expected_url = GetPNGDataUrl(expected);
auto actual_url = GetPNGDataUrl(actual);
if (actual_url == expected_url)
return;
ADD_FAILURE() << "\nExpected: " << expected_url
<< "\nActual: " << actual_url;
}
private:
viz::TestGpuMemoryBufferManager gpu_memory_buffer_manager_;
TestImageFactory image_factory_;
std::unique_ptr<gpu::GLInProcessContext> context_;
std::unique_ptr<gpu::GLInProcessContext> raster_context_;
std::unique_ptr<gpu::raster::RasterImplementationGLES> raster_implementation_;
gl::DisableNullDrawGLBindings enable_pixel_output_;
NoOpImageProvider image_provider_;
};
TEST_F(OopPixelTest, DrawColor) {
gfx::Rect rect(10, 10);
auto display_item_list = base::MakeRefCounted<DisplayItemList>();
display_item_list->StartPaint();
display_item_list->push<DrawColorOp>(SK_ColorBLUE, SkBlendMode::kSrc);
display_item_list->EndPaintOfUnpaired(rect);
display_item_list->Finalize();
auto actual = Raster(std::move(display_item_list), rect.size());
std::vector<SkPMColor> expected_pixels(rect.width() * rect.height(),
SkPreMultiplyARGB(255, 0, 0, 255));
SkBitmap expected;
expected.installPixels(
SkImageInfo::MakeN32Premul(rect.width(), rect.height()),
expected_pixels.data(), rect.width() * sizeof(SkColor));
ExpectEquals(actual, expected);
}
TEST_F(OopPixelTest, DrawRect) {
gfx::Rect rect(10, 10);
auto color_paint = [](int r, int g, int b) {
PaintFlags flags;
flags.setColor(SkColorSetARGB(255, r, g, b));
return flags;
};
std::vector<std::pair<SkRect, PaintFlags>> input = {
{SkRect::MakeXYWH(0, 0, 5, 5), color_paint(0, 0, 255)},
{SkRect::MakeXYWH(5, 0, 5, 5), color_paint(0, 255, 0)},
{SkRect::MakeXYWH(0, 5, 5, 5), color_paint(0, 255, 255)},
{SkRect::MakeXYWH(5, 5, 5, 5), color_paint(255, 0, 0)}};
auto display_item_list = base::MakeRefCounted<DisplayItemList>();
for (auto& op : input) {
display_item_list->StartPaint();
display_item_list->push<DrawRectOp>(op.first, op.second);
display_item_list->EndPaintOfUnpaired(
gfx::ToEnclosingRect(gfx::SkRectToRectF(op.first)));
}
display_item_list->Finalize();
auto actual = Raster(std::move(display_item_list), rect.size());
// Expected colors are 5x5 rects of
// BLUE GREEN
// CYAN RED
std::vector<SkPMColor> expected_pixels(rect.width() * rect.height());
for (int h = 0; h < rect.height(); ++h) {
auto start = expected_pixels.begin() + h * rect.width();
SkPMColor left_color = SkPreMultiplyColor(
h < 5 ? input[0].second.getColor() : input[2].second.getColor());
SkPMColor right_color = SkPreMultiplyColor(
h < 5 ? input[1].second.getColor() : input[3].second.getColor());
std::fill(start, start + 5, left_color);
std::fill(start + 5, start + 10, right_color);
}
SkBitmap expected;
expected.installPixels(
SkImageInfo::MakeN32Premul(rect.width(), rect.height()),
expected_pixels.data(), rect.width() * sizeof(SkPMColor));
ExpectEquals(actual, expected);
}
// Test various bitmap and playback rects in the raster options, to verify
// that in process (RasterSource) and out of process (GLES2Implementation)
// raster behave identically.
TEST_F(OopPixelTest, DrawRectBasicRasterOptions) {
PaintFlags flags;
flags.setColor(SkColorSetARGB(255, 250, 10, 20));
gfx::Rect draw_rect(3, 1, 8, 9);
auto display_item_list = base::MakeRefCounted<DisplayItemList>();
display_item_list->StartPaint();
display_item_list->push<DrawRectOp>(gfx::RectToSkRect(draw_rect), flags);
display_item_list->EndPaintOfUnpaired(draw_rect);
display_item_list->Finalize();
std::vector<std::pair<gfx::Rect, gfx::Rect>> input = {
{{0, 0, 10, 10}, {0, 0, 10, 10}},
{{1, 2, 10, 10}, {4, 2, 5, 6}},
{{5, 5, 15, 10}, {0, 0, 10, 10}}};
for (size_t i = 0; i < input.size(); ++i) {
SCOPED_TRACE(base::StringPrintf("Case %zd", i));
RasterOptions options;
options.bitmap_rect = input[i].first;
options.playback_rect = input[i].second;
options.background_color = SK_ColorMAGENTA;
auto actual = Raster(display_item_list, options);
auto expected = RasterExpectedBitmap(display_item_list, options);
ExpectEquals(actual, expected);
}
}
TEST_F(OopPixelTest, DrawRectScaleTransformOptions) {
PaintFlags flags;
// Use powers of two here to make floating point blending consistent.
flags.setColor(SkColorSetARGB(128, 64, 128, 32));
flags.setAntiAlias(true);
gfx::Rect draw_rect(3, 4, 8, 9);
auto display_item_list = base::MakeRefCounted<DisplayItemList>();
display_item_list->StartPaint();
display_item_list->push<DrawRectOp>(gfx::RectToSkRect(draw_rect), flags);
display_item_list->EndPaintOfUnpaired(draw_rect);
display_item_list->Finalize();
// Draw a greenish transparent box, partially offset and clipped in the
// bottom right. It should appear near the upper left of a cyan background,
// with the left and top sides of the greenish box partially blended due to
// the post translate.
RasterOptions options;
options.bitmap_rect = {5, 5, 20, 20};
options.playback_rect = {3, 2, 15, 12};
options.background_color = SK_ColorCYAN;
options.post_translate = {0.5f, 0.25f};
options.post_scale = 2.f;
auto actual = Raster(display_item_list, options);
auto expected = RasterExpectedBitmap(display_item_list, options);
ExpectEquals(actual, expected);
}
TEST_F(OopPixelTest, DrawRectQueryMiddleOfDisplayList) {
auto display_item_list = base::MakeRefCounted<DisplayItemList>();
std::vector<SkColor> colors = {
SkColorSetARGB(255, 0, 0, 255), SkColorSetARGB(255, 0, 255, 0),
SkColorSetARGB(255, 0, 255, 255), SkColorSetARGB(255, 255, 0, 0),
SkColorSetARGB(255, 255, 0, 255), SkColorSetARGB(255, 255, 255, 0),
SkColorSetARGB(255, 255, 255, 255)};
for (int i = 0; i < 20; ++i) {
gfx::Rect draw_rect(0, i, 1, 1);
PaintFlags flags;
flags.setColor(colors[i % colors.size()]);
display_item_list->StartPaint();
display_item_list->push<DrawRectOp>(gfx::RectToSkRect(draw_rect), flags);
display_item_list->EndPaintOfUnpaired(draw_rect);
}
display_item_list->Finalize();
// Draw a "tile" in the middle of the display list with a post scale.
RasterOptions options;
options.bitmap_rect = {0, 10, 1, 10};
options.playback_rect = {0, 10, 1, 10};
options.background_color = SK_ColorGRAY;
options.post_translate = {0.f, 0.f};
options.post_scale = 2.f;
auto actual = Raster(display_item_list, options);
auto expected = RasterExpectedBitmap(display_item_list, options);
ExpectEquals(actual, expected);
}
} // namespace
} // namespace cc