| // Copyright 2017 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/gles2_command_buffer_stub.h" |
| |
| #include <memory> |
| #include <utility> |
| |
| #include "base/functional/bind.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/hash/hash.h" |
| #include "base/json/json_writer.h" |
| #include "base/memory/memory_pressure_listener.h" |
| #include "base/memory/unsafe_shared_memory_region.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/task/single_thread_task_runner.h" |
| #include "base/time/time.h" |
| #include "base/trace_event/trace_event.h" |
| #include "build/build_config.h" |
| #include "gpu/command_buffer/common/constants.h" |
| #include "gpu/command_buffer/common/gpu_memory_buffer_support.h" |
| #include "gpu/command_buffer/common/mailbox.h" |
| #include "gpu/command_buffer/common/presentation_feedback_utils.h" |
| #include "gpu/command_buffer/common/swap_buffers_flags.h" |
| #include "gpu/command_buffer/service/gl_context_virtual.h" |
| #include "gpu/command_buffer/service/gl_state_restorer_impl.h" |
| #include "gpu/command_buffer/service/gpu_fence_manager.h" |
| #include "gpu/command_buffer/service/logger.h" |
| #include "gpu/command_buffer/service/mailbox_manager.h" |
| #include "gpu/command_buffer/service/memory_tracking.h" |
| #include "gpu/command_buffer/service/scheduler.h" |
| #include "gpu/command_buffer/service/service_utils.h" |
| #include "gpu/command_buffer/service/sync_point_manager.h" |
| #include "gpu/command_buffer/service/transfer_buffer_manager.h" |
| #include "gpu/config/gpu_crash_keys.h" |
| #include "gpu/ipc/service/gpu_channel.h" |
| #include "gpu/ipc/service/gpu_channel_manager.h" |
| #include "gpu/ipc/service/gpu_channel_manager_delegate.h" |
| #include "gpu/ipc/service/gpu_memory_buffer_factory.h" |
| #include "gpu/ipc/service/gpu_watchdog_thread.h" |
| #include "gpu/ipc/service/image_transport_surface.h" |
| #include "ui/gfx/buffer_format_util.h" |
| #include "ui/gfx/gpu_fence.h" |
| #include "ui/gfx/gpu_fence_handle.h" |
| #include "ui/gfx/switches.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_egl.h" |
| #include "ui/gl/gl_switches.h" |
| #include "ui/gl/gl_utils.h" |
| #include "ui/gl/init/gl_factory.h" |
| |
| #if BUILDFLAG(IS_WIN) |
| #include "base/win/win_util.h" |
| #endif |
| |
| namespace gpu { |
| |
| GLES2CommandBufferStub::GLES2CommandBufferStub( |
| GpuChannel* channel, |
| const mojom::CreateCommandBufferParams& init_params, |
| CommandBufferId command_buffer_id, |
| SequenceId sequence_id, |
| int32_t stream_id, |
| int32_t route_id) |
| : CommandBufferStub(channel, |
| init_params, |
| command_buffer_id, |
| sequence_id, |
| stream_id, |
| route_id), |
| gles2_decoder_(nullptr) {} |
| |
| GLES2CommandBufferStub::~GLES2CommandBufferStub() = default; |
| |
| gpu::ContextResult GLES2CommandBufferStub::Initialize( |
| CommandBufferStub* share_command_buffer_stub, |
| const mojom::CreateCommandBufferParams& init_params, |
| base::UnsafeSharedMemoryRegion shared_state_shm) { |
| TRACE_EVENT0("gpu", "GLES2CommandBufferStub::Initialize"); |
| UpdateActiveUrl(); |
| |
| GpuChannelManager* manager = channel_->gpu_channel_manager(); |
| DCHECK(manager); |
| memory_tracker_ = CreateMemoryTracker(); |
| |
| if (share_command_buffer_stub) { |
| context_group_ = |
| share_command_buffer_stub->decoder_context()->GetContextGroup(); |
| if (!context_group_) { |
| LOG(ERROR) << "ContextResult::kFatalFailure: attempt to create a GLES2 " |
| "context sharing with a non-GLES2 context"; |
| return gpu::ContextResult::kFatalFailure; |
| } |
| if (context_group_->bind_generates_resource() != |
| init_params.attribs.bind_generates_resource) { |
| LOG(ERROR) << "ContextResult::kFatalFailure: attempt to create a shared " |
| "GLES2 context with inconsistent bind_generates_resource"; |
| return gpu::ContextResult::kFatalFailure; |
| } |
| } else { |
| scoped_refptr<gles2::FeatureInfo> feature_info = new gles2::FeatureInfo( |
| manager->gpu_driver_bug_workarounds(), manager->gpu_feature_info()); |
| context_group_ = new gles2::ContextGroup( |
| manager->gpu_preferences(), gles2::PassthroughCommandDecoderSupported(), |
| manager->mailbox_manager(), CreateMemoryTracker(), |
| manager->shader_translator_cache(), |
| manager->framebuffer_completeness_cache(), feature_info, |
| init_params.attribs.bind_generates_resource, |
| manager->watchdog() /* progress_reporter */, |
| manager->gpu_feature_info(), manager->discardable_manager(), |
| manager->passthrough_discardable_manager(), |
| manager->shared_image_manager()); |
| } |
| |
| #if BUILDFLAG(IS_MAC) |
| // Virtualize GpuPreference::kLowPower contexts by default on OS X to prevent |
| // performance regressions when enabling FCM. |
| // http://crbug.com/180463 |
| if (init_params.attribs.gpu_preference == gl::GpuPreference::kLowPower) |
| use_virtualized_gl_context_ = true; |
| #endif |
| |
| use_virtualized_gl_context_ |= |
| context_group_->feature_info()->workarounds().use_virtualized_gl_contexts; |
| |
| command_buffer_ = std::make_unique<CommandBufferService>( |
| this, context_group_->memory_tracker()); |
| gles2_decoder_ = gles2::GLES2Decoder::Create( |
| this, command_buffer_.get(), manager->outputter(), context_group_.get()); |
| set_decoder_context(std::unique_ptr<DecoderContext>(gles2_decoder_)); |
| |
| sync_point_client_state_ = |
| channel_->sync_point_manager()->CreateSyncPointClientState( |
| CommandBufferNamespace::GPU_IO, command_buffer_id_, sequence_id_); |
| |
| // TODO(crbug.com/1251724): Remove this after testing. |
| // Only enable multiple displays on ANGLE/Metal and only behind a feature. |
| bool force_default_display = true; |
| if (gl::GetGLImplementation() == gl::kGLImplementationEGLANGLE && |
| gl::GetANGLEImplementation() == gl::ANGLEImplementation::kMetal && |
| features::SupportsEGLDualGPURendering()) { |
| force_default_display = false; |
| } |
| gl::GpuPreference gpu_preference = init_params.attribs.gpu_preference; |
| // If the user queries a low-power context, it's better to use whatever the |
| // default GPU used by Chrome is, which may be different than the low-power |
| // GPU determined by GLDisplayManager. |
| if (gpu_preference == gl::GpuPreference::kLowPower || |
| gpu_preference == gl::GpuPreference::kNone || force_default_display) { |
| gpu_preference = gl::GpuPreference::kDefault; |
| } |
| |
| // Query and initialize the default display for this GPU preference, |
| // ignoring any queried display key for now. For simplicity we need |
| // to initialize the default display per-GPU first. |
| // We may be requesting a new GPU/display, so get or initialize the display. |
| gl::GLDisplay* display = |
| gl::init::GetOrInitializeGLOneOffPlatformImplementation( |
| /*fallback_to_software_gl=*/false, /*disable_gl_drawing=*/false, |
| /*init_extensions=*/true, |
| /*gpu_preference=*/gpu_preference); |
| |
| // If the user queries a key to create a distinct display on this GPU, |
| // check if this display already exists, and if not, initialize it from |
| // the default display on this GPU. |
| gl::DisplayKey display_key = gl::DisplayKey::kDefault; |
| if (manager->gpu_preferences().force_separate_egl_display_for_webgl_testing && |
| features::SupportsEGLDualGPURendering()) { |
| display_key = gl::DisplayKey::kSeparateEGLDisplayForWebGLTesting; |
| } |
| |
| if (display_key != gl::DisplayKey::kDefault) { |
| gl::GLDisplay* keyed_display = gl::GetDisplay(gpu_preference, display_key); |
| if (!keyed_display->IsInitialized()) { |
| keyed_display->Initialize(display); |
| } |
| display = keyed_display; |
| } |
| |
| gl::GLSurface* default_surface = manager->default_offscreen_surface(); |
| |
| #if BUILDFLAG(IS_ANDROID) |
| const bool offscreen = init_params.surface_handle == kNullSurfaceHandle; |
| #else |
| constexpr bool offscreen = true; |
| #endif |
| |
| #if BUILDFLAG(IS_ANDROID) |
| if (!offscreen) { |
| // To use virtualized contexts we need on screen surface format match the |
| // offscreen. |
| auto surface_format = default_surface->GetFormat(); |
| surface_ = ImageTransportSurface::CreateNativeGLSurface( |
| display, weak_ptr_factory_.GetWeakPtr(), init_params.surface_handle, |
| surface_format); |
| if (!surface_ || !surface_->Initialize(surface_format)) { |
| surface_ = nullptr; |
| LOG(ERROR) << "ContextResult::kSurfaceFailure: Failed to create surface."; |
| return gpu::ContextResult::kSurfaceFailure; |
| } |
| if (!features::UseGpuVsync()) { |
| surface_->SetVSyncEnabled(false); |
| } |
| } else |
| #endif |
| { |
| if (default_surface->GetGLDisplay() == display) { |
| surface_ = default_surface; |
| } else { |
| // The default surface was created on a different display, create a |
| // new surface on the requested display. |
| surface_ = gl::init::CreateOffscreenGLSurface(display, gfx::Size()); |
| } |
| } |
| |
| if (context_group_->use_passthrough_cmd_decoder()) { |
| // Virtualized contexts don't work with passthrough command decoder. |
| // See https://crbug.com/914976 |
| use_virtualized_gl_context_ = false; |
| // When using the passthrough command decoder, only share with other |
| // contexts in the explicitly requested share group |
| if (share_command_buffer_stub) { |
| share_group_ = share_command_buffer_stub->share_group(); |
| } else { |
| share_group_ = base::MakeRefCounted<gl::GLShareGroup>(); |
| } |
| } else { |
| // When using the validating command decoder, always use the global share |
| // group |
| share_group_ = channel_->share_group(); |
| } |
| |
| // TODO(sunnyps): Should this use ScopedCrashKey instead? |
| crash_keys::gpu_gl_context_is_virtual.Set(use_virtualized_gl_context_ ? "1" |
| : "0"); |
| |
| scoped_refptr<gl::GLContext> context; |
| if (use_virtualized_gl_context_ && share_group_) { |
| context = share_group_->shared_context(); |
| if (context && (!context->MakeCurrent(surface_.get()) || |
| context->CheckStickyGraphicsResetStatus() != GL_NO_ERROR)) { |
| context = nullptr; |
| } |
| if (!context) { |
| context = gl::init::CreateGLContext( |
| share_group_.get(), surface_.get(), |
| GenerateGLContextAttribsForDecoder(init_params.attribs, |
| context_group_.get())); |
| if (!context) { |
| // TODO(piman): This might not be fatal, we could recurse into |
| // CreateGLContext to get more info, tho it should be exceedingly |
| // rare and may not be recoverable anyway. |
| LOG(ERROR) << "ContextResult::kFatalFailure: " |
| "Failed to create shared context for virtualization."; |
| return gpu::ContextResult::kFatalFailure; |
| } |
| // Ensure that context creation did not lose track of the intended share |
| // group. |
| DCHECK(context->share_group() == share_group_.get()); |
| share_group_->SetSharedContext(context.get()); |
| |
| // This needs to be called against the real shared context, not the |
| // virtual context created below. |
| manager->gpu_feature_info().ApplyToGLContext(context.get()); |
| } |
| // This should be either: |
| // (1) a non-virtual GL context, or |
| // (2) a mock/stub context. |
| DCHECK(context->GetHandle() || |
| gl::GetGLImplementation() == gl::kGLImplementationMockGL || |
| gl::GetGLImplementation() == gl::kGLImplementationStubGL); |
| context = base::MakeRefCounted<GLContextVirtual>( |
| share_group_.get(), context.get(), gles2_decoder_->AsWeakPtr()); |
| if (!context->Initialize(surface_.get(), |
| GenerateGLContextAttribsForDecoder( |
| init_params.attribs, context_group_.get()))) { |
| // The real context created above for the default offscreen surface |
| // might not be compatible with this surface. |
| context = nullptr; |
| // TODO(piman): This might not be fatal, we could recurse into |
| // CreateGLContext to get more info, tho it should be exceedingly |
| // rare and may not be recoverable anyway. |
| LOG(ERROR) << "ContextResult::kFatalFailure: " |
| "Failed to initialize virtual GL context."; |
| return gpu::ContextResult::kFatalFailure; |
| } |
| } else { |
| context = gl::init::CreateGLContext( |
| share_group_.get(), surface_.get(), |
| GenerateGLContextAttribsForDecoder(init_params.attribs, |
| context_group_.get())); |
| if (!context) { |
| // TODO(piman): This might not be fatal, we could recurse into |
| // CreateGLContext to get more info, tho it should be exceedingly |
| // rare and may not be recoverable anyway. |
| LOG(ERROR) << "ContextResult::kFatalFailure: Failed to create context."; |
| return gpu::ContextResult::kFatalFailure; |
| } |
| |
| manager->gpu_feature_info().ApplyToGLContext(context.get()); |
| } |
| |
| if (!context->MakeCurrent(surface_.get())) { |
| LOG(ERROR) << "ContextResult::kTransientFailure: " |
| "Failed to make context current."; |
| return gpu::ContextResult::kTransientFailure; |
| } |
| |
| // The GLStateRestorer is not used with the passthrough command decoder |
| // because not all state is tracked in the decoder. Virtualized contexts are |
| // also not used. |
| if (!context->GetGLStateRestorer() && |
| !context_group_->use_passthrough_cmd_decoder()) { |
| context->SetGLStateRestorer( |
| new GLStateRestorerImpl(gles2_decoder_->AsWeakPtr())); |
| } |
| |
| if (!context_group_->has_program_cache() && |
| !context_group_->feature_info()->workarounds().disable_program_cache) { |
| context_group_->set_program_cache(manager->program_cache()); |
| } |
| |
| // Initialize the decoder with either the view or pbuffer GLContext. |
| auto result = gles2_decoder_->Initialize(surface_, context, offscreen, |
| gpu::gles2::DisallowedFeatures(), |
| init_params.attribs); |
| if (result != gpu::ContextResult::kSuccess) { |
| DLOG(ERROR) << "Failed to initialize decoder."; |
| return result; |
| } |
| |
| if (manager->gpu_preferences().enable_gpu_service_logging) { |
| gles2_decoder_->SetLogCommands(true); |
| } |
| |
| const size_t kSharedStateSize = sizeof(CommandBufferSharedState); |
| base::WritableSharedMemoryMapping shared_state_mapping = |
| shared_state_shm.MapAt(0, kSharedStateSize); |
| if (!shared_state_mapping.IsValid()) { |
| LOG(ERROR) << "ContextResult::kFatalFailure: " |
| "Failed to map shared state buffer."; |
| return gpu::ContextResult::kFatalFailure; |
| } |
| command_buffer_->SetSharedStateBuffer(MakeBackingFromSharedMemory( |
| std::move(shared_state_shm), std::move(shared_state_mapping))); |
| |
| if (offscreen && !active_url_.is_empty()) |
| manager->delegate()->DidCreateOffscreenContext(active_url_.url()); |
| |
| if (use_virtualized_gl_context_) { |
| // If virtualized GL contexts are in use, then real GL context state |
| // is in an indeterminate state, since the GLStateRestorer was not |
| // initialized at the time the GLContextVirtual was made current. In |
| // the case that this command decoder is the next one to be |
| // processed, force a "full virtual" MakeCurrent to be performed. |
| // Note that GpuChannel's initialization of the gpu::Capabilities |
| // expects the context to be left current. |
| context->ForceReleaseVirtuallyCurrent(); |
| if (!context->MakeCurrent(surface_.get())) { |
| LOG(ERROR) << "ContextResult::kTransientFailure: " |
| "Failed to make context current after initialization."; |
| return gpu::ContextResult::kTransientFailure; |
| } |
| } |
| |
| if (IsWebGLContextType(init_params.attribs.context_type)) { |
| gl::GLDisplayEGL* display_egl = display->GetAs<gl::GLDisplayEGL>(); |
| if (display_egl) { |
| UMA_HISTOGRAM_ENUMERATION("GPU.WebGLDisplayType", |
| display_egl->GetDisplayType(), |
| gl::DISPLAY_TYPE_MAX); |
| |
| constexpr uint64_t kLargeCanvasNumPixels = 128 * 128; |
| uint64_t surface_area = surface_->GetSize().Area64(); |
| if (surface_area >= kLargeCanvasNumPixels) { |
| UMA_HISTOGRAM_ENUMERATION("GPU.WebGLDisplayTypeLarge", |
| display_egl->GetDisplayType(), |
| gl::DISPLAY_TYPE_MAX); |
| } |
| } |
| } |
| |
| manager->delegate()->DidCreateContextSuccessfully(); |
| initialized_ = true; |
| return gpu::ContextResult::kSuccess; |
| } |
| |
| #if BUILDFLAG(IS_WIN) |
| void GLES2CommandBufferStub::AddChildWindowToBrowser( |
| gpu::SurfaceHandle child_window) { |
| NOTREACHED(); |
| } |
| #endif |
| |
| const gles2::FeatureInfo* GLES2CommandBufferStub::GetFeatureInfo() const { |
| return context_group_->feature_info(); |
| } |
| |
| const GpuPreferences& GLES2CommandBufferStub::GetGpuPreferences() const { |
| return context_group_->gpu_preferences(); |
| } |
| |
| MemoryTracker* GLES2CommandBufferStub::GetContextGroupMemoryTracker() const { |
| return context_group_->memory_tracker(); |
| } |
| |
| void GLES2CommandBufferStub::OnGpuSwitched( |
| gl::GpuPreference active_gpu_heuristic) { |
| client().OnGpuSwitched(active_gpu_heuristic); |
| } |
| |
| void GLES2CommandBufferStub::OnSetDefaultFramebufferSharedImage( |
| const Mailbox& mailbox, |
| int samples_count, |
| bool preserve, |
| bool needs_depth, |
| bool needs_stencil) { |
| gles2_decoder_->SetDefaultFramebufferSharedImage( |
| mailbox, samples_count, preserve, needs_depth, needs_stencil); |
| } |
| |
| void GLES2CommandBufferStub::CreateGpuFenceFromHandle( |
| uint32_t gpu_fence_id, |
| gfx::GpuFenceHandle handle) { |
| ScopedContextOperation operation(*this); |
| if (!operation.is_context_current()) |
| return; |
| |
| if (!context_group_->feature_info()->feature_flags().chromium_gpu_fence) { |
| DLOG(ERROR) << "CHROMIUM_gpu_fence unavailable"; |
| command_buffer_->SetParseError(error::kLostContext); |
| return; |
| } |
| |
| if (gles2_decoder_->GetGpuFenceManager()->CreateGpuFenceFromHandle( |
| gpu_fence_id, std::move(handle))) |
| return; |
| |
| // The insertion failed. This shouldn't happen, force context loss to avoid |
| // inconsistent state. |
| command_buffer_->SetParseError(error::kLostContext); |
| CheckContextLost(); |
| } |
| |
| void GLES2CommandBufferStub::GetGpuFenceHandle( |
| uint32_t gpu_fence_id, |
| GetGpuFenceHandleCallback callback) { |
| ScopedContextOperation operation(*this); |
| if (!operation.is_context_current()) |
| return; |
| |
| if (!context_group_->feature_info()->feature_flags().chromium_gpu_fence) { |
| DLOG(ERROR) << "CHROMIUM_gpu_fence unavailable"; |
| command_buffer_->SetParseError(error::kLostContext); |
| return; |
| } |
| |
| auto* manager = gles2_decoder_->GetGpuFenceManager(); |
| gfx::GpuFenceHandle handle; |
| if (manager->IsValidGpuFence(gpu_fence_id)) { |
| std::unique_ptr<gfx::GpuFence> gpu_fence = |
| manager->GetGpuFence(gpu_fence_id); |
| handle = gpu_fence->GetGpuFenceHandle().Clone(); |
| } else { |
| // Retrieval failed. This shouldn't happen, force context loss to avoid |
| // inconsistent state. |
| DLOG(ERROR) << "GpuFence not found"; |
| command_buffer_->SetParseError(error::kLostContext); |
| CheckContextLost(); |
| } |
| |
| std::move(callback).Run(std::move(handle)); |
| } |
| |
| void GLES2CommandBufferStub::OnSwapBuffers(uint64_t swap_id, uint32_t flags) {} |
| |
| } // namespace gpu |