blob: f217ee6ec8027f0a2c832dbbb2f38942eef32d77 [file] [log] [blame]
// Copyright 2020 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 "host-common/opengl/OpenglEsPipe.h"
#include "aemu/base/Optional.h"
#include "aemu/base/files/PathUtils.h"
#include "aemu/base/files/StreamSerializing.h"
#include "aemu/base/threads/FunctorThread.h"
#include "aemu/base/system/System.h"
#include "host-common/globals.h"
// #include "loadpng.h"
#include "host-common/opengl/GLProcessPipe.h"
#include "host-common/opengles-pipe.h"
#include "host-common/opengles.h"
// #include "snapshot/Loader.h"
// #include "snapshot/Saver.h"
// #include "snapshot/Snapshotter.h"
#include <atomic>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
// Set to 1 or 2 for debug traces
#define DEBUG 0
#if DEBUG >= 1
#define D(...) printf(__VA_ARGS__), printf("\n"), fflush(stdout)
#else
#define D(...) ((void)0)
#endif
#if DEBUG >= 2
#define DD(...) printf(__VA_ARGS__), printf("\n"), fflush(stdout)
#else
#define DD(...) ((void)0)
#endif
using ChannelBuffer = gfxstream::RenderChannel::Buffer;
using gfxstream::RenderChannel;
using gfxstream::RenderChannelPtr;
using ChannelState = gfxstream::RenderChannel::State;
using IoResult = gfxstream::RenderChannel::IoResult;
// using android::base::Stopwatch;
// using android::snapshot::Snapshotter;
#define OPENGL_SAVE_VERSION 1
namespace android {
namespace opengl {
// TODO (b/138549350): See if Android/Fuchsia pipe protocols can be unified
// to give the best performance in each guest OS.
enum class RecvMode {
Android = 0,
Fuchsia = 1,
VirtioGpu = 2,
};
static RecvMode recvMode = RecvMode::Android;
namespace {
class EmuglPipe : public AndroidPipe {
public:
//////////////////////////////////////////////////////////////////////////
// The pipe service class for this implementation.
class Service : public AndroidPipe::Service {
public:
Service() : AndroidPipe::Service("opengles") {}
// Create a new EmuglPipe instance.
AndroidPipe* create(void* hwPipe, const char* args, AndroidPipeFlags flags) override {
return createPipe(hwPipe, this, args);
}
bool canLoad() const override { return true; }
virtual void preLoad(android::base::Stream* stream) override {
// #ifdef SNAPSHOT_PROFILE
// mLoadMeter.restartUs();
// #endif
// const bool hasRenderer = stream->getByte();
// const auto& renderer = android_getOpenglesRenderer();
// if (hasRenderer != (bool)renderer) {
// // die?
// return;
// }
// if (!hasRenderer) {
// return;
// }
// int version = stream->getBe32();
// (void)version;
// renderer->load(stream, Snapshotter::get().loader().textureLoader());
// #ifdef SNAPSHOT_PROFILE
// printf("OpenglEs preload time: %lld ms\n",
// (long long)(mLoadMeter.elapsedUs() / 1000));
// #endif
}
void postLoad(android::base::Stream* stream) override {
if (const auto& renderer = android_getOpenglesRenderer()) {
renderer->resumeAll();
}
#ifdef SNAPSHOT_PROFILE
printf("OpenglEs total load time: %lld ms\n",
(long long)(mLoadMeter.elapsedUs() / 1000));
#endif
}
void preSave(android::base::Stream* stream) override {
// #ifdef SNAPSHOT_PROFILE
// mSaveMeter.restartUs();
// #endif
// if (const auto& renderer = android_getOpenglesRenderer()) {
// renderer->pauseAllPreSave();
// stream->putByte(1);
// stream->putBe32(OPENGL_SAVE_VERSION);
// renderer->save(stream,
// Snapshotter::get().saver().textureSaver());
//
// writeScreenshot(*renderer);
// } else {
// stream->putByte(0);
// }
}
void postSave(android::base::Stream* stream) override {
if (const auto& renderer = android_getOpenglesRenderer()) {
renderer->resumeAll();
}
#ifdef SNAPSHOT_PROFILE
printf("OpenglEs total save time: %lld ms\n",
(long long)(mSaveMeter.elapsedUs() / 1000));
#endif
}
virtual AndroidPipe* load(void* hwPipe,
const char* args,
android::base::Stream* stream) override {
return createPipe(hwPipe, this, args, stream);
}
private:
static AndroidPipe* createPipe(
void* hwPipe,
Service* service,
const char* args,
android::base::Stream* loadStream = nullptr) {
const auto& renderer = android_getOpenglesRenderer();
if (!renderer) {
// This should never happen, unless there is a bug in the
// emulator's initialization, or the system image, or we're
// loading from an incompatible snapshot.
D("Trying to open the OpenGLES pipe without GPU emulation!");
return nullptr;
}
auto pipe = new EmuglPipe(hwPipe, service, renderer, loadStream);
if (!pipe->mIsWorking) {
delete pipe;
pipe = nullptr;
}
return pipe;
}
void writeScreenshot(gfxstream::Renderer& renderer) {
// #if SNAPSHOT_PROFILE > 1
// Stopwatch sw;
// #endif
// if (!mSnapshotCallbackRegistered) {
// // We have to wait for the screenshot saving thread, but
// // there's no need to join it too soon: it is ok to only
// // block when the rest of snapshot saving is complete.
// // Snapshotter::get().addOperationCallback(
// // [this](Snapshotter::Operation op,
// // Snapshotter::Stage stage) {
// // if (op == Snapshotter::Operation::Save &&
// // stage == Snapshotter::Stage::End) {
// // if (mScreenshotSaver) {
// // mScreenshotSaver->wait();
// // mScreenshotSaver.clear();
// // }
// // }
// // });
// mSnapshotCallbackRegistered = true;
// }
// // always do 4 channel screenshot because swiftshader_indirect
// // has issues with 3 channels
// const unsigned int nChannels = 4;
// unsigned int width;
// unsigned int height;
// std::vector<unsigned char> pixels;
// renderer.getScreenshot(nChannels, &width, &height, pixels);
// #if SNAPSHOT_PROFILE > 1
// printf("Screenshot load texture time %lld ms\n",
// (long long)(sw.elapsedUs() / 1000));
// #endif
// if (width > 0 && height > 0) {
// std::string dataDir =
// Snapshotter::get().saver().snapshot().dataDir();
// mScreenshotSaver.emplace([nChannels, width, height,
// dataDir = std::move(dataDir),
// pixels = std::move(pixels)] {
// #if SNAPSHOT_PROFILE > 1
// Stopwatch sw;
// #endif
// std::string fileName = android::base::PathUtils::join(
// dataDir, "screenshot.png");
// // TODO: fix the screenshot rotation?
// savepng(fileName.c_str(), nChannels, width, height,
// SKIN_ROTATION_0,
// const_cast<unsigned char*>(pixels.data()));
// #if SNAPSHOT_PROFILE > 1
// printf("Screenshot image write time %lld ms\n",
// (long long)(sw.elapsedUs() / 1000));
// #endif
// });
// mScreenshotSaver->start();
// }
}
base::Optional<base::FunctorThread> mScreenshotSaver;
#ifdef SNAPSHOT_PROFILE
Stopwatch mSaveMeter;
Stopwatch mLoadMeter;
#endif
};
/////////////////////////////////////////////////////////////////////////
// Constructor, check that |mIsWorking| is true after this call to verify
// that everything went well.
EmuglPipe(void* hwPipe, Service* service, const gfxstream::RendererPtr& renderer,
android::base::Stream* loadStream = nullptr)
: AndroidPipe(hwPipe, service) {
bool isWorking = true;
if (loadStream) {
DD("%s: loading GLES pipe state for hwpipe=%p", __func__, mHwPipe);
isWorking = (bool)loadStream->getBe32();
android::base::loadBuffer(loadStream, &mDataForReading);
mDataForReadingLeft = loadStream->getBe32();
}
mChannel = renderer->createRenderChannel(loadStream);
if (!mChannel) {
D("Failed to create an OpenGLES pipe channel!");
return;
}
mIsWorking = isWorking;
mChannel->setEventCallback([this](RenderChannel::State events) {
onChannelHostEvent(events);
});
}
//////////////////////////////////////////////////////////////////////////
// Overriden AndroidPipe methods
virtual void onSave(android::base::Stream* stream) override {
DD("%s: saving GLES pipe state for hwpipe=%p", __FUNCTION__, mHwPipe);
stream->putBe32(mIsWorking);
android::base::saveBuffer(stream, mDataForReading);
stream->putBe32(mDataForReadingLeft);
mChannel->onSave(stream);
}
virtual void onGuestClose(PipeCloseReason reason) override {
D("%s", __func__);
mIsWorking = false;
mChannel->stop();
// Make sure there's no operation scheduled for this pipe instance to
// run on the main thread.
abortPendingOperation();
delete this;
}
virtual unsigned onGuestPoll() const override {
DD("%s", __func__);
unsigned ret = 0;
if (mDataForReadingLeft > 0) {
ret |= PIPE_POLL_IN;
}
ChannelState state = mChannel->state();
if ((state & ChannelState::CanRead) != 0) {
ret |= PIPE_POLL_IN;
}
if ((state & ChannelState::CanWrite) != 0) {
ret |= PIPE_POLL_OUT;
}
if ((state & ChannelState::Stopped) != 0) {
ret |= PIPE_POLL_HUP;
}
DD("%s: returning %d", __func__, ret);
return ret;
}
virtual int onGuestRecv(AndroidPipeBuffer* buffers,
int numBuffers) override {
DD("%s", __func__);
// Consume the pipe's dataForReading, then put the next received data
// piece there. Repeat until the buffers are full or we're out of data
// in the channel.
int len = 0;
size_t buffOffset = 0;
auto buff = buffers;
const auto buffEnd = buff + numBuffers;
while (buff != buffEnd) {
if (mDataForReadingLeft == 0) {
if (android::opengl::recvMode == android::opengl::RecvMode::Android) {
// No data left, read a new chunk from the channel.
int spinCount = 20;
for (;;) {
auto result = mChannel->tryRead(&mDataForReading);
if (result == IoResult::Ok) {
mDataForReadingLeft = mDataForReading.size();
break;
}
DD("%s: tryRead() failed with %d", __func__, (int)result);
if (len > 0) {
DD("%s: returning %d bytes", __func__, (int)len);
return len;
}
// This failed either because the channel was stopped
// from the host, or if there was no data yet in the
if (result == IoResult::Error) {
return PIPE_ERROR_IO;
}
// Spin a little before declaring there is nothing
// to read. Many GL calls are much faster than the
// whole host-to-guest-to-host transition.
if (--spinCount > 0) {
continue;
}
DD("%s: returning PIPE_ERROR_AGAIN", __func__);
return PIPE_ERROR_AGAIN;
}
} else if (android::opengl::recvMode == android::opengl::RecvMode::Fuchsia) {
// No data left, return if we already received some data,
// otherwise read a new chunk from the channel.
if (len > 0) {
DD("%s: returning %d bytes", __func__, (int)len);
return len;
}
// Block a little before declaring there is nothing
// to read. This gives the render thread a chance to
// process pending data before we return control to
// the guest. The amount of time we block here should
// be kept at a minimum. It's preferred to instead have
// the guest block on work that takes a significant
// amount of time.
const RenderChannel::Duration kBlockAtMostUs = 100;
auto currTime = android::base::getUnixTimeUs();
auto result = mChannel->readBefore(&mDataForReading, currTime + kBlockAtMostUs);
if (result != IoResult::Ok) {
DD("%s: tryRead() failed with %d", __func__, (int)result);
// This failed either because the channel was stopped
// from the host, or if there was no data yet in the
// channel.
if (len > 0) {
DD("%s: returning %d bytes", __func__, (int)len);
return len;
}
if (result == IoResult::Error) {
return PIPE_ERROR_IO;
}
DD("%s: returning PIPE_ERROR_AGAIN", __func__);
return PIPE_ERROR_AGAIN;
}
mDataForReadingLeft = mDataForReading.size();
} else { // Virtio-gpu
// No data left, return if we already received some data,
// otherwise read a new chunk from the channel.
if (len > 0) {
DD("%s: returning %d bytes", __func__, (int)len);
return len;
}
// Block a little before declaring there is nothing
// to read. This gives the render thread a chance to
// process pending data before we return control to
// the guest. The amount of time we block here should
// be kept at a minimum. It's preferred to instead have
// the guest block on work that takes a significant
// amount of time.
const RenderChannel::Duration kBlockAtMostUs = 10000;
auto currTime = android::base::getUnixTimeUs();
auto result = mChannel->readBefore(&mDataForReading, currTime + kBlockAtMostUs);
if (result != IoResult::Ok) {
DD("%s: tryRead() failed with %d", __func__, (int)result);
// This failed either because the channel was stopped
// from the host, or if there was no data yet in the
// channel.
if (len > 0) {
DD("%s: returning %d bytes", __func__, (int)len);
return len;
}
if (result == IoResult::Error) {
return PIPE_ERROR_IO;
}
DD("%s: returning PIPE_ERROR_AGAIN", __func__);
return PIPE_ERROR_AGAIN;
}
mDataForReadingLeft = mDataForReading.size();
}
}
const size_t curSize = std::min<size_t>(buff->size - buffOffset,
mDataForReadingLeft);
memcpy(buff->data + buffOffset,
mDataForReading.data() +
(mDataForReading.size() - mDataForReadingLeft),
curSize);
len += curSize;
mDataForReadingLeft -= curSize;
buffOffset += curSize;
if (buffOffset == buff->size) {
++buff;
buffOffset = 0;
}
}
DD("%s: received %d bytes", __func__, (int)len);
return len;
}
virtual int onGuestSend(const AndroidPipeBuffer* buffers,
int numBuffers,
void** newPipePtr) override {
DD("%s", __func__);
if (!mIsWorking) {
DD("%s: pipe already closed!", __func__);
return PIPE_ERROR_IO;
}
// Count the total bytes to send.
int count = 0;
for (int n = 0; n < numBuffers; ++n) {
count += buffers[n].size;
}
// Copy everything into a single ChannelBuffer.
ChannelBuffer outBuffer;
outBuffer.resize_noinit(count);
auto ptr = outBuffer.data();
for (int n = 0; n < numBuffers; ++n) {
memcpy(ptr, buffers[n].data, buffers[n].size);
ptr += buffers[n].size;
}
D("%s: %p sending %d bytes to host", __func__, this, count);
// Send it through the channel.
auto result = mChannel->tryWrite(std::move(outBuffer));
if (result != IoResult::Ok) {
D("%s: tryWrite() failed with %d", __func__, (int)result);
return result == IoResult::Error ? PIPE_ERROR_IO : PIPE_ERROR_AGAIN;
}
return count;
}
virtual void onGuestWantWakeOn(int flags) override {
DD("%s: flags=%d", __func__, flags);
// Translate |flags| into ChannelState flags.
ChannelState wanted = ChannelState::Empty;
if (flags & PIPE_WAKE_READ) {
wanted |= ChannelState::CanRead;
}
if (flags & PIPE_WAKE_WRITE) {
wanted |= ChannelState::CanWrite;
}
// Signal events that are already available now.
ChannelState state = mChannel->state();
ChannelState available = state & wanted;
DD("%s: state=%d wanted=%d available=%d", __func__, (int)state,
(int)wanted, (int)available);
if (available != ChannelState::Empty) {
DD("%s: signaling events %d", __func__, (int)available);
signalState(available);
wanted &= ~available;
}
// Ask the channel to be notified of remaining events.
if (wanted != ChannelState::Empty) {
DD("%s: waiting for events %d", __func__, (int)wanted);
mChannel->setWantedEvents(wanted);
}
}
private:
// Called to signal the guest that read/write wake events occured.
// Note: this can be called from either the guest or host render
// thread.
void signalState(ChannelState state) {
int wakeFlags = 0;
if ((state & ChannelState::CanRead) != 0) {
wakeFlags |= PIPE_WAKE_READ;
}
if ((state & ChannelState::CanWrite) != 0) {
wakeFlags |= PIPE_WAKE_WRITE;
}
if (wakeFlags != 0) {
this->signalWake(wakeFlags);
}
}
// Called when an i/o event occurs on the render channel
void onChannelHostEvent(ChannelState state) {
D("%s: events %d (working %d)", __func__, (int)state, (int)mIsWorking);
// NOTE: This is called from the host-side render thread.
// but closeFromHost() and signalWake() can be called from
// any thread.
if (!mIsWorking) {
return;
}
if ((state & ChannelState::Stopped) != 0) {
this->closeFromHost();
return;
}
signalState(state);
}
// A RenderChannel pointer used for communication.
RenderChannelPtr mChannel;
// Set to |true| if the pipe is in working state, |false| means we're not
// initialized or the pipe is closed.
bool mIsWorking = false;
// These two variables serve as a reading buffer for the guest.
// Each time we get a read request, first we extract a single chunk from
// the |mChannel| into here, and then copy its content into the
// guest-supplied memory.
// If guest didn't have enough room for the whole buffer, we track the
// number of remaining bytes in |mDataForReadingLeft| for the next read().
uint32_t mDataForReadingLeft = 0;
ChannelBuffer mDataForReading;
DISALLOW_COPY_ASSIGN_AND_MOVE(EmuglPipe);
};
} // namespace
void registerPipeService() {
android::AndroidPipe::Service::add(std::make_unique<EmuglPipe::Service>());
registerGLProcessPipeService();
}
void pipeSetRecvMode(int mode) {
recvMode = (RecvMode)mode;
}
} // namespace opengl
} // namespace android
// Declared in android/opengles-pipe.h
void android_init_opengles_pipe() {
android::opengl::registerPipeService();
}
void android_opengles_pipe_set_recv_mode(int mode) {
android::opengl::pipeSetRecvMode(mode);
}