blob: edf510e788871bbc51eb9db9f394316270c2b991 [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 <stddef.h>
#include <stdint.h>
#include <algorithm>
#include <array>
#include <memory>
#include <optional>
#include <tuple>
#include "base/command_line.h"
#include "base/compiler_specific.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/aligned_memory.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/read_only_shared_memory_region.h"
#include "base/memory/shared_memory_mapping.h"
#include "base/strings/stringprintf.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "cc/base/math_util.h"
#include "cc/paint/paint_flags.h"
#include "cc/paint/skia_paint_canvas.h"
#include "cc/test/fake_raster_source.h"
#include "cc/test/fake_recording_source.h"
#include "cc/test/pixel_comparator.h"
#include "cc/test/pixel_test.h"
#include "cc/test/render_pass_test_utils.h"
#include "cc/test/resource_provider_test_utils.h"
#include "cc/test/test_types.h"
#include "components/viz/client/client_resource_provider.h"
#include "components/viz/common/features.h"
#include "components/viz/common/quads/aggregated_render_pass_draw_quad.h"
#include "components/viz/common/quads/compositor_render_pass_draw_quad.h"
#include "components/viz/common/quads/picture_draw_quad.h"
#include "components/viz/common/quads/solid_color_draw_quad.h"
#include "components/viz/common/quads/texture_draw_quad.h"
#include "components/viz/common/resources/shared_image_format.h"
#include "components/viz/common/resources/shared_image_format_utils.h"
#include "components/viz/common/switches.h"
#include "components/viz/service/display/delegated_ink_point_pixel_test_helper.h"
#include "components/viz/service/display/software_renderer.h"
#include "components/viz/service/display/viz_pixel_test.h"
#include "components/viz/test/buildflags.h"
#include "components/viz/test/test_in_process_context_provider.h"
#include "components/viz/test/test_types.h"
#include "gpu/command_buffer/client/client_shared_image.h"
#include "gpu/command_buffer/client/shared_image_interface.h"
#include "gpu/command_buffer/common/shared_image_usage.h"
#include "media/base/media_switches.h"
#include "media/base/video_frame.h"
#include "media/base/video_types.h"
#include "media/renderers/video_resource_updater.h"
#include "third_party/skia/include/core/SkColor.h"
#include "third_party/skia/include/core/SkMatrix.h"
#include "third_party/skia/include/core/SkRefCnt.h"
#include "third_party/skia/include/core/SkSurface.h"
#include "third_party/skia/include/effects/SkColorMatrixFilter.h"
#include "third_party/skia/include/private/chromium/SkPMColor.h"
#include "ui/gfx/color_transform.h"
#include "ui/gfx/geometry/mask_filter_info.h"
#include "ui/gfx/geometry/point_f.h"
#include "ui/gfx/geometry/rect_f.h"
#include "ui/gfx/geometry/skia_conversions.h"
#include "ui/gfx/geometry/transform_util.h"
#include "ui/gfx/video_types.h"
#include "ui/gl/gl_implementation.h"
namespace viz {
namespace {
const gfx::DisplayColorSpaces kRec601DisplayColorSpaces(
gfx::ColorSpace(gfx::ColorSpace::PrimaryID::SMPTE170M,
gfx::ColorSpace::TransferID::SMPTE170M));
#if !BUILDFLAG(IS_ANDROID)
constexpr char kANGLEMetalStr[] = "_angle_metal";
constexpr char kGraphiteStr[] = "_graphite";
bool IsANGLEMetal() {
return gl::GetGLImplementationParts() ==
gl::GLImplementationParts(gl::ANGLEImplementation::kMetal);
}
template <typename T>
base::span<const uint8_t> MakePixelSpan(const std::vector<T>& vec) {
return base::as_byte_span(vec);
}
base::span<const uint8_t> MakePixelSpan(const SkBitmap& bitmap) {
return UNSAFE_TODO(base::span(static_cast<const uint8_t*>(bitmap.getPixels()),
bitmap.computeByteSize()));
}
void DeleteSharedImage(scoped_refptr<gpu::ClientSharedImage> shared_image,
const gpu::SyncToken& sync_token,
bool is_lost) {
shared_image->UpdateDestructionSyncToken(sync_token);
}
ResourceId CreateGpuResource(
scoped_refptr<RasterContextProvider> context_provider,
ClientResourceProvider* resource_provider,
const gfx::Size& size,
SharedImageFormat format,
SkAlphaType alpha_type,
gfx::ColorSpace color_space,
base::span<const uint8_t> pixels,
GrSurfaceOrigin origin = kTopLeft_GrSurfaceOrigin) {
DCHECK(context_provider);
gpu::SharedImageInterface* sii = context_provider->SharedImageInterface();
DCHECK(sii);
auto client_shared_image = sii->CreateSharedImage(
{format, size, color_space, origin, alpha_type,
gpu::SHARED_IMAGE_USAGE_DISPLAY_READ, "TestLabel"},
pixels);
TransferableResource gl_resource = TransferableResource::Make(
client_shared_image, TransferableResource::ResourceSource::kTest,
client_shared_image->creation_sync_token());
auto release_callback =
base::BindOnce(&DeleteSharedImage, std::move(client_shared_image));
return resource_provider->ImportResource(gl_resource,
std::move(release_callback));
}
std::unique_ptr<AggregatedRenderPass> CreateTestRootRenderPass(
AggregatedRenderPassId id,
const gfx::Rect& rect) {
auto pass = std::make_unique<AggregatedRenderPass>();
const gfx::Rect output_rect = rect;
const gfx::Rect damage_rect = rect;
const gfx::Transform transform_to_root_target;
pass->SetNew(id, output_rect, damage_rect, transform_to_root_target);
return pass;
}
std::unique_ptr<CompositorRenderPass> CreateTestRootRenderPass(
CompositorRenderPassId id,
const gfx::Rect& rect) {
auto pass = CompositorRenderPass::Create(/*shared_quad_state_list_size=*/1u,
/*quad_list_size*/ 1u);
const gfx::Rect output_rect = rect;
const gfx::Rect damage_rect = rect;
const gfx::Transform transform_to_root_target;
pass->SetNew(id, output_rect, damage_rect, transform_to_root_target);
return pass;
}
std::unique_ptr<AggregatedRenderPass> CreateTestRenderPass(
AggregatedRenderPassId id,
const gfx::Rect& rect,
const gfx::Transform& transform_to_root_target) {
auto pass = std::make_unique<AggregatedRenderPass>();
const gfx::Rect output_rect = rect;
const gfx::Rect damage_rect = rect;
pass->SetNew(id, output_rect, damage_rect, transform_to_root_target);
return pass;
}
SharedQuadState* CreateTestSharedQuadState(
gfx::Transform quad_to_target_transform,
const gfx::Rect& rect,
RenderPassInternal* render_pass,
const gfx::MaskFilterInfo& mask_filter_info) {
const gfx::Rect layer_rect = rect;
const gfx::Rect visible_layer_rect = rect;
const bool are_contents_opaque = false;
const float opacity = 1.0f;
const SkBlendMode blend_mode = SkBlendMode::kSrcOver;
int sorting_context_id = 0;
SharedQuadState* shared_state = render_pass->CreateAndAppendSharedQuadState();
shared_state->SetAll(quad_to_target_transform, layer_rect, visible_layer_rect,
mask_filter_info, /**clip_rect=*/std::nullopt,
are_contents_opaque, opacity, blend_mode,
sorting_context_id,
/*layer_id=*/0u, /*fast_rounded_corner=*/false);
return shared_state;
}
SharedQuadState* CreateTestSharedQuadStateClipped(
gfx::Transform quad_to_target_transform,
const gfx::Rect& rect,
const gfx::Rect& clip_rect,
AggregatedRenderPass* render_pass) {
const gfx::Rect layer_rect = rect;
const gfx::Rect visible_layer_rect = clip_rect;
const bool are_contents_opaque = false;
const float opacity = 1.0f;
const SkBlendMode blend_mode = SkBlendMode::kSrcOver;
int sorting_context_id = 0;
SharedQuadState* shared_state = render_pass->CreateAndAppendSharedQuadState();
shared_state->SetAll(quad_to_target_transform, layer_rect, visible_layer_rect,
/*mask_filter_info=*/gfx::MaskFilterInfo(), clip_rect,
are_contents_opaque, opacity, blend_mode,
sorting_context_id, /*layer_id=*/0u,
/*fast_rounded_corner=*/false);
return shared_state;
}
void CreateTestRenderPassDrawQuad(const SharedQuadState* shared_state,
const gfx::Rect& rect,
AggregatedRenderPassId pass_id,
AggregatedRenderPass* render_pass) {
auto* quad =
render_pass->CreateAndAppendDrawQuad<AggregatedRenderPassDrawQuad>();
// The full `rect` is drawn (visible_rect == rect) but texture coords
// are relative to the underlying image.
gfx::RectF tex_coords{static_cast<float>(rect.width()),
static_cast<float>(rect.height())};
quad->SetNew(shared_state, rect, rect, pass_id,
kInvalidResourceId, // mask_resource_id
gfx::RectF(), // mask_uv_rect
gfx::Size(), // mask_texture_size
tex_coords, // tex_coord_rect
false); // force_anti_aliasing_off
}
// Create a TextureDrawDrawQuad with two given colors.
// flipped_texture_quad: The TextureDrawDrawQuad is y flipped.
// half_and_half: if true, the upper half part of the texture is filled with
// texel_color_one, other part of the texture is filled with texel_color_two.
// if false, a 1/2 width and height rectangle in the middle of the quad will
// be filled with texel_color_two, other part of the texture is filled with
// texel_color_one,
// TODO(crbug.com/40219248): Make this function use SkColor4f
void CreateTestTwoColoredTextureDrawQuad(
bool gpu_resource,
const gfx::Rect& rect,
SkColor4f texel_color_one,
SkColor4f texel_color_two,
SkColor4f background_color,
bool premultiplied_alpha,
bool flipped_texture_quad,
bool half_and_half,
const SharedQuadState* shared_state,
DisplayResourceProvider* resource_provider,
ClientResourceProvider* child_resource_provider,
scoped_refptr<RasterContextProvider> child_context_provider,
AggregatedRenderPass* render_pass) {
// As this function renders to an RGBA_8888 texture, it makes sense to use
// integer colors
SkPMColor pixel_color_one =
premultiplied_alpha
? SkPreMultiplyColor(texel_color_one.toSkColor())
: SkPMColorSetARGB(255 * texel_color_one.fA, 255 * texel_color_one.fR,
255 * texel_color_one.fG,
255 * texel_color_one.fB);
SkPMColor pixel_color_two =
premultiplied_alpha
? SkPreMultiplyColor(texel_color_two.toSkColor())
: SkPMColorSetARGB(255 * texel_color_two.fA, 255 * texel_color_two.fR,
255 * texel_color_two.fG,
255 * texel_color_two.fB);
// The default color is texel_color_one
std::vector<uint32_t> pixels(rect.size().GetArea(), pixel_color_one);
if (half_and_half) {
// Fill the bottom half part of the texture with texel_color_two.
for (int i = rect.height() / 2; i < rect.height(); ++i) {
for (int k = 0; k < rect.width(); ++k) {
pixels[i * rect.width() + k] = pixel_color_two;
}
}
} else {
// Fill a 1/2 width and height rectangle with pixel_color_two.
for (int i = rect.height() / 4; i < (rect.height() * 3 / 4); ++i) {
for (int k = rect.width() / 4; k < (rect.width() * 3 / 4); ++k) {
pixels[i * rect.width() + k] = pixel_color_two;
}
}
}
const GrSurfaceOrigin origin = flipped_texture_quad
? kBottomLeft_GrSurfaceOrigin
: kTopLeft_GrSurfaceOrigin;
const SkAlphaType alpha_type =
premultiplied_alpha ? kPremul_SkAlphaType : kUnpremul_SkAlphaType;
ResourceId resource;
if (gpu_resource) {
resource = CreateGpuResource(
child_context_provider, child_resource_provider, rect.size(),
SinglePlaneFormat::kBGRA_8888, alpha_type, gfx::ColorSpace(),
MakePixelSpan(pixels), origin);
} else {
auto shared_image =
child_context_provider->SharedImageInterface()
->CreateSharedImageForSoftwareCompositor(
{SinglePlaneFormat::kBGRA_8888, rect.size(), gfx::ColorSpace(),
origin, alpha_type, gpu::SHARED_IMAGE_USAGE_CPU_WRITE_ONLY,
"PixelTest"});
auto mapping = shared_image->Map();
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));
resource = child_resource_provider->ImportResource(
std::move(transferable_resource), std::move(release_callback));
uint32_t* ptr =
reinterpret_cast<uint32_t*>(mapping->GetMemoryForPlane(0).data());
base::span<uint32_t> span = UNSAFE_BUFFERS(base::span(ptr, pixels.size()));
std::ranges::copy(pixels, span.begin());
}
// Return the mapped resource id.
std::unordered_map<ResourceId, ResourceId, ResourceIdHasher> resource_map =
cc::SendResourceAndGetChildToParentMap(
{resource}, resource_provider, child_resource_provider,
child_context_provider->SharedImageInterface());
ResourceId mapped_resource = resource_map[resource];
bool needs_blending = true;
const gfx::PointF uv_top_left(0.0f, 0.0f);
const gfx::PointF uv_bottom_right(1.0f, 1.0f);
const bool nearest_neighbor = false;
auto* quad = render_pass->CreateAndAppendDrawQuad<TextureDrawQuad>();
quad->SetNew(shared_state, rect, rect, needs_blending, mapped_resource,
uv_top_left, uv_bottom_right, background_color, nearest_neighbor,
/*secure_output=*/false, gfx::ProtectedVideoType::kClear);
}
// TODO(crbug.com/40219248): Make this function use SkColor4f
void CreateTestTextureDrawQuad(
bool gpu_resource,
const gfx::Rect& rect,
SkColor4f texel_color,
SkColor4f background_color,
bool premultiplied_alpha,
const SharedQuadState* shared_state,
DisplayResourceProvider* resource_provider,
ClientResourceProvider* child_resource_provider,
scoped_refptr<RasterContextProvider> child_context_provider,
AggregatedRenderPass* render_pass) {
// As this function renders to an RGBA_8888 texture, it makes sense to use
// integer colors
SkPMColor pixel_color =
premultiplied_alpha
? SkPreMultiplyColor(texel_color.toSkColor())
: SkPMColorSetARGB(texel_color.fA * 255, texel_color.fR * 255,
texel_color.fG * 255, texel_color.fB * 255);
size_t num_pixels = static_cast<size_t>(rect.width()) * rect.height();
std::vector<uint32_t> pixels(num_pixels, pixel_color);
const SkAlphaType alpha_type =
premultiplied_alpha ? kPremul_SkAlphaType : kUnpremul_SkAlphaType;
ResourceId resource;
if (gpu_resource) {
resource =
CreateGpuResource(child_context_provider, child_resource_provider,
rect.size(), SinglePlaneFormat::kRGBA_8888,
alpha_type, gfx::ColorSpace(), MakePixelSpan(pixels));
} else {
auto shared_image =
child_context_provider->SharedImageInterface()
->CreateSharedImageForSoftwareCompositor(
{SinglePlaneFormat::kBGRA_8888, rect.size(), gfx::ColorSpace(),
kTopLeft_GrSurfaceOrigin, alpha_type,
gpu::SHARED_IMAGE_USAGE_CPU_WRITE_ONLY, "PixelTest"});
auto mapping = shared_image->Map();
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));
resource = child_resource_provider->ImportResource(
std::move(transferable_resource), std::move(release_callback));
uint32_t* ptr =
reinterpret_cast<uint32_t*>(mapping->GetMemoryForPlane(0).data());
base::span<uint32_t> span = UNSAFE_BUFFERS(base::span(ptr, pixels.size()));
std::ranges::copy(pixels, span.begin());
}
// Return the mapped resource id.
std::unordered_map<ResourceId, ResourceId, ResourceIdHasher> resource_map =
cc::SendResourceAndGetChildToParentMap(
{resource}, resource_provider, child_resource_provider,
child_context_provider->SharedImageInterface());
ResourceId mapped_resource = resource_map[resource];
bool needs_blending = true;
const gfx::PointF uv_top_left(0.0f, 0.0f);
const gfx::PointF uv_bottom_right(1.0f, 1.0f);
const bool nearest_neighbor = false;
auto* quad = render_pass->CreateAndAppendDrawQuad<TextureDrawQuad>();
quad->SetNew(shared_state, rect, rect, needs_blending, mapped_resource,
uv_top_left, uv_bottom_right, background_color, nearest_neighbor,
/*secure_output=*/false, gfx::ProtectedVideoType::kClear);
}
void CreateTestY16TextureDrawQuad_FromVideoFrame(
scoped_refptr<media::VideoFrame> video_frame,
const gfx::Transform& transform,
int sorting_context_id,
CompositorRenderPass* render_pass,
media::VideoResourceUpdater* video_resource_updater,
const gfx::Rect& rect,
const gfx::Rect& visible_rect) {
bool contents_opaque = false;
float draw_opacity = 1.0f;
// Obtain frame resources and perform AppendQuads which chooses the correct
// quad to append to.
video_resource_updater->ObtainFrameResource(video_frame);
video_resource_updater->AppendQuad(render_pass, video_frame, transform, rect,
visible_rect, gfx::MaskFilterInfo(),
/*clip_rect=*/std::nullopt,
contents_opaque, draw_opacity,
sorting_context_id);
}
void CreateTestY16TextureDrawQuad_TwoColor(
const gfx::Transform& transform,
int sorting_context_id,
uint8_t g_foreground,
uint8_t g_background,
CompositorRenderPass* render_pass,
media::VideoResourceUpdater* video_resource_updater,
const gfx::Rect& rect,
const gfx::Rect& visible_rect,
const gfx::Rect& foreground_rect) {
base::AlignedHeapArray<uint8_t> memory = base::AlignedUninit<uint8_t>(
rect.size().GetArea() * 2, media::VideoFrame::kFrameAddressAlignment);
const gfx::Rect video_visible_rect = gfx::Rect(rect.width(), rect.height());
scoped_refptr<media::VideoFrame> video_frame =
media::VideoFrame::WrapExternalData(
media::PIXEL_FORMAT_Y16, rect.size(), video_visible_rect,
visible_rect.size(), memory, base::TimeDelta());
DCHECK_EQ(video_frame->rows(0) % 2, 0);
DCHECK_EQ(video_frame->stride(0) % 2, 0ul);
for (int j = 0; j < video_frame->rows(0); ++j) {
uint8_t* row =
UNSAFE_TODO(video_frame->writable_data(0) + j * video_frame->stride(0));
if (j < foreground_rect.y() || j >= foreground_rect.bottom()) {
for (size_t i = 0; i < video_frame->stride(0) / 2; ++i) {
*UNSAFE_TODO(row++) =
i & 0xFF; // Fill R with anything. It is not rendered.
*UNSAFE_TODO(row++) = g_background;
}
} else {
for (size_t i = 0; i < std::min<size_t>(video_frame->stride(0) / 2,
foreground_rect.x());
++i) {
*UNSAFE_TODO(row++) = i & 0xFF;
*UNSAFE_TODO(row++) = g_background;
}
for (size_t i = foreground_rect.x();
i < std::min<size_t>(video_frame->stride(0) / 2,
foreground_rect.right());
++i) {
*UNSAFE_TODO(row++) = i & 0xFF;
*UNSAFE_TODO(row++) = g_foreground;
}
for (size_t i = foreground_rect.right(); i < video_frame->stride(0) / 2;
++i) {
*UNSAFE_TODO(row++) = i & 0xFF;
*UNSAFE_TODO(row++) = g_background;
}
}
}
CreateTestY16TextureDrawQuad_FromVideoFrame(
video_frame, transform, sorting_context_id, render_pass,
video_resource_updater, rect, visible_rect);
}
void CreateTestMultiplanarVideoDrawQuad(
scoped_refptr<media::VideoFrame> video_frame,
uint8_t alpha_value,
gfx::Transform transform,
gfx::MaskFilterInfo mask_filter_info,
int sorting_context_id,
CompositorRenderPass* render_pass,
media::VideoResourceUpdater* video_resource_updater,
const gfx::Rect& rect,
const gfx::Rect& visible_rect) {
DCHECK(video_frame->ColorSpace().IsValid());
float draw_opacity = 1.0f;
const bool with_alpha = (video_frame->format() == media::PIXEL_FORMAT_I420A);
if (with_alpha) {
UNSAFE_TODO(memset(video_frame->writable_data(media::VideoFrame::Plane::kA),
alpha_value,
video_frame->stride(media::VideoFrame::Plane::kA) *
video_frame->rows(media::VideoFrame::Plane::kA)));
} else {
EXPECT_EQ(alpha_value, 255);
}
// Obtain frame resources and perform AppendQuads which chooses the correct
// quad to append to.
video_resource_updater->ObtainFrameResource(video_frame);
video_resource_updater->AppendQuad(
render_pass, video_frame, transform, rect, visible_rect, mask_filter_info,
/*clip_rect=*/std::nullopt, /*context_opaque=*/with_alpha, draw_opacity,
sorting_context_id);
}
// A unit square at the origin to indicate full tex coordinate coverage.
constexpr gfx::RectF kUnitSquare(0.f, 0.f, 1.f, 1.f);
class TestVideoFrameBuilder {
public:
TestVideoFrameBuilder() = delete;
// Create an empty video frame with common parameters.
TestVideoFrameBuilder(media::VideoPixelFormat format,
const gfx::ColorSpace& color_space,
const gfx::RectF& tex_coord_rect,
const gfx::Size& coded_size,
const gfx::Size& natural_size) {
const gfx::Rect video_visible_rect = gfx::ToNearestRect(
gfx::RectF(tex_coord_rect.x() * coded_size.width(),
tex_coord_rect.y() * coded_size.height(),
tex_coord_rect.width() * coded_size.width(),
tex_coord_rect.height() * coded_size.height()));
video_frame_ =
media::VideoFrame::CreateFrame(format, coded_size, video_visible_rect,
natural_size, base::TimeDelta());
video_frame_->set_color_space(color_space);
}
scoped_refptr<media::VideoFrame> DrawStriped() {
// YUV values representing a striped pattern, for validating texture
// coordinates for sampling.
uint8_t y_value = 0;
uint8_t u_value = 0;
uint8_t v_value = 0;
for (int i = 0; i < video_frame_->rows(media::VideoFrame::Plane::kY); ++i) {
uint8_t* y_row = UNSAFE_TODO(
video_frame_->writable_data(media::VideoFrame::Plane::kY) +
video_frame_->stride(media::VideoFrame::Plane::kY) * i);
for (int j = 0; j < video_frame_->row_bytes(media::VideoFrame::Plane::kY);
++j) {
UNSAFE_TODO(y_row[j]) = (y_value += 1);
}
}
for (int i = 0; i < video_frame_->rows(media::VideoFrame::Plane::kU); ++i) {
uint8_t* u_row = UNSAFE_TODO(
video_frame_->writable_data(media::VideoFrame::Plane::kU) +
video_frame_->stride(media::VideoFrame::Plane::kU) * i);
uint8_t* v_row = UNSAFE_TODO(
video_frame_->writable_data(media::VideoFrame::Plane::kV) +
video_frame_->stride(media::VideoFrame::Plane::kV) * i);
for (int j = 0; j < video_frame_->row_bytes(media::VideoFrame::Plane::kU);
++j) {
UNSAFE_TODO(u_row[j]) = (u_value += 3);
UNSAFE_TODO(v_row[j]) = (v_value += 5);
}
}
return std::move(video_frame_);
}
// Creates a video frame of size background_size filled with yuv_background,
// and then draws a foreground rectangle in a different color on top of
// that. The foreground rectangle must have coordinates that are divisible
// by 2 because YUV is a block format.
scoped_refptr<media::VideoFrame> DrawTwoColor(
uint8_t y_background,
uint8_t u_background,
uint8_t v_background,
const gfx::Rect& foreground_rect,
uint8_t y_foreground,
uint8_t u_foreground,
uint8_t v_foreground) {
auto planes = std::to_array<int>({
media::VideoFrame::Plane::kY,
media::VideoFrame::Plane::kU,
media::VideoFrame::Plane::kV,
});
auto yuv_background = std::to_array<uint8_t>({
y_background,
u_background,
v_background,
});
auto yuv_foreground = std::to_array<uint8_t>({
y_foreground,
u_foreground,
v_foreground,
});
auto sample_size = std::to_array<int>({1, 2, 2});
for (int i = 0; i < 3; ++i) {
UNSAFE_TODO(memset(
video_frame_->writable_data(planes[i]), yuv_background[i],
video_frame_->stride(planes[i]) * video_frame_->rows(planes[i])));
}
for (int i = 0; i < 3; ++i) {
// Since yuv encoding uses block encoding, widths have to be divisible
// by the sample size in order for this function to behave properly.
DCHECK_EQ(foreground_rect.x() % sample_size[i], 0);
DCHECK_EQ(foreground_rect.y() % sample_size[i], 0);
DCHECK_EQ(foreground_rect.width() % sample_size[i], 0);
DCHECK_EQ(foreground_rect.height() % sample_size[i], 0);
gfx::Rect sample_rect(foreground_rect.x() / sample_size[i],
foreground_rect.y() / sample_size[i],
foreground_rect.width() / sample_size[i],
foreground_rect.height() / sample_size[i]);
for (int y = sample_rect.y(); y < sample_rect.bottom(); ++y) {
for (int x = sample_rect.x(); x < sample_rect.right(); ++x) {
size_t offset = y * video_frame_->stride(planes[i]) + x;
UNSAFE_TODO(video_frame_->writable_data(planes[i])[offset]) =
yuv_foreground[i];
}
}
}
return std::move(video_frame_);
}
scoped_refptr<media::VideoFrame> DrawSolid(uint8_t y, uint8_t u, uint8_t v) {
// YUV values of a solid, constant, color. Useful for testing that color
// space/color range are being handled properly.
UNSAFE_TODO(
memset(video_frame_->writable_data(media::VideoFrame::Plane::kY), y,
video_frame_->stride(media::VideoFrame::Plane::kY) *
video_frame_->rows(media::VideoFrame::Plane::kY)));
if (video_frame_->format() == media::PIXEL_FORMAT_NV12) {
const int stride_uv = video_frame_->stride(media::VideoFrame::Plane::kUV);
const int half_height = (video_frame_->coded_size().height() + 1) / 2;
uint8_t* uv_plane =
video_frame_->writable_data(media::VideoFrame::Plane::kUV);
// Set U and V.
for (int row = 0; row < half_height; ++row) {
for (int col = 0; col < stride_uv; col++) {
*uv_plane = col % 2 == 0 ? u : v;
UNSAFE_TODO(uv_plane++);
}
}
} else {
// Only NV12, YV12 and I420 formats are used for testing here.
CHECK(video_frame_->format() == media::PIXEL_FORMAT_I420 ||
video_frame_->format() == media::PIXEL_FORMAT_YV12);
UNSAFE_TODO(
memset(video_frame_->writable_data(media::VideoFrame::Plane::kU), u,
video_frame_->stride(media::VideoFrame::Plane::kU) *
video_frame_->rows(media::VideoFrame::Plane::kU)));
UNSAFE_TODO(
memset(video_frame_->writable_data(media::VideoFrame::Plane::kV), v,
video_frame_->stride(media::VideoFrame::Plane::kV) *
video_frame_->rows(media::VideoFrame::Plane::kV)));
}
return std::move(video_frame_);
}
private:
scoped_refptr<media::VideoFrame> video_frame_;
};
// Create two quads of specified colors on half-pixel boundaries.
void CreateTestAxisAlignedQuads(const gfx::Rect& rect,
SkColor4f front_color,
SkColor4f back_color,
bool needs_blending,
bool force_aa_off,
AggregatedRenderPass* pass) {
gfx::Transform front_quad_to_target_transform;
front_quad_to_target_transform.Translate(50, 50);
front_quad_to_target_transform.Scale(0.5f + 1.0f / (rect.width() * 2.0f),
0.5f + 1.0f / (rect.height() * 2.0f));
SharedQuadState* front_shared_state =
CreateTestSharedQuadState(front_quad_to_target_transform, rect, pass,
gfx::MaskFilterInfo());
auto* front = pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
front->SetAll(front_shared_state, rect, rect, needs_blending, front_color,
force_aa_off);
gfx::Transform back_quad_to_target_transform;
back_quad_to_target_transform.Translate(25.5f, 25.5f);
back_quad_to_target_transform.Scale(0.5f, 0.5f);
SharedQuadState* back_shared_state =
CreateTestSharedQuadState(back_quad_to_target_transform, rect, pass,
gfx::MaskFilterInfo());
auto* back = pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
back->SetAll(back_shared_state, rect, rect, needs_blending, back_color,
force_aa_off);
}
using RendererPixelTest = VizPixelTestWithParam;
INSTANTIATE_TEST_SUITE_P(,
RendererPixelTest,
testing::ValuesIn(GetRendererTypes()),
testing::PrintToStringParamName());
using GPURendererPixelTest = VizPixelTestWithParam;
INSTANTIATE_TEST_SUITE_P(,
GPURendererPixelTest,
// TODO(crbug.com/40106226): Enable these tests for
// SkiaRenderer Dawn once video is supported.
testing::ValuesIn(GetGpuRendererTypes()),
testing::PrintToStringParamName());
// GetGpuRendererTypes() can return an empty list, e.g. on Fuchsia ARM64.
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(GPURendererPixelTest);
TEST_P(RendererPixelTest, SimpleGreenRect) {
gfx::Rect rect(this->device_viewport_size_);
AggregatedRenderPassId id{1};
auto pass = CreateTestRootRenderPass(id, rect);
SharedQuadState* shared_state = CreateTestSharedQuadState(
gfx::Transform(), rect, pass.get(), gfx::MaskFilterInfo());
auto* color_quad = pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
color_quad->SetNew(shared_state, rect, rect, SkColors::kGreen, false);
AggregatedRenderPassList pass_list;
pass_list.push_back(std::move(pass));
EXPECT_TRUE(this->RunPixelTest(&pass_list,
base::FilePath(FILE_PATH_LITERAL("green.png")),
cc::AlphaDiscardingExactPixelComparator()));
}
// Check that RendererPixelTest can run tests that verify incremental damage.
TEST_P(RendererPixelTest, SimpleDamageRect) {
const gfx::Rect rect(this->device_viewport_size_);
const gfx::Rect damage_rect = gfx::Rect(20, 30, 40, 50);
const SkColor4f background_color = SkColors::kGreen;
const SkColor4f foreground_color = SkColors::kBlue;
std::vector<SkColor> expected_output_colors(rect.width() * rect.height());
for (int y = 0; y < rect.height(); y++) {
for (int x = 0; x < rect.width(); x++) {
expected_output_colors[y * rect.width() + x] =
damage_rect.Contains(x, y) ? foreground_color.toSkColor()
: background_color.toSkColor();
}
}
// Draw two frames with semi-transparent content. Both frames should result in
// the same image.
for (size_t i = 0; i < 2; i++) {
SCOPED_TRACE(base::StringPrintf("Frame %zu", i));
auto pass = CreateTestRootRenderPass(AggregatedRenderPassId{1}, rect);
if (i != 0) {
pass->damage_rect = damage_rect;
}
SharedQuadState* shared_state = CreateTestSharedQuadState(
gfx::Transform(), rect, pass.get(), gfx::MaskFilterInfo());
auto* foreground_quad = pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
foreground_quad->SetNew(shared_state, damage_rect, damage_rect,
foreground_color, false);
// Only add the background in the first frame. If the renderer forces full
// damage for all frames, the second frame will not contain the background
// color from the first frame.
if (i == 0) {
auto* background_quad =
pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
background_quad->SetNew(shared_state, rect, rect, background_color,
false);
}
AggregatedRenderPassList pass_list;
pass_list.push_back(std::move(pass));
EXPECT_TRUE(this->RunPixelTest(&pass_list, &expected_output_colors,
cc::AlphaDiscardingExactPixelComparator()));
}
}
TEST_P(RendererPixelTest, OutputSurfaceClipRect) {
gfx::Rect rect(device_viewport_size_);
auto draw_frame = [&](base::FilePath::StringViewType path, SkColor4f color) {
AggregatedRenderPassId id{1};
auto pass = CreateTestRootRenderPass(id, rect);
SharedQuadState* shared_state = CreateTestSharedQuadState(
gfx::Transform(), rect, pass.get(), gfx::MaskFilterInfo());
auto* color_quad = pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
color_quad->SetNew(shared_state, rect, rect, color, false);
AggregatedRenderPassList pass_list;
pass_list.push_back(std::move(pass));
EXPECT_TRUE(RunPixelTest(&pass_list, base::FilePath(path),
cc::AlphaDiscardingExactPixelComparator()));
};
draw_frame(FILE_PATH_LITERAL("green.png"), SkColors::kGreen);
renderer_->SetOutputSurfaceClipRect(gfx::Rect(150, 150, 50, 50));
draw_frame(FILE_PATH_LITERAL("green_with_blue_corner.png"), SkColors::kBlue);
}
TEST_P(RendererPixelTest, SimpleGreenRectNonRootRenderPass) {
gfx::Rect rect(this->device_viewport_size_);
gfx::Rect small_rect(100, 100);
AggregatedRenderPassId child_id{2};
auto child_pass =
CreateTestRenderPass(child_id, small_rect, gfx::Transform());
SharedQuadState* child_shared_state =
CreateTestSharedQuadState(gfx::Transform(), small_rect, child_pass.get(),
gfx::MaskFilterInfo());
auto* color_quad = child_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
color_quad->SetNew(child_shared_state, rect, rect, SkColors::kGreen, false);
AggregatedRenderPassId root_id{1};
auto root_pass = CreateTestRenderPass(root_id, rect, gfx::Transform());
SharedQuadState* root_shared_state =
CreateTestSharedQuadState(gfx::Transform(), rect, root_pass.get(),
gfx::MaskFilterInfo());
CreateTestRenderPassDrawQuad(root_shared_state, small_rect, child_id,
root_pass.get());
auto* child_pass_ptr = child_pass.get();
AggregatedRenderPassList pass_list;
pass_list.push_back(std::move(child_pass));
pass_list.push_back(std::move(root_pass));
EXPECT_TRUE(this->RunPixelTestWithCopyOutputRequest(
&pass_list, child_pass_ptr,
base::FilePath(FILE_PATH_LITERAL("green_small.png")),
cc::AlphaDiscardingExactPixelComparator()));
}
TEST_P(RendererPixelTest, PremultipliedTextureWithoutBackground) {
gfx::Rect rect(this->device_viewport_size_);
AggregatedRenderPassId id{1};
auto pass = CreateTestRootRenderPass(id, rect);
SharedQuadState* shared_state = CreateTestSharedQuadState(
gfx::Transform(), rect, pass.get(), gfx::MaskFilterInfo());
CreateTestTextureDrawQuad(!is_software_renderer(),
gfx::Rect(this->device_viewport_size_),
{0.0f, 1.0f, 0.0f, 0.5f}, // Texel color.
SkColors::kTransparent, // Background color.
true, // Premultiplied alpha.
shared_state, this->resource_provider_.get(),
this->child_resource_provider_.get(),
this->child_context_provider_, pass.get());
auto* color_quad = pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
color_quad->SetNew(shared_state, rect, rect, SkColors::kWhite, false);
AggregatedRenderPassList pass_list;
pass_list.push_back(std::move(pass));
EXPECT_TRUE(this->RunPixelTest(
&pass_list, base::FilePath(FILE_PATH_LITERAL("green_alpha.png")),
cc::AlphaDiscardingFuzzyPixelOffByOneComparator()));
}
TEST_P(RendererPixelTest, PremultipliedTextureWithBackground) {
gfx::Rect rect(this->device_viewport_size_);
AggregatedRenderPassId id{1};
auto pass = CreateTestRootRenderPass(id, rect);
SharedQuadState* texture_quad_state = CreateTestSharedQuadState(
gfx::Transform(), rect, pass.get(), gfx::MaskFilterInfo());
texture_quad_state->opacity = 0.8f;
CreateTestTextureDrawQuad(
!is_software_renderer(), gfx::Rect(this->device_viewport_size_),
SkColor4f::FromColor(SkColorSetARGB(204, 120, 255, 120)), // Texel color.
SkColors::kGreen, // Background color.
true, // Premultiplied alpha.
texture_quad_state, this->resource_provider_.get(),
this->child_resource_provider_.get(), this->child_context_provider_,
pass.get());
SharedQuadState* color_quad_state = CreateTestSharedQuadState(
gfx::Transform(), rect, pass.get(), gfx::MaskFilterInfo());
auto* color_quad = pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
color_quad->SetNew(color_quad_state, rect, rect, SkColors::kWhite, false);
AggregatedRenderPassList pass_list;
pass_list.push_back(std::move(pass));
EXPECT_TRUE(this->RunPixelTest(
&pass_list, base::FilePath(FILE_PATH_LITERAL("green_alpha.png")),
cc::AlphaDiscardingFuzzyPixelOffByOneComparator()));
}
TEST_P(RendererPixelTest, TextureDrawQuadVisibleRectInsetTopLeft) {
gfx::Rect rect(this->device_viewport_size_);
AggregatedRenderPassId id{1};
auto pass = CreateTestRootRenderPass(id, rect);
SharedQuadState* texture_quad_state = CreateTestSharedQuadState(
gfx::Transform(), rect, pass.get(), gfx::MaskFilterInfo());
CreateTestTwoColoredTextureDrawQuad(
!is_software_renderer(), gfx::Rect(this->device_viewport_size_),
SkColor4f::FromColor(SkColorSetARGB(0, 120, 255, 255)), // Texel color 1.
SkColor4f::FromColor(SkColorSetARGB(204, 120, 0, 255)), // Texel color 2.
SkColors::kGreen, // Background color.
true, // Premultiplied alpha.
false, // flipped_texture_quad.
false, // Half and half.
texture_quad_state, this->resource_provider_.get(),
this->child_resource_provider_.get(), this->child_context_provider_,
pass.get());
pass->quad_list.front()->visible_rect.Inset(gfx::Insets::TLBR(50, 30, 0, 0));
SharedQuadState* color_quad_state = CreateTestSharedQuadState(
gfx::Transform(), rect, pass.get(), gfx::MaskFilterInfo());
auto* color_quad = pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
color_quad->SetNew(color_quad_state, rect, rect, SkColors::kWhite, false);
AggregatedRenderPassList pass_list;
pass_list.push_back(std::move(pass));
EXPECT_TRUE(this->RunPixelTest(
&pass_list, base::FilePath(FILE_PATH_LITERAL("inset_top_left.png")),
cc::AlphaDiscardingFuzzyPixelOffByOneComparator()));
}
// This tests drawing a TextureDrawQuad with a visible_rect strictly included in
// rect, custom UVs, and rect.origin() that is not in the origin.
TEST_P(RendererPixelTest,
TextureDrawQuadTranslatedAndVisibleRectInsetTopLeftAndCustomUV) {
gfx::Rect rect(this->device_viewport_size_);
AggregatedRenderPassId id{1};
auto pass = CreateTestRootRenderPass(id, rect);
SharedQuadState* texture_quad_state = CreateTestSharedQuadState(
gfx::Transform(), rect, pass.get(), gfx::MaskFilterInfo());
CreateTestTwoColoredTextureDrawQuad(
!is_software_renderer(), gfx::Rect(this->device_viewport_size_),
SkColor4f::FromColor(SkColorSetARGB(0, 120, 255, 255)), // Texel color 1.
SkColor4f::FromColor(SkColorSetARGB(204, 120, 0, 255)), // Texel color 2.
SkColors::kGreen, // Background color.
true, // Premultiplied alpha.
false, // flipped_texture_quad.
false, // Half and half.
texture_quad_state, this->resource_provider_.get(),
this->child_resource_provider_.get(), this->child_context_provider_,
pass.get());
auto* quad = static_cast<TextureDrawQuad*>(pass->quad_list.front());
quad->rect.Offset(10, 10);
quad->visible_rect.Offset(10, 10);
quad->visible_rect.Inset(gfx::Insets::TLBR(50, 30, 12, 12));
quad->SetNormalizedTexCoordsForTesting(
gfx::BoundingRect(gfx::PointF(0.2f, 0.3f), gfx::PointF(0.4f, 0.7f)),
this->device_viewport_size_);
quad->nearest_neighbor = true; // To avoid bilinear filter differences.
SharedQuadState* color_quad_state = CreateTestSharedQuadState(
gfx::Transform(), rect, pass.get(), gfx::MaskFilterInfo());
auto* color_quad = pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
color_quad->SetNew(color_quad_state, rect, rect, SkColors::kWhite, false);
AggregatedRenderPassList pass_list;
pass_list.push_back(std::move(pass));
EXPECT_TRUE(this->RunPixelTest(
&pass_list,
base::FilePath(FILE_PATH_LITERAL("offset_inset_top_left.png")),
cc::AlphaDiscardingFuzzyPixelOffByOneComparator()));
}
TEST_P(RendererPixelTest, BypassableTextureQuad_ClipRect) {
gfx::Rect root_pass_rect(device_viewport_size_);
gfx::Rect child_pass_rect(180, 180);
AggregatedRenderPassId root_pass_id{1};
AggregatedRenderPassId child_pass_id{2};
gfx::Transform transform_root_to_child_pass;
transform_root_to_child_pass.Translate(10, 10);
AggregatedRenderPassList pass_list;
{
// The child render pass has a single TextureDrawQuad so it will be bypassed
// by SkiaRenderer. There is a clip_rect that clips the rightmost 10 pixels
// in the x-axis only.
auto child_pass = std::make_unique<AggregatedRenderPass>();
child_pass->SetNew(child_pass_id, child_pass_rect, child_pass_rect,
transform_root_to_child_pass.GetCheckedInverse());
auto* sqs =
CreateTestSharedQuadState(gfx::Transform(), child_pass_rect,
child_pass.get(), gfx::MaskFilterInfo());
sqs->clip_rect = gfx::Rect(170, 200);
CreateTestTwoColoredTextureDrawQuad(
!is_software_renderer(), child_pass_rect,
/*texel_color_one=*/SkColors::kYellow,
/*texel_color_two=*/SkColors::kMagenta,
/*background_color=*/SkColors::kGreen,
/*premultiplied_alpha=*/true,
/*flipped_texture_quad=*/false,
/*half_and_half=*/false, sqs, resource_provider_.get(),
child_resource_provider_.get(), child_context_provider_,
child_pass.get());
pass_list.push_back(std::move(child_pass));
}
{
// The root render pass has a blue background and draws the (bypassed)
// render pass into center 180x180 of the root render pass.
auto root_pass = CreateTestRootRenderPass(root_pass_id, root_pass_rect);
{
auto* sqs = CreateTestSharedQuadState(transform_root_to_child_pass,
child_pass_rect, root_pass.get(),
gfx::MaskFilterInfo());
auto* pass_quad =
root_pass->CreateAndAppendDrawQuad<AggregatedRenderPassDrawQuad>();
pass_quad->SetNew(sqs, child_pass_rect, child_pass_rect, child_pass_id,
kInvalidResourceId, gfx::RectF(), gfx::Size(),
gfx::RectF(child_pass_rect), false);
}
{
auto* sqs =
CreateTestSharedQuadState(gfx::Transform(), root_pass_rect,
root_pass.get(), gfx::MaskFilterInfo());
auto* blue_quad =
root_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
blue_quad->SetNew(sqs, root_pass_rect, root_pass_rect, SkColors::kBlue,
false);
}
pass_list.push_back(std::move(root_pass));
}
EXPECT_TRUE(this->RunPixelTest(
&pass_list, base::FilePath(FILE_PATH_LITERAL("bypass_texture.png")),
cc::ExactPixelComparator()));
}
TEST_P(RendererPixelTest, BypassableTextureQuad_Rotation_ClipRect) {
gfx::Rect root_pass_rect(device_viewport_size_);
gfx::Rect child_pass_rect(120, 120);
AggregatedRenderPassId root_pass_id{1};
AggregatedRenderPassId child_pass_id{2};
gfx::Transform transform_root_to_child_pass;
transform_root_to_child_pass.RotateAboutZAxis(45.0);
transform_root_to_child_pass.Translate(-child_pass_rect.width() / 2,
-child_pass_rect.height() / 2);
transform_root_to_child_pass.PostTranslate(root_pass_rect.width() / 2,
root_pass_rect.height() / 2);
AggregatedRenderPassList pass_list;
{
// The child render pass has a single TextureDrawQuad so it will be bypassed
// by SkiaRenderer. The texture is drawn rotated 45° so all four corners are
// clipped by the render pass output_rect. There is a clip_rect that clips
// the rightmost 10 pixels in the x-axis only.
auto child_pass = std::make_unique<AggregatedRenderPass>();
child_pass->SetNew(child_pass_id, child_pass_rect, child_pass_rect,
transform_root_to_child_pass.GetCheckedInverse());
gfx::Transform transform_texture_quad;
{
int half_length = child_pass_rect.width() / 2;
transform_texture_quad.RotateAboutZAxis(45.0);
transform_texture_quad.Translate(-half_length, -half_length);
transform_texture_quad.PostTranslate(half_length, half_length);
}
auto* sqs =
CreateTestSharedQuadState(transform_texture_quad, child_pass_rect,
child_pass.get(), gfx::MaskFilterInfo());
sqs->clip_rect = gfx::Rect(110, 140);
CreateTestTwoColoredTextureDrawQuad(
!is_software_renderer(), child_pass_rect,
/*texel_color_one=*/SkColors::kYellow,
/*texel_color_two=*/SkColors::kMagenta,
/*background_color=*/SkColors::kGreen,
/*premultiplied_alpha=*/true,
/*flipped_texture_quad=*/false,
/*half_and_half=*/false, sqs, resource_provider_.get(),
child_resource_provider_.get(), child_context_provider_,
child_pass.get());
pass_list.push_back(std::move(child_pass));
}
{
// The root render pass has a blue background and draws the (bypassed)
// render pass rotated another 45°.
auto root_pass = CreateTestRootRenderPass(root_pass_id, root_pass_rect);
{
auto* sqs = CreateTestSharedQuadState(transform_root_to_child_pass,
child_pass_rect, root_pass.get(),
gfx::MaskFilterInfo());
auto* pass_quad =
root_pass->CreateAndAppendDrawQuad<AggregatedRenderPassDrawQuad>();
pass_quad->SetNew(sqs, child_pass_rect, child_pass_rect, child_pass_id,
kInvalidResourceId, gfx::RectF(), gfx::Size(),
gfx::RectF(child_pass_rect), false);
}
{
auto* sqs =
CreateTestSharedQuadState(gfx::Transform(), root_pass_rect,
root_pass.get(), gfx::MaskFilterInfo());
auto* blue_quad =
root_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
blue_quad->SetNew(sqs, root_pass_rect, root_pass_rect, SkColors::kBlue,
false);
}
pass_list.push_back(std::move(root_pass));
}
base::FilePath expected_result =
base::FilePath(FILE_PATH_LITERAL("bypass_texture_rotated.png"));
if (is_skia_graphite()) {
expected_result = expected_result.InsertBeforeExtensionASCII(kGraphiteStr);
}
EXPECT_TRUE(this->RunPixelTest(&pass_list, expected_result,
cc::FuzzyPixelComparator()
.SetErrorPixelsPercentageLimit(3.5f)
.SetAbsErrorLimit(127)
.SetAvgAbsErrorLimit(40)));
}
TEST_P(RendererPixelTest, BypassableRenderPassQuad) {
AggregatedRenderPassId root_pass_id{1};
AggregatedRenderPassId child_pass_id{2};
AggregatedRenderPassId grand_child_pass_id{3};
gfx::Rect root_pass_rect(device_viewport_size_);
gfx::Rect child_pass_rect(180, 180);
gfx::Rect grand_child_pass_rect(320, 320);
gfx::Transform transform_root_to_child_pass;
transform_root_to_child_pass.Translate(10, 10);
gfx::Transform transform_child_to_grand_child_pass;
transform_child_to_grand_child_pass.Translate(15, 15);
transform_child_to_grand_child_pass.Scale(0.5f, 0.5f);
AggregatedRenderPassList pass_list;
{
// This render pass has two quads so it can't be bypassed. The quads are
// bigger than the render pass so they are clipped by the render pass
// output_rect.
gfx::Transform transform_root_to_grand_child_pass =
transform_root_to_child_pass * transform_child_to_grand_child_pass;
auto grand_child_pass = std::make_unique<AggregatedRenderPass>();
grand_child_pass->SetNew(
grand_child_pass_id, grand_child_pass_rect, grand_child_pass_rect,
transform_root_to_grand_child_pass.GetCheckedInverse());
gfx::Rect quad_rect(360, 360);
auto* sqs = CreateTestSharedQuadState(gfx::Transform(), quad_rect,
grand_child_pass.get(),
gfx::MaskFilterInfo());
gfx::Rect magenta_rect(quad_rect.width() / 4, quad_rect.height() / 4,
quad_rect.width() / 2, quad_rect.height() / 2);
auto* magenta_quad =
grand_child_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
magenta_quad->SetNew(sqs, magenta_rect, magenta_rect, SkColors::kMagenta,
false);
auto* yellow_quad =
grand_child_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
yellow_quad->SetNew(sqs, quad_rect, quad_rect, SkColors::kYellow, false);
pass_list.push_back(std::move(grand_child_pass));
}
{
// This render pass can be bypassed by SkiaRenderer. There is a clip_rect
// that clips the rightmost 20 pixels in the x-axis only.
auto child_pass = std::make_unique<AggregatedRenderPass>();
child_pass->SetNew(child_pass_id, child_pass_rect, child_pass_rect,
transform_root_to_child_pass.GetCheckedInverse());
auto* sqs = CreateTestSharedQuadState(
transform_child_to_grand_child_pass, grand_child_pass_rect,
child_pass.get(), gfx::MaskFilterInfo());
sqs->clip_rect = gfx::Rect(160, 200);
auto* pass_quad =
child_pass->CreateAndAppendDrawQuad<AggregatedRenderPassDrawQuad>();
pass_quad->SetNew(sqs, grand_child_pass_rect, grand_child_pass_rect,
grand_child_pass_id, kInvalidResourceId, gfx::RectF(),
gfx::Size(), gfx::RectF(grand_child_pass_rect), false);
pass_list.push_back(std::move(child_pass));
}
{
// The root render pass has a blue background and draws the (bypassed)
// render pass into center 180x180 of the root render pass.
auto root_pass = CreateTestRootRenderPass(root_pass_id, root_pass_rect);
{
auto* sqs = CreateTestSharedQuadState(transform_root_to_child_pass,
child_pass_rect, root_pass.get(),
gfx::MaskFilterInfo());
auto* pass_quad =
root_pass->CreateAndAppendDrawQuad<AggregatedRenderPassDrawQuad>();
pass_quad->SetNew(sqs, child_pass_rect, child_pass_rect, child_pass_id,
kInvalidResourceId, gfx::RectF(), gfx::Size(),
gfx::RectF(child_pass_rect), false);
}
{
auto* sqs =
CreateTestSharedQuadState(gfx::Transform(), root_pass_rect,
root_pass.get(), gfx::MaskFilterInfo());
auto* blue_quad =
root_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
blue_quad->SetNew(sqs, root_pass_rect, root_pass_rect, SkColors::kBlue,
false);
}
pass_list.push_back(std::move(root_pass));
}
EXPECT_TRUE(this->RunPixelTest(
&pass_list, base::FilePath(FILE_PATH_LITERAL("bypass_render_pass.png")),
cc::ExactPixelComparator()));
}
TEST_P(RendererPixelTest, BypassableRenderPassQuad_DoubleBypass) {
AggregatedRenderPassId root_pass_id{1};
AggregatedRenderPassId child_pass_id{2};
AggregatedRenderPassId grand_child_pass_id{3};
gfx::Rect root_pass_rect(device_viewport_size_);
gfx::Rect child_pass_rect(180, 180);
gfx::Rect grand_child_pass_rect(160, 160);
gfx::Transform transform_root_to_child_pass;
transform_root_to_child_pass.Translate(10, 10);
gfx::Transform transform_child_to_grand_child_pass;
transform_child_to_grand_child_pass.Translate(15, 15);
AggregatedRenderPassList pass_list;
{
// This render pass contains a single TextureDrawQuad so SkiaRenderer can
// bypass it. The quad is bigger than the render pass so it is clipped by
// render pass output_rect.
gfx::Transform transform_root_to_grand_child_pass =
transform_root_to_child_pass * transform_child_to_grand_child_pass;
auto grand_child_pass = std::make_unique<AggregatedRenderPass>();
grand_child_pass->SetNew(
grand_child_pass_id, grand_child_pass_rect, grand_child_pass_rect,
transform_root_to_grand_child_pass.GetCheckedInverse());
auto* sqs = CreateTestSharedQuadState(gfx::Transform(), child_pass_rect,
grand_child_pass.get(),
gfx::MaskFilterInfo());
CreateTestTwoColoredTextureDrawQuad(
!is_software_renderer(), child_pass_rect,
/*texel_color_one=*/SkColors::kYellow,
/*texel_color_two=*/SkColors::kMagenta,
/*background_color=*/SkColors::kGreen,
/*premultiplied_alpha=*/true,
/*flipped_texture_quad=*/false,
/*half_and_half=*/false, sqs, resource_provider_.get(),
child_resource_provider_.get(), child_context_provider_,
grand_child_pass.get());
pass_list.push_back(std::move(grand_child_pass));
}
{
// This render pass contains a single RenderPassDrawQuad so SkiaRenderer can
// also bypass it. There is a clip_rect that clips the rightmost 20 pixels
// in the x-axis only.
auto child_pass = std::make_unique<AggregatedRenderPass>();
child_pass->SetNew(child_pass_id, child_pass_rect, child_pass_rect,
transform_root_to_child_pass.GetCheckedInverse());
auto* sqs = CreateTestSharedQuadState(
transform_child_to_grand_child_pass, grand_child_pass_rect,
child_pass.get(), gfx::MaskFilterInfo());
sqs->clip_rect = gfx::Rect(160, 200);
auto* pass_quad =
child_pass->CreateAndAppendDrawQuad<AggregatedRenderPassDrawQuad>();
pass_quad->SetNew(sqs, grand_child_pass_rect, grand_child_pass_rect,
grand_child_pass_id, kInvalidResourceId, gfx::RectF(),
gfx::Size(), gfx::RectF(grand_child_pass_rect), false);
pass_list.push_back(std::move(child_pass));
}
{
// The root render pass has a blue background and draws the (bypassed)
// render pass into center 180x180 of the root render pass.
auto root_pass = CreateTestRootRenderPass(root_pass_id, root_pass_rect);
{
auto* sqs = CreateTestSharedQuadState(transform_root_to_child_pass,
child_pass_rect, root_pass.get(),
gfx::MaskFilterInfo());
auto* pass_quad =
root_pass->CreateAndAppendDrawQuad<AggregatedRenderPassDrawQuad>();
pass_quad->SetNew(sqs, child_pass_rect, child_pass_rect, child_pass_id,
kInvalidResourceId, gfx::RectF(), gfx::Size(),
gfx::RectF(child_pass_rect), false);
}
{
auto* sqs =
CreateTestSharedQuadState(gfx::Transform(), root_pass_rect,
root_pass.get(), gfx::MaskFilterInfo());
auto* blue_quad =
root_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
blue_quad->SetNew(sqs, root_pass_rect, root_pass_rect, SkColors::kBlue,
false);
}
pass_list.push_back(std::move(root_pass));
}
EXPECT_TRUE(this->RunPixelTest(
&pass_list, base::FilePath(FILE_PATH_LITERAL("bypass_render_pass.png")),
cc::ExactPixelComparator()));
}
TEST_P(RendererPixelTest, BypassableRenderPassQuad_DoubleBypass_ScaledClip) {
AggregatedRenderPassId root_pass_id{1};
AggregatedRenderPassId child_pass_id{2};
AggregatedRenderPassId grand_child_pass_id{3};
gfx::Rect root_pass_rect(device_viewport_size_);
gfx::Rect child_pass_rect(180, 180);
gfx::Rect grand_child_pass_rect(360, 360);
gfx::Transform transform_root_to_child_pass;
transform_root_to_child_pass.Translate(10, 10);
gfx::Transform transform_child_to_grand_child_pass;
transform_child_to_grand_child_pass.Translate(15, 15);
transform_child_to_grand_child_pass.Scale(0.5f, 0.5f);
AggregatedRenderPassList pass_list;
{
// This render pass contains a single TextureDrawQuad so SkiaRenderer can
// bypass it. The quad has a clip_rect which clips 40px on the right and
// bottom.
gfx::Transform transform_root_to_grand_child_pass =
transform_root_to_child_pass * transform_child_to_grand_child_pass;
auto grand_child_pass = std::make_unique<AggregatedRenderPass>();
grand_child_pass->SetNew(
grand_child_pass_id, grand_child_pass_rect, grand_child_pass_rect,
transform_root_to_grand_child_pass.GetCheckedInverse());
auto* sqs = CreateTestSharedQuadState(
gfx::Transform(), grand_child_pass_rect, grand_child_pass.get(),
gfx::MaskFilterInfo());
sqs->clip_rect = gfx::Rect(320, 320);
CreateTestTwoColoredTextureDrawQuad(
!is_software_renderer(), grand_child_pass_rect,
/*texel_color_one=*/SkColors::kYellow,
/*texel_color_two=*/SkColors::kMagenta,
/*background_color=*/SkColors::kGreen,
/*premultiplied_alpha=*/true,
/*flipped_texture_quad=*/false,
/*half_and_half=*/false, sqs, resource_provider_.get(),
child_resource_provider_.get(), child_context_provider_,
grand_child_pass.get());
pass_list.push_back(std::move(grand_child_pass));
}
{
// This render pass contains a single RenderPassDrawQuad so SkiaRenderer can
// also bypass it. There is a clip_rect that clips the rightmost 20 pixels
// in the x-axis only.
auto child_pass = std::make_unique<AggregatedRenderPass>();
child_pass->SetNew(child_pass_id, child_pass_rect, child_pass_rect,
transform_root_to_child_pass.GetCheckedInverse());
auto* sqs = CreateTestSharedQuadState(
transform_child_to_grand_child_pass, grand_child_pass_rect,
child_pass.get(), gfx::MaskFilterInfo());
sqs->clip_rect = gfx::Rect(160, 200);
auto* pass_quad =
child_pass->CreateAndAppendDrawQuad<AggregatedRenderPassDrawQuad>();
pass_quad->SetNew(sqs, grand_child_pass_rect, grand_child_pass_rect,
grand_child_pass_id, kInvalidResourceId, gfx::RectF(),
gfx::Size(), gfx::RectF(grand_child_pass_rect), false);
pass_list.push_back(std::move(child_pass));
}
{
// The root render pass has a blue background and draws the (bypassed)
// render pass into center 180x180 of the root render pass.
auto root_pass = CreateTestRootRenderPass(root_pass_id, root_pass_rect);
{
auto* sqs = CreateTestSharedQuadState(transform_root_to_child_pass,
child_pass_rect, root_pass.get(),
gfx::MaskFilterInfo());
auto* pass_quad =
root_pass->CreateAndAppendDrawQuad<AggregatedRenderPassDrawQuad>();
pass_quad->SetNew(sqs, child_pass_rect, child_pass_rect, child_pass_id,
kInvalidResourceId, gfx::RectF(), gfx::Size(),
gfx::RectF(child_pass_rect), false);
}
{
auto* sqs =
CreateTestSharedQuadState(gfx::Transform(), root_pass_rect,
root_pass.get(), gfx::MaskFilterInfo());
auto* blue_quad =
root_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
blue_quad->SetNew(sqs, root_pass_rect, root_pass_rect, SkColors::kBlue,
false);
}
pass_list.push_back(std::move(root_pass));
}
EXPECT_TRUE(this->RunPixelTest(
&pass_list, base::FilePath(FILE_PATH_LITERAL("bypass_render_pass.png")),
cc::ExactPixelComparator()));
}
TEST_P(RendererPixelTest, BypassableRenderPassQuad_BackdropFilter_Extents) {
// This tests that a bypassable render pass with a backdrop filter applies
// the backdrop filter to the RPDQ's entire visible_rect, even if the
// child of the render pass has smaller content that is being drawn directly
// because of the bypass.
gfx::Rect root_pass_rect(device_viewport_size_);
gfx::Rect backdrop_pass_rect(device_viewport_size_.width() - 20,
device_viewport_size_.height() - 20);
gfx::Rect child_content_rect(90, 90);
AggregatedRenderPassId root_pass_id{1};
AggregatedRenderPassId backdrop_pass_id{2};
gfx::Transform transform_root_to_backdrop_pass;
transform_root_to_backdrop_pass.Translate(10, 10);
AggregatedRenderPassList pass_list;
{
// The child render pass has a single TextureDrawQuad so it will be bypassed
// by SkiaRenderer. The TextureDrawQuad is smaller than the visible rect
// of the child render pass, which has a backdrop filter that should cover
// the root pass up to a 10px inset.
auto backdrop_pass = std::make_unique<AggregatedRenderPass>();
backdrop_pass->SetNew(backdrop_pass_id, backdrop_pass_rect,
backdrop_pass_rect,
transform_root_to_backdrop_pass.GetCheckedInverse());
backdrop_pass->backdrop_filters.Append(
cc::FilterOperation::CreateBlurFilter(8.f, SkTileMode::kMirror));
gfx::Transform transform_child_to_backdrop_pass;
transform_child_to_backdrop_pass.Translate(
backdrop_pass_rect.CenterPoint().x() -
0.5f * child_content_rect.width(),
backdrop_pass_rect.CenterPoint().y() -
0.5f * child_content_rect.height());
auto* sqs = CreateTestSharedQuadState(
transform_child_to_backdrop_pass, child_content_rect,
backdrop_pass.get(), gfx::MaskFilterInfo());
sqs->clip_rect = cc::MathUtil::MapEnclosingClippedRect(
transform_child_to_backdrop_pass, child_content_rect);
// NOTE: From https://g-issues.chromium.org/issues/355981041, the backdrop
// filter of a bypassed render pass was being restricted to the visible rect
// of the child. Use kTransparent for the outer color and background color
// to allow backdrop filtered content to be visible under part of this
// texture quad to highlight that the filter is being processed, but was
// incorrectly clipped during bypassing.
CreateTestTwoColoredTextureDrawQuad(
!is_software_renderer(), child_content_rect,
/*texel_color_one=*/SkColors::kTransparent,
/*texel_color_two=*/SkColors::kMagenta,
/*background_color=*/SkColors::kTransparent,
/*premultiplied_alpha=*/true,
/*flipped_texture_quad=*/false,
/*half_and_half=*/false, sqs, resource_provider_.get(),
child_resource_provider_.get(), child_context_provider_,
backdrop_pass.get());
pass_list.push_back(std::move(backdrop_pass));
}
{
// The root render pass has a blue and yellow checkerboard background and
// draws the (bypassed) render pass inset in the root by 10px.
auto root_pass = CreateTestRootRenderPass(root_pass_id, root_pass_rect);
{
auto* sqs = CreateTestSharedQuadState(transform_root_to_backdrop_pass,
backdrop_pass_rect, root_pass.get(),
gfx::MaskFilterInfo());
auto* pass_quad =
root_pass->CreateAndAppendDrawQuad<AggregatedRenderPassDrawQuad>();
pass_quad->SetNew(sqs, backdrop_pass_rect, backdrop_pass_rect,
backdrop_pass_id, kInvalidResourceId, gfx::RectF(),
gfx::Size(), gfx::RectF(backdrop_pass_rect), false);
}
{
auto* sqs =
CreateTestSharedQuadState(gfx::Transform(), root_pass_rect,
root_pass.get(), gfx::MaskFilterInfo());
static constexpr int checker_size = 16;
for (int y = root_pass_rect.y(); y < root_pass_rect.bottom();
y += checker_size) {
for (int x = root_pass_rect.x(); x < root_pass_rect.right();
x += checker_size) {
gfx::Rect box{x, y, checker_size, checker_size};
bool firstColor =
((x / checker_size) + ((y / checker_size) % 2)) % 2 == 0;
auto* quad = root_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
quad->SetNew(sqs, box, box,
firstColor ? SkColors::kBlue : SkColors::kYellow, false);
}
}
}
pass_list.push_back(std::move(root_pass));
}
// Use a fairly fuzz comparison to allow for deviations in how the renderer
// types implement the actual blur. In particular, the SW renderer does not
// support the mirror tile mode so its blurs deviate more from GPU renderers.
const bool blur_fully_supported = !is_software_renderer();
auto comparator =
cc::FuzzyPixelComparator()
.SetErrorPixelsPercentageLimit(blur_fully_supported ? 0.2f : 53.f)
.SetAvgAbsErrorLimit(blur_fully_supported ? 1 : 2)
.SetAbsErrorLimit(blur_fully_supported ? 1 : 8);
EXPECT_TRUE(this->RunPixelTest(
&pass_list,
base::FilePath(FILE_PATH_LITERAL("bypass_texture_backdrop_extent.png")),
comparator));
}
TEST_P(RendererPixelTest, TextureDrawQuadVisibleRectInsetBottomRight) {
#if BUILDFLAG(IS_LINUX) && defined(THREAD_SANITIZER)
// Test is flaking with failed large allocations under TSAN when using
// SkiaRenderer with GL backend. See https://crbug.com/1320955.
if (renderer_type() == RendererType::kSkiaGL)
return;
#endif
gfx::Rect rect(this->device_viewport_size_);
AggregatedRenderPassId id{1};
auto pass = CreateTestRootRenderPass(id, rect);
SharedQuadState* texture_quad_state = CreateTestSharedQuadState(
gfx::Transform(), rect, pass.get(), gfx::MaskFilterInfo());
CreateTestTwoColoredTextureDrawQuad(
!is_software_renderer(), gfx::Rect(this->device_viewport_size_),
SkColor4f::FromColor(SkColorSetARGB(0, 120, 255, 255)), // Texel color 1.
SkColor4f::FromColor(SkColorSetARGB(204, 120, 0, 255)), // Texel color 2.
SkColors::kGreen, // Background color.
true, // Premultiplied alpha.
false, // flipped_texture_quad.
false, // Half and half.
texture_quad_state, this->resource_provider_.get(),
this->child_resource_provider_.get(), this->child_context_provider_,
pass.get());
pass->quad_list.front()->visible_rect.Inset(gfx::Insets::TLBR(0, 0, 60, 40));
SharedQuadState* color_quad_state = CreateTestSharedQuadState(
gfx::Transform(), rect, pass.get(), gfx::MaskFilterInfo());
auto* color_quad = pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
color_quad->SetNew(color_quad_state, rect, rect, SkColors::kWhite, false);
AggregatedRenderPassList pass_list;
pass_list.push_back(std::move(pass));
EXPECT_TRUE(this->RunPixelTest(
&pass_list, base::FilePath(FILE_PATH_LITERAL("inset_bottom_right.png")),
cc::AlphaDiscardingFuzzyPixelOffByOneComparator()));
}
TEST_P(GPURendererPixelTest, SolidColorBlend) {
gfx::Rect rect(this->device_viewport_size_);
AggregatedRenderPassId id{1};
auto pass = CreateTestRootRenderPass(id, rect);
pass->has_transparent_background = false;
SharedQuadState* shared_state = CreateTestSharedQuadState(
gfx::Transform(), rect, pass.get(), gfx::MaskFilterInfo());
shared_state->opacity = 1 - 16.0f / 255;
shared_state->blend_mode = SkBlendMode::kDstOut;
auto* color_quad = pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
color_quad->SetNew(shared_state, rect, rect, SkColors::kRed, false);
SharedQuadState* shared_state_background = CreateTestSharedQuadState(
gfx::Transform(), rect, pass.get(), gfx::MaskFilterInfo());
SkColor4f background_color =
SkColor4f::FromColor(SkColorSetRGB(0xff, 0xff * 14 / 16, 0xff));
auto* color_quad_background =
pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
color_quad_background->SetNew(shared_state_background, rect, rect,
background_color, false);
// Result should be r=16, g=14, b=16.
AggregatedRenderPassList pass_list;
pass_list.push_back(std::move(pass));
EXPECT_TRUE(this->RunPixelTest(
&pass_list, base::FilePath(FILE_PATH_LITERAL("dark_grey.png")),
cc::AlphaDiscardingFuzzyPixelOffByOneComparator()));
}
TEST_P(GPURendererPixelTest, SolidColorWithTemperature) {
gfx::Rect rect(this->device_viewport_size_);
AggregatedRenderPassId id{1};
auto pass = CreateTestRootRenderPass(id, rect);
SharedQuadState* shared_state = CreateTestSharedQuadState(
gfx::Transform(), rect, pass.get(), gfx::MaskFilterInfo());
auto* color_quad = pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
color_quad->SetNew(shared_state, rect, rect, SkColors::kYellow, false);
AggregatedRenderPassList pass_list;
pass_list.push_back(std::move(pass));
SkM44 color_matrix;
color_matrix.setRC(0, 0, 0.7f);
color_matrix.setRC(1, 1, 0.4f);
color_matrix.setRC(2, 2, 0.5f);
this->output_surface_->set_color_matrix(color_matrix);
EXPECT_TRUE(this->RunPixelTest(
&pass_list, base::FilePath(FILE_PATH_LITERAL("temperature_brown.png")),
cc::AlphaDiscardingFuzzyPixelOffByOneComparator()));
}
TEST_P(GPURendererPixelTest, SolidColorWithTemperatureNonRootRenderPass) {
// Create a root and a child passes with two different solid color quads.
AggregatedRenderPassList render_passes_in_draw_order;
gfx::Rect viewport_rect(this->device_viewport_size_);
gfx::Rect root_rect(0, 0, viewport_rect.width(), viewport_rect.height() / 2);
gfx::Rect child_rect(0, root_rect.bottom(), viewport_rect.width(),
root_rect.height());
// Child pass.
AggregatedRenderPassId child_pass_id{2};
AggregatedRenderPass* child_pass = cc::AddRenderPass(
&render_passes_in_draw_order, child_pass_id, viewport_rect,
gfx::Transform(), cc::FilterOperations());
cc::AddQuad(child_pass, child_rect, SkColors::kGreen);
// Root pass.
AggregatedRenderPassId root_pass_id{1};
AggregatedRenderPass* root_pass = cc::AddRenderPass(
&render_passes_in_draw_order, root_pass_id, viewport_rect,
gfx::Transform(), cc::FilterOperations());
cc::AddQuad(root_pass, root_rect, SkColors::kYellow);
SharedQuadState* pass_shared_state =
CreateTestSharedQuadState(gfx::Transform(), viewport_rect, root_pass,
gfx::MaskFilterInfo());
CreateTestRenderPassDrawQuad(pass_shared_state, viewport_rect, child_pass_id,
root_pass);
// Set a non-identity output color matrix on the output surface, and expect
// that the colors will be transformed.
SkM44 color_matrix;
color_matrix.setRC(0, 0, 0.7f);
color_matrix.setRC(1, 1, 0.4f);
color_matrix.setRC(2, 2, 0.5f);
this->output_surface_->set_color_matrix(color_matrix);
EXPECT_TRUE(this->RunPixelTest(
&render_passes_in_draw_order,
base::FilePath(FILE_PATH_LITERAL("temperature_brown_non_root.png")),
cc::AlphaDiscardingFuzzyPixelOffByOneComparator()));
}
// Check that the renderer draws a fallback quad for quads that require overlay.
TEST_P(GPURendererPixelTest, OverlayHintRequiredFallback) {
gfx::Rect rect(this->device_viewport_size_);
AggregatedRenderPassId id{1};
auto pass = CreateTestRootRenderPass(id, rect);
SharedQuadState* texture_quad_state = CreateTestSharedQuadState(
gfx::Transform(), rect, pass.get(), gfx::MaskFilterInfo());
// Add a texture quad with the overlay priority of "required". Most properties
// shouldn't matter since the renderer shouldn't attempt to draw this quad.
TextureDrawQuad* quad = pass->CreateAndAppendDrawQuad<TextureDrawQuad>();
quad->SetNew(texture_quad_state, gfx::Rect(this->device_viewport_size_),
gfx::Rect(this->device_viewport_size_), false, ResourceId{1},
gfx::PointF(), gfx::PointF(), SkColors::kTransparent, false,
false, gfx::ProtectedVideoType::kClear);
quad->overlay_priority_hint = OverlayPriority::kRequired;
// Add a background that's not the expected fallback color.
SharedQuadState* color_quad_state = CreateTestSharedQuadState(
gfx::Transform(), rect, pass.get(), gfx::MaskFilterInfo());
auto* color_quad = pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
color_quad->SetNew(color_quad_state, rect, rect, SkColors::kWhite, false);
AggregatedRenderPassList pass_list;
pass_list.push_back(std::move(pass));
#if DCHECK_IS_ON()
EXPECT_TRUE(this->RunPixelTest(
&pass_list, base::FilePath(FILE_PATH_LITERAL("magenta.png")),
cc::AlphaDiscardingFuzzyPixelOffByOneComparator()));
#else
EXPECT_TRUE(this->RunPixelTest(
&pass_list, base::FilePath(FILE_PATH_LITERAL("black.png")),
cc::AlphaDiscardingFuzzyPixelOffByOneComparator()));
#endif
}
// Check that the renderer draws a fallback quad for quads that require overlay,
// but are processed by the RPDQ bypass case.
TEST_P(GPURendererPixelTest, OverlayHintRequiredFallbackRPDQBypassCase) {
gfx::Rect rect(this->device_viewport_size_);
AggregatedRenderPassList pass_list;
// Inner pass with just a video quad. This is intended to trigger the RPDQ
// bypass case in DirectRenderer.
AggregatedRenderPassId inner_id{2};
{
auto pass = CreateTestRenderPass(inner_id, rect, gfx::Transform());
SharedQuadState* sqs = CreateTestSharedQuadState(
gfx::Transform(), rect, pass.get(), gfx::MaskFilterInfo());
// Add a texture quad with the overlay priority of "required". Most
// properties shouldn't matter since the renderer shouldn't attempt to draw
// this quad.
TextureDrawQuad* quad = pass->CreateAndAppendDrawQuad<TextureDrawQuad>();
quad->SetNew(sqs, gfx::Rect(this->device_viewport_size_),
gfx::Rect(this->device_viewport_size_), false, ResourceId{1},
gfx::PointF(), gfx::PointF(), SkColors::kTransparent, false,
false, gfx::ProtectedVideoType::kClear);
quad->overlay_priority_hint = OverlayPriority::kRequired;
pass_list.push_back(std::move(pass));
}
// Root pass with a RPDQ
{
AggregatedRenderPassId id{1};
auto pass = CreateTestRootRenderPass(id, rect);
SharedQuadState* sqs = CreateTestSharedQuadState(
gfx::Transform(), rect, pass.get(), gfx::MaskFilterInfo());
CreateTestRenderPassDrawQuad(sqs, rect, inner_id, pass.get());
// Add a background that's not the expected fallback color.
auto* color_quad = pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
color_quad->SetNew(sqs, rect, rect, SkColors::kWhite, false);
pass_list.push_back(std::move(pass));
}
const size_t num_passes = pass_list.size();
base::HistogramTester histogram;
#if DCHECK_IS_ON()
EXPECT_TRUE(this->RunPixelTest(
&pass_list, base::FilePath(FILE_PATH_LITERAL("magenta.png")),
cc::AlphaDiscardingFuzzyPixelOffByOneComparator()));
#else
EXPECT_TRUE(this->RunPixelTest(
&pass_list, base::FilePath(FILE_PATH_LITERAL("black.png")),
cc::AlphaDiscardingFuzzyPixelOffByOneComparator()));
#endif
// Check that we have two render passes, but one of them hit the RPDQ bypass
// case.
EXPECT_EQ(num_passes, 2u);
histogram.ExpectTotalCount("Compositing.Display.FlattenedRenderPassCount", 1);
}
class IntersectingQuadPixelTest : public VizPixelTestWithParam {
protected:
void SetupQuadStateAndRenderPass() {
// This sets up a pair of draw quads. They are both rotated
// relative to the root plane, they are also rotated relative to each other.
// The intersect in the middle at a non-perpendicular angle so that any
// errors are hopefully magnified.
// The quads should intersect correctly, as in the front quad should only
// be partially in front of the back quad, and partially behind.
viewport_rect_ = gfx::Rect(this->device_viewport_size_);
quad_rect_ = gfx::Rect(0, 0, this->device_viewport_size_.width(),
this->device_viewport_size_.height() / 2.0);
AggregatedRenderPassId id{1};
render_pass_ = CreateTestRootRenderPass(id, viewport_rect_);
// Create the front quad rotated on the Z and Y axis.
gfx::Transform trans;
trans.Translate3d(0, 0, 0.707 * this->device_viewport_size_.width() / 2.0);
trans.RotateAboutZAxis(45.0);
trans.RotateAboutYAxis(45.0);
front_quad_state_ = CreateTestSharedQuadState(
trans, viewport_rect_, render_pass_.get(), gfx::MaskFilterInfo());
// Make sure they end up in a 3d sorting context.
front_quad_state_->sorting_context_id = 1;
// Create the back quad, and rotate on just the y axis. This will intersect
// the first quad partially.
trans = gfx::Transform();
trans.Translate3d(0, 0, -0.707 * this->device_viewport_size_.width() / 2.0);
trans.RotateAboutYAxis(-45.0);
back_quad_state_ =
CreateTestSharedQuadState(trans, viewport_rect_, render_pass_.get(),
gfx::MaskFilterInfo());
back_quad_state_->sorting_context_id = 1;
}
void AppendBackgroundAndRunTest(const cc::PixelComparator& comparator,
const base::FilePath& ref_file) {
SharedQuadState* background_quad_state =
CreateTestSharedQuadState(gfx::Transform(), viewport_rect_,
render_pass_.get(), gfx::MaskFilterInfo());
auto* background_quad =
render_pass_->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
background_quad->SetNew(background_quad_state, viewport_rect_,
viewport_rect_, SkColors::kWhite, false);
pass_list_.push_back(std::move(render_pass_));
EXPECT_TRUE(this->RunPixelTest(&pass_list_, ref_file, comparator));
}
template <typename T>
T* CreateAndAppendDrawQuad() {
return render_pass_->CreateAndAppendDrawQuad<T>();
}
std::unique_ptr<AggregatedRenderPass> render_pass_;
gfx::Rect viewport_rect_;
raw_ptr<SharedQuadState, DanglingUntriaged> front_quad_state_;
raw_ptr<SharedQuadState, DanglingUntriaged> back_quad_state_;
gfx::Rect quad_rect_;
AggregatedRenderPassList pass_list_;
};
INSTANTIATE_TEST_SUITE_P(,
IntersectingQuadPixelTest,
testing::ValuesIn(GetRendererTypes()),
testing::PrintToStringParamName());
class IntersectingMultiplanarVideoQuadPixelTest : public VizPixelTestWithParam {
public:
void SetUp() override {
VizPixelTestWithParam::SetUp();
constexpr bool kUseGpuMemoryBufferResources = false;
constexpr int kMaxResourceSize = 10000;
video_resource_updater_ = std::make_unique<media::VideoResourceUpdater>(
this->child_context_provider_.get(),
this->child_resource_provider_.get(),
/*shared_image_interface=*/nullptr, kUseGpuMemoryBufferResources,
kMaxResourceSize);
video_resource_updater2_ = std::make_unique<media::VideoResourceUpdater>(
this->child_context_provider_.get(),
this->child_resource_provider_.get(),
/*shared_image_interface=*/nullptr, kUseGpuMemoryBufferResources,
kMaxResourceSize);
}
void TearDown() override {
video_resource_updater_.reset();
video_resource_updater2_.reset();
VizPixelTest::TearDown();
}
protected:
void SetupQuadStateTransformsAndRenderPass() {
// This sets up transforms for a pair of draw quads created by
// VideoResourceUpdater. They are both rotated relative to the root plane,
// they are also rotated relative to each other. The intersect in the middle
// at a non-perpendicular angle so that any errors are hopefully magnified.
// The quads should intersect correctly, as in the front quad should only
// be partially in front of the back quad, and partially behind.
viewport_rect_ = gfx::Rect(this->device_viewport_size_);
quad_rect_ = gfx::Rect(0, 0, this->device_viewport_size_.width(),
this->device_viewport_size_.height() / 2.0);
CompositorRenderPassId id{1};
render_pass_ = CreateTestRootRenderPass(id, viewport_rect_);
// Create the transform for front quad rotated on the Z and Y axis.
transform_.Translate3d(0, 0,
0.707 * this->device_viewport_size_.width() / 2.0);
transform_.RotateAboutZAxis(45.0);
transform_.RotateAboutYAxis(45.0);
// Create the transform for back quad, and rotate on just the y axis. This
// will intersect the first quad partially.
transform2_.Translate3d(0, 0,
-0.707 * this->device_viewport_size_.width() / 2.0);
transform2_.RotateAboutYAxis(-45.0);
}
void AppendBackgroundAndRunTest(const cc::PixelComparator& comparator,
const base::FilePath& ref_file) {
SharedQuadState* background_quad_state =
CreateTestSharedQuadState(gfx::Transform(), viewport_rect_,
render_pass_.get(), gfx::MaskFilterInfo());
auto* background_quad =
render_pass_->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
background_quad->SetNew(background_quad_state, viewport_rect_,
viewport_rect_, SkColors::kWhite, false);
AggregatedRenderPassId new_id{1};
auto copy_pass = cc::CopyToAggregatedRenderPass(
render_pass_.get(), new_id, gfx::ContentColorUsage::kSRGB,
this->resource_provider_.get(), this->child_resource_provider_.get(),
this->child_context_provider_.get());
pass_list_.push_back(std::move(copy_pass));
EXPECT_TRUE(this->RunPixelTest(&pass_list_, ref_file, comparator));
}
template <typename T>
T* CreateAndAppendDrawQuad() {
return render_pass_->CreateAndAppendDrawQuad<T>();
}
std::unique_ptr<CompositorRenderPass> render_pass_;
gfx::Rect viewport_rect_;
gfx::Rect quad_rect_;
AggregatedRenderPassList pass_list_;
gfx::Transform transform_;
gfx::Transform transform2_;
std::unique_ptr<media::VideoResourceUpdater> video_resource_updater_;
std::unique_ptr<media::VideoResourceUpdater> video_resource_updater2_;
// Make sure they end up in a 3d sorting context.
const int sorting_context_id_ = 1;
};
INSTANTIATE_TEST_SUITE_P(,
IntersectingMultiplanarVideoQuadPixelTest,
// TODO(crbug.com/40106224): Enable these tests for
// SkiaRenderer Dawn once video is supported.
testing::ValuesIn(GetGpuRendererTypes()),
testing::PrintToStringParamName());
// GetGpuRendererTypes() can return an empty list, e.g. on Fuchsia ARM64.
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(
IntersectingMultiplanarVideoQuadPixelTest);
class IntersectingQuadSoftwareTest : public IntersectingQuadPixelTest {};
INSTANTIATE_TEST_SUITE_P(,
IntersectingQuadSoftwareTest,
testing::Values(RendererType::kSoftware),
testing::PrintToStringParamName());
TEST_P(IntersectingQuadPixelTest, SolidColorQuads) {
this->SetupQuadStateAndRenderPass();
auto* quad = this->template CreateAndAppendDrawQuad<SolidColorDrawQuad>();
auto* quad2 = this->template CreateAndAppendDrawQuad<SolidColorDrawQuad>();
quad->SetNew(this->front_quad_state_, this->quad_rect_, this->quad_rect_,
SkColors::kBlue, false);
quad2->SetNew(this->back_quad_state_, this->quad_rect_, this->quad_rect_,
SkColors::kGreen, false);
this->AppendBackgroundAndRunTest(
cc::FuzzyPixelComparator().SetErrorPixelsPercentageLimit(2.f),
base::FilePath(FILE_PATH_LITERAL("intersecting_blue_green.png")));
}
TEST_P(IntersectingQuadPixelTest, TexturedQuads) {
this->SetupQuadStateAndRenderPass();
CreateTestTwoColoredTextureDrawQuad(
!is_software_renderer(), this->quad_rect_, SkColors::kBlack,
SkColors::kBlue, SkColors::kTransparent, true /* premultiplied_alpha */,
false /* flipped_texture_quad */, false /* half_and_half */,
this->front_quad_state_, this->resource_provider_.get(),
this->child_resource_provider_.get(), this->child_context_provider_,
this->render_pass_.get());
CreateTestTwoColoredTextureDrawQuad(
!is_software_renderer(), this->quad_rect_, SkColors::kGreen,
SkColors::kBlack, SkColors::kTransparent, true /* premultiplied_alpha */,
false /* flipped_texture_quad */, false /* half_and_half */,
this->back_quad_state_, this->resource_provider_.get(),
this->child_resource_provider_.get(), this->child_context_provider_,
this->render_pass_.get());
this->AppendBackgroundAndRunTest(
cc::FuzzyPixelComparator().SetErrorPixelsPercentageLimit(2.f),
base::FilePath(FILE_PATH_LITERAL("intersecting_blue_green_squares.png")));
}
TEST_P(IntersectingQuadPixelTest, NonFlippedTexturedQuads) {
this->SetupQuadStateAndRenderPass();
CreateTestTwoColoredTextureDrawQuad(
!is_software_renderer(), this->quad_rect_,
SkColor4f::FromColor(SkColorSetARGB(255, 0, 0, 0)),
SkColor4f::FromColor(SkColorSetARGB(255, 0, 0, 255)),
SkColors::kTransparent, true /* premultiplied_alpha */,
false /* flipped_texture_quad */, true /* half_and_half */,
this->front_quad_state_, this->resource_provider_.get(),
this->child_resource_provider_.get(), this->child_context_provider_,
this->render_pass_.get());
CreateTestTwoColoredTextureDrawQuad(
!is_software_renderer(), this->quad_rect_,
SkColor4f::FromColor(SkColorSetARGB(255, 0, 255, 0)),
SkColor4f::FromColor(SkColorSetARGB(255, 0, 0, 0)),
SkColors::kTransparent, true /* premultiplied_alpha */,
false /* flipped_texture_quad */, true /* half_and_half */,
this->back_quad_state_, this->resource_provider_.get(),
this->child_resource_provider_.get(), this->child_context_provider_,
this->render_pass_.get());
this->AppendBackgroundAndRunTest(
cc::FuzzyPixelComparator().SetErrorPixelsPercentageLimit(2.f),
base::FilePath(FILE_PATH_LITERAL(
"intersecting_non_flipped_blue_green_half_size_rectangles.png")));
}
TEST_P(IntersectingQuadPixelTest, FlippedTexturedQuads) {
this->SetupQuadStateAndRenderPass();
CreateTestTwoColoredTextureDrawQuad(
!is_software_renderer(), this->quad_rect_,
SkColor4f::FromColor(SkColorSetARGB(255, 0, 0, 0)),
SkColor4f::FromColor(SkColorSetARGB(255, 0, 0, 255)),
SkColors::kTransparent, true /* premultiplied_alpha */,
true /* flipped_texture_quad */, true /* half_and_half */,
this->front_quad_state_, this->resource_provider_.get(),
this->child_resource_provider_.get(), this->child_context_provider_,
this->render_pass_.get());
CreateTestTwoColoredTextureDrawQuad(
!is_software_renderer(), this->quad_rect_,
SkColor4f::FromColor(SkColorSetARGB(255, 0, 255, 0)),
SkColor4f::FromColor(SkColorSetARGB(255, 0, 0, 0)),
SkColors::kTransparent, true /* premultiplied_alpha */,
true /* flipped_texture_quad */, true /* half_and_half */,
this->back_quad_state_, this->resource_provider_.get(),
this->child_resource_provider_.get(), this->child_context_provider_,
this->render_pass_.get());
this->AppendBackgroundAndRunTest(
cc::FuzzyPixelComparator().SetErrorPixelsPercentageLimit(2.f),
base::FilePath(FILE_PATH_LITERAL(
"intersecting_flipped_blue_green_half_size_rectangles.png")));
}
TEST_P(IntersectingQuadSoftwareTest, PictureQuads) {
bool needs_blending = true;
this->SetupQuadStateAndRenderPass();
gfx::Rect outer_rect(this->quad_rect_);
gfx::Rect inner_rect(this->quad_rect_.x() + (this->quad_rect_.width() / 4),
this->quad_rect_.y() + (this->quad_rect_.height() / 4),
this->quad_rect_.width() / 2,
this->quad_rect_.height() / 2);
cc::PaintFlags black_flags;
black_flags.setColor(SkColors::kBlack);
cc::PaintFlags blue_flags;
blue_flags.setColor(SkColors::kBlue);
cc::PaintFlags green_flags;
green_flags.setColor(SkColors::kGreen);
cc::FakeRecordingSource blue_recording(quad_rect_.size());
blue_recording.add_draw_rect_with_flags(outer_rect, black_flags);
blue_recording.add_draw_rect_with_flags(inner_rect, blue_flags);
blue_recording.Rerecord();
scoped_refptr<cc::RasterSource> blue_raster_source =
blue_recording.CreateRasterSource();
auto* blue_quad =
this->render_pass_->template CreateAndAppendDrawQuad<PictureDrawQuad>();
blue_quad->SetNew(
this->front_quad_state_, this->quad_rect_, this->quad_rect_,
needs_blending, gfx::RectF(this->quad_rect_), false, this->quad_rect_,
1.f, {}, blue_raster_source->GetDisplayItemList(), cc::ScrollOffsetMap());
cc::FakeRecordingSource green_recording(quad_rect_.size());
green_recording.add_draw_rect_with_flags(outer_rect, green_flags);
green_recording.add_draw_rect_with_flags(inner_rect, black_flags);
green_recording.Rerecord();
scoped_refptr<cc::RasterSource> green_raster_source =
green_recording.CreateRasterSource();
auto* green_quad =
this->render_pass_->template CreateAndAppendDrawQuad<PictureDrawQuad>();
green_quad->SetNew(this->back_quad_state_, this->quad_rect_, this->quad_rect_,
needs_blending, gfx::RectF(this->quad_rect_), false,
this->quad_rect_, 1.f, {},
green_raster_source->GetDisplayItemList(),
cc::ScrollOffsetMap());
this->AppendBackgroundAndRunTest(
cc::FuzzyPixelComparator().SetErrorPixelsPercentageLimit(2.f),
base::FilePath(FILE_PATH_LITERAL("intersecting_blue_green_squares.png")));
}
TEST_P(IntersectingQuadPixelTest, RenderPassQuads) {
this->SetupQuadStateAndRenderPass();
AggregatedRenderPassId child_pass_id1{2};
AggregatedRenderPassId child_pass_id2{3};
auto child_pass1 =
CreateTestRenderPass(child_pass_id1, this->quad_rect_, gfx::Transform());
SharedQuadState* child1_quad_state = CreateTestSharedQuadState(
gfx::Transform(), this->quad_rect_, child_pass1.get(), gfx::MaskFilterInfo());
auto child_pass2 =
CreateTestRenderPass(child_pass_id2, this->quad_rect_, gfx::Transform());
SharedQuadState* child2_quad_state = CreateTestSharedQuadState(
gfx::Transform(), this->quad_rect_, child_pass2.get(), gfx::MaskFilterInfo());
CreateTestTwoColoredTextureDrawQuad(
!is_software_renderer(), this->quad_rect_,
SkColor4f::FromColor(SkColorSetARGB(255, 0, 0, 0)),
SkColor4f::FromColor(SkColorSetARGB(255, 0, 0, 255)),
SkColors::kTransparent, true /* premultiplied_alpha */,
false /* flipped_texture_quad */, false /* half_and_half */,
child1_quad_state, this->resource_provider_.get(),
this->child_resource_provider_.get(), this->child_context_provider_,
child_pass1.get());
CreateTestTwoColoredTextureDrawQuad(
!is_software_renderer(), this->quad_rect_,
SkColor4f::FromColor(SkColorSetARGB(255, 0, 255, 0)),
SkColor4f::FromColor(SkColorSetARGB(255, 0, 0, 0)),
SkColors::kTransparent, true /* premultiplied_alpha */,
false /* flipped_texture_quad */, false /* half_and_half */,
child2_quad_state, this->resource_provider_.get(),
this->child_resource_provider_.get(), this->child_context_provider_,
child_pass2.get());
CreateTestRenderPassDrawQuad(this->front_quad_state_, this->quad_rect_,
child_pass_id1, this->render_pass_.get());
CreateTestRenderPassDrawQuad(this->back_quad_state_, this->quad_rect_,
child_pass_id2, this->render_pass_.get());
this->pass_list_.push_back(std::move(child_pass1));
this->pass_list_.push_back(std::move(child_pass2));
this->AppendBackgroundAndRunTest(
cc::FuzzyPixelComparator().SetErrorPixelsPercentageLimit(2.f),
base::FilePath(FILE_PATH_LITERAL("intersecting_blue_green_squares.png")));
}
TEST_P(IntersectingMultiplanarVideoQuadPixelTest, YUVVideoQuads) {
this->SetupQuadStateTransformsAndRenderPass();
gfx::Rect inner_rect(
((this->quad_rect_.x() + (this->quad_rect_.width() / 4)) & ~0xF),
((this->quad_rect_.y() + (this->quad_rect_.height() / 4)) & ~0xF),
(this->quad_rect_.width() / 2) & ~0xF,
(this->quad_rect_.height() / 2) & ~0xF);
CreateTestMultiplanarVideoDrawQuad(
TestVideoFrameBuilder(media::PIXEL_FORMAT_I420,
gfx::ColorSpace::CreateJpeg(), kUnitSquare,
this->quad_rect_.size(), this->quad_rect_.size())
.DrawTwoColor(0, 128, 128, inner_rect, 29, 255, 107),
/*alpha_value=*/255, transform_, gfx::MaskFilterInfo(),
sorting_context_id_, this->render_pass_.get(),
this->video_resource_updater_.get(), this->quad_rect_, this->quad_rect_);
CreateTestMultiplanarVideoDrawQuad(
TestVideoFrameBuilder(media::PIXEL_FORMAT_I420,
gfx::ColorSpace::CreateJpeg(), kUnitSquare,
this->quad_rect_.size(), this->quad_rect_.size())
.DrawTwoColor(149, 43, 21, inner_rect, 0, 128, 128),
/*alpha_value=*/255, transform2_, gfx::MaskFilterInfo(),
sorting_context_id_, this->render_pass_.get(),
this->video_resource_updater_.get(), this->quad_rect_, this->quad_rect_);
base::FilePath baseline = base::FilePath(
FILE_PATH_LITERAL("intersecting_blue_green_squares_video.png"));
if (is_skia_graphite()) {
baseline = baseline.InsertBeforeExtensionASCII(kGraphiteStr);
}
if (renderer_type() == RendererType::kSkiaGL && IsANGLEMetal()) {
baseline = baseline.InsertBeforeExtensionASCII(kANGLEMetalStr);
}
this->AppendBackgroundAndRunTest(cc::FuzzyPixelComparator()
.DiscardAlpha()
.SetErrorPixelsPercentageLimit(0.50f)
.SetAvgAbsErrorLimit(1.2f)
.SetAbsErrorLimit(2),
baseline);
}
TEST_P(IntersectingMultiplanarVideoQuadPixelTest, Y16VideoQuads) {
this->SetupQuadStateTransformsAndRenderPass();
gfx::Rect inner_rect(
((this->quad_rect_.x() + (this->quad_rect_.width() / 4)) & ~0xF),
((this->quad_rect_.y() + (this->quad_rect_.height() / 4)) & ~0xF),
(this->quad_rect_.width() / 2) & ~0xF,
(this->quad_rect_.height() / 2) & ~0xF);
CreateTestY16TextureDrawQuad_TwoColor(
transform_, sorting_context_id_, 18, 0, this->render_pass_.get(),
this->video_resource_updater_.get(), this->quad_rect_, this->quad_rect_,
inner_rect);
CreateTestY16TextureDrawQuad_TwoColor(
transform2_, sorting_context_id_, 0, 182, this->render_pass_.get(),
this->video_resource_updater2_.get(), this->quad_rect_, this->quad_rect_,
inner_rect);
base::FilePath baseline = base::FilePath(
FILE_PATH_LITERAL("intersecting_light_dark_squares_video.png"));
if (is_skia_graphite()) {
baseline = baseline.InsertBeforeExtensionASCII(kGraphiteStr);
}
if (renderer_type() == RendererType::kSkiaGL && IsANGLEMetal()) {
baseline = baseline.InsertBeforeExtensionASCII(kANGLEMetalStr);
}
this->AppendBackgroundAndRunTest(cc::FuzzyPixelOffByOneComparator(),
baseline);
}
// TODO(skaslev): The software renderer does not support non-premultplied alpha.
TEST_P(GPURendererPixelTest, NonPremultipliedTextureWithoutBackground) {
gfx::Rect rect(this->device_viewport_size_);
AggregatedRenderPassId id{1};
auto pass = CreateTestRootRenderPass(id, rect);
SharedQuadState* shared_state = CreateTestSharedQuadState(
gfx::Transform(), rect, pass.get(), gfx::MaskFilterInfo());
CreateTestTextureDrawQuad(!is_software_renderer(),
gfx::Rect(this->device_viewport_size_),
{0.0f, 1.0f, 0.0f, 0.5f}, // Texel color.
SkColors::kTransparent, // Background color.
false, // Premultiplied alpha.
shared_state, this->resource_provider_.get(),
this->child_resource_provider_.get(),
this->child_context_provider_, pass.get());
auto* color_quad = pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
color_quad->SetNew(shared_state, rect, rect, SkColors::kWhite, false);
AggregatedRenderPassList pass_list;
pass_list.push_back(std::move(pass));
EXPECT_TRUE(this->RunPixelTest(
&pass_list, base::FilePath(FILE_PATH_LITERAL("green_alpha.png")),
cc::AlphaDiscardingFuzzyPixelOffByOneComparator()));
}
// TODO(skaslev): The software renderer does not support non-premultplied alpha.
TEST_P(GPURendererPixelTest, NonPremultipliedTextureWithBackground) {
gfx::Rect rect(this->device_viewport_size_);
AggregatedRenderPassId id{1};
auto pass = CreateTestRootRenderPass(id, rect);
SharedQuadState* texture_quad_state = CreateTestSharedQuadState(
gfx::Transform(), rect, pass.get(), gfx::MaskFilterInfo());
texture_quad_state->opacity = 0.8f;
CreateTestTextureDrawQuad(
!is_software_renderer(), gfx::Rect(this->device_viewport_size_),
SkColor4f::FromColor(SkColorSetARGB(204, 120, 255, 120)), // Texel color.
SkColors::kGreen, // Background color.
false, // Premultiplied alpha.
texture_quad_state, this->resource_provider_.get(),
this->child_resource_provider_.get(), this->child_context_provider_,
pass.get());
SharedQuadState* color_quad_state = CreateTestSharedQuadState(
gfx::Transform(), rect, pass.get(), gfx::MaskFilterInfo());
auto* color_quad = pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
color_quad->SetNew(color_quad_state, rect, rect, SkColors::kWhite, false);
AggregatedRenderPassList pass_list;
pass_list.push_back(std::move(pass));
EXPECT_TRUE(this->RunPixelTest(
&pass_list, base::FilePath(FILE_PATH_LITERAL("green_alpha.png")),
cc::AlphaDiscardingFuzzyPixelOffByOneComparator()));
}
class VideoRendererPixelTestBase : public VizPixelTest {
public:
explicit VideoRendererPixelTestBase(RendererType type) : VizPixelTest(type) {}
protected:
// Include the protected member variables from the parent class.
using cc::PixelTest::child_context_provider_;
using cc::PixelTest::child_resource_provider_;
using cc::PixelTest::resource_provider_;
void CreateEdgeBleedPass(media::VideoPixelFormat format,
const gfx::ColorSpace& color_space,
AggregatedRenderPassList* pass_list) {
gfx::Rect rect(200, 200);
CompositorRenderPassId id{1};
auto pass = CreateTestRootRenderPass(id, rect);
// Scale the video up so that bilinear filtering kicks in to sample more
// than just nearest neighbor would.
gfx::Transform scale_by_2;
scale_by_2.Scale(2.f, 2.f);
gfx::Size background_size(200, 200);
gfx::Rect green_rect(16, 20, 100, 100);
gfx::RectF tex_coord_rect(
static_cast<float>(green_rect.x()) / background_size.width(),
static_cast<float>(green_rect.y()) / background_size.height(),
static_cast<float>(green_rect.width()) / background_size.width(),
static_cast<float>(green_rect.height()) / background_size.height());
// YUV of (149,43,21) should be green (0,255,0) in RGB.
// Create a video frame that has a non-green background rect, with a
// green sub-rectangle that should be the only thing displayed in
// the final image. Bleeding will appear on all four sides of the video
// if the tex coords are not clamped.
CreateTestMultiplanarVideoDrawQuad(
TestVideoFrameBuilder(format, color_space, tex_coord_rect,
background_size, background_size)
.DrawTwoColor(128, 128, 128, green_rect, 149, 43, 21),
/*alpha_value=*/255, scale_by_2, gfx::MaskFilterInfo(),
/*sorting_context_id=*/0, pass.get(),
this->video_resource_updater_.get(), gfx::Rect(background_size),
gfx::Rect(background_size));
AggregatedRenderPassId new_id{1};
auto copy_pass = cc::CopyToAggregatedRenderPass(
pass.get(), new_id, gfx::ContentColorUsage::kSRGB,
this->resource_provider_.get(), this->child_resource_provider_.get(),
this->child_context_provider_.get());
pass_list->push_back(std::move(copy_pass));
}
void SetUp() override {
VizPixelTest::SetUp();
constexpr bool kUseGpuMemoryBufferResources = false;
constexpr int kMaxResourceSize = 10000;
video_resource_updater_ = std::make_unique<media::VideoResourceUpdater>(
child_context_provider_.get(), child_resource_provider_.get(),
/*shared_image_interface=*/nullptr, kUseGpuMemoryBufferResources,
kMaxResourceSize);
}
void TearDown() override {
video_resource_updater_ = nullptr;
VizPixelTest::TearDown();
}
std::unique_ptr<media::VideoResourceUpdater> video_resource_updater_;
};
#if BUILDFLAG(ENABLE_GL_BACKEND_TESTS)
// Upshift video frame to 10 bit.
scoped_refptr<media::VideoFrame> CreateHighbitVideoFrame(
media::VideoFrame* video_frame) {
media::VideoPixelFormat format;
switch (video_frame->format()) {
case media::PIXEL_FORMAT_I420:
format = media::PIXEL_FORMAT_YUV420P10;
break;
case media::PIXEL_FORMAT_I422:
format = media::PIXEL_FORMAT_YUV422P10;
break;
case media::PIXEL_FORMAT_I444:
format = media::PIXEL_FORMAT_YUV444P10;
break;
default:
NOTREACHED();
}
scoped_refptr<media::VideoFrame> ret = media::VideoFrame::CreateFrame(
format, video_frame->coded_size(), video_frame->visible_rect(),
video_frame->natural_size(), video_frame->timestamp());
ret->set_color_space(video_frame->ColorSpace());
// Copy all metadata.
ret->metadata().MergeMetadataFrom(video_frame->metadata());
for (int plane = media::VideoFrame::Plane::kY;
plane <= media::VideoFrame::Plane::kV; ++plane) {
int width = video_frame->row_bytes(plane);
const uint8_t* src = video_frame->data(plane);
uint16_t* dst = reinterpret_cast<uint16_t*>(ret->writable_data(plane));
for (int row = 0; row < video_frame->rows(plane); row++) {
for (int x = 0; x < width; x++) {
// Replicate the top bits into the lower bits, this way
// 0xFF becomes 0x3FF.
UNSAFE_TODO(dst[x]) =
(UNSAFE_TODO(src[x]) << 2) | (UNSAFE_TODO(src[x]) >> 6);
}
UNSAFE_TODO(src += video_frame->stride(plane));
UNSAFE_TODO(dst += ret->stride(plane) / 2);
}
}
return ret;
}
class VideoRendererPixelHiLoTest : public VideoRendererPixelTestBase,
public testing::WithParamInterface<bool> {
public:
VideoRendererPixelHiLoTest()
: VideoRendererPixelTestBase(RendererType::kSkiaGL) {}
bool IsHighbit() const { return GetParam(); }
};
INSTANTIATE_TEST_SUITE_P(, VideoRendererPixelHiLoTest, testing::Bool());
TEST_P(VideoRendererPixelHiLoTest, SimpleYUVRect) {
gfx::Rect rect(this->device_viewport_size_);
CompositorRenderPassId id{1};
auto pass = CreateTestRootRenderPass(id, rect);
// Set the output color space to match the input primaries and transfer.
this->display_color_spaces_ = kRec601DisplayColorSpaces;
auto video_frame =
TestVideoFrameBuilder(media::PIXEL_FORMAT_I420,
gfx::ColorSpace::CreateREC601(), kUnitSquare,
rect.size(), rect.size())
.DrawStriped();
if (IsHighbit()) {
video_frame = CreateHighbitVideoFrame(video_frame.get());
}
CreateTestMultiplanarVideoDrawQuad(
std::move(video_frame), /*alpha_value=*/255, gfx::Transform(),
gfx::MaskFilterInfo(),
/*sorting_context_id=*/0, pass.get(), this->video_resource_updater_.get(),
rect, rect);
AggregatedRenderPassId new_id{1};
auto copy_pass = cc::CopyToAggregatedRenderPass(
pass.get(), new_id, gfx::ContentColorUsage::kSRGB,
this->resource_provider_.get(), this->child_resource_provider_.get(),
this->child_context_provider_.get());
AggregatedRenderPassList pass_list;
pass_list.push_back(std::move(copy_pass));
EXPECT_TRUE(this->RunPixelTest(
&pass_list, base::FilePath(FILE_PATH_LITERAL("yuv_stripes.png")),
cc::AlphaDiscardingFuzzyPixelOffByOneComparator()));
}
class VideoRendererPixelHiLoColorSpaceTest
: public VideoRendererPixelTestBase,
public testing::WithParamInterface<std::tuple<bool, gfx::ColorSpace>> {
public:
VideoRendererPixelHiLoColorSpaceTest()
: VideoRendererPixelTestBase(RendererType::kSkiaGL) {}
bool IsHighbit() const { return std::get<0>(GetParam()); }
gfx::ColorSpace GetColorSpace() const { return std::get<1>(GetParam()); }
const std::string GetName() const {
auto cs = GetColorSpace();
switch (cs.GetMatrixID()) {
case gfx::ColorSpace::MatrixID::FCC:
return "_fcc_limited";
case gfx::ColorSpace::MatrixID::YCOCG:
return "_ycocg_limited";
case gfx::ColorSpace::MatrixID::SMPTE240M:
return "_smpte240m_limited";
case gfx::ColorSpace::MatrixID::YDZDX:
return "_ydzdx_limited";
case gfx::ColorSpace::MatrixID::GBR:
return "_gbr_limited";
default:
NOTREACHED();
}
}
};
gfx::ColorSpace yuv_color_spaces[] = {
gfx::ColorSpace(gfx::ColorSpace::PrimaryID::SMPTE170M,
gfx::ColorSpace::TransferID::SMPTE170M,
gfx::ColorSpace::MatrixID::YCOCG,
gfx::ColorSpace::RangeID::LIMITED),
gfx::ColorSpace(gfx::ColorSpace::PrimaryID::SMPTE170M,
gfx::ColorSpace::TransferID::SMPTE170M,
gfx::ColorSpace::MatrixID::FCC,
gfx::ColorSpace::RangeID::LIMITED),
gfx::ColorSpace(gfx::ColorSpace::PrimaryID::SMPTE170M,
gfx::ColorSpace::TransferID::SMPTE170M,
gfx::ColorSpace::MatrixID::SMPTE240M,
gfx::ColorSpace::RangeID::LIMITED),
gfx::ColorSpace(gfx::ColorSpace::PrimaryID::SMPTE170M,
gfx::ColorSpace::TransferID::SMPTE170M,
gfx::ColorSpace::MatrixID::YDZDX,
gfx::ColorSpace::RangeID::LIMITED),
gfx::ColorSpace(gfx::ColorSpace::PrimaryID::SMPTE170M,
gfx::ColorSpace::TransferID::SMPTE170M,
gfx::ColorSpace::MatrixID::GBR,
gfx::ColorSpace::RangeID::LIMITED),
};
INSTANTIATE_TEST_SUITE_P(,
VideoRendererPixelHiLoColorSpaceTest,
testing::Combine(testing::Bool(),
testing::ValuesIn(yuv_color_spaces)));
TEST_P(VideoRendererPixelHiLoColorSpaceTest, SimpleYUVRect) {
gfx::Rect rect(this->device_viewport_size_);
CompositorRenderPassId id{1};
auto pass = CreateTestRootRenderPass(id, rect);
// Set the output color space to match the input primaries and transfer.
this->display_color_spaces_ = kRec601DisplayColorSpaces;
scoped_refptr<media::VideoFrame> video_frame =
TestVideoFrameBuilder(media::PIXEL_FORMAT_I420, GetColorSpace(),
kUnitSquare, rect.size(), rect.size())
.DrawStriped();
if (IsHighbit()) {
video_frame = CreateHighbitVideoFrame(video_frame.get());
}
CreateTestMultiplanarVideoDrawQuad(
std::move(video_frame), /*alpha_value=*/255, gfx::Transform(),
gfx::MaskFilterInfo(),
/*sorting_context_id=*/0, pass.get(), this->video_resource_updater_.get(),
rect, rect);
AggregatedRenderPassId new_id{1};
auto copy_pass = cc::CopyToAggregatedRenderPass(
pass.get(), new_id, gfx::ContentColorUsage::kSRGB,
this->resource_provider_.get(), this->child_resource_provider_.get(),
this->child_context_provider_.get());
AggregatedRenderPassList pass_list;
pass_list.push_back(std::move(copy_pass));
base::FilePath expected_result =
base::FilePath(FILE_PATH_LITERAL("yuv_stripes.png"));
expected_result = expected_result.InsertBeforeExtensionASCII(GetName());
// YCgCo color space supports highbit formats.
if (IsHighbit() &&
GetColorSpace().GetMatrixID() == gfx::ColorSpace::MatrixID::YCOCG) {
expected_result = expected_result.InsertBeforeExtensionASCII("_highbit");
}
EXPECT_TRUE(
this->RunPixelTest(&pass_list, expected_result,
cc::AlphaDiscardingFuzzyPixelOffByOneComparator()));
}
#if BUILDFLAG(IS_IOS)
// TODO(crbug.com/40259140): currently failing on iOS.
#define MAYBE_ClippedYUVRect DISABLED_ClippedYUVRect
#else
#define MAYBE_ClippedYUVRect ClippedYUVRect
#endif // BUILDFLAG(IS_IOS)
TEST_P(VideoRendererPixelHiLoTest, MAYBE_ClippedYUVRect) {
gfx::Rect viewport(this->device_viewport_size_);
gfx::Rect draw_rect(this->device_viewport_size_.width() * 1.5,
this->device_viewport_size_.height() * 1.5);
CompositorRenderPassId id{1};
auto pass = CreateTestRootRenderPass(id, viewport);
// Set the output color space to match the input primaries and transfer.
this->display_color_spaces_ = kRec601DisplayColorSpaces;
scoped_refptr<media::VideoFrame> video_frame =
TestVideoFrameBuilder(media::PIXEL_FORMAT_I420,
gfx::ColorSpace::CreateREC601(), kUnitSquare,
draw_rect.size(), viewport.size())
.DrawStriped();
if (IsHighbit()) {
video_frame = CreateHighbitVideoFrame(video_frame.get());
}
CreateTestMultiplanarVideoDrawQuad(
std::move(video_frame), /*alpha_value=*/255, gfx::Transform(),
gfx::MaskFilterInfo(),
/*sorting_context_id=*/0, pass.get(), this->video_resource_updater_.get(),
draw_rect, viewport);
AggregatedRenderPassId new_id{1};
auto copy_pass = cc::CopyToAggregatedRenderPass(
pass.get(), new_id, gfx::ContentColorUsage::kSRGB,
this->resource_provider_.get(), this->child_resource_provider_.get(),
this->child_context_provider_.get());
AggregatedRenderPassList pass_list;
pass_list.push_back(std::move(copy_pass));
EXPECT_TRUE(this->RunPixelTest(
&pass_list, base::FilePath(FILE_PATH_LITERAL("yuv_stripes_clipped.png")),
cc::AlphaDiscardingFuzzyPixelOffByOneComparator()));
}
#endif // #if BUILDFLAG(ENABLE_GL_BACKEND_TESTS)
class VideoRendererPixelTest
: public VideoRendererPixelTestBase,
public testing::WithParamInterface<RendererType> {
public:
VideoRendererPixelTest() : VideoRendererPixelTestBase(GetParam()) {}
};
INSTANTIATE_TEST_SUITE_P(,
VideoRendererPixelTest,
// TODO(crbug.com/40106226): Enable these tests for
// SkiaRenderer Dawn once video is supported.
testing::ValuesIn(GetGpuRendererTypes()),
testing::PrintToStringParamName());
// GetGpuRendererTypes() can return an empty list, e.g. on Fuchsia ARM64.
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(VideoRendererPixelTest);
TEST_P(VideoRendererPixelTest, OffsetYUVRect) {
gfx::Rect rect(this->device_viewport_size_);
CompositorRenderPassId id{1};
auto pass = CreateTestRootRenderPass(id, rect);
// Set the output color space to match the input primaries and transfer.
this->display_color_spaces_ = kRec601DisplayColorSpaces;
// Intentionally sets frame format to I420 for testing coverage.
CreateTestMultiplanarVideoDrawQuad(
TestVideoFrameBuilder(
media::PIXEL_FORMAT_I420, gfx::ColorSpace::CreateREC601(),
gfx::RectF(0.125f, 0.25f, 0.75f, 0.5f), rect.size(), rect.size())
.DrawStriped(),
/*alpha_value=*/255, gfx::Transform(), gfx::MaskFilterInfo(),
/*sorting_context_id=*/0, pass.get(), this->video_resource_updater_.get(),
rect, rect);
AggregatedRenderPassId new_id{1};
auto copy_pass = cc::CopyToAggregatedRenderPass(
pass.get(), new_id, gfx::ContentColorUsage::kSRGB,
this->resource_provider_.get(), this->child_resource_provider_.get(),
this->child_context_provider_.get());
AggregatedRenderPassList pass_list;
pass_list.push_back(std::move(copy_pass));
base::FilePath expected_result =
base::FilePath(FILE_PATH_LITERAL("yuv_stripes_offset.png"));
if (is_skia_graphite()) {
expected_result = expected_result.InsertBeforeExtensionASCII(kGraphiteStr);
}
EXPECT_TRUE(
this->RunPixelTest(&pass_list, expected_result,
cc::AlphaDiscardingFuzzyPixelOffByOneComparator()));
}
TEST_P(VideoRendererPixelTest, SimpleYUVRectBlack) {
gfx::Rect rect(this->device_viewport_size_);
CompositorRenderPassId id{1};
auto pass = CreateTestRootRenderPass(id, rect);
// Set the output color space to match the input primaries and transfer.
this->display_color_spaces_ = kRec601DisplayColorSpaces;
// In MPEG color range YUV values of (15,128,128) should produce black.
CreateTestMultiplanarVideoDrawQuad(
TestVideoFrameBuilder(media::PIXEL_FORMAT_I420,
gfx::ColorSpace::CreateREC601(), kUnitSquare,
rect.size(), rect.size())
.DrawSolid(15, 128, 128),
/*alpha_value=*/255, gfx::Transform(), gfx::MaskFilterInfo(),
/*sorting_context_id=*/0, pass.get(), this->video_resource_updater_.get(),
rect, rect);
AggregatedRenderPassId new_id{1};
auto copy_pass = cc::CopyToAggregatedRenderPass(
pass.get(), new_id, gfx::ContentColorUsage::kSRGB,
this->resource_provider_.get(), this->child_resource_provider_.get(),
this->child_context_provider_.get());
AggregatedRenderPassList pass_list;
pass_list.push_back(std::move(copy_pass));
// If we didn't get black out of the YUV values above, then we probably have a
// color range issue.
EXPECT_TRUE(this->RunPixelTest(
&pass_list, base::FilePath(FILE_PATH_LITERAL("black.png")),
cc::AlphaDiscardingFuzzyPixelOffByOneComparator()));
}
TEST_P(VideoRendererPixelTest, SimpleYUVJRect) {
gfx::Rect rect(this->device_viewport_size_);
CompositorRenderPassId id{1};
auto pass = CreateTestRootRenderPass(id, rect);
// YUV of (149,43,21) should be green (0,255,0) in RGB.
CreateTestMultiplanarVideoDrawQuad(
TestVideoFrameBuilder(media::PIXEL_FORMAT_I420,
gfx::ColorSpace::CreateJpeg(), kUnitSquare,
rect.size(), rect.size())
.DrawSolid(149, 43, 21),
/*alpha_value=*/255, gfx::Transform(), gfx::MaskFilterInfo(),
/*sorting_context_id=*/0, pass.get(), this->video_resource_updater_.get(),
rect, rect);
AggregatedRenderPassId new_id{1};
auto copy_pass = cc::CopyToAggregatedRenderPass(
pass.get(), new_id, gfx::ContentColorUsage::kSRGB,
this->resource_provider_.get(), this->child_resource_provider_.get(),
this->child_context_provider_.get());
AggregatedRenderPassList pass_list;
pass_list.push_back(std::move(copy_pass));
EXPECT_TRUE(this->RunPixelTest(
&pass_list, base::FilePath(FILE_PATH_LITERAL("green.png")),
cc::AlphaDiscardingFuzzyPixelOffByOneComparator()));
}
TEST_P(VideoRendererPixelTest, SimpleYUVJRectWithYV12) {
gfx::Rect rect(this->device_viewport_size_);
CompositorRenderPassId id{1};
auto pass = CreateTestRootRenderPass(id, rect);
// YUV of (84,114,224) should be crimson red (220,20,60) in RGB.
CreateTestMultiplanarVideoDrawQuad(
TestVideoFrameBuilder(media::PIXEL_FORMAT_YV12,
gfx::ColorSpace::CreateJpeg(), kUnitSquare,
rect.size(), rect.size())
.DrawSolid(84, 114, 224),
/*alpha_value=*/255, gfx::Transform(), gfx::MaskFilterInfo(),
/*sorting_context_id=*/0, pass.get(), this->video_resource_updater_.get(),
rect, rect);
AggregatedRenderPassId new_id{1};
auto copy_pass = cc::CopyToAggregatedRenderPass(
pass.get(), new_id, gfx::ContentColorUsage::kSRGB,
this->resource_provider_.get(), this->child_resource_provider_.get(),
this->child_context_provider_.get());
AggregatedRenderPassList pass_list;
pass_list.push_back(std::move(copy_pass));
SkBitmap ref_bitmap;
ref_bitmap.allocPixels(
SkImageInfo::MakeN32Premul(rect.width(), rect.height()));
ref_bitmap.eraseColor(SkColor4f::FromColor(SkColorSetARGB(255, 220, 20, 60)));
EXPECT_TRUE(
this->RunPixelTest(&pass_list, ref_bitmap,
cc::AlphaDiscardingFuzzyPixelOffByOneComparator()));
}
TEST_P(VideoRendererPixelTest, SimpleYUVJRectWithTemperature) {
gfx::Rect rect(this->device_viewport_size_);
CompositorRenderPassId id{1};
auto pass = CreateTestRootRenderPass(id, rect);
// YUV of (225,0,148) should be yellow (255,255,0) in RGB.
CreateTestMultiplanarVideoDrawQuad(
TestVideoFrameBuilder(media::PIXEL_FORMAT_I420,
gfx::ColorSpace::CreateJpeg(), kUnitSquare,
rect.size(), rect.size())
.DrawSolid(225, 0, 148),
/*alpha_value=*/255, gfx::Transform(), gfx::MaskFilterInfo(),
/*sorting_context_id=*/0, pass.get(), this->video_resource_updater_.get(),
rect, rect);
AggregatedRenderPassId new_id{1};
auto copy_pass = cc::CopyToAggregatedRenderPass(
pass.get(), new_id, gfx::ContentColorUsage::kSRGB,
this->resource_provider_.get(), this->child_resource_provider_.get(),
this->child_context_provider_.get());
AggregatedRenderPassList pass_list;
pass_list.push_back(std::move(copy_pass));
SkM44 color_matrix;
color_matrix.setRC(0, 0, 0.7f);
color_matrix.setRC(1, 1, 0.4f);
color_matrix.setRC(2, 2, 0.5f);
this->output_surface_->set_color_matrix(color_matrix);
EXPECT_TRUE(this->RunPixelTest(
&pass_list, base::FilePath(FILE_PATH_LITERAL("temperature_brown.png")),
cc::AlphaDiscardingFuzzyPixelOffByOneComparator()));
}
TEST_P(VideoRendererPixelTest, SimpleNV12JRect) {
gfx::Rect rect(this->device_viewport_size_);
CompositorRenderPassId id{1};
auto pass = CreateTestRootRenderPass(id, rect);
// YUV of (149,100,50) should be emerald green (39, 214, 99) in RGB.
CreateTestMultiplanarVideoDrawQuad(
TestVideoFrameBuilder(media::PIXEL_FORMAT_NV12,
gfx::ColorSpace::CreateJpeg(), kUnitSquare,
rect.size(), rect.size())
.DrawSolid(149, 100, 50),
/*alpha_value=*/255, gfx::Transform(), gfx::MaskFilterInfo(),
/*sorting_context_id=*/0, pass.get(), this->video_resource_updater_.get(),
rect, rect);
AggregatedRenderPassId new_id{1};
auto copy_pass = cc::CopyToAggregatedRenderPass(
pass.get(), new_id, gfx::ContentColorUsage::kSRGB,
this->resource_provider_.get(), this->child_resource_provider_.get(),
this->child_context_provider_.get());
AggregatedRenderPassList pass_list;
pass_list.push_back(std::move(copy_pass));
EXPECT_TRUE(this->RunPixelTest(
&pass_list, base::FilePath(FILE_PATH_LITERAL("emerald_green.png")),
cc::AlphaDiscardingFuzzyPixelOffByOneComparator()));
}
// Test that a YUV video doesn't bleed outside of its tex coords when the
// tex coord rect is only a partial subrectangle of the coded contents.
TEST_P(VideoRendererPixelTest, YUVEdgeBleed) {
AggregatedRenderPassList pass_list;
this->CreateEdgeBleedPass(media::PIXEL_FORMAT_I420,
gfx::ColorSpace::CreateJpeg(), &pass_list);
EXPECT_TRUE(this->RunPixelTest(
&pass_list, base::FilePath(FILE_PATH_LITERAL("green.png")),
cc::AlphaDiscardingFuzzyPixelOffByOneComparator()));
}
TEST_P(VideoRendererPixelTest, YUVAEdgeBleed) {
AggregatedRenderPassList pass_list;
this->CreateEdgeBleedPass(media::PIXEL_FORMAT_I420A,
gfx::ColorSpace::CreateREC601(), &pass_list);
// Set the output color space to match the input primaries and transfer.
this->display_color_spaces_ = kRec601DisplayColorSpaces;
EXPECT_TRUE(this->RunPixelTest(
&pass_list, base::FilePath(FILE_PATH_LITERAL("green.png")),
cc::AlphaDiscardingFuzzyPixelOffByOneComparator()));
}
TEST_P(VideoRendererPixelTest, SimpleYUVJRectGrey) {
gfx::Rect rect(this->device_viewport_size_);
CompositorRenderPassId id{1};
auto pass = CreateTestRootRenderPass(id, rect);
// Dark grey in JPEG color range (in MPEG, this is black).
CreateTestMultiplanarVideoDrawQuad(
TestVideoFrameBuilder(media::PIXEL_FORMAT_I420,
gfx::ColorSpace::CreateJpeg(), kUnitSquare,
rect.size(), rect.size())
.DrawSolid(15, 128, 128),
/*alpha_value=*/255, gfx::Transform(), gfx::MaskFilterInfo(),
/*sorting_context_id=*/0, pass.get(), this->video_resource_updater_.get(),
rect, rect);
AggregatedRenderPassId new_id{1};
auto copy_pass = cc::CopyToAggregatedRenderPass(
pass.get(), new_id, gfx::ContentColorUsage::kSRGB,
this->resource_provider_.get(), this->child_resource_provider_.get(),
this->child_context_provider_.get());
AggregatedRenderPassList pass_list;
pass_list.push_back(std::move(copy_pass));
EXPECT_TRUE(this->RunPixelTest(
&pass_list, base::FilePath(FILE_PATH_LITERAL("dark_grey.png")),
cc::AlphaDiscardingFuzzyPixelOffByOneComparator()));
}
TEST_P(VideoRendererPixelTest, SimpleYUVARect) {
gfx::Rect rect(this->device_viewport_size_);
CompositorRenderPassId id{1};
auto pass = CreateTestRootRenderPass(id, rect);
// Set the output color space to match the input primaries and transfer.
this->display_color_spaces_ = kRec601DisplayColorSpaces;
CreateTestMultiplanarVideoDrawQuad(
TestVideoFrameBuilder(media::PIXEL_FORMAT_I420A,
gfx::ColorSpace::CreateREC601(), kUnitSquare,
rect.size(), rect.size())
.DrawStriped(),
/*alpha_value=*/128, gfx::Transform(), gfx::MaskFilterInfo(),
/*sorting_context_id=*/0, pass.get(), this->video_resource_updater_.get(),
rect, rect);
SharedQuadState* shared_state = CreateTestSharedQuadState(
gfx::Transform(), rect, pass.get(), gfx::MaskFilterInfo());
auto* color_quad = pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
color_quad->SetNew(shared_state, rect, rect, SkColors::kWhite, false);
AggregatedRenderPassId new_id{1};
auto copy_pass = cc::CopyToAggregatedRenderPass(
pass.get(), new_id, gfx::ContentColorUsage::kSRGB,
this->resource_provider_.get(), this->child_resource_provider_.get(),
this->child_context_provider_.get());
AggregatedRenderPassList pass_list;
pass_list.push_back(std::move(copy_pass));
EXPECT_TRUE(this->RunPixelTest(
&pass_list, base::FilePath(FILE_PATH_LITERAL("yuv_stripes_alpha.png")),
cc::AlphaDiscardingFuzzyPixelOffByOneComparator()));
}
TEST_P(VideoRendererPixelTest, FullyTransparentYUVARect) {
gfx::Rect rect(this->device_viewport_size_);
CompositorRenderPassId id{1};
auto pass = CreateTestRootRenderPass(id, rect);
// Set the output color space to match the input primaries and transfer.
this->display_color_spaces_ = kRec601DisplayColorSpaces;
CreateTestMultiplanarVideoDrawQuad(
TestVideoFrameBuilder(media::PIXEL_FORMAT_I420A,
gfx::ColorSpace::CreateREC601(), kUnitSquare,
rect.size(), rect.size())
.DrawStriped(),
/*alpha_value=*/0, gfx::Transform(), gfx::MaskFilterInfo(),
/*sorting_context_id=*/0, pass.get(), this->video_resource_updater_.get(),
rect, rect);
SharedQuadState* shared_state = CreateTestSharedQuadState(
gfx::Transform(), rect, pass.get(), gfx::MaskFilterInfo());
auto* color_quad = pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
color_quad->SetNew(shared_state, rect, rect, SkColors::kBlack, false);
AggregatedRenderPassId new_id{1};
auto copy_pass = cc::CopyToAggregatedRenderPass(
pass.get(), new_id, gfx::ContentColorUsage::kSRGB,
this->resource_provider_.get(), this->child_resource_provider_.get(),
this->child_context_provider_.get());
AggregatedRenderPassList pass_list;
pass_list.push_back(std::move(copy_pass));
EXPECT_TRUE(this->RunPixelTest(&pass_list,
base::FilePath(FILE_PATH_LITERAL("black.png")),
cc::AlphaDiscardingExactPixelComparator()));
}
TEST_P(VideoRendererPixelTest, TwoColorY16Rect) {
gfx::Rect rect(this->device_viewport_size_);
CompositorRenderPassId id{1};
auto pass = CreateTestRootRenderPass(id, rect);
gfx::Rect upper_rect(rect.x(), rect.y(), rect.width(), rect.height() / 2);
CreateTestY16TextureDrawQuad_TwoColor(
gfx::Transform(), /*sorting_context_id=*/0, 68, 123, pass.get(),
this->video_resource_updater_.get(), rect, rect, upper_rect);
AggregatedRenderPassId new_id{1};
auto copy_pass = cc::CopyToAggregatedRenderPass(
pass.get(), new_id, gfx::ContentColorUsage::kSRGB,
this->resource_provider_.get(), this->child_resource_provider_.get(),
this->child_context_provider_.get());
AggregatedRenderPassList pass_list;
pass_list.push_back(std::move(copy_pass));
EXPECT_TRUE(this->RunPixelTest(
&pass_list,
base::FilePath(FILE_PATH_LITERAL("blue_yellow_filter_chain.png")),
cc::AlphaDiscardingFuzzyPixelOffByOneComparator()));
}
TEST_P(RendererPixelTest, FastPassColorFilterAlpha) {
gfx::Rect viewport_rect(this->device_viewport_size_);
AggregatedRenderPassId root_pass_id{1};
auto root_pass = CreateTestRootRenderPass(root_pass_id, viewport_rect);
AggregatedRenderPassId child_pass_id{2};
gfx::Rect pass_rect(this->device_viewport_size_);
gfx::Transform transform_to_root;
float matrix[20];
float amount = 0.5f;
matrix[0] = 0.213f + 0.787f * amount;
matrix[1] = 0.715f - 0.715f * amount;
matrix[2] = 1.f - (matrix[0] + matrix[1]);
matrix[3] = matrix[4] = 0;
matrix[5] = 0.213f - 0.213f * amount;
matrix[6] = 0.715f + 0.285f * amount;
matrix[7] = 1.f - (matrix[5] + matrix[6]);
matrix[8] = matrix[9] = 0;
matrix[10] = 0.213f - 0.213f * amount;
matrix[11] = 0.715f - 0.715f * amount;
matrix[12] = 1.f - (matrix[10] + matrix[11]);
matrix[13] = matrix[14] = 0;
matrix[15] = matrix[16] = matrix[17] = matrix[19] = 0;
matrix[18] = 1;
cc::FilterOperations filters;
filters.Append(cc::FilterOperation::CreateReferenceFilter(
sk_make_sp<cc::ColorFilterPaintFilter>(
cc::ColorFilter::MakeMatrix(matrix), nullptr)));
auto child_pass =
CreateTestRenderPass(child_pass_id, pass_rect, transform_to_root);
child_pass->filters = filters;
gfx::Transform quad_to_target_transform;
SharedQuadState* shared_state = CreateTestSharedQuadState(
quad_to_target_transform, viewport_rect, child_pass.get(), gfx::MaskFilterInfo());
shared_state->opacity = 0.5f;
gfx::Rect blue_rect(0, 0, this->device_viewport_size_.width(),
this->device_viewport_size_.height() / 2);
auto* blue = child_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
blue->SetNew(shared_state, blue_rect, blue_rect, SkColors::kBlue, false);
gfx::Rect yellow_rect(0, this->device_viewport_size_.height() / 2,
this->device_viewport_size_.width(),
this->device_viewport_size_.height() / 2);
auto* yellow = child_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
yellow->SetNew(shared_state, yellow_rect, yellow_rect, SkColors::kYellow,
false);
SharedQuadState* blank_state = CreateTestSharedQuadState(
quad_to_target_transform, viewport_rect, child_pass.get(), gfx::MaskFilterInfo());
auto* white = child_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
white->SetNew(blank_state, viewport_rect, viewport_rect, SkColors::kWhite,
false);
SharedQuadState* pass_shared_state =
CreateTestSharedQuadState(gfx::Transform(), pass_rect, root_pass.get(),
gfx::MaskFilterInfo());
auto* render_pass_quad =
root_pass->CreateAndAppendDrawQuad<AggregatedRenderPassDrawQuad>();
render_pass_quad->SetNew(pass_shared_state, pass_rect, pass_rect,
child_pass_id, kInvalidResourceId, gfx::RectF(),
gfx::Size(), gfx::RectF(pass_rect), false);
AggregatedRenderPassList pass_list;
pass_list.push_back(std::move(child_pass));
pass_list.push_back(std::move(root_pass));
// This test has alpha=254 for the software renderer vs. alpha=255 for the gl
// renderer so use a fuzzy comparator.
EXPECT_TRUE(this->RunPixelTest(
&pass_list, base::FilePath(FILE_PATH_LITERAL("blue_yellow_alpha.png")),
cc::FuzzyPixelOffByOneComparator()));
}
TEST_P(RendererPixelTest, FastPassSaturateFilter) {
gfx::Rect viewport_rect(this->device_viewport_size_);
AggregatedRenderPassId root_pass_id{1};
auto root_pass = CreateTestRootRenderPass(root_pass_id, viewport_rect);
AggregatedRenderPassId child_pass_id{2};
gfx::Rect pass_rect(this->device_viewport_size_);
gfx::Transform transform_to_root;
cc::FilterOperations filters;
filters.Append(cc::FilterOperation::CreateSaturateFilter(0.5f));
auto child_pass =
CreateTestRenderPass(child_pass_id, pass_rect, transform_to_root);
child_pass->filters = filters;
gfx::Transform quad_to_target_transform;
SharedQuadState* shared_state = CreateTestSharedQuadState(
quad_to_target_transform, viewport_rect, child_pass.get(), gfx::MaskFilterInfo());
shared_state->opacity = 0.5f;
gfx::Rect blue_rect(0, 0, this->device_viewport_size_.width(),
this->device_viewport_size_.height() / 2);
auto* blue = child_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
blue->SetNew(shared_state, blue_rect, blue_rect, SkColors::kBlue, false);
gfx::Rect yellow_rect(0, this->device_viewport_size_.height() / 2,
this->device_viewport_size_.width(),
this->device_viewport_size_.height() / 2);
auto* yellow = child_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
yellow->SetNew(shared_state, yellow_rect, yellow_rect, SkColors::kYellow,
false);
SharedQuadState* blank_state = CreateTestSharedQuadState(
quad_to_target_transform, viewport_rect, child_pass.get(), gfx::MaskFilterInfo());
auto* white = child_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
white->SetNew(blank_state, viewport_rect, viewport_rect, SkColors::kWhite,
false);
SharedQuadState* pass_shared_state =
CreateTestSharedQuadState(gfx::Transform(), pass_rect, root_pass.get(),
gfx::MaskFilterInfo());
auto* render_pass_quad =
root_pass->CreateAndAppendDrawQuad<AggregatedRenderPassDrawQuad>();
render_pass_quad->SetNew(pass_shared_state, pass_rect, pass_rect,
child_pass_id, kInvalidResourceId, gfx::RectF(),
gfx::Size(), gfx::RectF(pass_rect), false);
AggregatedRenderPassList pass_list;
pass_list.push_back(std::move(child_pass));
pass_list.push_back(std::move(root_pass));
// This test blends slightly differently with the software renderer vs. the gl
// renderer so use a fuzzy comparator.
EXPECT_TRUE(this->RunPixelTest(
&pass_list, base::FilePath(FILE_PATH_LITERAL("blue_yellow_alpha.png")),
cc::FuzzyPixelOffByOneComparator()));
}
TEST_P(RendererPixelTest, FastPassFilterChain) {
gfx::Rect viewport_rect(this->device_viewport_size_);
AggregatedRenderPassId root_pass_id{1};
auto root_pass = CreateTestRootRenderPass(root_pass_id, viewport_rect);
AggregatedRenderPassId child_pass_id{2};
gfx::Rect pass_rect(this->device_viewport_size_);
gfx::Transform transform_to_root;
cc::FilterOperations filters;
filters.Append(cc::FilterOperation::CreateGrayscaleFilter(1.f));
filters.Append(cc::FilterOperation::CreateBrightnessFilter(0.5f));
auto child_pass =
CreateTestRenderPass(child_pass_id, pass_rect, transform_to_root);
child_pass->filters = filters;
gfx::Transform quad_to_target_transform;
SharedQuadState* shared_state = CreateTestSharedQuadState(
quad_to_target_transform, viewport_rect, child_pass.get(), gfx::MaskFilterInfo());
shared_state->opacity = 0.5f;
gfx::Rect blue_rect(0, 0, this->device_viewport_size_.width(),
this->device_viewport_size_.height() / 2);
auto* blue = child_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
blue->SetNew(shared_state, blue_rect, blue_rect, SkColors::kBlue, false);
gfx::Rect yellow_rect(0, this->device_viewport_size_.height() / 2,
this->device_viewport_size_.width(),
this->device_viewport_size_.height() / 2);
auto* yellow = child_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
yellow->SetNew(shared_state, yellow_rect, yellow_rect, SkColors::kYellow,
false);
SharedQuadState* blank_state = CreateTestSharedQuadState(
quad_to_target_transform, viewport_rect, child_pass.get(), gfx::MaskFilterInfo());
auto* white = child_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
white->SetNew(blank_state, viewport_rect, viewport_rect, SkColors::kWhite,
false);
SharedQuadState* pass_shared_state =
CreateTestSharedQuadState(gfx::Transform(), pass_rect, root_pass.get(),
gfx::MaskFilterInfo());
auto* render_pass_quad =
root_pass->CreateAndAppendDrawQuad<AggregatedRenderPassDrawQuad>();
render_pass_quad->SetNew(pass_shared_state, pass_rect, pass_rect,
child_pass_id, kInvalidResourceId, gfx::RectF(),
gfx::Size(), gfx::RectF(pass_rect), false);
AggregatedRenderPassList pass_list;
pass_list.push_back(std::move(child_pass));
pass_list.push_back(std::move(root_pass));
// This test blends slightly differently with the software renderer vs. the gl
// renderer so use a fuzzy comparator.
EXPECT_TRUE(this->RunPixelTest(
&pass_list,
base::FilePath(FILE_PATH_LITERAL("blue_yellow_filter_chain.png")),
cc::FuzzyPixelOffByOneComparator()));
}
TEST_P(RendererPixelTest, FastPassColorFilterAlphaTranslation) {
gfx::Rect viewport_rect(this->device_viewport_size_);
AggregatedRenderPassId root_pass_id{1};
auto root_pass = CreateTestRootRenderPass(root_pass_id, viewport_rect);
AggregatedRenderPassId child_pass_id{2};
gfx::Rect pass_rect(this->device_viewport_size_);
gfx::Transform transform_to_root;
float matrix[20];
float amount = 0.5f;
matrix[0] = 0.213f + 0.787f * amount;
matrix[1] = 0.715f - 0.715f * amount;
matrix[2] = 1.f - (matrix[0] + matrix[1]);
matrix[3] = 0;
matrix[4] = 20.f / 255;
matrix[5] = 0.213f - 0.213f * amount;
matrix[6] = 0.715f + 0.285f * amount;
matrix[7] = 1.f - (matrix[5] + matrix[6]);
matrix[8] = 0;
matrix[9] = 200.f / 255;
matrix[10] = 0.213f - 0.213f * amount;
matrix[11] = 0.715f - 0.715f * amount;
matrix[12] = 1.f - (matrix[10] + matrix[11]);
matrix[13] = 0;
matrix[14] = 1.5f / 255;
matrix[15] = matrix[16] = matrix[17] = matrix[19] = 0;
matrix[18] = 1;
cc::FilterOperations filters;
filters.Append(cc::FilterOperation::CreateReferenceFilter(
sk_make_sp<cc::ColorFilterPaintFilter>(
cc::ColorFilter::MakeMatrix(matrix), nullptr)));
auto child_pass =
CreateTestRenderPass(child_pass_id, pass_rect, transform_to_root);
child_pass->filters = filters;
gfx::Transform quad_to_target_transform;
SharedQuadState* shared_state = CreateTestSharedQuadState(
quad_to_target_transform, viewport_rect, child_pass.get(), gfx::MaskFilterInfo());
shared_state->opacity = 0.5f;
gfx::Rect blue_rect(0, 0, this->device_viewport_size_.width(),
this->device_viewport_size_.height() / 2);
auto* blue = child_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
blue->SetNew(shared_state, blue_rect, blue_rect, SkColors::kBlue, false);
gfx::Rect yellow_rect(0, this->device_viewport_size_.height() / 2,
this->device_viewport_size_.width(),
this->device_viewport_size_.height() / 2);
auto* yellow = child_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
yellow->SetNew(shared_state, yellow_rect, yellow_rect, SkColors::kYellow,
false);
SharedQuadState* blank_state = CreateTestSharedQuadState(
quad_to_target_transform, viewport_rect, child_pass.get(), gfx::MaskFilterInfo());
auto* white = child_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
white->SetNew(blank_state, viewport_rect, viewport_rect, SkColors::kWhite,
false);
SharedQuadState* pass_shared_state =
CreateTestSharedQuadState(gfx::Transform(), pass_rect, root_pass.get(),
gfx::MaskFilterInfo());
auto* render_pass_quad =
root_pass->CreateAndAppendDrawQuad<AggregatedRenderPassDrawQuad>();
render_pass_quad->SetNew(pass_shared_state, pass_rect, pass_rect,
child_pass_id, kInvalidResourceId, gfx::RectF(),
gfx::Size(), gfx::RectF(pass_rect), false);
AggregatedRenderPassList pass_list;
pass_list.push_back(std::move(child_pass));
pass_list.push_back(std::move(root_pass));
// This test has alpha=254 for the software renderer vs. alpha=255 for the gl
// renderer so use a fuzzy comparator.
EXPECT_TRUE(this->RunPixelTest(
&pass_list,
base::FilePath(FILE_PATH_LITERAL("blue_yellow_alpha_translate.png")),
cc::FuzzyPixelOffByOneComparator()));
}
// Test that an RPDQ that has a filter that samples outside the output rect of
// the render pass can draw the filter contents even if the embedding RPDQ has
// an empty size.
TEST_P(RendererPixelTest, NonEmptyFilterClipRectOnEmptyRenderPassQuad) {
const gfx::Rect viewport_rect(this->device_viewport_size_);
// Add a non-zero offset to the RPDQ to avoid depending on a zero offset in
// the implementation.
const gfx::Vector2d rpdq_offset(20, 10);
const gfx::Rect empty_rpdq_rect;
AggregatedRenderPassList pass_list;
// Zero-sized render pass with a reference filter that fills an area green.
AggregatedRenderPassId child_pass_id{2};
{
cc::FilterOperations filters;
const SkRect filter_clip_rect = gfx::RectToSkRect(
gfx::Rect(gfx::Point() - rpdq_offset, this->device_viewport_size_));
filters.Append(cc::FilterOperation::CreateReferenceFilter(
sk_make_sp<cc::ColorFilterPaintFilter>(
cc::ColorFilter::MakeBlend(SkColors::kGreen, SkBlendMode::kSrc),
nullptr, &filter_clip_rect)));
auto child_pass =
CreateTestRenderPass(child_pass_id, empty_rpdq_rect,
/*transform_to_root_target=*/
gfx::Transform::MakeTranslation(rpdq_offset));
child_pass->filters = filters;
pass_list.push_back(std::move(child_pass));
}
// Root pass that embeds the child pass as a zero-sized RPDQ.
{
AggregatedRenderPassId root_pass_id{1};
auto root_pass = CreateTestRootRenderPass(root_pass_id, viewport_rect);
CreateTestRenderPassDrawQuad(
CreateTestSharedQuadState(
/*quad_to_target_transform=*/gfx::Transform::MakeTranslation(
rpdq_offset),
gfx::Rect(gfx::Point() - rpdq_offset, this->device_viewport_size_),
root_pass.get(), gfx::MaskFilterInfo()),
empty_rpdq_rect, child_pass_id, root_pass.get());
// Add a red background to make it clear when the test is failing.
auto* color_quad = root_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
color_quad->SetNew(
CreateTestSharedQuadState(gfx::Transform(), viewport_rect,
root_pass.get(), gfx::MaskFilterInfo()),
viewport_rect, viewport_rect, SkColors::kRed, false);
pass_list.push_back(std::move(root_pass));
}
EXPECT_TRUE(this->RunPixelTest(&pass_list,
base::FilePath(FILE_PATH_LITERAL("green.png")),
cc::AlphaDiscardingExactPixelComparator()));
}
TEST_P(RendererPixelTest, EnlargedRenderPassTexture) {
gfx::Rect viewport_rect(this->device_viewport_size_);
AggregatedRenderPassId root_pass_id{1};
auto root_pass = CreateTestRootRenderPass(root_pass_id, viewport_rect);
AggregatedRenderPassId child_pass_id{2};
gfx::Rect pass_rect(this->device_viewport_size_);
gfx::Transform transform_to_root;
auto child_pass =
CreateTestRenderPass(child_pass_id, pass_rect, transform_to_root);
gfx::Transform quad_to_target_transform;
SharedQuadState* shared_state = CreateTestSharedQuadState(
quad_to_target_transform, viewport_rect, child_pass.get(), gfx::MaskFilterInfo());
gfx::Rect blue_rect(0, 0, this->device_viewport_size_.width(),
this->device_viewport_size_.height() / 2);
auto* blue = child_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
blue->SetNew(shared_state, blue_rect, blue_rect, SkColors::kBlue, false);
gfx::Rect yellow_rect(0, this->device_viewport_size_.height() / 2,
this->device_viewport_size_.width(),
this->device_viewport_size_.height() / 2);
auto* yellow = child_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
yellow->SetNew(shared_state, yellow_rect, yellow_rect, SkColors::kYellow,
false);
SharedQuadState* pass_shared_state =
CreateTestSharedQuadState(gfx::Transform(), pass_rect, root_pass.get(),
gfx::MaskFilterInfo());
CreateTestRenderPassDrawQuad(pass_shared_state, pass_rect, child_pass_id,
root_pass.get());
AggregatedRenderPassList pass_list;
pass_list.push_back(std::move(child_pass));
pass_list.push_back(std::move(root_pass));
this->renderer_->SetEnlargePassTextureAmountForTesting(gfx::Size(50, 75));
EXPECT_TRUE(this->RunPixelTest(
&pass_list, base::FilePath(FILE_PATH_LITERAL("blue_yellow.png")),
cc::AlphaDiscardingFuzzyPixelOffByOneComparator()));
}
TEST_P(RendererPixelTest, EnlargedRenderPassTextureWithAntiAliasing) {
gfx::Rect viewport_rect(this->device_viewport_size_);
AggregatedRenderPassId root_pass_id{1};
auto root_pass = CreateTestRootRenderPass(root_pass_id, viewport_rect);
AggregatedRenderPassId child_pass_id{2};
gfx::Rect pass_rect(this->device_viewport_size_);
gfx::Transform transform_to_root;
auto child_pass =
CreateTestRenderPass(child_pass_id, pass_rect, transform_to_root);
gfx::Transform quad_to_target_transform;
SharedQuadState* shared_state = CreateTestSharedQuadState(
quad_to_target_transform, viewport_rect, child_pass.get(), gfx::MaskFilterInfo());
gfx::Rect blue_rect(0, 0, this->device_viewport_size_.width(),
this->device_viewport_size_.height() / 2);
auto* blue = child_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
blue->SetNew(shared_state, blue_rect, blue_rect, SkColors::kBlue, false);
gfx::Rect yellow_rect(0, this->device_viewport_size_.height() / 2,
this->device_viewport_size_.width(),
this->device_viewport_size_.height() / 2);
auto* yellow = child_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
yellow->SetNew(shared_state, yellow_rect, yellow_rect, SkColors::kYellow,
false);
gfx::Transform aa_transform;
aa_transform.Translate(0.5, 0.0);
SharedQuadState* pass_shared_state =
CreateTestSharedQuadState(aa_transform, pass_rect, root_pass.get(),
gfx::MaskFilterInfo());
CreateTestRenderPassDrawQuad(pass_shared_state, pass_rect, child_pass_id,
root_pass.get());
SharedQuadState* root_shared_state = CreateTestSharedQuadState(
gfx::Transform(), viewport_rect, root_pass.get(), gfx::MaskFilterInfo());
auto* background = root_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
background->SetNew(root_shared_state, gfx::Rect(this->device_viewport_size_),
gfx::Rect(this->device_viewport_size_), SkColors::kWhite,
false);
AggregatedRenderPassList pass_list;
pass_list.push_back(std::move(child_pass));
pass_list.push_back(std::move(root_pass));
this->renderer_->SetEnlargePassTextureAmountForTesting(gfx::Size(50, 75));
EXPECT_TRUE(this->RunPixelTest(
&pass_list,
base::FilePath(FILE_PATH_LITERAL("blue_yellow_anti_aliasing.png")),
cc::FuzzyPixelComparator()
.DiscardAlpha()
.SetErrorPixelsPercentageLimit(100.f)
.SetAvgAbsErrorLimit(5.f)
.SetAbsErrorLimit(7)));
}
// This tests the case where we have a RenderPass with a mask, but the quad
// for the masked surface does not include the full surface texture.
TEST_P(RendererPixelTest, RenderPassAndMaskWithPartialQuad) {
gfx::Rect viewport_rect(this->device_viewport_size_);
AggregatedRenderPassId root_pass_id{1};
auto root_pass = CreateTestRootRenderPass(root_pass_id, viewport_rect);
SharedQuadState* root_pass_shared_state = CreateTestSharedQuadState(
gfx::Transform(), viewport_rect, root_pass.get(), gfx::MaskFilterInfo());
AggregatedRenderPassId child_pass_id{2};
gfx::Transform transform_to_root;
auto child_pass =
CreateTestRenderPass(child_pass_id, viewport_rect, transform_to_root);
SharedQuadState* child_pass_shared_state = CreateTestSharedQuadState(
gfx::Transform(), viewport_rect, child_pass.get(), gfx::MaskFilterInfo());
// The child render pass is just a green box.
static const SkColor4f kCSSGreen = SkColor4f::FromColor(0xff008000);
auto* green = child_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
green->SetNew(child_pass_shared_state, viewport_rect, viewport_rect,
kCSSGreen, false);
// Make a mask.
gfx::Rect mask_rect = viewport_rect;
SkBitmap bitmap;
bitmap.allocPixels(
SkImageInfo::MakeN32Premul(mask_rect.width(), mask_rect.height()));
cc::SkiaPaintCanvas canvas(bitmap);
cc::PaintFlags flags;
flags.setStyle(cc::PaintFlags::kStroke_Style);
flags.setStrokeWidth(SkIntToScalar(4));
flags.setColor(SkColors::kWhite);
canvas.clear(SkColors::kTransparent);
gfx::Rect rect = mask_rect;
while (!rect.IsEmpty()) {
rect.Inset(gfx::Insets::TLBR(6, 6, 4, 4));
canvas.drawRect(
SkRect::MakeXYWH(rect.x(), rect.y(), rect.width(), rect.height()),
flags);
rect.Inset(gfx::Insets::TLBR(6, 6, 4, 4));
}
ResourceId mask_resource_id;
if (!is_software_renderer()) {
mask_resource_id = CreateGpuResource(
this->child_context_provider_, this->child_resource_provider_.get(),
mask_rect.size(), SinglePlaneFormat::kRGBA_8888, kPremul_SkAlphaType,
gfx::ColorSpace(), MakePixelSpan(bitmap));
} else {
mask_resource_id = this->AllocateAndFillSoftwareResource(
this->child_context_provider_, mask_rect.size(), bitmap);
}
// Return the mapped resource id.
std::unordered_map<ResourceId, ResourceId, ResourceIdHasher> resource_map =
cc::SendResourceAndGetChildToParentMap(
{mask_resource_id}, this->resource_provider_.get(),
this->child_resource_provider_.get(),
this->child_context_provider_->SharedImageInterface());
ResourceId mapped_mask_resource_id = resource_map[mask_resource_id];
// This AggregatedRenderPassDrawQuad does not include the full |viewport_rect|
// which is the size of the child render pass.
gfx::Rect sub_rect = gfx::Rect(50, 50, 200, 100);
EXPECT_NE(sub_rect.x(), child_pass->output_rect.x());
EXPECT_NE(sub_rect.y(), child_pass->output_rect.y());
EXPECT_NE(sub_rect.right(), child_pass->output_rect.right());
EXPECT_NE(sub_rect.bottom(), child_pass->output_rect.bottom());
// Set up a mask on the AggregatedRenderPassDrawQuad.
auto* mask_quad =
root_pass->CreateAndAppendDrawQuad<AggregatedRenderPassDrawQuad>();
mask_quad->SetNew(
root_pass_shared_state, sub_rect, sub_rect, child_pass_id,
mapped_mask_resource_id,
gfx::ScaleRect(gfx::RectF(sub_rect), 2.f / mask_rect.width(),
2.f / mask_rect.height()), // mask_uv_rect
gfx::Size(mask_rect.size()), // mask_texture_size
gfx::RectF(sub_rect), // tex_coord_rect
false); // force_anti_aliasing_off
// White background behind the masked render pass.
auto* white = root_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
white->SetNew(root_pass_shared_state, viewport_rect, viewport_rect,
SkColors::kWhite, false);
AggregatedRenderPassList pass_list;
pass_list.push_back(std::move(child_pass));
pass_list.push_back(std::move(root_pass));
EXPECT_TRUE(this->RunPixelTest(
&pass_list, base::FilePath(FILE_PATH_LITERAL("mask_bottom_right.png")),
cc::AlphaDiscardingExactPixelComparator()));
}
// This tests the case where we have a RenderPass with a mask, but the quad
// for the masked surface does not include the full surface texture.
TEST_P(RendererPixelTest, RenderPassAndMaskWithPartialQuad2) {
gfx::Rect viewport_rect(this->device_viewport_size_);
AggregatedRenderPassId root_pass_id{1};
auto root_pass = CreateTestRootRenderPass(root_pass_id, viewport_rect);
SharedQuadState* root_pass_shared_state = CreateTestSharedQuadState(
gfx::Transform(), viewport_rect, root_pass.get(), gfx::MaskFilterInfo());
AggregatedRenderPassId child_pass_id{2};
gfx::Transform transform_to_root;
auto child_pass =
CreateTestRenderPass(child_pass_id, viewport_rect, transform_to_root);
SharedQuadState* child_pass_shared_state = CreateTestSharedQuadState(
gfx::Transform(), viewport_rect, child_pass.get(), gfx::MaskFilterInfo());
// The child render pass is just a green box.
static const SkColor4f kCSSGreen = SkColor4f::FromColor(0xff008000);
auto* green = child_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
green->SetNew(child_pass_shared_state, viewport_rect, viewport_rect,
kCSSGreen, false);
// Make a mask.
gfx::Rect mask_rect = viewport_rect;
SkBitmap bitmap;
bitmap.allocPixels(
SkImageInfo::MakeN32Premul(mask_rect.width(), mask_rect.height()));
cc::SkiaPaintCanvas canvas(bitmap);
cc::PaintFlags flags;
flags.setStyle(cc::PaintFlags::kStroke_Style);
flags.setStrokeWidth(SkIntToScalar(4));
flags.setColor(SkColors::kWhite);
canvas.clear(SkColors::kTransparent);
gfx::Rect rect = mask_rect;
while (!rect.IsEmpty()) {
rect.Inset(gfx::Insets::TLBR(6, 6, 4, 4));
canvas.drawRect(
SkRect::MakeXYWH(rect.x(), rect.y(), rect.width(), rect.height()),
flags);
rect.Inset(gfx::Insets::TLBR(6, 6, 4, 4));
}
ResourceId mask_resource_id;
if (!is_software_renderer()) {
mask_resource_id = CreateGpuResource(
this->child_context_provider_, this->child_resource_provider_.get(),
mask_rect.size(), SinglePlaneFormat::kRGBA_8888, kPremul_SkAlphaType,
gfx::ColorSpace(), MakePixelSpan(bitmap));
} else {
mask_resource_id = this->AllocateAndFillSoftwareResource(
this->child_context_provider_, mask_rect.size(), bitmap);
}
// Return the mapped resource id.
std::unordered_map<ResourceId, ResourceId, ResourceIdHasher> resource_map =
cc::SendResourceAndGetChildToParentMap(
{mask_resource_id}, this->resource_provider_.get(),
this->child_resource_provider_.get(),
this->child_context_provider_->SharedImageInterface());
ResourceId mapped_mask_resource_id = resource_map[mask_resource_id];
// This AggregatedRenderPassDrawQuad does not include the full |viewport_rect|
// which is the size of the child render pass.
gfx::Rect sub_rect = gfx::Rect(50, 20, 200, 60);
EXPECT_NE(sub_rect.x(), child_pass->output_rect.x());
EXPECT_NE(sub_rect.y(), child_pass->output_rect.y());
EXPECT_NE(sub_rect.right(), child_pass->output_rect.right());
EXPECT_NE(sub_rect.bottom(), child_pass->output_rect.bottom());
// Set up a mask on the AggregatedRenderPassDrawQuad.
auto* mask_quad =
root_pass->CreateAndAppendDrawQuad<AggregatedRenderPassDrawQuad>();
mask_quad->SetNew(
root_pass_shared_state, sub_rect, sub_rect, child_pass_id,
mapped_mask_resource_id,
gfx::ScaleRect(gfx::RectF(sub_rect), 2.f / mask_rect.width(),
2.f / mask_rect.height()), // mask_uv_rect
gfx::Size(mask_rect.size()), // mask_texture_size
gfx::RectF(sub_rect), // tex_coord_rect
false); // force_anti_aliasing_off
// White background behind the masked render pass.
auto* white = root_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
white->SetNew(root_pass_shared_state, viewport_rect, viewport_rect,
SkColors::kWhite, false);
AggregatedRenderPassList pass_list;
pass_list.push_back(std::move(child_pass));
pass_list.push_back(std::move(root_pass));
EXPECT_TRUE(this->RunPixelTest(
&pass_list, base::FilePath(FILE_PATH_LITERAL("mask_middle.png")),
cc::AlphaDiscardingExactPixelComparator()));
}
TEST_P(RendererPixelTest, RenderPassAndMaskForRoundedCorner) {
gfx::Rect viewport_rect(this->device_viewport_size_);
constexpr int kInset = 20;
constexpr int kCornerRadius = 20;
AggregatedRenderPassId root_pass_id{1};
auto root_pass = CreateTestRootRenderPass(root_pass_id, viewport_rect);
SharedQuadState* root_pass_shared_state = CreateTestSharedQuadState(
gfx::Transform(), viewport_rect, root_pass.get(), gfx::MaskFilterInfo());
AggregatedRenderPassId child_pass_id{2};
gfx::Transform transform_to_root;
auto child_pass =
CreateTestRenderPass(child_pass_id, viewport_rect, transform_to_root);
SharedQuadState* child_pass_shared_state = CreateTestSharedQuadState(
gfx::Transform(), viewport_rect, child_pass.get(), gfx::MaskFilterInfo());
// The child render pass is just a blue box.
auto* blue = child_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
blue->SetNew(child_pass_shared_state, viewport_rect, viewport_rect,
SkColors::kBlue, false);
// Make a mask.
gfx::Rect mask_rect = viewport_rect;
SkBitmap bitmap;
bitmap.allocPixels(
SkImageInfo::MakeN32Premul(mask_rect.width(), mask_rect.height()));
cc::SkiaPaintCanvas canvas(bitmap);
cc::PaintFlags flags;
flags.setStyle(cc::PaintFlags::kFill_Style);
flags.setColor(SkColors::kWhite);
flags.setAntiAlias(true);
canvas.clear(SkColors::kTransparent);
gfx::Rect rounded_corner_rect = mask_rect;
rounded_corner_rect.Inset(kInset);
SkRRect rounded_corner = SkRRect::MakeRectXY(
gfx::RectToSkRect(rounded_corner_rect), kCornerRadius, kCornerRadius);
canvas.drawRRect(rounded_corner, flags);
ResourceId mask_resource_id;
if (!is_software_renderer()) {
mask_resource_id = CreateGpuResource(
this->child_context_provider_, this->child_resource_provider_.get(),
mask_rect.size(), SinglePlaneFormat::kRGBA_8888, kPremul_SkAlphaType,
gfx::ColorSpace(), MakePixelSpan(bitmap));
} else {
mask_resource_id = this->AllocateAndFillSoftwareResource(
this->child_context_provider_, mask_rect.size(), bitmap);
}
// Return the mapped resource id.
std::unordered_map<ResourceId, ResourceId, ResourceIdHasher> resource_map =
cc::SendResourceAndGetChildToParentMap(
{mask_resource_id}, this->resource_provider_.get(),
this->child_resource_provider_.get(),
this->child_context_provider_->SharedImageInterface());
ResourceId mapped_mask_resource_id = resource_map[mask_resource_id];
// Set up a mask on the AggregatedRenderPassDrawQuad.
auto* mask_quad =
root_pass->CreateAndAppendDrawQuad<AggregatedRenderPassDrawQuad>();
mask_quad->SetNew(
root_pass_shared_state, viewport_rect, viewport_rect, child_pass_id,
mapped_mask_resource_id,
gfx::ScaleRect(gfx::RectF(viewport_rect), 1.f / mask_rect.width(),
1.f / mask_rect.height()), // mask_uv_rect
gfx::Size(mask_rect.size()), // mask_texture_size
gfx::RectF(viewport_rect), // tex_coord_rect
false); // force_anti_aliasing_off
// White background behind the masked render pass.
auto* white = root_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
white->SetNew(root_pass_shared_state, viewport_rect, viewport_rect,
SkColors::kWhite, false);
AggregatedRenderPassList pass_list;
pass_list.push_back(std::move(child_pass));
pass_list.push_back(std::move(root_pass));
// The rounded corners generated by masks should be very close to the rounded
// corners generated by the fragment shader approach. The percentage of pixel
// mismatch is around 0.52%.
EXPECT_TRUE(this->RunPixelTest(
&pass_list,
base::FilePath(FILE_PATH_LITERAL("rounded_corner_simple.png")),
cc::FuzzyPixelComparator().DiscardAlpha().SetErrorPixelsPercentageLimit(
0.6f)));
}
TEST_P(RendererPixelTest, RenderPassAndMaskForRoundedCornerMultiRadii) {
gfx::Rect viewport_rect(this->device_viewport_size_);
constexpr int kInset = 20;
const SkVector kCornerRadii[4] = {
SkVector::Make(5.0, 5.0),
SkVector::Make(15.0, 15.0),
SkVector::Make(25.0, 25.0),
SkVector::Make(35.0, 35.0),
};
AggregatedRenderPassId root_pass_id{1};
auto root_pass = CreateTestRootRenderPass(root_pass_id, viewport_rect);
SharedQuadState* root_pass_shared_state = CreateTestSharedQuadState(
gfx::Transform(), viewport_rect, root_pass.get(), gfx::MaskFilterInfo());
AggregatedRenderPassId child_pass_id{2};
gfx::Transform transform_to_root;
auto child_pass =
CreateTestRenderPass(child_pass_id, viewport_rect, transform_to_root);
SharedQuadState* child_pass_shared_state = CreateTestSharedQuadState(
gfx::Transform(), viewport_rect, child_pass.get(), gfx::MaskFilterInfo());
// The child render pass is half a blue box and other half yellow box.
gfx::Rect blue_rect(0, 0, this->device_viewport_size_.width(),
this->device_viewport_size_.height() / 2);
auto* blue = child_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
blue->SetNew(child_pass_shared_state, blue_rect, blue_rect, SkColors::kBlue,
false);
gfx::Rect yellow_rect(0, this->device_viewport_size_.height() / 2,
this->device_viewport_size_.width(),
this->device_viewport_size_.height() / 2);
auto* yellow = child_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
yellow->SetNew(child_pass_shared_state, yellow_rect, yellow_rect,
SkColors::kYellow, false);
// Make a mask.
gfx::Rect mask_rect = viewport_rect;
SkBitmap bitmap;
bitmap.allocPixels(
SkImageInfo::MakeN32Premul(mask_rect.width(), mask_rect.height()));
cc::SkiaPaintCanvas canvas(bitmap);
cc::PaintFlags flags;
flags.setStyle(cc::PaintFlags::kFill_Style);
flags.setColor(SkColors::kWhite);
flags.setAntiAlias(true);
canvas.clear(SkColors::kTransparent);
gfx::Rect rounded_corner_rect = mask_rect;
rounded_corner_rect.Inset(kInset);
SkRRect rounded_corner =
SkRRect::MakeRect(gfx::RectToSkRect(rounded_corner_rect));
rounded_corner.setRectRadii(rounded_corner.rect(), kCornerRadii);
canvas.drawRRect(rounded_corner, flags);
ResourceId mask_resource_id;
if (!is_software_renderer()) {
mask_resource_id = CreateGpuResource(
this->child_context_provider_, this->child_resource_provider_.get(),
mask_rect.size(), SinglePlaneFormat::kRGBA_8888, kPremul_SkAlphaType,
gfx::ColorSpace(), MakePixelSpan(bitmap));
} else {
mask_resource_id = this->AllocateAndFillSoftwareResource(
this->child_context_provider_, mask_rect.size(), bitmap);
}
// Return the mapped resource id.
std::unordered_map<ResourceId, ResourceId, ResourceIdHasher> resource_map =
cc::SendResourceAndGetChildToParentMap(
{mask_resource_id}, this->resource_provider_.get(),
this->child_resource_provider_.get(),
this->child_context_provider_->SharedImageInterface());
ResourceId mapped_mask_resource_id = resource_map[mask_resource_id];
// Set up a mask on the AggregatedRenderPassDrawQuad.
auto* mask_quad =
root_pass->CreateAndAppendDrawQuad<AggregatedRenderPassDrawQuad>();
mask_quad->SetNew(
root_pass_shared_state, viewport_rect, viewport_rect, child_pass_id,
mapped_mask_resource_id,
gfx::ScaleRect(gfx::RectF(viewport_rect), 1.f / mask_rect.width(),
1.f / mask_rect.height()), // mask_uv_rect
gfx::Size(mask_rect.size()), // mask_texture_size
gfx::RectF(viewport_rect), // tex_coord_rect
false); // force_anti_aliasing_off
mask_quad->SetFilters(/*scale=*/gfx::Vector2dF(),
/*origin=*/gfx::PointF(),
/*backdrop_quality=*/1.0f);
// White background behind the masked render pass.
auto* white = root_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
white->SetNew(root_pass_shared_state, viewport_rect, viewport_rect,
SkColors::kWhite, false);
AggregatedRenderPassList pass_list;
pass_list.push_back(std::move(child_pass));
pass_list.push_back(std::move(root_pass));
EXPECT_TRUE(this->RunPixelTest(
&pass_list,
base::FilePath(FILE_PATH_LITERAL("rounded_corner_multi_radii.png")),
cc::FuzzyPixelComparator().DiscardAlpha().SetErrorPixelsPercentageLimit(
0.6f)));
}
class RendererPixelTestWithBackdropFilter : public VizPixelTestWithParam {
protected:
void SetUp() override {
VizPixelTestWithParam::SetUp();
filter_pass_layer_rect_ = gfx::Rect(device_viewport_size_);
filter_pass_layer_rect_.Inset(gfx::Insets::TLBR(14, 12, 18, 16));
backdrop_filter_bounds_ =
SkPath::Rect(gfx::RectToSkRect(filter_pass_layer_rect_));
}
void SetUpRenderPassList() {
gfx::Rect device_viewport_rect(this->device_viewport_size_);
AggregatedRenderPassId root_id{1};
auto root_pass = CreateTestRootRenderPass(root_id, device_viewport_rect);
root_pass->has_transparent_background = false;
gfx::Transform identity_quad_to_target_transform;
AggregatedRenderPassId filter_pass_id{2};
gfx::Transform transform_to_root;
auto filter_pass = CreateTestRenderPass(
filter_pass_id, filter_pass_layer_rect_, transform_to_root);
filter_pass->backdrop_filters = this->backdrop_filters_;
filter_pass->backdrop_filter_bounds = this->backdrop_filter_bounds_;
// A non-visible quad in the filtering render pass.
{
SharedQuadState* shared_state = CreateTestSharedQuadState(
identity_quad_to_target_transform, filter_pass_layer_rect_,
filter_pass.get(), gfx::MaskFilterInfo());
auto* color_quad =
filter_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
color_quad->SetNew(shared_state, filter_pass_layer_rect_,
filter_pass_layer_rect_, SkColors::kTransparent,
false);
}
ResourceId mapped_mask_resource_id(0);
gfx::RectF mask_uv_rect;
gfx::Size mask_texture_size;
if (include_backdrop_mask_) {
// Make a mask.
gfx::Rect viewport_rect(this->device_viewport_size_);
constexpr int kInset = 20;
const SkVector kCornerRadii[4] = {
SkVector::Make(5.0, 5.0),
SkVector::Make(15.0, 15.0),
SkVector::Make(25.0, 25.0),
SkVector::Make(35.0, 35.0),
};
gfx::Rect mask_rect = viewport_rect;
SkBitmap bitmap;
bitmap.allocPixels(
SkImageInfo::MakeN32Premul(mask_rect.width(), mask_rect.height()));
cc::SkiaPaintCanvas canvas(bitmap);
cc::PaintFlags flags;
flags.setStyle(cc::PaintFlags::kFill_Style);
flags.setColor(SkColors::kWhite);
flags.setAntiAlias(true);
canvas.clear(SkColors::kTransparent);
gfx::Rect rounded_corner_rect = mask_rect;
rounded_corner_rect.Inset(kInset);
SkRRect rounded_corner =
SkRRect::MakeRect(gfx::RectToSkRect(rounded_corner_rect));
rounded_corner.setRectRadii(rounded_corner.rect(), kCornerRadii);
canvas.drawRRect(rounded_corner, flags);
ResourceId mask_resource_id;
if (!is_software_renderer()) {
mask_resource_id = CreateGpuResource(
this->child_context_provider_, this->child_resource_provider_.get(),
mask_rect.size(), SinglePlaneFormat::kRGBA_8888,
kPremul_SkAlphaType, gfx::ColorSpace(), MakePixelSpan(bitmap));
} else {
mask_resource_id = this->AllocateAndFillSoftwareResource(
this->child_context_provider_, mask_rect.size(), bitmap);
}
// Return the mapped resource id.
std::unordered_map<ResourceId, ResourceId, ResourceIdHasher>
resource_map = cc::SendResourceAndGetChildToParentMap(
{mask_resource_id}, this->resource_provider_.get(),
this->child_resource_provider_.get(),
this->child_context_provider_->SharedImageInterface());
mapped_mask_resource_id = resource_map[mask_resource_id];
mask_uv_rect =
gfx::ScaleRect(gfx::RectF(viewport_rect), 1.f / mask_rect.width(),
1.f / mask_rect.height()), // mask_uv_rect
mask_texture_size = gfx::Size(mask_rect.size());
}
{
SharedQuadState* shared_state = CreateTestSharedQuadState(
filter_pass_to_target_transform_, filter_pass_layer_rect_,
filter_pass.get(), gfx::MaskFilterInfo());
auto* filter_pass_quad =
root_pass->CreateAndAppendDrawQuad<AggregatedRenderPassDrawQuad>();
filter_pass_quad->SetNew(shared_state, filter_pass_layer_rect_,
filter_pass_layer_rect_, filter_pass_id,
mapped_mask_resource_id, mask_uv_rect,
mask_texture_size,
gfx::RectF(), // tex_coord_rect
false); // force_anti_aliasing_off
}
const int kColumnWidth = device_viewport_rect.width() / 3;
gfx::Rect left_rect = gfx::Rect(0, 0, kColumnWidth, 20);
while (left_rect.y() < device_viewport_rect.height()) {
SharedQuadState* shared_state = CreateTestSharedQuadState(
identity_quad_to_target_transform, left_rect, root_pass.get(),
gfx::MaskFilterInfo());
auto* color_quad =
root_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
color_quad->SetNew(shared_state, left_rect, left_rect, SkColors::kGreen,
false);
left_rect += gfx::Vector2d(0, left_rect.height() + 1);
}
gfx::Rect middle_rect = gfx::Rect(kColumnWidth + 1, 0, kColumnWidth, 20);
while (middle_rect.y() < device_viewport_rect.height()) {
SharedQuadState* shared_state = CreateTestSharedQuadState(
identity_quad_to_target_transform, middle_rect, root_pass.get(),
gfx::MaskFilterInfo());
auto* color_quad =
root_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
color_quad->SetNew(shared_state, middle_rect, middle_rect, SkColors::kRed,
false);
middle_rect += gfx::Vector2d(0, middle_rect.height() + 1);
}
gfx::Rect right_rect =
gfx::Rect((kColumnWidth + 1) * 2, 0, kColumnWidth, 20);
while (right_rect.y() < device_viewport_rect.height()) {
SharedQuadState* shared_state = CreateTestSharedQuadState(
identity_quad_to_target_transform, right_rect, root_pass.get(),
gfx::MaskFilterInfo());
auto* color_quad =
root_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
color_quad->SetNew(shared_state, right_rect, right_rect, SkColors::kBlue,
false);
right_rect += gfx::Vector2d(0, right_rect.height() + 1);
}
SharedQuadState* shared_state = CreateTestSharedQuadState(
identity_quad_to_target_transform, device_viewport_rect,
root_pass.get(), gfx::MaskFilterInfo());
auto* background_quad =
root_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
background_quad->SetNew(shared_state, device_viewport_rect,
device_viewport_rect, SkColors::kWhite, false);
pass_list_.push_back(std::move(filter_pass));
pass_list_.push_back(std::move(root_pass));
}
AggregatedRenderPassList pass_list_;
cc::FilterOperations backdrop_filters_;
std::optional<SkPath> backdrop_filter_bounds_;
bool include_backdrop_mask_ = false;
gfx::Transform filter_pass_to_target_transform_;
gfx::Rect filter_pass_layer_rect_;
};
INSTANTIATE_TEST_SUITE_P(,
RendererPixelTestWithBackdropFilter,
testing::ValuesIn(GetRendererTypes()),
testing::PrintToStringParamName());
TEST_P(RendererPixelTestWithBackdropFilter, ZoomFilter) {
backdrop_filters_.Append(cc::FilterOperation::CreateZoomFilter(2.0f, 20));
SetUpRenderPassList();
EXPECT_TRUE(RunPixelTest(
&pass_list_,
base::FilePath(FILE_PATH_LITERAL("backdrop_filter_zoom.png")),
cc::ExactPixelComparator()));
}
TEST_P(RendererPixelTestWithBackdropFilter, OffsetFilter) {
backdrop_filters_.Append(
cc::FilterOperation::CreateOffsetFilter(gfx::Point(5, 5)));
SetUpRenderPassList();
base::FilePath expected_path(FILE_PATH_LITERAL("backdrop_filter_offset.png"));
EXPECT_TRUE(
RunPixelTest(&pass_list_, expected_path, cc::ExactPixelComparator()));
}
TEST_P(RendererPixelTestWithBackdropFilter, InvertFilter) {
backdrop_filters_.Append(cc::FilterOperation::CreateInvertFilter(1.f));
SetUpRenderPassList();
EXPECT_TRUE(RunPixelTest(
&pass_list_, base::FilePath(FILE_PATH_LITERAL("backdrop_filter.png")),
cc::AlphaDiscardingExactPixelComparator()));
}
TEST_P(RendererPixelTestWithBackdropFilter, InvertFilterWithMask) {
backdrop_filters_.Append(cc::FilterOperation::CreateInvertFilter(1.f));
include_backdrop_mask_ = true;
SetUpRenderPassList();
base::FilePath expected_path(
is_software_renderer()
? FILE_PATH_LITERAL("backdrop_filter_masked_sw.png")
: FILE_PATH_LITERAL("backdrop_filter_masked.png"));
EXPECT_TRUE(RunPixelTest(&pass_list_, expected_path,
cc::FuzzyPixelOffByOneComparator()));
}
// Software renderer does not support anti-aliased edges.
TEST_P(GPURendererPixelTest, AntiAliasing) {
gfx::Rect rect(this->device_viewport_size_);
AggregatedRenderPassId id{1};
auto pass = CreateTestRootRenderPass(id, rect);
gfx::Transform red_quad_to_target_transform;
red_quad_to_target_transform.Rotate(10);
SharedQuadState* red_shared_state =
CreateTestSharedQuadState(red_quad_to_target_transform, rect, pass.get(),
gfx::MaskFilterInfo());
auto* red = pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
red->SetNew(red_shared_state, rect, rect, SkColors::kRed, false);
gfx::Transform yellow_quad_to_target_transform;
yellow_quad_to_target_transform.Rotate(5);
SharedQuadState* yellow_shared_state = CreateTestSharedQuadState(
yellow_quad_to_target_transform, rect, pass.get(), gfx::MaskFilterInfo());
auto* yellow = pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
yellow->SetNew(yellow_shared_state, rect, rect, SkColors::kYellow, false);
gfx::Transform blue_quad_to_target_transform;
SharedQuadState* blue_shared_state =
CreateTestSharedQuadState(blue_quad_to_target_transform, rect, pass.get(),
gfx::MaskFilterInfo());
auto* blue = pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
blue->SetNew(blue_shared_state, rect, rect, SkColors::kBlue, false);
AggregatedRenderPassList pass_list;
pass_list.push_back(std::move(pass));
base::FilePath baseline =
base::FilePath(FILE_PATH_LITERAL("anti_aliasing_.png"))
.InsertBeforeExtensionASCII(this->renderer_str());
if (renderer_type() == RendererType::kSkiaGL && IsANGLEMetal()) {
baseline = baseline.InsertBeforeExtensionASCII(kANGLEMetalStr);
}
EXPECT_TRUE(this->RunPixelTest(
&pass_list, baseline, cc::AlphaDiscardingFuzzyPixelOffByOneComparator()));
}
// Software renderer does not support anti-aliased edges.
TEST_P(GPURendererPixelTest, AntiAliasingPerspective) {
gfx::Rect rect(this->device_viewport_size_);
auto pass = CreateTestRootRenderPass(AggregatedRenderPassId{1}, rect);
gfx::Rect red_rect(0, 0, 180, 500);
auto red_quad_to_target_transform = gfx::Transform::RowMajor(
1.0f, 2.4520f, 10.6206f, 19.0f, 0.0f, 0.3528f, 5.9737f, 9.5f, 0.0f,
-0.2250f, -0.9744f, 0.0f, 0.0f, 0.0225f, 0.0974f, 1.0f);
SharedQuadState* red_shared_state = CreateTestSharedQuadState(
red_quad_to_target_transform, red_rect, pass.get(), gfx::MaskFilterInfo());
auto* red = pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
red->SetNew(red_shared_state, red_rect, red_rect, SkColors::kRed, false);
gfx::Rect green_rect(19, 7, 180, 10);
SharedQuadState* green_shared_state =
CreateTestSharedQuadState(gfx::Transform(), green_rect, pass.get(),
gfx::MaskFilterInfo());
auto* green = pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
green->SetNew(green_shared_state, green_rect, green_rect, SkColors::kGreen,
false);
SharedQuadState* blue_shared_state = CreateTestSharedQuadState(
gfx::Transform(), rect, pass.get(), gfx::MaskFilterInfo());
auto* blue = pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
blue->SetNew(blue_shared_state, rect, rect, SkColors::kBlue, false);
AggregatedRenderPassList pass_list;
pass_list.push_back(std::move(pass));
base::FilePath baseline =
base::FilePath(FILE_PATH_LITERAL("anti_aliasing_perspective_.png"))
.InsertBeforeExtensionASCII(this->renderer_str());
if (renderer_type() == RendererType::kSkiaGL && IsANGLEMetal()) {
baseline = baseline.InsertBeforeExtensionASCII(kANGLEMetalStr);
}
EXPECT_TRUE(this->RunPixelTest(
&pass_list, baseline, cc::AlphaDiscardingFuzzyPixelOffByOneComparator()));
}
// This test tests that anti-aliasing works for axis aligned quads.
// Anti-aliasing is only supported in the gl and skia renderers.
TEST_P(GPURendererPixelTest, AxisAligned) {
gfx::Rect rect(this->device_viewport_size_);
AggregatedRenderPassId id{1};
gfx::Transform transform_to_root;
auto pass = CreateTestRenderPass(id, rect, transform_to_root);
CreateTestAxisAlignedQuads(rect, SkColors::kRed, SkColors::kYellow, false,
false, pass.get());
gfx::Transform blue_quad_to_target_transform;
SharedQuadState* blue_shared_state =
CreateTestSharedQuadState(blue_quad_to_target_transform, rect, pass.get(),
gfx::MaskFilterInfo());
auto* blue = pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
blue->SetNew(blue_shared_state, rect, rect, SkColors::kBlue, false);
AggregatedRenderPassList pass_list;
pass_list.push_back(std::move(pass));
EXPECT_TRUE(this->RunPixelTest(
&pass_list, base::FilePath(FILE_PATH_LITERAL("axis_aligned.png")),
cc::AlphaDiscardingFuzzyPixelOffByOneComparator()));
}
// This test tests that forcing anti-aliasing off works as expected for
// solid color draw quads.
// Anti-aliasing is only supported in the gl and skia renderers.
TEST_P(GPURendererPixelTest, SolidColorDrawQuadForceAntiAliasingOff) {
gfx::Rect rect(this->device_viewport_size_);
AggregatedRenderPassId id{1};
gfx::Transform transform_to_root;
auto pass = CreateTestRenderPass(id, rect, transform_to_root);
pass->has_transparent_background = false;
gfx::Transform hole_quad_to_target_transform;
hole_quad_to_target_transform.Translate(50, 50);
hole_quad_to_target_transform.Scale(0.5f + 1.0f / (rect.width() * 2.0f),
0.5f + 1.0f / (rect.height() * 2.0f));
SharedQuadState* hole_shared_state =
CreateTestSharedQuadState(hole_quad_to_target_transform, rect, pass.get(),
gfx::MaskFilterInfo());
auto* hole = pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
hole->SetAll(hole_shared_state, rect, rect, false, SkColors::kTransparent,
true);
gfx::Transform green_quad_to_target_transform;
SharedQuadState* green_shared_state = CreateTestSharedQuadState(
green_quad_to_target_transform, rect, pass.get(), gfx::MaskFilterInfo());
auto* green = pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
green->SetNew(green_shared_state, rect, rect, SkColors::kGreen, false);
AggregatedRenderPassList pass_list;
pass_list.push_back(std::move(pass));
base::FilePath expected_result =
base::FilePath(FILE_PATH_LITERAL("force_anti_aliasing_off.png"));
if (is_skia_graphite()) {
expected_result = expected_result.InsertBeforeExtensionASCII(kGraphiteStr);
}
EXPECT_TRUE(this->RunPixelTest(&pass_list, expected_result,
cc::AlphaDiscardingExactPixelComparator()));
}
// This test tests that forcing anti-aliasing off works as expected for
// render pass draw quads.
// Anti-aliasing is only supported in the gl and skia renderers.
TEST_P(GPURendererPixelTest, RenderPassDrawQuadForceAntiAliasingOff) {
gfx::Rect rect(this->device_viewport_size_);
AggregatedRenderPassId root_pass_id{1};
gfx::Transform transform_to_root;
auto root_pass = CreateTestRenderPass(root_pass_id, rect, transform_to_root);
AggregatedRenderPassId child_pass_id{2};
gfx::Transform child_pass_transform;
auto child_pass =
CreateTestRenderPass(child_pass_id, rect, child_pass_transform);
gfx::Transform quad_to_target_transform;
SharedQuadState* hole_shared_state = CreateTestSharedQuadState(
quad_to_target_transform, rect, child_pass.get(), gfx::MaskFilterInfo());
SolidColorDrawQuad* hole =
child_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
hole->SetAll(hole_shared_state, rect, rect, false, SkColors::kTransparent,
false);
bool needs_blending = false;
bool force_anti_aliasing_off = true;
float backdrop_filter_quality = 1.0f;
bool intersects_damage_under = true;
gfx::Transform hole_pass_to_target_transform;
hole_pass_to_target_transform.Translate(50, 50);
hole_pass_to_target_transform.Scale(0.5f + 1.0f / (rect.width() * 2.0f),
0.5f + 1.0f / (rect.height() * 2.0f));
SharedQuadState* pass_shared_state = CreateTestSharedQuadState(
hole_pass_to_target_transform, rect, root_pass.get(), gfx::MaskFilterInfo());
AggregatedRenderPassDrawQuad* pass_quad =
root_pass->CreateAndAppendDrawQuad<AggregatedRenderPassDrawQuad>();
pass_quad->SetAll(pass_shared_state, rect, rect, needs_blending,
child_pass_id, kInvalidResourceId, gfx::RectF(),
gfx::Size(), gfx::Vector2dF(1.0f, 1.0f), gfx::PointF(),
gfx::RectF(rect), force_anti_aliasing_off,
backdrop_filter_quality, intersects_damage_under);
gfx::Transform green_quad_to_target_transform;
SharedQuadState* green_shared_state = CreateTestSharedQuadState(
green_quad_to_target_transform, rect, root_pass.get(), gfx::MaskFilterInfo());
SolidColorDrawQuad* green =
root_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
green->SetNew(green_shared_state, rect, rect, SkColors::kGreen, false);
AggregatedRenderPassList pass_list;
pass_list.push_back(std::move(child_pass));
pass_list.push_back(std::move(root_pass));
base::FilePath expected_result =
base::FilePath(FILE_PATH_LITERAL("force_anti_aliasing_off.png"));
if (is_skia_graphite()) {
expected_result = expected_result.InsertBeforeExtensionASCII(kGraphiteStr);
}
EXPECT_TRUE(this->RunPixelTest(&pass_list, expected_result,
cc::AlphaDiscardingExactPixelComparator()));
}
// This test tests that forcing anti-aliasing off works as expected for
// tile draw quads.
// Anti-aliasing is only supported in the gl and skia renderers.
TEST_P(GPURendererPixelTest, TileDrawQuadForceAntiAliasingOff) {
gfx::Rect rect(this->device_viewport_size_);
SkBitmap bitmap;
bitmap.allocN32Pixels(32, 32);
SkCanvas canvas(bitmap, SkSurfaceProps{});
canvas.clear(SkColors::kTransparent);
gfx::Size tile_size(32, 32);
ResourceId resource;
if (!is_software_renderer()) {
resource = CreateGpuResource(
this->child_context_provider_, this->child_resource_provider_.get(),
tile_size, SinglePlaneFormat::kRGBA_8888, kPremul_SkAlphaType,
gfx::ColorSpace(), MakePixelSpan(bitmap));
} else {
resource = this->AllocateAndFillSoftwareResource(
this->child_context_provider_, tile_size, bitmap);
}
// Return the mapped resource id.
std::unordered_map<ResourceId, ResourceId, ResourceIdHasher> resource_map =
cc::SendResourceAndGetChildToParentMap(
{resource}, this->resource_provider_.get(),
this->child_resource_provider_.get(),
this->child_context_provider_->SharedImageInterface());
ResourceId mapped_resource = resource_map[resource];
AggregatedRenderPassId id{1};
gfx::Transform transform_to_root;
auto pass = CreateTestRenderPass(id, rect, transform_to_root);
pass->has_transparent_background = false;
bool needs_blending = false;
bool nearest_neighbor = true;
bool force_anti_aliasing_off = true;
gfx::Transform hole_quad_to_target_transform;
hole_quad_to_target_transform.Translate(50, 50);
hole_quad_to_target_transform.Scale(0.5f + 1.0f / (rect.width() * 2.0f),
0.5f + 1.0f / (rect.height() * 2.0f));
SharedQuadState* hole_shared_state =
CreateTestSharedQuadState(hole_quad_to_target_transform, rect, pass.get(),
gfx::MaskFilterInfo());
TileDrawQuad* hole = pass->CreateAndAppendDrawQuad<TileDrawQuad>();
hole->SetNew(hole_shared_state, rect, rect, needs_blending, mapped_resource,
gfx::RectF(gfx::Rect(tile_size)), nearest_neighbor,
force_anti_aliasing_off);
gfx::Transform green_quad_to_target_transform;
SharedQuadState* green_shared_state = CreateTestSharedQuadState(
green_quad_to_target_transform, rect, pass.get(), gfx::MaskFilterInfo());
SolidColorDrawQuad* green =
pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
green->SetNew(green_shared_state, rect, rect, SkColors::kGreen, false);
AggregatedRenderPassList pass_list;
pass_list.push_back(std::move(pass));
base::FilePath expected_result =
base::FilePath(FILE_PATH_LITERAL("force_anti_aliasing_off.png"));
if (is_skia_graphite()) {
expected_result = expected_result.InsertBeforeExtensionASCII(kGraphiteStr);
}
EXPECT_TRUE(this->RunPixelTest(&pass_list, expected_result,
cc::AlphaDiscardingExactPixelComparator()));
}
// This test tests that forcing anti-aliasing off works as expected while
// blending is still enabled.
// Anti-aliasing is only supported in the gl and skia renderers.
TEST_P(GPURendererPixelTest, BlendingWithoutAntiAliasing) {
gfx::Rect rect(this->device_viewport_size_);
AggregatedRenderPassId id{1};
gfx::Transform transform_to_root;
auto pass = CreateTestRenderPass(id, rect, transform_to_root);
pass->has_transparent_background = false;
CreateTestAxisAlignedQuads(rect, SkColor4f{0.0f, 0.0f, 1.0f, 0.5},
SkColor4f{0.0f, 1.0f, 0.0f, 0.5f}, true, true,
pass.get());
SharedQuadState* background_quad_state = CreateTestSharedQuadState(
gfx::Transform(), rect, pass.get(), gfx::MaskFilterInfo());
auto* background_quad = pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
background_quad->SetNew(background_quad_state, rect, rect, SkColors::kBlack,
false);
AggregatedRenderPassList pass_list;
pass_list.push_back(std::move(pass));
base::FilePath expected_result =
base::FilePath(FILE_PATH_LITERAL("translucent_quads_no_aa.png"));
if (is_skia_graphite()) {
expected_result = expected_result.InsertBeforeExtensionASCII(kGraphiteStr);
}
EXPECT_TRUE(this->RunPixelTest(&pass_list, expected_result,
cc::AlphaDiscardingExactPixelComparator()));
}
TEST_P(GPURendererPixelTest, TrilinearFiltering) {
gfx::Rect viewport_rect(this->device_viewport_size_);
AggregatedRenderPassId root_pass_id{1};
auto root_pass = CreateTestRootRenderPass(root_pass_id, viewport_rect);
root_pass->has_transparent_background = false;
AggregatedRenderPassId child_pass_id{2};
gfx::Transform transform_to_root;
gfx::Rect child_pass_rect(
ScaleToCeiledSize(this->device_viewport_size_, 4.0f));
bool generate_mipmap = true;
auto child_pass = std::make_unique<AggregatedRenderPass>();
child_pass->SetAll(
child_pass_id, child_pass_rect, child_pass_rect, transform_to_root,
cc::FilterOperations(), cc::FilterOperations(), SkPath(),
gfx::ContentColorUsage::kSRGB, false, false, false, generate_mipmap);
gfx::Rect red_rect(child_pass_rect);
// Small enough red rect that linear filtering will miss it but large enough
// that it makes a meaningful contribution when using trilinear filtering.
red_rect.ClampToCenteredSize(gfx::Size(2, child_pass_rect.height()));
SharedQuadState* red_shared_state =
CreateTestSharedQuadState(gfx::Transform(), red_rect, child_pass.get(),
gfx::MaskFilterInfo());
auto* red = child_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
red->SetNew(red_shared_state, red_rect, red_rect, SkColors::kRed, false);
SharedQuadState* blue_shared_state = CreateTestSharedQuadState(
gfx::Transform(), child_pass_rect, child_pass.get(), gfx::MaskFilterInfo());
auto* blue = child_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
blue->SetNew(blue_shared_state, child_pass_rect, child_pass_rect,
SkColors::kBlue, false);
auto child_to_root_transform = gfx::TransformBetweenRects(
gfx::RectF(child_pass_rect), gfx::RectF(viewport_rect));
SharedQuadState* child_pass_shared_state = CreateTestSharedQuadState(
child_to_root_transform, child_pass_rect, root_pass.get(), gfx::MaskFilterInfo());
auto* child_pass_quad =
root_pass->CreateAndAppendDrawQuad<AggregatedRenderPassDrawQuad>();
child_pass_quad->SetNew(child_pass_shared_state, child_pass_rect,
child_pass_rect, child_pass_id, kInvalidResourceId,
gfx::RectF(), gfx::Size(),
gfx::RectF(child_pass_rect), false);
AggregatedRenderPassList pass_list;
pass_list.push_back(std::move(child_pass));
pass_list.push_back(std::move(root_pass));
if (is_skia_graphite()) {
// Rendering with Graphite results in a imperceptible, one-unit difference
// from the result when rendering with Skia's previous GPU backend.
EXPECT_TRUE(this->RunPixelTest(
&pass_list,
base::FilePath(
FILE_PATH_LITERAL("trilinear_filtering_skia_graphite.png")),
cc::AlphaDiscardingExactPixelComparator()));
} else {
base::FilePath baseline =
base::FilePath(FILE_PATH_LITERAL("trilinear_filtering.png"));
if (renderer_type() == RendererType::kSkiaGL && IsANGLEMetal()) {
baseline = baseline.InsertBeforeExtensionASCII(kANGLEMetalStr);
}
EXPECT_TRUE(this->RunPixelTest(&pass_list, baseline,
cc::AlphaDiscardingExactPixelComparator()));
}
}
class SoftwareRendererPixelTest : public VizPixelTest {
public:
SoftwareRendererPixelTest() : VizPixelTest(RendererType::kSoftware) {}
};
TEST_F(SoftwareRendererPixelTest, PictureDrawQuadIdentityScale) {
gfx::Rect viewport(this->device_viewport_size_);
// TODO(enne): the renderer should figure this out on its own.
bool nearest_neighbor = false;
AggregatedRenderPassId id{1};
gfx::Transform transform_to_root;
auto pass = CreateTestRenderPass(id, viewport, transform_to_root);
// One clipped blue quad in the lower right corner. Outside the clip
// is red, which should not appear.
gfx::Rect blue_rect(gfx::Size(100, 100));
gfx::Rect blue_clip_rect(gfx::Point(50, 50), gfx::Size(50, 50));
cc::FakeRecordingSource blue_recording(blue_rect.size());
cc::PaintFlags red_flags;
red_flags.setColor(SkColors::kRed);
blue_recording.add_draw_rect_with_flags(blue_rect, red_flags);
cc::PaintFlags blue_flags;
blue_flags.setColor(SkColors::kBlue);
blue_recording.add_draw_rect_with_flags(blue_clip_rect, blue_flags);
blue_recording.Rerecord();
scoped_refptr<cc::RasterSource> blue_raster_source =
blue_recording.CreateRasterSource();
gfx::Vector2d offset(viewport.bottom_right() - blue_rect.bottom_right());
bool needs_blending = true;
gfx::Transform blue_quad_to_target_transform;
blue_quad_to_target_transform.Translate(offset.x(), offset.y());
gfx::Rect blue_target_clip_rect = cc::MathUtil::MapEnclosingClippedRect(
blue_quad_to_target_transform, blue_clip_rect);
SharedQuadState* blue_shared_state =
CreateTestSharedQuadStateClipped(blue_quad_to_target_transform, blue_rect,
blue_target_clip_rect, pass.get());
auto* blue_quad = pass->CreateAndAppendDrawQuad<PictureDrawQuad>();
blue_quad->SetNew(blue_shared_state,
viewport, // Intentionally bigger than clip.
viewport, needs_blending, gfx::RectF(viewport),
nearest_neighbor, viewport, 1.f, {},
blue_raster_source->GetDisplayItemList(),
cc::ScrollOffsetMap());
// One viewport-filling green quad.
cc::FakeRecordingSource green_recording(viewport.size());
cc::PaintFlags green_flags;
green_flags.setColor(SkColors::kGreen);
green_recording.add_draw_rect_with_flags(viewport, green_flags);
green_recording.Rerecord();
scoped_refptr<cc::RasterSource> green_raster_source =
green_recording.CreateRasterSource();
gfx::Transform green_quad_to_target_transform;
SharedQuadState* green_shared_state =
CreateTestSharedQuadState(green_quad_to_target_transform, viewport,
pass.get(), gfx::MaskFilterInfo());
auto* green_quad = pass->CreateAndAppendDrawQuad<PictureDrawQuad>();
green_quad->SetNew(green_shared_state, viewport, viewport, needs_blending,
gfx::RectF(0.f, 0.f, 1.f, 1.f), nearest_neighbor, viewport,
1.f, {}, green_raster_source->GetDisplayItemList(),
cc::ScrollOffsetMap());
AggregatedRenderPassList pass_list;
pass_list.push_back(std::move(pass));
EXPECT_TRUE(this->RunPixelTest(
&pass_list,
base::FilePath(FILE_PATH_LITERAL("green_with_blue_corner.png")),
cc::AlphaDiscardingExactPixelComparator()));
}
// Not WithSkiaGPUBackend since that path currently requires tiles for opacity.
TEST_F(SoftwareRendererPixelTest, PictureDrawQuadOpacity) {
gfx::Rect viewport(this->device_viewport_size_);
bool needs_blending = true;
bool nearest_neighbor = false;
AggregatedRenderPassId id{1};
gfx::Transform transform_to_root;
auto pass = CreateTestRenderPass(id, viewport, transform_to_root);
// One viewport-filling 0.5-opacity green quad.
cc::FakeRecordingSource green_recording(viewport.size());
cc::PaintFlags green_flags;
green_flags.setColor(SkColors::kGreen);
green_recording.add_draw_rect_with_flags(viewport, green_flags);
green_recording.Rerecord();
scoped_refptr<cc::RasterSource> green_raster_source =
green_recording.CreateRasterSource();
gfx::Transform green_quad_to_target_transform;
SharedQuadState* green_shared_state =
CreateTestSharedQuadState(green_quad_to_target_transform, viewport,
pass.get(), gfx::MaskFilterInfo());
green_shared_state->opacity = 0.5f;
auto* green_quad = pass->CreateAndAppendDrawQuad<PictureDrawQuad>();
green_quad->SetNew(green_shared_state, viewport, viewport, needs_blending,
gfx::RectF(0, 0, 1, 1), nearest_neighbor, viewport, 1.f,
{}, green_raster_source->GetDisplayItemList(),
cc::ScrollOffsetMap());
// One viewport-filling white quad.
cc::FakeRecordingSource white_recording(viewport.size());
cc::PaintFlags white_flags;
white_flags.setColor(SkColors::kWhite);
white_recording.add_draw_rect_with_flags(viewport, white_flags);
white_recording.Rerecord();
scoped_refptr<cc::RasterSource> white_raster_source =
white_recording.CreateRasterSource();
gfx::Transform white_quad_to_target_transform;
SharedQuadState* white_shared_state = CreateTestSharedQuadState(
white_quad_to_target_transform, viewport, pass.get(), gfx::MaskFilterInfo());
auto* white_quad = pass->CreateAndAppendDrawQuad<PictureDrawQuad>();
white_quad->SetNew(white_shared_state, viewport, viewport, needs_blending,
gfx::RectF(0, 0, 1, 1), nearest_neighbor, viewport, 1.f,
{}, white_raster_source->GetDisplayItemList(),
cc::ScrollOffsetMap());
AggregatedRenderPassList pass_list;
pass_list.push_back(std::move(pass));
EXPECT_TRUE(this->RunPixelTest(
&pass_list, base::FilePath(FILE_PATH_LITERAL("green_alpha.png")),
cc::AlphaDiscardingFuzzyPixelOffByOneComparator()));
}
TEST_F(SoftwareRendererPixelTest, PictureDrawQuadOpacityWithAlpha) {
gfx::Rect viewport(this->device_viewport_size_);
bool needs_blending = true;
bool nearest_neighbor = false;
AggregatedRenderPassId id{1};
gfx::Transform transform_to_root;
auto pass = CreateTestRenderPass(id, viewport, transform_to_root);
// One viewport-filling 0.5-opacity transparent quad.
cc::FakeRecordingSource transparent_recording(viewport.size());
cc::PaintFlags transparent_flags;
transparent_flags.setColor(SkColors::kTransparent);
transparent_recording.add_draw_rect_with_flags(viewport, transparent_flags);
transparent_recording.Rerecord();
scoped_refptr<cc::RasterSource> transparent_raster_source =
transparent_recording.CreateRasterSource();
gfx::Transform transparent_quad_to_target_transform;
SharedQuadState* transparent_shared_state = CreateTestSharedQuadState(
transparent_quad_to_target_transform, viewport, pass.get(), gfx::MaskFilterInfo());
transparent_shared_state->opacity = 0.5f;
auto* transparent_quad = pass->CreateAndAppendDrawQuad<PictureDrawQuad>();
transparent_quad->SetNew(
transparent_shared_state, viewport, viewport, needs_blending,
gfx::RectF(0, 0, 1, 1), nearest_neighbor, viewport, 1.f, {},
transparent_raster_source->GetDisplayItemList(), cc::ScrollOffsetMap());
// One viewport-filling white quad.
cc::FakeRecordingSource white_recording(viewport.size());
cc::PaintFlags white_flags;
white_flags.setColor(SkColors::kWhite);
white_recording.add_draw_rect_with_flags(viewport, white_flags);
white_recording.Rerecord();
scoped_refptr<cc::RasterSource> white_raster_source =
white_recording.CreateRasterSource();
gfx::Transform white_quad_to_target_transform;
SharedQuadState* white_shared_state = CreateTestSharedQuadState(
white_quad_to_target_transform, viewport, pass.get(), gfx::MaskFilterInfo());
auto* white_quad = pass->CreateAndAppendDrawQuad<PictureDrawQuad>();
white_quad->SetNew(white_shared_state, viewport, viewport, needs_blending,
gfx::RectF(0, 0, 1, 1), nearest_neighbor, viewport, 1.f,
{}, white_raster_source->GetDisplayItemList(),
cc::ScrollOffsetMap());
AggregatedRenderPassList pass_list;
pass_list.push_back(std::move(pass));
EXPECT_TRUE(this->RunPixelTest(
&pass_list, base::FilePath(FILE_PATH_LITERAL("white.png")),
cc::AlphaDiscardingFuzzyPixelOffByOneComparator()));
}
void draw_point_color(SkCanvas* canvas,
SkScalar x,
SkScalar y,
SkColor4f color) {
SkPaint paint;
paint.setColor(color, nullptr /* SkColorSpace* colorSpace */);
canvas->drawPoint(x, y, paint);
}
// This disables filtering by setting |nearest_neighbor| on the
// PictureDrawQuad.
TEST_F(SoftwareRendererPixelTest, PictureDrawQuadNearestNeighbor) {
gfx::Rect viewport(this->device_viewport_size_);
bool needs_blending = true;
bool nearest_neighbor = true;
AggregatedRenderPassId id{1};
gfx::Transform transform_to_root;
auto pass = CreateTestRenderPass(id, viewport, transform_to_root);
sk_sp<SkSurface> surface =
SkSurfaces::Raster(SkImageInfo::MakeN32Premul(2, 2));
ASSERT_NE(surface, nullptr);
SkCanvas* canvas = surface->getCanvas();
draw_point_color(canvas, 0, 0, SkColors::kGreen);
draw_point_color(canvas, 0, 1, SkColors::kBlue);
draw_point_color(canvas, 1, 0, SkColors::kBlue);
draw_point_color(canvas, 1, 1, SkColors::kGreen);
cc::FakeRecordingSource recording(viewport.size());
recording.add_draw_image_with_flags(
surface->makeImageSnapshot(), gfx::Point(),
SkSamplingOptions(SkFilterMode::kLinear), cc::PaintFlags());
recording.Rerecord();
scoped_refptr<cc::RasterSource> raster_source =
recording.CreateRasterSource();
gfx::Transform quad_to_target_transform;
SharedQuadState* shared_state =
CreateTestSharedQuadState(quad_to_target_transform, viewport, pass.get(),
gfx::MaskFilterInfo());
auto* quad = pass->CreateAndAppendDrawQuad<PictureDrawQuad>();
quad->SetNew(shared_state, viewport, viewport, needs_blending,
gfx::RectF(0, 0, 2, 2), nearest_neighbor, viewport, 1.f, {},
raster_source->GetDisplayItemList(), cc::ScrollOffsetMap());
AggregatedRenderPassList pass_list;
pass_list.push_back(std::move(pass));
EXPECT_TRUE(this->RunPixelTest(
&pass_list,
base::FilePath(FILE_PATH_LITERAL("four_blue_green_checkers.png")),
cc::AlphaDiscardingExactPixelComparator()));
}
TEST_P(RendererPixelTest, PictureDrawQuadRasterInducingScroll) {
gfx::Rect viewport(this->device_viewport_size_);
bool needs_blending = true;
bool nearest_neighbor = false;
AggregatedRenderPassId id{1};
gfx::Transform transform_to_root;
auto pass = CreateTestRenderPass(id, viewport, transform_to_root);
cc::PaintFlags red_flags;
red_flags.setColor(SkColors::kRed);
cc::PaintFlags green_flags;
green_flags.setColor(SkColors::kGreen);
cc::PaintFlags blue_flags;
blue_flags.setColor(SkColors::kBlue);
gfx::PointF blue_offset1(123, 456);
auto scroll_list1 = base::MakeRefCounted<cc::DisplayItemList>();
scroll_list1->StartPaint();
scroll_list1->push<cc::DrawRectOp>(SkRect::MakeWH(1000, 1000), red_flags);
scroll_list1->push<cc::DrawRectOp>(
SkRect::MakeXYWH(blue_offset1.x(), blue_offset1.y(), 150, 100),
blue_flags);
scroll_list1->EndPaintOfUnpaired(gfx::Rect(1000, 1000));
scroll_list1->Finalize();
gfx::PointF blue_offset2(234, 789);
auto scroll_list2 = base::MakeRefCounted<cc::DisplayItemList>();
scroll_list2->StartPaint();
scroll_list2->push<cc::DrawRectOp>(SkRect::MakeWH(1000, 1000), red_flags);
scroll_list2->push<cc::DrawRectOp>(
SkRect::MakeXYWH(blue_offset2.x(), blue_offset2.y(), 100, 100),
blue_flags);
scroll_list2->EndPaintOfUnpaired(gfx::Rect(1000, 1000));
scroll_list2->Finalize();
cc::ElementId scroll_element_id1(123);
cc::ElementId scroll_element_id2(456);
auto display_list = base::MakeRefCounted<cc::DisplayItemList>();
display_list->StartPaint();
display_list->push<cc::DrawRectOp>(SkRect::MakeWH(200, 200), green_flags);
display_list->EndPaintOfUnpaired(gfx::Rect(200, 200));
// Draw scrolling contents op 1 under a clip.
display_list->StartPaint();
display_list->push<cc::SaveOp>();
display_list->push<cc::TranslateOp>(100.f, 0.f);
display_list->push<cc::ClipRectOp>(SkRect::MakeXYWH(0, 0, 100, 100),
SkClipOp::kIntersect, false);
display_list->EndPaintOfPairedBegin();
display_list->PushDrawScrollingContentsOp(
scroll_element_id1, std::move(scroll_list1), gfx::Rect(100, 0, 100, 100));
display_list->StartPaint();
display_list->push<cc::RestoreOp>();
display_list->EndPaintOfPairedEnd();
// Draw another scrolling contents op 2 under a translate and a clip.
display_list->StartPaint();
display_list->push<cc::SaveOp>();
display_list->push<cc::TranslateOp>(0.f, 100.f);
display_list->push<cc::ClipRectOp>(SkRect::MakeWH(100, 100),
SkClipOp::kIntersect, false);
display_list->EndPaintOfPairedBegin();
display_list->PushDrawScrollingContentsOp(
scroll_element_id2, std::move(scroll_list2), gfx::Rect(0, 100, 100, 100));
display_list->StartPaint();
display_list->push<cc::RestoreOp>();
display_list->EndPaintOfPairedEnd();
display_list->Finalize();
EXPECT_EQ(2u, display_list->raster_inducing_scrolls().size());
cc::FakeContentLayerClient client;
client.set_display_item_list(std::move(display_list));
cc::RecordingSource recording;
cc::Region invalidation;
recording.Update(gfx::Size(200, 200), 1, client, invalidation);
scoped_refptr<cc::RasterSource> raster_source =
recording.CreateRasterSource();
gfx::Transform quad_to_target_transform;
SharedQuadState* shared_state = CreateTestSharedQuadState(
quad_to_target_transform, viewport, pass.get(), gfx::MaskFilterInfo());
cc::ScrollOffsetMap raster_inducing_scroll_offsets = {
{scroll_element_id1, blue_offset1},
{scroll_element_id2, blue_offset2},
};
auto* quad = pass->CreateAndAppendDrawQuad<PictureDrawQuad>();
quad->SetNew(shared_state, viewport, viewport, needs_blending,
gfx::RectF(viewport), nearest_neighbor, viewport, 1.f, {},
raster_source->GetDisplayItemList(),
raster_inducing_scroll_offsets);
AggregatedRenderPassList pass_list;
pass_list.push_back(std::move(pass));
EXPECT_TRUE(this->RunPixelTest(
&pass_list,
base::FilePath(FILE_PATH_LITERAL("four_blue_green_checkers.png")),
cc::AlphaDiscardingExactPixelComparator()));
}
// This disables filtering by setting |nearest_neighbor| on the
// TileDrawQuad.
TEST_P(RendererPixelTest, TileDrawQuadNearestNeighbor) {
constexpr bool needs_blending = true;
constexpr bool nearest_neighbor = true;
constexpr bool force_anti_aliasing_off = false;
const SharedImageFormat format = is_software_renderer()
? SinglePlaneFormat::kBGRA_8888
: SinglePlaneFormat::kRGBA_8888;
gfx::Rect viewport(this->device_viewport_size_);
SkColorType ct = ToClosestSkColorType(format);
SkImageInfo info = SkImageInfo::Make(2, 2, ct, kPremul_SkAlphaType);
SkBitmap bitmap;
bitmap.allocPixels(info);
SkCanvas canvas(bitmap, SkSurfaceProps{});
draw_point_color(&canvas, 0, 0, SkColors::kGreen);
draw_point_color(&canvas, 0, 1, SkColors::kBlue);
draw_point_color(&canvas, 1, 0, SkColors::kBlue);
draw_point_color(&canvas, 1, 1, SkColors::kGreen);
gfx::Size tile_size(2, 2);
ResourceId resource;
if (!is_software_renderer()) {
resource = CreateGpuResource(this->child_context_provider_,
this->child_resource_provider_.get(),
tile_size, format, kPremul_SkAlphaType,
gfx::ColorSpace(), MakePixelSpan(bitmap));
} else {
resource = this->AllocateAndFillSoftwareResource(
this->child_context_provider_, tile_size, bitmap);
}
// Return the mapped resource id.
std::unordered_map<ResourceId, ResourceId, ResourceIdHasher> resource_map =
cc::SendResourceAndGetChildToParentMap(
{resource}, this->resource_provider_.get(),
this->child_resource_provider_.get(),
this->child_context_provider_->SharedImageInterface());
ResourceId mapped_resource = resource_map[resource];
AggregatedRenderPassId id{1};
gfx::Transform transform_to_root;
auto pass = CreateTestRenderPass(id, viewport, transform_to_root);
gfx::Transform quad_to_target_transform;
SharedQuadState* shared_state =
CreateTestSharedQuadState(quad_to_target_transform, viewport, pass.get(),
gfx::MaskFilterInfo());
auto* quad = pass->CreateAndAppendDrawQuad<TileDrawQuad>();
quad->SetNew(shared_state, viewport, viewport, needs_blending,
mapped_resource, gfx::RectF(gfx::Rect(tile_size)),
nearest_neighbor, force_anti_aliasing_off);
AggregatedRenderPassList pass_list;
pass_list.push_back(std::move(pass));
EXPECT_TRUE(this->RunPixelTest(
&pass_list,
base::FilePath(FILE_PATH_LITERAL("four_blue_green_checkers.png")),
cc::AlphaDiscardingExactPixelComparator()));
}
// This disables filtering by setting |nearest_neighbor| to true on the
// TextureDrawQuad.
TEST_F(SoftwareRendererPixelTest, TextureDrawQuadNearestNeighbor) {
gfx::Rect viewport(this->device_viewport_size_);
bool needs_blending = true;
bool nearest_neighbor = true;
SkBitmap bitmap;
bitmap.allocN32Pixels(2, 2);
SkCanvas canvas(bitmap, SkSurfaceProps{});
draw_point_color(&canvas, 0, 0, SkColors::kGreen);
draw_point_color(&canvas, 0, 1, SkColors::kBlue);
draw_point_color(&canvas, 1, 0, SkColors::kBlue);
draw_point_color(&canvas, 1, 1, SkColors::kGreen);
gfx::Size tile_size(2, 2);
ResourceId resource = this->AllocateAndFillSoftwareResource(
this->child_context_provider_, tile_size, bitmap);
// Return the mapped resource id.
std::unordered_map<ResourceId, ResourceId, ResourceIdHasher> resource_map =
cc::SendResourceAndGetChildToParentMap(
{resource}, this->resource_provider_.get(),
this->child_resource_provider_.get(),
this->child_context_provider_->SharedImageInterface());
ResourceId mapped_resource = resource_map[resource];
AggregatedRenderPassId id{1};
gfx::Transform transform_to_root;
auto pass = CreateTestRenderPass(id, viewport, transform_to_root);
gfx::Transform quad_to_target_transform;
SharedQuadState* shared_state =
CreateTestSharedQuadState(quad_to_target_transform, viewport, pass.get(),
gfx::MaskFilterInfo());
auto* quad = pass->CreateAndAppendDrawQuad<TextureDrawQuad>();
quad->SetNew(shared_state, viewport, viewport, needs_blending,
mapped_resource, gfx::PointF(0, 0), gfx::PointF(1, 1),
SkColors::kBlack, nearest_neighbor,
/*secure_output_only=*/false, gfx::ProtectedVideoType::kClear);
AggregatedRenderPassList pass_list;
pass_list.push_back(std::move(pass));
EXPECT_TRUE(this->RunPixelTest(
&pass_list,
base::FilePath(FILE_PATH_LITERAL("four_blue_green_checkers.png")),
cc::FuzzyPixelComparator().SetErrorPixelsPercentageLimit(2.f)));
}
// This ensures filtering is enabled by setting |nearest_neighbor| to false on
// the TextureDrawQuad.
TEST_F(SoftwareRendererPixelTest, TextureDrawQuadLinear) {
gfx::Rect viewport(this->device_viewport_size_);
bool needs_blending = true;
bool nearest_neighbor = false;
SkBitmap bitmap;
bitmap.allocN32Pixels(2, 2);
{
SkCanvas canvas(bitmap, SkSurfaceProps{});
draw_point_color(&canvas, 0, 0, SkColors::kGreen);
draw_point_color(&canvas, 0, 1, SkColors::kBlue);
draw_point_color(&canvas, 1, 0, SkColors::kBlue);
draw_point_color(&canvas, 1, 1, SkColors::kGreen);
}
gfx::Size tile_size(2, 2);
ResourceId resource = this->AllocateAndFillSoftwareResource(
this->child_context_provider_, tile_size, bitmap);
// Return the mapped resource id.
std::unordered_map<ResourceId, ResourceId, ResourceIdHasher> resource_map =
cc::SendResourceAndGetChildToParentMap(
{resource}, this->resource_provider_.get(),
this->child_resource_provider_.get(),
this->child_context_provider_->SharedImageInterface());
ResourceId mapped_resource = resource_map[resource];
AggregatedRenderPassId id{1};
gfx::Transform transform_to_root;
auto pass = CreateTestRenderPass(id, viewport, transform_to_root);
gfx::Transform quad_to_target_transform;
SharedQuadState* shared_state =
CreateTestSharedQuadState(quad_to_target_transform, viewport, pass.get(),
gfx::MaskFilterInfo());
auto* quad = pass->CreateAndAppendDrawQuad<TextureDrawQuad>();
quad->SetNew(shared_state, viewport, viewport, needs_blending,
mapped_resource, gfx::PointF(0, 0), gfx::PointF(1, 1),
SkColors::kBlack, nearest_neighbor,
/*secure_output=*/false, gfx::ProtectedVideoType::kClear);
AggregatedRenderPassList pass_list;
pass_list.push_back(std::move(pass));
// Allow for a small amount of error as the blending alogrithm used by Skia is
// affected by the offset in the expanded rect.
EXPECT_TRUE(this->RunPixelTest(
&pass_list,
base::FilePath(FILE_PATH_LITERAL("four_blue_green_checkers_linear.png")),
cc::FuzzyPixelComparator()
.SetErrorPixelsPercentageLimit(100.f)
.SetAbsErrorLimit(16)));
}
TEST_F(SoftwareRendererPixelTest, PictureDrawQuadNonIdentityScale) {
gfx::Rect viewport(this->device_viewport_size_);
// TODO(enne): the renderer should figure this out on its own.
bool needs_blending = true;
bool nearest_neighbor = false;
AggregatedRenderPassId id{1};
gfx::Transform transform_to_root;
auto pass = CreateTestRenderPass(id, viewport, transform_to_root);
// As scaling up the blue checkerboards will cause sampling on the GPU,
// a few extra "cleanup rects" need to be added to clobber the blending
// to make the output image more clean. This will also test subrects
// of the layer.
gfx::Transform green_quad_to_target_transform;
gfx::Rect green_rect1(gfx::Point(80, 0), gfx::Size(20, 100));
gfx::Rect green_rect2(gfx::Point(0, 80), gfx::Size(100, 20));
cc::FakeRecordingSource green_recording(viewport.size());
cc::PaintFlags red_flags;
red_flags.setColor(SkColors::kRed);
green_recording.add_draw_rect_with_flags(viewport, red_flags);
cc::PaintFlags green_flags;
green_flags.setColor(SkColors::kGreen);
green_recording.add_draw_rect_with_flags(green_rect1, green_flags);
green_recording.add_draw_rect_with_flags(green_rect2, green_flags);
green_recording.Rerecord();
scoped_refptr<cc::RasterSource> green_raster_source =
green_recording.CreateRasterSource();
SharedQuadState* top_right_green_shared_quad_state =
CreateTestSharedQuadState(green_quad_to_target_transform, viewport,
pass.get(), gfx::MaskFilterInfo());
auto* green_quad1 = pass->CreateAndAppendDrawQuad<PictureDrawQuad>();
green_quad1->SetNew(
top_right_green_shared_quad_state, green_rect1, green_rect1,
needs_blending, gfx::RectF(gfx::SizeF(green_rect1.size())),
nearest_neighbor, green_rect1, 1.f, {},
green_raster_source->GetDisplayItemList(), cc::ScrollOffsetMap());
auto* green_quad2 = pass->CreateAndAppendDrawQuad<PictureDrawQuad>();
green_quad2->SetNew(
top_right_green_shared_quad_state, green_rect2, green_rect2,
needs_blending, gfx::RectF(gfx::SizeF(green_rect2.size())),
nearest_neighbor, green_rect2, 1.f, {},
green_raster_source->GetDisplayItemList(), cc::ScrollOffsetMap());
// Add a green clipped checkerboard in the bottom right to help test
// interleaving picture quad content and solid color content.
gfx::Rect bottom_right_rect(
gfx::Point(viewport.width() / 2, viewport.height() / 2),
gfx::Size(viewport.width() / 2, viewport.height() / 2));
SharedQuadState* bottom_right_green_shared_state =
CreateTestSharedQuadStateClipped(green_quad_to_target_transform, viewport,
bottom_right_rect, pass.get());
auto* bottom_right_color_quad =
pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
bottom_right_color_quad->SetNew(bottom_right_green_shared_state, viewport,
viewport, SkColors::kGreen, false);
// Add two blue checkerboards taking up the bottom left and top right,
// but use content scales as content rects to make this happen.
// The content is at a 4x content scale.
gfx::Rect layer_rect(gfx::Size(20, 30));
float contents_scale = 4.f;
// Two rects that touch at their corners, arbitrarily placed in the layer.
gfx::RectF blue_layer_rect1(gfx::PointF(5.5f, 9.0f), gfx::SizeF(2.5f, 2.5f));
gfx::RectF blue_layer_rect2(gfx::PointF(8.0f, 6.5f), gfx::SizeF(2.5f, 2.5f));
gfx::RectF union_layer_rect = blue_layer_rect1;
union_layer_rect.Union(blue_layer_rect2);
// Because scaling up will cause sampling outside the rects, add one extra
// pixel of buffer at the final content scale.
float inset = -1.f / contents_scale;
blue_layer_rect1.Inset(inset);
blue_layer_rect2.Inset(inset);
cc::FakeRecordingSource recording(layer_rect.size());
cc::Region outside(layer_rect);
outside.Subtract(gfx::ToEnclosingRect(union_layer_rect));
for (gfx::Rect rect : outside) {
recording.add_draw_rect_with_flags(rect, red_flags);
}
cc::PaintFlags blue_flags;
blue_flags.setColor(SkColors::kBlue);
recording.add_draw_rectf_with_flags(blue_layer_rect1, blue_flags);
recording.add_draw_rectf_with_flags(blue_layer_rect2, blue_flags);
recording.Rerecord();
scoped_refptr<cc::RasterSource> raster_source =
recording.CreateRasterSource();
gfx::Rect content_union_rect(
gfx::ToEnclosingRect(gfx::ScaleRect(union_layer_rect, contents_scale)));
// At a scale of 4x the rectangles with a width of 2.5 will take up 10 pixels,
// so scale an additional 10x to make them 100x100.
gfx::Transform quad_to_target_transform;
quad_to_target_transform.Scale(10.0, 10.0);
gfx::Rect quad_content_rect(gfx::Size(20, 20));
SharedQuadState* blue_shared_state = CreateTestSharedQuadState(
quad_to_target_transform, quad_content_rect, pass.get(), gfx::MaskFilterInfo());
auto* blue_quad = pass->CreateAndAppendDrawQuad<PictureDrawQuad>();
blue_quad->SetNew(blue_shared_state, quad_content_rect, quad_content_rect,
needs_blending, gfx::RectF(quad_content_rect),
nearest_neighbor, content_union_rect, contents_scale, {},
raster_source->GetDisplayItemList(), cc::ScrollOffsetMap());
// Fill left half of viewport with green.
gfx::Transform half_green_quad_to_target_transform;
gfx::Rect half_green_rect(gfx::Size(viewport.width() / 2, viewport.height()));
SharedQuadState* half_green_shared_state = CreateTestSharedQuadState(
half_green_quad_to_target_transform, half_green_rect, pass.get(),
gfx::MaskFilterInfo());
auto* half_color_quad = pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
half_color_quad->SetNew(half_green_shared_state, half_green_rect,
half_green_rect, SkColors::kGreen, false);
AggregatedRenderPassList pass_list;
pass_list.push_back(std::move(pass));
EXPECT_TRUE(this->RunPixelTest(
&pass_list,
base::FilePath(FILE_PATH_LITERAL("four_blue_green_checkers.png")),
cc::AlphaDiscardingExactPixelComparator()));
}
class RendererPixelTestWithFlippedOutputSurface : public VizPixelTestWithParam {
protected:
gfx::SurfaceOrigin GetSurfaceOrigin() const override {
return gfx::SurfaceOrigin::kTopLeft;
}
};
INSTANTIATE_TEST_SUITE_P(,
RendererPixelTestWithFlippedOutputSurface,
testing::ValuesIn(GetGpuRendererTypes()),
testing::PrintToStringParamName());
// GetGpuRendererTypes() can return an empty list, e.g. on Fuchsia ARM64.
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(
RendererPixelTestWithFlippedOutputSurface);
TEST_P(RendererPixelTestWithFlippedOutputSurface, ExplicitFlipTest) {
// This draws a blue rect above a yellow rect with an inverted output surface.
gfx::Rect viewport_rect(this->device_viewport_size_);
AggregatedRenderPassId root_pass_id{1};
auto root_pass = CreateTestRootRenderPass(root_pass_id, viewport_rect);
AggregatedRenderPassId child_pass_id{2};
gfx::Rect pass_rect(this->device_viewport_size_);
gfx::Transform transform_to_root;
auto child_pass =
CreateTestRenderPass(child_pass_id, pass_rect, transform_to_root);
gfx::Transform quad_to_target_transform;
SharedQuadState* shared_state = CreateTestSharedQuadState(
quad_to_target_transform, viewport_rect, child_pass.get(), gfx::MaskFilterInfo());
gfx::Rect blue_rect(0, 0, this->device_viewport_size_.width(),
this->device_viewport_size_.height() / 2);
auto* blue = child_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
blue->SetNew(shared_state, blue_rect, blue_rect, SkColors::kBlue, false);
gfx::Rect yellow_rect(0, this->device_viewport_size_.height() / 2,
this->device_viewport_size_.width(),
this->device_viewport_size_.height() / 2);
auto* yellow = child_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
yellow->SetNew(shared_state, yellow_rect, yellow_rect, SkColors::kYellow,
false);
SharedQuadState* pass_shared_state =
CreateTestSharedQuadState(gfx::Transform(), pass_rect, root_pass.get(),
gfx::MaskFilterInfo());
CreateTestRenderPassDrawQuad(pass_shared_state, pass_rect, child_pass_id,
root_pass.get());
AggregatedRenderPassList pass_list;
pass_list.push_back(std::move(child_pass));
pass_list.push_back(std::move(root_pass));
// Note: RunPixelTest() will issue a CopyOutputRequest on the root pass. The
// implementation should realize the output surface is flipped, and return a
// right-side up result regardless (i.e., NOT blue_yellow_flipped.png).
EXPECT_TRUE(this->RunPixelTest(
&pass_list, base::FilePath(FILE_PATH_LITERAL("blue_yellow.png")),
cc::AlphaDiscardingFuzzyPixelOffByOneComparator()));
}
TEST_P(RendererPixelTestWithFlippedOutputSurface, CheckChildPassUnflipped) {
// This draws a blue rect above a yellow rect with an inverted output surface.
gfx::Rect viewport_rect(this->device_viewport_size_);
AggregatedRenderPassId root_pass_id{1};
auto root_pass = CreateTestRootRenderPass(root_pass_id, viewport_rect);
AggregatedRenderPassId child_pass_id{2};
gfx::Rect pass_rect(this->device_viewport_size_);
gfx::Transform transform_to_root;
auto child_pass =
CreateTestRenderPass(child_pass_id, pass_rect, transform_to_root);
gfx::Transform quad_to_target_transform;
SharedQuadState* shared_state = CreateTestSharedQuadState(
quad_to_target_transform, viewport_rect, child_pass.get(), gfx::MaskFilterInfo());
gfx::Rect blue_rect(0, 0, this->device_viewport_size_.width(),
this->device_viewport_size_.height() / 2);
auto* blue = child_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
blue->SetNew(shared_state, blue_rect, blue_rect, SkColors::kBlue, false);
gfx::Rect yellow_rect(0, this->device_viewport_size_.height() / 2,
this->device_viewport_size_.width(),
this->device_viewport_size_.height() / 2);
auto* yellow = child_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
yellow->SetNew(shared_state, yellow_rect, yellow_rect, SkColors::kYellow,
false);
SharedQuadState* pass_shared_state =
CreateTestSharedQuadState(gfx::Transform(), pass_rect, root_pass.get(),
gfx::MaskFilterInfo());
CreateTestRenderPassDrawQuad(pass_shared_state, pass_rect, child_pass_id,
root_pass.get());
AggregatedRenderPassList pass_list;
pass_list.push_back(std::move(child_pass));
pass_list.push_back(std::move(root_pass));
// Check that the child pass remains unflipped.
EXPECT_TRUE(this->RunPixelTestWithCopyOutputRequest(
&pass_list, pass_list.front().get(),
base::FilePath(FILE_PATH_LITERAL("blue_yellow.png")),
cc::AlphaDiscardingExactPixelComparator()));
}
TEST_P(GPURendererPixelTest, CheckReadbackSubset) {
gfx::Rect viewport_rect(this->device_viewport_size_);
AggregatedRenderPassId root_pass_id{1};
auto root_pass = CreateTestRootRenderPass(root_pass_id, viewport_rect);
AggregatedRenderPassId child_pass_id{2};
gfx::Rect pass_rect(this->device_viewport_size_);
gfx::Transform transform_to_root;
auto child_pass =
CreateTestRenderPass(child_pass_id, pass_rect, transform_to_root);
gfx::Transform quad_to_target_transform;
SharedQuadState* shared_state = CreateTestSharedQuadState(
quad_to_target_transform, viewport_rect, child_pass.get(), gfx::MaskFilterInfo());
// Draw a green quad full-size with a blue quad in the lower-right corner.
gfx::Rect blue_rect(this->device_viewport_size_.width() * 3 / 4,
this->device_viewport_size_.height() * 3 / 4,
this->device_viewport_size_.width() * 3 / 4,
this->device_viewport_size_.height() * 3 / 4);
auto* blue = child_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
blue->SetNew(shared_state, blue_rect, blue_rect, SkColors::kBlue, false);
gfx::Rect green_rect(0, 0, this->device_viewport_size_.width(),
this->device_viewport_size_.height());
auto* green = child_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
green->SetNew(shared_state, green_rect, green_rect, SkColors::kGreen, false);
SharedQuadState* pass_shared_state =
CreateTestSharedQuadState(gfx::Transform(), pass_rect, root_pass.get(),
gfx::MaskFilterInfo());
CreateTestRenderPassDrawQuad(pass_shared_state, pass_rect, child_pass_id,
root_pass.get());
AggregatedRenderPassList pass_list;
pass_list.push_back(std::move(child_pass));
pass_list.push_back(std::move(root_pass));
// Check that the child pass remains unflipped.
gfx::Rect capture_rect(this->device_viewport_size_.width() / 2,
this->device_viewport_size_.height() / 2,
this->device_viewport_size_.width() / 2,
this->device_viewport_size_.height() / 2);
EXPECT_TRUE(this->RunPixelTestWithCopyOutputRequestAndArea(
&pass_list, pass_list.front().get(),
base::FilePath(FILE_PATH_LITERAL("green_small_with_blue_corner.png")),
cc::AlphaDiscardingExactPixelComparator(), &capture_rect));
}
TEST_P(GPURendererPixelTest, TextureQuadBatching) {
// This test verifies that multiple texture quads using the same resource
// get drawn correctly. It implicitly is trying to test that the
// renderer does the right thing with its draw quad cache.
gfx::Rect rect(this->device_viewport_size_);
bool needs_blending = false;
AggregatedRenderPassId id{1};
auto pass = CreateTestRootRenderPass(id, rect);
SharedQuadState* shared_state = CreateTestSharedQuadState(
gfx::Transform(), rect, pass.get(), gfx::MaskFilterInfo());
// Make a mask.
gfx::Rect mask_rect = rect;
SkBitmap bitmap;
bitmap.allocPixels(
SkImageInfo::MakeN32Premul(mask_rect.width(), mask_rect.height()));
SkCanvas canvas(bitmap, SkSurfaceProps{});
SkPaint paint;
paint.setStyle(SkPaint::kStroke_Style);
paint.setStrokeWidth(SkIntToScalar(4));
paint.setColor(SkColors::kGreen);
canvas.clear(SkColors::kWhite);
gfx::Rect inset_rect = rect;
while (!inset_rect.IsEmpty()) {
inset_rect.Inset(gfx::Insets::TLBR(6, 6, 4, 4));
canvas.drawRect(SkRect::MakeXYWH(inset_rect.x(), inset_rect.y(),
inset_rect.width(), inset_rect.height()),
paint);
inset_rect.Inset(gfx::Insets::TLBR(6, 6, 4, 4));
}
ResourceId resource = CreateGpuResource(
this->child_context_provider_, this->child_resource_provider_.get(),
mask_rect.size(), SinglePlaneFormat::kRGBA_8888, kPremul_SkAlphaType,
gfx::ColorSpace(), MakePixelSpan(bitmap));
// Return the mapped resource id.
std::unordered_map<ResourceId, ResourceId, ResourceIdHasher> resource_map =
cc::SendResourceAndGetChildToParentMap(
{resource}, this->resource_provider_.get(),
this->child_resource_provider_.get(),
this->child_context_provider_->SharedImageInterface());
ResourceId mapped_resource = resource_map[resource];
// Arbitrary dividing lengths to divide up the resource into 16 quads.
auto widths = std::to_array<int>({
0,
60,
50,
40,
});
auto heights = std::to_array<int>({
0,
10,
80,
50,
});
size_t num_quads = 4;
for (size_t i = 0; i < num_quads; ++i) {
int x_start = widths[i];
int x_end = i == num_quads - 1 ? rect.width() : widths[i + 1];
DCHECK_LE(x_end, rect.width());
for (size_t j = 0; j < num_quads; ++j) {
int y_start = heights[j];
int y_end = j == num_quads - 1 ? rect.height() : heights[j + 1];
DCHECK_LE(y_end, rect.height());
gfx::Rect layer_rect(x_start, y_start, x_end - x_start, y_end - y_start);
gfx::RectF uv_rect = gfx::ScaleRect(
gfx::RectF(layer_rect), 1.f / rect.width(), 1.f / rect.height());
auto* texture_quad = pass->CreateAndAppendDrawQuad<TextureDrawQuad>();
texture_quad->SetNew(
shared_state, layer_rect, layer_rect, needs_blending, mapped_resource,
uv_rect.origin(), uv_rect.bottom_right(), SkColors::kWhite, false,
/*secure_output_only=*/false, gfx::ProtectedVideoType::kClear);
}
}
AggregatedRenderPassList pass_list;
pass_list.push_back(std::move(pass));
EXPECT_TRUE(this->RunPixelTest(
&pass_list, base::FilePath(FILE_PATH_LITERAL("spiral.png")),
cc::AlphaDiscardingFuzzyPixelOffByOneComparator()));
}
TEST_P(GPURendererPixelTest, TileQuadClamping) {
gfx::Rect viewport(this->device_viewport_size_);
bool needs_blending = true;
bool nearest_neighbor = false;
bool use_aa = false;
gfx::Size layer_size(4, 4);
gfx::Size tile_size(20, 20);
gfx::Rect quad_rect(layer_size);
gfx::RectF tex_coord_rect(quad_rect);
// tile sized bitmap, with valid contents green and contents outside the
// layer rect red.
SkBitmap bitmap;
bitmap.allocN32Pixels(tile_size.width(), tile_size.height());
SkCanvas canvas(bitmap, SkSurfaceProps{});
SkPaint red;
red.setColor(SkColors::kRed);
canvas.drawRect(SkRect::MakeWH(tile_size.width(), tile_size.height()), red);
SkPaint green;
green.setColor(SkColors::kGreen);
canvas.drawRect(SkRect::MakeWH(layer_size.width(), layer_size.height()),
green);
ResourceId resource;
if (!is_software_renderer()) {
resource = CreateGpuResource(
this->child_context_provider_, this->child_resource_provider_.get(),
tile_size, SinglePlaneFormat::kRGBA_8888, kPremul_SkAlphaType,
gfx::ColorSpace(), MakePixelSpan(bitmap));
} else {
resource = this->AllocateAndFillSoftwareResource(
this->child_context_provider_, tile_size, bitmap);
}
// Return the mapped resource id.
std::unordered_map<ResourceId, ResourceId, ResourceIdHasher> resource_map =
cc::SendResourceAndGetChildToParentMap(
{resource}, this->resource_provider_.get(),
this->child_resource_provider_.get(),
this->child_context_provider_->SharedImageInterface());
ResourceId mapped_resource = resource_map[resource];
AggregatedRenderPassId id{1};
gfx::Transform transform_to_root;
auto pass = CreateTestRenderPass(id, viewport, transform_to_root);
// Green quad that should not show any red pixels from outside the
// tex coord rect.
gfx::Transform transform;
transform.Scale(40, 40);
SharedQuadState* quad_shared =
CreateTestSharedQuadState(transform, gfx::Rect(layer_size), pass.get(),
gfx::MaskFilterInfo());
auto* quad = pass->CreateAndAppendDrawQuad<TileDrawQuad>();
quad->SetNew(quad_shared, gfx::Rect(layer_size), gfx::Rect(layer_size),
needs_blending, mapped_resource, tex_coord_rect,
nearest_neighbor, use_aa);
// Green background.
SharedQuadState* background_shared =
CreateTestSharedQuadState(gfx::Transform(), viewport, pass.get(),
gfx::MaskFilterInfo());
auto* color_quad = pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
color_quad->SetNew(background_shared, viewport, viewport, SkColors::kGreen,
false);
AggregatedRenderPassList pass_list;
pass_list.push_back(std::move(pass));
EXPECT_TRUE(this->RunPixelTest(&pass_list,
base::FilePath(FILE_PATH_LITERAL("green.png")),
cc::AlphaDiscardingExactPixelComparator()));
}
TEST_P(RendererPixelTest, RoundedCornerSimpleSolidDrawQuad) {
gfx::Rect viewport_rect(this->device_viewport_size_);
constexpr int kInset = 20;
constexpr int kCornerRadius = 20;
AggregatedRenderPassId root_pass_id{1};
auto root_pass = CreateTestRootRenderPass(root_pass_id, viewport_rect);
gfx::Transform quad_to_target_transform;
gfx::Rect blue_rect(0, 0, this->device_viewport_size_.width(),
this->device_viewport_size_.height());
gfx::Rect red_rect = blue_rect;
blue_rect.Inset(kInset);
gfx::RRectF rounded_corner_rrect(gfx::RectF(blue_rect), kCornerRadius);
SharedQuadState* shared_state_rounded = CreateTestSharedQuadState(
quad_to_target_transform, viewport_rect, root_pass.get(),
gfx::MaskFilterInfo(rounded_corner_rrect));
auto* blue = root_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
blue->SetNew(shared_state_rounded, blue_rect, blue_rect, SkColors::kBlue,
false);
SharedQuadState* shared_state_normal = CreateTestSharedQuadState(
quad_to_target_transform, viewport_rect, root_pass.get(), gfx::MaskFilterInfo());
auto* white = root_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
white->SetNew(shared_state_normal, red_rect, red_rect, SkColors::kWhite,
false);
AggregatedRenderPassList pass_list;
pass_list.push_back(std::move(root_pass));
EXPECT_TRUE(this->RunPixelTest(
&pass_list,
base::FilePath(FILE_PATH_LITERAL("rounded_corner_simple.png")),
cc::FuzzyPixelComparator().DiscardAlpha().SetErrorPixelsPercentageLimit(
0.55f)));
}
TEST_P(GPURendererPixelTest, RoundedCornerSimpleTextureDrawQuad) {
gfx::Rect viewport_rect(this->device_viewport_size_);
constexpr int kInset = 20;
constexpr int kCornerRadius = 20;
AggregatedRenderPassId root_pass_id{1};
auto root_pass = CreateTestRootRenderPass(root_pass_id, viewport_rect);
gfx::Transform quad_to_target_transform;
gfx::Rect blue_rect(0, 0, this->device_viewport_size_.width(),
this->device_viewport_size_.height());
gfx::Rect red_rect = blue_rect;
blue_rect.Inset(kInset);
gfx::RRectF rounded_corner_rrect(gfx::RectF(blue_rect), kCornerRadius);
SharedQuadState* shared_state_rounded = CreateTestSharedQuadState(
quad_to_target_transform, viewport_rect, root_pass.get(),
gfx::MaskFilterInfo(rounded_corner_rrect));
const uint8_t colors[] = {0, 0, 255, 255, 0, 0, 255, 255,
0, 0, 255, 255, 0, 0, 255, 255};
ResourceId resource = CreateGpuResource(
this->child_context_provider_, this->child_resource_provider_.get(),
gfx::Size(2, 2), SinglePlaneFormat::kRGBA_8888, kPremul_SkAlphaType,
gfx::ColorSpace(), colors);
std::unordered_map<ResourceId, ResourceId, ResourceIdHasher> resource_map =
cc::SendResourceAndGetChildToParentMap(
{resource}, this->resource_provider_.get(),
this->child_resource_provider_.get(),
this->child_context_provider_->SharedImageInterface());
ResourceId mapped_resource = resource_map[resource];
bool needs_blending = true;
const gfx::PointF uv_top_left(0.0f, 0.0f);
const gfx::PointF uv_bottom_right(1.0f, 1.0f);
const bool nearest_neighbor = false;
auto* blue = root_pass->CreateAndAppendDrawQuad<TextureDrawQuad>();
blue->SetNew(shared_state_rounded, blue_rect, blue_rect, needs_blending,
mapped_resource, uv_top_left, uv_bottom_right, SkColors::kBlack,
nearest_neighbor,
/*secure_output_only=*/false, gfx::ProtectedVideoType::kClear);
SharedQuadState* shared_state_normal = CreateTestSharedQuadState(
quad_to_target_transform, viewport_rect, root_pass.get(), gfx::MaskFilterInfo());
auto* white = root_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
white->SetNew(shared_state_normal, red_rect, red_rect, SkColors::kWhite,
false);
AggregatedRenderPassList pass_list;
pass_list.push_back(std::move(root_pass));
EXPECT_TRUE(this->RunPixelTest(
&pass_list,
base::FilePath(FILE_PATH_LITERAL("rounded_corner_simple.png")),
cc::FuzzyPixelComparator().DiscardAlpha().SetErrorPixelsPercentageLimit(
0.6f)));
}
TEST_P(RendererPixelTest, RoundedCornerOnRenderPass) {
gfx::Rect viewport_rect(this->device_viewport_size_);
constexpr int kInset = 20;
constexpr int kCornerRadius = 20;
constexpr int kBlueCornerRadius = 10;
AggregatedRenderPassId root_pass_id{1};
auto root_pass = CreateTestRootRenderPass(root_pass_id, viewport_rect);
AggregatedRenderPassId child_pass_id{2};
gfx::Rect pass_rect(this->device_viewport_size_);
pass_rect.Inset(kInset);
gfx::Rect child_pass_local_rect = gfx::Rect(pass_rect.size());
gfx::Transform transform_to_root;
transform_to_root.Translate(pass_rect.OffsetFromOrigin());
auto child_pass = CreateTestRenderPass(child_pass_id, child_pass_local_rect,
transform_to_root);
gfx::Rect blue_rect = child_pass_local_rect;
gfx::Vector2dF blue_offset_from_target(-30, 40);
gfx::RRectF blue_rrect(gfx::RectF(blue_rect), kBlueCornerRadius);
blue_rrect.Offset(blue_offset_from_target);
gfx::Transform quad_to_target_transform;
quad_to_target_transform.Translate(blue_offset_from_target);
SharedQuadState* shared_state_with_rrect = CreateTestSharedQuadState(
quad_to_target_transform, child_pass_local_rect, child_pass.get(),
gfx::MaskFilterInfo(blue_rrect));
auto* blue = child_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
blue->SetNew(shared_state_with_rrect, blue_rect, blue_rect, SkColors::kBlue,
false);
SharedQuadState* shared_state_without_rrect = CreateTestSharedQuadState(
gfx::Transform(), child_pass_local_rect, child_pass.get(), gfx::MaskFilterInfo());
gfx::Rect yellow_rect = child_pass_local_rect;
yellow_rect.Offset(30, -60);
auto* yellow = child_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
yellow->SetNew(shared_state_without_rrect, yellow_rect, yellow_rect,
SkColors::kYellow, false);
gfx::Rect white_rect = child_pass_local_rect;
auto* white = child_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
white->SetNew(shared_state_without_rrect, white_rect, white_rect,
SkColors::kWhite, false);
gfx::RRectF rounded_corner_bounds(gfx::RectF(pass_rect), kCornerRadius);
SharedQuadState* pass_shared_state =
CreateTestSharedQuadState(gfx::Transform(), pass_rect, root_pass.get(),
gfx::MaskFilterInfo(rounded_corner_bounds));
CreateTestRenderPassDrawQuad(pass_shared_state, pass_rect, child_pass_id,
root_pass.get());
AggregatedRenderPassList pass_list;
pass_list.push_back(std::move(child_pass));
pass_list.push_back(std::move(root_pass));
base::FilePath path(FILE_PATH_LITERAL("rounded_corner_render_pass_.png"));
path = path.InsertBeforeExtensionASCII(this->renderer_str());
EXPECT_TRUE(this->RunPixelTest(
&pass_list, path, cc::AlphaDiscardingFuzzyPixelOffByOneComparator()));
}
#if BUILDFLAG(IS_IOS)
// TODO(crbug.com/40259140): currently failing on iOS.
#define MAYBE_LinearGradientOnRenderPass DISABLED_LinearGradientOnRenderPass
#else
#define MAYBE_LinearGradientOnRenderPass LinearGradientOnRenderPass
#endif // BUILDFLAG(IS_IOS)
TEST_P(GPURendererPixelTest, MAYBE_LinearGradientOnRenderPass) {
gfx::Rect viewport_rect(this->device_viewport_size_);
constexpr int kCornerRadius = 20;
AggregatedRenderPassId root_pass_id{1};
auto root_pass = CreateTestRootRenderPass(root_pass_id, viewport_rect);
AggregatedRenderPassId child_pass_id{2};
gfx::Rect pass_rect(this->device_viewport_size_);
gfx::Rect child_pass_local_rect = gfx::Rect(pass_rect.size());
gfx::Transform transform_to_root;
transform_to_root.Translate(pass_rect.OffsetFromOrigin());
auto child_pass = CreateTestRenderPass(child_pass_id, child_pass_local_rect,
transform_to_root);
gfx::Rect white_rect = child_pass_local_rect;
SharedQuadState* shared_state_without_rrect =
CreateTestSharedQuadState(gfx::Transform(), child_pass_local_rect,
child_pass.get(), gfx::MaskFilterInfo());
auto* white = child_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
white->SetNew(shared_state_without_rrect, white_rect, white_rect,
SkColors::kWhite, false);
gfx::RRectF rounded_corner_bounds(gfx::RectF(pass_rect), kCornerRadius);
gfx::LinearGradient gradient_mask(330);
gradient_mask.AddStep(/*fraction=*/0, /*alpha=*/0);
gradient_mask.AddStep(.5, 255);
gradient_mask.AddStep(1, 255);
SharedQuadState* pass_shared_state = CreateTestSharedQuadState(
gfx::Transform(), pass_rect, root_pass.get(),
gfx::MaskFilterInfo(rounded_corner_bounds, gradient_mask));
CreateTestRenderPassDrawQuad(pass_shared_state, pass_rect, child_pass_id,
root_pass.get());
AggregatedRenderPassList pass_list;
pass_list.push_back(std::move(child_pass));
pass_list.push_back(std::move(root_pass));
EXPECT_TRUE(this->RunPixelTest(
&pass_list,
base::FilePath(FILE_PATH_LITERAL("linear_gradient_render_pass.png")),
cc::FuzzyPixelComparator().DiscardAlpha().SetErrorPixelsPercentageLimit(
0.6f)));
}
#if BUILDFLAG(IS_IOS)
// TODO(crbug.com/40259140): currently failing on iOS.
#define MAYBE_MultiLinearGradientOnRenderPass \
DISABLED_MultiLinearGradientOnRenderPass
#else
#define MAYBE_MultiLinearGradientOnRenderPass MultiLinearGradientOnRenderPass
#endif // BUILDFLAG(IS_IOS)
TEST_P(GPURendererPixelTest, MAYBE_MultiLinearGradientOnRenderPass) {
gfx::Rect viewport_rect(this->device_viewport_size_);
constexpr int kCornerRadius = 20;
constexpr int kInset = 20;
constexpr int kBlueCornerRadius = 10;
AggregatedRenderPassId root_pass_id{1};
auto root_pass = CreateTestRootRenderPass(root_pass_id, viewport_rect);
AggregatedRenderPassId child_pass_id{2};
gfx::Rect pass_rect(this->device_viewport_size_);
pass_rect.Inset(kInset);
gfx::Rect child_pass_local_rect = gfx::Rect(pass_rect.size());
gfx::Transform transform_to_root;
transform_to_root.Translate(pass_rect.OffsetFromOrigin());
auto child_pass = CreateTestRenderPass(child_pass_id, child_pass_local_rect,
transform_to_root);
gfx::Rect blue_rect = child_pass_local_rect;
gfx::Vector2dF blue_offset_from_target(-30, 40);
gfx::RRectF blue_rrect(gfx::RectF(blue_rect), kBlueCornerRadius);
blue_rrect.Offset(blue_offset_from_target);
gfx::LinearGradient blue_gradient(0);
blue_gradient.AddStep(/*fraction=*/0, /*alpha=*/255);
blue_gradient.AddStep(1, 0);
gfx::Transform quad_to_target_transform;
quad_to_target_transform.Translate(blue_offset_from_target);
SharedQuadState* shared_state_with_rrect = CreateTestSharedQuadState(
quad_to_target_transform, child_pass_local_rect, child_pass.get(),
gfx::MaskFilterInfo(blue_rrect, blue_gradient));
auto* blue = child_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
blue->SetNew(shared_state_with_rrect, blue_rect, blue_rect, SkColors::kBlue,
false);
gfx::Rect white_rect = child_pass_local_rect;
SharedQuadState* shared_state_without_rrect =
CreateTestSharedQuadState(gfx::Transform(), child_pass_local_rect,
child_pass.get(), gfx::MaskFilterInfo());
auto* white = child_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
white->SetNew(shared_state_without_rrect, white_rect, white_rect,
SkColors::kWhite, false);
gfx::RRectF rounded_corner_bounds(gfx::RectF(pass_rect), kCornerRadius);
gfx::LinearGradient gradient_mask(-30);
gradient_mask.AddStep(/*fraction=*/0, /*alpha=*/0);
gradient_mask.AddStep(.5, 255);
gradient_mask.AddStep(1, 255);
SharedQuadState* pass_shared_state = CreateTestSharedQuadState(
gfx::Transform(), pass_rect, root_pass.get(),
gfx::MaskFilterInfo(rounded_corner_bounds, gradient_mask));
CreateTestRenderPassDrawQuad(pass_shared_state, pass_rect, child_pass_id,
root_pass.get());
AggregatedRenderPassList pass_list;
pass_list.push_back(std::move(child_pass));
pass_list.push_back(std::move(root_pass));
EXPECT_TRUE(this->RunPixelTest(
&pass_list,
base::FilePath(
FILE_PATH_LITERAL("multi_linear_gradient_render_pass.png")),
cc::FuzzyPixelComparator().DiscardAlpha().SetErrorPixelsPercentageLimit(
0.6f)));
}
TEST_P(RendererPixelTest, RoundedCornerMultiRadii) {
gfx::Rect viewport_rect(this->device_viewport_size_);
constexpr gfx::RoundedCornersF kCornerRadii(5, 15, 25, 35);
constexpr int kInset = 20;
AggregatedRenderPassId root_pass_id{1};
auto root_pass = CreateTestRootRenderPass(root_pass_id, viewport_rect);
gfx::Rect pass_rect(this->device_viewport_size_);
pass_rect.Inset(kInset);
gfx::RRectF rounded_corner_bounds(gfx::RectF(pass_rect), kCornerRadii);
gfx::Rect blue_rect = pass_rect;
blue_rect.set_height(blue_rect.height() / 2);
gfx::Transform quad_to_target_transform;
SharedQuadState* shared_state_normal = CreateTestSharedQuadState(
quad_to_target_transform, pass_rect, root_pass.get(),
gfx::MaskFilterInfo(rounded_corner_bounds));
auto* blue = root_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
blue->SetNew(shared_state_normal, blue_rect, blue_rect, SkColors::kBlue,
false);
gfx::Rect yellow_rect = blue_rect;
yellow_rect.Offset(0, blue_rect.height());
auto* yellow = root_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
yellow->SetNew(shared_state_normal, yellow_rect, yellow_rect,
SkColors::kYellow, false);
SharedQuadState* sqs_white = CreateTestSharedQuadState(
quad_to_target_transform, viewport_rect, root_pass.get(), gfx::MaskFilterInfo());
gfx::Rect white_rect = gfx::Rect(this->device_viewport_size_);
auto* white = root_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
white->SetNew(sqs_white, white_rect, white_rect, SkColors::kWhite, false);
AggregatedRenderPassList pass_list;
pass_list.push_back(std::move(root_pass));
// Software/skia renderer uses skia rrect to create rounded corner clip.
// This results in a different corner path due to a different anti aliasing
// approach than the fragment shader in gl renderer.
EXPECT_TRUE(this->RunPixelTest(
&pass_list,
base::FilePath(FILE_PATH_LITERAL("rounded_corner_multi_radii.png")),
cc::FuzzyPixelComparator().DiscardAlpha().SetErrorPixelsPercentageLimit(
0.55f)));
}
TEST_P(RendererPixelTest, RoundedCornerMultipleQads) {
const gfx::Rect viewport_rect(this->device_viewport_size_);
constexpr gfx::RoundedCornersF kCornerRadiiUL(5, 0, 0, 0);
constexpr gfx::RoundedCornersF kCornerRadiiUR(0, 15, 0, 0);
constexpr gfx::RoundedCornersF kCornerRadiiLR(0, 0, 25, 0);
constexpr gfx::RoundedCornersF kCornerRadiiLL(0, 0, 0, 35);
constexpr int kInset = 20;
AggregatedRenderPassId root_pass_id{1};
auto root_pass = CreateTestRootRenderPass(root_pass_id, viewport_rect);
gfx::Rect pass_rect(this->device_viewport_size_);
pass_rect.Inset(kInset);
gfx::RRectF rounded_corner_bounds_ul(gfx::RectF(pass_rect), kCornerRadiiUL);
gfx::RRectF rounded_corner_bounds_ur(gfx::RectF(pass_rect), kCornerRadiiUR);
gfx::RRectF rounded_corner_bounds_lr(gfx::RectF(pass_rect), kCornerRadiiLR);
gfx::RRectF rounded_corner_bounds_ll(gfx::RectF(pass_rect), kCornerRadiiLL);
gfx::Rect ul_rect = pass_rect;
ul_rect.set_height(ul_rect.height() / 2);
ul_rect.set_width(ul_rect.width() / 2);
gfx::Rect ur_rect = pass_rect;
ur_rect.set_x(ul_rect.right());
ur_rect.set_width(pass_rect.right() - ur_rect.x());
ur_rect.set_height(ul_rect.height());
gfx::Rect lr_rect = pass_rect;
lr_rect.set_y(ur_rect.bottom());
lr_rect.set_x(ur_rect.x());
lr_rect.set_width(ur_rect.width());
lr_rect.set_height(pass_rect.bottom() - lr_rect.y());
gfx::Rect ll_rect = pass_rect;
ll_rect.set_y(lr_rect.y());
ll_rect.set_width(ul_rect.width());
ll_rect.set_height(lr_rect.height());
SharedQuadState* shared_state_normal_ul =
CreateTestSharedQuadState(gfx::Transform(), pass_rect, root_pass.get(),
gfx::MaskFilterInfo(rounded_corner_bounds_ul));
SharedQuadState* shared_state_normal_ur =
CreateTestSharedQuadState(gfx::Transform(), pass_rect, root_pass.get(),
gfx::MaskFilterInfo(rounded_corner_bounds_ur));
SharedQuadState* shared_state_normal_lr =
CreateTestSharedQuadState(gfx::Transform(), pass_rect, root_pass.get(),
gfx::MaskFilterInfo(rounded_corner_bounds_lr));
SharedQuadState* shared_state_normal_ll =
CreateTestSharedQuadState(gfx::Transform(), pass_rect, root_pass.get(),
gfx::MaskFilterInfo(rounded_corner_bounds_ll));
auto* ul = root_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
auto* ur = root_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
auto* lr = root_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
auto* ll = root_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
ul->SetNew(shared_state_normal_ul, ul_rect, ul_rect, SkColors::kRed, false);
ur->SetNew(shared_state_normal_ur, ur_rect, ur_rect, SkColors::kGreen, false);
lr->SetNew(shared_state_normal_lr, lr_rect, lr_rect, SkColors::kBlue, false);
ll->SetNew(shared_state_normal_ll, ll_rect, ll_rect, SkColors::kYellow,
false);
SharedQuadState* sqs_white = CreateTestSharedQuadState(
gfx::Transform(), viewport_rect, root_pass.get(), gfx::MaskFilterInfo());
gfx::Rect white_rect = gfx::Rect(this->device_viewport_size_);
auto* white = root_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
white->SetNew(sqs_white, white_rect, white_rect, SkColors::kWhite, false);
AggregatedRenderPassList pass_list;
pass_list.push_back(std::move(root_pass));
auto comparator =
cc::FuzzyPixelComparator().DiscardAlpha().SetErrorPixelsPercentageLimit(
0.55f);
EXPECT_TRUE(this->RunPixelTest(
&pass_list,
base::FilePath(FILE_PATH_LITERAL("rounded_corner_multi_quad.png")),
comparator));
}
TEST_P(RendererPixelTest, BlurExpandsBounds) {
#if defined(MEMORY_SANITIZER)
// TODO(crbug.com/40266622): Re-enable this test.
// Skia Vulkan renderer had problems with this test when MSAN was enabled.
if (renderer_type() == RendererType::kSkiaVk) {
GTEST_SKIP();
}
#endif // defined(MEMORY_SANITIZER)
gfx::Rect viewport_rect(this->device_viewport_size_);
AggregatedRenderPassId root_pass_id{1};
auto root_pass = CreateTestRootRenderPass(root_pass_id, viewport_rect);
AggregatedRenderPassId child_pass_id{2};
gfx::Rect pass_rect(this->device_viewport_size_);
auto child_pass =
CreateTestRenderPass(child_pass_id, pass_rect, gfx::Transform());
// Add 60px blur to child pass.
child_pass->filters.Append(cc::FilterOperation::CreateBlurFilter(20.0f));
// Add blue and yellow rect to child render pass.
SharedQuadState* shared_state = CreateTestSharedQuadState(
gfx::Transform(), viewport_rect, child_pass.get(), gfx::MaskFilterInfo());
gfx::Rect blue_rect(0, 0, viewport_rect.width(), viewport_rect.height() / 2);
auto* blue = child_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
blue->SetNew(shared_state, blue_rect, blue_rect, SkColors::kBlue, false);
gfx::Rect yellow_rect(0, viewport_rect.height() / 2, viewport_rect.width(),
viewport_rect.height() / 2);
auto* yellow = child_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
yellow->SetNew(shared_state, yellow_rect, yellow_rect, SkColors::kYellow,
false);
// Transform child pass off the screen, but within the blur size.
gfx::Transform child_transform;
child_transform.Translate(viewport_rect.width() + 5, 0);
SharedQuadState* pass_shared_state = CreateTestSharedQuadState(
child_transform, pass_rect, root_pass.get(), gfx::MaskFilterInfo());
auto* render_pass_quad =
root_pass->CreateAndAppendDrawQuad<AggregatedRenderPassDrawQuad>();
render_pass_quad->SetNew(pass_shared_state, pass_rect, pass_rect,
child_pass_id, kInvalidResourceId, gfx::RectF(),
gfx::Size(), gfx::RectF(pass_rect), false);
// White background underneath
SharedQuadState* blank_state = CreateTestSharedQuadState(
gfx::Transform(), viewport_rect, root_pass.get(), gfx::MaskFilterInfo());
SolidColorDrawQuad* color_quad =
root_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
color_quad->SetNew(blank_state, viewport_rect, viewport_rect,
SkColors::kWhite, false);
AggregatedRenderPassList pass_list;
pass_list.push_back(std::move(child_pass));
pass_list.push_back(std::move(root_pass));
base::FilePath expected_result =
base::FilePath(FILE_PATH_LITERAL("blur_expands_bounds.png"));
if (is_software_renderer()) {
expected_result = expected_result.InsertBeforeExtensionASCII("_sw");
} else if (is_skia_graphite()) {
expected_result = expected_result.InsertBeforeExtensionASCII(kGraphiteStr);
}
EXPECT_TRUE(this->RunPixelTest(
&pass_list, expected_result,
// Allow 55/200 ~= 28% of pixels to be off by a small amount in each
// channel to permit some small difference between renderers.
cc::FuzzyPixelComparator()
.SetAbsErrorLimit(2.0f)
.SetErrorPixelsPercentageLimit(28.f)));
}
class RendererPixelTestWithOverdrawFeedback : public VizPixelTestWithParam {
protected:
void SetUp() override {
this->debug_settings_.show_overdraw_feedback = true;
VizPixelTestWithParam::SetUp();
}
};
TEST_P(RendererPixelTestWithOverdrawFeedback, TranslucentRectangles) {
// TODO(crbug.com/40279711): Enable this test once issue is fixed for
// Graphite.
if (is_skia_graphite()) {
GTEST_SKIP();
}
gfx::Rect rect(this->device_viewport_size_);
AggregatedRenderPassId id{1};
gfx::Transform transform_to_root;
auto pass = CreateTestRenderPass(id, rect, transform_to_root);
CreateTestAxisAlignedQuads(rect, SkColor4f{0.267f, 0.267f, 0.267f, 0.063f},
SkColor4f{0.8f, 0.8f, 0.8f, 0.063f}, true, false,
pass.get());
gfx::Transform bg_quad_to_target_transform;
SharedQuadState* bg_shared_state =
CreateTestSharedQuadState(bg_quad_to_target_transform, rect, pass.get(),
gfx::MaskFilterInfo());
auto* bg = pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
bg->SetNew(bg_shared_state, rect, rect, SkColors::kBlack, false);
AggregatedRenderPassList pass_list;
pass_list.push_back(std::move(pass));
// TODO(xing.xu): investigate why overdraw feedback has small difference
// (http://crbug.com/909971)
EXPECT_TRUE(this->RunPixelTest(
&pass_list,
base::FilePath(FILE_PATH_LITERAL("translucent_rectangles.png")),
cc::FuzzyPixelComparator().SetErrorPixelsPercentageLimit(2.f)));
}
INSTANTIATE_TEST_SUITE_P(,
RendererPixelTestWithOverdrawFeedback,
testing::ValuesIn(GetGpuRendererTypes()),
testing::PrintToStringParamName());
// GetGpuRendererTypes() can return an empty list, e.g. on Fuchsia ARM64.
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(
RendererPixelTestWithOverdrawFeedback);
class RendererPixelTestColorConversion : public VizPixelTestWithParam {
public:
RendererPixelTestColorConversion() {
// Set a color space that is not suitable for blending to ensure we go
// through the color conversion code paths.
this->display_color_spaces_ =
gfx::DisplayColorSpaces(gfx::ColorSpace::CreateSCRGBLinear80Nits());
this->display_color_spaces_.SetSDRMaxLuminanceNits(80.f);
this->display_color_spaces_.SetOutputFormats(SinglePlaneFormat::kRGBA_F16,
SinglePlaneFormat::kRGBA_F16);
}
};
// Check that render pass updates do not blend with previous frames.
TEST_P(RendererPixelTestColorConversion,
RenderPassClearsUpdatesWithHdrContent) {
gfx::Rect rect(this->device_viewport_size_);
SkColor4f semi_transparent_white = SkColors::kWhite;
semi_transparent_white.fA = 0.5;
const int value = 255 * semi_transparent_white.fA;
std::vector<SkColor> expected_output_colors(
rect.width() * rect.height(), SkColorSetARGB(255, value, value, value));
// Draw two frames with semi-transparent content. Both frames should result in
// the same image.
for (int i = 0; i < 2; i++) {
SCOPED_TRACE(base::StringPrintf("Frame %d", i));
AggregatedRenderPassId id{1};
auto pass = CreateTestRootRenderPass(id, rect);
pass->content_color_usage = gfx::ContentColorUsage::kHDR;
SharedQuadState* shared_state = CreateTestSharedQuadState(
gfx::Transform(), rect, pass.get(), gfx::MaskFilterInfo());
auto* color_quad = pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
color_quad->SetNew(shared_state, rect, rect, semi_transparent_white, false);
AggregatedRenderPassList pass_list;
pass_list.push_back(std::move(pass));
EXPECT_TRUE(this->RunPixelTest(&pass_list, &expected_output_colors,
cc::AlphaDiscardingExactPixelComparator()));
}
}
INSTANTIATE_TEST_SUITE_P(,
RendererPixelTestColorConversion,
testing::ValuesIn(GetGpuRendererTypes()),
testing::PrintToStringParamName());
// GetGpuRendererTypes() can return an empty list, e.g. on Fuchsia ARM64.
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(RendererPixelTestColorConversion);
#if BUILDFLAG(IS_WIN)
class VideoPixelRendererPixelTestColorConversion
: public VideoRendererPixelTestBase,
public testing::WithParamInterface<RendererType> {
public:
VideoPixelRendererPixelTestColorConversion()
: VideoRendererPixelTestBase(GetParam()) {}
void SetUp() override {
// Set a color space that is not suitable for blending to ensure we go
// through the color conversion code paths.
this->display_color_spaces_ =
gfx::DisplayColorSpaces(gfx::ColorSpace::CreateSCRGBLinear80Nits());
this->display_color_spaces_.SetSDRMaxLuminanceNits(80.f);
this->display_color_spaces_.SetOutputFormats(SinglePlaneFormat::kRGBA_F16,
SinglePlaneFormat::kRGBA_F16);
// Allow non-root render passes to have the above non-suitable-for-blending
// color space by being scanout.
renderer_settings_.force_non_scanout_backing_for_pixel_tests = true;
features_.InitAndEnableFeature(features::kDelegatedCompositing);
VideoRendererPixelTestBase::SetUp();
}
private:
base::test::ScopedFeatureList features_;
};
// This checks the correct color conversion is happening in the following case:
// a non-root render pass has a texture quad that requires a color conversion
// filter, but no quads in the render pass require blending.
//
// In this case, the color conversion layer is elided, but we choose the wrong
// "destination" color space for the color conversion filter when we draw the
// texture quad.
// See: crbug.com/397995970
TEST_P(VideoPixelRendererPixelTestColorConversion,
RenderPassWithHdrVideoDoesntNeedBlending) {
// Create a test frame that embeds a pass which:
// - contains a texture quad that requires a color conversion filter and
// - needs blending, iff `child_pass_needs_blending`.
auto CreateFrame =
[&](bool child_pass_needs_blending) -> AggregatedRenderPassList {
const gfx::Rect rect(this->device_viewport_size_);
CompositorRenderPassId id{1};
auto child_pass = CreateTestRootRenderPass(id, rect);
auto color_space = gfx::ColorSpace(
gfx::ColorSpace::PrimaryID::BT2020, gfx::ColorSpace::TransferID::PQ,
gfx::ColorSpace::MatrixID::BT2020_NCL, gfx::ColorSpace::RangeID::FULL);
CreateTestMultiplanarVideoDrawQuad(
TestVideoFrameBuilder(media::PIXEL_FORMAT_I420, color_space,
kUnitSquare, rect.size(), rect.size())
.DrawSolid(144, 54, 34),
/*alpha_value=*/255, gfx::Transform(), gfx::MaskFilterInfo(),
/*sorting_context_id=*/0, child_pass.get(),
this->video_resource_updater_.get(), rect, rect);
AggregatedRenderPassList pass_list;
AggregatedRenderPassId hdr_child_id{2};
{
auto child_pass_copy = cc::CopyToAggregatedRenderPass(
child_pass.get(), hdr_child_id, gfx::ContentColorUsage::kHDR,
this->resource_provider_.get(), this->child_resource_provider_.get(),
this->child_context_provider_.get());
// Make `is_scanout == true` on Windows for non-root pass.
// See: `DirectRenderer::CalculateRenderPassRequirements`
{
EXPECT_TRUE(
base::FeatureList::IsEnabled(features::kDelegatedCompositing));
child_pass_copy->is_from_surface_root_pass = true;
child_pass_copy->will_backing_be_read_by_viz = false;
}
// Make the HDR child render pass not use the color conversion layer if
// it doesn't contain quads that require blending.
for (auto* quad : child_pass_copy->quad_list) {
quad->needs_blending = child_pass_needs_blending;
}
pass_list.push_back(std::move(child_pass_copy));
}
// Add a root pass that embeds the problematic pass. The root render pass
// color space is handled specially and we are testing the non-root case.
{
AggregatedRenderPassId root_id{1};
auto root_pass = CreateTestRootRenderPass(root_id, rect);
root_pass->content_color_usage = gfx::ContentColorUsage::kSRGB;
SharedQuadState* shared_state = CreateTestSharedQuadState(
gfx::Transform(), rect, root_pass.get(), gfx::MaskFilterInfo());
CreateTestRenderPassDrawQuad(shared_state, rect, hdr_child_id,
root_pass.get());
pass_list.push_back(std::move(root_pass));
}
return pass_list;
};
// Render the child pass with blending to use as a baseline, since we expect
// the output to be the same in both cases.
AggregatedRenderPassList pass_list_with_blending =
CreateFrame(/*child_pass_needs_blending=*/true);
AggregatedRenderPassList pass_list_without_blending =
CreateFrame(/*child_pass_needs_blending=*/false);
EXPECT_TRUE(this->RunPixelTest(&pass_list_without_blending,
&pass_list_with_blending,
cc::AlphaDiscardingExactPixelComparator()));
}
INSTANTIATE_TEST_SUITE_P(,
VideoPixelRendererPixelTestColorConversion,
testing::ValuesIn(GetGpuRendererTypes()),
testing::PrintToStringParamName());
#endif
using PrimaryID = gfx::ColorSpace::PrimaryID;
using TransferID = gfx::ColorSpace::TransferID;
enum class NamedColorSpace {
kBt709Bt709,
kWideGamutColorSpinGamma28,
kBt709LinearHdr,
kBt709Linear,
kBt709SrgbHdr,
kBt709Srgb,
kXyzD50Linear,
kXyzD50SrgbHdr,
kHdr10,
kHlg,
kScrgbLinear80Nits,
};
const char* ToString(NamedColorSpace named_color_space) {
// Note that these names are used in the test parametrization stringification
// and thus used in test filter files.
switch (named_color_space) {
case NamedColorSpace::kBt709Bt709:
return "Bt709Bt709";
case NamedColorSpace::kWideGamutColorSpinGamma28:
return "WideGamutColorSpinGamma28";
case NamedColorSpace::kBt709LinearHdr:
return "Bt709LinearHdr";
case NamedColorSpace::kBt709Linear:
return "Bt709Linear";
case NamedColorSpace::kBt709SrgbHdr:
return "Bt709SrgbHdr";
case NamedColorSpace::kBt709Srgb:
return "Bt709Srgb";
case NamedColorSpace::kXyzD50Linear:
return "XyzD50Linear";
case NamedColorSpace::kXyzD50SrgbHdr:
return "XyzD50SrgbHdr";
case NamedColorSpace::kHdr10:
return "Hdr10";
case NamedColorSpace::kHlg:
return "Hlg";
case NamedColorSpace::kScrgbLinear80Nits:
return "ScrgbLinear80Nits";
}
NOTREACHED();
}
gfx::ColorSpace GetColorSpace(NamedColorSpace named_color_space) {
switch (named_color_space) {
case NamedColorSpace::kBt709Bt709:
return gfx::ColorSpace(PrimaryID::BT709, TransferID::BT709);
case NamedColorSpace::kWideGamutColorSpinGamma28:
return gfx::ColorSpace(PrimaryID::WIDE_GAMUT_COLOR_SPIN,
TransferID::GAMMA28);
case NamedColorSpace::kBt709LinearHdr:
return gfx::ColorSpace(PrimaryID::BT709, TransferID::LINEAR_HDR);
case NamedColorSpace::kBt709Linear:
return gfx::ColorSpace(PrimaryID::BT709, TransferID::LINEAR);
case NamedColorSpace::kBt709SrgbHdr:
return gfx::ColorSpace(PrimaryID::BT709, TransferID::SRGB_HDR);
case NamedColorSpace::kBt709Srgb:
return gfx::ColorSpace(PrimaryID::BT709, TransferID::SRGB);
case NamedColorSpace::kXyzD50Linear:
return gfx::ColorSpace(PrimaryID::XYZ_D50, TransferID::LINEAR);
case NamedColorSpace::kXyzD50SrgbHdr:
return gfx::ColorSpace(PrimaryID::XYZ_D50, TransferID::SRGB_HDR);
case NamedColorSpace::kHdr10:
return gfx::ColorSpace::CreateHDR10();
case NamedColorSpace::kHlg:
return gfx::ColorSpace::CreateHLG();
case NamedColorSpace::kScrgbLinear80Nits:
return gfx::ColorSpace::CreateSCRGBLinear80Nits();
}
NOTREACHED();
}
class ColorTransformPixelTest
: public VizPixelTest,
public testing::WithParamInterface<
std::tuple<RendererType, NamedColorSpace, NamedColorSpace, bool>> {
public:
static std::string GetParamName(
const testing::TestParamInfo<ParamType>& info) {
return base::StringPrintf(
"%s_%s_%s_%s", testing::PrintToString(std::get<0>(info.param)),
ToString(std::get<1>(info.param)), ToString(std::get<2>(info.param)),
std::get<3>(info.param) ? "premul" : "unpremul");
}
ColorTransformPixelTest() : VizPixelTest(std::get<0>(GetParam())) {
// Note that this size of 17 is not random -- it is chosen to match the
// size of LUTs that are created. If we did not match the LUT size exactly,
// then the error for LUT based transforms is much larger.
this->device_viewport_size_ = gfx::Size(17, 5);
this->src_color_space_ = GetColorSpace(std::get<1>(GetParam()));
this->dst_color_space_ = GetColorSpace(std::get<2>(GetParam()));
this->display_color_spaces_ =
gfx::DisplayColorSpaces(this->dst_color_space_);
if (this->dst_color_space_.IsWide()) {
this->display_color_spaces_.SetOutputFormats(
SinglePlaneFormat::kRGBA_F16, SinglePlaneFormat::kRGBA_F16);
}
this->premultiplied_alpha_ = std::get<3>(GetParam());
}
void Basic() {
gfx::Rect rect(this->device_viewport_size_);
std::vector<uint8_t> input_colors(4 * rect.width() * rect.height(), 0);
std::vector<SkColor> expected_output_colors(rect.width() * rect.height());
// Set the input data to be:
// Row 0: Gradient of red from 0 to 255
// Row 1: Gradient of green from 0 to 255
// Row 2: Gradient of blue from 0 to 255
// Row 3: Gradient of grey from 0 to 255
// Row 4: Gradient of alpha from 0 to 255 with mixed colors.
for (int x = 0; x < rect.width(); ++x) {
int gradient_value = (x * 255) / (rect.width() - 1);
for (int y = 0; y < rect.height(); ++y) {
uint8_t* pixel = &input_colors[4 * (x + rect.width() * y)];
UNSAFE_TODO(pixel[3]) = 255;
if (y < 3) {
UNSAFE_TODO(pixel[y]) = gradient_value;
} else if (y == 3) {
pixel[0] = UNSAFE_TODO(pixel[1]) = UNSAFE_TODO(pixel[2]) =
gradient_value;
} else {
if (this->premultiplied_alpha_) {
UNSAFE_TODO(pixel[x % 3]) = gradient_value;
UNSAFE_TODO(pixel[3]) = gradient_value;
} else {
UNSAFE_TODO(pixel[x % 3]) = 0xFF;
UNSAFE_TODO(pixel[3]) = gradient_value;
}
}
}
}
gfx::ColorTransform::Options options;
options.tone_map_pq_and_hlg_to_dst = true;
gfx::ColorTransform::RuntimeOptions runtime_options;
runtime_options.dst_sdr_max_luminance_nits =
this->display_color_spaces_.GetSDRMaxLuminanceNits();
// Ensure our expected color contains the texture color blended in a
// blending-suitable space, if a color conversion was required.
const gfx::ColorSpace blend_color_space =
this->display_color_spaces_.GetRasterAndCompositeColorSpace(
this->dst_color_space_.GetContentColorUsage());
std::unique_ptr<gfx::ColorTransform> transform_src_to_blend =
gfx::ColorTransform::NewColorTransform(this->src_color_space_,
blend_color_space, options);
// If |dst_color_space_| is suitable for blending, this is a no-op.
std::unique_ptr<gfx::ColorTransform> transform_blend_to_dst =
gfx::ColorTransform::NewColorTransform(blend_color_space,
this->dst_color_space_, options);
for (size_t i = 0; i < expected_output_colors.size(); ++i) {
gfx::ColorTransform::TriStim color;
color.set_x(input_colors[4 * i + 0] / 255.f);
color.set_y(input_colors[4 * i + 1] / 255.f);
color.set_z(input_colors[4 * i + 2] / 255.f);
float alpha = input_colors[4 * i + 3] / 255.f;
if (this->premultiplied_alpha_ && alpha > 0.0) {
color.Scale(1.0f / alpha);
}
transform_src_to_blend->Transform(&color, 1, runtime_options);
// Simulate blending this color onto its black background in
// |blend_color_space|, which may be different than |dst_color_space_|.
color.Scale(alpha);
transform_blend_to_dst->Transform(&color, 1, runtime_options);
color.set_x(std::clamp(color.x(), 0.0f, 1.0f));
color.set_y(std::clamp(color.y(), 0.0f, 1.0f));
color.set_z(std::clamp(color.z(), 0.0f, 1.0f));
expected_output_colors[i] =
SkColorSetARGB(255, static_cast<size_t>(255.f * color.x() + 0.5f),
static_cast<size_t>(255.f * color.y() + 0.5f),
static_cast<size_t>(255.f * color.z() + 0.5f));
}
AggregatedRenderPassId id{1};
auto pass = CreateTestRootRenderPass(id, rect);
// Append a quad to execute the transform.
{
SharedQuadState* shared_state =
CreateTestSharedQuadState(gfx::Transform(), rect, pass.get(),
gfx::MaskFilterInfo());
ResourceId resource = CreateGpuResource(
this->child_context_provider_, this->child_resource_provider_.get(),
rect.size(), SinglePlaneFormat::kRGBA_8888,
premultiplied_alpha_ ? kPremul_SkAlphaType : kUnpremul_SkAlphaType,
this->src_color_space_, input_colors);
// Return the mapped resource id.
std::unordered_map<ResourceId, ResourceId, ResourceIdHasher>
resource_map = cc::SendResourceAndGetChildToParentMap(
{resource}, this->resource_provider_.get(),
this->child_resource_provider_.get(),
this->child_context_provider_->SharedImageInterface());
ResourceId mapped_resource = resource_map[resource];
bool needs_blending = true;
const gfx::PointF uv_top_left(0.0f, 0.0f);
const gfx::PointF uv_bottom_right(1.0f, 1.0f);
const bool nearest_neighbor = false;
auto* quad = pass->CreateAndAppendDrawQuad<TextureDrawQuad>();
quad->SetNew(shared_state, rect, rect, needs_blending, mapped_resource,
uv_top_left, uv_bottom_right, SkColors::kBlack,
nearest_neighbor,
/*secure_output=*/false, gfx::ProtectedVideoType::kClear);
auto* color_quad = pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
color_quad->SetNew(shared_state, rect, rect, SkColors::kBlack, false);
}
AggregatedRenderPassList pass_list;
pass_list.push_back(std::move(pass));
// Allow a difference of 2 bytes in comparison for most cases.
float avg_abs_error_limit = 2.0f;
int max_abs_error_limit = 2;
#if BUILDFLAG(IS_FUCHSIA)
if (this->src_color_space_.GetTransferID() == TransferID::PQ) {
// Fuchsia+SwiftShader/Vulkan has higher error on some pixels with HDR
// color spaces. See https://crbug.com/1312141.
max_abs_error_limit = 5;
}
#endif
auto comparator = cc::FuzzyPixelComparator()
.SetErrorPixelsPercentageLimit(100.f)
.SetAvgAbsErrorLimit(avg_abs_error_limit)
.SetAbsErrorLimit(max_abs_error_limit);
EXPECT_TRUE(
this->RunPixelTest(&pass_list, &expected_output_colors, comparator));
}
base::test::ScopedFeatureList features_;
gfx::ColorSpace src_color_space_;
gfx::ColorSpace dst_color_space_;
bool premultiplied_alpha_ = false;
};
// TODO(https://crbug.com/40922049): use-of-uninitialized-value
#if defined(MEMORY_SANITIZER)
#define MAYBE_Basic DISABLED_Basic
#else
#define MAYBE_Basic Basic
#endif
TEST_P(ColorTransformPixelTest, MAYBE_Basic) {
#if BUILDFLAG(IS_LINUX) && defined(THREAD_SANITIZER)
// Test is flaking with failed large allocations under TSAN when using
// SkiaRenderer with GL backend. See https://crbug.com/1320955.
if (renderer_type() == RendererType::kSkiaGL)
return;
#endif
Basic();
}
NamedColorSpace src_color_spaces[] = {
NamedColorSpace::kBt709Bt709, NamedColorSpace::kWideGamutColorSpinGamma28,
NamedColorSpace::kBt709Linear, NamedColorSpace::kBt709Srgb,
NamedColorSpace::kBt709SrgbHdr, NamedColorSpace::kBt709LinearHdr,
NamedColorSpace::kHdr10,
};
NamedColorSpace dst_color_spaces[] = {
NamedColorSpace::kBt709Bt709, NamedColorSpace::kWideGamutColorSpinGamma28,
NamedColorSpace::kBt709Linear, NamedColorSpace::kBt709Srgb,
NamedColorSpace::kBt709SrgbHdr, NamedColorSpace::kBt709LinearHdr,
};
NamedColorSpace intermediate_color_spaces[] = {
NamedColorSpace::kXyzD50Linear,
NamedColorSpace::kXyzD50SrgbHdr,
};
INSTANTIATE_TEST_SUITE_P(
FromColorSpace,
ColorTransformPixelTest,
testing::Combine(testing::ValuesIn(GetGpuRendererTypes()),
testing::ValuesIn(src_color_spaces),
testing::ValuesIn(intermediate_color_spaces),
testing::Bool()),
&ColorTransformPixelTest::GetParamName);
INSTANTIATE_TEST_SUITE_P(
ToColorSpace,
ColorTransformPixelTest,
testing::Combine(testing::ValuesIn(GetGpuRendererTypes()),
testing::ValuesIn(intermediate_color_spaces),
testing::ValuesIn(dst_color_spaces),
testing::Bool()),
&ColorTransformPixelTest::GetParamName);
// Test cases that simulate HDR content with tone mapping, which may require
// color conversion when the destination color space is not suitable for
// blending.
INSTANTIATE_TEST_SUITE_P(
HdrVideoCases,
ColorTransformPixelTest,
testing::Combine(testing::ValuesIn(GetGpuRendererTypes()),
testing::ValuesIn({
NamedColorSpace::kBt709SrgbHdr,
NamedColorSpace::kHdr10,
NamedColorSpace::kHlg,
}),
testing::ValuesIn({
NamedColorSpace::kBt709SrgbHdr,
NamedColorSpace::kScrgbLinear80Nits,
}),
testing::Bool()),
&ColorTransformPixelTest::GetParamName);
// GetGpuRendererTypes() can return an empty list, e.g. on Fuchsia ARM64.
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(ColorTransformPixelTest);
class DelegatedInkTest : public VizPixelTestWithParam,
public DelegatedInkPointPixelTestHelper {
public:
void SetUp() override {
// Partial swap must be enabled or else the test will pass even if the
// delegated ink trail damage rect is wrong, because the whole frame is
// always redrawn otherwise.
renderer_settings_.partial_swap_enabled = true;
VizPixelTestWithParam::SetUp();
EXPECT_TRUE(VizPixelTestWithParam::renderer_->use_partial_swap());
SetRendererAndCreateInkRenderer(VizPixelTestWithParam::renderer_.get());
}
void TearDown() override {
DropRenderer();
VizPixelTestWithParam::TearDown();
}
std::unique_ptr<AggregatedRenderPass> CreateTestRootRenderPass(
AggregatedRenderPassId id,
const gfx::Rect& output_rect,
const gfx::Rect& damage_rect) {
auto pass = std::make_unique<AggregatedRenderPass>();
const gfx::Transform transform_to_root_target;
pass->SetNew(id, output_rect, damage_rect, transform_to_root_target);
return pass;
}
bool DrawAndTestTrail(base::FilePath file, int render_pass_id) {
gfx::Rect rect(this->device_viewport_size_);
// Minimize the root render pass damage rect so that it has to be expanded
// by the delegated ink trail damage rect to confirm that it is the right
// size to remove old trails and add new ones.
gfx::Rect damage_rect(0, 0, 1, 1);
AggregatedRenderPassId id{static_cast<uint64_t>(render_pass_id)};
std::unique_ptr<AggregatedRenderPass> pass =
CreateTestRootRenderPass(id, rect, damage_rect);
SharedQuadState* shared_state = CreateTestSharedQuadState(
gfx::Transform(), rect, pass.get(), gfx::MaskFilterInfo());
SolidColorDrawQuad* color_quad =
pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
color_quad->SetNew(shared_state, rect, rect, SkColors::kWhite, false);
AggregatedRenderPassList pass_list;
pass_list.push_back(std::move(pass));
return this->RunPixelTest(
&pass_list, file, cc::AlphaDiscardingFuzzyPixelOffByOneComparator());
}
protected:
base::test::ScopedFeatureList feature_list_;
};
INSTANTIATE_TEST_SUITE_P(,
DelegatedInkTest,
testing::ValuesIn(GetGpuRendererTypes()),
testing::PrintToStringParamName());
// GetGpuRendererTypes() can return an empty list, e.g. on Fuchsia ARM64.
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(DelegatedInkTest);
class DelegatedInkWithPredictionTest : public DelegatedInkTest {};
INSTANTIATE_TEST_SUITE_P(,
DelegatedInkWithPredictionTest,
testing::ValuesIn(GetGpuRendererTypes()),
testing::PrintToStringParamName());
// GetGpuRendererTypes() can return an empty list, e.g. on Fuchsia ARM64.
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(DelegatedInkWithPredictionTest);
// Draw a single trail and erase it, making sure that no bits of trail are left
// behind.
TEST_P(DelegatedInkWithPredictionTest, DrawOneTrailAndErase) {
// Send some DelegatedInkPoints, numbers arbitrary. This will predict no
// points, so a trail made of 3 points will be drawn.
const gfx::PointF kFirstPoint(10, 10);
const base::TimeTicks kFirstTimestamp = base::TimeTicks::Now();
CreateAndSendPoint(kFirstPoint, kFirstTimestamp);
CreateAndSendPointFromLastPoint(gfx::PointF(75, 62));
CreateAndSendPointFromLastPoint(gfx::PointF(124, 45));
// Provide the metadata required to draw the trail, matching the first
// DelegatedInkPoint sent.
CreateAndSendMetadata(kFirstPoint, 3.5f, SkColors::kBlack, kFirstTimestamp,
gfx::RectF(0, 0, 175, 172), /*render_pass_id=*/1);
// Confirm that the trail was drawn. Test three times as
// the trail will persist for two more frames before being erased.
base::FilePath expected_result =
base::FilePath(FILE_PATH_LITERAL("delegated_ink_one_trail.png"));
if (is_skia_graphite()) {
expected_result = expected_result.InsertBeforeExtensionASCII(kGraphiteStr);
}
EXPECT_TRUE(DrawAndTestTrail(expected_result, /*render_pass_id=*/1));
// The metadata should have been cleared after drawing, so confirm that there
// is no trail after another draw.
EXPECT_TRUE(DrawAndTestTrail(base::FilePath(FILE_PATH_LITERAL("white.png")),
/*render_pass_id=*/1));
}
// Confirm that drawing a second trail completely removes the first trail.
TEST_P(DelegatedInkWithPredictionTest, DrawTwoTrailsAndErase) {
// Numbers chosen arbitrarily. No points will be predicted, so a trail made of
// 2 points will be drawn.
const gfx::PointF kFirstPoint(140, 48);
const base::TimeTicks kFirstTimestamp = base::TimeTicks::Now();
CreateAndSendPoint(kFirstPoint, kFirstTimestamp);
CreateAndSendPointFromLastPoint(gfx::PointF(115, 85));
// Provide the metadata required to draw the trail, numbers matching the first
// DelegatedInkPoint sent.
CreateAndSendMetadata(kFirstPoint, 8.2f, SkColors::kMagenta, kFirstTimestamp,
gfx::RectF(0, 0, 200, 200), /*render_pass_id=*/1);
// Confirm that the trail was drawn correctly.
base::FilePath expected_result =
base::FilePath(FILE_PATH_LITERAL("delegated_ink_two_trails_first.png"));
if (is_skia_graphite()) {
expected_result = expected_result.InsertBeforeExtensionASCII(kGraphiteStr);
}
EXPECT_TRUE(DrawAndTestTrail(expected_result, /*render_pass_id=*/1));
// Now provide new metadata and points to draw a new trail. Just use the last
// point draw above as the starting point for the new trail. One point will
// be predicted, so a trail consisting of 4 points will be drawn.
CreateAndSendMetadataFromLastPoint();
CreateAndSendPointFromLastPoint(gfx::PointF(134, 100));
CreateAndSendPointFromLastPoint(gfx::PointF(150, 81.44f));
// Confirm the first trail is gone and only the second remains.
base::FilePath expected_result_second =
base::FilePath(FILE_PATH_LITERAL("delegated_ink_two_trails_second.png"));
if (is_skia_graphite()) {
expected_result_second =
expected_result_second.InsertBeforeExtensionASCII(kGraphiteStr);
}
EXPECT_TRUE(DrawAndTestTrail(expected_result_second, /*render_pass_id=*/1));
// Confirm all trails are gone.
EXPECT_TRUE(DrawAndTestTrail(base::FilePath(FILE_PATH_LITERAL("white.png")),
/*render_pass_id=*/1));
}
// Confirm that the trail can't be drawn beyond the presentation area.
TEST_P(DelegatedInkWithPredictionTest, TrailExtendsBeyondPresentationArea) {
const gfx::PointF kFirstPoint(50.2f, 89.999f);
const base::TimeTicks kFirstTimestamp = base::TimeTicks::Now();
// Send points such that some extend beyond the presentation area to confirm
// that the trail is clipped correctly. One point will be predicted, so the
// trail will be made of 9 points.
CreateAndSendPoint(kFirstPoint, kFirstTimestamp);
CreateAndSendPointFromLastPoint(gfx::PointF(80.7f, 149.6f));
CreateAndSendPointFromLastPoint(gfx::PointF(128.999f, 110.01f));
CreateAndSendPointFromLastPoint(gfx::PointF(50, 50));
CreateAndSendPointFromLastPoint(gfx::PointF(10.1f, 30.3f));
CreateAndSendPointFromLastPoint(gfx::PointF(29.98f, 66));
CreateAndSendPointFromLastPoint(gfx::PointF(52.3456f, 2.31f));
CreateAndSendPointFromLastPoint(gfx::PointF(97, 36.9f));
const gfx::RectF kPresentationArea(30, 30, 100, 100);
CreateAndSendMetadata(kFirstPoint, 15.22f, SkColors::kCyan, kFirstTimestamp,
kPresentationArea, /*render_pass_id=*/1);
base::FilePath expected_result = base::FilePath(FILE_PATH_LITERAL(
"delegated_ink_trail_clipped_by_presentation_area.png"));
if (is_skia_graphite()) {
expected_result = expected_result.InsertBeforeExtensionASCII(kGraphiteStr);
}
EXPECT_TRUE(DrawAndTestTrail(expected_result, /*render_pass_id=*/1));
}
// Confirm that the trail appears on top of everything, including batched quads
// that are drawn as part of the call to FinishDrawingRenderPass.
TEST_P(DelegatedInkWithPredictionTest, DelegatedInkTrailAfterBatchedQuads) {
gfx::Rect rect(this->device_viewport_size_);
AggregatedRenderPassId id{1};
auto pass = CreateTestRootRenderPass(id, rect, rect);
SharedQuadState* shared_state = CreateTestSharedQuadState(
gfx::Transform(), rect, pass.get(), gfx::MaskFilterInfo());
CreateTestTextureDrawQuad(
!is_software_renderer(), gfx::Rect(this->device_viewport_size_),
SkColor4f::FromColor(SkColorSetARGB(128, 0, 255, 0)), // Texel color.
SkColors::kTransparent, // Background color.
true, // Premultiplied alpha.
shared_state, this->resource_provider_.get(),
this->child_resource_provider_.get(), this->child_context_provider_,
pass.get());
auto* color_quad = pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
color_quad->SetNew(shared_state, rect, rect, SkColors::kWhite, false);
AggregatedRenderPassList pass_list;
pass_list.push_back(std::move(pass));
const gfx::PointF kFirstPoint(34.f, 72.f);
const base::TimeTicks kFirstTimestamp = base::TimeTicks::Now();
CreateAndSendPoint(kFirstPoint, kFirstTimestamp);
CreateAndSendPointFromLastPoint(gfx::PointF(79, 101));
CreateAndSendPointFromLastPoint(gfx::PointF(134, 114));
const gfx::RectF kPresentationArea(0, 0, 200, 200);
CreateAndSendMetadata(kFirstPoint, 7.77f, SkColors::kDkGray, kFirstTimestamp,
kPresentationArea, /*render_pass_id=*/1);
base::FilePath expected_result = base::FilePath(
FILE_PATH_LITERAL("delegated_ink_trail_on_batched_quads.png"));
if (is_skia_graphite()) {
expected_result = expected_result.InsertBeforeExtensionASCII(kGraphiteStr);
}
EXPECT_TRUE(
this->RunPixelTest(&pass_list, expected_result,
cc::AlphaDiscardingFuzzyPixelOffByOneComparator()));
}
// Delegated ink trail is drawn on a non root render pass, with the correct
// transforms.
TEST_P(DelegatedInkWithPredictionTest, SimpleTrailNonRootRenderPass) {
gfx::Rect viewport_rect(this->device_viewport_size_);
constexpr int kInset = 20;
constexpr int kTargetPassId = 2;
AggregatedRenderPassId root_pass_id{1};
auto root_pass =
CreateTestRootRenderPass(root_pass_id, viewport_rect, gfx::Rect());
AggregatedRenderPassId child_pass_id{kTargetPassId};
gfx::Rect pass_rect(this->device_viewport_size_);
pass_rect.Inset(kInset);
gfx::Rect child_pass_local_rect = gfx::Rect(pass_rect.size());
gfx::Transform transform_to_root;
transform_to_root.Translate(pass_rect.OffsetFromOrigin());
transform_to_root.RotateAboutZAxis(10);
auto child_pass = CreateTestRenderPass(child_pass_id, child_pass_local_rect,
transform_to_root);
SharedQuadState* shared_state_without_rrect =
CreateTestSharedQuadState(gfx::Transform(), child_pass_local_rect,
child_pass.get(), gfx::MaskFilterInfo());
gfx::Rect yellow_rect = child_pass_local_rect;
yellow_rect.Offset(30, -60);
auto* yellow = child_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
yellow->SetNew(shared_state_without_rrect, yellow_rect, yellow_rect,
SkColors::kYellow, false);
gfx::Rect white_rect = child_pass_local_rect;
auto* white = child_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
white->SetNew(shared_state_without_rrect, white_rect, white_rect,
SkColors::kWhite, false);
SharedQuadState* pass_shared_state = CreateTestSharedQuadState(
transform_to_root, pass_rect, root_pass.get(), gfx::MaskFilterInfo());
CreateTestRenderPassDrawQuad(pass_shared_state, pass_rect, child_pass_id,
root_pass.get());
auto* child_pass_ptr = child_pass.get();
AggregatedRenderPassList pass_list;
pass_list.push_back(std::move(child_pass));
pass_list.push_back(std::move(root_pass));
// Simulate the user drawing a horizontal line.
const gfx::PointF kFirstPoint(156.f, 87.23f);
const base::TimeTicks kFirstTimestamp = base::TimeTicks::Now();
CreateAndSendPoint(kFirstPoint, kFirstTimestamp);
CreateAndSendPointFromLastPoint(gfx::PointF(119, 87.23f));
CreateAndSendPointFromLastPoint(gfx::PointF(75, 87.23f));
const gfx::RectF kPresentationArea(0, 0, 200, 200);
CreateAndSendMetadata(kFirstPoint, 19.177f, SkColors::kRed, kFirstTimestamp,
kPresentationArea, /*render_pass_id=*/kTargetPassId);
// Check that the ink trail is drawn on the child render pass. The trail
// should be slightly diagonal since the pass has been rotated; albeit in the
// opposite direction. That way when the pass is drawn relative to the root,
// the trail will appear horizontal.
EXPECT_TRUE(this->RunPixelTestWithCopyOutputRequest(
&pass_list, child_pass_ptr,
base::FilePath(
FILE_PATH_LITERAL("delegated_ink_trail_non_root_render_pass.png")),
cc::FuzzyPixelComparator().DiscardAlpha().SetErrorPixelsPercentageLimit(
1.0f)));
}
// Delegated ink trail is not drawn when the metadata is outside of the
// render pass area.
TEST_P(DelegatedInkWithPredictionTest, NonIntersectingMetadata) {
gfx::Rect viewport_rect(this->device_viewport_size_);
constexpr int kInset = 20;
AggregatedRenderPassId root_pass_id{1};
auto root_pass =
CreateTestRootRenderPass(root_pass_id, viewport_rect, gfx::Rect());
AggregatedRenderPassId child_pass_id{2};
gfx::Rect pass_rect(this->device_viewport_size_);
pass_rect.Inset(kInset);
gfx::Rect child_pass_local_rect = gfx::Rect(pass_rect.size());
gfx::Transform transform_to_root;
transform_to_root.Translate(pass_rect.OffsetFromOrigin());
auto child_pass = CreateTestRenderPass(child_pass_id, child_pass_local_rect,
transform_to_root);
SharedQuadState* shared_state_without_rrect =
CreateTestSharedQuadState(gfx::Transform(), child_pass_local_rect,
child_pass.get(), gfx::MaskFilterInfo());
gfx::Rect white_rect = child_pass_local_rect;
auto* white = child_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
white->SetNew(shared_state_without_rrect, white_rect, white_rect,
SkColors::kWhite, false);
SharedQuadState* pass_shared_state = CreateTestSharedQuadState(
transform_to_root, pass_rect, root_pass.get(), gfx::MaskFilterInfo());
CreateTestRenderPassDrawQuad(pass_shared_state, pass_rect, child_pass_id,
root_pass.get());
auto* child_pass_ptr = child_pass.get();
AggregatedRenderPassList pass_list;
pass_list.push_back(std::move(child_pass));
pass_list.push_back(std::move(root_pass));
// Simulate the user drawing a diagonal line. First pass is outside child
// render pass output rect.
const gfx::PointF kFirstPoint(5, 5);
const base::TimeTicks kFirstTimestamp = base::TimeTicks::Now();
CreateAndSendPoint(kFirstPoint, kFirstTimestamp);
// Subsequent points are inside the child render pass.
CreateAndSendPointFromLastPoint(gfx::PointF(25, 25));
CreateAndSendPointFromLastPoint(gfx::PointF(50, 50));
const gfx::RectF kPresentationArea(0, 0, 200, 200);
CreateAndSendMetadata(kFirstPoint, 5, SkColors::kRed, kFirstTimestamp,
kPresentationArea, /*render_pass_id=*/1);
// Check that the ink trail is not drawn on the render pass because the
// delegated ink metadata is outside the bounds of the render pass output
// rect.
EXPECT_TRUE(this->RunPixelTestWithCopyOutputRequest(
&pass_list, child_pass_ptr,
base::FilePath(FILE_PATH_LITERAL("no-trail-white.png")),
cc::FuzzyPixelComparator().DiscardAlpha().SetErrorPixelsPercentageLimit(
1.0f)));
}
// Draw two different trails that are made up of sets of DelegatedInkPoints with
// different pointer IDs. All numbers arbitrarily chosen.
TEST_P(DelegatedInkWithPredictionTest, DrawTrailsWithDifferentPointerIds) {
const int32_t kPointerId1 = 2;
const int32_t kPointerId2 = 100;
const base::TimeTicks kTimestamp = base::TimeTicks::Now();
// Constants used for sending points and making sure we can send matching
// DelegatedInkMetadata later.
const gfx::PointF kPointerId1StartPoint(40, 27);
const base::TimeTicks kPointerId1StartTime = kTimestamp;
const gfx::PointF kPointerId2StartPoint(160, 190);
const base::TimeTicks kPointerId2StartTime =
kTimestamp + base::Milliseconds(15);
// Send four points for pointer ID 1 and two points for pointer ID 2 in mixed
// order to confirm that they get put in the right buckets. Some timestamps
// match intentionally to make sure that point is considered when matching
// DelegatedInkMetadata to DelegatedInkPoints
CreateAndSendPoint(kPointerId1StartPoint, kPointerId1StartTime, kPointerId1);
CreateAndSendPoint(gfx::PointF(24, 80), kTimestamp + base::Milliseconds(15),
kPointerId1);
CreateAndSendPoint(kPointerId2StartPoint, kPointerId2StartTime, kPointerId2);
CreateAndSendPoint(gfx::PointF(60, 130), kTimestamp + base::Milliseconds(24),
kPointerId1);
CreateAndSendPoint(gfx::PointF(80, 118), kTimestamp + base::Milliseconds(20),
kPointerId2);
CreateAndSendPoint(gfx::PointF(100, 190), kTimestamp + base::Milliseconds(30),
kPointerId1);
const gfx::RectF kPresentationArea(200, 200);
// Now send a metadata to match the first point of the first pointer id to
// confirm that only that trail is drawn.
CreateAndSendMetadata(kPointerId1StartPoint, 7, SkColors::kYellow,
kPointerId1StartTime, kPresentationArea,
/*render_pass_id=*/1);
base::FilePath expected_result =
base::FilePath(FILE_PATH_LITERAL("delegated_ink_pointer_id_1.png"));
if (is_skia_graphite()) {
expected_result = expected_result.InsertBeforeExtensionASCII(kGraphiteStr);
}
EXPECT_TRUE(DrawAndTestTrail(expected_result, /*render_pass_id=*/1));
// Then send metadata that matches the first point of the other pointer id.
// These points should not have been erased, so all 3 points should be drawn.
CreateAndSendMetadata(kPointerId2StartPoint, 2.4f, SkColors::kRed,
kPointerId2StartTime, kPresentationArea,
/*render_pass_id=*/1);
base::FilePath expected_result_second =
base::FilePath(FILE_PATH_LITERAL("delegated_ink_pointer_id_2.png"));
if (is_skia_graphite()) {
expected_result_second =
expected_result_second.InsertBeforeExtensionASCII(kGraphiteStr);
}
EXPECT_TRUE(DrawAndTestTrail(expected_result_second, /*render_pass_id=*/1));
// The metadata should have been cleared after drawing, so confirm that there
// is no trail after another draw.
EXPECT_TRUE(DrawAndTestTrail(base::FilePath(FILE_PATH_LITERAL("white.png")),
/*render_pass_id=*/1));
}
// Draw a single trail and erase it, making sure that no bits of trail are left
// behind.
TEST_P(DelegatedInkWithPredictionTest,
IdenticalTrailDrawnAfterSameMetadataReceived) {
// Send some DelegatedInkPoints, numbers arbitrary. This will predict no
// points, so a trail made of 3 points will be drawn.
const gfx::PointF kFirstPoint(10, 10);
const base::TimeTicks kFirstTimestamp = base::TimeTicks::Now();
CreateAndSendPoint(kFirstPoint, kFirstTimestamp);
CreateAndSendPointFromLastPoint(gfx::PointF(75, 62));
CreateAndSendPointFromLastPoint(gfx::PointF(124, 45));
// Provide the metadata required to draw the trail, matching the first
// DelegatedInkPoint sent.
CreateAndSendMetadata(kFirstPoint, 3.5f, SkColors::kBlack, kFirstTimestamp,
gfx::RectF(0, 0, 175, 172), /*render_pass_id=*/1);
// Confirm that the trail was drawn. Test three times as
// the trail will persist for two more frames before being erased.
base::FilePath expected_result =
base::FilePath(FILE_PATH_LITERAL("delegated_ink_one_trail.png"));
if (is_skia_graphite()) {
expected_result = expected_result.InsertBeforeExtensionASCII(kGraphiteStr);
}
EXPECT_TRUE(DrawAndTestTrail(expected_result, /*render_pass_id=*/1));
// Send metadata again and expect the same trail to be drawn.
CreateAndSendMetadata(kFirstPoint, 3.5f, SkColors::kBlack, kFirstTimestamp,
gfx::RectF(0, 0, 175, 172), /*render_pass_id=*/1);
EXPECT_TRUE(DrawAndTestTrail(expected_result, /*render_pass_id=*/1));
// The metadata should have been cleared after drawing, so confirm that there
// is no trail after another draw.
EXPECT_TRUE(DrawAndTestTrail(base::FilePath(FILE_PATH_LITERAL("white.png")),
/*render_pass_id=*/1));
}
#endif // !BUILDFLAG(IS_ANDROID)
} // namespace
} // namespace viz