| // 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/mailbox_manager.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)) {} |
| |
| absl::optional<gl::DCLayerOverlayImage> GetDCLayerOverlayImage() override { |
| return absl::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::GenerateForSharedImage(); |
| |
| // 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 |