// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "gpu/ipc/service/dcomp_texture_win.h"
#include <string.h>
#include "base/functional/bind.h"
#include "base/memory/scoped_refptr.h"
#include "base/notreached.h"
#include "base/power_monitor/power_monitor.h"
#include "base/win/windows_types.h"
#include "gpu/command_buffer/common/shared_image_usage.h"
#include "gpu/command_buffer/service/scheduler.h"
#include "gpu/command_buffer/service/scheduler_task_runner.h"
#include "gpu/command_buffer/service/shared_image/shared_image_backing.h"
#include "gpu/command_buffer/service/shared_image/shared_image_factory.h"
#include "gpu/command_buffer/service/shared_image/shared_image_representation.h"
#include "gpu/ipc/common/gpu_channel.mojom.h"
#include "gpu/ipc/service/gpu_channel.h"
#include "gpu/ipc/service/gpu_channel_manager.h"
#include "ipc/ipc_mojo_bootstrap.h"
#include "ui/gfx/color_space.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/video_types.h"
#include "ui/gl/dcomp_surface_registry.h"
#include "ui/gl/scoped_make_current.h"
namespace gpu {
namespace {
constexpr base::TimeDelta kParentWindowPosPollingPeriod = base::Seconds(1);
constexpr base::TimeDelta kPowerChangeDetectionGracePeriod = base::Seconds(2);
class DCOMPTextureRepresentation : public OverlayImageRepresentation {
public:
DCOMPTextureRepresentation(
SharedImageManager* manager,
SharedImageBacking* backing,
MemoryTypeTracker* tracker,
scoped_refptr<gl::DCOMPSurfaceProxy> dcomp_surface_proxy)
: OverlayImageRepresentation(manager, backing, tracker),
dcomp_surface_proxy_(std::move(dcomp_surface_proxy)) {}
std::optional<gl::DCLayerOverlayImage> GetDCLayerOverlayImage() override {
return std::make_optional<gl::DCLayerOverlayImage>(size(),
dcomp_surface_proxy_);
}
bool BeginReadAccess(gfx::GpuFenceHandle& acquire_fence) override {
return true;
}
void EndReadAccess(gfx::GpuFenceHandle release_fence) override {}
private:
scoped_refptr<gl::DCOMPSurfaceProxy> dcomp_surface_proxy_;
};
class DCOMPTextureBacking : public ClearTrackingSharedImageBacking {
public:
DCOMPTextureBacking(scoped_refptr<gl::DCOMPSurfaceProxy> dcomp_surface_proxy,
const Mailbox& mailbox,
const gfx::Size& size)
: ClearTrackingSharedImageBacking(mailbox,
viz::SinglePlaneFormat::kBGRA_8888,
size,
gfx::ColorSpace::CreateSRGB(),
kTopLeft_GrSurfaceOrigin,
kPremul_SkAlphaType,
gpu::SHARED_IMAGE_USAGE_SCANOUT,
{},
/*estimated_size=*/0,
/*is_thread_safe=*/false),
dcomp_surface_proxy_(std::move(dcomp_surface_proxy)) {
SetCleared();
}
SharedImageBackingType GetType() const override {
return SharedImageBackingType::kDCOMPSurfaceProxy;
}
std::unique_ptr<OverlayImageRepresentation> ProduceOverlay(
SharedImageManager* manager,
MemoryTypeTracker* tracker) override {
return std::make_unique<DCOMPTextureRepresentation>(manager, this, tracker,
dcomp_surface_proxy_);
}
private:
scoped_refptr<gl::DCOMPSurfaceProxy> dcomp_surface_proxy_;
};
} // namespace
// static
scoped_refptr<DCOMPTexture> DCOMPTexture::Create(
GpuChannel* channel,
int route_id,
mojo::PendingAssociatedReceiver<mojom::DCOMPTexture> receiver) {
ContextResult result;
auto context_state =
channel->gpu_channel_manager()->GetSharedContextState(&result);
if (result != ContextResult::kSuccess) {
DLOG(ERROR) << "GetSharedContextState() failed.";
return nullptr;
}
return base::WrapRefCounted(new DCOMPTexture(
channel, route_id, std::move(receiver), std::move(context_state)));
}
DCOMPTexture::DCOMPTexture(
GpuChannel* channel,
int32_t route_id,
mojo::PendingAssociatedReceiver<mojom::DCOMPTexture> receiver,
scoped_refptr<SharedContextState> context_state)
: channel_(channel),
route_id_(route_id),
context_state_(std::move(context_state)),
sequence_(channel_->scheduler()->CreateSequence(SchedulingPriority::kLow,
channel_->task_runner())),
receiver_(this) {
auto runner = base::MakeRefCounted<SchedulerTaskRunner>(
*channel_->scheduler(), sequence_);
IPC::ScopedAllowOffSequenceChannelAssociatedBindings allow_binding;
receiver_.Bind(std::move(receiver), runner);
context_state_->AddContextLostObserver(this);
base::PowerMonitor::AddPowerSuspendObserver(this);
channel_->AddRoute(route_id, sequence_);
}
DCOMPTexture::~DCOMPTexture() {
DVLOG(1) << __func__;
// |channel_| is always released before GpuChannel releases its reference to
// this class.
DCHECK(!channel_);
context_state_->RemoveContextLostObserver(this);
base::PowerMonitor::RemovePowerSuspendObserver(this);
if (window_pos_timer_.IsRunning()) {
window_pos_timer_.Stop();
}
}
void DCOMPTexture::ReleaseChannel() {
DVLOG(1) << __func__;
DCHECK(channel_);
receiver_.ResetFromAnotherSequenceUnsafe();
channel_->RemoveRoute(route_id_);
channel_->scheduler()->DestroySequence(sequence_);
sequence_ = SequenceId();
channel_ = nullptr;
ResetSizeIfNeeded();
}
void DCOMPTexture::OnContextLost() {
DVLOG(1) << __func__;
}
// TODO(xhwang): Also observe GPU LUID change.
void DCOMPTexture::OnResume() {
DVLOG(1) << __func__;
last_power_change_time_ = base::TimeTicks::Now();
ResetSizeIfNeeded();
}
void DCOMPTexture::ResetSizeIfNeeded() {
DVLOG(2) << __func__;
// For `kHardwareProtected` video frame, when hardware content reset happens,
// e.g. OS suspend/resume or GPU hot swap, existing video frames become stale
// and presenting them could cause issues like black screen flash (see
// crbug.com/1384544). So we set `size_` to (1, 1) so that DComp surface
// resources will be released (see SwapChainPresenter::PresentDCOMPSurface()).
// We don't know for sure whether hardware content reset happened. So we check
// whether power suspend/resume or GPU change happened recently as a hint.
// Since it's a hint, to prevent breaking normal playback, we only do this
// when the video frame is orphaned (the media Renderer has been suspended or
// destroyed, but we are still showing the last frame), which will trigger
// `ReleaseChannel()` and set `channel_` to null.
if (!channel_ &&
protected_video_type_ == gfx::ProtectedVideoType::kHardwareProtected &&
base::TimeTicks::Now() - last_power_change_time_ <
kPowerChangeDetectionGracePeriod) {
DVLOG(1) << __func__
<< ": Resetting size to {1,1} to release dcomp surface resources "
"and prevent stale content from being displayed";
size_ = gfx::Size(1, 1);
}
}
void DCOMPTexture::StartListening(
mojo::PendingAssociatedRemote<mojom::DCOMPTextureClient> client) {
client_.Bind(std::move(client));
}
void DCOMPTexture::SetTextureSize(const gfx::Size& size) {
size_ = size;
if (!shared_image_mailbox_created_) {
if (client_) {
shared_image_mailbox_created_ = true;
gpu::Mailbox mailbox = CreateSharedImage();
client_->OnSharedImageMailboxBound(mailbox);
} else
DLOG(ERROR) << "Unable to call client_->OnSharedImageMailboxBound";
}
}
const gfx::Size& DCOMPTexture::GetSize() const {
return size_;
}
HANDLE DCOMPTexture::GetSurfaceHandle() {
return surface_handle_.get();
}
void DCOMPTexture::SetDCOMPSurfaceHandle(
const base::UnguessableToken& token,
SetDCOMPSurfaceHandleCallback callback) {
DVLOG(1) << __func__;
base::win::ScopedHandle surface_handle =
gl::DCOMPSurfaceRegistry::GetInstance()->TakeDCOMPSurfaceHandle(token);
if (!surface_handle.IsValid()) {
DLOG(ERROR) << __func__ << ": No surface registered for token " << token;
std::move(callback).Run(false);
return;
}
surface_handle_.Set(surface_handle.Take());
std::move(callback).Run(true);
}
gpu::Mailbox DCOMPTexture::CreateSharedImage() {
DCHECK(channel_);
auto mailbox = gpu::Mailbox::Generate();
// Use DCOMPTextureBacking as the backing to hold DCOMPSurfaceProxy i.e. this,
// and be able to retrieve it later via ProduceOverlay.
// Note: DCOMPTextureBacking shouldn't be accessed via GL at all.
auto shared_image =
std::make_unique<DCOMPTextureBacking>(this, mailbox, size_);
channel_->shared_image_stub()->factory()->RegisterBacking(
std::move(shared_image));
return mailbox;
}
gfx::Rect DCOMPTexture::GetParentWindowRect() {
RECT parent_window_rect = {};
::GetWindowRect(last_parent_, &parent_window_rect);
return gfx::Rect(parent_window_rect);
}
void DCOMPTexture::OnUpdateParentWindowRect() {
gfx::Rect parent_window_rect = GetParentWindowRect();
if (parent_window_rect_ != parent_window_rect) {
parent_window_rect_ = parent_window_rect;
SendOutputRect();
}
}
void DCOMPTexture::SetParentWindow(HWND parent) {
if (last_parent_ != parent) {
last_parent_ = parent;
OnUpdateParentWindowRect();
if (!window_pos_timer_.IsRunning()) {
window_pos_timer_.Start(FROM_HERE, kParentWindowPosPollingPeriod, this,
&DCOMPTexture::OnUpdateParentWindowRect);
}
}
}
void DCOMPTexture::SetRect(const gfx::Rect& window_relative_rect) {
bool should_send_output_rect = false;
if (window_relative_rect != window_relative_rect_) {
window_relative_rect_ = window_relative_rect;
should_send_output_rect = true;
}
gfx::Rect parent_window_rect = GetParentWindowRect();
if (parent_window_rect_ != parent_window_rect) {
parent_window_rect_ = parent_window_rect;
should_send_output_rect = true;
}
if (should_send_output_rect)
SendOutputRect();
}
void DCOMPTexture::SetProtectedVideoType(
gfx::ProtectedVideoType protected_video_type) {
if (protected_video_type == protected_video_type_)
return;
DVLOG(2) << __func__ << ": protected_video_type="
<< static_cast<int>(protected_video_type);
protected_video_type_ = protected_video_type;
}
void DCOMPTexture::SendOutputRect() {
if (!client_)
return;
gfx::Rect output_rect = window_relative_rect_;
output_rect.set_x(window_relative_rect_.x() + parent_window_rect_.x());
output_rect.set_y(window_relative_rect_.y() + parent_window_rect_.y());
if (last_output_rect_ != output_rect) {
if (!output_rect.IsEmpty()) {
// The initial `OnUpdateParentWindowRect()` call can cause an empty
// `output_rect`.
// Set MFMediaEngine's `UpdateVideoStream()` with an non-empty destination
// rectangle. Otherwise, the next `EnableWindowlessSwapchainMode()` call
// to MFMediaEngine will skip the creation of the DCOMP surface handle.
// Then, the next `GetVideoSwapchainHandle()` call returns S_FALSE.
client_->OnOutputRectChange(output_rect);
}
last_output_rect_ = output_rect;
}
}
} // namespace gpu