// 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 #include #include #include // 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, flags); } 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, ANDROID_PIPE_DEFAULT, stream); } private: static AndroidPipe* createPipe( void* hwPipe, Service* service, const char* args, AndroidPipeFlags flags, 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, flags, 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 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(pixels.data())); // #if SNAPSHOT_PROFILE > 1 // printf("Screenshot image write time %lld ms\n", // (long long)(sw.elapsedUs() / 1000)); // #endif // }); // mScreenshotSaver->start(); // } } base::Optional 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, AndroidPipeFlags flags, 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(); } uint32_t virtioGpuContextId = -1; if (flags & ANDROID_PIPE_VIRTIO_GPU_BIT) { virtioGpuContextId = (uint32_t)(uintptr_t)hwPipe; } mChannel = renderer->createRenderChannel(loadStream, virtioGpuContextId); 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(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()); 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); }