// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/rounded_display/rounded_display_frame_factory.h"
#include <algorithm>
#include <array>
#include <memory>
#include <vector>
#include "ash/frame_sink/ui_resource.h"
#include "ash/frame_sink/ui_resource_manager.h"
#include "ash/rounded_display/rounded_display_gutter.h"
#include "base/check.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "components/viz/common/quads/compositor_frame.h"
#include "components/viz/common/quads/texture_draw_quad.h"
#include "components/viz/common/resources/resource_id.h"
#include "components/viz/common/resources/shared_image_format.h"
#include "components/viz/common/resources/shared_image_format_utils.h"
#include "components/viz/common/resources/transferable_resource.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 "ipc/common/surface_handle.h"
#include "ui/aura/env.h"
#include "ui/aura/window.h"
#include "ui/aura/window_tree_host.h"
#include "ui/compositor/compositor.h"
#include "ui/compositor/layer.h"
#include "ui/display/screen.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/geometry/point_f.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/geometry/transform.h"
namespace ash {
namespace {
using RoundedCorner = RoundedDisplayGutter::RoundedCorner;
constexpr viz::SharedImageFormat kSharedImageFormat =
SK_B32_SHIFT ? viz::SinglePlaneFormat::kRGBA_8888
: viz::SinglePlaneFormat::kBGRA_8888;
gfx::Transform GetRootRotationTransform(const aura::Window& host_window) {
// Root transform has both the rotation and scaling of the whole UI, therefore
// we need undo the scaling of UI to get the rotation transform.
const auto* host = host_window.GetHost();
gfx::Transform root_rotation_transform = host->GetRootTransform();
float device_scale_factor = host_window.layer()->device_scale_factor();
root_rotation_transform.Scale(1 / device_scale_factor,
1 / device_scale_factor);
return root_rotation_transform;
}
viz::TextureDrawQuad::RoundedDisplayMasksInfo MapToRoundedDisplayMasksInfo(
const std::vector<RoundedCorner>& corners) {
DCHECK(corners.size() <= 2) << "Currently, viz can only handle textures that "
"have up to 2 corner masks drawn into them";
if (corners.size() == 1) {
return viz::TextureDrawQuad::RoundedDisplayMasksInfo::
CreateRoundedDisplayMasksInfo(corners.back().radius(), 0,
/*is_horizontally_positioned=*/true);
}
std::array<const RoundedCorner*, 2> sorted_corners = {&corners.at(0),
&corners.at(1)};
std::sort(sorted_corners.begin(), sorted_corners.end(),
[](const RoundedCorner* c1, const RoundedCorner* c2) {
return c1->bounds().origin() < c2->bounds().origin();
});
const RoundedDisplayGutter::RoundedCorner& first_corner =
*sorted_corners.at(0);
const RoundedDisplayGutter::RoundedCorner& second_corner =
*sorted_corners.at(1);
// Corners of a gutter need to be either vertically or horizontally
// aligned.
DCHECK(first_corner.bounds().x() == second_corner.bounds().x() ||
first_corner.bounds().y() == second_corner.bounds().y());
DCHECK(!first_corner.bounds().Intersects(second_corner.bounds()));
bool is_horizontally_positioned =
first_corner.bounds().y() == second_corner.bounds().y();
return viz::TextureDrawQuad::RoundedDisplayMasksInfo::
CreateRoundedDisplayMasksInfo(first_corner.radius(),
second_corner.radius(),
is_horizontally_positioned);
}
} // namespace
// -----------------------------------------------------------------------------
// RoundedDisplayUiResource:
RoundedDisplayUiResource::RoundedDisplayUiResource() = default;
RoundedDisplayUiResource::~RoundedDisplayUiResource() = default;
// -----------------------------------------------------------------------------
// RoundedDisplayFrameFactory:
// static
std::unique_ptr<RoundedDisplayUiResource>
RoundedDisplayFrameFactory::CreateUiResource(const gfx::Size& size,
viz::SharedImageFormat format,
UiSourceId ui_source_id,
bool is_overlay) {
DCHECK(!size.IsEmpty());
DCHECK(ui_source_id > 0);
auto resource = std::make_unique<RoundedDisplayUiResource>();
if (!resource->context_provider) {
resource->context_provider = aura::Env::GetInstance()
->context_factory()
->SharedMainThreadRasterContextProvider();
if (!resource->context_provider) {
LOG(ERROR) << "Failed to acquire a context provider";
return nullptr;
}
}
gpu::SharedImageInterface* sii =
resource->context_provider->SharedImageInterface();
gpu::SharedImageUsageSet usage = gpu::SHARED_IMAGE_USAGE_DISPLAY_READ;
if (is_overlay) {
usage |= gpu::SHARED_IMAGE_USAGE_SCANOUT;
}
auto client_shared_image = sii->CreateSharedImage({
format, size, gfx::ColorSpace(), usage, "RoundedDisplayFrameUi"},
gpu::kNullSurfaceHandle, gfx::BufferUsage::SCANOUT_CPU_READ_WRITE);
if (!client_shared_image) {
LOG(ERROR) << "Failed to create MappableSharedImage";
return nullptr;
}
resource->SetClientSharedImage(std::move(client_shared_image));
resource->sync_token = sii->GenVerifiedSyncToken();
resource->damaged = true;
resource->ui_source_id = ui_source_id;
resource->is_overlay_candidate = is_overlay;
resource->format = format;
resource->resource_size = size;
return resource;
}
std::unique_ptr<RoundedDisplayUiResource>
RoundedDisplayFrameFactory::AcquireUiResource(
const RoundedDisplayGutter& gutter,
UiResourceManager& resource_manager) const {
gfx::Size resource_size = gutter.bounds().size();
viz::ResourceId reusable_resource_id = resource_manager.FindResourceToReuse(
resource_size, kSharedImageFormat, gutter.ui_source_id());
std::unique_ptr<RoundedDisplayUiResource> resource;
if (reusable_resource_id != viz::kInvalidResourceId) {
resource = base::WrapUnique(static_cast<RoundedDisplayUiResource*>(
resource_manager.ReleaseAvailableResource(reusable_resource_id)
.release()));
} else {
resource = CreateUiResource(resource_size, kSharedImageFormat,
gutter.ui_source_id(), gutter.NeedsOverlays());
}
return resource;
}
std::unique_ptr<viz::CompositorFrame>
RoundedDisplayFrameFactory::CreateCompositorFrame(
const viz::BeginFrameAck& begin_frame_ack,
aura::Window& host_window,
UiResourceManager& resource_manager,
const std::vector<RoundedDisplayGutter*>& gutters) {
auto frame = std::make_unique<viz::CompositorFrame>();
frame->metadata.begin_frame_ack = begin_frame_ack;
frame->metadata.begin_frame_ack.has_damage = true;
float device_scale_factor = host_window.layer()->device_scale_factor();
frame->metadata.device_scale_factor = device_scale_factor;
auto render_pass =
viz::CompositorRenderPass::Create(/*shared_quad_state_list_size=*/1u,
/*quad_list_size=*/6u);
const display::Display display =
display::Screen::GetScreen()->GetDisplayNearestWindow(&host_window);
gfx::Rect output_rect(display.GetSizeInPixel());
render_pass->SetNew(viz::CompositorRenderPassId{1}, output_rect, output_rect,
gfx::Transform());
gfx::Transform root_rotation_inverse =
GetRootRotationTransform(host_window).GetCheckedInverse();
for (const auto* gutter : gutters) {
DCHECK(gutter);
auto resource = Draw(*gutter, resource_manager);
if (!resource) {
return nullptr;
}
// By applying the inverse of root rotation transform, we ensure that our
// rounded corner textures are not rotated with the rest of the UI. This
// also saves us from dealing with having the reverse rotation transform
// requirements of using hardware overlays.
const gfx::Transform& buffer_to_target_transform = root_rotation_inverse;
viz::ResourceId resource_id =
resource_manager.OfferResource(std::move(resource));
viz::TransferableResource transferable_resource =
resource_manager.PrepareResourceForExport(resource_id);
AppendQuad(transferable_resource, buffer_to_target_transform, *gutter,
*render_pass);
frame->resource_list.push_back(std::move(transferable_resource));
}
frame->render_pass_list.push_back(std::move(render_pass));
return frame;
}
std::unique_ptr<RoundedDisplayUiResource> RoundedDisplayFrameFactory::Draw(
const RoundedDisplayGutter& gutter,
UiResourceManager& resource_manager) const {
std::unique_ptr<RoundedDisplayUiResource> resource =
AcquireUiResource(gutter, resource_manager);
if (!resource) {
return nullptr;
}
Paint(gutter, resource.get());
if (resource->damaged) {
DCHECK(resource->context_provider);
gpu::SharedImageInterface* sii =
resource->context_provider->SharedImageInterface();
sii->UpdateSharedImage(resource->sync_token, resource->mailbox());
resource->sync_token = sii->GenVerifiedSyncToken();
resource->damaged = false;
}
return resource;
}
void RoundedDisplayFrameFactory::Paint(
const RoundedDisplayGutter& gutter,
RoundedDisplayUiResource* resource) const {
gfx::Canvas canvas(gutter.bounds().size(), 1.0, true);
gutter.Paint(&canvas);
CHECK(resource->client_shared_image());
auto mapping = resource->client_shared_image()->Map();
if (!mapping) {
return;
}
uint8_t* data = static_cast<uint8_t*>(mapping->Memory(0));
int stride = mapping->Stride(0);
canvas.GetBitmap().readPixels(
SkImageInfo::MakeN32Premul(mapping->Size().width(),
mapping->Size().height()),
data, stride, 0, 0);
}
void RoundedDisplayFrameFactory::AppendQuad(
const viz::TransferableResource& resource,
const gfx::Transform& buffer_to_target_transform,
const RoundedDisplayGutter& gutter,
viz::CompositorRenderPass& render_pass_out) const {
// Each gutter can be thought of as a single ui::Layer that produces only one
// quad. Therefore the layer should be of the same size as the texture
// produced by the gutter making layer_rect the size of the gutter in pixels.
const gfx::Rect& layer_rect = gutter.bounds();
viz::SharedQuadState* quad_state =
render_pass_out.CreateAndAppendSharedQuadState();
quad_state->SetAll(buffer_to_target_transform,
/*layer_rect=*/layer_rect,
/*visible_layer_rect=*/layer_rect,
/*filter_info=*/gfx::MaskFilterInfo(),
/*clip=*/std::nullopt, /*contents_opaque=*/false,
/*opacity_f=*/1.f,
/*blend=*/SkBlendMode::kSrcOver,
/*sorting_context=*/0,
/*layer_id=*/0u, /*fast_rounded_corner=*/false);
viz::TextureDrawQuad* texture_quad =
render_pass_out.CreateAndAppendDrawQuad<viz::TextureDrawQuad>();
// Since a single gutter is created for the full layer and we re-render the
// full texture making the quad_rect same as the layer_rect.
const gfx::Rect& quad_rect = layer_rect;
// Since the gutter texture is drawn into a buffer of exact size, therefore
// we do not need to scale uv coordinates (zoom in or out on texture) to fit
// the buffer size.
texture_quad->SetNew(
quad_state, quad_rect, quad_rect,
/*needs_blending=*/true, resource.id,
/*premultiplied=*/true, /*uv_top_left=*/gfx::PointF(0, 0),
/*uv_bottom_right=*/gfx::PointF(1, 1),
/*background=*/SkColors::kTransparent,
/*flipped=*/false,
/*nearest=*/false,
/*secure_output=*/false, gfx::ProtectedVideoType::kClear);
texture_quad->set_resource_size_in_pixels(resource.size);
texture_quad->rounded_display_masks_info =
MapToRoundedDisplayMasksInfo(gutter.GetGutterCorners());
}
} // namespace ash