blob: 13243d187e7372bb22a51aab59bad3c78dc2d429 [file] [log] [blame]
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/viz/service/display/software_renderer.h"
#include <stdint.h>
#include <memory>
#include <unordered_map>
#include <utility>
#include <vector>
#include "base/functional/bind.h"
#include "cc/test/pixel_test_utils.h"
#include "cc/test/render_pass_test_utils.h"
#include "cc/test/resource_provider_test_utils.h"
#include "components/viz/client/client_resource_provider.h"
#include "components/viz/common/frame_sinks/copy_output_result.h"
#include "components/viz/common/quads/compositor_render_pass.h"
#include "components/viz/common/quads/debug_border_draw_quad.h"
#include "components/viz/common/quads/solid_color_draw_quad.h"
#include "components/viz/common/quads/tile_draw_quad.h"
#include "components/viz/service/display/software_output_device.h"
#include "components/viz/service/display/viz_pixel_test.h"
#include "gpu/command_buffer/client/client_shared_image.h"
#include "gpu/command_buffer/client/shared_image_interface.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/utils/SkNWayCanvas.h"
#include "ui/gfx/geometry/skia_conversions.h"
namespace viz {
namespace {
void DeleteSharedImage(scoped_refptr<gpu::ClientSharedImage> shared_image,
const gpu::SyncToken& sync_token,
bool is_lost) {
shared_image->UpdateDestructionSyncToken(sync_token);
}
// A single rect drawn into `GenerateExpectedImage`.
struct ExpectedImageRect {
gfx::Rect rect;
SkColor4f color;
bool debug_border = false;
};
SkBitmap GenerateExpectedImage(
const gfx::Size& size,
std::vector<ExpectedImageRect> back_to_front_rects) {
SkBitmap bitmap;
bitmap.allocN32Pixels(size.width(), size.height());
bitmap.eraseColor(SkColors::kTransparent);
SkCanvas canvas(bitmap);
for (const auto& filled_rect : back_to_front_rects) {
SkPaint paint;
paint.setColor(filled_rect.color);
if (filled_rect.debug_border) {
// `SoftwareRenderer` draws debug borders as a path with a miter join.
paint.setStyle(SkPaint::kStroke_Style);
paint.setStrokeJoin(SkPaint::kMiter_Join);
canvas.drawPath(SkPath::Rect(gfx::RectToSkRect(filled_rect.rect)), paint);
} else {
canvas.drawRect(gfx::RectToSkRect(filled_rect.rect), paint);
}
}
return bitmap;
}
class SoftwareRendererTest : public VizPixelTest {
public:
SoftwareRendererTest() : VizPixelTest(RendererType::kSoftware) {}
void SetUp() override {
this->device_viewport_size_ = gfx::Size(100, 100);
VizPixelTest::SetUp();
}
DisplayResourceProvider* resource_provider() const {
return resource_provider_.get();
}
ClientResourceProvider* child_resource_provider() const {
return child_resource_provider_.get();
}
ResourceId AllocateAndFillSoftwareResource(const gfx::Size& size,
const SkBitmap& source) {
auto* shared_image_interface =
child_context_provider_->SharedImageInterface();
auto shared_image =
shared_image_interface->CreateSharedImageForSoftwareCompositor(
{SinglePlaneFormat::kBGRA_8888, size, gfx::ColorSpace(),
gpu::SHARED_IMAGE_USAGE_CPU_WRITE_ONLY,
"SoftwareRendererTestSharedBitmap"});
auto mapping = shared_image->Map();
SkImageInfo info = SkImageInfo::MakeN32Premul(size.width(), size.height());
source.readPixels(info, mapping->GetMemoryForPlane(0).data(),
info.minRowBytes(), 0, 0);
auto transferable_resource = TransferableResource::Make(
shared_image, TransferableResource::ResourceSource::kTileRasterTask,
shared_image->creation_sync_token());
auto release_callback =
base::BindOnce(&DeleteSharedImage, std::move(shared_image));
return child_resource_provider_->ImportResource(
std::move(transferable_resource), std::move(release_callback));
}
};
TEST_F(SoftwareRendererTest, SolidColorQuad) {
gfx::Size outer_size(100, 100);
gfx::Size inner_size(98, 98);
gfx::Rect outer_rect(outer_size);
gfx::Rect inner_rect(gfx::Point(1, 1), inner_size);
gfx::Rect visible_rect(gfx::Point(1, 2), gfx::Size(98, 97));
AggregatedRenderPassId root_render_pass_id{1};
auto root_render_pass = std::make_unique<AggregatedRenderPass>();
root_render_pass->SetNew(root_render_pass_id, outer_rect, outer_rect,
gfx::Transform());
SharedQuadState* shared_quad_state =
root_render_pass->CreateAndAppendSharedQuadState();
shared_quad_state->SetAll(gfx::Transform(), outer_rect, outer_rect,
gfx::MaskFilterInfo(), /*clip=*/std::nullopt,
/*contents_opaque=*/true, /*opacity_f=*/1.0,
SkBlendMode::kSrcOver, /*sorting_context=*/0,
/*layer_id=*/0u, /*fast_rounded_corner=*/false);
auto* inner_quad =
root_render_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
inner_quad->SetNew(shared_quad_state, inner_rect, inner_rect, SkColors::kCyan,
false);
inner_quad->visible_rect = visible_rect;
auto* outer_quad =
root_render_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
outer_quad->SetNew(shared_quad_state, outer_rect, outer_rect,
SkColors::kYellow, false);
AggregatedRenderPassList list;
list.push_back(std::move(root_render_pass));
EXPECT_TRUE(
RunPixelTest(&list,
GenerateExpectedImage(
outer_size,
{
{.rect = outer_rect, .color = SkColors::kYellow},
{.rect = visible_rect, .color = SkColors::kCyan},
}),
cc::ExactPixelComparator()));
}
TEST_F(SoftwareRendererTest, DebugBorderDrawQuad) {
gfx::Size rect_size(10, 10);
gfx::Size full_size(100, 100);
gfx::Rect screen_rect(full_size);
gfx::Rect rect_1(rect_size);
gfx::Rect rect_2(gfx::Point(1, 1), rect_size);
gfx::Rect rect_3(gfx::Point(2, 2), rect_size);
gfx::Rect rect_4(gfx::Point(3, 3), rect_size);
AggregatedRenderPassId root_render_pass_id{1};
auto root_render_pass = std::make_unique<AggregatedRenderPass>();
root_render_pass->SetNew(root_render_pass_id, screen_rect, screen_rect,
gfx::Transform());
SharedQuadState* shared_quad_state =
root_render_pass->CreateAndAppendSharedQuadState();
shared_quad_state->SetAll(gfx::Transform(), screen_rect, screen_rect,
gfx::MaskFilterInfo(), /*clip=*/std::nullopt,
/*contents_opaque=*/true, /*opacity_f=*/1.0,
SkBlendMode::kSrcOver, /*sorting_context=*/0,
/*layer_id=*/0u, /*fast_rounded_corner=*/false);
auto* quad_1 =
root_render_pass->CreateAndAppendDrawQuad<DebugBorderDrawQuad>();
quad_1->SetNew(shared_quad_state, rect_1, rect_1, SkColors::kCyan, false);
auto* quad_2 =
root_render_pass->CreateAndAppendDrawQuad<DebugBorderDrawQuad>();
quad_2->SetNew(shared_quad_state, rect_2, rect_2, SkColors::kMagenta, false);
auto* quad_3 =
root_render_pass->CreateAndAppendDrawQuad<DebugBorderDrawQuad>();
quad_3->SetNew(shared_quad_state, rect_3, rect_3, SkColors::kYellow, false);
// Test one non-opaque color.
// TODO(crbug.com/40219248): Colors clearly get transformed into ints at some
// point in the pipeline, so we need to use values n/255 for now.
SkColor4f semi_transparent_white =
SkColor4f{1.0f, 1.0f, 1.0f, 128.0 / 255.0f};
auto* quad_4 =
root_render_pass->CreateAndAppendDrawQuad<DebugBorderDrawQuad>();
quad_4->SetNew(shared_quad_state, rect_4, rect_4, semi_transparent_white,
false);
AggregatedRenderPassList list;
list.push_back(std::move(root_render_pass));
EXPECT_TRUE(RunPixelTest(
&list,
GenerateExpectedImage(
full_size,
{
{.rect = rect_4,
.color = semi_transparent_white,
.debug_border = true},
{.rect = rect_3,
.color = SkColors::kYellow,
.debug_border = true},
{.rect = rect_2,
.color = SkColors::kMagenta,
.debug_border = true},
{.rect = rect_1, .color = SkColors::kCyan, .debug_border = true},
}),
cc::ExactPixelComparator()));
}
#if !BUILDFLAG(IS_ANDROID)
TEST_F(SoftwareRendererTest, TileQuad) {
gfx::Size outer_size(100, 100);
gfx::Size inner_size(98, 98);
gfx::Rect outer_rect(outer_size);
gfx::Rect inner_rect(gfx::Point(1, 1), inner_size);
bool needs_blending = false;
SkBitmap yellow_tile;
yellow_tile.allocN32Pixels(outer_size.width(), outer_size.height());
yellow_tile.eraseColor(SK_ColorYELLOW);
SkBitmap cyan_tile;
cyan_tile.allocN32Pixels(inner_size.width(), inner_size.height());
cyan_tile.eraseColor(SK_ColorCYAN);
ResourceId resource_yellow =
this->AllocateAndFillSoftwareResource(outer_size, yellow_tile);
ResourceId resource_cyan =
this->AllocateAndFillSoftwareResource(inner_size, cyan_tile);
// Transfer resources to the parent, and get the resource map.
std::unordered_map<ResourceId, ResourceId, ResourceIdHasher> resource_map =
cc::SendResourceAndGetChildToParentMap(
{resource_yellow, resource_cyan}, resource_provider(),
child_resource_provider(),
child_context_provider_->SharedImageInterface());
ResourceId mapped_resource_yellow = resource_map[resource_yellow];
ResourceId mapped_resource_cyan = resource_map[resource_cyan];
gfx::Rect root_rect = outer_rect;
AggregatedRenderPassId root_render_pass_id{1};
auto root_render_pass = std::make_unique<AggregatedRenderPass>();
root_render_pass->SetNew(root_render_pass_id, root_rect, root_rect,
gfx::Transform());
SharedQuadState* shared_quad_state =
root_render_pass->CreateAndAppendSharedQuadState();
shared_quad_state->SetAll(gfx::Transform(), outer_rect, outer_rect,
gfx::MaskFilterInfo(), /*clip=*/std::nullopt,
/*contents_opaque=*/true, /*opacity_f=*/1.0,
SkBlendMode::kSrcOver, /*sorting_context=*/0,
/*layer_id=*/0u, /*fast_rounded_corner=*/false);
auto* inner_quad = root_render_pass->CreateAndAppendDrawQuad<TileDrawQuad>();
inner_quad->SetNew(shared_quad_state, inner_rect, inner_rect, needs_blending,
mapped_resource_cyan, gfx::RectF(gfx::SizeF(inner_size)),
false, false);
auto* outer_quad = root_render_pass->CreateAndAppendDrawQuad<TileDrawQuad>();
outer_quad->SetNew(shared_quad_state, outer_rect, outer_rect, needs_blending,
mapped_resource_yellow, gfx::RectF(gfx::SizeF(outer_size)),
false, false);
AggregatedRenderPassList list;
list.push_back(std::move(root_render_pass));
EXPECT_TRUE(
RunPixelTest(&list,
GenerateExpectedImage(
outer_size,
{
{.rect = outer_rect, .color = SkColors::kYellow},
{.rect = inner_rect, .color = SkColors::kCyan},
}),
cc::ExactPixelComparator()));
}
TEST_F(SoftwareRendererTest, TileQuadVisibleRect) {
gfx::Size tile_size(100, 100);
gfx::Rect tile_rect(tile_size);
gfx::Rect visible_rect = tile_rect;
bool needs_blending = false;
visible_rect.Inset(gfx::Insets::TLBR(2, 1, 4, 3));
SkBitmap cyan_tile; // The lowest five rows are yellow.
cyan_tile.allocN32Pixels(tile_size.width(), tile_size.height());
cyan_tile.eraseColor(SK_ColorCYAN);
cyan_tile.eraseArea(SkIRect::MakeLTRB(0, visible_rect.bottom() - 1,
tile_rect.width(), tile_rect.bottom()),
SK_ColorYELLOW);
ResourceId resource_cyan =
AllocateAndFillSoftwareResource(tile_size, cyan_tile);
// Transfer resources to the parent, and get the resource map.
std::unordered_map<ResourceId, ResourceId, ResourceIdHasher> resource_map =
cc::SendResourceAndGetChildToParentMap(
{resource_cyan}, resource_provider(), child_resource_provider(),
child_context_provider_->SharedImageInterface());
ResourceId mapped_resource_cyan = resource_map[resource_cyan];
gfx::Rect root_rect(tile_size);
AggregatedRenderPassId root_render_pass_id{1};
std::unique_ptr<AggregatedRenderPass> root_render_pass =
std::make_unique<AggregatedRenderPass>();
root_render_pass->SetNew(root_render_pass_id, root_rect, root_rect,
gfx::Transform());
SharedQuadState* shared_quad_state =
root_render_pass->CreateAndAppendSharedQuadState();
shared_quad_state->SetAll(gfx::Transform(), tile_rect, tile_rect,
gfx::MaskFilterInfo(), /*clip=*/std::nullopt,
/*contents_opaque=*/true, /*opacity_f=*/1.0,
SkBlendMode::kSrcOver, /*sorting_context=*/0,
/*layer_id=*/0u, /*fast_rounded_corner=*/false);
auto* quad = root_render_pass->CreateAndAppendDrawQuad<TileDrawQuad>();
quad->SetNew(shared_quad_state, tile_rect, tile_rect, needs_blending,
mapped_resource_cyan, gfx::RectF(gfx::SizeF(tile_size)), false,
false);
quad->visible_rect = visible_rect;
AggregatedRenderPassList list;
list.push_back(std::move(root_render_pass));
EXPECT_TRUE(RunPixelTest(
&list,
GenerateExpectedImage(
root_rect.size(),
{
{.rect = visible_rect, .color = SkColors::kCyan},
// Ensure last visible line is correct.
{.rect = gfx::Rect(visible_rect.x(), visible_rect.bottom() - 1,
visible_rect.width(), 1),
.color = SkColors::kYellow},
}),
cc::ExactPixelComparator()));
}
#endif // BUILDFLAG(IS_ANDROID)
class SoftwareRendererTestShouldClearRootRenderPass
: public SoftwareRendererTest {
public:
void SetUp() override {
renderer_settings_.should_clear_root_render_pass = false;
SoftwareRendererTest::SetUp();
}
};
TEST_F(SoftwareRendererTestShouldClearRootRenderPass,
ShouldClearRootRenderPass) {
gfx::Size viewport_size(100, 100);
AggregatedRenderPassList list;
// Draw a fullscreen green quad in a first frame.
AggregatedRenderPassId root_clear_pass_id{1};
AggregatedRenderPass* root_clear_pass =
cc::AddRenderPass(&list, root_clear_pass_id, gfx::Rect(viewport_size),
gfx::Transform(), cc::FilterOperations());
cc::AddQuad(root_clear_pass, gfx::Rect(viewport_size), SkColors::kGreen);
EXPECT_TRUE(RunPixelTest(
&list,
GenerateExpectedImage(
viewport_size,
{
{.rect = gfx::Rect(viewport_size), .color = SkColors::kGreen},
}),
cc::ExactPixelComparator()));
// Draw a smaller magenta rect without filling the viewport in a separate
// frame.
gfx::Rect smaller_rect(20, 20, 60, 60);
AggregatedRenderPassId root_smaller_pass_id{2};
AggregatedRenderPass* root_smaller_pass =
cc::AddRenderPass(&list, root_smaller_pass_id, gfx::Rect(viewport_size),
gfx::Transform(), cc::FilterOperations());
cc::AddQuad(root_smaller_pass, smaller_rect, SkColors::kMagenta);
EXPECT_TRUE(RunPixelTest(
&list,
GenerateExpectedImage(
viewport_size,
{
// If we didn't clear, the borders should still be green.
{.rect = gfx::Rect(viewport_size), .color = SkColors::kGreen},
{.rect = smaller_rect, .color = SkColors::kMagenta},
}),
cc::ExactPixelComparator()));
}
TEST_F(SoftwareRendererTest, RenderPassVisibleRect) {
gfx::Size viewport_size(100, 100);
AggregatedRenderPassList list;
// Pass drawn as inner quad is magenta.
gfx::Rect smaller_rect(20, 20, 60, 60);
AggregatedRenderPassId smaller_pass_id{2};
auto* smaller_pass =
cc::AddRenderPass(&list, smaller_pass_id, smaller_rect, gfx::Transform(),
cc::FilterOperations());
cc::AddQuad(smaller_pass, smaller_rect, SkColors::kMagenta);
// Root pass is green.
AggregatedRenderPassId root_clear_pass_id{1};
AggregatedRenderPass* root_clear_pass =
AddRenderPass(&list, root_clear_pass_id, gfx::Rect(viewport_size),
gfx::Transform(), cc::FilterOperations());
cc::AddRenderPassQuad(root_clear_pass, smaller_pass);
cc::AddQuad(root_clear_pass, gfx::Rect(viewport_size), SkColors::kGreen);
// Interior pass quad has smaller visible rect.
gfx::Rect interior_visible_rect(30, 30, 40, 40);
root_clear_pass->quad_list.front()->visible_rect = interior_visible_rect;
EXPECT_TRUE(RunPixelTest(
&list,
GenerateExpectedImage(
viewport_size,
{
{.rect = gfx::Rect(viewport_size), .color = SkColors::kGreen},
{.rect = interior_visible_rect, .color = SkColors::kMagenta},
}),
cc::ExactPixelComparator()));
}
TEST_F(SoftwareRendererTest, ClipRoundRect) {
gfx::Size viewport_size(100, 100);
gfx::Rect clip_rect = gfx::Rect(1, 1, 30, 30);
AggregatedRenderPassList list;
AggregatedRenderPassId root_pass_id{1};
AggregatedRenderPass* root_pass =
AddRenderPass(&list, root_pass_id, gfx::Rect(viewport_size),
gfx::Transform(), cc::FilterOperations());
// Draw outer rect with clipping.
{
gfx::Size outer_size(50, 50);
gfx::Rect outer_rect(outer_size);
SharedQuadState* shared_quad_state =
root_pass->CreateAndAppendSharedQuadState();
shared_quad_state->SetAll(gfx::Transform(), outer_rect, outer_rect,
gfx::MaskFilterInfo(), clip_rect,
/*contents_opaque=*/true, /*opacity_f=*/1.0,
SkBlendMode::kSrcOver, /*sorting_context=*/0,
/*layer_id=*/0u, /*fast_rounded_corner=*/false);
auto* outer_quad = root_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
outer_quad->SetNew(shared_quad_state, outer_rect, outer_rect,
SkColors::kGreen, false);
}
// Draw inner round rect.
{
gfx::Size inner_size(20, 20);
gfx::Rect inner_rect(inner_size);
SharedQuadState* shared_quad_state =
root_pass->CreateAndAppendSharedQuadState();
shared_quad_state->SetAll(
gfx::Transform(), inner_rect, inner_rect,
gfx::MaskFilterInfo(gfx::RRectF(gfx::RectF(5, 5, 10, 10), 2)),
/*clip=*/std::nullopt, /*contents_opaque=*/true, /*opacity_f=*/1.0,
SkBlendMode::kSrcOver, /*sorting_context=*/0,
/*layer_id=*/0u, /*fast_rounded_corner=*/false);
auto* inner_quad = root_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
inner_quad->SetNew(shared_quad_state, inner_rect, inner_rect,
SkColors::kRed, false);
}
EXPECT_TRUE(RunPixelTest(
&list,
GenerateExpectedImage(viewport_size,
{
{.rect = clip_rect, .color = SkColors::kGreen},
}),
cc::ExactPixelComparator()));
}
class SoftwareRendererTestPartialSwap : public SoftwareRendererTest {
void SetUp() override {
renderer_settings_.partial_swap_enabled = true;
SoftwareRendererTest::SetUp();
}
};
TEST_F(SoftwareRendererTestPartialSwap, PartialSwap) {
gfx::Size viewport_size(100, 100);
{
// Draw one black frame to make sure output surface is reshaped before
// tests.
AggregatedRenderPassList list;
AggregatedRenderPassId root_pass_id{1};
SurfaceDamageRectList surface_damage_rect_list;
auto* root_pass =
AddRenderPass(&list, root_pass_id, gfx::Rect(viewport_size),
gfx::Transform(), cc::FilterOperations());
cc::AddQuad(root_pass, gfx::Rect(viewport_size), SkColors::kBlack);
// Partial frame, we should pass this rect to the SoftwareOutputDevice.
// partial swap is enabled.
root_pass->damage_rect = gfx::Rect(viewport_size);
EXPECT_TRUE(RunPixelTest(
&list,
GenerateExpectedImage(
viewport_size,
{
{.rect = gfx::Rect(viewport_size), .color = SkColors::kBlack},
}),
cc::ExactPixelComparator()));
}
{
AggregatedRenderPassList list;
AggregatedRenderPassId root_pass_id{1};
SurfaceDamageRectList surface_damage_rect_list;
auto* root_pass =
AddRenderPass(&list, root_pass_id, gfx::Rect(viewport_size),
gfx::Transform(), cc::FilterOperations());
cc::AddQuad(root_pass, gfx::Rect(viewport_size), SkColors::kGreen);
// Partial frame, only this region will draw, even though the quad covers
// the whole frame.
gfx::Rect damage_rect = gfx::Rect(2, 2, 3, 3);
root_pass->damage_rect = damage_rect;
EXPECT_TRUE(RunPixelTest(
&list,
GenerateExpectedImage(
viewport_size,
{
{.rect = gfx::Rect(viewport_size), .color = SkColors::kBlack},
{.rect = damage_rect, .color = SkColors::kGreen},
}),
cc::ExactPixelComparator()));
}
}
} // namespace
} // namespace viz