// Copyright 2014-2015 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 "RenderWindow.h" #include "base/Thread.h" #include "base/MessageChannel.h" #include "host-common/logging.h" #include "FrameBuffer.h" #include "RendererImpl.h" #include #include #ifndef _WIN32 #include #include #endif #define DEBUG 0 #if DEBUG # define D(...) my_debug(__PRETTY_FUNCTION__, __LINE__, __VA_ARGS__) #else # define D(...) ((void)0) #endif namespace { #if DEBUG void my_debug(const char* function, int line, const char* format, ...) { static ::android::base::Lock mutex; va_list args; va_start(args, format); mutex.lock(); fprintf(stderr, "%s:%d:", function, line); vfprintf(stderr, format, args); mutex.unlock(); va_end(args); } #endif // List of possible commands to send to the render window thread from // the main one. enum Command { CMD_INITIALIZE, CMD_SET_POST_CALLBACK, CMD_SETUP_SUBWINDOW, CMD_REMOVE_SUBWINDOW, CMD_SET_ROTATION, CMD_SET_TRANSLATION, CMD_REPAINT, CMD_HAS_GUEST_POSTED_A_FRAME, CMD_RESET_GUEST_POSTED_A_FRAME, CMD_FINALIZE, }; } // namespace // A single message sent from the main thread to the render window thread. // |cmd| determines which fields are valid to read. struct RenderWindowMessage { Command cmd; union { // CMD_INITIALIZE struct { int width; int height; bool useSubWindow; bool egl2egl; } init; // CMD_SET_POST_CALLBACK struct { emugl::Renderer::OnPostCallback on_post; void* on_post_context; uint32_t on_post_displayId; bool use_bgra_readback; } set_post_callback; // CMD_SETUP_SUBWINDOW struct { FBNativeWindowType parent; int wx; int wy; int ww; int wh; int fbw; int fbh; float dpr; float rotation; bool deleteExisting; bool hideWindow; } subwindow; // CMD_SET_TRANSLATION; struct { float px; float py; } trans; // CMD_SET_ROTATION float rotation; // result of operations. bool result; }; // Process the current message, and updates its |result| field. // Returns true on success, or false on failure. bool process() const { const RenderWindowMessage& msg = *this; FrameBuffer* fb; bool result = false; switch (msg.cmd) { case CMD_INITIALIZE: GL_LOG("RenderWindow: CMD_INITIALIZE w=%d h=%d", msg.init.width, msg.init.height); result = FrameBuffer::initialize(msg.init.width, msg.init.height, msg.init.useSubWindow, msg.init.egl2egl); break; case CMD_FINALIZE: GL_LOG("CMD_FINALIZE"); D("CMD_FINALIZE\n"); // this command may be issued even when frame buffer is not // yet created (e.g. if CMD_INITIALIZE failed), // so make sure we check if it is there before finalizing if (const auto fb = FrameBuffer::getFB()) { fb->finalize(); } result = true; break; case CMD_SET_POST_CALLBACK: GL_LOG("CMD_SET_POST_CALLBACK"); D("CMD_SET_POST_CALLBACK\n"); fb = FrameBuffer::getFB(); fb->setPostCallback(msg.set_post_callback.on_post, msg.set_post_callback.on_post_context, msg.set_post_callback.on_post_displayId, msg.set_post_callback.use_bgra_readback); result = true; break; case CMD_SETUP_SUBWINDOW: GL_LOG("CMD_SETUP_SUBWINDOW: parent=%p wx=%d wy=%d ww=%d wh=%d fbw=%d fbh=%d dpr=%f rotation=%f", (void*)(intptr_t)msg.subwindow.parent, msg.subwindow.wx, msg.subwindow.wy, msg.subwindow.ww, msg.subwindow.wh, msg.subwindow.fbw, msg.subwindow.fbh, msg.subwindow.dpr, msg.subwindow.rotation); D("CMD_SETUP_SUBWINDOW: parent=%p wx=%d wy=%d ww=%d wh=%d fbw=%d fbh=%d dpr=%f rotation=%f\n", (void*)(intptr_t)msg.subwindow.parent, msg.subwindow.wx, msg.subwindow.wy, msg.subwindow.ww, msg.subwindow.wh, msg.subwindow.fbw, msg.subwindow.fbh, msg.subwindow.dpr, msg.subwindow.rotation); result = FrameBuffer::getFB()->setupSubWindow( msg.subwindow.parent, msg.subwindow.wx, msg.subwindow.wy, msg.subwindow.ww, msg.subwindow.wh, msg.subwindow.fbw, msg.subwindow.fbh, msg.subwindow.dpr, msg.subwindow.rotation, msg.subwindow.deleteExisting, msg.subwindow.hideWindow); break; case CMD_REMOVE_SUBWINDOW: GL_LOG("CMD_REMOVE_SUBWINDOW"); D("CMD_REMOVE_SUBWINDOW\n"); result = FrameBuffer::getFB()->removeSubWindow(); break; case CMD_SET_ROTATION: GL_LOG("CMD_SET_ROTATION rotation=%f", msg.rotation); D("CMD_SET_ROTATION rotation=%f\n", msg.rotation); fb = FrameBuffer::getFB(); if (fb) { fb->setDisplayRotation(msg.rotation); result = true; } break; case CMD_SET_TRANSLATION: GL_LOG("CMD_SET_TRANSLATION translation=%f,%f", msg.trans.px, msg.trans.py); D("CMD_SET_TRANSLATION translation=%f,%f\n", msg.trans.px, msg.trans.py); fb = FrameBuffer::getFB(); if (fb) { fb->setDisplayTranslation(msg.trans.px, msg.trans.py); result = true; } break; case CMD_REPAINT: GL_LOG("CMD_REPAINT"); D("CMD_REPAINT\n"); fb = FrameBuffer::getFB(); if (fb) { fb->repost(); result = true; } else { GL_LOG("CMD_REPAINT: no repost, no FrameBuffer"); } break; case CMD_HAS_GUEST_POSTED_A_FRAME: GL_LOG("CMD_HAS_GUEST_POSTED_A_FRAME"); D("CMD_HAS_GUEST_POSTED_A_FRAME\n"); fb = FrameBuffer::getFB(); if (fb) { result = fb->hasGuestPostedAFrame(); } else { GL_LOG("CMD_HAS_GUEST_POSTED_A_FRAME: no FrameBuffer"); } break; case CMD_RESET_GUEST_POSTED_A_FRAME: GL_LOG("CMD_RESET_GUEST_POSTED_A_FRAME"); D("CMD_RESET_GUEST_POSTED_A_FRAME\n"); fb = FrameBuffer::getFB(); if (fb) { fb->resetGuestPostedAFrame(); result = true; } else { GL_LOG("CMD_RESET_GUEST_POSTED_A_FRAME: no FrameBuffer"); } break; default: ; } return result; } }; // Simple synchronization structure used to exchange data between the // main and render window threads. Usage is the following: // // The main thread does the following in a loop: // // canWriteCmd.wait() // updates |message| by writing a new |cmd| value and appropriate // parameters. // canReadCmd.signal() // canReadResult.wait() // reads |message.result| // canWriteResult.signal() // // The render window thread will do the following: // // canReadCmd.wait() // reads |message.cmd| and acts upon it. // canWriteResult.wait() // writes |message.result| // canReadResult.signal() // canWriteCmd.signal() // class RenderWindowChannel { public: RenderWindowChannel() : mIn(), mOut() {} ~RenderWindowChannel() {} // Send a message from the main thread. // Note that the content of |msg| is copied into the channel. // Returns with the command's result (true or false). bool sendMessageAndGetResult(const RenderWindowMessage& msg) { D("msg.cmd=%d\n", msg.cmd); mIn.send(msg); D("waiting for result\n"); bool result = false; mOut.receive(&result); D("result=%s\n", result ? "success" : "failure"); return result; } // Receive a message from the render window thread. // On exit, |*msg| gets a copy of the message. The caller // must always call sendResult() after processing the message. void receiveMessage(RenderWindowMessage* msg) { D("entering\n"); mIn.receive(msg); D("message cmd=%d\n", msg->cmd); } // Send result from the render window thread to the main one. // Must always be called after receiveMessage(). void sendResult(bool result) { D("waiting to send result (%s)\n", result ? "success" : "failure"); mOut.send(result); D("result sent\n"); } private: android::base::MessageChannel mIn; android::base::MessageChannel mOut; }; namespace { // This class implements the window render thread. // Its purpose is to listen for commands from the main thread in a loop, // process them, then return a boolean result for each one of them. // // The thread ends with a CMD_FINALIZE. // class RenderWindowThread : public android::base::Thread { public: RenderWindowThread(RenderWindowChannel* channel) : mChannel(channel) {} virtual intptr_t main() { D("Entering render window thread thread\n"); #ifndef _WIN32 sigset_t set; sigfillset(&set); pthread_sigmask(SIG_SETMASK, &set, NULL); #endif bool running = true; while (running) { RenderWindowMessage msg = {}; D("Waiting for message from main thread\n"); mChannel->receiveMessage(&msg); bool result = msg.process(); if (msg.cmd == CMD_FINALIZE) { running = false; } D("Sending result (%s) to main thread\n", result ? "success" : "failure"); mChannel->sendResult(result); } D("Exiting thread\n"); return 0; } private: RenderWindowChannel* mChannel; }; } // namespace RenderWindow::RenderWindow(int width, int height, bool use_thread, bool use_sub_window, bool egl2egl) : mRepostThread([this] { while (auto cmd = mRepostCommands.receive()) { if (*cmd == RepostCommand::Sync) { continue; } else if (*cmd == RepostCommand::Repost && !mPaused) { GL_LOG("Reposting thread dequeueing a CMD_REPAINT"); RenderWindowMessage msg = {CMD_REPAINT}; (void)msg.process(); } } }) { if (use_thread) { mChannel = new RenderWindowChannel(); mThread = new RenderWindowThread(mChannel); mThread->start(); } else { mRepostThread.start(); } RenderWindowMessage msg = {}; msg.cmd = CMD_INITIALIZE; msg.init.width = width; msg.init.height = height; msg.init.useSubWindow = use_sub_window; msg.init.egl2egl = egl2egl; mValid = processMessage(msg); } RenderWindow::~RenderWindow() { D("Entering\n"); removeSubWindow(); mRepostCommands.stop(); D("Sending CMD_FINALIZE\n"); RenderWindowMessage msg = {}; msg.cmd = CMD_FINALIZE; (void) processMessage(msg); if (useThread()) { mThread->wait(NULL); delete mThread; delete mChannel; } else { mRepostThread.wait(); } } void RenderWindow::setPaused(bool paused) { // If pausing, flush commands if (!mPaused && paused) { if (useThread()) { fprintf(stderr, "WARNING: flushMessages unsupported for RenderWindowThread. " "Generic snapshot load might segfault.\n"); } else { mRepostCommands.waitForEmpty(); } } mPaused = paused; } bool RenderWindow::getHardwareStrings(const char** vendor, const char** renderer, const char** version) { D("Entering\n"); // TODO(digit): Move this to render window thread. FrameBuffer* fb = FrameBuffer::getFB(); if (!fb) { D("No framebuffer!\n"); return false; } fb->getGLStrings(vendor, renderer, version); D("Exiting vendor=[%s] renderer=[%s] version=[%s]\n", *vendor, *renderer, *version); return true; } void RenderWindow::setPostCallback(emugl::Renderer::OnPostCallback onPost, void* onPostContext, uint32_t displayId, bool useBgraReadback) { D("Entering\n"); RenderWindowMessage msg = {}; msg.cmd = CMD_SET_POST_CALLBACK; msg.set_post_callback.on_post = onPost; msg.set_post_callback.on_post_context = onPostContext; msg.set_post_callback.on_post_displayId = displayId; msg.set_post_callback.use_bgra_readback = useBgraReadback; (void) processMessage(msg); D("Exiting\n"); } bool RenderWindow::asyncReadbackSupported() { D("Entering\n"); return FrameBuffer::getFB()->asyncReadbackSupported(); } emugl::Renderer::ReadPixelsCallback RenderWindow::getReadPixelsCallback() { D("Entering\n"); return FrameBuffer::getFB()->getReadPixelsCallback(); } emugl::Renderer::FlushReadPixelPipeline RenderWindow::getFlushReadPixelPipeline() { return FrameBuffer::getFB()->getFlushReadPixelPipeline(); } bool RenderWindow::setupSubWindow(FBNativeWindowType window, int wx, int wy, int ww, int wh, int fbw, int fbh, float dpr, float zRot, bool deleteExisting, bool hideWindow) { D("Entering mHasSubWindow=%s\n", mHasSubWindow ? "true" : "false"); RenderWindowMessage msg = {}; msg.cmd = CMD_SETUP_SUBWINDOW; msg.subwindow.parent = window; msg.subwindow.wx = wx; msg.subwindow.wy = wy; msg.subwindow.ww = ww; msg.subwindow.wh = wh; msg.subwindow.fbw = fbw; msg.subwindow.fbh = fbh; msg.subwindow.dpr = dpr; msg.subwindow.rotation = zRot; msg.subwindow.deleteExisting = deleteExisting; msg.subwindow.hideWindow = hideWindow; mHasSubWindow = processMessage(msg); D("Exiting mHasSubWindow=%s\n", mHasSubWindow ? "true" : "false"); return mHasSubWindow; } bool RenderWindow::removeSubWindow() { D("Entering mHasSubWindow=%s\n", mHasSubWindow ? "true" : "false"); if (!mHasSubWindow) { return false; } mHasSubWindow = false; if (!useThread()) { mRepostCommands.send(RepostCommand::Sync); mRepostCommands.waitForEmpty(); } RenderWindowMessage msg = {}; msg.cmd = CMD_REMOVE_SUBWINDOW; bool result = processMessage(msg); D("Exiting result=%s\n", result ? "success" : "failure"); return result; } void RenderWindow::setRotation(float zRot) { D("Entering rotation=%f\n", zRot); RenderWindowMessage msg = {}; msg.cmd = CMD_SET_ROTATION; msg.rotation = zRot; (void) processMessage(msg); D("Exiting\n"); } void RenderWindow::setTranslation(float px, float py) { D("Entering translation=%f,%f\n", px, py); RenderWindowMessage msg = {}; msg.cmd = CMD_SET_TRANSLATION; msg.trans.px = px; msg.trans.py = py; (void) processMessage(msg); D("Exiting\n"); } void RenderWindow::setScreenMask(int width, int height, const unsigned char* rgbaData) { FrameBuffer* fb = FrameBuffer::getFB(); if (fb) { fb->getTextureDraw()->setScreenMask(width, height, rgbaData); } } void RenderWindow::repaint() { D("Entering\n"); RenderWindowMessage msg = {}; msg.cmd = CMD_REPAINT; (void) processMessage(msg); D("Exiting\n"); } bool RenderWindow::hasGuestPostedAFrame() { D("Entering\n"); RenderWindowMessage msg = {}; msg.cmd = CMD_HAS_GUEST_POSTED_A_FRAME; bool res = processMessage(msg); D("Exiting\n"); return res; } void RenderWindow::resetGuestPostedAFrame() { D("Entering\n"); RenderWindowMessage msg = {}; msg.cmd = CMD_RESET_GUEST_POSTED_A_FRAME; (void) processMessage(msg); D("Exiting\n"); } bool RenderWindow::processMessage(const RenderWindowMessage& msg) { if (useThread()) { if (msg.cmd == CMD_REPAINT) { GL_LOG("Sending CMD_REPAINT to render window channel"); } return mChannel->sendMessageAndGetResult(msg); } else if (msg.cmd == CMD_REPAINT) { GL_LOG("Sending CMD_REPAINT to reposting thread"); mRepostCommands.send(RepostCommand::Repost); return true; } else { return msg.process(); } }