/* * Copyright 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "EGL.h" #include #include #include #define LOG_TAG "Swappy::EGL" #include "SwappyLog.h" using namespace std::chrono_literals; namespace swappy { std::unique_ptr EGL::create(std::chrono::nanoseconds fenceTimeout) { auto eglLib = dlopen("libEGL.so", RTLD_LAZY | RTLD_LOCAL); if (eglLib == nullptr) { SWAPPY_LOGE("Can't load libEGL"); return nullptr; } auto eglGetProcAddress = reinterpret_cast( dlsym(eglLib, "eglGetProcAddress")); if (eglGetProcAddress == nullptr) { SWAPPY_LOGE("Failed to load eglGetProcAddress"); return nullptr; } auto eglSwapBuffers = reinterpret_cast(dlsym(eglLib, "eglSwapBuffers")); if (eglSwapBuffers == nullptr) { SWAPPY_LOGE("Failed to load eglSwapBuffers"); return nullptr; } auto eglPresentationTimeANDROID = reinterpret_cast( eglGetProcAddress("eglPresentationTimeANDROID")); if (eglPresentationTimeANDROID == nullptr) { SWAPPY_LOGE("Failed to load eglPresentationTimeANDROID"); return nullptr; } auto eglCreateSyncKHR = reinterpret_cast( eglGetProcAddress("eglCreateSyncKHR")); if (eglCreateSyncKHR == nullptr) { SWAPPY_LOGE("Failed to load eglCreateSyncKHR"); return nullptr; } auto eglDestroySyncKHR = reinterpret_cast( eglGetProcAddress("eglDestroySyncKHR")); if (eglDestroySyncKHR == nullptr) { SWAPPY_LOGE("Failed to load eglDestroySyncKHR"); return nullptr; } auto eglGetSyncAttribKHR = reinterpret_cast( eglGetProcAddress("eglGetSyncAttribKHR")); if (eglGetSyncAttribKHR == nullptr) { SWAPPY_LOGE("Failed to load eglGetSyncAttribKHR"); return nullptr; } auto eglClientWaitSyncKHR = reinterpret_cast( eglGetProcAddress("eglClientWaitSyncKHR")); if (eglClientWaitSyncKHR == nullptr) { SWAPPY_LOGE("Failed to load eglClientWaitSyncKHR"); return nullptr; } auto eglGetError = reinterpret_cast(eglGetProcAddress("eglGetError")); if (eglGetError == nullptr) { SWAPPY_LOGE("Failed to load eglGetError"); return nullptr; } auto eglSurfaceAttrib = reinterpret_cast( eglGetProcAddress("eglSurfaceAttrib")); if (eglSurfaceAttrib == nullptr) { SWAPPY_LOGE("Failed to load eglSurfaceAttrib"); return nullptr; } // stats may not be supported on all versions auto eglGetNextFrameIdANDROID = reinterpret_cast( eglGetProcAddress("eglGetNextFrameIdANDROID")); if (eglGetNextFrameIdANDROID == nullptr) { SWAPPY_LOGI("Failed to load eglGetNextFrameIdANDROID"); } auto eglGetFrameTimestampsANDROID = reinterpret_cast( eglGetProcAddress("eglGetFrameTimestampsANDROID")); if (eglGetFrameTimestampsANDROID == nullptr) { SWAPPY_LOGI("Failed to load eglGetFrameTimestampsANDROID"); } auto egl = std::make_unique(fenceTimeout, eglGetProcAddress, ConstructorTag{}); egl->eglLib = eglLib; egl->eglSwapBuffers = eglSwapBuffers; egl->eglGetProcAddress = eglGetProcAddress; egl->eglPresentationTimeANDROID = eglPresentationTimeANDROID; egl->eglCreateSyncKHR = eglCreateSyncKHR; egl->eglClientWaitSyncKHR = eglClientWaitSyncKHR; egl->eglDestroySyncKHR = eglDestroySyncKHR; egl->eglGetSyncAttribKHR = eglGetSyncAttribKHR; egl->eglGetError = eglGetError; egl->eglSurfaceAttrib = eglSurfaceAttrib; egl->eglGetNextFrameIdANDROID = eglGetNextFrameIdANDROID; egl->eglGetFrameTimestampsANDROID = eglGetFrameTimestampsANDROID; std::lock_guard lock(egl->mWaiterThreadContext.lock); egl->mWaiterThreadContext.thread = Thread([egl = egl.get()]() { egl->waitForFenceThreadMain(); }); return egl; } EGL::~EGL() { // Stop the fence waiter thread { std::lock_guard lock(mWaiterThreadContext.lock); mWaiterThreadContext.running = false; mWaiterThreadContext.condition.notify_one(); } mWaiterThreadContext.thread.join(); while (mWaitPendingSyncs.size() > 0) { auto sync = mWaitPendingSyncs.front(); mWaitPendingSyncs.pop_front(); // There is no need to wait here as the API allows for queueing pending // sync for deleting. EGLBoolean result = eglDestroySyncKHR(sync.display, sync.fence); if (result == EGL_FALSE) { SWAPPY_LOGE("Failed to destroy sync fence"); } } if (eglLib) { dlclose(eglLib); } } bool EGL::setPresentationTime(EGLDisplay display, EGLSurface surface, std::chrono::steady_clock::time_point time) { eglPresentationTimeANDROID(display, surface, time.time_since_epoch().count()); return EGL_TRUE; } bool EGL::statsSupported() { return (eglGetNextFrameIdANDROID != nullptr && eglGetFrameTimestampsANDROID != nullptr); } std::pair EGL::getNextFrameId(EGLDisplay dpy, EGLSurface surface) const { if (eglGetNextFrameIdANDROID == nullptr) { SWAPPY_LOGE("stats are not supported on this platform"); return {false, 0}; } EGLuint64KHR frameId; EGLBoolean result = eglGetNextFrameIdANDROID(dpy, surface, &frameId); if (result == EGL_FALSE) { SWAPPY_LOGE("Failed to get next frame ID"); return {false, 0}; } return {true, frameId}; } std::unique_ptr EGL::getFrameTimestamps( EGLDisplay dpy, EGLSurface surface, EGLuint64KHR frameId) const { #if (not defined ANDROID_NDK_VERSION) || ANDROID_NDK_VERSION >= 15 if (eglGetFrameTimestampsANDROID == nullptr) { SWAPPY_LOGE("stats are not supported on this platform"); return nullptr; } const std::vector timestamps = { EGL_REQUESTED_PRESENT_TIME_ANDROID, EGL_RENDERING_COMPLETE_TIME_ANDROID, EGL_COMPOSITION_LATCH_TIME_ANDROID, EGL_DISPLAY_PRESENT_TIME_ANDROID, }; std::vector values(timestamps.size()); EGLBoolean result = eglGetFrameTimestampsANDROID(dpy, surface, frameId, timestamps.size(), timestamps.data(), values.data()); if (result == EGL_FALSE) { EGLint reason = eglGetError(); if (reason == EGL_BAD_SURFACE) { eglSurfaceAttrib(dpy, surface, EGL_TIMESTAMPS_ANDROID, EGL_TRUE); } else { SWAPPY_LOGE_ONCE("Failed to get timestamps for frame %llu", (unsigned long long)frameId); } return nullptr; } // try again if we got some pending stats for (auto i : values) { if (i == EGL_TIMESTAMP_PENDING_ANDROID) return nullptr; } std::unique_ptr frameTimestamps = std::make_unique(); frameTimestamps->requested = values[0]; frameTimestamps->renderingCompleted = values[1]; frameTimestamps->compositionLatched = values[2]; frameTimestamps->presented = values[3]; return frameTimestamps; #else return nullptr; #endif } void EGL::insertSyncFence(EGLDisplay display) { EGLSyncKHR sync_fence = eglCreateSyncKHR(display, EGL_SYNC_FENCE_KHR, nullptr); if (sync_fence != EGL_NO_SYNC_KHR) { EGLSync sync = {display, sync_fence}; // kick off the thread work to wait for the fence and measure its time std::lock_guard lock(mWaiterThreadContext.lock); mWaitPendingSyncs.push_back(sync); mWaiterThreadContext.hasPendingWork = true; mWaiterThreadContext.condition.notify_all(); } else { SWAPPY_LOGE("Failed to create sync fence"); } } bool EGL::lastFrameIsComplete(EGLDisplay display, bool pipelineMode) { std::lock_guard lock(mWaiterThreadContext.lock); if (pipelineMode) { // We are in pipeline mode so we need to check the fence of frame N-1 return mWaitPendingSyncs.size() < 2; } // We are not in pipeline mode so we need to check the fence of the current // frame. i.e. there are not unsignaled frames return mWaitPendingSyncs.empty(); } void EGL::waitForFenceThreadMain() { while (true) { bool waitingSyncsEmpty; { std::lock_guard lock(mWaiterThreadContext.lock); mWaiterThreadContext.condition.wait( mWaiterThreadContext.lock, [&]() REQUIRES(mWaiterThreadContext.lock) { return mWaiterThreadContext.hasPendingWork || !mWaiterThreadContext.running; }); mWaiterThreadContext.hasPendingWork = false; if (!mWaiterThreadContext.running) { break; } waitingSyncsEmpty = mWaitPendingSyncs.empty(); } // No other consumers can empty the syncs while this thread is running, // the destructor of EGL waits for this thread to finish before emptying // the pending syncs. while (!waitingSyncsEmpty) { EGLSync sync; { // Get the latest fence to wait on. std::lock_guard lock(mWaiterThreadContext.lock); sync = mWaitPendingSyncs.front(); } gamesdk::ScopedTrace tracer("Swappy: GPU frame time"); const auto startTime = std::chrono::steady_clock::now(); EGLBoolean result = eglClientWaitSyncKHR(sync.display, sync.fence, 0, mFenceTimeout.count()); switch (result) { case EGL_FALSE: SWAPPY_LOGE("Failed to wait sync"); break; case EGL_TIMEOUT_EXPIRED_KHR: SWAPPY_LOGE("Timeout waiting for fence"); break; } mFencePendingTime = std::chrono::steady_clock::now() - startTime; { std::lock_guard lock(mWaiterThreadContext.lock); mWaitPendingSyncs.pop_front(); // Once the wait has timed out/succeeded, we can submit it for // deletion as the API allows for pending syncs to be queued for // deletion. result = eglDestroySyncKHR(sync.display, sync.fence); if (result == EGL_FALSE) { SWAPPY_LOGE("Failed to destroy sync fence"); } waitingSyncsEmpty = mWaitPendingSyncs.empty(); } } } } } // namespace swappy