You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
529 lines
17 KiB
529 lines
17 KiB
/*
|
|
* 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 "apigen-codec-common/ChecksumCalculatorThreadInfo.h"
|
|
|
|
#include "base/System.h"
|
|
#include "base/Tracing.h"
|
|
#include "base/StreamSerializing.h"
|
|
#include "base/Lock.h"
|
|
#include "base/MessageChannel.h"
|
|
|
|
#define EMUGL_DEBUG_LEVEL 0
|
|
#include "host-common/crash_reporter.h"
|
|
#include "host-common/debug.h"
|
|
|
|
#ifndef _WIN32
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
#include <assert.h>
|
|
#include <string.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::getEnvironmentVariable("ANDROID_EMUGL_RENDERTHREAD_STATS");
|
|
if (threadEnabled == "1") return true;
|
|
return false;
|
|
}
|
|
|
|
// Start with a smaller buffer to not waste memory on a low-used render threads.
|
|
static constexpr int kStreamBufferSize = 128 * 1024;
|
|
|
|
// Requires this many threads on the system available to run unlimited.
|
|
static constexpr int kMinThreadsToRunUnlimited = 5;
|
|
|
|
// A thread run limiter that limits render threads to run one slice at a time.
|
|
static android::base::Lock sThreadRunLimiter;
|
|
|
|
RenderThread::RenderThread(RenderChannelImpl* channel,
|
|
android::base::Stream* loadStream)
|
|
: android::base::Thread(android::base::ThreadFlags::MaskSignals, 2 * 1024 * 1024),
|
|
mChannel(channel),
|
|
mRunInLimitedMode(android::base::getCpuCoreCount() < kMinThreadsToRunUnlimited)
|
|
{
|
|
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::base::Stream* loadStream,
|
|
android::emulation::asg::ConsumerCallbacks callbacks)
|
|
: android::base::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 (mRingStream) {
|
|
mRingStream->pausePreSnapshot();
|
|
// mCondVar.broadcastAndUnlock(&lock);
|
|
}
|
|
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;
|
|
}
|
|
if (mRingStream) mRingStream->resume();
|
|
waitForSnapshotCompletion(&lock);
|
|
mStream.clear();
|
|
mState = SnapshotState::Empty;
|
|
if (mChannel) mChannel->resume();
|
|
if (mRingStream) mRingStream->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::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;
|
|
}
|
|
|
|
uint32_t* seqnoPtr = nullptr;
|
|
|
|
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) {
|
|
// If we're using RingStream that might load before FrameBuffer
|
|
// restores the contexts from the handles, so check again here.
|
|
|
|
tInfo.postLoadRefreshCurrentContextSurfacePtrs();
|
|
// 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::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::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);
|
|
}
|
|
|
|
bool progress;
|
|
|
|
do {
|
|
|
|
if (!seqnoPtr && tInfo.m_puid) {
|
|
seqnoPtr = FrameBuffer::getFB()->getProcessSequenceNumberPtr(tInfo.m_puid);
|
|
}
|
|
|
|
progress = false;
|
|
size_t last;
|
|
|
|
//
|
|
// try to process some of the command buffer using the
|
|
// Vulkan decoder
|
|
//
|
|
// Note: It's risky to limit Vulkan decoding to one thread,
|
|
// so we do it outside the limiter
|
|
{
|
|
last = tInfo.m_vkDec.decode(readBuf.buf(), readBuf.validData(),
|
|
ioStream, seqnoPtr);
|
|
if (last > 0) {
|
|
readBuf.consume(last);
|
|
progress = true;
|
|
}
|
|
}
|
|
|
|
if (mRunInLimitedMode) {
|
|
sThreadRunLimiter.lock();
|
|
}
|
|
|
|
// 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();
|
|
}
|
|
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
|
|
if (mRunInLimitedMode) {
|
|
sThreadRunLimiter.unlock();
|
|
}
|
|
|
|
} 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
|