| // Copyright (C) 2018 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 "SampleApplication.h" |
| |
| #include "aemu/base/GLObjectCounter.h" |
| #include "aemu/base/synchronization/ConditionVariable.h" |
| #include "aemu/base/synchronization/Lock.h" |
| #include "aemu/base/system/System.h" |
| #include "aemu/base/threads/FunctorThread.h" |
| #include "aemu/base/testing/TestSystem.h" |
| #include "host-common/GraphicsAgentFactory.h" |
| #include "host-common/multi_display_agent.h" |
| #include "host-common/MultiDisplay.h" |
| #include "host-common/opengl/misc.h" |
| #include "Standalone.h" |
| |
| #include <EGL/egl.h> |
| #include <EGL/eglext.h> |
| #include <GLES3/gl3.h> |
| |
| namespace gfxstream { |
| |
| using android::base::AutoLock; |
| using android::base::ConditionVariable; |
| using android::base::FunctorThread; |
| using android::base::Lock; |
| using android::base::MessageChannel; |
| using android::base::TestSystem; |
| using gl::EmulatedEglFenceSync; |
| using gl::GLESApi; |
| using gl::GLESApi_3_0; |
| using gl::GLESApi_CM; |
| |
| // Class holding the persistent test window. |
| class TestWindow { |
| public: |
| TestWindow() { |
| window = CreateOSWindow(); |
| } |
| |
| ~TestWindow() { |
| if (window) { |
| window->destroy(); |
| } |
| } |
| |
| void setRect(int xoffset, int yoffset, int width, int height) { |
| if (mFirstResize) { |
| initializeWithRect(xoffset, yoffset, width, height); |
| } else { |
| resizeWithRect(xoffset, yoffset, width, height); |
| } |
| } |
| |
| // Check on initialization if windows are available. |
| bool initializeWithRect(int xoffset, int yoffset, int width, int height) { |
| if (!window->initialize("libOpenglRender test", width, height)) { |
| window->destroy(); |
| window = nullptr; |
| return false; |
| } |
| window->setVisible(true); |
| window->setPosition(xoffset, yoffset); |
| window->messageLoop(); |
| mFirstResize = false; |
| return true; |
| } |
| |
| void resizeWithRect(int xoffset, int yoffset, int width, int height) { |
| if (!window) return; |
| |
| window->setPosition(xoffset, yoffset); |
| window->resize(width, height); |
| window->messageLoop(); |
| } |
| |
| OSWindow* window = nullptr; |
| private: |
| bool mFirstResize = true; |
| }; |
| |
| static TestWindow* sTestWindow() { |
| static TestWindow* w = new TestWindow; |
| return w; |
| } |
| |
| bool shouldUseHostGpu() { |
| bool useHost = android::base::getEnvironmentVariable("ANDROID_EMU_TEST_WITH_HOST_GPU") == "1"; |
| |
| // Also set the global emugl renderer accordingly. |
| if (useHost) { |
| emugl::setRenderer(SELECTED_RENDERER_HOST); |
| } else { |
| emugl::setRenderer(SELECTED_RENDERER_SWIFTSHADER_INDIRECT); |
| } |
| |
| return useHost; |
| } |
| |
| bool shouldUseWindow() { |
| bool useWindow = android::base::getEnvironmentVariable("ANDROID_EMU_TEST_WITH_WINDOW") == "1"; |
| return useWindow; |
| } |
| |
| OSWindow* createOrGetTestWindow(int xoffset, int yoffset, int width, int height) { |
| if (!shouldUseWindow()) return nullptr; |
| |
| sTestWindow()->setRect(xoffset, yoffset, width, height); |
| return sTestWindow()->window; |
| } |
| |
| class Vsync { |
| public: |
| Vsync(int refreshRate = 60) : |
| mRefreshRate(refreshRate), |
| mRefreshIntervalUs(1000000ULL / mRefreshRate), |
| mThread([this] { |
| while (true) { |
| if (mShouldStop) return 0; |
| android::base::sleepUs(mRefreshIntervalUs); |
| AutoLock lock(mLock); |
| mSync = 1; |
| mCv.signal(); |
| } |
| return 0; |
| }) { |
| mThread.start(); |
| } |
| |
| ~Vsync() { |
| mShouldStop = true; |
| } |
| |
| void waitUntilNextVsync() { |
| AutoLock lock(mLock); |
| mSync = 0; |
| while (!mSync) { |
| mCv.wait(&mLock); |
| } |
| } |
| |
| private: |
| int mShouldStop = false; |
| int mRefreshRate = 60; |
| uint64_t mRefreshIntervalUs; |
| volatile int mSync = 0; |
| |
| Lock mLock; |
| ConditionVariable mCv; |
| |
| FunctorThread mThread; |
| }; |
| |
| // app -> SF queue: separate storage, bindTexture blits |
| // SF queue -> HWC: shared storage |
| class ColorBufferQueue { // Note: we could have called this BufferQueue but there is another |
| // class of name BufferQueue that does something totally different |
| |
| public: |
| static constexpr int kCapacity = 3; |
| class Item { |
| public: |
| Item(unsigned int cb = 0, EmulatedEglFenceSync* s = nullptr) |
| : colorBuffer(cb), sync(s) { } |
| unsigned int colorBuffer = 0; |
| EmulatedEglFenceSync* sync = nullptr; |
| }; |
| |
| ColorBufferQueue() = default; |
| |
| void queueBuffer(const Item& item) { |
| mQueue.send(item); |
| } |
| |
| void dequeueBuffer(Item* outItem) { |
| mQueue.receive(outItem); |
| } |
| |
| private: |
| MessageChannel<Item, kCapacity> mQueue; |
| }; |
| |
| class AutoComposeDevice { |
| public: |
| AutoComposeDevice(uint32_t targetCb, uint32_t layerCnt = 2) : |
| mData(sizeof(ComposeDevice) + layerCnt * sizeof(ComposeLayer)) |
| { |
| mComposeDevice = reinterpret_cast<ComposeDevice*>(mData.data()); |
| mComposeDevice->version = 1; |
| mComposeDevice->targetHandle = targetCb; |
| mComposeDevice->numLayers = layerCnt; |
| } |
| |
| ComposeDevice* get() { |
| return mComposeDevice; |
| } |
| |
| uint32_t getSize() { |
| return mData.size(); |
| } |
| |
| void configureLayer(uint32_t layerId, unsigned int cb, |
| hwc2_composition_t composeMode, |
| hwc_rect_t displayFrame, |
| hwc_frect_t crop, |
| hwc2_blend_mode_t blendMode, |
| float alpha, |
| hwc_color_t color |
| ) { |
| mComposeDevice->layer[layerId].cbHandle = cb; |
| mComposeDevice->layer[layerId].composeMode = composeMode; |
| mComposeDevice->layer[layerId].displayFrame = displayFrame; |
| mComposeDevice->layer[layerId].crop = crop; |
| mComposeDevice->layer[layerId].blendMode = blendMode; |
| mComposeDevice->layer[layerId].alpha = alpha; |
| mComposeDevice->layer[layerId].color = color; |
| mComposeDevice->layer[layerId].transform = HWC_TRANSFORM_FLIP_H; |
| } |
| |
| private: |
| std::vector<uint8_t> mData; |
| ComposeDevice* mComposeDevice; |
| }; |
| |
| extern "C" const QAndroidMultiDisplayAgent* const gMockQAndroidMultiDisplayAgent; |
| |
| // SampleApplication implementation///////////////////////////////////////////// |
| SampleApplication::SampleApplication(int windowWidth, int windowHeight, int refreshRate, GLESApi glVersion, bool compose) : |
| mWidth(windowWidth), mHeight(windowHeight), mRefreshRate(refreshRate), mIsCompose(compose) { |
| |
| // setupStandaloneLibrarySearchPaths(); |
| emugl::setGLObjectCounter(android::base::GLObjectCounter::get()); |
| emugl::set_emugl_window_operations(*getGraphicsAgents()->emu);; |
| emugl::set_emugl_multi_display_operations(*getGraphicsAgents()->multi_display); |
| gl::LazyLoadedEGLDispatch::get(); |
| if (glVersion == GLESApi_CM) gl::LazyLoadedGLESv1Dispatch::get(); |
| gl::LazyLoadedGLESv2Dispatch::get(); |
| |
| bool useHostGpu = shouldUseHostGpu(); |
| mWindow = createOrGetTestWindow(mXOffset, mYOffset, mWidth, mHeight); |
| mUseSubWindow = mWindow != nullptr; |
| |
| FrameBuffer::initialize( |
| mWidth, mHeight, {}, |
| mUseSubWindow, |
| !useHostGpu /* egl2egl */); |
| mFb = FrameBuffer::getFB(); |
| |
| if (mUseSubWindow) { |
| mFb->setupSubWindow( |
| (FBNativeWindowType)(uintptr_t) |
| mWindow->getFramebufferNativeWindow(), |
| 0, 0, |
| mWidth, mHeight, mWidth, mHeight, |
| mWindow->getDevicePixelRatio(), 0, false, false); |
| mWindow->messageLoop(); |
| } |
| |
| mRenderThreadInfo.reset(new RenderThreadInfo()); |
| mRenderThreadInfo->initGl(); |
| |
| mColorBuffer = mFb->createColorBuffer(mWidth, mHeight, GL_RGBA, FRAMEWORK_FORMAT_GL_COMPATIBLE); |
| mContext = mFb->createEmulatedEglContext(0, 0, glVersion); |
| mSurface = mFb->createEmulatedEglWindowSurface(0, mWidth, mHeight); |
| |
| mFb->bindContext(mContext, mSurface, mSurface); |
| mFb->setEmulatedEglWindowSurfaceColorBuffer(mSurface, mColorBuffer); |
| |
| if (mIsCompose && mTargetCb == 0) { |
| mTargetCb = mFb->createColorBuffer(mFb->getWidth(), |
| mFb->getHeight(), |
| GL_RGBA, |
| FRAMEWORK_FORMAT_GL_COMPATIBLE); |
| mFb->openColorBuffer(mTargetCb); |
| } |
| } |
| |
| SampleApplication::~SampleApplication() { |
| if (mFb) { |
| if (mTargetCb) { |
| mFb->closeColorBuffer(mTargetCb); |
| } |
| mFb->bindContext(0, 0, 0); |
| mFb->closeColorBuffer(mColorBuffer); |
| mFb->destroyEmulatedEglWindowSurface(mSurface); |
| mFb = nullptr; |
| FrameBuffer::finalize(); |
| } |
| } |
| |
| void SampleApplication::rebind() { |
| mFb->bindContext(mContext, mSurface, mSurface); |
| } |
| |
| void SampleApplication::drawLoop() { |
| this->initialize(); |
| |
| Vsync vsync(mRefreshRate); |
| |
| while (true) { |
| this->draw(); |
| mFb->flushEmulatedEglWindowSurfaceColorBuffer(mSurface); |
| vsync.waitUntilNextVsync(); |
| if (mUseSubWindow) { |
| mFb->post(mColorBuffer); |
| mWindow->messageLoop(); |
| } |
| } |
| } |
| |
| EmulatedEglFenceSync* SampleApplication::getFenceSync() { |
| uint64_t sync; |
| mFb->createEmulatedEglFenceSync(EGL_SYNC_FENCE_KHR, false, &sync); |
| return EmulatedEglFenceSync::getFromHandle(sync); |
| } |
| |
| void SampleApplication::drawWorkerWithCompose(ColorBufferQueue& app2sfQueue, |
| ColorBufferQueue& sf2appQueue) { |
| ColorBufferQueue::Item appItem = {}; |
| AutoComposeDevice autoComposeDevice(mTargetCb); |
| hwc_rect_t displayFrame = {0, mHeight/2, mWidth, mHeight}; |
| hwc_frect_t crop = {0.0, 0.0, 0.0, 0.0}; |
| hwc_color_t color = {200, 0, 0, 255}; |
| autoComposeDevice.configureLayer(0, 0, |
| HWC2_COMPOSITION_SOLID_COLOR, |
| displayFrame, |
| crop, |
| HWC2_BLEND_MODE_NONE, |
| 1.0, |
| color); |
| |
| while (true) { |
| app2sfQueue.dequeueBuffer(&appItem); |
| if (appItem.sync) { appItem.sync->wait(EGL_FOREVER_KHR); } |
| |
| hwc_rect_t displayFrame = {0, 0, mWidth, mHeight/2}; |
| hwc_frect_t crop = {0.0, 0.0, (float)mWidth, (float)mHeight}; |
| hwc_color_t color = {0, 0, 0, 0}; |
| autoComposeDevice.configureLayer(1, |
| appItem.colorBuffer, |
| HWC2_COMPOSITION_DEVICE, |
| displayFrame, |
| crop, |
| HWC2_BLEND_MODE_PREMULTIPLIED, |
| 0.8, |
| color); |
| mFb->compose(autoComposeDevice.getSize(), autoComposeDevice.get()); |
| |
| if (appItem.sync) { appItem.sync->decRef(); } |
| sf2appQueue.queueBuffer(ColorBufferQueue::Item(appItem.colorBuffer, getFenceSync())); |
| } |
| } |
| |
| void SampleApplication::drawWorker(ColorBufferQueue& app2sfQueue, |
| ColorBufferQueue& sf2appQueue, |
| ColorBufferQueue& sf2hwcQueue, |
| ColorBufferQueue& hwc2sfQueue) { |
| RenderThreadInfo* tInfo = new RenderThreadInfo; |
| unsigned int sfContext = mFb->createEmulatedEglContext(0, 0, GLESApi_3_0); |
| unsigned int sfSurface = mFb->createEmulatedEglWindowSurface(0, mWidth, mHeight); |
| mFb->bindContext(sfContext, sfSurface, sfSurface); |
| |
| auto gl = getGlDispatch(); |
| |
| static constexpr char blitVshaderSrc[] = R"(#version 300 es |
| precision highp float; |
| layout (location = 0) in vec2 pos; |
| layout (location = 1) in vec2 texcoord; |
| out vec2 texcoord_varying; |
| void main() { |
| gl_Position = vec4(pos, 0.0, 1.0); |
| texcoord_varying = texcoord; |
| })"; |
| |
| static constexpr char blitFshaderSrc[] = R"(#version 300 es |
| precision highp float; |
| uniform sampler2D tex; |
| in vec2 texcoord_varying; |
| out vec4 fragColor; |
| void main() { |
| fragColor = texture(tex, texcoord_varying); |
| })"; |
| |
| GLint blitProgram = |
| compileAndLinkShaderProgram( |
| blitVshaderSrc, blitFshaderSrc); |
| |
| GLint samplerLoc = gl->glGetUniformLocation(blitProgram, "tex"); |
| |
| GLuint blitVbo; |
| gl->glGenBuffers(1, &blitVbo); |
| gl->glBindBuffer(GL_ARRAY_BUFFER, blitVbo); |
| const float attrs[] = { |
| -1.0f, -1.0f, 0.0f, 1.0f, |
| 1.0f, -1.0f, 1.0f, 1.0f, |
| 1.0f, 1.0f, 1.0f, 0.0f, |
| -1.0f, -1.0f, 0.0f, 1.0f, |
| 1.0f, 1.0f, 1.0f, 0.0f, |
| -1.0f, 1.0f, 0.0f, 0.0f, |
| }; |
| gl->glBufferData(GL_ARRAY_BUFFER, sizeof(attrs), attrs, GL_STATIC_DRAW); |
| gl->glEnableVertexAttribArray(0); |
| gl->glEnableVertexAttribArray(1); |
| |
| gl->glVertexAttribPointer( |
| 0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), 0); |
| gl->glVertexAttribPointer( |
| 1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), |
| (GLvoid*)(uintptr_t)(2 * sizeof(GLfloat))); |
| |
| GLuint blitTexture; |
| gl->glActiveTexture(GL_TEXTURE0); |
| gl->glGenTextures(1, &blitTexture); |
| gl->glBindTexture(GL_TEXTURE_2D, blitTexture); |
| |
| gl->glUseProgram(blitProgram); |
| gl->glUniform1i(samplerLoc, 0); |
| |
| ColorBufferQueue::Item appItem = {}; |
| ColorBufferQueue::Item hwcItem = {}; |
| |
| while (true) { |
| hwc2sfQueue.dequeueBuffer(&hwcItem); |
| if (hwcItem.sync) { hwcItem.sync->wait(EGL_FOREVER_KHR); } |
| |
| mFb->setEmulatedEglWindowSurfaceColorBuffer(sfSurface, hwcItem.colorBuffer); |
| |
| { |
| app2sfQueue.dequeueBuffer(&appItem); |
| |
| mFb->bindColorBufferToTexture(appItem.colorBuffer); |
| |
| gl->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); |
| |
| gl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); |
| gl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); |
| |
| if (appItem.sync) { appItem.sync->wait(EGL_FOREVER_KHR); } |
| |
| gl->glDrawArrays(GL_TRIANGLES, 0, 6); |
| |
| if (appItem.sync) { appItem.sync->decRef(); } |
| sf2appQueue.queueBuffer(ColorBufferQueue::Item(appItem.colorBuffer, getFenceSync())); |
| } |
| |
| mFb->flushEmulatedEglWindowSurfaceColorBuffer(sfSurface); |
| |
| if (hwcItem.sync) { hwcItem.sync->decRef(); } |
| sf2hwcQueue.queueBuffer(ColorBufferQueue::Item(hwcItem.colorBuffer, getFenceSync())); |
| } |
| delete tInfo; |
| } |
| |
| void SampleApplication::surfaceFlingerComposerLoop() { |
| ColorBufferQueue app2sfQueue; |
| ColorBufferQueue sf2appQueue; |
| ColorBufferQueue sf2hwcQueue; |
| ColorBufferQueue hwc2sfQueue; |
| |
| std::vector<unsigned int> sfColorBuffers; |
| std::vector<unsigned int> hwcColorBuffers; |
| |
| for (int i = 0; i < ColorBufferQueue::kCapacity; i++) { |
| sfColorBuffers.push_back(mFb->createColorBuffer(mWidth, mHeight, GL_RGBA, FRAMEWORK_FORMAT_GL_COMPATIBLE)); |
| hwcColorBuffers.push_back(mFb->createColorBuffer(mWidth, mHeight, GL_RGBA, FRAMEWORK_FORMAT_GL_COMPATIBLE)); |
| } |
| |
| for (int i = 0; i < ColorBufferQueue::kCapacity; i++) { |
| mFb->openColorBuffer(sfColorBuffers[i]); |
| mFb->openColorBuffer(hwcColorBuffers[i]); |
| } |
| |
| // prime the queue |
| for (int i = 0; i < ColorBufferQueue::kCapacity; i++) { |
| sf2appQueue.queueBuffer(ColorBufferQueue::Item(sfColorBuffers[i], nullptr)); |
| hwc2sfQueue.queueBuffer(ColorBufferQueue::Item(hwcColorBuffers[i], nullptr)); |
| } |
| |
| FunctorThread appThread([&]() { |
| RenderThreadInfo* tInfo = new RenderThreadInfo; |
| unsigned int appContext = mFb->createEmulatedEglContext(0, 0, GLESApi_3_0); |
| unsigned int appSurface = mFb->createEmulatedEglWindowSurface(0, mWidth, mHeight); |
| mFb->bindContext(appContext, appSurface, appSurface); |
| |
| ColorBufferQueue::Item sfItem = {}; |
| |
| sf2appQueue.dequeueBuffer(&sfItem); |
| mFb->setEmulatedEglWindowSurfaceColorBuffer(appSurface, sfItem.colorBuffer); |
| if (sfItem.sync) { sfItem.sync->wait(EGL_FOREVER_KHR); sfItem.sync->decRef(); } |
| |
| this->initialize(); |
| |
| while (true) { |
| this->draw(); |
| mFb->flushEmulatedEglWindowSurfaceColorBuffer(appSurface); |
| app2sfQueue.queueBuffer(ColorBufferQueue::Item(sfItem.colorBuffer, getFenceSync())); |
| |
| sf2appQueue.dequeueBuffer(&sfItem); |
| mFb->setEmulatedEglWindowSurfaceColorBuffer(appSurface, sfItem.colorBuffer); |
| if (sfItem.sync) { sfItem.sync->wait(EGL_FOREVER_KHR); sfItem.sync->decRef(); } |
| } |
| |
| delete tInfo; |
| }); |
| |
| FunctorThread sfThread([&]() { |
| if (mIsCompose) { |
| drawWorkerWithCompose(app2sfQueue, sf2appQueue); |
| } |
| else { |
| drawWorker(app2sfQueue, sf2appQueue, sf2hwcQueue, hwc2sfQueue); |
| } |
| }); |
| |
| sfThread.start(); |
| appThread.start(); |
| |
| Vsync vsync(mRefreshRate); |
| ColorBufferQueue::Item sfItem = {}; |
| if (!mIsCompose) { |
| while (true) { |
| sf2hwcQueue.dequeueBuffer(&sfItem); |
| if (sfItem.sync) { sfItem.sync->wait(EGL_FOREVER_KHR); sfItem.sync->decRef(); } |
| vsync.waitUntilNextVsync(); |
| mFb->post(sfItem.colorBuffer); |
| if (mUseSubWindow) { |
| mWindow->messageLoop(); |
| } |
| hwc2sfQueue.queueBuffer(ColorBufferQueue::Item(sfItem.colorBuffer, getFenceSync())); |
| } |
| } |
| |
| appThread.wait(); |
| sfThread.wait(); |
| } |
| |
| void SampleApplication::drawOnce() { |
| this->initialize(); |
| this->draw(); |
| mFb->flushEmulatedEglWindowSurfaceColorBuffer(mSurface); |
| if (mUseSubWindow) { |
| mFb->post(mColorBuffer); |
| mWindow->messageLoop(); |
| } |
| } |
| |
| const gl::GLESv2Dispatch* SampleApplication::getGlDispatch() { |
| return gl::LazyLoadedGLESv2Dispatch::get(); |
| } |
| |
| bool SampleApplication::isSwANGLE() { |
| const char* vendor; |
| const char* renderer; |
| const char* version; |
| mFb->getGLStrings(&vendor, &renderer, &version); |
| return strstr(renderer, "ANGLE") && strstr(renderer, "SwiftShader"); |
| } |
| |
| } // namespace gfxstream |