blob: c8b9747a696b09b9c728db2de130b82dd0dfdc6e [file] [log] [blame]
// 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