/* * Copyright (C) 2016 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 "EmulatedEglFenceSync.h" #include #include "OpenGLESDispatch/DispatchTables.h" #include "OpenGLESDispatch/EGLDispatch.h" #include "RenderThreadInfoGl.h" #include "StalePtrRegistry.h" #include "aemu/base/containers/Lookup.h" #include "aemu/base/containers/StaticMap.h" #include "aemu/base/files/StreamSerializing.h" #include "aemu/base/synchronization/Lock.h" namespace gfxstream { namespace gl { namespace { using android::base::AutoLock; using android::base::Lock; using android::base::StaticMap; // Timeline class is meant to delete native fences after the // sync device has incremented the timeline. We assume a // maximum number of outstanding timelines in the guest (16) in // order to derive when a native fence is definitely safe to // delete. After at least that many timeline increments have // happened, we sweep away the remaining native fences. // The function that performs the deleting, // incrementTimelineAndDeleteOldFences(), happens on the SyncThread. class Timeline { public: Timeline() = default; static constexpr int kMaxGuestTimelines = 16; void addFence(EmulatedEglFenceSync* fence) { mFences.set(fence, mTime.load() + kMaxGuestTimelines); } void incrementTimelineAndDeleteOldFences() { ++mTime; sweep(); } void sweep() { mFences.eraseIf([time = mTime.load()](EmulatedEglFenceSync* fence, int fenceTime) { EmulatedEglFenceSync* actual = EmulatedEglFenceSync::getFromHandle((uint64_t)(uintptr_t)fence); if (!actual) return true; bool shouldErase = fenceTime <= time; if (shouldErase) { if (!actual->decRef() && actual->shouldDestroyWhenSignaled()) { actual->decRef(); } } return shouldErase; }); } private: std::atomic mTime {0}; StaticMap mFences; }; static Timeline* sTimeline() { static Timeline* t = new Timeline; return t; } } // namespace // static void EmulatedEglFenceSync::incrementTimelineAndDeleteOldFences() { sTimeline()->incrementTimelineAndDeleteOldFences(); } // static std::unique_ptr EmulatedEglFenceSync::create( EGLDisplay display, bool hasNativeFence, bool destroyWhenSignaled) { auto sync = s_egl.eglCreateSyncKHR(display, EGL_SYNC_FENCE_KHR, nullptr); if (sync == EGL_NO_SYNC_KHR) { ERR("Failed to create EGL fence sync: %d", s_egl.eglGetError()); return nullptr; } // This MUST be present, or we get a deadlock effect. s_gles2.glFlush(); return std::unique_ptr( new EmulatedEglFenceSync(display, sync, hasNativeFence, destroyWhenSignaled)); } EmulatedEglFenceSync::EmulatedEglFenceSync(EGLDisplay display, EGLSyncKHR sync, bool hasNativeFence, bool destroyWhenSignaled) : mDestroyWhenSignaled(destroyWhenSignaled), mDisplay(display), mSync(sync) { addToRegistry(); assert(mCount == 1); if (hasNativeFence) { incRef(); sTimeline()->addFence(this); } // Assumes that there is a valid + current OpenGL context assert(RenderThreadInfoGl::get()); } EmulatedEglFenceSync::~EmulatedEglFenceSync() { removeFromRegistry(); } EGLint EmulatedEglFenceSync::wait(uint64_t timeout) { incRef(); EGLint wait_res = s_egl.eglClientWaitSyncKHR(mDisplay, mSync, EGL_SYNC_FLUSH_COMMANDS_BIT_KHR, timeout); decRef(); return wait_res; } void EmulatedEglFenceSync::waitAsync() { s_egl.eglWaitSyncKHR(mDisplay, mSync, 0); } bool EmulatedEglFenceSync::isSignaled() { EGLint val; if (EGL_TRUE == s_egl.eglGetSyncAttribKHR( mDisplay, mSync, EGL_SYNC_STATUS_KHR, &val)) return val == EGL_SIGNALED_KHR; return true; // if invalid, treat as signaled } void EmulatedEglFenceSync::destroy() { s_egl.eglDestroySyncKHR(mDisplay, mSync); } // Snapshots for EmulatedEglFenceSync////////////////////////////////////////////////////// // It's possible, though it does not happen often, that a fence // can be created but not yet waited on by the guest, which // needs careful handling: // // 1. Avoid manipulating garbage memory on snapshot restore; // rcCreateSyncKHR *creates new fence in valid memory* // --snapshot-- // rcClientWaitSyncKHR *refers to uninitialized memory* // rcDestroySyncKHR *refers to uninitialized memory* // 2. Make rcCreateSyncKHR/rcDestroySyncKHR implementations return // the "signaled" status if referring to previous snapshot fences. It's // assumed that the GPU is long done with them. // 3. Avoid name collisions where a new EmulatedEglFenceSync object is created // that has the same uint64_t casting as a EmulatedEglFenceSync object from a previous // snapshot. // Maintain a StalePtrRegistry: static StalePtrRegistry* sFenceRegistry() { static StalePtrRegistry* s = new StalePtrRegistry; return s; } // static void EmulatedEglFenceSync::addToRegistry() { sFenceRegistry()->addPtr(this); } // static void EmulatedEglFenceSync::removeFromRegistry() { sFenceRegistry()->removePtr(this); } // static void EmulatedEglFenceSync::onSave(android::base::Stream* stream) { sFenceRegistry()->makeCurrentPtrsStale(); sFenceRegistry()->onSave(stream); } // static void EmulatedEglFenceSync::onLoad(android::base::Stream* stream) { sFenceRegistry()->onLoad(stream); } // static EmulatedEglFenceSync* EmulatedEglFenceSync::getFromHandle(uint64_t handle) { return sFenceRegistry()->getPtr(handle); } } // namespace gl } // namespace gfxstream