| // Copyright 2019 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_gl.h" |
| |
| #include <tuple> |
| #include <utility> |
| |
| #include "base/debug/alias.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "base/notreached.h" |
| #include "base/time/time.h" |
| #include "build/build_config.h" |
| #include "gpu/command_buffer/service/feature_info.h" |
| #include "gpu/command_buffer/service/shared_context_state.h" |
| #include "gpu/command_buffer/service/texture_manager.h" |
| #include "skia/ext/legacy_display_globals.h" |
| #include "third_party/skia/include/core/SkColorType.h" |
| #include "third_party/skia/include/core/SkSurface.h" |
| #include "third_party/skia/include/core/SkSurfaceProps.h" |
| #include "third_party/skia/include/gpu/ganesh/GrBackendSurface.h" |
| #include "third_party/skia/include/gpu/ganesh/GrDirectContext.h" |
| #include "third_party/skia/include/gpu/ganesh/SkSurfaceGanesh.h" |
| #include "third_party/skia/include/gpu/ganesh/gl/GrGLBackendSurface.h" |
| #include "third_party/skia/include/gpu/ganesh/gl/GrGLTypes.h" |
| #include "ui/gl/gl_bindings.h" |
| #include "ui/gl/gl_context.h" |
| #include "ui/gl/gl_features.h" |
| #include "ui/gl/gl_implementation.h" |
| #include "ui/gl/gl_surface.h" |
| |
| namespace viz { |
| |
| namespace { |
| base::TimeTicks g_last_reshape_failure = base::TimeTicks(); |
| |
| NOINLINE void CheckForLoopFailures() { |
| const auto threshold = base::Seconds(1); |
| auto now = base::TimeTicks::Now(); |
| if (!g_last_reshape_failure.is_null() && |
| now - g_last_reshape_failure < threshold) { |
| NOTREACHED(); |
| } |
| g_last_reshape_failure = now; |
| } |
| |
| } // namespace |
| |
| class SkiaOutputDeviceGL::MultiSurfaceSwapBuffersTracker { |
| public: |
| // Returns true if multiple surface swaps detected. |
| bool TrackSwapBuffers() { |
| static int next_global_swap_generation = 0; |
| static int num_swaps_in_current_swap_generation = 0; |
| static int last_multi_window_swap_generation = 0; |
| |
| // This code is a simple way of enforcing that we only vsync if one surface |
| // is swapping per frame. This provides single window cases a stable refresh |
| // while allowing multi-window cases to not slow down due to multiple syncs |
| // on a single thread. A better way to fix this problem would be to have |
| // each surface present on its own thread. |
| |
| // If next global swap generation equals to our surface's next swap |
| // generation means we start new swap generation and this is first surface |
| // to swap. |
| if (next_global_swap_generation == next_surface_swap_generation_) { |
| // Start new generation. |
| next_global_swap_generation++; |
| |
| // Store number of swaps in the previous generation. |
| if (num_swaps_in_current_swap_generation > 1) { |
| last_multi_window_swap_generation = next_global_swap_generation; |
| } |
| num_swaps_in_current_swap_generation = 0; |
| } |
| |
| next_surface_swap_generation_ = next_global_swap_generation; |
| num_swaps_in_current_swap_generation++; |
| |
| // Number of swap generations before vsync is re-enabled after we've stopped |
| // doing multiple swaps per frame. |
| constexpr int kMultiWindowSwapEnableVSyncDelay = 60; |
| |
| return (num_swaps_in_current_swap_generation > 1) || |
| (next_global_swap_generation - last_multi_window_swap_generation < |
| kMultiWindowSwapEnableVSyncDelay); |
| } |
| |
| private: |
| int next_surface_swap_generation_ = 0; |
| }; |
| |
| SkiaOutputDeviceGL::SkiaOutputDeviceGL( |
| gpu::SharedContextState* context_state, |
| scoped_refptr<gl::GLSurface> gl_surface, |
| scoped_refptr<gpu::gles2::FeatureInfo> feature_info, |
| scoped_refptr<gpu::MemoryTracker> memory_tracker, |
| DidSwapBufferCompleteCallback did_swap_buffer_complete_callback) |
| : SkiaOutputDevice(context_state->gr_context(), |
| context_state->graphite_shared_context(), |
| std::move(memory_tracker), |
| std::move(did_swap_buffer_complete_callback)), |
| context_state_(context_state), |
| gl_surface_(std::move(gl_surface)), |
| supports_async_swap_(gl_surface_->SupportsAsyncSwap()) { |
| capabilities_.uses_default_gl_framebuffer = true; |
| capabilities_.output_surface_origin = gl_surface_->GetOrigin(); |
| capabilities_.supports_post_sub_buffer = gl_surface_->SupportsPostSubBuffer(); |
| if (feature_info->workarounds() |
| .disable_post_sub_buffers_for_onscreen_surfaces) { |
| capabilities_.supports_post_sub_buffer = false; |
| } |
| capabilities_.pending_swap_params.max_pending_swaps = |
| gl_surface_->GetBufferCount() - 1; |
| #if BUILDFLAG(IS_ANDROID) |
| // TODO(weiliangc): This capability is used to check whether we should do |
| // overlay. Since currently none of the other overlay system is implemented, |
| // only update this for Android. |
| // This output device is never offscreen. |
| capabilities_.supports_surfaceless = gl_surface_->IsSurfaceless(); |
| #endif |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| // If Chrome OS is run on Linux for development purposes, we need to |
| // advertise a hardware orientation mode since Ash manages a separate device |
| // rotation independent of the host's native windowing system. |
| capabilities_.orientation_mode = OutputSurface::OrientationMode::kHardware; |
| #endif // IS_CHROMEOS |
| |
| DCHECK(context_state_); |
| DCHECK(gl_surface_); |
| |
| DCHECK(context_state_->gr_context()); |
| DCHECK(context_state_->context()); |
| |
| GrDirectContext* gr_context = context_state_->gr_context(); |
| |
| // Get alpha bits from the default frame buffer. |
| int alpha_bits = 0; |
| glBindFramebufferEXT(GL_FRAMEBUFFER, 0); |
| gr_context->resetContext(kRenderTarget_GrGLBackendState); |
| glGetIntegerv(GL_ALPHA_BITS, &alpha_bits); |
| CHECK_GL_ERROR(); |
| |
| auto color_type = kRGBA_8888_SkColorType; |
| |
| if (alpha_bits == 0) { |
| color_type = gl_surface_->GetFormat().IsRGB565() ? kRGB_565_SkColorType |
| : kRGB_888x_SkColorType; |
| // Skia disables RGBx on some GPUs, fallback to RGBA if it's the |
| // case. This doesn't change framebuffer itself, as we already allocated it, |
| // but will change any temporary buffer Skia needs to allocate. |
| if (!context_state_->gr_context() |
| ->defaultBackendFormat(color_type, GrRenderable::kYes) |
| .isValid()) { |
| color_type = kRGBA_8888_SkColorType; |
| } |
| } |
| // SRGB |
| capabilities_.sk_color_type_map[SinglePlaneFormat::kRGBA_8888] = color_type; |
| capabilities_.sk_color_type_map[SinglePlaneFormat::kRGBX_8888] = color_type; |
| capabilities_.sk_color_type_map[SinglePlaneFormat::kBGRA_8888] = color_type; |
| capabilities_.sk_color_type_map[SinglePlaneFormat::kBGRX_8888] = color_type; |
| // HDR10 |
| capabilities_.sk_color_type_map[SinglePlaneFormat::kRGBA_1010102] = |
| kRGBA_1010102_SkColorType; |
| // scRGB linear |
| capabilities_.sk_color_type_map[SinglePlaneFormat::kRGBA_F16] = |
| kRGBA_F16_SkColorType; |
| |
| if (features::UseGpuVsync()) { |
| // Historically we never disabled vsync on Android and it's very rare |
| // use-case to have multiple active windows there. On other platforms we |
| // disable GLSurface's VSync if we're swapping multiple surfaces per frame |
| // to prevent SwapBuffers from blocking and slowing down other windows. |
| #if !BUILDFLAG(IS_ANDROID) |
| multisurface_swapbuffers_tracker_ = |
| std::make_unique<MultiSurfaceSwapBuffersTracker>(); |
| #endif |
| } else { |
| gl_surface_->SetVSyncEnabled(false); |
| } |
| } |
| |
| SkiaOutputDeviceGL::~SkiaOutputDeviceGL() { |
| // gl_surface_ will be destructed soon. |
| memory_type_tracker_->TrackMemFree(backbuffer_estimated_size_); |
| } |
| |
| bool SkiaOutputDeviceGL::Reshape(const ReshapeParams& params) { |
| #if !BUILDFLAG(IS_CHROMEOS) |
| DCHECK_EQ(params.transform, gfx::OVERLAY_TRANSFORM_NONE); |
| #endif // !BUILDFLAG(IS_CHROMEOS) |
| |
| const gfx::Size size = params.GfxSize(); |
| const SkColorType color_type = params.image_info.colorType(); |
| const bool has_alpha = !params.image_info.isOpaque(); |
| |
| if (!gl_surface_->Resize(size, params.device_scale_factor, params.color_space, |
| has_alpha)) { |
| CheckForLoopFailures(); |
| // To prevent tail call, so we can see the stack. |
| base::debug::Alias(nullptr); |
| return false; |
| } |
| SkSurfaceProps surface_props; |
| |
| GrGLFramebufferInfo framebuffer_info = {0}; |
| DCHECK_EQ(gl_surface_->GetBackingFramebufferObject(), 0u); |
| |
| auto* gr_context = context_state_->gr_context(); |
| |
| GrBackendFormat backend_format = |
| gr_context->defaultBackendFormat(color_type, GrRenderable::kYes); |
| DCHECK(backend_format.isValid()) << "color_type: " << color_type; |
| framebuffer_info.fFormat = GrBackendFormats::AsGLFormatEnum(backend_format); |
| |
| auto render_target = GrBackendRenderTargets::MakeGL( |
| size.width(), size.height(), params.sample_count, |
| /*stencilBits=*/0, framebuffer_info); |
| auto origin = (gl_surface_->GetOrigin() == gfx::SurfaceOrigin::kTopLeft) |
| ? kTopLeft_GrSurfaceOrigin |
| : kBottomLeft_GrSurfaceOrigin; |
| sk_surface_ = SkSurfaces::WrapBackendRenderTarget( |
| gr_context, render_target, origin, color_type, |
| params.image_info.refColorSpace(), &surface_props); |
| if (!sk_surface_) { |
| LOG(ERROR) << "Couldn't create surface:" << "\n abandoned()=" |
| << gr_context->abandoned() << "\n color_type=" << color_type |
| << "\n framebuffer_info.fFBOID=" << framebuffer_info.fFBOID |
| << "\n framebuffer_info.fFormat=" << framebuffer_info.fFormat |
| << "\n color_space=" << params.color_space.ToString() |
| << "\n size=" << size.ToString(); |
| CheckForLoopFailures(); |
| // To prevent tail call, so we can see the stack. |
| base::debug::Alias(nullptr); |
| } |
| |
| memory_type_tracker_->TrackMemFree(backbuffer_estimated_size_); |
| GLenum format = gpu::gles2::TextureManager::ExtractFormatFromStorageFormat( |
| framebuffer_info.fFormat); |
| GLenum type = gpu::gles2::TextureManager::ExtractTypeFromStorageFormat( |
| framebuffer_info.fFormat); |
| uint32_t estimated_size; |
| gpu::gles2::GLES2Util::ComputeImageDataSizes( |
| size.width(), size.height(), 1 /* depth */, format, type, |
| 4 /* alignment */, &estimated_size, nullptr, nullptr); |
| backbuffer_estimated_size_ = estimated_size * gl_surface_->GetBufferCount(); |
| memory_type_tracker_->TrackMemAlloc(backbuffer_estimated_size_); |
| |
| return !!sk_surface_; |
| } |
| |
| void SkiaOutputDeviceGL::Present(const std::optional<gfx::Rect>& update_rect, |
| BufferPresentedCallback feedback, |
| OutputSurfaceFrame frame) { |
| if (multisurface_swapbuffers_tracker_) { |
| const bool multiple_surface_swaps = |
| multisurface_swapbuffers_tracker_->TrackSwapBuffers(); |
| gl_surface_->SetVSyncEnabled(!multiple_surface_swaps); |
| } |
| |
| StartSwapBuffers({}); |
| |
| gfx::Size surface_size = |
| gfx::Size(sk_surface_->width(), sk_surface_->height()); |
| |
| auto data = frame.data; |
| if (supports_async_swap_) { |
| auto callback = base::BindOnce( |
| &SkiaOutputDeviceGL::DoFinishSwapBuffersAsync, |
| weak_ptr_factory_.GetWeakPtr(), surface_size, std::move(frame)); |
| |
| if (update_rect) { |
| gl_surface_->PostSubBufferAsync( |
| update_rect->x(), update_rect->y(), update_rect->width(), |
| update_rect->height(), std::move(callback), std::move(feedback), |
| std::move(data)); |
| } else { |
| gl_surface_->SwapBuffersAsync(std::move(callback), std::move(feedback), |
| std::move(data)); |
| } |
| } else { |
| gfx::SwapResult result; |
| if (update_rect) { |
| result = gl_surface_->PostSubBuffer( |
| update_rect->x(), update_rect->y(), update_rect->width(), |
| update_rect->height(), std::move(feedback), std::move(data)); |
| } else { |
| result = gl_surface_->SwapBuffers(std::move(feedback), std::move(data)); |
| } |
| DoFinishSwapBuffers(surface_size, std::move(frame), |
| gfx::SwapCompletionResult(result)); |
| } |
| } |
| |
| void SkiaOutputDeviceGL::DoFinishSwapBuffersAsync( |
| const gfx::Size& size, |
| OutputSurfaceFrame frame, |
| gfx::SwapCompletionResult result) { |
| DCHECK(result.release_fence.is_null()); |
| FinishSwapBuffers(std::move(result), size, std::move(frame)); |
| } |
| |
| void SkiaOutputDeviceGL::DoFinishSwapBuffers(const gfx::Size& size, |
| OutputSurfaceFrame frame, |
| gfx::SwapCompletionResult result) { |
| DCHECK(result.release_fence.is_null()); |
| FinishSwapBuffers(std::move(result), size, std::move(frame)); |
| } |
| |
| SkSurface* SkiaOutputDeviceGL::BeginPaint( |
| std::vector<GrBackendSemaphore>* end_semaphores) { |
| DCHECK(sk_surface_); |
| return sk_surface_.get(); |
| } |
| |
| void SkiaOutputDeviceGL::EndPaint() {} |
| |
| } // namespace viz |