| // Copyright 2016 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/command_buffer/service/gles2_cmd_decoder_passthrough.h" | |
| #include <algorithm> | |
| #include <memory> | |
| #include <string> | |
| #include <string_view> | |
| #include <utility> | |
| #include "base/command_line.h" | |
| #include "base/compiler_specific.h" | |
| #include "base/containers/span.h" | |
| #include "base/debug/crash_logging.h" | |
| #include "base/debug/dump_without_crashing.h" | |
| #include "base/feature_list.h" | |
| #include "base/functional/bind.h" | |
| #include "base/functional/callback.h" | |
| #include "base/functional/callback_helpers.h" | |
| #include "base/memory/raw_ptr.h" | |
| #include "base/notimplemented.h" | |
| #include "base/strings/string_split.h" | |
| #include "base/strings/string_view_util.h" | |
| #include "base/task/single_thread_task_runner.h" | |
| #include "base/trace_event/trace_event.h" | |
| #include "build/build_config.h" | |
| #include "gpu/command_buffer/service/command_buffer_service.h" | |
| #include "gpu/command_buffer/service/decoder_client.h" | |
| #include "gpu/command_buffer/service/feature_info.h" | |
| #include "gpu/command_buffer/service/gl_utils.h" | |
| #include "gpu/command_buffer/service/gpu_fence_manager.h" | |
| #include "gpu/command_buffer/service/gpu_switches.h" | |
| #include "gpu/command_buffer/service/gpu_tracer.h" | |
| #include "gpu/command_buffer/service/multi_draw_manager.h" | |
| #include "gpu/command_buffer/service/passthrough_program_cache.h" | |
| #include "gpu/command_buffer/service/program_cache.h" | |
| #include "gpu/command_buffer/service/service_utils.h" | |
| #include "gpu/command_buffer/service/shared_image/shared_image_manager.h" | |
| #include "gpu/command_buffer/service/shared_image/shared_image_representation.h" | |
| #include "gpu/config/gpu_crash_keys.h" | |
| #include "gpu/config/gpu_finch_features.h" | |
| #include "gpu/config/gpu_switches.h" | |
| #include "third_party/perfetto/include/perfetto/tracing/track.h" | |
| #include "ui/gl/gl_implementation.h" | |
| #include "ui/gl/gl_utils.h" | |
| #include "ui/gl/gl_version_info.h" | |
| #include "ui/gl/init/gl_factory.h" | |
| #include "ui/gl/progress_reporter.h" | |
| #include "ui/gl/scoped_make_current.h" | |
| #if BUILDFLAG(IS_WIN) | |
| #include "gpu/command_buffer/service/shared_image/d3d_image_backing_factory.h" | |
| #endif // BUILDFLAG(IS_WIN) | |
| namespace gpu { | |
| namespace gles2 { | |
| namespace { | |
| // TODO(https://crbug.com/574216445): Remove post safe roll out of M160. | |
| BASE_FEATURE(kDisallowSharedImageRedifinition, | |
| base::FEATURE_ENABLED_BY_DEFAULT); | |
| GLenum GetterForTextureTarget(GLenum target) { | |
| switch (target) { | |
| case GL_TEXTURE_2D: | |
| return GL_TEXTURE_BINDING_2D; | |
| case GL_TEXTURE_2D_ARRAY: | |
| return GL_TEXTURE_BINDING_2D_ARRAY; | |
| case GL_TEXTURE_EXTERNAL_OES: | |
| return GL_TEXTURE_BINDING_EXTERNAL_OES; | |
| case GL_TEXTURE_RECTANGLE_ANGLE: | |
| return GL_TEXTURE_BINDING_RECTANGLE_ANGLE; | |
| default: | |
| // Other targets not currently used. | |
| NOTIMPLEMENTED(); | |
| return GL_TEXTURE_2D; | |
| } | |
| } | |
| class ScopedFramebufferBindingReset { | |
| public: | |
| ScopedFramebufferBindingReset(gl::GLApi* api, | |
| bool supports_separate_fbo_bindings) | |
| : api_(api), | |
| supports_separate_fbo_bindings_(supports_separate_fbo_bindings), | |
| draw_framebuffer_(0), | |
| read_framebuffer_(0) { | |
| if (supports_separate_fbo_bindings_) { | |
| api_->glGetIntegervFn(GL_DRAW_FRAMEBUFFER_BINDING, &draw_framebuffer_); | |
| api_->glGetIntegervFn(GL_READ_FRAMEBUFFER_BINDING, &read_framebuffer_); | |
| } else { | |
| api_->glGetIntegervFn(GL_FRAMEBUFFER_BINDING, &draw_framebuffer_); | |
| } | |
| } | |
| ~ScopedFramebufferBindingReset() { | |
| if (supports_separate_fbo_bindings_) { | |
| api_->glBindFramebufferEXTFn(GL_DRAW_FRAMEBUFFER, draw_framebuffer_); | |
| api_->glBindFramebufferEXTFn(GL_READ_FRAMEBUFFER, read_framebuffer_); | |
| } else { | |
| api_->glBindFramebufferEXTFn(GL_FRAMEBUFFER, draw_framebuffer_); | |
| } | |
| } | |
| private: | |
| raw_ptr<gl::GLApi> api_; | |
| bool supports_separate_fbo_bindings_; | |
| GLint draw_framebuffer_; | |
| GLint read_framebuffer_; | |
| }; | |
| class ScopedTextureBindingReset { | |
| public: | |
| // |texture_target| only supports GL_TEXTURE_2D, GL_TEXTURE_2D_ARRAY, | |
| // GL_TEXTURE_EXTERNAL_OES, and GL_TEXTURE_RECTANGLE_ANGLE. | |
| ScopedTextureBindingReset(gl::GLApi* api, GLenum texture_target) | |
| : api_(api), texture_target_(texture_target), texture_(0) { | |
| api_->glGetIntegervFn(GetterForTextureTarget(texture_target_), &texture_); | |
| } | |
| ~ScopedTextureBindingReset() { | |
| api_->glBindTextureFn(texture_target_, texture_); | |
| } | |
| private: | |
| raw_ptr<gl::GLApi> api_; | |
| GLenum texture_target_; | |
| GLint texture_; | |
| }; | |
| class ScopedClearColorReset { | |
| public: | |
| explicit ScopedClearColorReset(gl::GLApi* api) : api_(api) { | |
| api_->glGetFloatvFn(GL_COLOR_CLEAR_VALUE, clear_color_); | |
| } | |
| ~ScopedClearColorReset() { | |
| api_->glClearColorFn(clear_color_[0], clear_color_[1], clear_color_[2], | |
| clear_color_[3]); | |
| } | |
| private: | |
| raw_ptr<gl::GLApi> api_; | |
| GLfloat clear_color_[4]; | |
| }; | |
| // Reset the color mask for buffer zero only. | |
| class ScopedColorMaskZeroReset { | |
| public: | |
| explicit ScopedColorMaskZeroReset(gl::GLApi* api, | |
| bool oes_draw_buffers_indexed) | |
| : api_(api), oes_draw_buffers_indexed_(oes_draw_buffers_indexed) { | |
| api_->glGetBooleanvFn(GL_COLOR_WRITEMASK, color_mask_); | |
| } | |
| ~ScopedColorMaskZeroReset() { | |
| if (oes_draw_buffers_indexed_) { | |
| api_->glColorMaskiOESFn(0, color_mask_[0], color_mask_[1], color_mask_[2], | |
| color_mask_[3]); | |
| } else { | |
| api_->glColorMaskFn(color_mask_[0], color_mask_[1], color_mask_[2], | |
| color_mask_[3]); | |
| } | |
| } | |
| private: | |
| raw_ptr<gl::GLApi> api_; | |
| const bool oes_draw_buffers_indexed_; | |
| // The color mask, or the color mask of buffer zero, if | |
| // OES_draw_buffers_indexed is enabled. | |
| GLboolean color_mask_[4]; | |
| }; | |
| class ScopedScissorTestReset { | |
| public: | |
| explicit ScopedScissorTestReset(gl::GLApi* api) : api_(api) { | |
| api_->glGetBooleanvFn(GL_SCISSOR_TEST, &scissor_test_); | |
| } | |
| ~ScopedScissorTestReset() { | |
| if (scissor_test_) | |
| api_->glEnableFn(GL_SCISSOR_TEST); | |
| else | |
| api_->glDisableFn(GL_SCISSOR_TEST); | |
| } | |
| private: | |
| raw_ptr<gl::GLApi> api_; | |
| GLboolean scissor_test_; | |
| }; | |
| class ScopedRasterizerDiscardReset { | |
| public: | |
| explicit ScopedRasterizerDiscardReset(gl::GLApi* api, | |
| bool rasterizer_discard_available) | |
| : api_(api), rasterizer_discard_available_(rasterizer_discard_available) { | |
| if (rasterizer_discard_available_) { | |
| api_->glGetBooleanvFn(GL_RASTERIZER_DISCARD, &rasterizer_discard_); | |
| } | |
| } | |
| ~ScopedRasterizerDiscardReset() { | |
| if (rasterizer_discard_available_) { | |
| if (rasterizer_discard_) { | |
| api_->glEnableFn(GL_RASTERIZER_DISCARD); | |
| } else { | |
| api_->glDisableFn(GL_RASTERIZER_DISCARD); | |
| } | |
| } | |
| } | |
| private: | |
| raw_ptr<gl::GLApi> api_; | |
| const bool rasterizer_discard_available_; | |
| GLboolean rasterizer_discard_; | |
| }; | |
| template <typename ClientType, typename ServiceType, typename DeleteFunction> | |
| void DeleteServiceObjects(ClientServiceMap<ClientType, ServiceType>* id_map, | |
| bool have_context, | |
| DeleteFunction delete_function) { | |
| if (have_context) { | |
| id_map->ForEach(delete_function); | |
| } | |
| id_map->Clear(); | |
| } | |
| template <typename ClientType, typename ServiceType, typename ResultType> | |
| bool GetClientID(const ClientServiceMap<ClientType, ServiceType>* map, | |
| ResultType service_id, | |
| ResultType* result) { | |
| ClientType client_id = 0; | |
| if (!map->GetClientID(static_cast<ServiceType>(service_id), &client_id)) { | |
| return false; | |
| } | |
| *result = static_cast<ResultType>(client_id); | |
| return true; | |
| } | |
| // Extensions that are enabled via emulation on the client side or needed for | |
| // basic command buffer functionality. Make sure they are always enabled. | |
| constexpr const char* kRequiredFunctionalityExtensions[] = { | |
| "GL_ANGLE_blob_cache", | |
| "GL_ANGLE_framebuffer_blit", | |
| #if BUILDFLAG(IS_FUCHSIA) | |
| "GL_ANGLE_memory_object_fuchsia", | |
| #endif | |
| "GL_ANGLE_memory_size", | |
| "GL_ANGLE_native_id", | |
| #if BUILDFLAG(IS_FUCHSIA) | |
| "GL_ANGLE_semaphore_fuchsia", | |
| #endif | |
| "GL_ANGLE_texture_storage_external", | |
| "GL_ANGLE_texture_usage", | |
| "GL_ANGLE_trim_memory", | |
| "GL_CHROMIUM_bind_uniform_location", | |
| "GL_CHROMIUM_sync_query", | |
| "GL_EXT_debug_marker", | |
| "GL_EXT_memory_object", | |
| "GL_EXT_memory_object_fd", | |
| "GL_EXT_semaphore", | |
| "GL_EXT_semaphore_fd", | |
| "GL_KHR_debug", | |
| "GL_NV_fence", | |
| "GL_EXT_EGL_image_array", | |
| "GL_EXT_EGL_image_storage", | |
| "GL_OES_EGL_image", | |
| "GL_OES_EGL_image_external", | |
| "GL_OES_EGL_image_external_essl3", | |
| #if BUILDFLAG(IS_APPLE) | |
| "GL_ANGLE_texture_rectangle", | |
| #endif | |
| "GL_ANGLE_vulkan_image", | |
| }; | |
| // Extensions that are not required but are used for command buffer | |
| // functionality. They are enabled by default but some tests request them | |
| // individually to ensure there is test coverage of them not being present. | |
| static constexpr const char* kOptionalFunctionalityExtensions[] = { | |
| "GL_ANGLE_depth_texture", | |
| "GL_ANGLE_framebuffer_multisample", | |
| "GL_ANGLE_get_tex_level_parameter", | |
| "GL_ANGLE_instanced_arrays", | |
| "GL_ANGLE_memory_object_flags", | |
| "GL_ANGLE_pack_reverse_row_order", | |
| "GL_ANGLE_translated_shader_source", | |
| "GL_EXT_blend_minmax", | |
| "GL_EXT_discard_framebuffer", | |
| "GL_EXT_disjoint_timer_query", | |
| "GL_EXT_map_buffer_range", | |
| "GL_EXT_multisampled_render_to_texture", | |
| "GL_EXT_occlusion_query_boolean", | |
| "GL_EXT_sRGB", | |
| "GL_EXT_sRGB_write_control", | |
| "GL_EXT_texture_format_BGRA8888", | |
| "GL_EXT_texture_norm16", | |
| "GL_EXT_texture_rg", | |
| "GL_EXT_texture_sRGB_decode", | |
| "GL_EXT_texture_storage", | |
| "GL_EXT_unpack_subimage", | |
| "GL_KHR_parallel_shader_compile", | |
| "GL_KHR_robust_buffer_access_behavior", | |
| "GL_OES_mapbuffer", | |
| #if BUILDFLAG(IS_CHROMEOS) | |
| // Required for Webgl to display in overlay on ChromeOS devices. | |
| // TODO(crbug.com/40244202): Consider for other platforms. | |
| "GL_MESA_framebuffer_flip_y", | |
| #endif | |
| "GL_NV_pack_subimage", | |
| "GL_NV_pixel_buffer_object", | |
| "GL_OES_depth32", | |
| "GL_OES_packed_depth_stencil", | |
| "GL_OES_rgb8_rgba8", | |
| "GL_OES_vertex_array_object", | |
| "NV_EGL_stream_consumer_external", | |
| }; | |
| // List of extensions needed to implement WebGL extensions and other command | |
| // decoder client functionality. | |
| constexpr const char* kValidRequestableExtensions[] = { | |
| "GL_ANGLE_clip_cull_distance", | |
| "GL_ANGLE_compressed_texture_etc", | |
| "GL_ANGLE_instanced_arrays", | |
| "GL_ANGLE_multi_draw", | |
| "GL_ANGLE_polygon_mode", | |
| "GL_ANGLE_provoking_vertex", | |
| "GL_ANGLE_shader_pixel_local_storage", | |
| "GL_ANGLE_shader_pixel_local_storage_coherent", | |
| "GL_ANGLE_stencil_texturing", | |
| "GL_ANGLE_texture_compression_dxt1", | |
| "GL_ANGLE_texture_compression_dxt3", | |
| "GL_ANGLE_texture_compression_dxt5", | |
| "GL_ANGLE_translated_shader_source", | |
| "GL_CHROMIUM_color_buffer_float_rgb", | |
| "GL_CHROMIUM_color_buffer_float_rgba", | |
| "GL_CHROMIUM_depth_texture", | |
| "GL_CHROMIUM_framebuffer_multisample", | |
| "GL_EXT_blend_func_extended", | |
| "GL_EXT_blend_minmax", | |
| "GL_EXT_clip_control", | |
| "GL_EXT_color_buffer_float", | |
| "GL_EXT_color_buffer_half_float", | |
| "GL_EXT_conservative_depth", | |
| "GL_EXT_depth_clamp", | |
| "GL_EXT_discard_framebuffer", | |
| "GL_EXT_disjoint_timer_query", | |
| "GL_EXT_disjoint_timer_query_webgl2", | |
| "GL_EXT_draw_buffers", | |
| "GL_EXT_float_blend", | |
| "GL_EXT_frag_depth", | |
| "GL_EXT_multisampled_render_to_texture", | |
| "GL_EXT_polygon_offset_clamp", | |
| "GL_EXT_render_snorm", | |
| "GL_EXT_sRGB", | |
| "GL_EXT_shader_texture_lod", | |
| "GL_EXT_texture_compression_bptc", | |
| "GL_EXT_texture_compression_dxt1", | |
| "GL_EXT_texture_compression_rgtc", | |
| "GL_EXT_texture_compression_s3tc", | |
| "GL_EXT_texture_compression_s3tc_srgb", | |
| "GL_EXT_texture_filter_anisotropic", | |
| "GL_EXT_texture_mirror_clamp_to_edge", | |
| "GL_EXT_texture_norm16", | |
| "GL_IMG_texture_compression_pvrtc", | |
| "GL_KHR_parallel_shader_compile", | |
| "GL_KHR_texture_compression_astc_hdr", | |
| "GL_KHR_texture_compression_astc_ldr", | |
| "GL_NV_shader_noperspective_interpolation", | |
| "GL_OES_compressed_ETC1_RGB8_texture", | |
| "GL_OES_draw_buffers_indexed", | |
| "GL_OES_element_index_uint", | |
| "GL_OES_fbo_render_mipmap", | |
| "GL_OES_packed_depth_stencil", | |
| "GL_OES_rgb8_rgba8", | |
| "GL_OES_sample_variables", | |
| "GL_OES_shader_multisample_interpolation", | |
| "GL_OES_standard_derivatives", | |
| "GL_OES_texture_float", | |
| "GL_OES_texture_float_linear", | |
| "GL_OES_texture_half_float", | |
| "GL_OES_texture_half_float_linear", | |
| "GL_OES_vertex_array_object", | |
| "GL_OVR_multiview2", | |
| "GL_QCOM_render_shared_exponent", | |
| }; | |
| // List of extensions needed to implement draft (not-yet-released) | |
| // WebGL extensions. | |
| constexpr const char* kValidRequestableWebGLDraftExtensions[] = { | |
| "GL_ANGLE_base_vertex_base_instance", | |
| }; | |
| void RequestExtensions(gl::GLApi* api, | |
| const gfx::ExtensionSet& requestable_extensions, | |
| base::span<const char* const> extensions_to_request) { | |
| for (auto* extension : extensions_to_request) { | |
| if (gfx::HasExtension(requestable_extensions, extension)) { | |
| // Request the intersection of the two sets │ | |
| api->glRequestExtensionANGLEFn(extension); | |
| } | |
| } | |
| } | |
| void GL_APIENTRY PassthroughGLDebugMessageCallback(GLenum source, | |
| GLenum type, | |
| GLuint id, | |
| GLenum severity, | |
| GLsizei length, | |
| const GLchar* message, | |
| const GLvoid* user_param) { | |
| DCHECK(user_param != nullptr); | |
| GLES2DecoderPassthroughImpl* command_decoder = | |
| static_cast<GLES2DecoderPassthroughImpl*>(const_cast<void*>(user_param)); | |
| command_decoder->OnDebugMessage(source, type, id, severity, length, message); | |
| LogGLDebugMessage(source, type, id, severity, length, message, | |
| command_decoder->GetLogger()); | |
| if (source == GL_DEBUG_SOURCE_OTHER && type == GL_DEBUG_TYPE_PORTABILITY && | |
| (id == 0xBADDEF || id == 0xBADBA5E)) { | |
| // Note: log_message cannot contain any user data. The error strings | |
| // generated from ANGLE are all static strings and do not contain user | |
| // information such as shader source code. Be careful if updating the | |
| // contents of this string. | |
| std::string log_message = base::NumberToString(id); | |
| if (message && length > 0) { | |
| log_message += ": " + std::string(message, length); | |
| } | |
| LOG(ERROR) << log_message; | |
| crash_keys::gpu_gl_error_message.Set(log_message); | |
| // Limit to 1 report per GPU process | |
| static uint32_t reports_left = 1; | |
| static bool no_dump = base::CommandLine::ForCurrentProcess()->HasSwitch( | |
| switches::kDisableDumpWithoutCrashingOnAnglePortabilityMessages); | |
| if (reports_left > 0 && !no_dump) { | |
| base::debug::DumpWithoutCrashing(); | |
| --reports_left; | |
| } | |
| } | |
| } | |
| GLsizeiptr GL_APIENTRY | |
| PassthroughGLBlobCacheGetCallback(const void* key, | |
| GLsizeiptr key_size, | |
| void* value, | |
| GLsizeiptr value_size, | |
| const void* user_param) { | |
| DCHECK(user_param != nullptr); | |
| GLES2DecoderPassthroughImpl* command_decoder = | |
| static_cast<GLES2DecoderPassthroughImpl*>(const_cast<void*>(user_param)); | |
| return command_decoder->BlobCacheGet(key, key_size, value, value_size); | |
| } | |
| void GL_APIENTRY PassthroughGLBlobCacheSetCallback(const void* key, | |
| GLsizeiptr key_size, | |
| const void* value, | |
| GLsizeiptr value_size, | |
| const void* user_param) { | |
| DCHECK(user_param != nullptr); | |
| GLES2DecoderPassthroughImpl* command_decoder = | |
| static_cast<GLES2DecoderPassthroughImpl*>(const_cast<void*>(user_param)); | |
| command_decoder->BlobCacheSet(key, key_size, value, value_size); | |
| } | |
| void RunCallbacks(std::vector<base::OnceClosure> callbacks) { | |
| for (base::OnceClosure& callback : callbacks) { | |
| std::move(callback).Run(); | |
| } | |
| } | |
| // Converts texture targets to texture binding types. Does not validate the | |
| // input. | |
| GLenum TextureTargetToTextureType(GLenum texture_target) { | |
| switch (texture_target) { | |
| case GL_TEXTURE_CUBE_MAP_POSITIVE_X: | |
| case GL_TEXTURE_CUBE_MAP_NEGATIVE_X: | |
| case GL_TEXTURE_CUBE_MAP_POSITIVE_Y: | |
| case GL_TEXTURE_CUBE_MAP_NEGATIVE_Y: | |
| case GL_TEXTURE_CUBE_MAP_POSITIVE_Z: | |
| case GL_TEXTURE_CUBE_MAP_NEGATIVE_Z: | |
| return GL_TEXTURE_CUBE_MAP; | |
| default: | |
| return texture_target; | |
| } | |
| } | |
| void UpdateBoundTexturePassthroughSize(gl::GLApi* api, | |
| TexturePassthrough* texture) { | |
| GLint texture_memory_size = 0; | |
| api->glGetTexParameterivFn(texture->target(), GL_MEMORY_SIZE_ANGLE, | |
| &texture_memory_size); | |
| texture->SetEstimatedSize(texture_memory_size); | |
| } | |
| void ReturnProgramInfoData(DecoderClient* client, | |
| const std::vector<uint8_t>& info, | |
| GLES2ReturnDataType type, | |
| uint32_t program) { | |
| // Limit the data size in order not to block the IO threads too long. | |
| // https://docs.google.com/document/d/1qEfU0lAkeZ8lU06qtxv7ENGxRxExxztXu1LhIDNGqtU/edit?disco=AAAACksORlU | |
| constexpr static size_t kMaxDataSize = 8 * 1024 * 1024; | |
| if (info.size() > kMaxDataSize) | |
| return; | |
| std::vector<uint8_t> return_data; | |
| return_data.resize(sizeof(cmds::GLES2ReturnProgramInfo) + info.size()); | |
| auto* return_program_info = UNSAFE_TODO( | |
| reinterpret_cast<cmds::GLES2ReturnProgramInfo*>(return_data.data())); | |
| return_program_info->return_data_header.return_data_type = type; | |
| return_program_info->program_client_id = program; | |
| UNSAFE_TODO(memcpy(return_program_info->deserialized_buffer, info.data(), | |
| info.size())); | |
| client->HandleReturnData( | |
| UNSAFE_TODO(base::span<uint8_t>(return_data.data(), return_data.size()))); | |
| } | |
| } // anonymous namespace | |
| GLES2DecoderPassthroughImpl::ScopedPixelLocalStorageInterrupt:: | |
| ScopedPixelLocalStorageInterrupt(const GLES2DecoderPassthroughImpl* impl) | |
| : impl_(impl) { | |
| if (impl_->has_activated_pixel_local_storage_) { | |
| impl_->api()->glFramebufferPixelLocalStorageInterruptANGLEFn(); | |
| } | |
| } | |
| GLES2DecoderPassthroughImpl::ScopedPixelLocalStorageInterrupt:: | |
| ~ScopedPixelLocalStorageInterrupt() { | |
| if (impl_->has_activated_pixel_local_storage_) { | |
| impl_->api()->glFramebufferPixelLocalStorageRestoreANGLEFn(); | |
| } | |
| } | |
| GLES2DecoderPassthroughImpl::ScopedSharedContextCurrent:: | |
| ScopedSharedContextCurrent(GLES2DecoderPassthroughImpl* impl, | |
| gl::GLContext* context, | |
| gl::GLSurface* surface) | |
| : impl_(impl), | |
| decoder_context_was_current_(impl_->IsDecoderContextCurrent()) { | |
| // Only interrupt pixel local storage on the decoder's own context. | |
| if (decoder_context_was_current_ && | |
| impl_->has_activated_pixel_local_storage_) { | |
| impl_->api()->glFramebufferPixelLocalStorageInterruptANGLEFn(); | |
| pixel_local_storage_interrupted_ = true; | |
| } | |
| scoped_make_current_.emplace(context, surface); | |
| } | |
| GLES2DecoderPassthroughImpl::ScopedSharedContextCurrent:: | |
| ~ScopedSharedContextCurrent() { | |
| // Restore the previously current context. | |
| scoped_make_current_.reset(); | |
| if (!impl_->IsDecoderContextCurrent()) { | |
| if (decoder_context_was_current_) { | |
| LOG(ERROR) << "GLES2DecoderPassthroughImpl: failed to restore context " | |
| "after using the shared context."; | |
| impl_->MarkContextLost(error::kMakeCurrentFailed); | |
| } | |
| // Don't make further GL calls; they would run on the wrong context. | |
| return; | |
| } | |
| if (pixel_local_storage_interrupted_) { | |
| impl_->api()->glFramebufferPixelLocalStorageRestoreANGLEFn(); | |
| } | |
| } | |
| bool GLES2DecoderPassthroughImpl::ScopedSharedContextCurrent:: | |
| IsContextCurrent() { | |
| return scoped_make_current_->IsContextCurrent(); | |
| } | |
| bool GLES2DecoderPassthroughImpl::IsDecoderContextCurrent() const { | |
| // Check Chromium's notion of the current context first; IsCurrent() DCHECKs | |
| // that it matches when the native context is current. | |
| return context_ && gl::GLContext::GetCurrent() == context_.get() && | |
| context_->IsCurrent(nullptr); | |
| } | |
| PassthroughResources::PassthroughResources() : texture_object_map(nullptr) {} | |
| PassthroughResources::~PassthroughResources() = default; | |
| void PassthroughResources::SuspendSharedImageAccessIfNeeded() { | |
| for (auto& [texture_id, shared_image_data] : texture_shared_image_map) { | |
| shared_image_data.SuspendAccessIfNeeded(); | |
| } | |
| } | |
| bool PassthroughResources::ResumeSharedImageAccessIfNeeded(gl::GLApi* api) { | |
| bool success = true; | |
| for (auto& [texture_id, shared_image_data] : texture_shared_image_map) { | |
| if (!shared_image_data.ResumeAccessIfNeeded(api)) { | |
| success = false; | |
| } | |
| } | |
| return success; | |
| } | |
| void PassthroughResources::MarkContextLost() { | |
| texture_object_map.ForEach( | |
| [](GLuint client_id, scoped_refptr<TexturePassthrough> texture) { | |
| texture->MarkContextLost(); | |
| }); | |
| for (auto& pair : texture_shared_image_map) { | |
| pair.second.representation()->OnContextLost(); | |
| } | |
| } | |
| void PassthroughResources::Destroy(gl::GLApi* api, | |
| gl::ProgressReporter* progress_reporter) { | |
| bool have_context = !!api; | |
| // Only delete textures that are not referenced by a TexturePassthrough | |
| // object, they handle their own deletion once all references are lost | |
| DeleteServiceObjects( | |
| &texture_id_map, have_context, | |
| [this, api, progress_reporter](GLuint client_id, GLuint texture) { | |
| if (!texture_object_map.HasClientID(client_id)) { | |
| api->glDeleteTexturesFn(1, &texture); | |
| if (progress_reporter) { | |
| progress_reporter->ReportProgress(); | |
| } | |
| } | |
| }); | |
| DeleteServiceObjects( | |
| &buffer_id_map, have_context, | |
| [api, progress_reporter](GLuint client_id, GLuint buffer) { | |
| api->glDeleteBuffersARBFn(1, &buffer); | |
| if (progress_reporter) { | |
| progress_reporter->ReportProgress(); | |
| } | |
| }); | |
| DeleteServiceObjects( | |
| &renderbuffer_id_map, have_context, | |
| [api, progress_reporter](GLuint client_id, GLuint renderbuffer) { | |
| api->glDeleteRenderbuffersEXTFn(1, &renderbuffer); | |
| if (progress_reporter) { | |
| progress_reporter->ReportProgress(); | |
| } | |
| }); | |
| DeleteServiceObjects(&sampler_id_map, have_context, | |
| [api](GLuint client_id, GLuint sampler) { | |
| api->glDeleteSamplersFn(1, &sampler); | |
| }); | |
| DeleteServiceObjects( | |
| &program_id_map, have_context, | |
| [api, progress_reporter](GLuint client_id, GLuint program) { | |
| api->glDeleteProgramFn(program); | |
| if (progress_reporter) { | |
| progress_reporter->ReportProgress(); | |
| } | |
| }); | |
| DeleteServiceObjects(&shader_id_map, have_context, | |
| [api](GLuint client_id, GLuint shader) { | |
| api->glDeleteShaderFn(shader); | |
| }); | |
| DeleteServiceObjects(&sync_id_map, have_context, | |
| [api](GLuint client_id, uintptr_t sync) { | |
| api->glDeleteSyncFn(reinterpret_cast<GLsync>(sync)); | |
| }); | |
| if (!have_context) { | |
| texture_object_map.ForEach( | |
| [](GLuint client_id, scoped_refptr<TexturePassthrough> texture) { | |
| texture->MarkContextLost(); | |
| }); | |
| for (auto& pair : texture_shared_image_map) { | |
| pair.second.representation()->OnContextLost(); | |
| } | |
| } | |
| texture_object_map.Clear(); | |
| texture_shared_image_map.clear(); | |
| } | |
| PassthroughResources::SharedImageData::SharedImageData() = default; | |
| PassthroughResources::SharedImageData::SharedImageData( | |
| GLES2DecoderPassthroughImpl* impl, | |
| std::unique_ptr<GLTexturePassthroughImageRepresentation> representation) | |
| : representation_(std::move(representation)) { | |
| DCHECK(representation_); | |
| // Note, that ideally we could defer clear till BeginAccess, but there is no | |
| // enforcement that will require clients to call Begin/End access. | |
| EnsureClear(impl); | |
| } | |
| PassthroughResources::SharedImageData::SharedImageData( | |
| SharedImageData&& other) = default; | |
| PassthroughResources::SharedImageData::~SharedImageData() = default; | |
| PassthroughResources::SharedImageData& | |
| PassthroughResources::SharedImageData::operator=(SharedImageData&& other) { | |
| scoped_access_ = std::move(other.scoped_access_); | |
| access_mode_ = std::move(other.access_mode_); | |
| other.access_mode_.reset(); | |
| representation_ = std::move(other.representation_); | |
| return *this; | |
| } | |
| void PassthroughResources::SharedImageData::EnsureClear( | |
| GLES2DecoderPassthroughImpl* impl) { | |
| // To avoid unnessary overhead we don't enable robust initialization on shared | |
| // gl context where all shared images are created, so we clear image here if | |
| // necessary. | |
| if (!representation_->IsCleared()) { | |
| // Move any pending errors into the decoder's error state so that only | |
| // errors raised while clearing are checked below. | |
| impl->FlushErrors(); | |
| if (impl->WasContextLost()) { | |
| return; | |
| } | |
| // Allow uncleared access as we're going to clear the image. | |
| auto scoped_access = representation_->BeginScopedAccess( | |
| GL_SHARED_IMAGE_ACCESS_MODE_READWRITE_CHROMIUM, | |
| SharedImageRepresentation::AllowUnclearedAccess::kYes); | |
| if (!scoped_access) { | |
| impl->FlushErrors(); | |
| return; | |
| } | |
| auto texture = representation_->GetTexturePassthrough(); | |
| const bool use_oes_draw_buffers_indexed = | |
| impl->features().oes_draw_buffers_indexed; | |
| bool has_rasterizer_discard = | |
| impl->GetFeatureInfo()->gl_version_info().IsAtLeastGLES(3, 0); | |
| // Back up all state we are about to change. | |
| gl::GLApi* api = impl->api(); | |
| GLES2DecoderPassthroughImpl::ScopedPixelLocalStorageInterrupt | |
| scoped_pls_interrupt(impl); | |
| ScopedFramebufferBindingReset fbo_reset( | |
| api, false /* supports_seperate_fbo_bindings */); | |
| ScopedTextureBindingReset texture_reset(api, texture->target()); | |
| ScopedClearColorReset clear_color_reset(api); | |
| ScopedColorMaskZeroReset color_mask_reset(api, | |
| use_oes_draw_buffers_indexed); | |
| ScopedScissorTestReset scissor_test_reset(api); | |
| ScopedRasterizerDiscardReset rasterizer_discard_reset( | |
| api, has_rasterizer_discard); | |
| // Generate a new framebuffer and bind the shared image's uncleared texture | |
| // to it. | |
| GLuint fbo = 0; | |
| api->glGenFramebuffersEXTFn(1, &fbo); | |
| api->glBindFramebufferEXTFn(GL_FRAMEBUFFER, fbo); | |
| api->glBindTextureFn(texture->target(), texture->service_id()); | |
| // Clear the bound framebuffer. | |
| api->glClearColorFn(0, 0, 0, 0); | |
| if (use_oes_draw_buffers_indexed) | |
| api->glColorMaskiOESFn(0, true, true, true, true); | |
| else | |
| api->glColorMaskFn(true, true, true, true); | |
| api->glDisableFn(GL_SCISSOR_TEST); | |
| if (has_rasterizer_discard) { | |
| api->glDisableFn(GL_RASTERIZER_DISCARD); | |
| } | |
| bool all_layers_cleared = true; | |
| if (texture->target() == GL_TEXTURE_2D_ARRAY) { | |
| const uint32_t layers = representation_->array_layers(); | |
| for (uint32_t layer = 0; layer < layers; ++layer) { | |
| api->glFramebufferTextureLayerFn(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, | |
| texture->service_id(), 0, layer); | |
| api->glClearFn(GL_COLOR_BUFFER_BIT); | |
| if (api->glCheckFramebufferStatusEXTFn(GL_FRAMEBUFFER) != | |
| GL_FRAMEBUFFER_COMPLETE) { | |
| all_layers_cleared = false; | |
| } | |
| } | |
| api->glFramebufferTextureLayerFn(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, 0, | |
| 0, 0); | |
| } else { | |
| api->glFramebufferTexture2DEXTFn(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, | |
| texture->target(), texture->service_id(), | |
| 0); | |
| api->glClearFn(GL_COLOR_BUFFER_BIT); | |
| if (api->glCheckFramebufferStatusEXTFn(GL_FRAMEBUFFER) != | |
| GL_FRAMEBUFFER_COMPLETE) { | |
| all_layers_cleared = false; | |
| } | |
| api->glFramebufferTexture2DEXTFn(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, | |
| texture->target(), 0, 0); | |
| } | |
| // Don't mark the image cleared if the driver reported an error. Errors | |
| // remain in the decoder's error state for the client. | |
| if (impl->FlushErrors() || impl->WasContextLost()) { | |
| all_layers_cleared = false; | |
| } | |
| if (all_layers_cleared) { | |
| // Mark the shared image as cleared. | |
| representation_->SetCleared(); | |
| } | |
| // Delete the generated framebuffer. | |
| api->glDeleteFramebuffersEXTFn(1, &fbo); | |
| } | |
| } | |
| bool PassthroughResources::SharedImageData::BeginAccess(GLenum mode, | |
| gl::GLApi* api) { | |
| DCHECK(!is_being_accessed()); | |
| // The image should have been cleared already when we created the texture if | |
| // necessary. | |
| scoped_access_ = representation_->BeginScopedAccess( | |
| mode, SharedImageRepresentation::AllowUnclearedAccess::kNo); | |
| if (scoped_access_) { | |
| access_mode_.emplace(mode); | |
| return true; | |
| } | |
| return false; | |
| } | |
| void PassthroughResources::SharedImageData::EndAccess() { | |
| DCHECK(is_being_accessed()); | |
| scoped_access_.reset(); | |
| access_mode_.reset(); | |
| } | |
| bool PassthroughResources::SharedImageData::ResumeAccessIfNeeded( | |
| gl::GLApi* api) { | |
| // Do not resume access if BeginAccess was never called or if a scoped access | |
| // is already present. | |
| if (!is_being_accessed() || scoped_access_) { | |
| return true; | |
| } | |
| scoped_access_ = representation_->BeginScopedAccess( | |
| access_mode_.value(), | |
| SharedImageRepresentation::AllowUnclearedAccess::kNo); | |
| return !!scoped_access_; | |
| } | |
| void PassthroughResources::SharedImageData::SuspendAccessIfNeeded() { | |
| // Suspend access if shared image is being accessed and doesn't support | |
| // concurrent read access on other clients or devices. | |
| if (is_being_accessed() && | |
| representation_->NeedsSuspendAccessForDXGIKeyedMutex()) { | |
| scoped_access_.reset(); | |
| } | |
| } | |
| GLES2DecoderPassthroughImpl::PendingQuery::PendingQuery() = default; | |
| GLES2DecoderPassthroughImpl::PendingQuery::~PendingQuery() { | |
| // Run all callbacks when a query is destroyed even if it did not complete. | |
| // This avoids leaks due to outstandsing callbacks. | |
| RunCallbacks(std::move(callbacks)); | |
| } | |
| GLES2DecoderPassthroughImpl::PendingQuery::PendingQuery(PendingQuery&&) = | |
| default; | |
| GLES2DecoderPassthroughImpl::PendingQuery& | |
| GLES2DecoderPassthroughImpl::PendingQuery::operator=(PendingQuery&&) = default; | |
| GLES2DecoderPassthroughImpl::ActiveQuery::ActiveQuery() = default; | |
| GLES2DecoderPassthroughImpl::ActiveQuery::~ActiveQuery() = default; | |
| GLES2DecoderPassthroughImpl::ActiveQuery::ActiveQuery(ActiveQuery&&) = default; | |
| GLES2DecoderPassthroughImpl::ActiveQuery& | |
| GLES2DecoderPassthroughImpl::ActiveQuery::operator=(ActiveQuery&&) = default; | |
| GLES2DecoderPassthroughImpl::BoundTexture::BoundTexture() = default; | |
| GLES2DecoderPassthroughImpl::BoundTexture::~BoundTexture() = default; | |
| GLES2DecoderPassthroughImpl::BoundTexture::BoundTexture(const BoundTexture&) = | |
| default; | |
| GLES2DecoderPassthroughImpl::BoundTexture::BoundTexture(BoundTexture&&) = | |
| default; | |
| GLES2DecoderPassthroughImpl::BoundTexture& | |
| GLES2DecoderPassthroughImpl::BoundTexture::operator=(const BoundTexture&) = | |
| default; | |
| GLES2DecoderPassthroughImpl::BoundTexture& | |
| GLES2DecoderPassthroughImpl::BoundTexture::operator=(BoundTexture&&) = default; | |
| GLES2DecoderPassthroughImpl::PendingReadPixels::PendingReadPixels() = default; | |
| GLES2DecoderPassthroughImpl::PendingReadPixels::~PendingReadPixels() = default; | |
| GLES2DecoderPassthroughImpl::PendingReadPixels::PendingReadPixels( | |
| PendingReadPixels&&) = default; | |
| GLES2DecoderPassthroughImpl::PendingReadPixels& | |
| GLES2DecoderPassthroughImpl::PendingReadPixels::operator=(PendingReadPixels&&) = | |
| default; | |
| GLES2DecoderPassthroughImpl::BufferShadowUpdate::BufferShadowUpdate() = default; | |
| GLES2DecoderPassthroughImpl::BufferShadowUpdate::~BufferShadowUpdate() = | |
| default; | |
| GLES2DecoderPassthroughImpl::BufferShadowUpdate::BufferShadowUpdate( | |
| BufferShadowUpdate&&) = default; | |
| GLES2DecoderPassthroughImpl::BufferShadowUpdate& | |
| GLES2DecoderPassthroughImpl::BufferShadowUpdate::operator=( | |
| BufferShadowUpdate&&) = default; | |
| GLES2DecoderPassthroughImpl::EmulatedDefaultFramebuffer:: | |
| EmulatedDefaultFramebuffer(const GLES2DecoderPassthroughImpl* impl) | |
| : impl_(impl) {} | |
| GLES2DecoderPassthroughImpl::EmulatedDefaultFramebuffer:: | |
| ~EmulatedDefaultFramebuffer() = default; | |
| bool GLES2DecoderPassthroughImpl::EmulatedDefaultFramebuffer::Initialize( | |
| const gfx::Size& size) { | |
| DCHECK(!size.IsEmpty()); | |
| ScopedPixelLocalStorageInterrupt scoped_pls_interrupt(impl_); | |
| ScopedFramebufferBindingReset scoped_fbo_reset( | |
| api(), impl_->supports_separate_fbo_bindings_); | |
| ScopedTextureBindingReset scoped_texture_reset(api(), GL_TEXTURE_2D); | |
| api()->glGenFramebuffersEXTFn(1, &framebuffer_service_id); | |
| api()->glBindFramebufferEXTFn(GL_FRAMEBUFFER, framebuffer_service_id); | |
| const auto format = impl_->emulated_default_framebuffer_format_; | |
| GLuint color_buffer_texture = 0; | |
| api()->glGenTexturesFn(1, &color_buffer_texture); | |
| api()->glBindTextureFn(GL_TEXTURE_2D, color_buffer_texture); | |
| api()->glTexParameteriFn(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); | |
| api()->glTexParameteriFn(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); | |
| api()->glTexParameteriFn(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); | |
| api()->glTexParameteriFn(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); | |
| api()->glTexImage2DFn(GL_TEXTURE_2D, 0, format, size.width(), size.height(), | |
| 0, format, GL_UNSIGNED_BYTE, nullptr); | |
| texture = new TexturePassthrough(color_buffer_texture, GL_TEXTURE_2D); | |
| UpdateBoundTexturePassthroughSize(api(), texture.get()); | |
| api()->glFramebufferTexture2DEXTFn(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, | |
| GL_TEXTURE_2D, texture->service_id(), 0); | |
| // Check that the framebuffer is complete | |
| if (api()->glCheckFramebufferStatusEXTFn(GL_FRAMEBUFFER) != | |
| GL_FRAMEBUFFER_COMPLETE) { | |
| LOG(ERROR) | |
| << "GLES2DecoderPassthroughImpl::ResizeOffscreenFramebuffer failed " | |
| << "because the resulting framebuffer was not complete."; | |
| return false; | |
| } | |
| return true; | |
| } | |
| void GLES2DecoderPassthroughImpl::EmulatedDefaultFramebuffer::Destroy( | |
| bool have_context) { | |
| if (have_context) { | |
| api()->glDeleteFramebuffersEXTFn(1, &framebuffer_service_id); | |
| framebuffer_service_id = 0; | |
| } else { | |
| texture->MarkContextLost(); | |
| } | |
| texture = nullptr; | |
| } | |
| GLES2DecoderPassthroughImpl::GLES2DecoderPassthroughImpl( | |
| DecoderClient* client, | |
| CommandBufferServiceBase* command_buffer_service, | |
| Outputter* outputter, | |
| ContextGroup* group) | |
| : GLES2Decoder(client, command_buffer_service, outputter), | |
| commands_to_process_(0), | |
| debug_marker_manager_(), | |
| logger_(&debug_marker_manager_, | |
| base::BindRepeating(&DecoderClient::OnConsoleMessage, | |
| base::Unretained(client), | |
| 0), | |
| group->gpu_preferences().disable_gl_error_limit), | |
| surface_(), | |
| context_(), | |
| offscreen_(false), | |
| group_(group), | |
| feature_info_(base::MakeRefCounted<FeatureInfo>( | |
| group->feature_info()->workarounds(), | |
| group->gpu_feature_info())), | |
| emulated_back_buffer_(nullptr), | |
| bound_draw_framebuffer_(0), | |
| bound_read_framebuffer_(0), | |
| gpu_trace_level_(2), | |
| gpu_trace_commands_(false), | |
| gpu_debug_commands_(false), | |
| context_lost_(false), | |
| reset_by_robustness_extension_(false), | |
| lose_context_when_out_of_memory_(false) { | |
| DCHECK(client); | |
| DCHECK(group); | |
| } | |
| GLES2DecoderPassthroughImpl::~GLES2DecoderPassthroughImpl() = default; | |
| GLES2Decoder::Error GLES2DecoderPassthroughImpl::DoCommands( | |
| unsigned int num_commands, | |
| const volatile void* buffer, | |
| int num_entries, | |
| int* entries_processed) { | |
| if (gpu_debug_commands_) { | |
| return DoCommandsImpl<true>(num_commands, buffer, num_entries, | |
| entries_processed); | |
| } else { | |
| return DoCommandsImpl<false>(num_commands, buffer, num_entries, | |
| entries_processed); | |
| } | |
| } | |
| template <bool DebugImpl> | |
| GLES2Decoder::Error GLES2DecoderPassthroughImpl::DoCommandsImpl( | |
| unsigned int num_commands, | |
| const volatile void* buffer, | |
| int num_entries, | |
| int* entries_processed) { | |
| commands_to_process_ = num_commands; | |
| error::Error result = error::kNoError; | |
| const volatile CommandBufferEntry* cmd_data = | |
| static_cast<const volatile CommandBufferEntry*>(buffer); | |
| int process_pos = 0; | |
| unsigned int command = 0; | |
| while (process_pos < num_entries && result == error::kNoError && | |
| commands_to_process_--) { | |
| const unsigned int size = cmd_data->value_header.size; | |
| command = cmd_data->value_header.command; | |
| if (size == 0) { | |
| result = error::kInvalidSize; | |
| break; | |
| } | |
| // size can't overflow because it is 21 bits. | |
| if (static_cast<int>(size) + process_pos > num_entries) { | |
| result = error::kOutOfBounds; | |
| break; | |
| } | |
| if (DebugImpl && log_commands()) { | |
| LOG(ERROR) << "[" << logger_.GetLogPrefix() << "]" | |
| << "cmd: " << GetCommandName(command); | |
| } | |
| const unsigned int arg_count = size - 1; | |
| unsigned int command_index = command - kFirstGLES2Command; | |
| if (command_index < command_info.size()) { | |
| const CommandInfo& info = command_info[command_index]; | |
| unsigned int info_arg_count = static_cast<unsigned int>(info.arg_count); | |
| if ((info.arg_flags == cmd::kFixed && arg_count == info_arg_count) || | |
| (info.arg_flags == cmd::kAtLeastN && arg_count >= info_arg_count)) { | |
| bool doing_gpu_trace = false; | |
| if (DebugImpl && gpu_trace_commands_) { | |
| if (CMD_FLAG_GET_TRACE_LEVEL(info.cmd_flags) <= gpu_trace_level_) { | |
| doing_gpu_trace = true; | |
| gpu_tracer_->Begin(TRACE_DISABLED_BY_DEFAULT("gpu.decoder"), | |
| GetCommandName(command), kTraceDecoder); | |
| } | |
| } | |
| if (DebugImpl) { | |
| VerifyServiceTextureObjectsExist(); | |
| } | |
| uint32_t immediate_data_size = (arg_count - info_arg_count) * | |
| sizeof(CommandBufferEntry); // NOLINT | |
| if (info.cmd_handler) { | |
| result = (this->*info.cmd_handler)(immediate_data_size, cmd_data); | |
| } else { | |
| result = error::kUnknownCommand; | |
| } | |
| if (DebugImpl && doing_gpu_trace) { | |
| gpu_tracer_->End(kTraceDecoder); | |
| } | |
| } else { | |
| result = error::kInvalidArguments; | |
| } | |
| } else { | |
| result = DoCommonCommand(command, arg_count, cmd_data); | |
| } | |
| // A command must not leave a different GL context or surface current. If | |
| // one did, subsequent commands would operate on the wrong context or | |
| // surface, so treat it as a lost context. This only checks Chromium's | |
| // bookkeeping, which is cheap enough to do per command. | |
| if (result == error::kNoError && !context_lost_ && | |
| (gl::GLContext::GetCurrent() != context_.get() || | |
| gl::GLSurface::GetCurrent() != surface_.get())) { | |
| LOG(ERROR) << "GLES2DecoderPassthroughImpl: context or surface is no " | |
| "longer current after command " | |
| << GetCommandName(command); | |
| MarkContextLost(error::kMakeCurrentFailed); | |
| } | |
| if (result == error::kNoError && context_lost_) { | |
| result = error::kLostContext; | |
| } | |
| if (result != error::kDeferCommandUntilLater) { | |
| process_pos += size; | |
| UNSAFE_TODO(cmd_data += size); | |
| } | |
| } | |
| if (entries_processed) | |
| *entries_processed = process_pos; | |
| #if BUILDFLAG(IS_MAC) | |
| // Aggressively call glFlush on macOS. This is the only fix that has been | |
| // found so far to avoid crashes on Intel drivers. The workaround | |
| // isn't needed for WebGL contexts, though. | |
| // https://crbug.com/863817 | |
| if (!feature_info_->IsWebGLContext()) | |
| context_->FlushForDriverCrashWorkaround(); | |
| #endif | |
| if (process_pos > 0 && feature_info_->IsWebGLContext()) { | |
| ScheduleIdleMemoryTrim(); | |
| } | |
| return result; | |
| } | |
| void GLES2DecoderPassthroughImpl::ExitCommandProcessingEarly() { | |
| commands_to_process_ = 0; | |
| } | |
| base::WeakPtr<DecoderContext> GLES2DecoderPassthroughImpl::AsWeakPtr() { | |
| return weak_ptr_factory_.GetWeakPtr(); | |
| } | |
| gpu::ContextResult GLES2DecoderPassthroughImpl::Initialize( | |
| const scoped_refptr<gl::GLSurface>& surface, | |
| const scoped_refptr<gl::GLContext>& context, | |
| bool offscreen, | |
| ContextType context_type, | |
| bool lose_context_when_out_of_memory) { | |
| TRACE_EVENT0("gpu", "GLES2DecoderPassthroughImpl::Initialize"); | |
| CHECK(gl::GetGLImplementation() == gl::kGLImplementationEGLANGLE) | |
| << "Running WebGL through passthrough command decoder without ANGLE's " | |
| << "validation is a security risk"; | |
| DCHECK(context->IsCurrent(surface.get())); | |
| api_ = gl::g_current_gl_context; | |
| // Take ownership of the context and surface. The surface can be replaced | |
| // with SetSurface. | |
| context_ = context; | |
| surface_ = surface; | |
| offscreen_ = offscreen; | |
| // For WebGL contexts, log GL errors so they appear in devtools. Otherwise | |
| // only enable debug logging if requested. | |
| bool log_non_errors = | |
| group_->gpu_preferences().enable_gpu_driver_debug_logging; | |
| InitializeGLDebugLogging(log_non_errors, PassthroughGLDebugMessageCallback, | |
| this); | |
| // Create GPU Tracer for timing values. | |
| gpu_tracer_ = std::make_unique<GPUTracer>(this); | |
| gpu_fence_manager_ = std::make_unique<GpuFenceManager>(); | |
| multi_draw_manager_ = std::make_unique<MultiDrawManager>( | |
| MultiDrawManager::IndexStorageType::Pointer); | |
| auto result = group_->Initialize(this, context_type); | |
| if (result != gpu::ContextResult::kSuccess) { | |
| // Must not destroy ContextGroup if it is not initialized. | |
| group_ = nullptr; | |
| Destroy(true); | |
| return result; | |
| } | |
| { | |
| gfx::ExtensionSet requestable_extensions( | |
| gl::GetRequestableGLExtensionsFromCurrentContext()); | |
| // Request extensions needed for command decoder functionality | |
| RequestExtensions(api(), requestable_extensions, | |
| kRequiredFunctionalityExtensions); | |
| // Request optional functionality extensions. `request_optional_extensions_` | |
| // may be set by tests to disable this behavior. | |
| if (request_optional_extensions_) { | |
| RequestExtensions(api(), requestable_extensions, | |
| kOptionalFunctionalityExtensions); | |
| } | |
| // For non-WebGL command decoders, request all valid requestable extensions | |
| // right away. | |
| if (!IsWebGLContextType(context_type)) { | |
| RequestExtensions(api(), requestable_extensions, | |
| kValidRequestableExtensions); | |
| } | |
| } | |
| context->ReinitializeDynamicBindings(); | |
| BuildRequestableExtensionString(); | |
| // Each context initializes its own feature info because some extensions may | |
| // be enabled dynamically. Don't disallow any features, leave it up to ANGLE | |
| // to dynamically enable extensions. | |
| feature_info_->Initialize(context_type, /*is_passthrough_cmd_decoder=*/true, | |
| DisallowedFeatures()); | |
| // Check for required extensions | |
| // TODO(geofflang): verify | |
| // feature_info_->feature_flags().angle_robust_resource_initialization and | |
| // api()->glIsEnabledFn(GL_ROBUST_RESOURCE_INITIALIZATION_ANGLE) | |
| #define FAIL_INIT_IF_NOT(feature, message) \ | |
| if (!(feature)) { \ | |
| Destroy(true); \ | |
| LOG(ERROR) << "ContextResult::kFatalFailure: " << (message); \ | |
| return gpu::ContextResult::kFatalFailure; \ | |
| } | |
| FAIL_INIT_IF_NOT(feature_info_->feature_flags().angle_robust_client_memory, | |
| "missing GL_ANGLE_robust_client_memory"); | |
| FAIL_INIT_IF_NOT( | |
| feature_info_->feature_flags().chromium_bind_generates_resource, | |
| "missing GL_CHROMIUM_bind_generates_resource"); | |
| FAIL_INIT_IF_NOT(feature_info_->feature_flags().chromium_copy_texture, | |
| "missing GL_CHROMIUM_copy_texture"); | |
| FAIL_INIT_IF_NOT(feature_info_->feature_flags().angle_client_arrays, | |
| "missing GL_ANGLE_client_arrays"); | |
| FAIL_INIT_IF_NOT(api()->glIsEnabledFn(GL_CLIENT_ARRAYS_ANGLE) == GL_FALSE, | |
| "GL_ANGLE_client_arrays shouldn't be enabled"); | |
| FAIL_INIT_IF_NOT(feature_info_->feature_flags().angle_webgl_compatibility == | |
| IsWebGLContextType(context_type), | |
| "missing GL_ANGLE_webgl_compatibility"); | |
| FAIL_INIT_IF_NOT(feature_info_->feature_flags().angle_request_extension, | |
| "missing GL_ANGLE_request_extension"); | |
| FAIL_INIT_IF_NOT(feature_info_->feature_flags().khr_debug, | |
| "missing GL_KHR_debug"); | |
| #undef FAIL_INIT_IF_NOT | |
| if (feature_info_->feature_flags().angle_blob_cache) { | |
| api()->glBlobCacheCallbacksANGLEFn(PassthroughGLBlobCacheSetCallback, | |
| PassthroughGLBlobCacheGetCallback, this); | |
| } | |
| resources_ = group_->passthrough_resources(); | |
| // Query information about the texture units | |
| GLint num_texture_units = 0; | |
| api()->glGetIntegervFn(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, | |
| &num_texture_units); | |
| if (num_texture_units > static_cast<GLint>(kMaxTextureUnits)) { | |
| Destroy(true); | |
| LOG(ERROR) << "kMaxTextureUnits (" << kMaxTextureUnits | |
| << ") must be at least GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS (" | |
| << num_texture_units << ")."; | |
| return gpu::ContextResult::kFatalFailure; | |
| } | |
| active_texture_unit_ = 0; | |
| // Initialize the tracked buffer bindings | |
| bound_buffers_[GL_ARRAY_BUFFER] = 0; | |
| bound_buffers_[GL_ELEMENT_ARRAY_BUFFER] = 0; | |
| if (feature_info_->gl_version_info().IsAtLeastGLES(3, 0) || | |
| feature_info_->feature_flags().ext_pixel_buffer_object) { | |
| bound_buffers_[GL_PIXEL_PACK_BUFFER] = 0; | |
| bound_buffers_[GL_PIXEL_UNPACK_BUFFER] = 0; | |
| } | |
| if (feature_info_->gl_version_info().IsAtLeastGLES(3, 0)) { | |
| bound_buffers_[GL_COPY_READ_BUFFER] = 0; | |
| bound_buffers_[GL_COPY_WRITE_BUFFER] = 0; | |
| bound_buffers_[GL_TRANSFORM_FEEDBACK_BUFFER] = 0; | |
| bound_buffers_[GL_UNIFORM_BUFFER] = 0; | |
| } | |
| bound_element_array_buffer_dirty_ = false; | |
| lose_context_when_out_of_memory_ = lose_context_when_out_of_memory; | |
| GLint max_2d_texture_size = 0; | |
| api()->glGetIntegervFn(GL_MAX_TEXTURE_SIZE, &max_2d_texture_size); | |
| api()->glGetIntegervFn(GL_MAX_RENDERBUFFER_SIZE, &max_renderbuffer_size_); | |
| max_offscreen_framebuffer_size_ = | |
| std::min(max_2d_texture_size, max_renderbuffer_size_); | |
| if (offscreen_) { | |
| emulated_default_framebuffer_format_ = GL_RGB; | |
| CheckErrorCallbackState(); | |
| emulated_back_buffer_ = std::make_unique<EmulatedDefaultFramebuffer>(this); | |
| // Some ChromeOS platforms (particularly MediaTek devices), there are driver | |
| // limitations on the minimum size of a buffer. Thus, we set the initial | |
| // size to 64x64 here instead of 1x1. | |
| gfx::Size initial_size(64, 64); | |
| if (!emulated_back_buffer_->Initialize(initial_size)) { | |
| bool was_lost = CheckResetStatus(); | |
| Destroy(true); | |
| LOG(ERROR) << (was_lost ? "ContextResult::kTransientFailure: " | |
| : "ContextResult::kFatalFailure: ") | |
| << "Resize of emulated back buffer failed"; | |
| return was_lost ? gpu::ContextResult::kTransientFailure | |
| : gpu::ContextResult::kFatalFailure; | |
| } | |
| if (CheckErrorCallbackState()) { | |
| Destroy(true); | |
| // Errors are considered fatal, including OOM. | |
| LOG(ERROR) | |
| << "ContextResult::kFatalFailure: " | |
| "Creation of the offscreen framebuffer failed because errors were " | |
| "generated."; | |
| return gpu::ContextResult::kFatalFailure; | |
| } | |
| framebuffer_id_map_.SetIDMapping( | |
| 0, emulated_back_buffer_->framebuffer_service_id); | |
| // Bind the emulated default framebuffer and initialize the viewport | |
| api()->glBindFramebufferEXTFn( | |
| GL_FRAMEBUFFER, emulated_back_buffer_->framebuffer_service_id); | |
| api()->glViewportFn(0, 0, initial_size.width(), initial_size.height()); | |
| } | |
| #if BUILDFLAG(IS_MAC) | |
| // On mac we need the ANGLE_texture_rectangle extension to support IOSurface | |
| // backbuffers, but we don't want it exposed to WebGL user shaders. This | |
| // disables support for it in the shader compiler. We then enable it | |
| // temporarily when necessary; see | |
| // ScopedEnableTextureRectangleInShaderCompiler. | |
| if (feature_info_->IsWebGLContext()) | |
| api()->glDisableFn(GL_TEXTURE_RECTANGLE_ANGLE); | |
| #endif | |
| // TEMPORARY: Set primitive restart to enabled by default for WebGL2. Clear | |
| // errors afterwards so that when this state is initialized and validated in | |
| // ANGLE, it will not generate errors during command buffer initialization. | |
| if (feature_info_->context_type() == CONTEXT_TYPE_WEBGL2) { | |
| // If WebGL 2, the PRIMITIVE_RESTART_FIXED_INDEX should be always enabled. | |
| // See the section <Primitive Restart is Always Enabled> in WebGL 2 spec: | |
| // https://www.khronos.org/registry/webgl/specs/latest/2.0/#4.1.4 | |
| api()->glEnableFn(GL_PRIMITIVE_RESTART_FIXED_INDEX); | |
| CheckErrorCallbackState(); | |
| } | |
| // Deprecation warning for SwiftShader WebGL fallback | |
| if (feature_info_->IsWebGLContext() && | |
| gl::GetANGLEImplementation() == gl::ANGLEImplementation::kSwiftShader && | |
| !base::CommandLine::ForCurrentProcess()->HasSwitch( | |
| switches::kEnableUnsafeSwiftShader)) { | |
| constexpr const char* kSwiftShaderFallbackDeprcationMessage = | |
| "Automatic fallback to software WebGL has been deprecated. Please use " | |
| "the --enable-unsafe-swiftshader " | |
| "flag to opt in to lower " | |
| "security guarantees for trusted content."; | |
| logger_.LogMessage(__FILE__, __LINE__, | |
| kSwiftShaderFallbackDeprcationMessage); | |
| } | |
| set_initialized(); | |
| return gpu::ContextResult::kSuccess; | |
| } | |
| void GLES2DecoderPassthroughImpl::Destroy(bool have_context) { | |
| idle_trim_cb_.Cancel(); | |
| if (have_context && feature_info_->feature_flags().angle_blob_cache) { | |
| api()->glBlobCacheCallbacksANGLEFn(nullptr, nullptr, nullptr); | |
| } | |
| if (have_context) { | |
| FlushErrors(); | |
| } | |
| // Destroy all pending read pixels operations | |
| for (PendingReadPixels& pending_read_pixels : pending_read_pixels_) { | |
| if (have_context) { | |
| api()->glDeleteBuffersARBFn(1, &pending_read_pixels.buffer_service_id); | |
| } else { | |
| pending_read_pixels.fence->Invalidate(); | |
| } | |
| } | |
| pending_read_pixels_.clear(); | |
| for (auto& bound_texture_type : bound_textures_) { | |
| for (auto& bound_texture : bound_texture_type) { | |
| if (!have_context && bound_texture.texture) { | |
| bound_texture.texture->MarkContextLost(); | |
| } | |
| bound_texture.texture = nullptr; | |
| } | |
| } | |
| for (PendingQuery& pending_query : pending_queries_) { | |
| if (!have_context) { | |
| if (pending_query.commands_completed_fence) { | |
| pending_query.commands_completed_fence->Invalidate(); | |
| } | |
| if (pending_query.buffer_shadow_update_fence) { | |
| pending_query.buffer_shadow_update_fence->Invalidate(); | |
| } | |
| } | |
| } | |
| pending_queries_.clear(); | |
| for (PendingReadPixels& pending_read_pixels : pending_read_pixels_) { | |
| if (!have_context) { | |
| if (pending_read_pixels.fence) { | |
| pending_read_pixels.fence->Invalidate(); | |
| } | |
| } | |
| } | |
| pending_read_pixels_.clear(); | |
| DeleteServiceObjects(&framebuffer_id_map_, have_context, | |
| [this](GLuint client_id, GLuint framebuffer) { | |
| api()->glDeleteFramebuffersEXTFn(1, &framebuffer); | |
| }); | |
| DeleteServiceObjects(&transform_feedback_id_map_, have_context, | |
| [this](GLuint client_id, GLuint transform_feedback) { | |
| api()->glDeleteTransformFeedbacksFn( | |
| 1, &transform_feedback); | |
| }); | |
| DeleteServiceObjects( | |
| &query_id_map_, have_context, [this](GLuint client_id, GLuint query) { | |
| // glDeleteQueries is not loaded unless GL_EXT_occlusion_query_boolean | |
| // is present. All queries must be emulated so they don't need to be | |
| // deleted. | |
| if (feature_info_->feature_flags().occlusion_query_boolean) { | |
| api()->glDeleteQueriesFn(1, &query); | |
| } | |
| }); | |
| DeleteServiceObjects(&vertex_array_id_map_, have_context, | |
| [this](GLuint client_id, GLuint vertex_array) { | |
| api()->glDeleteVertexArraysOESFn(1, &vertex_array); | |
| }); | |
| // Destroy the emulated backbuffer | |
| if (emulated_back_buffer_) { | |
| emulated_back_buffer_->Destroy(have_context); | |
| emulated_back_buffer_.reset(); | |
| } | |
| if (gpu_fence_manager_.get()) { | |
| gpu_fence_manager_->Destroy(have_context); | |
| gpu_fence_manager_.reset(); | |
| } | |
| // Destroy the GPU Tracer which may own some in process GPU Timings. | |
| if (gpu_tracer_) { | |
| gpu_tracer_->Destroy(have_context); | |
| gpu_tracer_.reset(); | |
| } | |
| if (multi_draw_manager_.get()) { | |
| multi_draw_manager_.reset(); | |
| } | |
| if (!have_context) { | |
| for (auto& fence : deschedule_until_finished_fences_) { | |
| fence->Invalidate(); | |
| } | |
| } | |
| deschedule_until_finished_fences_.clear(); | |
| // Destroy the surface before the context, some surface destructors make GL | |
| // calls. | |
| surface_ = nullptr; | |
| if (group_) { | |
| // `resources_` depends on `group_`. It must be cleared before we set | |
| // `group_` to nullptr. | |
| resources_ = nullptr; | |
| group_->Destroy(this, have_context); | |
| group_ = nullptr; | |
| } | |
| if (have_context) { | |
| api()->glDebugMessageCallbackKHRFn(nullptr, nullptr); | |
| } | |
| if (context_.get()) { | |
| context_->ReleaseCurrent(nullptr); | |
| //`api_` might depend on `context_`. It must be cleared before we set | |
| //`context_` to nullptr. | |
| api_ = nullptr; | |
| context_ = nullptr; | |
| } | |
| } | |
| void GLES2DecoderPassthroughImpl::SetSurface( | |
| const scoped_refptr<gl::GLSurface>& surface) { | |
| DCHECK(context_->IsCurrent(nullptr)); | |
| DCHECK(surface_.get()); | |
| surface_ = surface; | |
| } | |
| void GLES2DecoderPassthroughImpl::ReleaseSurface() { | |
| if (!context_.get()) | |
| return; | |
| if (WasContextLost()) { | |
| DLOG(ERROR) << " GLES2DecoderImpl: Trying to release lost context."; | |
| return; | |
| } | |
| context_->ReleaseCurrent(surface_.get()); | |
| surface_ = nullptr; | |
| } | |
| bool GLES2DecoderPassthroughImpl::MakeCurrent() { | |
| if (!context_.get()) | |
| return false; | |
| if (WasContextLost()) { | |
| LOG(ERROR) << " GLES2DecoderPassthroughImpl: Trying to make lost context " | |
| "current."; | |
| return false; | |
| } | |
| if (!context_->MakeCurrent(surface_.get())) { | |
| LOG(ERROR) | |
| << " GLES2DecoderPassthroughImpl: Context lost during MakeCurrent."; | |
| MarkContextLost(error::kMakeCurrentFailed); | |
| group_->LoseContexts(error::kUnknown); | |
| return false; | |
| } | |
| DCHECK_EQ(api(), gl::g_current_gl_context); | |
| if (CheckResetStatus()) { | |
| LOG(ERROR) << " GLES2DecoderPassthroughImpl: Context reset detected after " | |
| "MakeCurrent."; | |
| group_->LoseContexts(error::kUnknown); | |
| return false; | |
| } | |
| ProcessReadPixels(false); | |
| ProcessQueries(false); | |
| return true; | |
| } | |
| gpu::gles2::GLES2Util* GLES2DecoderPassthroughImpl::GetGLES2Util() { | |
| return nullptr; | |
| } | |
| gl::GLContext* GLES2DecoderPassthroughImpl::GetGLContext() { | |
| return context_.get(); | |
| } | |
| gl::GLSurface* GLES2DecoderPassthroughImpl::GetGLSurface() { | |
| return surface_.get(); | |
| } | |
| gpu::gles2::ContextGroup* GLES2DecoderPassthroughImpl::GetContextGroup() { | |
| return group_.get(); | |
| } | |
| const FeatureInfo* GLES2DecoderPassthroughImpl::GetFeatureInfo() const { | |
| return group_->feature_info(); | |
| } | |
| gpu::Capabilities GLES2DecoderPassthroughImpl::GetCapabilities() { | |
| DCHECK(initialized()); | |
| Capabilities caps; | |
| PopulateNumericCapabilities(&caps, feature_info_.get()); | |
| caps.egl_image_external = | |
| feature_info_->feature_flags().oes_egl_image_external; | |
| caps.egl_image_external_essl3 = | |
| feature_info_->feature_flags().oes_egl_image_external_essl3; | |
| caps.texture_format_bgra8888 = | |
| feature_info_->feature_flags().ext_texture_format_bgra8888; | |
| caps.disable_mac_swangle_rgbx = | |
| feature_info_->feature_flags().disable_mac_swangle_rgbx; | |
| caps.texture_format_etc1_npot = | |
| feature_info_->feature_flags().oes_compressed_etc1_rgb8_texture && | |
| !feature_info_->workarounds().etc1_power_of_two_only; | |
| caps.sync_query = feature_info_->feature_flags().chromium_sync_query; | |
| caps.texture_rg = feature_info_->feature_flags().ext_texture_rg; | |
| caps.texture_norm16 = feature_info_->feature_flags().ext_texture_norm16; | |
| caps.texture_half_float_linear = | |
| feature_info_->feature_flags().enable_texture_half_float_linear; | |
| caps.image_ar30 = feature_info_->feature_flags().chromium_image_ar30; | |
| caps.image_ab30 = feature_info_->feature_flags().chromium_image_ab30; | |
| if (feature_info_->workarounds().webgl_or_caps_max_texture_size) { | |
| caps.max_texture_size = | |
| std::min(caps.max_texture_size, | |
| feature_info_->workarounds().webgl_or_caps_max_texture_size); | |
| } | |
| caps.render_buffer_format_bgra8888 = | |
| feature_info_->feature_flags().ext_render_buffer_format_bgra8888; | |
| caps.msaa_is_slow = MSAAIsSlow(feature_info_->workarounds()); | |
| caps.avoid_stencil_buffers = | |
| feature_info_->workarounds().avoid_stencil_buffers; | |
| // Technically, YUV readback is handled on the client side, but enable it | |
| // here so that clients can use this to detect support. | |
| caps.supports_yuv_readback = true; | |
| caps.mesa_framebuffer_flip_y = | |
| feature_info_->feature_flags().mesa_framebuffer_flip_y; | |
| #if BUILDFLAG(IS_CHROMEOS) | |
| PopulateDRMCapabilities(&caps, feature_info_.get()); | |
| #endif | |
| return caps; | |
| } | |
| gpu::GLCapabilities GLES2DecoderPassthroughImpl::GetGLCapabilities() { | |
| CHECK(initialized()); | |
| GLCapabilities caps; | |
| PopulateGLCapabilities(&caps, feature_info_.get()); | |
| caps.occlusion_query_boolean = | |
| feature_info_->feature_flags().occlusion_query_boolean; | |
| caps.timer_queries = feature_info_->feature_flags().ext_disjoint_timer_query; | |
| if (feature_info_->workarounds().webgl_or_caps_max_texture_size) { | |
| caps.max_texture_size = | |
| std::min(caps.max_texture_size, | |
| feature_info_->workarounds().webgl_or_caps_max_texture_size); | |
| } | |
| caps.sync_query = feature_info_->feature_flags().chromium_sync_query; | |
| return caps; | |
| } | |
| void GLES2DecoderPassthroughImpl::RestoreState(const ContextState* prev_state) { | |
| } | |
| void GLES2DecoderPassthroughImpl::RestoreActiveTexture() const {} | |
| void GLES2DecoderPassthroughImpl::RestoreAllTextureUnitAndSamplerBindings( | |
| const ContextState* prev_state) const {} | |
| void GLES2DecoderPassthroughImpl::RestoreActiveTextureUnitBinding( | |
| unsigned int target) const {} | |
| void GLES2DecoderPassthroughImpl::RestoreBufferBinding(unsigned int target) {} | |
| void GLES2DecoderPassthroughImpl::RestoreBufferBindings() const {} | |
| void GLES2DecoderPassthroughImpl::RestoreFramebufferBindings() const {} | |
| void GLES2DecoderPassthroughImpl::RestoreRenderbufferBindings() {} | |
| void GLES2DecoderPassthroughImpl::RestoreGlobalState() const {} | |
| void GLES2DecoderPassthroughImpl::RestoreProgramBindings() const {} | |
| void GLES2DecoderPassthroughImpl::RestoreTextureState(unsigned service_id) {} | |
| void GLES2DecoderPassthroughImpl::RestoreTextureUnitBindings( | |
| unsigned unit) const {} | |
| void GLES2DecoderPassthroughImpl::RestoreVertexAttribArray(unsigned index) {} | |
| void GLES2DecoderPassthroughImpl::RestoreAllExternalTextureBindingsIfNeeded() {} | |
| void GLES2DecoderPassthroughImpl::RestoreDeviceWindowRectangles() const {} | |
| void GLES2DecoderPassthroughImpl::ClearAllAttributes() const {} | |
| void GLES2DecoderPassthroughImpl::RestoreAllAttributes() const {} | |
| void GLES2DecoderPassthroughImpl::SetIgnoreCachedStateForTest(bool ignore) {} | |
| void GLES2DecoderPassthroughImpl::SetForceShaderNameHashingForTest(bool force) { | |
| } | |
| gpu::QueryManager* GLES2DecoderPassthroughImpl::GetQueryManager() { | |
| return nullptr; | |
| } | |
| void GLES2DecoderPassthroughImpl::SetQueryCallback(unsigned int query_client_id, | |
| base::OnceClosure callback) { | |
| GLuint service_id = query_id_map_.GetServiceIDOrInvalid(query_client_id); | |
| for (auto& pending_query : pending_queries_) { | |
| if (pending_query.service_id == service_id) { | |
| pending_query.callbacks.push_back(std::move(callback)); | |
| return; | |
| } | |
| } | |
| VLOG(1) << "GLES2DecoderPassthroughImpl::SetQueryCallback: No pending query " | |
| "with ID " | |
| << query_client_id << ". Running the callback immediately."; | |
| std::move(callback).Run(); | |
| } | |
| void GLES2DecoderPassthroughImpl::CancelAllQueries() { | |
| // clear all pending queries. | |
| pending_queries_.clear(); | |
| query_id_map_.Clear(); | |
| } | |
| gpu::gles2::GpuFenceManager* GLES2DecoderPassthroughImpl::GetGpuFenceManager() { | |
| return gpu_fence_manager_.get(); | |
| } | |
| gpu::gles2::FramebufferManager* | |
| GLES2DecoderPassthroughImpl::GetFramebufferManager() { | |
| return nullptr; | |
| } | |
| gpu::gles2::TransformFeedbackManager* | |
| GLES2DecoderPassthroughImpl::GetTransformFeedbackManager() { | |
| return nullptr; | |
| } | |
| gpu::gles2::VertexArrayManager* | |
| GLES2DecoderPassthroughImpl::GetVertexArrayManager() { | |
| return nullptr; | |
| } | |
| bool GLES2DecoderPassthroughImpl::HasPendingQueries() const { | |
| return !pending_queries_.empty(); | |
| } | |
| void GLES2DecoderPassthroughImpl::ProcessPendingQueries(bool did_finish) { | |
| // TODO(geofflang): If this returned an error, store it somewhere. | |
| ProcessQueries(did_finish); | |
| } | |
| bool GLES2DecoderPassthroughImpl::HasMoreIdleWork() const { | |
| return gpu_tracer_->HasTracesToProcess() || !pending_read_pixels_.empty(); | |
| } | |
| void GLES2DecoderPassthroughImpl::PerformIdleWork() { | |
| gpu_tracer_->ProcessTraces(); | |
| ProcessReadPixels(false); | |
| } | |
| bool GLES2DecoderPassthroughImpl::HasPollingWork() const { | |
| return deschedule_until_finished_fences_.size() >= 2; | |
| } | |
| void GLES2DecoderPassthroughImpl::PerformPollingWork() { | |
| ProcessDescheduleUntilFinished(); | |
| } | |
| bool GLES2DecoderPassthroughImpl::GetServiceTextureId( | |
| uint32_t client_texture_id, | |
| uint32_t* service_texture_id) { | |
| return resources_->texture_id_map.GetServiceID(client_texture_id, | |
| service_texture_id); | |
| } | |
| TextureBase* GLES2DecoderPassthroughImpl::GetTextureBase(uint32_t client_id) { | |
| scoped_refptr<TexturePassthrough> texture; | |
| if (resources_->texture_object_map.GetServiceID(client_id, &texture)) { | |
| return texture.get(); | |
| } else { | |
| return nullptr; | |
| } | |
| } | |
| bool GLES2DecoderPassthroughImpl::ClearLevel(Texture* texture, | |
| unsigned target, | |
| int level, | |
| unsigned format, | |
| unsigned type, | |
| int xoffset, | |
| int yoffset, | |
| int width, | |
| int height) { | |
| return true; | |
| } | |
| bool GLES2DecoderPassthroughImpl::ClearCompressedTextureLevel(Texture* texture, | |
| unsigned target, | |
| int level, | |
| unsigned format, | |
| int width, | |
| int height) { | |
| return true; | |
| } | |
| bool GLES2DecoderPassthroughImpl::ClearCompressedTextureLevel3D( | |
| Texture* texture, | |
| unsigned target, | |
| int level, | |
| unsigned format, | |
| int width, | |
| int height, | |
| int depth) { | |
| return true; | |
| } | |
| bool GLES2DecoderPassthroughImpl::IsCompressedTextureFormat(unsigned format) { | |
| return false; | |
| } | |
| bool GLES2DecoderPassthroughImpl::ClearLevel3D(Texture* texture, | |
| unsigned target, | |
| int level, | |
| unsigned format, | |
| unsigned type, | |
| int width, | |
| int height, | |
| int depth) { | |
| return true; | |
| } | |
| gpu::gles2::ErrorState* GLES2DecoderPassthroughImpl::GetErrorState() { | |
| return nullptr; | |
| } | |
| void GLES2DecoderPassthroughImpl::BindFramebuffer(unsigned target, | |
| uint32_t service_id) const { | |
| NOTREACHED(); | |
| } | |
| void GLES2DecoderPassthroughImpl::WaitForReadPixels( | |
| base::OnceClosure callback) {} | |
| bool GLES2DecoderPassthroughImpl::WasContextLost() const { | |
| return context_lost_; | |
| } | |
| bool GLES2DecoderPassthroughImpl::WasContextLostByRobustnessExtension() const { | |
| return WasContextLost() && reset_by_robustness_extension_; | |
| } | |
| void GLES2DecoderPassthroughImpl::MarkContextLost( | |
| error::ContextLostReason reason) { | |
| idle_trim_cb_.Cancel(); | |
| // Only lose the context once. | |
| if (WasContextLost()) { | |
| return; | |
| } | |
| bool have_context = context_ && context_->IsCurrent(nullptr); | |
| if (have_context) { | |
| for (auto& bound_texture_type : bound_textures_) { | |
| for (auto& bound_texture : bound_texture_type) { | |
| if (bound_texture.texture) { | |
| bound_texture.texture->MarkContextLost(); | |
| } | |
| } | |
| } | |
| if (resources_) { | |
| resources_->MarkContextLost(); | |
| } | |
| // SECURITY: crbug.com/500187083 and crbug.com/517018374. | |
| // Unconditionally clear per-context callbacks that hold a raw | |
| // `this` pointer if the context IsCurrent before it gets lost, to | |
| // prevent UAF when Destroy(have_context=false) skips them. | |
| if (api()) { | |
| api()->glDebugMessageCallbackKHRFn(nullptr, nullptr); | |
| if (feature_info_ && feature_info_->feature_flags().angle_blob_cache) { | |
| api()->glBlobCacheCallbacksANGLEFn(nullptr, nullptr, nullptr); | |
| } | |
| } | |
| } | |
| // Don't make GL calls in here, the context might not be current. | |
| command_buffer_service()->SetContextLostReason(reason); | |
| context_lost_ = true; | |
| } | |
| gpu::gles2::Logger* GLES2DecoderPassthroughImpl::GetLogger() { | |
| return &logger_; | |
| } | |
| void GLES2DecoderPassthroughImpl::BeginDecoding() { | |
| gpu_tracer_->BeginDecoding(); | |
| gpu_trace_commands_ = | |
| gpu_tracer_->IsTracing() && | |
| TRACE_EVENT_CATEGORY_ENABLED(TRACE_DISABLED_BY_DEFAULT("gpu.decoder")); | |
| gpu_debug_commands_ = log_commands() || debug() || gpu_trace_commands_; | |
| #if BUILDFLAG(IS_WIN) | |
| if (!resources_->ResumeSharedImageAccessIfNeeded(api())) { | |
| LOG(ERROR) << " GLES2DecoderPassthroughImpl: Failed to resume shared " | |
| "image access."; | |
| group_->LoseContexts(error::kUnknown); | |
| } | |
| #endif | |
| auto it = active_queries_.find(GL_COMMANDS_ISSUED_CHROMIUM); | |
| if (it != active_queries_.end()) { | |
| DCHECK_EQ(it->second.command_processing_start_time, base::TimeTicks()); | |
| it->second.command_processing_start_time = base::TimeTicks::Now(); | |
| } | |
| if (has_activated_pixel_local_storage_) { | |
| api()->glFramebufferPixelLocalStorageRestoreANGLEFn(); | |
| } | |
| } | |
| void GLES2DecoderPassthroughImpl::EndDecoding() { | |
| if (has_activated_pixel_local_storage_) { | |
| api()->glFramebufferPixelLocalStorageInterruptANGLEFn(); | |
| } | |
| #if BUILDFLAG(IS_WIN) | |
| resources_->SuspendSharedImageAccessIfNeeded(); | |
| #endif | |
| gpu_tracer_->EndDecoding(); | |
| auto it = active_queries_.find(GL_COMMANDS_ISSUED_CHROMIUM); | |
| if (it != active_queries_.end()) { | |
| DCHECK_NE(it->second.command_processing_start_time, base::TimeTicks()); | |
| it->second.active_time += | |
| (base::TimeTicks::Now() - it->second.command_processing_start_time); | |
| it->second.command_processing_start_time = base::TimeTicks(); | |
| } | |
| } | |
| const gpu::gles2::ContextState* GLES2DecoderPassthroughImpl::GetContextState() { | |
| return nullptr; | |
| } | |
| scoped_refptr<ShaderTranslatorInterface> | |
| GLES2DecoderPassthroughImpl::GetTranslator(GLenum type) { | |
| return nullptr; | |
| } | |
| void GLES2DecoderPassthroughImpl::OnDebugMessage(GLenum source, | |
| GLenum type, | |
| GLuint id, | |
| GLenum severity, | |
| GLsizei length, | |
| const GLchar* message) { | |
| if (type == GL_DEBUG_TYPE_ERROR && source == GL_DEBUG_SOURCE_API) { | |
| had_error_callback_ = true; | |
| } | |
| } | |
| GLsizeiptr GLES2DecoderPassthroughImpl::BlobCacheGet(const void* key, | |
| GLsizeiptr key_size, | |
| void* value, | |
| GLsizeiptr value_size) { | |
| PassthroughProgramCache* cache = get_passthrough_program_cache(); | |
| if (!cache) { | |
| return 0; | |
| } | |
| if (key_size < 0) { | |
| return 0; | |
| } | |
| const uint8_t* key_begin = reinterpret_cast<const uint8_t*>(key); | |
| PassthroughProgramCache::Key entry_key(key_begin, | |
| UNSAFE_TODO(key_begin + key_size)); | |
| return cache->Get(entry_key, value, value_size); | |
| } | |
| void GLES2DecoderPassthroughImpl::BlobCacheSet(const void* key, | |
| GLsizeiptr key_size, | |
| const void* value, | |
| GLsizeiptr value_size) { | |
| PassthroughProgramCache* cache = get_passthrough_program_cache(); | |
| if (!cache) { | |
| return; | |
| } | |
| if (key_size < 0 || value_size < 0) { | |
| return; | |
| } | |
| const uint8_t* key_begin = reinterpret_cast<const uint8_t*>(key); | |
| PassthroughProgramCache::Key entry_key(key_begin, | |
| UNSAFE_TODO(key_begin + key_size)); | |
| const uint8_t* value_begin = reinterpret_cast<const uint8_t*>(value); | |
| PassthroughProgramCache::Value entry_value( | |
| value_begin, UNSAFE_TODO(value_begin + value_size)); | |
| cache->Set( | |
| std::move(entry_key), std::move(entry_value), | |
| base::BindRepeating(&DecoderClient::CacheBlob, base::Unretained(client()), | |
| gpu::GpuDiskCacheType::kGlShaders)); | |
| } | |
| void GLES2DecoderPassthroughImpl::SetCopyTextureResourceManagerForTest( | |
| CopyTextureCHROMIUMResourceManager* copy_texture_resource_manager) { | |
| NOTIMPLEMENTED(); | |
| } | |
| void GLES2DecoderPassthroughImpl::SetCopyTexImageBlitterForTest( | |
| CopyTexImageResourceManager* copy_tex_image_blit) { | |
| NOTIMPLEMENTED(); | |
| } | |
| const char* GLES2DecoderPassthroughImpl::GetCommandName( | |
| unsigned int command_id) const { | |
| if (command_id >= kFirstGLES2Command && command_id < kNumCommands) { | |
| return gles2::GetCommandName(static_cast<CommandId>(command_id)); | |
| } | |
| return GetCommonCommandName(static_cast<cmd::CommandId>(command_id)); | |
| } | |
| void GLES2DecoderPassthroughImpl::SetOptionalExtensionsRequestedForTesting( | |
| bool request_extensions) { | |
| request_optional_extensions_ = request_extensions; | |
| } | |
| template <typename T> | |
| error::Error GLES2DecoderPassthroughImpl::PatchGetNumericResults(GLenum pname, | |
| GLsizei length, | |
| T* params) { | |
| // Likely a gl error if no parameters were returned | |
| if (length < 1) { | |
| return error::kNoError; | |
| } | |
| switch (pname) { | |
| case GL_NUM_EXTENSIONS: | |
| // Currently handled on the client side. | |
| params[0] = 0; | |
| break; | |
| case GL_TEXTURE_BINDING_2D: | |
| case GL_TEXTURE_BINDING_CUBE_MAP: | |
| case GL_TEXTURE_BINDING_2D_ARRAY: | |
| case GL_TEXTURE_BINDING_3D: | |
| if (*params != 0 && | |
| !GetClientID(&resources_->texture_id_map, *params, params)) { | |
| return error::kInvalidArguments; | |
| } | |
| break; | |
| case GL_ARRAY_BUFFER_BINDING: | |
| case GL_ELEMENT_ARRAY_BUFFER_BINDING: | |
| case GL_PIXEL_PACK_BUFFER_BINDING: | |
| case GL_PIXEL_UNPACK_BUFFER_BINDING: | |
| case GL_TRANSFORM_FEEDBACK_BUFFER_BINDING: | |
| case GL_COPY_READ_BUFFER_BINDING: | |
| case GL_COPY_WRITE_BUFFER_BINDING: | |
| case GL_UNIFORM_BUFFER_BINDING: | |
| if (*params != 0 && | |
| !GetClientID(&resources_->buffer_id_map, *params, params)) { | |
| return error::kInvalidArguments; | |
| } | |
| break; | |
| case GL_RENDERBUFFER_BINDING: | |
| if (*params != 0 && | |
| !GetClientID(&resources_->renderbuffer_id_map, *params, params)) { | |
| return error::kInvalidArguments; | |
| } | |
| break; | |
| case GL_SAMPLER_BINDING: | |
| if (*params != 0 && | |
| !GetClientID(&resources_->sampler_id_map, *params, params)) { | |
| return error::kInvalidArguments; | |
| } | |
| break; | |
| case GL_ACTIVE_PROGRAM: | |
| if (*params != 0 && | |
| !GetClientID(&resources_->program_id_map, *params, params)) { | |
| return error::kInvalidArguments; | |
| } | |
| break; | |
| case GL_FRAMEBUFFER_BINDING: | |
| case GL_READ_FRAMEBUFFER_BINDING: | |
| if (*params != 0 && !GetClientID(&framebuffer_id_map_, *params, params)) { | |
| return error::kInvalidArguments; | |
| } | |
| break; | |
| case GL_TRANSFORM_FEEDBACK_BINDING: | |
| if (*params != 0 && | |
| !GetClientID(&transform_feedback_id_map_, *params, params)) { | |
| return error::kInvalidArguments; | |
| } | |
| break; | |
| case GL_VERTEX_ARRAY_BINDING: | |
| if (*params != 0 && | |
| !GetClientID(&vertex_array_id_map_, *params, params)) { | |
| return error::kInvalidArguments; | |
| } | |
| break; | |
| case GL_MAX_PIXEL_LOCAL_STORAGE_PLANES_ANGLE: | |
| // Impose an upper bound on the number ANGLE_shader_pixel_local_storage | |
| // planes so we can stack-allocate load/store ops. | |
| *params = std::min<T>(*params, kPassthroughMaxPLSPlanes); | |
| break; | |
| default: | |
| break; | |
| } | |
| return error::kNoError; | |
| } | |
| // Instantiate templated functions | |
| #define INSTANTIATE_PATCH_NUMERIC_RESULTS(type) \ | |
| template error::Error GLES2DecoderPassthroughImpl::PatchGetNumericResults( \ | |
| GLenum, GLsizei, type*) | |
| INSTANTIATE_PATCH_NUMERIC_RESULTS(GLint); | |
| INSTANTIATE_PATCH_NUMERIC_RESULTS(GLint64); | |
| INSTANTIATE_PATCH_NUMERIC_RESULTS(GLfloat); | |
| INSTANTIATE_PATCH_NUMERIC_RESULTS(GLboolean); | |
| #undef INSTANTIATE_PATCH_NUMERIC_RESULTS | |
| error::Error GLES2DecoderPassthroughImpl:: | |
| PatchGetFramebufferPixelLocalStorageParameterivANGLE(GLint plane, | |
| GLenum pname, | |
| GLsizei length, | |
| GLint* params) { | |
| // Likely a gl error if no parameters were returned | |
| if (length < 1) { | |
| return error::kNoError; | |
| } | |
| switch (pname) { | |
| case GL_PIXEL_LOCAL_TEXTURE_NAME_ANGLE: | |
| if (*params != 0 && | |
| !GetClientID(&resources_->texture_id_map, *params, params)) { | |
| return error::kInvalidArguments; | |
| } | |
| break; | |
| } | |
| return error::kNoError; | |
| } | |
| error::Error GLES2DecoderPassthroughImpl:: | |
| PatchGetFramebufferPixelLocalStorageParameteruivANGLE(GLint plane, | |
| GLenum pname, | |
| GLsizei length, | |
| GLuint* params) { | |
| // Likely a gl error if no parameters were returned | |
| if (length < 1) { | |
| return error::kNoError; | |
| } | |
| switch (pname) { | |
| case GL_PIXEL_LOCAL_TEXTURE_NAME_ANGLE: | |
| if (*params != 0 && | |
| !GetClientID(&resources_->texture_id_map, *params, params)) { | |
| return error::kInvalidArguments; | |
| } | |
| break; | |
| } | |
| return error::kNoError; | |
| } | |
| error::Error | |
| GLES2DecoderPassthroughImpl::PatchGetFramebufferAttachmentParameter( | |
| GLenum target, | |
| GLenum attachment, | |
| GLenum pname, | |
| GLsizei length, | |
| GLint* params) { | |
| // Likely a gl error if no parameters were returned | |
| if (length < 1) { | |
| return error::kNoError; | |
| } | |
| switch (pname) { | |
| // If the attached object name was requested, it needs to be converted back | |
| // to a client id. | |
| case GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME: { | |
| GLint object_type = GL_NONE; | |
| api()->glGetFramebufferAttachmentParameterivEXTFn( | |
| target, attachment, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, | |
| &object_type); | |
| switch (object_type) { | |
| case GL_TEXTURE: | |
| if (!GetClientID(&resources_->texture_id_map, *params, params)) { | |
| return error::kInvalidArguments; | |
| } | |
| break; | |
| case GL_RENDERBUFFER: | |
| if (!GetClientID(&resources_->renderbuffer_id_map, *params, params)) { | |
| return error::kInvalidArguments; | |
| } | |
| break; | |
| case GL_NONE: | |
| // Default framebuffer, don't transform the result | |
| break; | |
| default: | |
| NOTREACHED(); | |
| } | |
| } break; | |
| // If the framebuffer is an emulated default framebuffer, all attachment | |
| // object types are GL_FRAMEBUFFER_DEFAULT | |
| case GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE: | |
| if (IsEmulatedFramebufferBound(target)) { | |
| *params = GL_FRAMEBUFFER_DEFAULT; | |
| } | |
| break; | |
| default: | |
| break; | |
| } | |
| return error::kNoError; | |
| } | |
| // static | |
| std::unique_ptr<GLES2DecoderPassthroughImpl::LazySharedContextState> | |
| GLES2DecoderPassthroughImpl::LazySharedContextState::Create( | |
| GLES2DecoderPassthroughImpl* impl) { | |
| auto context = | |
| std::make_unique<GLES2DecoderPassthroughImpl::LazySharedContextState>( | |
| impl); | |
| if (!context->Initialize()) { | |
| return nullptr; | |
| } | |
| return context; | |
| } | |
| GLES2DecoderPassthroughImpl::LazySharedContextState::LazySharedContextState( | |
| GLES2DecoderPassthroughImpl* impl) | |
| : impl_(impl) {} | |
| GLES2DecoderPassthroughImpl::LazySharedContextState::~LazySharedContextState() { | |
| if (shared_context_state_) { | |
| ScopedSharedContextCurrent scoped_current(impl_, | |
| shared_context_state_->context(), | |
| shared_context_state_->surface()); | |
| shared_context_state_.reset(); | |
| } | |
| } | |
| bool GLES2DecoderPassthroughImpl::LazySharedContextState::Initialize() { | |
| auto gl_surface = gl::init::CreateOffscreenGLSurface( | |
| impl_->context_->GetGLDisplayEGL(), gfx::Size()); | |
| if (!gl_surface) { | |
| impl_->InsertError( | |
| GL_INVALID_OPERATION, | |
| "ContextResult::kFatalFailure: Failed to create GL Surface " | |
| "for SharedContextState"); | |
| return false; | |
| } | |
| gl::GLContextAttribs attribs; | |
| attribs.global_texture_share_group = true; | |
| attribs.global_semaphore_share_group = true; | |
| attribs.robust_resource_initialization = true; | |
| attribs.robust_buffer_access = true; | |
| attribs.allow_client_arrays = false; | |
| attribs.allow_es_version_fallback = true; | |
| auto gl_context = gl::init::CreateGLContext(impl_->context_->share_group(), | |
| gl_surface.get(), attribs); | |
| if (!gl_context) { | |
| impl_->InsertError( | |
| GL_INVALID_OPERATION, | |
| "ContextResult::kFatalFailure: Failed to create GL Context " | |
| "for SharedContextState"); | |
| return false; | |
| } | |
| // Make current context using `gl_context` and `gl_surface` | |
| ScopedSharedContextCurrent scoped_current(impl_, gl_context.get(), | |
| gl_surface.get()); | |
| if (!scoped_current.IsContextCurrent()) { | |
| impl_->InsertError( | |
| GL_INVALID_OPERATION, | |
| "ContextResult::kFatalFailure: Failed to make SharedContextState " | |
| "context current"); | |
| return false; | |
| } | |
| ContextGroup* group = impl_->GetContextGroup(); | |
| const GpuPreferences& gpu_preferences = group->gpu_preferences(); | |
| const GpuDriverBugWorkarounds& workarounds = | |
| group->feature_info()->workarounds(); | |
| // TODO(crbug.com/40064510): Add copying shared image to GL Texture support | |
| // within Graphite. | |
| shared_context_state_ = base::MakeRefCounted<SharedContextState>( | |
| impl_->context_->share_group(), std::move(gl_surface), | |
| std::move(gl_context), | |
| /*use_virtualized_gl_contexts=*/false, base::DoNothing(), | |
| GrContextType::kGL); | |
| if (!shared_context_state_->InitializeGL(gpu_preferences, workarounds, | |
| group->gpu_feature_info())) { | |
| impl_->InsertError(GL_INVALID_OPERATION, | |
| "ContextResult::kFatalFailure: Failed to Initialize GL " | |
| "for SharedContextState"); | |
| return false; | |
| } | |
| if (!shared_context_state_->InitializeSkia(gpu_preferences, workarounds, | |
| /*cache=*/nullptr)) { | |
| impl_->InsertError(GL_INVALID_OPERATION, | |
| "ContextResult::kFatalFailure: Failed to Initialize " | |
| "Skia for SharedContextState"); | |
| return false; | |
| } | |
| return true; | |
| } | |
| void GLES2DecoderPassthroughImpl::InsertError(GLenum error, | |
| const std::string& message) { | |
| errors_.insert(error); | |
| LogGLDebugMessage(GL_DEBUG_SOURCE_API, GL_DEBUG_TYPE_ERROR, error, | |
| GL_DEBUG_SEVERITY_HIGH, message.length(), message.c_str(), | |
| GetLogger()); | |
| } | |
| GLenum GLES2DecoderPassthroughImpl::PopError() { | |
| GLenum error = GL_NO_ERROR; | |
| if (!errors_.empty()) { | |
| error = *errors_.begin(); | |
| errors_.erase(errors_.begin()); | |
| } | |
| return error; | |
| } | |
| bool GLES2DecoderPassthroughImpl::FlushErrors() { | |
| bool had_error = false; | |
| GLenum error = glGetError(); | |
| while (error != GL_NO_ERROR) { | |
| errors_.insert(error); | |
| had_error = true; | |
| // Check for context loss on out-of-memory errors | |
| if (error == GL_OUT_OF_MEMORY && !WasContextLost() && | |
| lose_context_when_out_of_memory_) { | |
| error::ContextLostReason other = error::kOutOfMemory; | |
| if (CheckResetStatus()) { | |
| other = error::kUnknown; | |
| } else { | |
| // Need to lose current context before broadcasting! | |
| MarkContextLost(error::kOutOfMemory); | |
| } | |
| group_->LoseContexts(other); | |
| break; | |
| } | |
| error = glGetError(); | |
| } | |
| return had_error; | |
| } | |
| bool GLES2DecoderPassthroughImpl::IsIgnoredCap(GLenum cap) const { | |
| switch (cap) { | |
| case GL_DEBUG_OUTPUT: | |
| return true; | |
| case GL_PRIMITIVE_RESTART_FIXED_INDEX: | |
| // Disable setting primitive restart at the command decoder level until | |
| // it's blocked in ANGLE for WebGL contexts. | |
| return feature_info_->IsWebGLContext(); | |
| case GL_TEXTURE_RECTANGLE_ANGLE: | |
| // Used internally, not exposed to WebGL contexts. | |
| return feature_info_->IsWebGLContext(); | |
| default: | |
| return false; | |
| } | |
| } | |
| bool GLES2DecoderPassthroughImpl::CheckResetStatus() { | |
| DCHECK(!WasContextLost()); | |
| DCHECK(context_->IsCurrent(nullptr)); | |
| // If the reason for the call was a GL error, we can try to determine the | |
| // reset status more accurately. | |
| GLenum driver_status = context_->CheckStickyGraphicsResetStatus(); | |
| if (driver_status == GL_NO_ERROR) { | |
| return false; | |
| } | |
| switch (driver_status) { | |
| case GL_GUILTY_CONTEXT_RESET: | |
| MarkContextLost(error::kGuilty); | |
| break; | |
| case GL_INNOCENT_CONTEXT_RESET: | |
| MarkContextLost(error::kInnocent); | |
| break; | |
| case GL_UNKNOWN_CONTEXT_RESET: | |
| MarkContextLost(error::kUnknown); | |
| break; | |
| default: | |
| NOTREACHED(); | |
| } | |
| reset_by_robustness_extension_ = true; | |
| return true; | |
| } | |
| bool GLES2DecoderPassthroughImpl::IsEmulatedQueryTarget(GLenum target) const { | |
| switch (target) { | |
| case GL_COMMANDS_COMPLETED_CHROMIUM: | |
| case GL_READBACK_SHADOW_COPIES_UPDATED_CHROMIUM: | |
| case GL_COMMANDS_ISSUED_CHROMIUM: | |
| case GL_ASYNC_PIXEL_PACK_COMPLETED_CHROMIUM: | |
| case GL_GET_ERROR_QUERY_CHROMIUM: | |
| case GL_PROGRAM_COMPLETION_QUERY_CHROMIUM: | |
| return true; | |
| default: | |
| return false; | |
| } | |
| } | |
| bool GLES2DecoderPassthroughImpl::OnlyHasPendingProgramCompletionQueries() { | |
| return std::ranges::all_of(pending_queries_, [](const auto& query) { | |
| return query.target == GL_PROGRAM_COMPLETION_QUERY_CHROMIUM; | |
| }); | |
| } | |
| void GLES2DecoderPassthroughImpl::BuildRequestableExtensionString() { | |
| const gfx::ExtensionSet driver_requestable_extensions = | |
| gl::GetRequestableGLExtensionsFromCurrentContext(); | |
| // Rebuild requestable_extensions_ using static strings from | |
| // kValidRequestableExtensions. | |
| requestable_extensions_.clear(); | |
| for (const char* valid_requestable_ext : kValidRequestableExtensions) { | |
| if (driver_requestable_extensions.contains(valid_requestable_ext)) { | |
| requestable_extensions_.insert(valid_requestable_ext); | |
| } | |
| } | |
| if (base::CommandLine::ForCurrentProcess()->HasSwitch( | |
| switches::kEnableWebGLDraftExtensions)) { | |
| for (const char* valid_requestable_draft_webgl_ext : | |
| kValidRequestableWebGLDraftExtensions) { | |
| if (driver_requestable_extensions.contains( | |
| valid_requestable_draft_webgl_ext)) { | |
| requestable_extensions_.insert(valid_requestable_draft_webgl_ext); | |
| } | |
| } | |
| } | |
| requestable_extension_string_ = | |
| gfx::MakeExtensionString(requestable_extensions_); | |
| } | |
| PassthroughProgramCache* | |
| GLES2DecoderPassthroughImpl::get_passthrough_program_cache() const { | |
| return static_cast<PassthroughProgramCache*>(group_->get_program_cache()); | |
| } | |
| error::Error GLES2DecoderPassthroughImpl::ProcessQueries(bool did_finish) { | |
| bool program_completion_query_deferred = false; | |
| while (!pending_queries_.empty()) { | |
| PendingQuery& query = pending_queries_.front(); | |
| GLuint result_available = GL_FALSE; | |
| GLuint64 result = 0; | |
| switch (query.target) { | |
| case GL_COMMANDS_COMPLETED_CHROMIUM: | |
| DCHECK(query.commands_completed_fence != nullptr); | |
| // Note: |did_finish| guarantees that the GPU has passed the fence but | |
| // we cannot assume that GLFence::HasCompleted() will return true yet as | |
| // that's not guaranteed by all GLFence implementations. | |
| result_available = | |
| did_finish || query.commands_completed_fence->HasCompleted(); | |
| result = result_available; | |
| break; | |
| case GL_COMMANDS_ISSUED_CHROMIUM: | |
| result_available = GL_TRUE; | |
| result = query.commands_issued_time.InMicroseconds(); | |
| break; | |
| case GL_ASYNC_PIXEL_PACK_COMPLETED_CHROMIUM: | |
| // Initialize the result to being available. Will be marked as | |
| // unavailable if any pending read pixels operations reference this | |
| // query. | |
| result_available = GL_TRUE; | |
| result = GL_TRUE; | |
| for (const PendingReadPixels& pending_read_pixels : | |
| pending_read_pixels_) { | |
| if (pending_read_pixels.waiting_async_pack_queries.count( | |
| query.service_id) > 0) { | |
| // Async read pixel processing happens before query processing. If | |
| // there was a finish then there should be no pending read pixels. | |
| DCHECK(!did_finish); | |
| result_available = GL_FALSE; | |
| result = GL_FALSE; | |
| break; | |
| } | |
| } | |
| break; | |
| case GL_READBACK_SHADOW_COPIES_UPDATED_CHROMIUM: | |
| DCHECK(query.buffer_shadow_update_fence); | |
| if (did_finish || query.buffer_shadow_update_fence->HasCompleted()) { | |
| ReadBackBuffersIntoShadowCopies(query.buffer_shadow_updates); | |
| result_available = GL_TRUE; | |
| result = 0; | |
| } | |
| break; | |
| case GL_GET_ERROR_QUERY_CHROMIUM: | |
| result_available = GL_TRUE; | |
| FlushErrors(); | |
| result = PopError(); | |
| break; | |
| case GL_PROGRAM_COMPLETION_QUERY_CHROMIUM: | |
| GLint status; | |
| if (!api()->glIsProgramFn(query.program_service_id)) { | |
| status = GL_TRUE; | |
| } else { | |
| api()->glGetProgramivFn(query.program_service_id, | |
| GL_COMPLETION_STATUS_KHR, &status); | |
| } | |
| result_available = (status == GL_TRUE); | |
| if (!result_available) { | |
| // Move the query to the end of queue, so that other queries may have | |
| // chance to be processed. | |
| auto temp = std::move(query); | |
| pending_queries_.pop_front(); | |
| pending_queries_.emplace_back(std::move(temp)); | |
| if (did_finish && !OnlyHasPendingProgramCompletionQueries()) { | |
| continue; | |
| } else { | |
| program_completion_query_deferred = true; | |
| } | |
| result = 0; | |
| } else { | |
| GLint link_status = 0; | |
| api()->glGetProgramivFn(query.program_service_id, GL_LINK_STATUS, | |
| &link_status); | |
| result = link_status; | |
| // Send back all program information as early as possible to be cached | |
| // at the client side. | |
| GLuint program_client_id = 0u; | |
| GetClientID(&resources_->program_id_map, query.program_service_id, | |
| &program_client_id); | |
| // TODO(jie.a.chen@intel.com): Merge the all return data into 1 IPC | |
| // message if it becomes a concern. | |
| std::vector<uint8_t> program_info; | |
| error::Error error = | |
| DoGetProgramInfoCHROMIUM(program_client_id, &program_info); | |
| if (error == error::kNoError) { | |
| ReturnProgramInfoData(client(), program_info, | |
| GLES2ReturnDataType::kES2ProgramInfo, | |
| program_client_id); | |
| } | |
| if (feature_info_->IsWebGL2OrES3OrHigherContext()) { | |
| std::vector<uint8_t> uniform_blocks; | |
| error = | |
| DoGetUniformBlocksCHROMIUM(program_client_id, &uniform_blocks); | |
| if (error == error::kNoError) { | |
| ReturnProgramInfoData(client(), uniform_blocks, | |
| GLES2ReturnDataType::kES3UniformBlocks, | |
| program_client_id); | |
| } | |
| std::vector<uint8_t> transform_feedback_varyings; | |
| error = DoGetTransformFeedbackVaryingsCHROMIUM( | |
| program_client_id, &transform_feedback_varyings); | |
| if (error == error::kNoError) { | |
| ReturnProgramInfoData( | |
| client(), transform_feedback_varyings, | |
| GLES2ReturnDataType::kES3TransformFeedbackVaryings, | |
| program_client_id); | |
| } | |
| std::vector<uint8_t> uniforms; | |
| error = DoGetUniformsES3CHROMIUM(program_client_id, &uniforms); | |
| if (error == error::kNoError) { | |
| ReturnProgramInfoData(client(), uniforms, | |
| GLES2ReturnDataType::kES3Uniforms, | |
| program_client_id); | |
| } | |
| } | |
| } | |
| break; | |
| default: | |
| DCHECK(!IsEmulatedQueryTarget(query.target)); | |
| if (did_finish) { | |
| result_available = GL_TRUE; | |
| } else { | |
| api()->glGetQueryObjectuivFn( | |
| query.service_id, GL_QUERY_RESULT_AVAILABLE, &result_available); | |
| } | |
| if (result_available == GL_TRUE) { | |
| if (feature_info_->feature_flags().ext_disjoint_timer_query) { | |
| api()->glGetQueryObjectui64vFn(query.service_id, GL_QUERY_RESULT, | |
| &result); | |
| } else { | |
| GLuint temp_result = 0; | |
| api()->glGetQueryObjectuivFn(query.service_id, GL_QUERY_RESULT, | |
| &temp_result); | |
| result = temp_result; | |
| } | |
| } | |
| break; | |
| } | |
| if (!result_available) { | |
| break; | |
| } | |
| // Mark the query as complete | |
| query.sync->result = result; | |
| base::subtle::Release_Store(&query.sync->process_count, query.submit_count); | |
| pending_queries_.pop_front(); | |
| } | |
| // If api()->glFinishFn() has been called, all of our queries should be | |
| // completed. | |
| DCHECK(!did_finish || pending_queries_.empty() || | |
| program_completion_query_deferred); | |
| return error::kNoError; | |
| } | |
| void GLES2DecoderPassthroughImpl::RemovePendingQuery(GLuint service_id) { | |
| auto pending_iter = std::ranges::find(pending_queries_, service_id, | |
| &PendingQuery::service_id); | |
| if (pending_iter != pending_queries_.end()) { | |
| QuerySync* sync = pending_iter->sync; | |
| sync->result = 0; | |
| base::subtle::Release_Store(&sync->process_count, | |
| pending_iter->submit_count); | |
| pending_queries_.erase(pending_iter); | |
| } | |
| } | |
| void GLES2DecoderPassthroughImpl::ReadBackBuffersIntoShadowCopies( | |
| const BufferShadowUpdateMap& updates) { | |
| if (updates.empty()) { | |
| return; | |
| } | |
| GLint old_binding = 0; | |
| api()->glGetIntegervFn(GL_ARRAY_BUFFER_BINDING, &old_binding); | |
| for (const auto& u : updates) { | |
| GLuint client_id = u.first; | |
| GLuint service_id = 0; | |
| if (!resources_->buffer_id_map.GetServiceID(client_id, &service_id)) { | |
| // Buffer no longer exists, this shadow update should have been removed by | |
| // DoDeleteBuffers | |
| NOTREACHED(); | |
| } | |
| const auto& update = u.second; | |
| void* shadow = update.shm->GetDataAddress(update.shm_offset, update.size); | |
| DCHECK(shadow); | |
| api()->glBindBufferFn(GL_ARRAY_BUFFER, service_id); | |
| GLint already_mapped = GL_TRUE; | |
| api()->glGetBufferParameterivFn(GL_ARRAY_BUFFER, GL_BUFFER_MAPPED, | |
| &already_mapped); | |
| if (already_mapped) { | |
| // The buffer is already mapped by the client. It's okay that the shadow | |
| // copy will be out-of-date, because the client will never read it: | |
| // * Client issues READBACK_SHADOW_COPIES_UPDATED_CHROMIUM query | |
| // * Client maps buffer | |
| // * Client receives signal that the query completed | |
| // * Client unmaps buffer - invalidating the shadow copy | |
| // * Client maps buffer to read back - hits the round-trip path | |
| continue; | |
| } | |
| void* mapped = api()->glMapBufferRangeFn(GL_ARRAY_BUFFER, 0, update.size, | |
| GL_MAP_READ_BIT); | |
| if (!mapped) { | |
| DLOG(ERROR) << "glMapBufferRange unexpectedly returned NULL"; | |
| MarkContextLost(error::kOutOfMemory); | |
| group_->LoseContexts(error::kUnknown); | |
| return; | |
| } | |
| if (feature_info_->workarounds() | |
| .check_graphics_reset_status_after_readback && | |
| CheckResetStatus()) { | |
| // Refuse shadow-copy delivery across a device reset. | |
| group_->LoseContexts(error::kUnknown); | |
| return; | |
| } | |
| UNSAFE_TODO(memcpy(shadow, mapped, update.size)); | |
| bool unmap_ok = api()->glUnmapBufferFn(GL_ARRAY_BUFFER); | |
| if (unmap_ok == GL_FALSE) { | |
| DLOG(ERROR) << "glUnmapBuffer unexpectedly returned GL_FALSE"; | |
| MarkContextLost(error::kUnknown); | |
| group_->LoseContexts(error::kUnknown); | |
| return; | |
| } | |
| } | |
| // Restore original GL_ARRAY_BUFFER binding | |
| api()->glBindBufferFn(GL_ARRAY_BUFFER, old_binding); | |
| } | |
| error::Error GLES2DecoderPassthroughImpl::ProcessReadPixels(bool did_finish) { | |
| while (!pending_read_pixels_.empty()) { | |
| const PendingReadPixels& pending_read_pixels = pending_read_pixels_.front(); | |
| if (did_finish || pending_read_pixels.fence->HasCompleted()) { | |
| using Result = cmds::ReadPixels::Result; | |
| Result* result = nullptr; | |
| if (pending_read_pixels.result_shm_id != 0) { | |
| result = GetSharedMemoryAs<Result*>( | |
| pending_read_pixels.result_shm_id, | |
| pending_read_pixels.result_shm_offset, sizeof(*result)); | |
| if (!result) { | |
| api()->glDeleteBuffersARBFn(1, | |
| &pending_read_pixels.buffer_service_id); | |
| pending_read_pixels_.pop_front(); | |
| break; | |
| } | |
| } | |
| void* pixels = | |
| GetSharedMemoryAs<void*>(pending_read_pixels.pixels_shm_id, | |
| pending_read_pixels.pixels_shm_offset, | |
| pending_read_pixels.pixels_size); | |
| if (!pixels) { | |
| api()->glDeleteBuffersARBFn(1, &pending_read_pixels.buffer_service_id); | |
| pending_read_pixels_.pop_front(); | |
| break; | |
| } | |
| // If the context was reset while this readback was queued, the pack | |
| // buffer's backing store was never written; mapping it may deliver | |
| // uninitialized driver memory on certain GPUs. The sync readback lane is | |
| // covered inside ANGLE (checkGraphicsResetStatusAfterReadback). | |
| if (feature_info_->workarounds() | |
| .check_graphics_reset_status_after_readback && | |
| !WasContextLost() && CheckResetStatus()) { | |
| pending_read_pixels_.pop_front(); | |
| group_->LoseContexts(error::kUnknown); | |
| return error::kLostContext; | |
| } | |
| api()->glBindBufferFn(GL_PIXEL_PACK_BUFFER, | |
| pending_read_pixels.buffer_service_id); | |
| void* data = nullptr; | |
| if (feature_info_->feature_flags().map_buffer_range) { | |
| data = api()->glMapBufferRangeFn(GL_PIXEL_PACK_BUFFER, 0, | |
| pending_read_pixels.pixels_size, | |
| GL_MAP_READ_BIT); | |
| } else { | |
| data = api()->glMapBufferFn(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY); | |
| } | |
| if (!data) { | |
| InsertError(GL_OUT_OF_MEMORY, "Failed to map pixel pack buffer."); | |
| pending_read_pixels_.pop_front(); | |
| break; | |
| } | |
| UNSAFE_TODO(memcpy(pixels, data, pending_read_pixels.pixels_size)); | |
| api()->glUnmapBufferFn(GL_PIXEL_PACK_BUFFER); | |
| api()->glBindBufferFn(GL_PIXEL_PACK_BUFFER, | |
| resources_->buffer_id_map.GetServiceIDOrInvalid( | |
| bound_buffers_[GL_PIXEL_PACK_BUFFER])); | |
| api()->glDeleteBuffersARBFn(1, &pending_read_pixels.buffer_service_id); | |
| if (result != nullptr) { | |
| result->success = 1; | |
| } | |
| pending_read_pixels_.pop_front(); | |
| } | |
| } | |
| // If api()->glFinishFn() has been called, all of our fences should be | |
| // completed. | |
| DCHECK(!did_finish || pending_read_pixels_.empty()); | |
| return error::kNoError; | |
| } | |
| void GLES2DecoderPassthroughImpl::ProcessDescheduleUntilFinished() { | |
| if (deschedule_until_finished_fences_.size() < 2) { | |
| return; | |
| } | |
| DCHECK_EQ(2u, deschedule_until_finished_fences_.size()); | |
| if (!deschedule_until_finished_fences_[0]->HasCompleted()) { | |
| return; | |
| } | |
| TRACE_EVENT_END( | |
| "cc", /*"GLES2DecoderPassthroughImpl::DescheduleUntilFinished"*/ | |
| perfetto::NamedTrack::FromPointer( | |
| "gpu::gles2::GLES2DecoderPassthroughImpl", this)); | |
| deschedule_until_finished_fences_.pop_front(); | |
| client()->OnRescheduleAfterFinished(); | |
| } | |
| void GLES2DecoderPassthroughImpl::UpdateTextureBinding( | |
| GLenum target, | |
| GLuint client_id, | |
| TexturePassthrough* texture) { | |
| GLuint texture_service_id = texture ? texture->service_id() : 0; | |
| size_t cur_texture_unit = active_texture_unit_; | |
| auto& target_bound_textures = | |
| bound_textures_[static_cast<size_t>(GLenumToTextureTarget(target))]; | |
| for (size_t bound_texture_index = 0; | |
| bound_texture_index < target_bound_textures.size(); | |
| bound_texture_index++) { | |
| if (target_bound_textures[bound_texture_index].client_id == client_id) { | |
| // Update the active texture unit if needed | |
| if (bound_texture_index != cur_texture_unit) { | |
| api()->glActiveTextureFn( | |
| static_cast<GLenum>(GL_TEXTURE0 + bound_texture_index)); | |
| cur_texture_unit = bound_texture_index; | |
| } | |
| // Update the texture binding | |
| api()->glBindTextureFn(target, texture_service_id); | |
| target_bound_textures[bound_texture_index].texture = texture; | |
| } | |
| } | |
| // Reset the active texture unit if it was changed | |
| if (cur_texture_unit != active_texture_unit_) { | |
| api()->glActiveTextureFn( | |
| static_cast<GLenum>(GL_TEXTURE0 + active_texture_unit_)); | |
| } | |
| } | |
| void GLES2DecoderPassthroughImpl::RebindTexture(TexturePassthrough* texture) { | |
| DCHECK(texture != nullptr); | |
| size_t cur_texture_unit = active_texture_unit_; | |
| GLenum target = texture->target(); | |
| auto& target_bound_textures = | |
| bound_textures_[static_cast<size_t>(GLenumToTextureTarget(target))]; | |
| for (size_t bound_texture_index = 0; | |
| bound_texture_index < target_bound_textures.size(); | |
| bound_texture_index++) { | |
| if (target_bound_textures[bound_texture_index].texture == texture) { | |
| // Update the active texture unit if needed | |
| if (bound_texture_index != cur_texture_unit) { | |
| api()->glActiveTextureFn( | |
| static_cast<GLenum>(GL_TEXTURE0 + bound_texture_index)); | |
| cur_texture_unit = bound_texture_index; | |
| } | |
| // Update the texture binding | |
| api()->glBindTextureFn(target, texture->service_id()); | |
| } | |
| } | |
| // Reset the active texture unit if it was changed | |
| if (cur_texture_unit != active_texture_unit_) { | |
| api()->glActiveTextureFn( | |
| static_cast<GLenum>(GL_TEXTURE0 + active_texture_unit_)); | |
| } | |
| } | |
| void GLES2DecoderPassthroughImpl::UpdateTextureSizeFromTexturePassthrough( | |
| TexturePassthrough* texture, | |
| GLuint client_id) { | |
| if (texture == nullptr) { | |
| return; | |
| } | |
| CheckErrorCallbackState(); | |
| GLenum target = texture->target(); | |
| TextureTarget internal_texture_type = GLenumToTextureTarget(target); | |
| BoundTexture& bound_texture = | |
| bound_textures_[static_cast<size_t>(internal_texture_type)] | |
| [active_texture_unit_]; | |
| bool needs_rebind = bound_texture.texture == texture; | |
| if (needs_rebind) { | |
| glBindTexture(target, texture->service_id()); | |
| } | |
| UpdateBoundTexturePassthroughSize(api(), texture); | |
| if (needs_rebind) { | |
| GLuint old_texture = | |
| bound_texture.texture ? bound_texture.texture->service_id() : 0; | |
| glBindTexture(target, old_texture); | |
| } | |
| DCHECK(!CheckErrorCallbackState()); | |
| } | |
| void GLES2DecoderPassthroughImpl::UpdateTextureSizeFromTarget(GLenum target) { | |
| GLenum texture_type = TextureTargetToTextureType(target); | |
| TextureTarget internal_texture_type = GLenumToTextureTarget(texture_type); | |
| DCHECK(internal_texture_type != TextureTarget::kUnkown); | |
| BoundTexture& bound_texture = | |
| bound_textures_[static_cast<size_t>(internal_texture_type)] | |
| [active_texture_unit_]; | |
| UpdateTextureSizeFromTexturePassthrough(bound_texture.texture.get(), | |
| bound_texture.client_id); | |
| } | |
| void GLES2DecoderPassthroughImpl::UpdateTextureSizeFromClientID( | |
| GLuint client_id) { | |
| scoped_refptr<TexturePassthrough> texture; | |
| if (resources_->texture_object_map.GetServiceID(client_id, &texture) && | |
| texture != nullptr) { | |
| UpdateTextureSizeFromTexturePassthrough(texture.get(), client_id); | |
| } | |
| } | |
| void GLES2DecoderPassthroughImpl:: | |
| LazilyUpdateCurrentlyBoundElementArrayBuffer() { | |
| if (!bound_element_array_buffer_dirty_) | |
| return; | |
| GLint service_element_array_buffer = 0; | |
| api_->glGetIntegervFn(GL_ELEMENT_ARRAY_BUFFER_BINDING, | |
| &service_element_array_buffer); | |
| GLuint client_element_array_buffer = 0; | |
| if (service_element_array_buffer != 0) { | |
| GetClientID(&resources_->buffer_id_map, | |
| static_cast<GLuint>(service_element_array_buffer), | |
| &client_element_array_buffer); | |
| } | |
| bound_buffers_[GL_ELEMENT_ARRAY_BUFFER] = client_element_array_buffer; | |
| bound_element_array_buffer_dirty_ = false; | |
| } | |
| error::Error GLES2DecoderPassthroughImpl::HandleSetActiveURLCHROMIUM( | |
| uint32_t immediate_data_size, | |
| const volatile void* cmd_data) { | |
| const volatile cmds::SetActiveURLCHROMIUM& c = | |
| *static_cast<const volatile cmds::SetActiveURLCHROMIUM*>(cmd_data); | |
| Bucket* url_bucket = GetBucket(c.url_bucket_id); | |
| static constexpr size_t kMaxStrLen = 1024; | |
| if (!url_bucket || url_bucket->size() == 0 || | |
| url_bucket->size() > kMaxStrLen + 1) { | |
| return error::kInvalidArguments; | |
| } | |
| size_t size = url_bucket->size() - 1; | |
| base::span<const uint8_t> url_bytes = url_bucket->GetDataAsByteSpan(0, size); | |
| if (url_bytes.empty()) { | |
| return error::kInvalidArguments; | |
| } | |
| GURL url(base::as_string_view(url_bytes)); | |
| client()->SetActiveURL(std::move(url)); | |
| return error::kNoError; | |
| } | |
| void GLES2DecoderPassthroughImpl::VerifyServiceTextureObjectsExist() { | |
| resources_->texture_object_map.ForEach( | |
| [this](GLuint client_id, scoped_refptr<TexturePassthrough> texture) { | |
| DCHECK_EQ(GL_TRUE, api()->glIsTextureFn(texture->service_id())); | |
| }); | |
| } | |
| bool GLES2DecoderPassthroughImpl::IsEmulatedFramebufferBound( | |
| GLenum target) const { | |
| if (!emulated_back_buffer_) { | |
| return false; | |
| } | |
| if ((target == GL_FRAMEBUFFER || target == GL_DRAW_FRAMEBUFFER) && | |
| bound_draw_framebuffer_ == 0) { | |
| return true; | |
| } | |
| if (target == GL_READ_FRAMEBUFFER && bound_read_framebuffer_ == 0) { | |
| return true; | |
| } | |
| return false; | |
| } | |
| // static | |
| GLES2DecoderPassthroughImpl::TextureTarget | |
| GLES2DecoderPassthroughImpl::GLenumToTextureTarget(GLenum target) { | |
| switch (target) { | |
| case GL_TEXTURE_2D: | |
| return TextureTarget::k2D; | |
| case GL_TEXTURE_CUBE_MAP: | |
| return TextureTarget::kCubeMap; | |
| case GL_TEXTURE_2D_ARRAY: | |
| return TextureTarget::k2DArray; | |
| case GL_TEXTURE_3D: | |
| return TextureTarget::k3D; | |
| case GL_TEXTURE_2D_MULTISAMPLE: | |
| return TextureTarget::k2DMultisample; | |
| case GL_TEXTURE_EXTERNAL_OES: | |
| return TextureTarget::kExternal; | |
| case GL_TEXTURE_RECTANGLE_ANGLE: | |
| return TextureTarget::kRectangle; | |
| case GL_TEXTURE_BUFFER: | |
| return TextureTarget::kBuffer; | |
| case GL_TEXTURE_CUBE_MAP_ARRAY: | |
| return TextureTarget::kCubeMapArray; | |
| case GL_TEXTURE_2D_MULTISAMPLE_ARRAY: | |
| return TextureTarget::k2DMultisampleArray; | |
| default: | |
| return TextureTarget::kUnkown; | |
| } | |
| } | |
| bool GLES2DecoderPassthroughImpl::CheckErrorCallbackState() { | |
| bool had_error_ = had_error_callback_; | |
| had_error_callback_ = false; | |
| if (had_error_) { | |
| // Make sure lose-context-on-OOM logic is triggered as early as possible. | |
| FlushErrors(); | |
| } | |
| return had_error_; | |
| } | |
| void GLES2DecoderPassthroughImpl::ScheduleIdleMemoryTrim() { | |
| if (WasContextLost()) { | |
| return; | |
| } | |
| if (!feature_info_->feature_flags().angle_trim_memory || | |
| !base::FeatureList::IsEnabled(features::kANGLETrimMemoryOnIdle)) { | |
| return; | |
| } | |
| last_active_time_ = base::TimeTicks::Now(); | |
| if (idle_trim_cb_.IsCancelled()) { | |
| PostIdleMemoryTrimTask(); | |
| } | |
| } | |
| void GLES2DecoderPassthroughImpl::PostIdleMemoryTrimTask() { | |
| if (!base::SingleThreadTaskRunner::HasCurrentDefault()) { | |
| return; | |
| } | |
| idle_trim_cb_.Reset( | |
| base::BindOnce(&GLES2DecoderPassthroughImpl::PerformIdleMemoryTrim, | |
| weak_ptr_factory_.GetWeakPtr())); | |
| base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( | |
| FROM_HERE, idle_trim_cb_.callback(), kIdleTrimDelay); | |
| } | |
| void GLES2DecoderPassthroughImpl::PerformIdleMemoryTrim() { | |
| TRACE_EVENT("gpu", __PRETTY_FUNCTION__); | |
| idle_trim_cb_.Cancel(); | |
| if (base::TimeTicks::Now() - last_active_time_ < kIdleTrimDelay) { | |
| PostIdleMemoryTrimTask(); | |
| return; | |
| } | |
| if (WasContextLost() || !MakeCurrent()) { | |
| return; | |
| } | |
| api()->glTrimMemoryANGLEFn(GL_MEMORY_TRIM_HIGH_ANGLE); | |
| } | |
| bool GLES2DecoderPassthroughImpl::CanRedefineBoundTexture(GLenum target) { | |
| if (!base::FeatureList::IsEnabled(kDisallowSharedImageRedifinition)) { | |
| return true; | |
| } | |
| TextureTarget internal_texture_target = | |
| GLenumToTextureTarget(TextureTargetToTextureType(target)); | |
| // Invalid texture targets will be discarded by angle. | |
| if (internal_texture_target == TextureTarget::kUnkown) { | |
| return true; | |
| } | |
| auto client_id = bound_textures_[static_cast<size_t>(internal_texture_target)] | |
| [active_texture_unit_] | |
| .client_id; | |
| return !resources_->texture_shared_image_map.contains(client_id); | |
| } | |
| #define GLES2_CMD_OP(name) \ | |
| { \ | |
| &GLES2DecoderPassthroughImpl::Handle##name, \ | |
| cmds::name::kArgFlags, \ | |
| cmds::name::cmd_flags, \ | |
| sizeof(cmds::name) / sizeof(CommandBufferEntry) - 1, \ | |
| }, /* NOLINT */ | |
| constexpr std::array<GLES2DecoderPassthroughImpl::CommandInfo, | |
| kNumCommands - kFirstGLES2Command> | |
| GLES2DecoderPassthroughImpl::command_info = { | |
| {GLES2_COMMAND_LIST(GLES2_CMD_OP)}}; | |
| #undef GLES2_CMD_OP | |
| } // namespace gles2 | |
| } // namespace gpu |