diff --git a/components/viz/service/BUILD.gn b/components/viz/service/BUILD.gn index fb793e98d5939..77fc13c924dd1 100644 --- a/components/viz/service/BUILD.gn +++ b/components/viz/service/BUILD.gn @@ -563,9 +563,16 @@ viz_source_set("unit_tests") { sources += [ "display/overlay_dc_unittest.cc", "display_embedder/output_device_backing_unittest.cc", + "display_embedder/skia_output_device_dcomp_unittest.cc", "display_embedder/software_output_device_win_unittest.cc", ] + deps += [ + "//ui/gl:test_support", + "//ui/platform_window", + "//ui/platform_window/win", + ] + # SkiaOutputDeviceBufferQueue doesn't support Windows. sources -= [ "display_embedder/skia_output_device_buffer_queue_unittest.cc" ] diff --git a/components/viz/service/display_embedder/skia_output_device.h b/components/viz/service/display_embedder/skia_output_device.h index 05e81063e3a84..6fcaeb911d3ac 100644 --- a/components/viz/service/display_embedder/skia_output_device.h +++ b/components/viz/service/display_embedder/skia_output_device.h @@ -18,6 +18,7 @@ #include "components/viz/service/display/output_surface_frame.h" #include "components/viz/service/display/overlay_processor_interface.h" #include "components/viz/service/display/skia_output_surface.h" +#include "components/viz/service/viz_service_export.h" #include "gpu/command_buffer/common/swap_buffers_complete_params.h" #include "third_party/abseil-cpp/absl/types/optional.h" #include "third_party/skia/include/core/SkRefCnt.h" @@ -50,10 +51,10 @@ namespace viz { class VulkanContextProvider; -class SkiaOutputDevice { +class VIZ_SERVICE_EXPORT SkiaOutputDevice { public: // A helper class for defining a BeginPaint() and EndPaint() scope. - class ScopedPaint { + class VIZ_SERVICE_EXPORT ScopedPaint { public: ScopedPaint(std::vector end_semaphores, SkiaOutputDevice* device, diff --git a/components/viz/service/display_embedder/skia_output_device_dcomp.cc b/components/viz/service/display_embedder/skia_output_device_dcomp.cc index 216f1f7438e32..7a2e5ad9edd92 100644 --- a/components/viz/service/display_embedder/skia_output_device_dcomp.cc +++ b/components/viz/service/display_embedder/skia_output_device_dcomp.cc @@ -12,18 +12,23 @@ #include "base/functional/callback_helpers.h" #include "base/memory/scoped_refptr.h" #include "components/viz/common/gpu/context_lost_reason.h" +#include "components/viz/common/resources/resource_format_utils.h" +#include "components/viz/common/resources/shared_image_format.h" #include "components/viz/service/display/dc_layer_overlay.h" +#include "gpu/command_buffer/common/mailbox.h" +#include "gpu/command_buffer/common/shared_image_usage.h" #include "gpu/command_buffer/common/swap_buffers_complete_params.h" #include "gpu/command_buffer/service/feature_info.h" #include "gpu/command_buffer/service/gl_utils.h" -#include "gpu/command_buffer/service/mailbox_manager.h" #include "gpu/command_buffer/service/shared_context_state.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/command_buffer/service/skia_utils.h" #include "gpu/command_buffer/service/texture_base.h" #include "gpu/command_buffer/service/texture_manager.h" #include "skia/ext/legacy_display_globals.h" +#include "third_party/skia/include/core/SkAlphaType.h" #include "third_party/skia/include/core/SkSurface.h" #include "third_party/skia/include/core/SkSurfaceProps.h" #include "third_party/skia/include/gpu/GrBackendSurface.h" @@ -87,7 +92,6 @@ class SkiaOutputDeviceDComp::OverlayData { }; SkiaOutputDeviceDComp::SkiaOutputDeviceDComp( - gpu::MailboxManager* mailbox_manager, gpu::SharedImageRepresentationFactory* shared_image_representation_factory, gpu::SharedContextState* context_state, gl::GLSurface* gl_surface, @@ -97,11 +101,8 @@ SkiaOutputDeviceDComp::SkiaOutputDeviceDComp( : SkiaOutputDevice(context_state->gr_context(), memory_tracker, std::move(did_swap_buffer_complete_callback)), - mailbox_manager_(mailbox_manager), shared_image_representation_factory_(shared_image_representation_factory), context_state_(context_state) { - DCHECK(gl_surface->SupportsPostSubBuffer()); - DCHECK(!gl_surface->SupportsAsyncSwap()); DCHECK(!feature_info->workarounds() .disable_post_sub_buffers_for_onscreen_surfaces); DCHECK(gl_surface->SupportsDCLayers()); @@ -111,7 +112,7 @@ SkiaOutputDeviceDComp::SkiaOutputDeviceDComp( capabilities_.uses_default_gl_framebuffer = true; capabilities_.output_surface_origin = gfx::SurfaceOrigin::kTopLeft; - capabilities_.supports_post_sub_buffer = true; + capabilities_.supports_post_sub_buffer = gl_surface->SupportsPostSubBuffer(); // DWM handles preserving the contents of the backbuffer in Present1, so we // don't need to have SkiaOutputSurface handle it. capabilities_.preserve_buffer_content = false; @@ -174,9 +175,15 @@ void SkiaOutputDeviceDComp::PostSubBuffer(const gfx::Rect& rect, OutputSurfaceFrame frame) { StartSwapBuffers({}); - gfx::SwapResult result = - DoPostSubBuffer(rect, std::move(feedback), frame.data); + DoPresent(rect, + base::BindOnce(&SkiaOutputDeviceDComp::OnPresentFinished, + weak_ptr_factory_.GetWeakPtr(), std::move(frame)), + std::move(feedback), frame.data); +} +void SkiaOutputDeviceDComp::OnPresentFinished( + OutputSurfaceFrame frame, + gfx::SwapCompletionResult result) { // Remove entries from |overlays_| for textures that weren't scheduled as an // overlay this frame. if (!overlays_.empty()) { @@ -190,8 +197,7 @@ void SkiaOutputDeviceDComp::PostSubBuffer(const gfx::Rect& rect, kv.second.EndOverlayAccess(); } - FinishSwapBuffers(gfx::SwapCompletionResult(result), GetRootSurfaceSize(), - std::move(frame)); + FinishSwapBuffers(std::move(result), GetRootSurfaceSize(), std::move(frame)); } void SkiaOutputDeviceDComp::ScheduleOverlays( @@ -244,21 +250,21 @@ SkiaOutputDeviceDComp::BeginOverlayAccess(const gpu::Mailbox& mailbox) { } SkiaOutputDeviceDCompGLSurface::SkiaOutputDeviceDCompGLSurface( - gpu::MailboxManager* mailbox_manager, gpu::SharedImageRepresentationFactory* shared_image_representation_factory, gpu::SharedContextState* context_state, scoped_refptr gl_surface, scoped_refptr feature_info, gpu::MemoryTracker* memory_tracker, DidSwapBufferCompleteCallback did_swap_buffer_complete_callback) - : SkiaOutputDeviceDComp(mailbox_manager, - shared_image_representation_factory, + : SkiaOutputDeviceDComp(shared_image_representation_factory, context_state, gl_surface.get(), std::move(feature_info), memory_tracker, std::move(did_swap_buffer_complete_callback)), - gl_surface_(std::move(gl_surface)) {} + gl_surface_(std::move(gl_surface)) { + DCHECK(!gl_surface_->SupportsAsyncSwap()); +} SkiaOutputDeviceDCompGLSurface::~SkiaOutputDeviceDCompGLSurface() { // gl_surface_ will be destructed soon. @@ -376,12 +382,288 @@ gfx::Size SkiaOutputDeviceDCompGLSurface::GetRootSurfaceSize() const { return gl_surface_->GetSize(); } -gfx::SwapResult SkiaOutputDeviceDCompGLSurface::DoPostSubBuffer( +void SkiaOutputDeviceDCompGLSurface::DoPresent( const gfx::Rect& rect, + gl::GLSurface::SwapCompletionCallback completed_callback, BufferPresentedCallback feedback, gfx::FrameData data) { - return gl_surface_->PostSubBuffer(rect.x(), rect.y(), rect.width(), - rect.height(), std::move(feedback), data); + gfx::SwapResult result = + gl_surface_->PostSubBuffer(rect.x(), rect.y(), rect.width(), + rect.height(), std::move(feedback), data); + + // Implement "async" swap synchronously. + std::move(completed_callback).Run(gfx::SwapCompletionResult(result)); +} + +SkiaOutputDeviceDCompPresenter::SkiaOutputDeviceDCompPresenter( + gpu::SharedImageFactory* shared_image_factory, + gpu::SharedImageRepresentationFactory* shared_image_representation_factory, + gpu::SharedContextState* context_state, + scoped_refptr presenter, + scoped_refptr feature_info, + gpu::MemoryTracker* memory_tracker, + DidSwapBufferCompleteCallback did_swap_buffer_complete_callback) + : SkiaOutputDeviceDComp(shared_image_representation_factory, + context_state, + presenter.get(), + std::move(feature_info), + memory_tracker, + std::move(did_swap_buffer_complete_callback)), + presenter_(std::move(presenter)), + shared_image_factory_(shared_image_factory) {} + +SkiaOutputDeviceDCompPresenter::~SkiaOutputDeviceDCompPresenter() { + DestroyRootSurface(); +} + +bool SkiaOutputDeviceDCompPresenter::Reshape( + const SkSurfaceCharacterization& characterization, + const gfx::ColorSpace& color_space, + float device_scale_factor, + gfx::OverlayTransform transform) { + DCHECK_EQ(transform, gfx::OVERLAY_TRANSFORM_NONE); + + if (!characterization.isValid()) { + DLOG(ERROR) << "Invalid SkSurfaceCharacterization"; + return false; + } + + if (characterization_ != characterization || color_space_ != color_space || + device_scale_factor_ != device_scale_factor || transform_ != transform) { + characterization_ = characterization; + color_space_ = color_space; + device_scale_factor_ = device_scale_factor; + transform_ = transform; + DestroyRootSurface(); + } + + // The |SkiaOutputDeviceDCompPresenter| alpha state depends on + // |characterization_| and |wants_dcomp_surface_|. Since |presenter_| can only + // be |DCompPresenter| and its |Resize| function ignores the |has_alpha| + // parameter, we opt to pass an arbitrary value that we expect to be ignored. + constexpr bool kDCompPresenterResizeHasAlphaIgnore = false; + + // DCompPresenter calls SetWindowPos on resize, so we call it to reflect the + // newly allocated root surface. + // Note, we could inline SetWindowPos here, but we need access to the HWND. + if (!presenter_->Resize(gfx::SkISizeToSize(characterization_.dimensions()), + device_scale_factor_, color_space_, + kDCompPresenterResizeHasAlphaIgnore)) { + CheckForLoopFailures(); + // To prevent tail call, so we can see the stack. + base::debug::Alias(nullptr); + return false; + } + + return true; +} + +bool SkiaOutputDeviceDCompPresenter::SetDrawRectangle( + const gfx::Rect& draw_rectangle) { + if (update_rect_.has_value()) { + DLOG(ERROR) << "SetDrawRectangle must be called only once per " + "BeginPaint/EndPaint pair"; + return false; + } + + if (!presenter_->SetDrawRectangle(draw_rectangle)) { + return false; + } + + update_rect_ = draw_rectangle; + return true; +} + +void SkiaOutputDeviceDCompPresenter::SetEnableDCLayers(bool enable) { + if (want_dcomp_surface_ != enable) { + want_dcomp_surface_ = enable; + + // Changing this value will require a new root SharedImage + DestroyRootSurface(); + } +} + +void SkiaOutputDeviceDCompPresenter::SetGpuVSyncEnabled(bool enabled) { + presenter_->SetGpuVSyncEnabled(enabled); +} + +bool SkiaOutputDeviceDCompPresenter::EnsureRootSurfaceAllocated() { + DCHECK(characterization_.isValid()) << "Must call Reshape first"; + + if (root_surface_mailbox_.IsZero()) { + ResourceFormat resource_format = + SkColorTypeToResourceFormat(characterization_.colorType()); + + const gfx::Size size = gfx::SkISizeToSize(characterization_.dimensions()); + + // If |want_dcomp_surface_|, it means we are layering the root surface with + // videos that might be underlays. In this case, we want to force + // transparency so the underlay appears correctly. + SkAlphaType alpha_type = want_dcomp_surface_ + ? kPremul_SkAlphaType + : characterization_.imageInfo().alphaType(); + + // TODO(tangm): DComp surfaces do not support RGB10A2 so we must fall back + // to swap chains. If this happens with video overlays, this can result in + // the video overlay and its parent surface having unsynchronized updates. + // We should clean this up by either avoiding HDR or using RGBAF32 surfaces + // in this case. + const bool dcomp_unsupported_format = + resource_format == ResourceFormat::RGBA_1010102; + + uint32_t usage = + gpu::SHARED_IMAGE_USAGE_DISPLAY_WRITE | gpu::SHARED_IMAGE_USAGE_SCANOUT; + if (want_dcomp_surface_ && !dcomp_unsupported_format) { + usage |= gpu::SHARED_IMAGE_USAGE_SCANOUT_DCOMP_SURFACE; + } else { + usage |= gpu::SHARED_IMAGE_USAGE_DISPLAY_READ; + } + + gpu::Mailbox root_surface_mailbox = gpu::Mailbox::GenerateForSharedImage(); + bool success = shared_image_factory_->CreateSharedImage( + root_surface_mailbox, SharedImageFormat::SinglePlane(resource_format), + size, color_space_, kTopLeft_GrSurfaceOrigin, alpha_type, + gpu::kNullSurfaceHandle, usage); + if (!success) { + CheckForLoopFailures(); + // To prevent tail call, so we can see the stack. + base::debug::Alias(nullptr); + return false; + } + + // Store the root surface's mailbox only on success. + root_surface_mailbox_ = root_surface_mailbox; + } + + if (!root_surface_skia_representation_) { + DCHECK(!root_surface_mailbox_.IsZero()); + + root_surface_skia_representation_ = + shared_image_representation_factory_->ProduceSkia( + root_surface_mailbox_, + scoped_refptr(context_state_)); + + if (!root_surface_skia_representation_) { + return false; + } + } + + return true; +} + +void SkiaOutputDeviceDCompPresenter::DestroyRootSurface() { + root_surface_write_access_.reset(); + root_surface_skia_representation_.reset(); + + if (!root_surface_mailbox_.IsZero()) { + shared_image_factory_->DestroySharedImage(root_surface_mailbox_); + root_surface_mailbox_.SetZero(); + } +} + +SkSurface* SkiaOutputDeviceDCompPresenter::BeginPaint( + std::vector* end_semaphores) { + if (!EnsureRootSurfaceAllocated()) { + DLOG(ERROR) << "Could not create root SharedImage"; + return nullptr; + } + + DCHECK(root_surface_skia_representation_); + DCHECK(update_rect_.has_value()); + + std::vector begin_semaphores; + root_surface_write_access_ = + root_surface_skia_representation_->BeginScopedWriteAccess( + characterization_.sampleCount(), characterization_.surfaceProps(), + update_rect_.value(), &begin_semaphores, end_semaphores, + gpu::SkiaImageRepresentation::AllowUnclearedAccess::kYes, true); + update_rect_.reset(); + if (!root_surface_write_access_) { + return nullptr; + } + + // We don't expect any semaphores on a Windows, non-Vulkan backend. + DCHECK(begin_semaphores.empty()); + DCHECK(end_semaphores->empty()); + + return root_surface_write_access_->surface(); +} + +void SkiaOutputDeviceDCompPresenter::Submit(bool sync_cpu, + base::OnceClosure callback) { + if (root_surface_write_access_) { + // On Windows, we expect `end_state` to be null, since DX11 doesn't use + // resource states/barriers. + auto end_state = root_surface_write_access_->TakeEndState(); + DCHECK_EQ(nullptr, end_state); + } + + SkiaOutputDevice::Submit(sync_cpu, std::move(callback)); +} + +void SkiaOutputDeviceDCompPresenter::EndPaint() { + DCHECK(root_surface_skia_representation_); + DCHECK(root_surface_write_access_); + + // Assume the caller has drawn to everything since the first update rect is + // required to cover the whole surface. + root_surface_skia_representation_->SetCleared(); + + root_surface_write_access_.reset(); +} + +bool SkiaOutputDeviceDCompPresenter::IsRootSurfaceAllocatedForTesting() const { + return !root_surface_mailbox_.IsZero(); +} + +bool SkiaOutputDeviceDCompPresenter::ScheduleDCLayer( + std::unique_ptr params) { + return presenter_->ScheduleDCLayer(std::move(params)); +} + +gfx::Size SkiaOutputDeviceDCompPresenter::GetRootSurfaceSize() const { + return presenter_->GetSize(); +} + +void SkiaOutputDeviceDCompPresenter::DoPresent( + const gfx::Rect& rect, + gl::GLSurface::SwapCompletionCallback completion_callback, + BufferPresentedCallback feedback, + gfx::FrameData data) { + if (!ScheduleRootSurfaceAsOverlay()) { + std::move(completion_callback) + .Run(gfx::SwapCompletionResult(gfx::SwapResult::SWAP_FAILED)); + // Notify the caller, the buffer is never presented on a screen. + std::move(feedback).Run(gfx::PresentationFeedback::Failure()); + return; + } + + // The |rect| is ignored because SetDrawRectangle specified the area to be + // swapped. + presenter_->Present(std::move(completion_callback), std::move(feedback), + data); +} + +bool SkiaOutputDeviceDCompPresenter::ScheduleRootSurfaceAsOverlay() { + auto overlay = shared_image_representation_factory_->ProduceOverlay( + root_surface_mailbox_); + if (!overlay) { + return false; + } + + auto read_access = overlay->BeginScopedReadAccess(); + if (!read_access) { + return false; + } + + auto params = std::make_unique(); + params->z_order = 0; + params->quad_rect = gfx::Rect(GetRootSurfaceSize()); + params->content_rect = params->quad_rect; + params->overlay_image = read_access->GetDCLayerOverlayImage(); + ScheduleDCLayer(std::move(params)); + + return true; } } // namespace viz diff --git a/components/viz/service/display_embedder/skia_output_device_dcomp.h b/components/viz/service/display_embedder/skia_output_device_dcomp.h index 5bd984ef8f4ba..2c1c2be552b5f 100644 --- a/components/viz/service/display_embedder/skia_output_device_dcomp.h +++ b/components/viz/service/display_embedder/skia_output_device_dcomp.h @@ -14,7 +14,9 @@ #include "base/memory/ref_counted.h" #include "base/memory/weak_ptr.h" #include "components/viz/service/display_embedder/skia_output_device.h" +#include "gpu/command_buffer/service/shared_image/shared_image_representation.h" #include "ui/gfx/frame_data.h" +#include "ui/gl/presenter.h" namespace gl { class DCLayerOverlayImage; @@ -23,9 +25,9 @@ class GLSurface; } // namespace gl namespace gpu { -class MailboxManager; class SharedContextState; class SharedImageRepresentationFactory; +class SharedImageFactory; namespace gles2 { class FeatureInfo; @@ -54,7 +56,6 @@ class SkiaOutputDeviceDComp : public SkiaOutputDevice { class OverlayData; SkiaOutputDeviceDComp( - gpu::MailboxManager* mailbox_manager, gpu::SharedImageRepresentationFactory* shared_image_representation_factory, gpu::SharedContextState* context_state, @@ -73,9 +74,11 @@ class SkiaOutputDeviceDComp : public SkiaOutputDevice { virtual gfx::Size GetRootSurfaceSize() const = 0; - virtual gfx::SwapResult DoPostSubBuffer(const gfx::Rect& rect, - BufferPresentedCallback feedback, - gfx::FrameData data) = 0; + virtual void DoPresent( + const gfx::Rect& rect, + gl::GLSurface::SwapCompletionCallback completion_callback, + BufferPresentedCallback feedback, + gfx::FrameData data) = 0; // Mailboxes of overlays scheduled in the current frame. base::flat_set scheduled_overlay_mailboxes_; @@ -83,22 +86,26 @@ class SkiaOutputDeviceDComp : public SkiaOutputDevice { // Holds references to overlay textures so they aren't destroyed while in use. base::flat_map overlays_; - const raw_ptr mailbox_manager_; - const raw_ptr shared_image_representation_factory_; const raw_ptr context_state_; + private: + // Completion callback for |DoPresent|. + void OnPresentFinished(OutputSurfaceFrame frame, + gfx::SwapCompletionResult result); + base::WeakPtrFactory weak_ptr_factory_{this}; }; // A DComp-backed OutputDevice whose root surface is wrapped in a GLSurface. +// It is intended to be replaced by |SkiaOutputDeviceDCompPresenter| when +// |DirectCompositionSurfaceWin| is removed. class VIZ_SERVICE_EXPORT SkiaOutputDeviceDCompGLSurface final : public SkiaOutputDeviceDComp { public: SkiaOutputDeviceDCompGLSurface( - gpu::MailboxManager* mailbox_manager, gpu::SharedImageRepresentationFactory* shared_image_representation_factory, gpu::SharedContextState* context_state, @@ -125,9 +132,10 @@ class VIZ_SERVICE_EXPORT SkiaOutputDeviceDCompGLSurface final bool ScheduleDCLayer( std::unique_ptr params) override; gfx::Size GetRootSurfaceSize() const override; - gfx::SwapResult DoPostSubBuffer(const gfx::Rect& rect, - BufferPresentedCallback feedback, - gfx::FrameData data) override; + void DoPresent(const gfx::Rect& rect, + gl::GLSurface::SwapCompletionCallback completion_callback, + BufferPresentedCallback feedback, + gfx::FrameData data) override; private: scoped_refptr gl_surface_; @@ -140,6 +148,82 @@ class VIZ_SERVICE_EXPORT SkiaOutputDeviceDCompGLSurface final uint64_t backbuffer_estimated_size_ = 0; }; +// A DComp-backed OutputDevice that directly owns the root surface. +class VIZ_SERVICE_EXPORT SkiaOutputDeviceDCompPresenter final + : public SkiaOutputDeviceDComp { + public: + SkiaOutputDeviceDCompPresenter( + gpu::SharedImageFactory* shared_image_factory, + gpu::SharedImageRepresentationFactory* + shared_image_representation_factory, + gpu::SharedContextState* context_state, + scoped_refptr presenter, + scoped_refptr feature_info, + gpu::MemoryTracker* memory_tracker, + DidSwapBufferCompleteCallback did_swap_buffer_complete_callback); + + ~SkiaOutputDeviceDCompPresenter() override; + + // SkiaOutputDevice implementation: + bool Reshape(const SkSurfaceCharacterization& characterization, + const gfx::ColorSpace& color_space, + float device_scale_factor, + gfx::OverlayTransform transform) override; + void Submit(bool sync_cpu, base::OnceClosure callback) override; + bool SetDrawRectangle(const gfx::Rect& draw_rectangle) override; + void SetEnableDCLayers(bool enable) override; + void SetGpuVSyncEnabled(bool enabled) override; + SkSurface* BeginPaint( + std::vector* end_semaphores) override; + void EndPaint() override; + + bool IsRootSurfaceAllocatedForTesting() const; + + protected: + bool ScheduleDCLayer( + std::unique_ptr params) override; + gfx::Size GetRootSurfaceSize() const override; + void DoPresent(const gfx::Rect& rect, + gl::GLSurface::SwapCompletionCallback completion_callback, + BufferPresentedCallback feedback, + gfx::FrameData data) override; + + private: + // Idempotent + bool EnsureRootSurfaceAllocated(); + // Idempotent + void DestroyRootSurface(); + + // Returns true on success. + bool ScheduleRootSurfaceAsOverlay(); + + // Any implementation capable of scheduling a DComp layer. Currently only + // |DCompPresenter|. + scoped_refptr presenter_; + + const raw_ptr shared_image_factory_; + + // Parameters from the most recent |Reshape|. + SkSurfaceCharacterization characterization_; + gfx::ColorSpace color_space_; + float device_scale_factor_ = 1.0; + gfx::OverlayTransform transform_; + + // Valid from SetDrawRectangle to BeginPaint + absl::optional update_rect_; + + bool want_dcomp_surface_ = false; + + // Valid from |EnsureRootSurfaceAllocated| to |DestroyRootSurface|. + gpu::Mailbox root_surface_mailbox_; + // Valid from |EnsureRootSurfaceAllocated| to |DestroyRootSurface|. + std::unique_ptr + root_surface_skia_representation_; + // Valid from BeginPaint to EndPaint + std::unique_ptr + root_surface_write_access_; +}; + } // namespace viz #endif // COMPONENTS_VIZ_SERVICE_DISPLAY_EMBEDDER_SKIA_OUTPUT_DEVICE_DCOMP_H_ diff --git a/components/viz/service/display_embedder/skia_output_device_dcomp_unittest.cc b/components/viz/service/display_embedder/skia_output_device_dcomp_unittest.cc new file mode 100644 index 0000000000000..f6da64733020a --- /dev/null +++ b/components/viz/service/display_embedder/skia_output_device_dcomp_unittest.cc @@ -0,0 +1,373 @@ +// 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 "components/viz/service/display_embedder/skia_output_device_dcomp.h" + +#include +#include + +#include "base/memory/scoped_refptr.h" +#include "components/viz/common/resources/shared_image_format.h" +#include "gpu/command_buffer/common/context_creation_attribs.h" +#include "gpu/command_buffer/common/mailbox.h" +#include "gpu/command_buffer/common/shared_image_usage.h" +#include "gpu/command_buffer/service/feature_info.h" +#include "gpu/command_buffer/service/service_utils.h" +#include "gpu/command_buffer/service/shared_context_state.h" +#include "gpu/command_buffer/service/shared_image/shared_image_backing.h" +#include "gpu/command_buffer/service/shared_image/shared_image_backing_factory.h" +#include "gpu/command_buffer/service/shared_image/shared_image_factory.h" +#include "gpu/command_buffer/service/shared_image/test_image_backing.h" +#include "gpu/config/gpu_driver_bug_workarounds.h" +#include "gpu/config/gpu_feature_info.h" +#include "gpu/config/gpu_preferences.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/skia/include/core/SkAlphaType.h" +#include "third_party/skia/include/core/SkColorType.h" +#include "third_party/skia/include/gpu/GrTypes.h" +#include "ui/gfx/color_space.h" +#include "ui/gfx/geometry/size.h" +#include "ui/gfx/surface_origin.h" +#include "ui/gl/dcomp_presenter.h" +#include "ui/gl/direct_composition_surface_win.h" +#include "ui/gl/gl_context.h" +#include "ui/gl/gl_implementation.h" +#include "ui/gl/gl_share_group.h" +#include "ui/gl/gl_surface.h" +#include "ui/gl/init/gl_factory.h" +#include "ui/gl/presenter.h" + +namespace viz { + +namespace { + +class TestSharedImageBackingFactory : public gpu::SharedImageBackingFactory { + public: + MOCK_METHOD(std::unique_ptr, + CreateSharedImage, + (const gpu::Mailbox& mailbox, + SharedImageFormat format, + gpu::SurfaceHandle surface_handle, + const gfx::Size& size, + const gfx::ColorSpace& color_space, + GrSurfaceOrigin surface_origin, + SkAlphaType alpha_type, + uint32_t usage, + bool is_thread_safe), + (override)); + + void SetCreateSharedImageSuccessByDefault() { + ON_CALL(*this, CreateSharedImage) + .WillByDefault( + [](const gpu::Mailbox& mailbox, SharedImageFormat format, + gpu::SurfaceHandle surface_handle, const gfx::Size& size, + const gfx::ColorSpace& color_space, + GrSurfaceOrigin surface_origin, SkAlphaType alpha_type, + uint32_t usage, bool is_thread_safe) { + return std::make_unique( + mailbox, format, size, color_space, surface_origin, + alpha_type, usage, 1); + }); + } + + std::unique_ptr CreateSharedImage( + const gpu::Mailbox& mailbox, + SharedImageFormat format, + const gfx::Size& size, + const gfx::ColorSpace& color_space, + GrSurfaceOrigin surface_origin, + SkAlphaType alpha_type, + uint32_t usage, + base::span pixel_data) override { + NOTREACHED(); + return nullptr; + } + std::unique_ptr CreateSharedImage( + const gpu::Mailbox& mailbox, + SharedImageFormat format, + const gfx::Size& size, + const gfx::ColorSpace& color_space, + GrSurfaceOrigin surface_origin, + SkAlphaType alpha_type, + uint32_t usage, + gfx::GpuMemoryBufferHandle handle) override { + NOTREACHED(); + return nullptr; + } + std::unique_ptr CreateSharedImage( + const gpu::Mailbox& mailbox, + gfx::GpuMemoryBufferHandle handle, + gfx::BufferFormat format, + gfx::BufferPlane plane, + const gfx::Size& size, + const gfx::ColorSpace& color_space, + GrSurfaceOrigin surface_origin, + SkAlphaType alpha_type, + uint32_t usage) override { + NOTREACHED(); + return nullptr; + } + bool IsSupported(uint32_t usage, + SharedImageFormat format, + const gfx::Size& size, + bool thread_safe, + gfx::GpuMemoryBufferType gmb_type, + gpu::GrContextType gr_context_type, + base::span pixel_data) override { + return true; + } +}; + +// No-op surface compatible with SkiaOutputDeviceDCompPresenter +class NoopDCompPresenter : public gl::Presenter { + public: + NoopDCompPresenter() + : gl::Presenter(gl::GLSurfaceEGL::GetGLDisplayEGL(), gfx::Size(1, 1)) {} + + bool SupportsDCLayers() const override { return true; } + bool SupportsGpuVSync() const override { return true; } + bool SupportsCommitOverlayPlanes() override { return false; } + + bool SetDrawRectangle(const gfx::Rect& rectangle) override { return true; } + + void Present(SwapCompletionCallback completion_callback, + PresentationCallback presentation_callback, + gfx::FrameData data) override { + NOTREACHED(); + } + + protected: + ~NoopDCompPresenter() override = default; +}; + +} // namespace + +class SkiaOutputDeviceDCompTest : public testing::Test { + public: + SkiaOutputDeviceDCompTest() {} + + protected: + void SetUp() override { + gpu::GpuDriverBugWorkarounds workarounds; + gpu::GpuPreferences gpu_preferences; + gpu_preferences.use_passthrough_cmd_decoder = true; + gpu_preferences.enable_gpu_debugging = true; + gpu::GpuFeatureInfo gpu_feature_info; + + scoped_refptr share_group = new gl::GLShareGroup(); + scoped_refptr surface = gl::init::CreateOffscreenGLSurface( + gl::GLSurfaceEGL::GetGLDisplayEGL(), gfx::Size()); + + gpu::ContextCreationAttribs attribs_helper; + attribs_helper.context_type = gpu::CONTEXT_TYPE_OPENGLES3; + gl::GLContextAttribs attribs = gpu::gles2::GenerateGLContextAttribs( + attribs_helper, gpu_preferences.use_passthrough_cmd_decoder); + attribs.can_skip_validation = false; + scoped_refptr context = + gl::init::CreateGLContext(share_group.get(), surface.get(), attribs); + ASSERT_NE(nullptr, context); + ASSERT_EQ(context->share_group(), share_group.get()); + gpu_feature_info.ApplyToGLContext(context.get()); + auto feature_info = base::MakeRefCounted( + workarounds, gpu_feature_info); + ASSERT_TRUE(context->MakeCurrent(surface.get())); + + context_state_ = base::MakeRefCounted( + std::move(share_group), surface, std::move(context), + false /* use_virtualized_gl_contexts */, base::DoNothing()); + ASSERT_TRUE(context_state_->MakeCurrent(surface.get())); + ASSERT_TRUE(context_state_->InitializeGL(gpu_preferences, feature_info)); + ASSERT_TRUE(context_state_->InitializeGrContext( + gpu_preferences, workarounds, /*cache=*/nullptr)); + EXPECT_EQ(gl::GetGLImplementation(), gl::kGLImplementationEGLANGLE); + + shared_image_factory_ = std::make_unique( + gpu_preferences, workarounds, gpu_feature_info, context_state_.get(), + &shared_image_manager_, nullptr, + /*is_for_display_compositor=*/false); + + test_shared_image_factory_ = + std::make_unique(); + test_shared_image_factory_->SetCreateSharedImageSuccessByDefault(); + shared_image_factory_->RegisterSharedImageBackingFactoryForTesting( + test_shared_image_factory_.get()); + + shared_image_representation_factory_ = + std::make_unique( + &shared_image_manager_, nullptr); + + surface_ = base::MakeRefCounted(); + + output_device_ = base::WrapUnique(new SkiaOutputDeviceDCompPresenter( + shared_image_factory_.get(), shared_image_representation_factory_.get(), + context_state_.get(), surface_, feature_info, nullptr, + base::DoNothing())); + } + + void TearDown() override { + output_device_.reset(); + surface_.reset(); + context_state_.reset(); + test_shared_image_factory_.reset(); + shared_image_factory_.reset(); + shared_image_representation_factory_.reset(); + } + + void Reshape(const gfx::Size size, + SkColorType color_type, + bool has_alpha, + const gfx::ColorSpace& color_space) { + sk_sp gr_thread_safe_proxy = + context_state_->gr_context()->threadSafeProxy(); + + SkImageInfo image_info = SkImageInfo::Make( + SkISize::Make(size.width(), size.height()), color_type, + has_alpha ? kPremul_SkAlphaType : kOpaque_SkAlphaType); + GrBackendFormat backend_format = gr_thread_safe_proxy->defaultBackendFormat( + color_type, GrRenderable::kYes); + + SkSurfaceCharacterization characterization = + gr_thread_safe_proxy->createCharacterization( + context_state_->gr_context()->getResourceCacheLimit(), image_info, + backend_format, 1, kTopLeft_GrSurfaceOrigin, SkSurfaceProps(), + false); + EXPECT_TRUE( + output_device_->Reshape(characterization, color_space, 1.0, + gfx::OverlayTransform::OVERLAY_TRANSFORM_NONE)); + + last_reshape_size_ = size; + } + + // Do a fake draw to force the root surface to allocate. + void EnsureRootSurfaceAllocated() { + EXPECT_TRUE( + output_device_->SetDrawRectangle(gfx::Rect(last_reshape_size_))); + std::vector end_semaphores; + EXPECT_NE(nullptr, output_device_->BeginPaint(&end_semaphores)); + EXPECT_EQ(0u, end_semaphores.size()); + output_device_->EndPaint(); + } + + std::unique_ptr output_device_; + + std::unique_ptr test_shared_image_factory_; + + private: + // Store the last size passed to Reshape so that EnsureRootSurfaceAllocated + // knows what update_rect to pass. + gfx::Size last_reshape_size_; + + scoped_refptr context_state_; + std::unique_ptr shared_image_factory_; + gpu::SharedImageManager shared_image_manager_; + std::unique_ptr + shared_image_representation_factory_; + scoped_refptr surface_; +}; + +// Tests that switching using EnableDCLayers works. +TEST_F(SkiaOutputDeviceDCompTest, DXGIDCLayerSwitch) { + Reshape(gfx::Size(100, 100), kRGBA_8888_SkColorType, true, + gfx::ColorSpace::CreateSRGB()); + + // Check we allocate a a DXGI swap chain when asked. + output_device_->SetEnableDCLayers(false); + EXPECT_FALSE(output_device_->IsRootSurfaceAllocatedForTesting()); + EXPECT_CALL(*test_shared_image_factory_, + CreateSharedImage(testing::_, testing::_, testing::_, testing::_, + testing::_, testing::_, kPremul_SkAlphaType, + gpu::SHARED_IMAGE_USAGE_DISPLAY_READ | + gpu::SHARED_IMAGE_USAGE_DISPLAY_WRITE | + gpu::SHARED_IMAGE_USAGE_SCANOUT, + testing::_)); + EnsureRootSurfaceAllocated(); + EXPECT_TRUE(output_device_->IsRootSurfaceAllocatedForTesting()); + + // Not changing the DC layer state should not affect anything. Note that since + // CreateSharedImage is mocked, EnsureRootSurfaceAllocated will cause the test + // to fail if it calls CreateSharedImage unexpectedly. + output_device_->SetEnableDCLayers(false); + EXPECT_TRUE(output_device_->IsRootSurfaceAllocatedForTesting()); + EnsureRootSurfaceAllocated(); + + // Check that switching DC layer state releases the root surface and allocated + // a DComp surface + output_device_->SetEnableDCLayers(true); + EXPECT_FALSE(output_device_->IsRootSurfaceAllocatedForTesting()); + EXPECT_CALL(*test_shared_image_factory_, + CreateSharedImage( + testing::_, testing::_, testing::_, testing::_, testing::_, + testing::_, SkAlphaType::kPremul_SkAlphaType, + gpu::SHARED_IMAGE_USAGE_DISPLAY_WRITE | + gpu::SHARED_IMAGE_USAGE_SCANOUT | + gpu::SHARED_IMAGE_USAGE_SCANOUT_DCOMP_SURFACE, + testing::_)); + EnsureRootSurfaceAllocated(); + EXPECT_TRUE(output_device_->IsRootSurfaceAllocatedForTesting()); + + // Not changing the DC layer state should not affect anything + output_device_->SetEnableDCLayers(true); + EXPECT_TRUE(output_device_->IsRootSurfaceAllocatedForTesting()); + EnsureRootSurfaceAllocated(); + + // Check that we can switch back to a swap chain + output_device_->SetEnableDCLayers(false); + EXPECT_FALSE(output_device_->IsRootSurfaceAllocatedForTesting()); + EXPECT_CALL(*test_shared_image_factory_, + CreateSharedImage(testing::_, testing::_, testing::_, testing::_, + testing::_, testing::_, + SkAlphaType::kPremul_SkAlphaType, + gpu::SHARED_IMAGE_USAGE_DISPLAY_READ | + gpu::SHARED_IMAGE_USAGE_DISPLAY_WRITE | + gpu::SHARED_IMAGE_USAGE_SCANOUT, + testing::_)); + EnsureRootSurfaceAllocated(); + EXPECT_TRUE(output_device_->IsRootSurfaceAllocatedForTesting()); +} + +// Check that Reshape only destroys the root surface when its parameters change. +TEST_F(SkiaOutputDeviceDCompTest, Reshape) { + EXPECT_FALSE(output_device_->IsRootSurfaceAllocatedForTesting()); + + Reshape(gfx::Size(100, 100), kRGBA_8888_SkColorType, true, + gfx::ColorSpace::CreateSRGB()); + EXPECT_FALSE(output_device_->IsRootSurfaceAllocatedForTesting()); + + EnsureRootSurfaceAllocated(); + EXPECT_TRUE(output_device_->IsRootSurfaceAllocatedForTesting()); + + // No parameters changed, root surface should remain allocated + Reshape(gfx::Size(100, 100), kRGBA_8888_SkColorType, true, + gfx::ColorSpace::CreateSRGB()); + EXPECT_TRUE(output_device_->IsRootSurfaceAllocatedForTesting()); + + // A change in parameters results in the root surface being deallocated + Reshape(gfx::Size(100, 100), kRGBA_8888_SkColorType, false, + gfx::ColorSpace::CreateSRGB()); + EXPECT_FALSE(output_device_->IsRootSurfaceAllocatedForTesting()); + EnsureRootSurfaceAllocated(); + EXPECT_TRUE(output_device_->IsRootSurfaceAllocatedForTesting()); +} + +// Check that we fallback to a swap chain when we want a DComp surface with an +// unsupported pixel format. +TEST_F(SkiaOutputDeviceDCompTest, HDR10ColorSpaceForcesSwapChain) { + output_device_->SetEnableDCLayers(true); + Reshape(gfx::Size(100, 100), kRGBA_1010102_SkColorType, true, + gfx::ColorSpace::CreateHDR10()); + EXPECT_CALL(*test_shared_image_factory_, + CreateSharedImage(testing::_, testing::_, testing::_, testing::_, + testing::_, testing::_, + SkAlphaType::kPremul_SkAlphaType, + gpu::SHARED_IMAGE_USAGE_DISPLAY_READ | + gpu::SHARED_IMAGE_USAGE_DISPLAY_WRITE | + gpu::SHARED_IMAGE_USAGE_SCANOUT, + testing::_)); + EXPECT_FALSE(output_device_->IsRootSurfaceAllocatedForTesting()); + EnsureRootSurfaceAllocated(); + EXPECT_TRUE(output_device_->IsRootSurfaceAllocatedForTesting()); +} + +} // namespace viz diff --git a/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.cc b/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.cc index 2c722f9f50d1a..c2d290302308b 100644 --- a/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.cc +++ b/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.cc @@ -1760,7 +1760,12 @@ bool SkiaOutputSurfaceImplOnGpu::InitializeForGL() { shared_gpu_deps_->memory_tracker(), GetDidSwapBuffersCompleteCallback()); #else // !BUILDFLAG(IS_WIN) - NOTIMPLEMENTED(); + DCHECK(presenter_->SupportsDCLayers()); + output_device_ = std::make_unique( + shared_image_factory_.get(), + shared_image_representation_factory_.get(), context_state_.get(), + presenter_, feature_info_, shared_gpu_deps_->memory_tracker(), + GetDidSwapBuffersCompleteCallback()); #endif // BUILDFLAG(IS_WIN) } else { if (dependency_->NeedsSupportForExternalStencil()) { @@ -1772,7 +1777,6 @@ bool SkiaOutputSurfaceImplOnGpu::InitializeForGL() { #if BUILDFLAG(IS_WIN) if (gl_surface_->SupportsDCLayers()) { output_device_ = std::make_unique( - dependency_->GetMailboxManager(), shared_image_representation_factory_.get(), context_state_.get(), gl_surface_, feature_info_, shared_gpu_deps_->memory_tracker(), diff --git a/gpu/ipc/service/image_transport_surface_win.cc b/gpu/ipc/service/image_transport_surface_win.cc index a2434a81784bf..2ee47325d28d7 100644 --- a/gpu/ipc/service/image_transport_surface_win.cc +++ b/gpu/ipc/service/image_transport_surface_win.cc @@ -11,6 +11,7 @@ #include "gpu/config/gpu_preferences.h" #include "gpu/ipc/service/pass_through_image_transport_surface.h" #include "ui/gfx/native_widget_types.h" +#include "ui/gl/dcomp_presenter.h" #include "ui/gl/direct_composition_support.h" #include "ui/gl/direct_composition_surface_win.h" #include "ui/gl/gl_bindings.h" @@ -23,6 +24,14 @@ namespace gpu { namespace { +// Use a DCompPresenter as the root surface, instead of a +// DirectCompositionSurfaceWin. DCompPresenter is surface-less and the actual +// allocation of the root surface will be owned by the +// SkiaOutputDeviceDCompPresenter. +BASE_FEATURE(kDCompPresenter, + "DCompPresenter", + base::FEATURE_DISABLED_BY_DEFAULT); + gl::DirectCompositionSurfaceWin::Settings CreateDirectCompositionSurfaceSettings( const GpuDriverBugWorkarounds& workarounds) { @@ -45,6 +54,22 @@ scoped_refptr ImageTransportSurface::CreatePresenter( base::WeakPtr delegate, SurfaceHandle surface_handle, gl::GLSurfaceFormat format) { + if (gl::DirectCompositionSupported() && + base::FeatureList::IsEnabled(kDCompPresenter)) { + auto vsync_callback = delegate->GetGpuVSyncCallback(); + auto settings = CreateDirectCompositionSurfaceSettings( + delegate->GetFeatureInfo()->workarounds()); + auto presenter = base::MakeRefCounted( + display->GetAs(), std::move(vsync_callback), + settings); + if (!presenter->Initialize(gl::GLSurfaceFormat())) { + return nullptr; + } + + delegate->AddChildWindowToBrowser(presenter->window()); + return presenter; + } + return nullptr; } diff --git a/ui/gl/dc_layer_tree.cc b/ui/gl/dc_layer_tree.cc index c7e87fc6e32a9..1412308555430 100644 --- a/ui/gl/dc_layer_tree.cc +++ b/ui/gl/dc_layer_tree.cc @@ -22,6 +22,22 @@ bool SizeContains(const gfx::Size& a, const gfx::Size& b) { return gfx::Rect(a).Contains(gfx::Rect(b)); } +bool NeedSwapChainPresenter(const DCLayerOverlayParams* overlay) { + // TODO(tangm): when we have more overlays originating from + // SkiaOutputDeviceDComp, we should replace this with an explicit "needs swap + // chain presenter" flag on DCLayerOverlayParams. + switch (overlay->overlay_image->type()) { + case DCLayerOverlayType::kNV12Texture: + case DCLayerOverlayType::kNV12Pixmap: + case DCLayerOverlayType::kDCompSurfaceProxy: + return true; + case DCLayerOverlayType::kDCompVisualContent: + // Z-order of 0 indicates the backbuffer, which already has been presented + // and ready for the DComp tree. + return overlay->z_order != 0; + } +} + } // namespace VideoProcessorWrapper::VideoProcessorWrapper() = default; @@ -305,11 +321,53 @@ bool DCLayerTree::CommitAndClearPendingOverlays( "num_pending_overlays", pending_overlays_.size()); DCHECK(!needs_rebuild_visual_tree_ || ink_renderer_->HasBeenInitialized()); - if (root_surface) { - if (root_surface->swap_chain() != root_swap_chain_ || - root_surface->dcomp_surface() != root_dcomp_surface_) { - root_swap_chain_ = root_surface->swap_chain(); - root_dcomp_surface_ = root_surface->dcomp_surface(); + { + Microsoft::WRL::ComPtr root_swap_chain; + Microsoft::WRL::ComPtr root_dcomp_surface; + if (root_surface) { + root_swap_chain = root_surface->swap_chain(); + root_dcomp_surface = root_surface->dcomp_surface(); + + Microsoft::WRL::ComPtr root_visual_content; + if (root_swap_chain) { + root_visual_content = root_swap_chain; + } else { + root_visual_content = root_dcomp_surface; + } + + // Add a placeholder overlay for the root surface, at a z-order of 0. + auto root_params = std::make_unique(); + root_params->z_order = 0; + root_params->overlay_image = DCLayerOverlayImage( + root_surface->GetSize(), std::move(root_visual_content), + root_surface->dcomp_surface_serial()); + ScheduleDCLayer(std::move(root_params)); + } else { + auto it = std::find_if( + pending_overlays_.begin(), pending_overlays_.end(), + [](const std::unique_ptr& overlay) { + return overlay->z_order == 0; + }); + if (it != pending_overlays_.end()) { + Microsoft::WRL::ComPtr root_visual_content = + (*it)->overlay_image->dcomp_visual_content(); + HRESULT hr = root_visual_content.As(&root_swap_chain); + if (hr == E_NOINTERFACE) { + DCHECK_EQ(nullptr, root_swap_chain); + hr = root_visual_content.As(&root_dcomp_surface); + } + CHECK_EQ(S_OK, hr); + } else { + // Note: this is allowed in tests, but not expected otherwise. + DLOG(WARNING) << "No root surface in overlay list"; + } + } + + if (root_swap_chain != root_swap_chain_ || + root_dcomp_surface != root_dcomp_surface_) { + DCHECK(!(root_swap_chain && root_dcomp_surface)); + root_swap_chain_ = std::move(root_swap_chain); + root_dcomp_surface_ = std::move(root_dcomp_surface); needs_rebuild_visual_tree_ = true; } } @@ -318,31 +376,18 @@ bool DCLayerTree::CommitAndClearPendingOverlays( std::swap(pending_overlays_, overlays); // Grow or shrink list of swap chain presenters to match pending overlays. - if (video_swap_chains_.size() != overlays.size()) { - video_swap_chains_.resize(overlays.size()); + const size_t num_swap_chain_presenters = + std::count_if(overlays.begin(), overlays.end(), [](const auto& overlay) { + return NeedSwapChainPresenter(overlay.get()); + }); + // Grow or shrink list of swap chain presenters to match pending overlays. + if (video_swap_chains_.size() != num_swap_chain_presenters) { + video_swap_chains_.resize(num_swap_chain_presenters); // If we need to grow or shrink swap chain presenters, we'll need to add or // remove visuals. needs_rebuild_visual_tree_ = true; } - // DCompSurfaceless also uses DCLayerTree and lets its caller schedule an - // overlay for the root surface, instead of owning its own. - if (root_surface) { - Microsoft::WRL::ComPtr root_visual_content; - if (root_swap_chain_) { - root_visual_content = root_swap_chain_; - } else { - root_visual_content = root_dcomp_surface_; - } - // Add a placeholder overlay for the root surface, at a z-order of 0. - auto root_params = std::make_unique(); - root_params->z_order = 0; - root_params->overlay_image = DCLayerOverlayImage( - root_surface->GetSize(), std::move(root_visual_content), - root_surface->dcomp_surface_serial()); - overlays.emplace_back(std::move(root_params)); - } - // Sort layers by z-order. std::sort(overlays.begin(), overlays.end(), [](const auto& a, const auto& b) -> bool { @@ -356,9 +401,9 @@ bool DCLayerTree::CommitAndClearPendingOverlays( // Populate |overlays| with information required to build dcomp visual tree. for (size_t i = 0; i < overlays.size(); ++i) { - // Skip root surface overlay. - if (overlays[i]->z_order == 0) + if (!NeedSwapChainPresenter(overlays[i].get())) { continue; + } // Present to swap chain and update the overlay with transform, clip // and content. auto& video_swap_chain = *(video_swap_iter++); diff --git a/ui/gl/dc_layer_tree.h b/ui/gl/dc_layer_tree.h index aac91bf056870..bcff97a43c113 100644 --- a/ui/gl/dc_layer_tree.h +++ b/ui/gl/dc_layer_tree.h @@ -122,6 +122,10 @@ class DCLayerTree { gfx::Point* offset, gfx::Rect* clip_rect) const; + size_t GetSwapChainPresenterCountForTesting() const { + return video_swap_chains_.size(); + } + void SetFrameRate(float frame_rate); const std::unique_ptr& GetHDRMetadataHelper() { diff --git a/ui/gl/dcomp_presenter.cc b/ui/gl/dcomp_presenter.cc index d2cccfbaaaca9..e17c1efb2a8bd 100644 --- a/ui/gl/dcomp_presenter.cc +++ b/ui/gl/dcomp_presenter.cc @@ -45,7 +45,7 @@ DCompPresenter::DCompPresenter( GLDisplayEGL* display, VSyncCallback vsync_callback, const DirectCompositionSurfaceWin::Settings& settings) - : SurfacelessEGL(display, gfx::Size(1, 1)), + : Presenter(display, gfx::Size(1, 1)), vsync_callback_(std::move(vsync_callback)), vsync_thread_(VSyncThreadWin::GetInstance()), task_runner_(base::SingleThreadTaskRunner::GetCurrentDefault()), @@ -96,10 +96,6 @@ void DCompPresenter::Destroy() { dcomp_device->Commit(); } -bool DCompPresenter::IsOffscreen() { - return false; -} - bool DCompPresenter::Resize(const gfx::Size& size, float scale_factor, const gfx::ColorSpace& color_space, @@ -117,32 +113,6 @@ bool DCompPresenter::Resize(const gfx::Size& size, return true; } -gfx::SwapResult DCompPresenter::SwapBuffers(PresentationCallback callback, - gfx::FrameData data) { - TRACE_EVENT0("gpu", "DCompPresenter::SwapBuffers"); - - // Callback will be dequeued on next vsync. - EnqueuePendingFrame(std::move(callback), - /*create_query=*/create_query_this_frame_); - create_query_this_frame_ = false; - - if (!layer_tree_->CommitAndClearPendingOverlays(nullptr)) - return gfx::SwapResult::SWAP_FAILED; - - return gfx::SwapResult::SWAP_ACK; -} - -gfx::SwapResult DCompPresenter::PostSubBuffer(int x, - int y, - int width, - int height, - PresentationCallback callback, - gfx::FrameData data) { - // The arguments are ignored because SetDrawRectangle specified the area to - // be swapped. - return SwapBuffers(std::move(callback), data); -} - gfx::VSyncProvider* DCompPresenter::GetVSyncProvider() { return vsync_thread_->vsync_provider(); } @@ -175,12 +145,24 @@ void DCompPresenter::SetFrameRate(float frame_rate) { layer_tree_->SetFrameRate(frame_rate); } -gfx::SurfaceOrigin DCompPresenter::GetOrigin() const { - return gfx::SurfaceOrigin::kTopLeft; -} +void DCompPresenter::Present(SwapCompletionCallback completion_callback, + PresentationCallback presentation_callback, + gfx::FrameData data) { + TRACE_EVENT0("gpu", "DCompPresenter::Present"); -bool DCompPresenter::SupportsPostSubBuffer() { - return true; + // Callback will be dequeued on next vsync. + EnqueuePendingFrame(std::move(presentation_callback), + /*create_query=*/create_query_this_frame_); + create_query_this_frame_ = false; + + if (!layer_tree_->CommitAndClearPendingOverlays(nullptr)) { + std::move(completion_callback) + .Run(gfx::SwapCompletionResult(gfx::SwapResult::SWAP_FAILED)); + return; + } + + std::move(completion_callback) + .Run(gfx::SwapCompletionResult(gfx::SwapResult::SWAP_ACK)); } bool DCompPresenter::SupportsDCLayers() const { diff --git a/ui/gl/dcomp_presenter.h b/ui/gl/dcomp_presenter.h index 367da965b9e7a..befaf6f529fe8 100644 --- a/ui/gl/dcomp_presenter.h +++ b/ui/gl/dcomp_presenter.h @@ -21,7 +21,7 @@ #include "ui/gl/child_window_win.h" #include "ui/gl/direct_composition_surface_win.h" #include "ui/gl/gl_export.h" -#include "ui/gl/gl_surface_egl.h" +#include "ui/gl/presenter.h" #include "ui/gl/vsync_observer.h" namespace base { @@ -41,7 +41,7 @@ class DCLayerTree; // This class owns the DComp layer tree and its presentation. It does not own // the root surface. -class GL_EXPORT DCompPresenter : public SurfacelessEGL, public VSyncObserver { +class GL_EXPORT DCompPresenter : public Presenter, public VSyncObserver { public: using VSyncCallback = base::RepeatingCallback; @@ -57,22 +57,11 @@ class GL_EXPORT DCompPresenter : public SurfacelessEGL, public VSyncObserver { // GLSurfaceEGL implementation. bool Initialize(GLSurfaceFormat format) override; void Destroy() override; - bool IsOffscreen() override; bool Resize(const gfx::Size& size, float scale_factor, const gfx::ColorSpace& color_space, bool has_alpha) override; - gfx::SwapResult SwapBuffers(PresentationCallback callback, - gfx::FrameData data) override; - gfx::SwapResult PostSubBuffer(int x, - int y, - int width, - int height, - PresentationCallback callback, - gfx::FrameData data) override; gfx::VSyncProvider* GetVSyncProvider() override; - gfx::SurfaceOrigin GetOrigin() const override; - bool SupportsPostSubBuffer() override; bool SupportsDCLayers() const override; bool SupportsProtectedVideo() const override; bool SetDrawRectangle(const gfx::Rect& rect) override; @@ -86,6 +75,10 @@ class GL_EXPORT DCompPresenter : public SurfacelessEGL, public VSyncObserver { bool ScheduleDCLayer(std::unique_ptr params) override; void SetFrameRate(float frame_rate) override; + void Present(SwapCompletionCallback completion_callback, + PresentationCallback presentation_callback, + gfx::FrameData data) override; + // VSyncObserver implementation. void OnVSync(base::TimeTicks vsync_time, base::TimeDelta interval) override; diff --git a/ui/gl/dcomp_presenter_unittest.cc b/ui/gl/dcomp_presenter_unittest.cc index 1374534bf9a87..d8dd40f260e7f 100644 --- a/ui/gl/dcomp_presenter_unittest.cc +++ b/ui/gl/dcomp_presenter_unittest.cc @@ -174,6 +174,21 @@ class DCompPresenterTest : public testing::Test { return context; } + // Wait for |surface_| to present asynchronously check the swap result. + void PresentAndCheckSwapResult(gfx::SwapResult expected_swap_result) { + base::RunLoop wait_for_present; + surface_->Present(base::BindOnce( + [](base::RepeatingClosure quit_closure, + gfx::SwapResult expected_swap_result, + gfx::SwapCompletionResult result) { + EXPECT_EQ(expected_swap_result, result.swap_result); + quit_closure.Run(); + }, + wait_for_present.QuitClosure(), expected_swap_result), + base::DoNothing(), gfx::FrameData()); + wait_for_present.Run(); + } + HWND parent_window_; scoped_refptr surface_; scoped_refptr context_; @@ -207,8 +222,7 @@ TEST_F(DCompPresenterTest, NoPresentTwice) { surface_->GetLayerSwapChainForTesting(0); ASSERT_FALSE(swap_chain); - EXPECT_EQ(gfx::SwapResult::SWAP_ACK, - surface_->SwapBuffers(base::DoNothing(), gfx::FrameData())); + PresentAndCheckSwapResult(gfx::SwapResult::SWAP_ACK); swap_chain = surface_->GetLayerSwapChainForTesting(0); ASSERT_TRUE(swap_chain); @@ -229,8 +243,7 @@ TEST_F(DCompPresenterTest, NoPresentTwice) { surface_->ScheduleDCLayer(std::move(params)); } - EXPECT_EQ(gfx::SwapResult::SWAP_ACK, - surface_->SwapBuffers(base::DoNothing(), gfx::FrameData())); + PresentAndCheckSwapResult(gfx::SwapResult::SWAP_ACK); Microsoft::WRL::ComPtr swap_chain2 = surface_->GetLayerSwapChainForTesting(0); @@ -253,8 +266,7 @@ TEST_F(DCompPresenterTest, NoPresentTwice) { surface_->ScheduleDCLayer(std::move(params)); } - EXPECT_EQ(gfx::SwapResult::SWAP_ACK, - surface_->SwapBuffers(base::DoNothing(), gfx::FrameData())); + PresentAndCheckSwapResult(gfx::SwapResult::SWAP_ACK); Microsoft::WRL::ComPtr swap_chain3 = surface_->GetLayerSwapChainForTesting(0); @@ -292,8 +304,7 @@ TEST_F(DCompPresenterTest, SwapchainSizeWithScaledOverlays) { surface_->ScheduleDCLayer(std::move(params)); } - EXPECT_EQ(gfx::SwapResult::SWAP_ACK, - surface_->SwapBuffers(base::DoNothing(), gfx::FrameData())); + PresentAndCheckSwapResult(gfx::SwapResult::SWAP_ACK); Microsoft::WRL::ComPtr swap_chain = surface_->GetLayerSwapChainForTesting(0); ASSERT_TRUE(swap_chain); @@ -307,8 +318,7 @@ TEST_F(DCompPresenterTest, SwapchainSizeWithScaledOverlays) { // Clear SwapChainPresenters // Must do Clear first because the swap chain won't resize immediately if // a new size is given unless this is the very first time after Clear. - EXPECT_EQ(gfx::SwapResult::SWAP_ACK, - surface_->SwapBuffers(base::DoNothing(), gfx::FrameData())); + PresentAndCheckSwapResult(gfx::SwapResult::SWAP_ACK); // The input texture size is bigger than the window size. quad_rect = gfx::Rect(32, 48); @@ -322,8 +332,7 @@ TEST_F(DCompPresenterTest, SwapchainSizeWithScaledOverlays) { surface_->ScheduleDCLayer(std::move(params)); } - EXPECT_EQ(gfx::SwapResult::SWAP_ACK, - surface_->SwapBuffers(base::DoNothing(), gfx::FrameData())); + PresentAndCheckSwapResult(gfx::SwapResult::SWAP_ACK); Microsoft::WRL::ComPtr swap_chain2 = surface_->GetLayerSwapChainForTesting(0); @@ -359,8 +368,7 @@ TEST_F(DCompPresenterTest, SwapchainSizeWithoutScaledOverlays) { surface_->ScheduleDCLayer(std::move(params)); } - EXPECT_EQ(gfx::SwapResult::SWAP_ACK, - surface_->SwapBuffers(base::DoNothing(), gfx::FrameData())); + PresentAndCheckSwapResult(gfx::SwapResult::SWAP_ACK); Microsoft::WRL::ComPtr swap_chain = surface_->GetLayerSwapChainForTesting(0); ASSERT_TRUE(swap_chain); @@ -383,8 +391,7 @@ TEST_F(DCompPresenterTest, SwapchainSizeWithoutScaledOverlays) { surface_->ScheduleDCLayer(std::move(params)); } - EXPECT_EQ(gfx::SwapResult::SWAP_ACK, - surface_->SwapBuffers(base::DoNothing(), gfx::FrameData())); + PresentAndCheckSwapResult(gfx::SwapResult::SWAP_ACK); Microsoft::WRL::ComPtr swap_chain2 = surface_->GetLayerSwapChainForTesting(0); @@ -420,8 +427,7 @@ TEST_F(DCompPresenterTest, ProtectedVideos) { params->protected_video_type = gfx::ProtectedVideoType::kClear; surface_->ScheduleDCLayer(std::move(params)); - EXPECT_EQ(gfx::SwapResult::SWAP_ACK, - surface_->SwapBuffers(base::DoNothing(), gfx::FrameData())); + PresentAndCheckSwapResult(gfx::SwapResult::SWAP_ACK); Microsoft::WRL::ComPtr swap_chain = surface_->GetLayerSwapChainForTesting(0); ASSERT_TRUE(swap_chain); @@ -444,8 +450,7 @@ TEST_F(DCompPresenterTest, ProtectedVideos) { params->protected_video_type = gfx::ProtectedVideoType::kSoftwareProtected; surface_->ScheduleDCLayer(std::move(params)); - EXPECT_EQ(gfx::SwapResult::SWAP_ACK, - surface_->SwapBuffers(base::DoNothing(), gfx::FrameData())); + PresentAndCheckSwapResult(gfx::SwapResult::SWAP_ACK); Microsoft::WRL::ComPtr swap_chain = surface_->GetLayerSwapChainForTesting(0); ASSERT_TRUE(swap_chain); @@ -547,8 +552,7 @@ class DCompPresenterPixelTest : public DCompPresenterTest { params->color_space = gfx::ColorSpace::CreateREC709(); surface_->ScheduleDCLayer(std::move(params)); - EXPECT_EQ(gfx::SwapResult::SWAP_ACK, - surface_->SwapBuffers(base::DoNothing(), gfx::FrameData())); + PresentAndCheckSwapResult(gfx::SwapResult::SWAP_ACK); Sleep(1000); } @@ -584,8 +588,7 @@ class DCompPresenterVideoPixelTest : public DCompPresenterPixelTest { surface_->ScheduleDCLayer(std::move(params)); } - EXPECT_EQ(gfx::SwapResult::SWAP_ACK, - surface_->SwapBuffers(base::DoNothing(), gfx::FrameData())); + PresentAndCheckSwapResult(gfx::SwapResult::SWAP_ACK); // Scaling up the swapchain with the same image should cause it to be // transformed again, but not presented again. @@ -598,8 +601,7 @@ class DCompPresenterVideoPixelTest : public DCompPresenterPixelTest { surface_->ScheduleDCLayer(std::move(params)); } - EXPECT_EQ(gfx::SwapResult::SWAP_ACK, - surface_->SwapBuffers(base::DoNothing(), gfx::FrameData())); + PresentAndCheckSwapResult(gfx::SwapResult::SWAP_ACK); Sleep(1000); if (check_color) { @@ -660,8 +662,7 @@ TEST_F(DCompPresenterPixelTest, SoftwareVideoSwapchain) { params->color_space = gfx::ColorSpace::CreateREC709(); surface_->ScheduleDCLayer(std::move(params)); - EXPECT_EQ(gfx::SwapResult::SWAP_ACK, - surface_->SwapBuffers(base::DoNothing(), gfx::FrameData())); + PresentAndCheckSwapResult(gfx::SwapResult::SWAP_ACK); Sleep(1000); SkColor expected_color = SkColorSetRGB(0xff, 0xb7, 0xff); @@ -737,8 +738,7 @@ TEST_F(DCompPresenterPixelTest, SkipVideoLayerEmptyContentsRect) { params->color_space = gfx::ColorSpace::CreateREC709(); surface_->ScheduleDCLayer(std::move(params)); - EXPECT_EQ(gfx::SwapResult::SWAP_ACK, - surface_->SwapBuffers(base::DoNothing(), gfx::FrameData())); + PresentAndCheckSwapResult(gfx::SwapResult::SWAP_ACK); Sleep(1000); @@ -896,8 +896,7 @@ TEST_F(DCompPresenterPixelTest, ResizeVideoLayer) { params->color_space = gfx::ColorSpace::CreateREC709(); surface_->ScheduleDCLayer(std::move(params)); - EXPECT_EQ(gfx::SwapResult::SWAP_ACK, - surface_->SwapBuffers(base::DoNothing(), gfx::FrameData())); + PresentAndCheckSwapResult(gfx::SwapResult::SWAP_ACK); } Microsoft::WRL::ComPtr swap_chain = @@ -919,8 +918,7 @@ TEST_F(DCompPresenterPixelTest, ResizeVideoLayer) { params->color_space = gfx::ColorSpace::CreateREC709(); surface_->ScheduleDCLayer(std::move(params)); - EXPECT_EQ(gfx::SwapResult::SWAP_ACK, - surface_->SwapBuffers(base::DoNothing(), gfx::FrameData())); + PresentAndCheckSwapResult(gfx::SwapResult::SWAP_ACK); } swap_chain = surface_->GetLayerSwapChainForTesting(0); EXPECT_TRUE(SUCCEEDED(swap_chain->GetDesc1(&desc))); @@ -945,8 +943,7 @@ TEST_F(DCompPresenterPixelTest, ResizeVideoLayer) { params->color_space = gfx::ColorSpace::CreateREC709(); surface_->ScheduleDCLayer(std::move(params)); - EXPECT_EQ(gfx::SwapResult::SWAP_ACK, - surface_->SwapBuffers(base::DoNothing(), gfx::FrameData())); + PresentAndCheckSwapResult(gfx::SwapResult::SWAP_ACK); } // Swap chain is set to monitor/onscreen size. @@ -977,8 +974,7 @@ TEST_F(DCompPresenterPixelTest, ResizeVideoLayer) { params->color_space = gfx::ColorSpace::CreateREC709(); surface_->ScheduleDCLayer(std::move(params)); - EXPECT_EQ(gfx::SwapResult::SWAP_ACK, - surface_->SwapBuffers(base::DoNothing(), gfx::FrameData())); + PresentAndCheckSwapResult(gfx::SwapResult::SWAP_ACK); } // Swap chain is set to monitor size (100, 100). @@ -1077,10 +1073,10 @@ TEST_F(DCompPresenterPixelTest, SwapChainImage) { dc_layer_params->content_rect = gfx::Rect(swap_chain_size); dc_layer_params->quad_rect = gfx::Rect(window_size); dc_layer_params->color_space = gfx::ColorSpace::CreateSRGB(); + dc_layer_params->z_order = 1; surface_->ScheduleDCLayer(std::move(dc_layer_params)); - EXPECT_EQ(gfx::SwapResult::SWAP_ACK, - surface_->SwapBuffers(base::DoNothing(), gfx::FrameData())); + PresentAndCheckSwapResult(gfx::SwapResult::SWAP_ACK); SkColor expected_color = SK_ColorRED; SkColor actual_color = @@ -1105,8 +1101,7 @@ TEST_F(DCompPresenterPixelTest, SwapChainImage) { dc_layer_params->color_space = gfx::ColorSpace::CreateSRGB(); surface_->ScheduleDCLayer(std::move(dc_layer_params)); - EXPECT_EQ(gfx::SwapResult::SWAP_ACK, - surface_->SwapBuffers(base::DoNothing(), gfx::FrameData())); + PresentAndCheckSwapResult(gfx::SwapResult::SWAP_ACK); SkColor expected_color = SK_ColorGREEN; SkColor actual_color = @@ -1129,8 +1124,7 @@ TEST_F(DCompPresenterPixelTest, SwapChainImage) { dc_layer_params->color_space = gfx::ColorSpace::CreateSRGB(); surface_->ScheduleDCLayer(std::move(dc_layer_params)); - EXPECT_EQ(gfx::SwapResult::SWAP_ACK, - surface_->SwapBuffers(base::DoNothing(), gfx::FrameData())); + PresentAndCheckSwapResult(gfx::SwapResult::SWAP_ACK); SkColor expected_color = SK_ColorRED; SkColor actual_color = @@ -1153,8 +1147,7 @@ TEST_F(DCompPresenterPixelTest, SwapChainImage) { dc_layer_params->color_space = gfx::ColorSpace::CreateSRGB(); surface_->ScheduleDCLayer(std::move(dc_layer_params)); - EXPECT_EQ(gfx::SwapResult::SWAP_ACK, - surface_->SwapBuffers(base::DoNothing(), gfx::FrameData())); + PresentAndCheckSwapResult(gfx::SwapResult::SWAP_ACK); SkColor expected_color = SK_ColorRED; SkColor actual_color = @@ -1215,8 +1208,7 @@ TEST_P(DCompPresenterBufferCountTest, VideoSwapChainBufferCount) { params->color_space = gfx::ColorSpace::CreateREC709(); EXPECT_TRUE(surface_->ScheduleDCLayer(std::move(params))); - EXPECT_EQ(gfx::SwapResult::SWAP_ACK, - surface_->SwapBuffers(base::DoNothing(), gfx::FrameData())); + PresentAndCheckSwapResult(gfx::SwapResult::SWAP_ACK); auto swap_chain = surface_->GetLayerSwapChainForTesting(0); ASSERT_TRUE(swap_chain);