/* * 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 "SyncThread.h" #if GFXSTREAM_ENABLE_HOST_GLES #include "OpenGLESDispatch/OpenGLDispatchLoader.h" #endif #include "aemu/base/Metrics.h" #include "aemu/base/system/System.h" #include "aemu/base/threads/Thread.h" #include "gfxstream/host/Tracing.h" #include "host-common/GfxstreamFatalError.h" #include "host-common/crash_reporter.h" #include "host-common/logging.h" #include "host-common/sync_device.h" #ifndef _MSC_VER #include #endif #include namespace gfxstream { using android::base::EventHangMetadata; using emugl::ABORT_REASON_OTHER; using emugl::FatalError; #if GFXSTREAM_ENABLE_HOST_GLES using gl::EGLDispatch; using gl::EmulatedEglFenceSync; #endif #define DEBUG 0 #if DEBUG static uint64_t curr_ms() { struct timeval tv; gettimeofday(&tv, NULL); return tv.tv_usec / 1000 + tv.tv_sec * 1000; } #define DPRINT(fmt, ...) do { \ if (!VERBOSE_CHECK(syncthreads)) VERBOSE_ENABLE(syncthreads); \ VERBOSE_TID_FUNCTION_DPRINT(syncthreads, "@ time=%llu: " fmt, curr_ms(), ##__VA_ARGS__); \ } while(0) #else #define DPRINT(...) #endif #define SYNC_THREAD_CHECK(condition) \ do { \ if (!(condition)) { \ GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER)) << \ #condition << " is false"; \ } \ } while (0) // The single global sync thread instance. class GlobalSyncThread { public: GlobalSyncThread() = default; void initialize(bool hasGl, HealthMonitor<>* healthMonitor) { AutoLock mutex(mLock); SYNC_THREAD_CHECK(!mSyncThread); mSyncThread = std::make_unique(hasGl, healthMonitor); } SyncThread* syncThreadPtr() { AutoLock mutex(mLock); return mSyncThread.get(); } void destroy() { AutoLock mutex(mLock); mSyncThread = nullptr; } private: std::unique_ptr mSyncThread = nullptr; // lock for the access to this object android::base::Lock mLock; using AutoLock = android::base::AutoLock; }; static GlobalSyncThread* sGlobalSyncThread() { static GlobalSyncThread* t = new GlobalSyncThread; return t; } static const uint32_t kTimelineInterval = 1; static const uint64_t kDefaultTimeoutNsecs = 5ULL * 1000ULL * 1000ULL * 1000ULL; SyncThread::SyncThread(bool hasGl, HealthMonitor<>* healthMonitor) : android::base::Thread(android::base::ThreadFlags::MaskSignals, 512 * 1024), mWorkerThreadPool(kNumWorkerThreads, [this](Command&& command, ThreadPool::WorkerId id) { doSyncThreadCmd(std::move(command), id); }), mHasGl(hasGl), mHealthMonitor(healthMonitor) { this->start(); mWorkerThreadPool.start(); #if GFXSTREAM_ENABLE_HOST_GLES if (hasGl) { initSyncEGLContext(); } #endif } SyncThread::~SyncThread() { cleanup(); } #if GFXSTREAM_ENABLE_HOST_GLES void SyncThread::triggerWait(EmulatedEglFenceSync* fenceSync, uint64_t timeline) { std::stringstream ss; ss << "triggerWait fenceSyncInfo=0x" << std::hex << reinterpret_cast(fenceSync) << " timeline=0x" << std::hex << timeline; sendAsync( [fenceSync, timeline, this](WorkerId) { doSyncWait(fenceSync, [timeline] { DPRINT("wait done (with fence), use goldfish sync timeline inc"); emugl::emugl_sync_timeline_inc(timeline, kTimelineInterval); }); }, ss.str()); } void SyncThread::triggerWaitWithCompletionCallback(EmulatedEglFenceSync* fenceSync, FenceCompletionCallback cb) { std::stringstream ss; ss << "triggerWaitWithCompletionCallback fenceSyncInfo=0x" << std::hex << reinterpret_cast(fenceSync); sendAsync( [fenceSync, cb = std::move(cb), this](WorkerId) { doSyncWait(fenceSync, std::move(cb)); }, ss.str()); } void SyncThread::initSyncEGLContext() { mWorkerThreadPool.broadcast([this] { return Command{ .mTask = std::packaged_task([this](WorkerId workerId) { DPRINT("for worker id: %d", workerId); // We shouldn't initialize EGL context, when SyncThread is initialized // without GL enabled. SYNC_THREAD_CHECK(mHasGl); const EGLDispatch* egl = gl::LazyLoadedEGLDispatch::get(); mDisplay = egl->eglGetDisplay(EGL_DEFAULT_DISPLAY); int eglMaj, eglMin; egl->eglInitialize(mDisplay, &eglMaj, &eglMin); const EGLint configAttribs[] = { EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_NONE, }; EGLint nConfigs; EGLConfig config; egl->eglChooseConfig(mDisplay, configAttribs, &config, 1, &nConfigs); const EGLint pbufferAttribs[] = { EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE, }; mSurface[workerId] = egl->eglCreatePbufferSurface(mDisplay, config, pbufferAttribs); const EGLint contextAttribs[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE}; mContext[workerId] = egl->eglCreateContext(mDisplay, config, EGL_NO_CONTEXT, contextAttribs); egl->eglMakeCurrent(mDisplay, mSurface[workerId], mSurface[workerId], mContext[workerId]); return 0; }), .mDescription = "init sync EGL context", }; }); mWorkerThreadPool.waitAllItems(); } void SyncThread::doSyncWait(EmulatedEglFenceSync* fenceSync, std::function onComplete) { DPRINT("enter"); if (!EmulatedEglFenceSync::getFromHandle((uint64_t)(uintptr_t)fenceSync)) { if (onComplete) { onComplete(); } return; } // We shouldn't use EmulatedEglFenceSync to wait, when SyncThread is initialized // without GL enabled, because EmulatedEglFenceSync uses EGL/GLES. SYNC_THREAD_CHECK(mHasGl); EGLint wait_result = 0x0; DPRINT("wait on sync obj: %p", fenceSync); wait_result = fenceSync->wait(kDefaultTimeoutNsecs); DPRINT( "done waiting, with wait result=0x%x. " "increment timeline (and signal fence)", wait_result); if (wait_result != EGL_CONDITION_SATISFIED_KHR) { EGLint error = gl::s_egl.eglGetError(); DPRINT("error: eglClientWaitSync abnormal exit 0x%x. sync handle 0x%llx. egl error = %#x\n", wait_result, (unsigned long long)fenceSync, error); (void)error; } DPRINT("issue timeline increment"); // We always unconditionally increment timeline at this point, even // if the call to eglClientWaitSync returned abnormally. // There are three cases to consider: // - EGL_CONDITION_SATISFIED_KHR: either the sync object is already // signaled and we need to increment this timeline immediately, or // we have waited until the object is signaled, and then // we increment the timeline. // - EGL_TIMEOUT_EXPIRED_KHR: the fence command we put in earlier // in the OpenGL stream is not actually ever signaled, and we // end up blocking in the above eglClientWaitSyncKHR call until // our timeout runs out. In this case, provided we have waited // for |kDefaultTimeoutNsecs|, the guest will have received all // relevant error messages about fence fd's not being signaled // in time, so we are properly emulating bad behavior even if // we now increment the timeline. // - EGL_FALSE (error): chances are, the underlying EGL implementation // on the host doesn't actually support fence objects. In this case, // we should fail safe: 1) It must be only very old or faulty // graphics drivers / GPU's that don't support fence objects. // 2) The consequences of signaling too early are generally, out of // order frames and scrambled textures in some apps. But, not // incrementing the timeline means that the app's rendering freezes. // So, despite the faulty GPU driver, not incrementing is too heavyweight a response. if (onComplete) { onComplete(); } EmulatedEglFenceSync::incrementTimelineAndDeleteOldFences(); DPRINT("done timeline increment"); DPRINT("exit"); } #endif void SyncThread::triggerWaitVk(VkFence vkFence, uint64_t timeline) { std::stringstream ss; ss << "triggerWaitVk vkFence=0x" << std::hex << reinterpret_cast(vkFence) << " timeline=0x" << std::hex << timeline; sendAsync( [vkFence, timeline](WorkerId) { doSyncWaitVk(vkFence, [timeline] { DPRINT("vk wait done, use goldfish sync timeline inc"); emugl::emugl_sync_timeline_inc(timeline, kTimelineInterval); }); }, ss.str()); } void SyncThread::triggerWaitVkWithCompletionCallback(VkFence vkFence, FenceCompletionCallback cb) { std::stringstream ss; ss << "triggerWaitVkWithCompletionCallback vkFence=0x" << std::hex << reinterpret_cast(vkFence); sendAsync([vkFence, cb = std::move(cb)](WorkerId) { doSyncWaitVk(vkFence, std::move(cb)); }, ss.str()); } void SyncThread::triggerWaitVkQsriWithCompletionCallback(VkImage vkImage, FenceCompletionCallback cb) { std::stringstream ss; ss << "triggerWaitVkQsriWithCompletionCallback vkImage=0x" << reinterpret_cast(vkImage); sendAsync( [vkImage, cb = std::move(cb)](WorkerId) { auto decoder = vk::VkDecoderGlobalState::get(); auto res = decoder->registerQsriCallback(vkImage, cb); // If registerQsriCallback does not schedule the callback, we still need to complete // the task, otherwise we may hit deadlocks on tasks on the same ring. if (!res.CallbackScheduledOrFired()) { cb(); } }, ss.str()); } void SyncThread::triggerWaitVkQsri(VkImage vkImage, uint64_t timeline) { std::stringstream ss; ss << "triggerWaitVkQsri vkImage=0x" << std::hex << vkImage << " timeline=0x" << std::hex << timeline; sendAsync( [vkImage, timeline](WorkerId) { auto decoder = vk::VkDecoderGlobalState::get(); auto res = decoder->registerQsriCallback(vkImage, [timeline](){ emugl::emugl_sync_timeline_inc(timeline, kTimelineInterval); }); // If registerQsriCallback does not schedule the callback, we still need to complete // the task, otherwise we may hit deadlocks on tasks on the same ring. if (!res.CallbackScheduledOrFired()) { emugl::emugl_sync_timeline_inc(timeline, kTimelineInterval); } }, ss.str()); } void SyncThread::triggerGeneral(FenceCompletionCallback cb, std::string description) { std::stringstream ss; ss << "triggerGeneral: " << description; sendAsync(std::bind(std::move(cb)), ss.str()); } void SyncThread::cleanup() { sendAndWaitForResult( [this](WorkerId workerId) { #if GFXSTREAM_ENABLE_HOST_GLES if (mHasGl) { const EGLDispatch* egl = gl::LazyLoadedEGLDispatch::get(); egl->eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); egl->eglDestroyContext(mDisplay, mContext[workerId]); egl->eglDestroySurface(mDisplay, mSurface[workerId]); mContext[workerId] = EGL_NO_CONTEXT; mSurface[workerId] = EGL_NO_SURFACE; } #endif return 0; }, "cleanup"); DPRINT("signal"); mLock.lock(); mExiting = true; mCv.signalAndUnlock(&mLock); DPRINT("exit"); // Wait for the control thread to exit. We can't destroy the SyncThread // before we wait the control thread. if (!wait(nullptr)) { ERR("Fail to wait the control thread of the SyncThread to exit."); } } // Private methods below//////////////////////////////////////////////////////// intptr_t SyncThread::main() { DPRINT("in sync thread"); mLock.lock(); mCv.wait(&mLock, [this] { return mExiting; }); mWorkerThreadPool.done(); mWorkerThreadPool.join(); DPRINT("exited sync thread"); return 0; } int SyncThread::sendAndWaitForResult(std::function job, std::string description) { DPRINT("sendAndWaitForResult task(%s)", description.c_str()); std::packaged_task task(std::move(job)); std::future resFuture = task.get_future(); Command command = { .mTask = std::move(task), .mDescription = std::move(description), }; mWorkerThreadPool.enqueue(std::move(command)); auto res = resFuture.get(); DPRINT("exit"); return res; } void SyncThread::sendAsync(std::function job, std::string description) { DPRINT("send task(%s)", description.c_str()); mWorkerThreadPool.enqueue(Command{ .mTask = std::packaged_task([job = std::move(job)](WorkerId workerId) mutable { job(workerId); return 0; }), .mDescription = std::move(description), }); DPRINT("exit"); } void SyncThread::doSyncThreadCmd(Command&& command, WorkerId workerId) { static thread_local std::once_flag sOnceFlag; std::call_once(sOnceFlag, [&] { GFXSTREAM_TRACE_NAME_TRACK(GFXSTREAM_TRACE_TRACK_FOR_CURRENT_THREAD(), "SyncThread"); }); std::unique_ptr> syncThreadData = std::make_unique>(); syncThreadData->insert({{"syncthread_cmd_desc", command.mDescription}}); auto watchdog = WATCHDOG_BUILDER(mHealthMonitor, "SyncThread task execution") .setHangType(EventHangMetadata::HangType::kSyncThread) .setAnnotations(std::move(syncThreadData)) .build(); command.mTask(workerId); } int SyncThread::doSyncWaitVk(VkFence vkFence, std::function onComplete) { DPRINT("enter"); auto decoder = vk::VkDecoderGlobalState::get(); auto result = decoder->waitForFence(vkFence, kDefaultTimeoutNsecs); if (result == VK_TIMEOUT) { DPRINT("SYNC_WAIT_VK timeout: vkFence=%p", vkFence); } else if (result != VK_SUCCESS) { DPRINT("SYNC_WAIT_VK error: %d vkFence=%p", result, vkFence); } DPRINT("issue timeline increment"); // We always unconditionally increment timeline at this point, even // if the call to vkWaitForFences returned abnormally. // See comments in |doSyncWait| about the rationale. if (onComplete) { onComplete(); } DPRINT("done timeline increment"); DPRINT("exit"); return result; } /* static */ SyncThread* SyncThread::get() { auto res = sGlobalSyncThread()->syncThreadPtr(); SYNC_THREAD_CHECK(res); return res; } void SyncThread::initialize(bool hasGl, HealthMonitor<>* healthMonitor) { sGlobalSyncThread()->initialize(hasGl, healthMonitor); } void SyncThread::destroy() { sGlobalSyncThread()->destroy(); } } // namespace gfxstream