Start building libOpenglRender and add more host support files
Bug: 171711491
Change-Id: Ie1a6ca9cc5310e5ee2a72ae558450bcb4f2b9263
diff --git a/stream-servers/RenderThread.cpp b/stream-servers/RenderThread.cpp
new file mode 100644
index 0000000..470e847
--- /dev/null
+++ b/stream-servers/RenderThread.cpp
@@ -0,0 +1,526 @@
+/*
+* Copyright (C) 2011 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 "RenderThread.h"
+
+#include "ChannelStream.h"
+#include "RingStream.h"
+#include "ErrorLog.h"
+#include "FrameBuffer.h"
+#include "ReadBuffer.h"
+#include "RenderControl.h"
+#include "RendererImpl.h"
+#include "RenderChannelImpl.h"
+#include "RenderThreadInfo.h"
+
+#include "OpenGLESDispatch/EGLDispatch.h"
+#include "OpenGLESDispatch/GLESv2Dispatch.h"
+#include "OpenGLESDispatch/GLESv1Dispatch.h"
+#include "../../../shared/OpenglCodecCommon/ChecksumCalculatorThreadInfo.h"
+
+#include "android/base/system/System.h"
+#include "android/base/Tracing.h"
+#include "android/base/files/StreamSerializing.h"
+#include "android/base/synchronization/MessageChannel.h"
+#include "android/utils/path.h"
+#include "android/utils/file_io.h"
+
+#define EMUGL_DEBUG_LEVEL 0
+#include "emugl/common/crash_reporter.h"
+#include "emugl/common/debug.h"
+
+#include <assert.h>
+
+using android::base::AutoLock;
+using android::base::MessageChannel;
+
+namespace emugl {
+
+struct RenderThread::SnapshotObjects {
+ RenderThreadInfo* threadInfo;
+ ChecksumCalculator* checksumCalc;
+ ChannelStream* channelStream;
+ RingStream* ringStream;
+ ReadBuffer* readBuffer;
+};
+
+static bool getBenchmarkEnabledFromEnv() {
+ auto threadEnabled = android::base::System::getEnvironmentVariable("ANDROID_EMUGL_RENDERTHREAD_STATS");
+ if (threadEnabled == "1") return true;
+ return false;
+}
+
+static uint64_t currTimeUs(bool enable) {
+ if (enable) {
+ return android::base::System::get()->getHighResTimeUs();
+ } else {
+ return 0;
+ }
+}
+
+// Start with a smaller buffer to not waste memory on a low-used render threads.
+static constexpr int kStreamBufferSize = 128 * 1024;
+
+RenderThread::RenderThread(RenderChannelImpl* channel,
+ android::base::Stream* loadStream)
+ : emugl::Thread(android::base::ThreadFlags::MaskSignals, 2 * 1024 * 1024),
+ mChannel(channel) {
+ if (loadStream) {
+ const bool success = loadStream->getByte();
+ if (success) {
+ mStream.emplace(0);
+ android::base::loadStream(loadStream, &*mStream);
+ mState = SnapshotState::StartLoading;
+ } else {
+ mFinished.store(true, std::memory_order_relaxed);
+ }
+ }
+}
+
+RenderThread::RenderThread(
+ struct asg_context context,
+ android::emulation::asg::ConsumerCallbacks callbacks,
+ android::base::Stream* loadStream)
+ : emugl::Thread(android::base::ThreadFlags::MaskSignals, 2 * 1024 * 1024),
+ mRingStream(
+ new RingStream(context, callbacks, kStreamBufferSize)) {
+ if (loadStream) {
+ const bool success = loadStream->getByte();
+ if (success) {
+ mStream.emplace(0);
+ android::base::loadStream(loadStream, &*mStream);
+ mState = SnapshotState::StartLoading;
+ } else {
+ mFinished.store(true, std::memory_order_relaxed);
+ }
+ }
+}
+
+
+RenderThread::~RenderThread() = default;
+
+void RenderThread::pausePreSnapshot() {
+ AutoLock lock(mLock);
+ assert(mState == SnapshotState::Empty);
+ mStream.emplace();
+ mState = SnapshotState::StartSaving;
+ if (mChannel) mChannel->pausePreSnapshot();
+ mCondVar.broadcastAndUnlock(&lock);
+}
+
+void RenderThread::resume() {
+ AutoLock lock(mLock);
+ // This function can be called for a thread from pre-snapshot loading
+ // state; it doesn't need to do anything.
+ if (mState == SnapshotState::Empty) {
+ return;
+ }
+ waitForSnapshotCompletion(&lock);
+ mStream.clear();
+ mState = SnapshotState::Empty;
+ if (mChannel) mChannel->resume();
+ mCondVar.broadcastAndUnlock(&lock);
+}
+
+void RenderThread::save(android::base::Stream* stream) {
+ bool success;
+ {
+ AutoLock lock(mLock);
+ assert(mState == SnapshotState::StartSaving ||
+ mState == SnapshotState::InProgress ||
+ mState == SnapshotState::Finished);
+ waitForSnapshotCompletion(&lock);
+ success = mState == SnapshotState::Finished;
+ }
+
+ if (success) {
+ assert(mStream);
+ stream->putByte(1);
+ android::base::saveStream(stream, *mStream);
+ } else {
+ stream->putByte(0);
+ }
+}
+
+void RenderThread::waitForSnapshotCompletion(AutoLock* lock) {
+ while (mState != SnapshotState::Finished &&
+ !mFinished.load(std::memory_order_relaxed)) {
+ mCondVar.wait(lock);
+ }
+}
+
+template <class OpImpl>
+void RenderThread::snapshotOperation(AutoLock* lock, OpImpl&& implFunc) {
+ assert(isPausedForSnapshotLocked());
+ mState = SnapshotState::InProgress;
+ mCondVar.broadcastAndUnlock(lock);
+
+ implFunc();
+
+ lock->lock();
+
+ mState = SnapshotState::Finished;
+ mCondVar.broadcast();
+
+ // Only return after we're allowed to proceed.
+ while (isPausedForSnapshotLocked()) {
+ mCondVar.wait(lock);
+ }
+}
+
+void RenderThread::loadImpl(AutoLock* lock, const SnapshotObjects& objects) {
+ snapshotOperation(lock, [this, &objects] {
+ objects.readBuffer->onLoad(&*mStream);
+ if (objects.channelStream) objects.channelStream->load(&*mStream);
+ if (objects.ringStream) objects.ringStream->load(&*mStream);
+ objects.checksumCalc->load(&*mStream);
+ objects.threadInfo->onLoad(&*mStream);
+ });
+}
+
+void RenderThread::saveImpl(AutoLock* lock, const SnapshotObjects& objects) {
+ snapshotOperation(lock, [this, &objects] {
+ objects.readBuffer->onSave(&*mStream);
+ if (objects.channelStream) objects.channelStream->save(&*mStream);
+ if (objects.ringStream) objects.ringStream->save(&*mStream);
+ objects.checksumCalc->save(&*mStream);
+ objects.threadInfo->onSave(&*mStream);
+ });
+}
+
+bool RenderThread::isPausedForSnapshotLocked() const {
+ return mState != SnapshotState::Empty;
+}
+
+bool RenderThread::doSnapshotOperation(const SnapshotObjects& objects,
+ SnapshotState state) {
+ AutoLock lock(mLock);
+ if (mState == state) {
+ switch (state) {
+ case SnapshotState::StartLoading:
+ loadImpl(&lock, objects);
+ return true;
+ case SnapshotState::StartSaving:
+ saveImpl(&lock, objects);
+ return true;
+ default:
+ return false;
+ }
+ }
+ return false;
+}
+
+void RenderThread::setFinished() {
+ // Make sure it never happens that we wait forever for the thread to
+ // save to snapshot while it was not even going to.
+ AutoLock lock(mLock);
+ mFinished.store(true, std::memory_order_relaxed);
+ if (mState != SnapshotState::Empty) {
+ mCondVar.broadcastAndUnlock(&lock);
+ }
+}
+
+intptr_t RenderThread::main() {
+ if (mFinished.load(std::memory_order_relaxed)) {
+ DBG("Error: fail loading a RenderThread @%p\n", this);
+ return 0;
+ }
+
+ RenderThreadInfo tInfo;
+ ChecksumCalculatorThreadInfo tChecksumInfo;
+ ChecksumCalculator& checksumCalc = tChecksumInfo.get();
+ bool needRestoreFromSnapshot = false;
+
+ //
+ // initialize decoders
+ //
+ tInfo.m_glDec.initGL(gles1_dispatch_get_proc_func, nullptr);
+ tInfo.m_gl2Dec.initGL(gles2_dispatch_get_proc_func, nullptr);
+ initRenderControlContext(&tInfo.m_rcDec);
+
+ if (!mChannel && !mRingStream) {
+ DBG("Exited a loader RenderThread @%p\n", this);
+ mFinished.store(true, std::memory_order_relaxed);
+ return 0;
+ }
+
+ ChannelStream stream(mChannel, RenderChannel::Buffer::kSmallSize);
+ IOStream* ioStream =
+ mChannel ? (IOStream*)&stream : (IOStream*)mRingStream.get();
+
+ ReadBuffer readBuf(kStreamBufferSize);
+ if (mRingStream) {
+ readBuf.setNeededFreeTailSize(0);
+ }
+
+ const SnapshotObjects snapshotObjects = {
+ &tInfo, &checksumCalc, &stream, mRingStream.get(), &readBuf,
+ };
+
+ // Framebuffer initialization is asynchronous, so we need to make sure
+ // it's completely initialized before running any GL commands.
+ FrameBuffer::waitUntilInitialized();
+
+ // This is the only place where we try loading from snapshot.
+ // But the context bind / restoration will be delayed after receiving
+ // the first GL command.
+ if (doSnapshotOperation(snapshotObjects, SnapshotState::StartLoading)) {
+ DBG("Loaded RenderThread @%p from snapshot\n", this);
+ needRestoreFromSnapshot = true;
+ } else {
+ // Not loading from a snapshot: continue regular startup, read
+ // the |flags|.
+ uint32_t flags = 0;
+ while (ioStream->read(&flags, sizeof(flags)) != sizeof(flags)) {
+ // Stream read may fail because of a pending snapshot.
+ if (!doSnapshotOperation(snapshotObjects, SnapshotState::StartSaving)) {
+ setFinished();
+ DBG("Exited a RenderThread @%p early\n", this);
+ return 0;
+ }
+ }
+
+ // |flags| used to mean something, now they're not used.
+ (void)flags;
+ }
+
+ int stats_totalBytes = 0;
+ uint64_t stats_progressTimeUs = 0;
+ auto stats_t0 = android::base::System::get()->getHighResTimeUs() / 1000;
+ bool benchmarkEnabled = getBenchmarkEnabledFromEnv();
+
+ //
+ // open dump file if RENDER_DUMP_DIR is defined
+ //
+ const char* dump_dir = getenv("RENDERER_DUMP_DIR");
+ FILE* dumpFP = nullptr;
+ if (dump_dir) {
+ size_t bsize = strlen(dump_dir) + 32;
+ char* fname = new char[bsize];
+ snprintf(fname, bsize, "%s" PATH_SEP "stream_%p", dump_dir, this);
+ dumpFP = android_fopen(fname, "wb");
+ if (!dumpFP) {
+ fprintf(stderr, "Warning: stream dump failed to open file %s\n",
+ fname);
+ }
+ delete[] fname;
+ }
+
+ while (1) {
+ // Let's make sure we read enough data for at least some processing.
+ int packetSize;
+ if (readBuf.validData() >= 8) {
+ // We know that packet size is the second int32_t from the start.
+ packetSize = *(const int32_t*)(readBuf.buf() + 4);
+ if (!packetSize) {
+ // Emulator will get live-stuck here if packet size is read to be zero;
+ // crash right away so we can see these events.
+ emugl::emugl_crash_reporter(
+ "Guest should never send a size-0 GL packet\n");
+ }
+ } else {
+ // Read enough data to at least be able to get the packet size next
+ // time.
+ packetSize = 8;
+ }
+
+ int stat = 0;
+ if (packetSize > (int)readBuf.validData()) {
+ stat = readBuf.getData(ioStream, packetSize);
+ if (stat <= 0) {
+ if (doSnapshotOperation(snapshotObjects, SnapshotState::StartSaving)) {
+ continue;
+ } else {
+ D("Warning: render thread could not read data from stream");
+ break;
+ }
+ } else if (needRestoreFromSnapshot) {
+ // We just loaded from a snapshot, need to initialize / bind
+ // the contexts.
+ needRestoreFromSnapshot = false;
+ HandleType ctx = tInfo.currContext ? tInfo.currContext->getHndl()
+ : 0;
+ HandleType draw = tInfo.currDrawSurf ? tInfo.currDrawSurf->getHndl()
+ : 0;
+ HandleType read = tInfo.currReadSurf ? tInfo.currReadSurf->getHndl()
+ : 0;
+ FrameBuffer::getFB()->bindContext(ctx, draw, read);
+ }
+ }
+
+ DD("render thread read %d bytes, op %d, packet size %d",
+ (int)readBuf.validData(), *(int32_t*)readBuf.buf(),
+ *(int32_t*)(readBuf.buf() + 4));
+
+ //
+ // log received bandwidth statistics
+ //
+ if (benchmarkEnabled) {
+ stats_totalBytes += readBuf.validData();
+ auto dt = android::base::System::get()->getHighResTimeUs() / 1000 - stats_t0;
+ if (dt > 1000) {
+ float dts = (float)dt / 1000.0f;
+ printf("Used Bandwidth %5.3f MB/s, time in progress %f ms total %f ms\n", ((float)stats_totalBytes / dts) / (1024.0f*1024.0f),
+ stats_progressTimeUs / 1000.0f,
+ (float)dt);
+ readBuf.printStats();
+ stats_t0 = android::base::System::get()->getHighResTimeUs() / 1000;
+ stats_progressTimeUs = 0;
+ stats_totalBytes = 0;
+ }
+ }
+
+ //
+ // dump stream to file if needed
+ //
+ if (dumpFP) {
+ int skip = readBuf.validData() - stat;
+ fwrite(readBuf.buf() + skip, 1, readBuf.validData() - skip, dumpFP);
+ fflush(dumpFP);
+ }
+
+ auto progressStart = currTimeUs(benchmarkEnabled);
+ bool progress;
+
+#define MAX_THREADS 1
+ struct ThreadRunLimiter {
+ ThreadRunLimiter() {
+ availableThreads.send(0);
+ }
+ android::base::MessageChannel<int, MAX_THREADS> availableThreads;
+ void acquire() {
+ int token;
+ availableThreads.receive(&token);
+ }
+ void release() {
+ availableThreads.send(0);
+ }
+ };
+
+ struct ScopedThreadRunLimiter {
+ ScopedThreadRunLimiter(ThreadRunLimiter* t) : m_limiter(t) {
+ m_limiter->acquire();
+ }
+ ~ScopedThreadRunLimiter() {
+ m_limiter->release();
+ }
+ private:
+ ThreadRunLimiter* m_limiter;
+ };
+
+ static ThreadRunLimiter* l = new ThreadRunLimiter;
+
+ ScopedThreadRunLimiter scoped(l);
+ do {
+
+
+ progress = false;
+
+ // try to process some of the command buffer using the GLESv1
+ // decoder
+ //
+ // DRIVER WORKAROUND:
+ // On Linux with NVIDIA GPU's at least, we need to avoid performing
+ // GLES ops while someone else holds the FrameBuffer write lock.
+ //
+ // To be more specific, on Linux with NVIDIA Quadro K2200 v361.xx,
+ // we get a segfault in the NVIDIA driver when glTexSubImage2D
+ // is called at the same time as glXMake(Context)Current.
+ //
+ // To fix, this driver workaround avoids calling
+ // any sort of GLES call when we are creating/destroying EGL
+ // contexts.
+ {
+ FrameBuffer::getFB()->lockContextStructureRead();
+ }
+ size_t last;
+
+ {
+ last = tInfo.m_glDec.decode(
+ readBuf.buf(), readBuf.validData(), ioStream, &checksumCalc);
+ if (last > 0) {
+ progress = true;
+ readBuf.consume(last);
+ }
+ }
+
+ //
+ // try to process some of the command buffer using the GLESv2
+ // decoder
+ //
+ {
+ last = tInfo.m_gl2Dec.decode(readBuf.buf(), readBuf.validData(),
+ ioStream, &checksumCalc);
+
+ if (last > 0) {
+ progress = true;
+ readBuf.consume(last);
+ }
+ }
+
+ FrameBuffer::getFB()->unlockContextStructureRead();
+ //
+ // try to process some of the command buffer using the
+ // renderControl decoder
+ //
+ {
+ last = tInfo.m_rcDec.decode(readBuf.buf(), readBuf.validData(),
+ ioStream, &checksumCalc);
+ if (last > 0) {
+ readBuf.consume(last);
+ progress = true;
+ }
+ }
+
+ //
+ // try to process some of the command buffer using the
+ // Vulkan decoder
+ //
+ {
+ last = tInfo.m_vkDec.decode(readBuf.buf(), readBuf.validData(),
+ ioStream);
+ if (last > 0) {
+ readBuf.consume(last);
+ progress = true;
+ }
+ }
+ } while (progress);
+ }
+
+ if (dumpFP) {
+ fclose(dumpFP);
+ }
+
+ // Don't check for snapshots here: if we're already exiting then snapshot
+ // should not contain this thread information at all.
+ if (!FrameBuffer::getFB()->isShuttingDown()) {
+ // Release references to the current thread's context/surfaces if any
+ FrameBuffer::getFB()->bindContext(0, 0, 0);
+ if (tInfo.currContext || tInfo.currDrawSurf || tInfo.currReadSurf) {
+ fprintf(stderr,
+ "ERROR: RenderThread exiting with current context/surfaces\n");
+ }
+
+ FrameBuffer::getFB()->drainWindowSurface();
+ FrameBuffer::getFB()->drainRenderContext();
+ }
+
+ setFinished();
+
+ DBG("Exited a RenderThread @%p\n", this);
+ return 0;
+}
+
+} // namespace emugl