| // Copyright 2012 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ui/gl/gl_surface_egl.h" |
| |
| #include <stddef.h> |
| #include <stdint.h> |
| |
| #include <map> |
| #include <memory> |
| #include <sstream> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/compiler_specific.h" |
| #include "base/containers/heap_array.h" |
| #include "base/lazy_instance.h" |
| #include "base/logging.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/memory/raw_ref.h" |
| #include "base/scoped_environment_variable_override.h" |
| #include "base/system/sys_info.h" |
| #include "base/trace_event/trace_event.h" |
| #include "build/build_config.h" |
| #include "ui/events/platform/platform_event_dispatcher.h" |
| #include "ui/gfx/geometry/rect.h" |
| #include "ui/gfx/gpu_fence.h" |
| #include "ui/gl/egl_util.h" |
| #include "ui/gl/gl_bindings.h" |
| #include "ui/gl/gl_context.h" |
| #include "ui/gl/gl_display_egl_util.h" |
| #include "ui/gl/gl_display_manager.h" |
| #include "ui/gl/gl_surface_presentation_helper.h" |
| #include "ui/gl/gl_surface_stub.h" |
| #include "ui/gl/gl_utils.h" |
| #include "ui/gl/scoped_make_current.h" |
| #include "ui/gl/sync_control_vsync_provider.h" |
| |
| using ui::GetLastEGLErrorString; |
| |
| namespace gl { |
| |
| namespace { |
| |
| constexpr const char kSwapEventTraceCategories[] = "gpu"; |
| |
| constexpr size_t kMaxTimestampsSupportable = 9; |
| |
| struct TraceSwapEventsInitializer { |
| TraceSwapEventsInitializer() |
| : value(*TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED( |
| kSwapEventTraceCategories)) {} |
| const raw_ref<const unsigned char> value; |
| }; |
| |
| static base::LazyInstance<TraceSwapEventsInitializer>::Leaky |
| g_trace_swap_enabled = LAZY_INSTANCE_INITIALIZER; |
| |
| class EGLSyncControlVSyncProvider : public SyncControlVSyncProvider { |
| public: |
| EGLSyncControlVSyncProvider(EGLSurface surface, GLDisplayEGL* display) |
| : surface_(surface), display_(display) { |
| DCHECK(display_); |
| } |
| |
| EGLSyncControlVSyncProvider(const EGLSyncControlVSyncProvider&) = delete; |
| EGLSyncControlVSyncProvider& operator=(const EGLSyncControlVSyncProvider&) = |
| delete; |
| |
| ~EGLSyncControlVSyncProvider() override {} |
| |
| static bool IsSupported(GLDisplayEGL* display) { |
| DCHECK(display); |
| return SyncControlVSyncProvider::IsSupported() && |
| display->ext->b_EGL_CHROMIUM_sync_control; |
| } |
| |
| protected: |
| bool GetSyncValues(int64_t* system_time, |
| int64_t* media_stream_counter, |
| int64_t* swap_buffer_counter) override { |
| uint64_t u_system_time, u_media_stream_counter, u_swap_buffer_counter; |
| bool result = |
| eglGetSyncValuesCHROMIUM(display_->GetDisplay(), surface_, |
| &u_system_time, &u_media_stream_counter, |
| &u_swap_buffer_counter) == EGL_TRUE; |
| if (result) { |
| *system_time = static_cast<int64_t>(u_system_time); |
| *media_stream_counter = static_cast<int64_t>(u_media_stream_counter); |
| *swap_buffer_counter = static_cast<int64_t>(u_swap_buffer_counter); |
| } |
| return result; |
| } |
| |
| bool GetMscRate(int32_t* numerator, int32_t* denominator) override { |
| if (!display_->ext->b_EGL_ANGLE_sync_control_rate) { |
| return false; |
| } |
| |
| bool result = eglGetMscRateANGLE(display_->GetDisplay(), surface_, |
| numerator, denominator) == EGL_TRUE; |
| return result; |
| } |
| |
| bool IsHWClock() const override { return true; } |
| |
| private: |
| EGLSurface surface_; |
| raw_ptr<GLDisplayEGL> display_; |
| }; |
| |
| bool ValidateEglConfig(EGLDisplay display, |
| const EGLint* config_attribs, |
| EGLint* num_configs) { |
| if (!eglChooseConfig(display, |
| config_attribs, |
| NULL, |
| 0, |
| num_configs)) { |
| LOG(ERROR) << "eglChooseConfig failed with error " |
| << GetLastEGLErrorString(); |
| return false; |
| } |
| if (*num_configs == 0) { |
| return false; |
| } |
| return true; |
| } |
| |
| EGLConfig ChooseConfig(EGLDisplay display, |
| GLSurfaceFormat format, |
| bool surfaceless, |
| bool offscreen, |
| EGLint visual_id) { |
| // Choose an EGL configuration. |
| // On X this is only used for PBuffer surfaces. |
| |
| std::vector<EGLint> renderable_types; |
| if (!GetGlWorkarounds().disable_es3gl_context) { |
| renderable_types.push_back(EGL_OPENGL_ES3_BIT); |
| } |
| renderable_types.push_back(EGL_OPENGL_ES2_BIT); |
| |
| EGLint alpha_size = 8; |
| bool want_rgb565 = format.IsRGB565(); |
| EGLint buffer_size = want_rgb565 ? 16 : 32; |
| |
| // Some platforms (eg. X11) may want to set custom values for alpha and buffer |
| // sizes. |
| GLDisplayEglUtil::GetInstance()->ChoosePlatformCustomAlphaAndBufferSize( |
| &alpha_size, &buffer_size); |
| |
| EGLint surface_type = |
| (surfaceless |
| ? EGL_DONT_CARE |
| : (offscreen ? EGL_PBUFFER_BIT : EGL_WINDOW_BIT | EGL_PBUFFER_BIT)); |
| |
| for (auto renderable_type : renderable_types) { |
| EGLint config_attribs_8888[] = {EGL_BUFFER_SIZE, |
| buffer_size, |
| EGL_ALPHA_SIZE, |
| alpha_size, |
| EGL_BLUE_SIZE, |
| 8, |
| EGL_GREEN_SIZE, |
| 8, |
| EGL_RED_SIZE, |
| 8, |
| EGL_RENDERABLE_TYPE, |
| renderable_type, |
| EGL_SURFACE_TYPE, |
| surface_type, |
| EGL_NONE}; |
| |
| EGLint config_attribs_565[] = {EGL_BUFFER_SIZE, |
| 16, |
| EGL_BLUE_SIZE, |
| 5, |
| EGL_GREEN_SIZE, |
| 6, |
| EGL_RED_SIZE, |
| 5, |
| EGL_RENDERABLE_TYPE, |
| renderable_type, |
| EGL_SURFACE_TYPE, |
| surface_type, |
| EGL_NONE}; |
| |
| EGLint* choose_attributes = config_attribs_8888; |
| if (want_rgb565) { |
| choose_attributes = config_attribs_565; |
| } |
| |
| EGLint num_configs; |
| EGLint config_size = 1; |
| EGLConfig config = nullptr; |
| EGLConfig* config_data = &config; |
| // Validate if there are any configs for given attribs. |
| if (!ValidateEglConfig(display, choose_attributes, &num_configs)) { |
| // Try the next renderable_type |
| continue; |
| } |
| |
| auto matching_configs = base::HeapArray<EGLConfig>::Uninit(num_configs); |
| if (want_rgb565 || visual_id >= 0) { |
| config_size = num_configs; |
| config_data = matching_configs.data(); |
| } |
| |
| if (!eglChooseConfig(display, choose_attributes, config_data, config_size, |
| &num_configs)) { |
| LOG(ERROR) << "eglChooseConfig failed with error " |
| << GetLastEGLErrorString(); |
| return config; |
| } |
| |
| if (want_rgb565) { |
| // Because of the EGL config sort order, we have to iterate |
| // through all of them (it'll put higher sum(R,G,B) bits |
| // first with the above attribs). |
| bool match_found = false; |
| for (int i = 0; i < num_configs; i++) { |
| EGLint red, green, blue, alpha; |
| // Read the relevant attributes of the EGLConfig. |
| if (eglGetConfigAttrib(display, matching_configs[i], EGL_RED_SIZE, |
| &red) && |
| eglGetConfigAttrib(display, matching_configs[i], EGL_BLUE_SIZE, |
| &blue) && |
| eglGetConfigAttrib(display, matching_configs[i], EGL_GREEN_SIZE, |
| &green) && |
| eglGetConfigAttrib(display, matching_configs[i], EGL_ALPHA_SIZE, |
| &alpha) && |
| alpha == 0 && red == 5 && green == 6 && blue == 5) { |
| config = matching_configs[i]; |
| match_found = true; |
| break; |
| } |
| } |
| if (!match_found) { |
| // To fall back to default 32 bit format, choose with |
| // the right attributes again. |
| if (!ValidateEglConfig(display, config_attribs_8888, &num_configs)) { |
| // Try the next renderable_type |
| continue; |
| } |
| if (!eglChooseConfig(display, config_attribs_8888, &config, 1, |
| &num_configs)) { |
| LOG(ERROR) << "eglChooseConfig failed with error " |
| << GetLastEGLErrorString(); |
| return config; |
| } |
| } |
| } else if (visual_id >= 0) { |
| for (int i = 0; i < num_configs; i++) { |
| EGLint id; |
| if (eglGetConfigAttrib(display, matching_configs[i], |
| EGL_NATIVE_VISUAL_ID, &id) && |
| id == visual_id) { |
| config = matching_configs[i]; |
| break; |
| } |
| } |
| } |
| return config; |
| } |
| |
| LOG(ERROR) << "No suitable EGL configs found."; |
| return nullptr; |
| } |
| |
| } // namespace |
| |
| GLSurfaceEGL::GLSurfaceEGL(GLDisplayEGL* display) : display_(display) { |
| DCHECK(display_); |
| } |
| |
| GLSurfaceFormat GLSurfaceEGL::GetFormat() { |
| return format_; |
| } |
| |
| GLDisplay* GLSurfaceEGL::GetGLDisplay() { |
| return display_; |
| } |
| |
| EGLConfig GLSurfaceEGL::GetConfig() { |
| if (!config_) { |
| config_ = ChooseConfig(display_->GetDisplay(), format_, IsSurfaceless(), |
| IsOffscreen(), GetNativeVisualID()); |
| } |
| return config_; |
| } |
| |
| EGLint GLSurfaceEGL::GetNativeVisualID() const { |
| return -1; |
| } |
| |
| EGLDisplay GLSurfaceEGL::GetEGLDisplay() { |
| return display_->GetDisplay(); |
| } |
| |
| // static |
| GLDisplayEGL* GLSurfaceEGL::GetGLDisplayEGL() { |
| return GLDisplayManagerEGL::GetInstance()->GetDisplay( |
| GpuPreference::kDefault); |
| } |
| |
| GLSurfaceEGL::~GLSurfaceEGL() { |
| // InvalidateWeakPtrs should be called from the concrete dtors. |
| CHECK(!HasWeakPtrs()); |
| } |
| |
| #if BUILDFLAG(IS_ANDROID) |
| NativeViewGLSurfaceEGL::NativeViewGLSurfaceEGL( |
| GLDisplayEGL* display, |
| ScopedANativeWindow scoped_window, |
| std::unique_ptr<gfx::VSyncProvider> vsync_provider) |
| : GLSurfaceEGL(display), |
| scoped_window_(std::move(scoped_window)), |
| window_(scoped_window_.a_native_window()), |
| vsync_provider_external_(std::move(vsync_provider)) {} |
| #else |
| NativeViewGLSurfaceEGL::NativeViewGLSurfaceEGL( |
| GLDisplayEGL* display, |
| EGLNativeWindowType window, |
| std::unique_ptr<gfx::VSyncProvider> vsync_provider) |
| : GLSurfaceEGL(display), |
| window_(window), |
| vsync_provider_external_(std::move(vsync_provider)) { |
| #if BUILDFLAG(IS_WIN) |
| RECT windowRect; |
| if (GetClientRect(window_, &windowRect)) { |
| size_ = gfx::Rect(windowRect).size(); |
| } |
| #endif |
| } |
| #endif // BUILDFLAG(IS_ANDROID) |
| |
| bool NativeViewGLSurfaceEGL::Initialize(GLSurfaceFormat format) { |
| DCHECK(!surface_); |
| format_ = format; |
| |
| if (display_->GetDisplay() == EGL_NO_DISPLAY) { |
| LOG(ERROR) << "Trying to create NativeViewGLSurfaceEGL with invalid " |
| << "display."; |
| return false; |
| } |
| |
| // We need to make sure that window_ is correctly initialized with all |
| // the platform-dependant quirks, if any, before creating the surface. |
| if (!InitializeNativeWindow()) { |
| LOG(ERROR) << "Error trying to initialize the native window."; |
| return false; |
| } |
| |
| if (!GetConfig()) { |
| LOG(ERROR) << "No suitable EGL configs found for initialization."; |
| return false; |
| } |
| |
| std::vector<EGLint> egl_window_attributes; |
| |
| if (display_->ext->b_EGL_ANGLE_window_fixed_size && |
| enable_fixed_size_angle_) { |
| egl_window_attributes.push_back(EGL_FIXED_SIZE_ANGLE); |
| egl_window_attributes.push_back(EGL_TRUE); |
| egl_window_attributes.push_back(EGL_WIDTH); |
| egl_window_attributes.push_back(size_.width()); |
| egl_window_attributes.push_back(EGL_HEIGHT); |
| egl_window_attributes.push_back(size_.height()); |
| } |
| |
| if (display_->ext->b_EGL_NV_post_sub_buffer) { |
| egl_window_attributes.push_back(EGL_POST_SUB_BUFFER_SUPPORTED_NV); |
| egl_window_attributes.push_back(EGL_TRUE); |
| } |
| |
| if (display_->ext->b_EGL_ANGLE_surface_orientation) { |
| EGLint attrib; |
| eglGetConfigAttrib(display_->GetDisplay(), GetConfig(), |
| EGL_OPTIMAL_SURFACE_ORIENTATION_ANGLE, &attrib); |
| surface_origin_ = (attrib == EGL_SURFACE_ORIENTATION_INVERT_Y_ANGLE) |
| ? gfx::SurfaceOrigin::kTopLeft |
| : gfx::SurfaceOrigin::kBottomLeft; |
| } |
| |
| if (surface_origin_ == gfx::SurfaceOrigin::kTopLeft) { |
| egl_window_attributes.push_back(EGL_SURFACE_ORIENTATION_ANGLE); |
| egl_window_attributes.push_back(EGL_SURFACE_ORIENTATION_INVERT_Y_ANGLE); |
| } |
| |
| // Note that COLORSPACE_LINEAR refers to the sRGB color space, but |
| // without opting into sRGB blending. It is equivalent to |
| // COLORSPACE_SRGB with Disable(FRAMEBUFFER_SRGB). |
| if (display_->ext->b_EGL_KHR_gl_colorspace) { |
| egl_window_attributes.push_back(EGL_GL_COLORSPACE_KHR); |
| egl_window_attributes.push_back(EGL_GL_COLORSPACE_LINEAR_KHR); |
| } |
| |
| egl_window_attributes.push_back(EGL_NONE); |
| // Create a surface for the native window. |
| surface_ = eglCreateWindowSurface(display_->GetDisplay(), GetConfig(), |
| window_, &egl_window_attributes[0]); |
| |
| if (!surface_) { |
| LOG(ERROR) << "eglCreateWindowSurface failed with error " |
| << GetLastEGLErrorString(); |
| Destroy(); |
| return false; |
| } |
| |
| if (display_->ext->b_EGL_NV_post_sub_buffer) { |
| EGLint surfaceVal; |
| EGLBoolean retVal = |
| eglQuerySurface(display_->GetDisplay(), surface_, |
| EGL_POST_SUB_BUFFER_SUPPORTED_NV, &surfaceVal); |
| supports_post_sub_buffer_ = (surfaceVal && retVal) == EGL_TRUE; |
| } |
| |
| supports_swap_buffer_with_damage_ = |
| display_->ext->b_EGL_KHR_swap_buffers_with_damage; |
| |
| if (!vsync_provider_external_ && |
| EGLSyncControlVSyncProvider::IsSupported(display_)) { |
| vsync_provider_internal_ = |
| std::make_unique<EGLSyncControlVSyncProvider>(surface_, display_); |
| } |
| |
| if (!vsync_provider_external_ && !vsync_provider_internal_) |
| vsync_provider_internal_ = CreateVsyncProviderInternal(); |
| |
| presentation_helper_ = |
| std::make_unique<GLSurfacePresentationHelper>(GetVSyncProvider()); |
| return true; |
| } |
| |
| bool NativeViewGLSurfaceEGL::SupportsSwapTimestamps() const { |
| return display_->ext->b_EGL_ANDROID_get_frame_timestamps; |
| } |
| |
| void NativeViewGLSurfaceEGL::SetEnableSwapTimestamps() { |
| DCHECK(display_->ext->b_EGL_ANDROID_get_frame_timestamps); |
| |
| // If frame timestamps are supported, set the proper attribute to enable the |
| // feature and then cache the timestamps supported by the underlying |
| // implementation. EGL_DISPLAY_PRESENT_TIME_ANDROID support, in particular, |
| // is spotty. |
| // Clear the supported timestamps here to protect against Initialize() being |
| // called twice. |
| supported_egl_timestamps_.clear(); |
| supported_event_names_.clear(); |
| presentation_feedback_index_ = -1; |
| composition_start_index_ = -1; |
| |
| eglSurfaceAttrib(display_->GetDisplay(), surface_, EGL_TIMESTAMPS_ANDROID, |
| EGL_TRUE); |
| |
| // Check if egl composite interval is supported or not. If not then return. |
| // Else check which other timestamps are supported. |
| EGLint interval_name = EGL_COMPOSITE_INTERVAL_ANDROID; |
| if (!eglGetCompositorTimingSupportedANDROID(display_->GetDisplay(), surface_, |
| interval_name)) |
| return; |
| |
| static const struct { |
| EGLint egl_name; |
| const char* name; |
| } all_timestamps[kMaxTimestampsSupportable] = { |
| {EGL_REQUESTED_PRESENT_TIME_ANDROID, "Queue"}, |
| {EGL_RENDERING_COMPLETE_TIME_ANDROID, "WritesDone"}, |
| {EGL_COMPOSITION_LATCH_TIME_ANDROID, "LatchedForDisplay"}, |
| {EGL_FIRST_COMPOSITION_START_TIME_ANDROID, "1stCompositeCpu"}, |
| {EGL_LAST_COMPOSITION_START_TIME_ANDROID, "NthCompositeCpu"}, |
| {EGL_FIRST_COMPOSITION_GPU_FINISHED_TIME_ANDROID, "GpuCompositeDone"}, |
| {EGL_DISPLAY_PRESENT_TIME_ANDROID, "ScanOutStart"}, |
| {EGL_DEQUEUE_READY_TIME_ANDROID, "DequeueReady"}, |
| {EGL_READS_DONE_TIME_ANDROID, "ReadsDone"}, |
| }; |
| |
| supported_egl_timestamps_.reserve(kMaxTimestampsSupportable); |
| supported_event_names_.reserve(kMaxTimestampsSupportable); |
| for (const auto& ts : all_timestamps) { |
| if (!eglGetFrameTimestampSupportedANDROID(display_->GetDisplay(), surface_, |
| ts.egl_name)) |
| continue; |
| |
| // For presentation feedback, prefer the actual scan out time, but fallback |
| // to SurfaceFlinger's composite time since some devices don't support |
| // the former. |
| switch (ts.egl_name) { |
| case EGL_FIRST_COMPOSITION_START_TIME_ANDROID: |
| // Value of presentation_feedback_index_ relies on the order of |
| // all_timestamps. |
| presentation_feedback_index_ = |
| static_cast<int>(supported_egl_timestamps_.size()); |
| composition_start_index_ = |
| static_cast<int>(supported_egl_timestamps_.size()); |
| presentation_flags_ = 0; |
| break; |
| case EGL_DISPLAY_PRESENT_TIME_ANDROID: |
| presentation_feedback_index_ = |
| static_cast<int>(supported_egl_timestamps_.size()); |
| presentation_flags_ = gfx::PresentationFeedback::kVSync | |
| gfx::PresentationFeedback::kHWCompletion; |
| break; |
| case EGL_RENDERING_COMPLETE_TIME_ANDROID: |
| writes_done_index_ = static_cast<int>(supported_egl_timestamps_.size()); |
| break; |
| } |
| |
| // Stored in separate vectors so we can pass the egl timestamps |
| // directly to the EGL functions. |
| supported_egl_timestamps_.push_back(ts.egl_name); |
| supported_event_names_.push_back(ts.name); |
| } |
| DCHECK_GE(presentation_feedback_index_, 0); |
| DCHECK_GE(composition_start_index_, 0); |
| |
| use_egl_timestamps_ = !supported_egl_timestamps_.empty(); |
| |
| // Recreate the presentation helper here to make sure egl_timestamp_client_ |
| // in |presentation_helper_| is initialized after |use_egl_timestamp_| is |
| // initialized. |
| presentation_helper_ = |
| std::make_unique<GLSurfacePresentationHelper>(GetVSyncProvider()); |
| } |
| |
| bool NativeViewGLSurfaceEGL::InitializeNativeWindow() { |
| return true; |
| } |
| |
| void NativeViewGLSurfaceEGL::Destroy() { |
| presentation_helper_ = nullptr; |
| vsync_provider_internal_ = nullptr; |
| |
| if (surface_) { |
| if (!eglDestroySurface(display_->GetDisplay(), surface_)) { |
| LOG(ERROR) << "eglDestroySurface failed with error " |
| << GetLastEGLErrorString(); |
| } |
| surface_ = NULL; |
| } |
| } |
| |
| bool NativeViewGLSurfaceEGL::IsOffscreen() { |
| return false; |
| } |
| |
| gfx::SwapResult NativeViewGLSurfaceEGL::SwapBuffers( |
| PresentationCallback callback, |
| gfx::FrameData data) { |
| TRACE_EVENT2("gpu", "NativeViewGLSurfaceEGL:RealSwapBuffers", |
| "width", GetSize().width(), |
| "height", GetSize().height()); |
| |
| EGLuint64KHR new_frame_id = 0; |
| bool new_frame_id_is_valid = true; |
| if (use_egl_timestamps_) { |
| new_frame_id_is_valid = !!eglGetNextFrameIdANDROID(display_->GetDisplay(), |
| surface_, &new_frame_id); |
| } |
| if (!new_frame_id_is_valid) |
| new_frame_id = -1; |
| |
| GLSurfacePresentationHelper::ScopedSwapBuffers scoped_swap_buffers( |
| presentation_helper_.get(), std::move(callback), new_frame_id); |
| |
| if (!eglSwapBuffers(display_->GetDisplay(), surface_)) { |
| DVLOG(1) << "eglSwapBuffers failed with error " |
| << GetLastEGLErrorString(); |
| scoped_swap_buffers.set_result(gfx::SwapResult::SWAP_FAILED); |
| } else if (use_egl_timestamps_) { |
| UpdateSwapEvents(new_frame_id, new_frame_id_is_valid); |
| } |
| |
| return scoped_swap_buffers.result(); |
| } |
| |
| void NativeViewGLSurfaceEGL::UpdateSwapEvents(EGLuint64KHR newFrameId, |
| bool newFrameIdIsValid) { |
| // Queue info for the frame just swapped. |
| swap_info_queue_.push({newFrameIdIsValid, newFrameId}); |
| |
| // Make sure we have a frame old enough that all it's timstamps should |
| // be available by now. |
| constexpr int kFramesAgoToGetServerTimestamps = 4; |
| if (swap_info_queue_.size() <= kFramesAgoToGetServerTimestamps) |
| return; |
| |
| // TraceEvents if needed. |
| // If we weren't able to get a valid frame id before the swap, we can't get |
| // its timestamps now. |
| const SwapInfo& old_swap_info = swap_info_queue_.front(); |
| if (old_swap_info.frame_id_is_valid && *g_trace_swap_enabled.Get().value) |
| TraceSwapEvents(old_swap_info.frame_id); |
| |
| swap_info_queue_.pop(); |
| } |
| |
| void NativeViewGLSurfaceEGL::TraceSwapEvents(EGLuint64KHR oldFrameId) { |
| // We shouldn't be calling eglGetFrameTimestampsANDROID with more timestamps |
| // than it supports. |
| DCHECK_LE(supported_egl_timestamps_.size(), kMaxTimestampsSupportable); |
| |
| // Get the timestamps. |
| std::vector<EGLnsecsANDROID> egl_timestamps(supported_egl_timestamps_.size(), |
| EGL_TIMESTAMP_INVALID_ANDROID); |
| if (!eglGetFrameTimestampsANDROID( |
| display_->GetDisplay(), surface_, oldFrameId, |
| static_cast<EGLint>(supported_egl_timestamps_.size()), |
| supported_egl_timestamps_.data(), egl_timestamps.data())) { |
| TRACE_EVENT_INSTANT0("gpu", "eglGetFrameTimestamps:Failed", |
| TRACE_EVENT_SCOPE_THREAD); |
| return; |
| } |
| |
| // Track supported and valid time/name pairs. |
| struct TimeNamePair { |
| base::TimeTicks time; |
| const char* name; |
| }; |
| |
| std::vector<TimeNamePair> tracePairs; |
| tracePairs.reserve(supported_egl_timestamps_.size()); |
| for (size_t i = 0; i < egl_timestamps.size(); i++) { |
| // Although a timestamp of 0 is technically valid, we shouldn't expect to |
| // see it in practice. 0's are more likely due to a known linux kernel bug |
| // that inadvertently discards timestamp information when merging two |
| // retired fences. |
| if (egl_timestamps[i] == 0 || |
| egl_timestamps[i] == EGL_TIMESTAMP_INVALID_ANDROID || |
| egl_timestamps[i] == EGL_TIMESTAMP_PENDING_ANDROID) { |
| continue; |
| } |
| // TODO(brianderson): Replace FromInternalValue usage. |
| tracePairs.push_back( |
| {base::TimeTicks::FromInternalValue( |
| egl_timestamps[i] / base::TimeTicks::kNanosecondsPerMicrosecond), |
| supported_event_names_[i]}); |
| } |
| if (tracePairs.empty()) { |
| TRACE_EVENT_INSTANT0("gpu", "TraceSwapEvents:NoValidTimestamps", |
| TRACE_EVENT_SCOPE_THREAD); |
| return; |
| } |
| |
| // Sort the pairs so we can trace them in order. |
| std::sort(tracePairs.begin(), tracePairs.end(), |
| [](auto& a, auto& b) { return a.time < b.time; }); |
| |
| // Trace the overall range under which the sub events will be nested. |
| // Add an epsilon since the trace viewer interprets timestamp ranges |
| // as closed on the left and open on the right. i.e.: [begin, end). |
| // The last sub event isn't nested properly without the epsilon. |
| auto epsilon = base::Microseconds(1); |
| static const char* SwapEvents = "SwapEvents"; |
| const int64_t trace_id = oldFrameId; |
| TRACE_EVENT_NESTABLE_ASYNC_BEGIN_WITH_TIMESTAMP0( |
| kSwapEventTraceCategories, SwapEvents, trace_id, tracePairs.front().time); |
| TRACE_EVENT_NESTABLE_ASYNC_END_WITH_TIMESTAMP1( |
| kSwapEventTraceCategories, SwapEvents, trace_id, |
| tracePairs.back().time + epsilon, "id", trace_id); |
| |
| // Trace the first event, which does not have a range before it. |
| TRACE_EVENT_NESTABLE_ASYNC_INSTANT_WITH_TIMESTAMP0( |
| kSwapEventTraceCategories, tracePairs.front().name, trace_id, |
| tracePairs.front().time); |
| |
| // Trace remaining events and their ranges. |
| // Use the first characters to represent events still pending. |
| // This helps color code the remaining events in the viewer, which makes |
| // it obvious: |
| // 1) when the order of events are different between frames and |
| // 2) if multiple events occurred very close together. |
| std::string valid_symbols(tracePairs.size(), '\0'); |
| for (size_t i = 0; i < valid_symbols.size(); i++) |
| valid_symbols[i] = tracePairs[i].name[0]; |
| |
| const char* pending_symbols = valid_symbols.c_str(); |
| for (size_t i = 1; i < tracePairs.size(); i++) { |
| UNSAFE_TODO(pending_symbols++); |
| TRACE_EVENT_COPY_NESTABLE_ASYNC_BEGIN_WITH_TIMESTAMP0( |
| kSwapEventTraceCategories, pending_symbols, trace_id, |
| tracePairs[i - 1].time); |
| TRACE_EVENT_COPY_NESTABLE_ASYNC_END_WITH_TIMESTAMP0( |
| kSwapEventTraceCategories, pending_symbols, trace_id, |
| tracePairs[i].time); |
| TRACE_EVENT_NESTABLE_ASYNC_INSTANT_WITH_TIMESTAMP0( |
| kSwapEventTraceCategories, tracePairs[i].name, trace_id, |
| tracePairs[i].time); |
| } |
| } |
| |
| std::unique_ptr<gfx::VSyncProvider> |
| NativeViewGLSurfaceEGL::CreateVsyncProviderInternal() { |
| return nullptr; |
| } |
| |
| gfx::Size NativeViewGLSurfaceEGL::GetSize() { |
| EGLint width; |
| EGLint height; |
| if (!eglQuerySurface(display_->GetDisplay(), surface_, EGL_WIDTH, &width) || |
| !eglQuerySurface(display_->GetDisplay(), surface_, EGL_HEIGHT, &height)) { |
| LOG(ERROR) << "eglQuerySurface failed with error " |
| << GetLastEGLErrorString(); |
| return gfx::Size(); |
| } |
| |
| return gfx::Size(width, height); |
| } |
| |
| bool NativeViewGLSurfaceEGL::Resize(const gfx::Size& size, |
| float scale_factor, |
| const gfx::ColorSpace& color_space, |
| bool has_alpha) { |
| if (size == GetSize()) |
| return true; |
| size_ = size; |
| GLContext* context = GLContext::GetCurrent(); |
| DCHECK(context); |
| GLSurface* surface = GLSurface::GetCurrent(); |
| DCHECK(surface); |
| // Current surface may not be |this| if it is wrapped, but it should point to |
| // the same handle. |
| DCHECK_EQ(surface->GetHandle(), GetHandle()); |
| context->ReleaseCurrent(surface); |
| Destroy(); |
| if (!Initialize(format_)) { |
| LOG(ERROR) << "Failed to resize window."; |
| return false; |
| } |
| if (!context->MakeCurrent(surface)) { |
| LOG(ERROR) << "Failed to make current in NativeViewGLSurfaceEGL::Resize"; |
| return false; |
| } |
| SetVSyncEnabled(vsync_enabled_); |
| if (use_egl_timestamps_) { |
| eglSurfaceAttrib(display_->GetDisplay(), surface_, EGL_TIMESTAMPS_ANDROID, |
| EGL_TRUE); |
| } |
| return true; |
| } |
| |
| bool NativeViewGLSurfaceEGL::Recreate() { |
| GLContext* context = GLContext::GetCurrent(); |
| DCHECK(context); |
| GLSurface* surface = GLSurface::GetCurrent(); |
| DCHECK(surface); |
| // Current surface may not be |this| if it is wrapped, but it should point to |
| // the same handle. |
| DCHECK_EQ(surface->GetHandle(), GetHandle()); |
| context->ReleaseCurrent(surface); |
| Destroy(); |
| if (!Initialize(format_)) { |
| LOG(ERROR) << "Failed to create surface."; |
| return false; |
| } |
| if (!context->MakeCurrent(surface)) { |
| LOG(ERROR) << "Failed to make current in NativeViewGLSurfaceEGL::Recreate"; |
| return false; |
| } |
| SetVSyncEnabled(vsync_enabled_); |
| if (use_egl_timestamps_) { |
| eglSurfaceAttrib(display_->GetDisplay(), surface_, EGL_TIMESTAMPS_ANDROID, |
| EGL_TRUE); |
| } |
| return true; |
| } |
| |
| EGLSurface NativeViewGLSurfaceEGL::GetHandle() { |
| return surface_; |
| } |
| |
| bool NativeViewGLSurfaceEGL::SupportsPostSubBuffer() { |
| return supports_post_sub_buffer_; |
| } |
| |
| gfx::SurfaceOrigin NativeViewGLSurfaceEGL::GetOrigin() const { |
| return surface_origin_; |
| } |
| |
| EGLTimestampClient* NativeViewGLSurfaceEGL::GetEGLTimestampClient() { |
| // This api call is used by GLSurfacePresentationHelper class which is member |
| // of this class NativeViewGLSurfaceEGL. Hence its guaranteed "this" pointer |
| // will live longer than the GLSurfacePresentationHelper class. |
| return this; |
| } |
| |
| bool NativeViewGLSurfaceEGL::IsEGLTimestampSupported() const { |
| return use_egl_timestamps_; |
| } |
| |
| bool NativeViewGLSurfaceEGL::GetFrameTimestampInfoIfAvailable( |
| base::TimeTicks* presentation_time, |
| base::TimeDelta* composite_interval, |
| base::TimeTicks* writes_done_time, |
| uint32_t* presentation_flags, |
| int frame_id) { |
| DCHECK(presentation_time); |
| DCHECK(composite_interval); |
| DCHECK(presentation_flags); |
| |
| TRACE_EVENT1("gpu", "NativeViewGLSurfaceEGL:GetFrameTimestampInfoIfAvailable", |
| "frame_id", frame_id); |
| |
| // Get the composite interval. |
| EGLint interval_name = EGL_COMPOSITE_INTERVAL_ANDROID; |
| EGLnsecsANDROID composite_interval_ns = 0; |
| *presentation_flags = 0; |
| |
| // If an error is generated, we will treat it as a frame done for timestamp |
| // reporting purpose. |
| if (!eglGetCompositorTimingANDROID(GetEGLDisplay(), surface_, 1, |
| &interval_name, &composite_interval_ns)) { |
| *composite_interval = |
| base::Nanoseconds(base::TimeTicks::kNanosecondsPerSecond / 60); |
| // If we couldn't get the correct presentation time due to some errors, |
| // return the current time. |
| *presentation_time = base::TimeTicks::Now(); |
| return true; |
| } |
| |
| // If the composite interval is pending, the frame is not yet done. |
| if (composite_interval_ns == EGL_TIMESTAMP_PENDING_ANDROID) { |
| return false; |
| } |
| DCHECK_GT(composite_interval_ns, 0); |
| *composite_interval = base::Nanoseconds(composite_interval_ns); |
| |
| // Get the all available timestamps for the frame. If a frame is invalid or |
| // an error is generated, we will treat it as a frame done for timestamp |
| // reporting purpose. |
| std::vector<EGLnsecsANDROID> egl_timestamps(supported_egl_timestamps_.size(), |
| EGL_TIMESTAMP_INVALID_ANDROID); |
| |
| // TODO(vikassoni): File a driver bug for eglGetFrameTimestampsANDROID(). |
| // See https://bugs.chromium.org/p/chromium/issues/detail?id=966638. |
| // As per the spec, the driver is expected to return a valid timestamp from |
| // the call eglGetFrameTimestampsANDROID() when its not |
| // EGL_TIMESTAMP_PENDING_ANDROID or EGL_TIMESTAMP_INVALID_ANDROID. But |
| // currently some buggy drivers an invalid timestamp 0. |
| // This is currentlt handled in chrome for by setting the presentation time to |
| // TimeTicks::Now() (snapped to the next vsync) instead of 0. |
| if ((frame_id < 0) || |
| !eglGetFrameTimestampsANDROID( |
| display_->GetDisplay(), surface_, frame_id, |
| static_cast<EGLint>(supported_egl_timestamps_.size()), |
| supported_egl_timestamps_.data(), egl_timestamps.data())) { |
| // If we couldn't get the correct presentation time due to some errors, |
| // return the current time. |
| *presentation_time = base::TimeTicks::Now(); |
| return true; |
| } |
| DCHECK_GE(presentation_feedback_index_, 0); |
| DCHECK_GE(composition_start_index_, 0); |
| |
| // Get the presentation time. |
| EGLnsecsANDROID presentation_time_ns = |
| egl_timestamps[presentation_feedback_index_]; |
| |
| // If the presentation time is pending, the frame is not yet done. |
| if (presentation_time_ns == EGL_TIMESTAMP_PENDING_ANDROID) { |
| return false; |
| } |
| if (presentation_time_ns == EGL_TIMESTAMP_INVALID_ANDROID) { |
| presentation_time_ns = egl_timestamps[composition_start_index_]; |
| if (presentation_time_ns == EGL_TIMESTAMP_INVALID_ANDROID || |
| presentation_time_ns == EGL_TIMESTAMP_PENDING_ANDROID) { |
| *presentation_time = base::TimeTicks::Now(); |
| } else { |
| *presentation_time = |
| base::TimeTicks() + base::Nanoseconds(presentation_time_ns); |
| } |
| } else { |
| *presentation_time = |
| base::TimeTicks() + base::Nanoseconds(presentation_time_ns); |
| *presentation_flags = presentation_flags_; |
| } |
| |
| // Get the WritesDone time if available, otherwise set to a null TimeTicks. |
| EGLnsecsANDROID writes_done_time_ns = egl_timestamps[writes_done_index_]; |
| if (writes_done_time_ns == EGL_TIMESTAMP_INVALID_ANDROID || |
| writes_done_time_ns == EGL_TIMESTAMP_PENDING_ANDROID) { |
| *writes_done_time = base::TimeTicks(); |
| } else { |
| *writes_done_time = |
| base::TimeTicks() + base::Nanoseconds(writes_done_time_ns); |
| } |
| |
| return true; |
| } |
| |
| gfx::SwapResult NativeViewGLSurfaceEGL::SwapBuffersWithDamage( |
| const std::vector<int>& rects, |
| PresentationCallback callback, |
| gfx::FrameData data) { |
| DCHECK(supports_swap_buffer_with_damage_); |
| |
| GLSurfacePresentationHelper::ScopedSwapBuffers scoped_swap_buffers( |
| presentation_helper_.get(), std::move(callback)); |
| if (!eglSwapBuffersWithDamageKHR(display_->GetDisplay(), surface_, |
| const_cast<EGLint*>(rects.data()), |
| static_cast<EGLint>(rects.size() / 4))) { |
| DVLOG(1) << "eglSwapBuffersWithDamageKHR failed with error " |
| << GetLastEGLErrorString(); |
| scoped_swap_buffers.set_result(gfx::SwapResult::SWAP_FAILED); |
| } |
| return scoped_swap_buffers.result(); |
| } |
| |
| gfx::SwapResult NativeViewGLSurfaceEGL::PostSubBuffer( |
| int x, |
| int y, |
| int width, |
| int height, |
| PresentationCallback callback, |
| gfx::FrameData data) { |
| TRACE_EVENT2("gpu", "NativeViewGLSurfaceEGL:PostSubBuffer", "width", width, |
| "height", height); |
| DCHECK(supports_post_sub_buffer_); |
| if (surface_origin_ == gfx::SurfaceOrigin::kTopLeft) { |
| // With EGL_SURFACE_ORIENTATION_INVERT_Y_ANGLE the contents are rendered |
| // inverted, but the PostSubBuffer rectangle is still measured from the |
| // bottom left. |
| y = GetSize().height() - y - height; |
| } |
| |
| GLSurfacePresentationHelper::ScopedSwapBuffers scoped_swap_buffers( |
| presentation_helper_.get(), std::move(callback)); |
| if (!eglPostSubBufferNV(GetEGLDisplay(), surface_, x, y, width, height)) { |
| DVLOG(1) << "eglPostSubBufferNV failed with error " |
| << GetLastEGLErrorString(); |
| scoped_swap_buffers.set_result(gfx::SwapResult::SWAP_FAILED); |
| } |
| return scoped_swap_buffers.result(); |
| } |
| |
| bool NativeViewGLSurfaceEGL::OnMakeCurrent(GLContext* context) { |
| if (presentation_helper_) |
| presentation_helper_->OnMakeCurrent(context, this); |
| return GLSurfaceEGL::OnMakeCurrent(context); |
| } |
| |
| gfx::VSyncProvider* NativeViewGLSurfaceEGL::GetVSyncProvider() { |
| return vsync_provider_external_ ? vsync_provider_external_.get() |
| : vsync_provider_internal_.get(); |
| } |
| |
| void NativeViewGLSurfaceEGL::SetVSyncEnabled(bool enabled) { |
| DCHECK(GLContext::GetCurrent() && GLContext::GetCurrent()->IsCurrent(this)); |
| vsync_enabled_ = enabled; |
| if (!eglSwapInterval(display_->GetDisplay(), enabled ? 1 : 0)) { |
| LOG(ERROR) << "eglSwapInterval failed with error " |
| << GetLastEGLErrorString(); |
| } |
| } |
| |
| NativeViewGLSurfaceEGL::~NativeViewGLSurfaceEGL() { |
| InvalidateWeakPtrs(); |
| Destroy(); |
| } |
| |
| PbufferGLSurfaceEGL::PbufferGLSurfaceEGL(GLDisplayEGL* display, |
| const gfx::Size& size) |
| : GLSurfaceEGL(display), size_(size), surface_(nullptr) { |
| // Some implementations of Pbuffer do not support having a 0 size. For such |
| // cases use a (1, 1) surface. |
| if (size_.GetArea() == 0) |
| size_.SetSize(1, 1); |
| } |
| |
| bool PbufferGLSurfaceEGL::Initialize(GLSurfaceFormat format) { |
| if (display_->GetDisplay() == EGL_NO_DISPLAY) { |
| LOG(ERROR) << "Trying to create PbufferGLSurfaceEGL with invalid " |
| << "display."; |
| return false; |
| } |
| |
| if (!GetConfig()) { |
| LOG(ERROR) << "No suitable EGL configs found for initialization."; |
| return false; |
| } |
| |
| EGLSurface old_surface = surface_; |
| |
| #if BUILDFLAG(IS_ANDROID) |
| // This is to allow context virtualization which requires on- and offscreen |
| // to use a compatible config. We expect the client to request RGB565 |
| // onscreen surface also for this to work (with the exception of |
| // fullscreen video). |
| if (base::SysInfo::AmountOfPhysicalMemoryMB() <= 512) |
| format.SetRGB565(); |
| #endif |
| |
| format_ = format; |
| |
| // Allocate the new pbuffer surface before freeing the old one to ensure |
| // they have different addresses. If they have the same address then a |
| // future call to MakeCurrent might early out because it appears the current |
| // context and surface have not changed. |
| std::vector<EGLint> pbuffer_attribs; |
| pbuffer_attribs.push_back(EGL_WIDTH); |
| pbuffer_attribs.push_back(size_.width()); |
| pbuffer_attribs.push_back(EGL_HEIGHT); |
| pbuffer_attribs.push_back(size_.height()); |
| |
| // Enable robust resource init when using SwANGLE |
| if (IsSoftwareGLImplementation(GetGLImplementationParts()) && |
| display_->ext->b_EGL_ANGLE_robust_resource_initialization) { |
| pbuffer_attribs.push_back(EGL_ROBUST_RESOURCE_INITIALIZATION_ANGLE); |
| pbuffer_attribs.push_back(EGL_TRUE); |
| } |
| |
| // Append final EGL_NONE to signal the pbuffer attributes are finished |
| pbuffer_attribs.push_back(EGL_NONE); |
| pbuffer_attribs.push_back(EGL_NONE); |
| |
| EGLSurface new_surface = eglCreatePbufferSurface( |
| display_->GetDisplay(), GetConfig(), &pbuffer_attribs[0]); |
| if (!new_surface) { |
| LOG(ERROR) << "eglCreatePbufferSurface failed with error " |
| << GetLastEGLErrorString(); |
| return false; |
| } |
| |
| if (old_surface) |
| eglDestroySurface(display_->GetDisplay(), old_surface); |
| |
| surface_ = new_surface; |
| return true; |
| } |
| |
| void PbufferGLSurfaceEGL::Destroy() { |
| if (surface_) { |
| if (!eglDestroySurface(display_->GetDisplay(), surface_)) { |
| LOG(ERROR) << "eglDestroySurface failed with error " |
| << GetLastEGLErrorString(); |
| } |
| surface_ = NULL; |
| } |
| } |
| |
| bool PbufferGLSurfaceEGL::IsOffscreen() { |
| return true; |
| } |
| |
| gfx::SwapResult PbufferGLSurfaceEGL::SwapBuffers(PresentationCallback callback, |
| gfx::FrameData data) { |
| NOTREACHED() << "Attempted to call SwapBuffers on a PbufferGLSurfaceEGL."; |
| } |
| |
| gfx::Size PbufferGLSurfaceEGL::GetSize() { |
| return size_; |
| } |
| |
| bool PbufferGLSurfaceEGL::Resize(const gfx::Size& size, |
| float scale_factor, |
| const gfx::ColorSpace& color_space, |
| bool has_alpha) { |
| if (size == size_) |
| return true; |
| |
| size_ = size; |
| |
| GLContext* context = GLContext::GetCurrent(); |
| DCHECK(context); |
| GLSurface* surface = GLSurface::GetCurrent(); |
| DCHECK(surface); |
| // Current surface may not be |this| if it is wrapped, but it should point to |
| // the same handle. |
| DCHECK_EQ(surface->GetHandle(), GetHandle()); |
| context->ReleaseCurrent(surface); |
| |
| if (!Initialize(format_)) { |
| LOG(ERROR) << "Failed to resize pbuffer."; |
| return false; |
| } |
| |
| if (!context->MakeCurrent(surface)) { |
| LOG(ERROR) << "Failed to make current in PbufferGLSurfaceEGL::Resize"; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| EGLSurface PbufferGLSurfaceEGL::GetHandle() { |
| return surface_; |
| } |
| |
| void* PbufferGLSurfaceEGL::GetShareHandle() { |
| #if BUILDFLAG(IS_ANDROID) |
| NOTREACHED(); |
| #else |
| if (!display_->ext->b_EGL_ANGLE_query_surface_pointer) |
| return nullptr; |
| |
| if (!display_->ext->b_EGL_ANGLE_surface_d3d_texture_2d_share_handle) |
| return nullptr; |
| |
| void* handle; |
| if (!eglQuerySurfacePointerANGLE(display_->GetDisplay(), GetHandle(), |
| EGL_D3D_TEXTURE_2D_SHARE_HANDLE_ANGLE, |
| &handle)) { |
| return nullptr; |
| } |
| |
| return handle; |
| #endif |
| } |
| |
| PbufferGLSurfaceEGL::~PbufferGLSurfaceEGL() { |
| InvalidateWeakPtrs(); |
| Destroy(); |
| } |
| |
| SurfacelessEGL::SurfacelessEGL(GLDisplayEGL* display, const gfx::Size& size) |
| : GLSurfaceEGL(display), size_(size) {} |
| |
| bool SurfacelessEGL::Initialize(GLSurfaceFormat format) { |
| format_ = format; |
| return true; |
| } |
| |
| void SurfacelessEGL::Destroy() { |
| } |
| |
| bool SurfacelessEGL::IsOffscreen() { |
| return true; |
| } |
| |
| bool SurfacelessEGL::IsSurfaceless() const { |
| return true; |
| } |
| |
| gfx::SwapResult SurfacelessEGL::SwapBuffers(PresentationCallback callback, |
| gfx::FrameData data) { |
| LOG(ERROR) << "Attempted to call SwapBuffers with SurfacelessEGL."; |
| return gfx::SwapResult::SWAP_FAILED; |
| } |
| |
| gfx::Size SurfacelessEGL::GetSize() { |
| return size_; |
| } |
| |
| bool SurfacelessEGL::Resize(const gfx::Size& size, |
| float scale_factor, |
| const gfx::ColorSpace& color_space, |
| bool has_alpha) { |
| size_ = size; |
| return true; |
| } |
| |
| EGLSurface SurfacelessEGL::GetHandle() { |
| return EGL_NO_SURFACE; |
| } |
| |
| void* SurfacelessEGL::GetShareHandle() { |
| return nullptr; |
| } |
| |
| SurfacelessEGL::~SurfacelessEGL() { |
| InvalidateWeakPtrs(); |
| } |
| |
| } // namespace gl |