| // 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 "aemu/base/threads/Thread.h" |
| #include "aemu/base/synchronization/MessageChannel.h" |
| #include "host-common/logging.h" |
| #include "FrameBuffer.h" |
| #include "RendererImpl.h" |
| |
| #include <stdarg.h> |
| #include <stdio.h> |
| #ifndef _WIN32 |
| #include <signal.h> |
| #include <pthread.h> |
| #endif |
| |
| namespace gfxstream { |
| |
| #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_SET_VSYNC_HZ, |
| CMD_SET_DISPLAY_CONFIGS, |
| CMD_SET_DISPLAY_ACTIVE_CONFIG, |
| 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 { |
| 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; |
| |
| // CMD_SET_VSYNC_HZ |
| int vsyncHz; |
| |
| // CMD_SET_COMPOSE_DIMENSIONS |
| struct { |
| int configId; |
| int width; |
| int height; |
| int dpiX; |
| int dpiY; |
| } displayConfigs; |
| |
| int displayActiveConfig; |
| |
| // 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 |
| FrameBuffer::finalize(); |
| result = true; |
| break; |
| |
| case CMD_SET_POST_CALLBACK: |
| GL_LOG("CMD_SET_POST_CALLBACK"); |
| D("CMD_SET_POST_CALLBACK\n"); |
| fb = FrameBuffer::getFB(); |
| if (fb) { |
| 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); |
| fb = FrameBuffer::getFB(); |
| if (fb) { |
| 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"); |
| fb = FrameBuffer::getFB(); |
| if (fb) { |
| result = fb->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; |
| |
| case CMD_SET_VSYNC_HZ: |
| GL_LOG("CMD_SET_VSYNC_HZ"); |
| D("CMD_SET_VSYNC_HZ\n"); |
| fb = FrameBuffer::getFB(); |
| if (fb) { |
| fb->setVsyncHz(msg.vsyncHz); |
| result = true; |
| } else { |
| GL_LOG("CMD_RESET_GUEST_POSTED_A_FRAME: no FrameBuffer"); |
| } |
| break; |
| |
| case CMD_SET_DISPLAY_CONFIGS: |
| GL_LOG("CMD_SET_DISPLAY_CONFIGS"); |
| D("CMD_SET_DISPLAY_CONFIGS"); |
| fb = FrameBuffer::getFB(); |
| if (fb) { |
| fb->setDisplayConfigs(msg.displayConfigs.configId, |
| msg.displayConfigs.width, |
| msg.displayConfigs.height, |
| msg.displayConfigs.dpiX, |
| msg.displayConfigs.dpiY); |
| result = true; |
| } else { |
| GL_LOG("CMD_SET_DISPLAY_CONFIGS: no FrameBuffer"); |
| } |
| break; |
| |
| case CMD_SET_DISPLAY_ACTIVE_CONFIG: |
| GL_LOG("CMD_SET_DISPLAY_ACTIVE_CONFIG"); |
| D("CMD_SET_DISPLAY_ACTIVE_CONFIG"); |
| fb = FrameBuffer::getFB(); |
| if (fb) { |
| fb->setDisplayActiveConfig(msg.displayActiveConfig); |
| result = true; |
| } else { |
| GL_LOG("CMD_SET_DISPLAY_ACTIVE_CONFIG: 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<RenderWindowMessage, 16U> mIn; |
| android::base::MessageChannel<bool, 16U> 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(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(); |
| } |
| |
| Renderer::ReadPixelsCallback RenderWindow::getReadPixelsCallback() { |
| D("Entering\n"); |
| return FrameBuffer::getFB()->getReadPixelsCallback(); |
| } |
| |
| void RenderWindow::addListener(Renderer::FrameBufferChangeEventListener* listener) { |
| FrameBuffer::getFB()->addListener(listener); |
| } |
| |
| void RenderWindow::removeListener(Renderer::FrameBufferChangeEventListener* listener) { |
| FrameBuffer::getFB()->removeListener(listener); |
| } |
| |
| 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) { |
| if (FrameBuffer* fb = FrameBuffer::getFB()) { |
| if (fb->hasEmulationGl()) { |
| fb->getTextureDraw()->setScreenMask(width, height, rgbaData); |
| } else { |
| ERR("RenderWindow::setScreenMask() not supported without GL emulation."); |
| } |
| } |
| } |
| |
| 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"); |
| } |
| |
| void RenderWindow::setVsyncHz(int vsyncHz) { |
| D("Entering\n"); |
| RenderWindowMessage msg = {}; |
| msg.cmd = CMD_SET_VSYNC_HZ; |
| msg.vsyncHz = vsyncHz; |
| (void) processMessage(msg); |
| D("Exiting\n"); |
| } |
| |
| void RenderWindow::setDisplayConfigs(int configId, int w, int h, |
| int dpiX, int dpiY) { |
| D("Entering\n"); |
| RenderWindowMessage msg = {}; |
| msg.cmd = CMD_SET_DISPLAY_CONFIGS; |
| msg.displayConfigs.configId = configId; |
| msg.displayConfigs.width = w; |
| msg.displayConfigs.height= h; |
| msg.displayConfigs.dpiX= dpiX; |
| msg.displayConfigs.dpiY = dpiY; |
| (void) processMessage(msg); |
| D("Exiting\n"); |
| } |
| |
| void RenderWindow::setDisplayActiveConfig(int configId) { |
| D("Entering\n"); |
| RenderWindowMessage msg = {}; |
| msg.cmd = CMD_SET_DISPLAY_ACTIVE_CONFIG; |
| msg.displayActiveConfig = configId; |
| (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(); |
| } |
| } |
| |
| } // namespace gfxstream |