blob: dc565401780d84e27b0d470ce302d85b1825afaf [file] [log] [blame]
// Copyright 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 "ClientComposer.h"
#include "AndroidBufferQueue.h"
#include "AndroidWindow.h"
#define EGL_EGLEXT_PROTOTYPES
#define GL_GLEXT_PROTOTYPES
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
#include "android/base/synchronization/Lock.h"
#include "android/base/threads/WorkerThread.h"
#include <atomic>
#include <unordered_map>
#include <stdio.h>
#define E(fmt,...) \
fprintf(stderr, "%s: " fmt "\n", __func__, ##__VA_ARGS__);
using android::base::AutoLock;
using android::base::WorkerProcessingResult;
using android::base::WorkerThread;
using android::base::Lock;
namespace aemu {
class ClientComposer::Impl {
public:
enum Command {
Stop = 0,
AdvanceFrame = 1,
};
Impl(AndroidWindow* composeWindow,
AndroidBufferQueue* fromApp,
AndroidBufferQueue* toApp)
: mWorkerThread([this, composeWindow, fromApp, toApp](
Command&& command) {
Command cmd = command;
return this->composeLoop(composeWindow, fromApp, toApp, cmd);
}) {
mWorkerThread.start();
}
void join() {
mWorkerThread.enqueue(Command::Stop);
mWorkerThread.join();
}
~Impl() {
join();
}
void advanceFrame() {
mWorkerThread.enqueue(Command::AdvanceFrame);
}
private:
void initialize(AndroidWindow* composeWindow) {
mDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
EGLint maj, min;
eglInitialize(mDisplay, &maj, &min);
eglBindAPI(EGL_OPENGL_ES_API);
EGLint total_num_configs = 0;
eglGetConfigs(mDisplay, 0, 0, &total_num_configs);
EGLint wantedRedSize = 8;
EGLint wantedGreenSize = 8;
EGLint wantedBlueSize = 8;
const GLint configAttribs[] = {
EGL_RED_SIZE, wantedRedSize,
EGL_GREEN_SIZE, wantedGreenSize,
EGL_BLUE_SIZE, wantedBlueSize,
EGL_SURFACE_TYPE, EGL_WINDOW_BIT | EGL_PBUFFER_BIT,
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_NONE };
EGLint total_compatible = 0;
std::vector<EGLConfig> configs(total_num_configs);
eglChooseConfig(mDisplay, configAttribs, configs.data(), total_num_configs,
&total_compatible);
EGLint exact_match_index = -1;
for (EGLint i = 0; i < total_compatible; i++) {
EGLint r, g, b;
EGLConfig c = configs[i];
eglGetConfigAttrib(mDisplay, c, EGL_RED_SIZE, &r);
eglGetConfigAttrib(mDisplay, c, EGL_GREEN_SIZE, &g);
eglGetConfigAttrib(mDisplay, c, EGL_BLUE_SIZE, &b);
if (r == wantedRedSize && g == wantedGreenSize &&
b == wantedBlueSize) {
exact_match_index = i;
break;
}
}
EGLConfig match = configs[exact_match_index];
static const GLint glesAtt[] = {EGL_CONTEXT_CLIENT_VERSION, 2,
EGL_NONE};
mContext = eglCreateContext(mDisplay, match, EGL_NO_CONTEXT, glesAtt);
mSurface = eglCreateWindowSurface(
mDisplay, match, (EGLNativeWindowType)composeWindow, 0);
eglMakeCurrent(mDisplay, mSurface, mSurface, mContext);
static constexpr char blitVshaderSrc[] = R"(
precision highp float;
attribute vec2 pos;
attribute vec2 texcoord;
varying vec2 texcoord_varying;
void main() {
gl_Position = vec4(pos, 0.0, 1.0);
texcoord_varying = texcoord;
})";
static constexpr char blitFshaderSrc[] = R"(
precision highp float;
uniform sampler2D tex;
varying vec2 texcoord_varying;
void main() {
gl_FragColor = texture2D(tex, texcoord_varying);
})";
mProgram =
compileAndLinkShaderProgram(
blitVshaderSrc, blitFshaderSrc);
GLint samplerLoc = glGetUniformLocation(mProgram, "tex");
glGenBuffers(1, &mVbo);
glBindBuffer(GL_ARRAY_BUFFER, mVbo);
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,
};
glBufferData(GL_ARRAY_BUFFER, sizeof(attrs), attrs, GL_STATIC_DRAW);
mPosLoc = glGetAttribLocation(mProgram, "pos");
mTexCoordLoc = glGetAttribLocation(mProgram, "texcoord");
glEnableVertexAttribArray(mPosLoc);
glEnableVertexAttribArray(mTexCoordLoc);
glVertexAttribPointer(mPosLoc, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat),
0);
glVertexAttribPointer(mTexCoordLoc, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat),
(GLvoid*)(uintptr_t)(2 * sizeof(GLfloat)));
glActiveTexture(GL_TEXTURE0);
glGenTextures(1, &mTexture);
glBindTexture(GL_TEXTURE_2D, mTexture);
glUseProgram(mProgram);
glUniform1i(samplerLoc, 0);
mInitialized = true;
}
WorkerProcessingResult composeLoop(AndroidWindow* composeWindow,
AndroidBufferQueue* fromApp,
AndroidBufferQueue* toApp,
Command command) {
if (!mInitialized)
initialize(composeWindow);
AndroidBufferQueue::Item appItem = {};
switch (command) {
default:
case Command::Stop:
teardown();
return WorkerProcessingResult::Stop;
case Command::AdvanceFrame:
fromApp->dequeueBuffer(&appItem);
glEGLImageTargetTexture2DOES(
GL_TEXTURE_2D, createOrGetEGLImage(appItem.buffer));
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
GL_NEAREST);
glDrawArrays(GL_TRIANGLES, 0, 6);
toApp->queueBuffer(
AndroidBufferQueue::Item(appItem.buffer, -1));
eglSwapBuffers(mDisplay, mSurface);
return WorkerProcessingResult::Continue;
}
}
void teardown() {
for (auto it : mEGLImages) {
eglDestroyImageKHR(mDisplay, it.second);
}
glDisableVertexAttribArray(mPosLoc);
glDisableVertexAttribArray(mTexCoordLoc);
glUseProgram(0);
glDeleteProgram(mProgram);
glBindTexture(GL_TEXTURE_2D, 0);
glDeleteTextures(1, &mTexture);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glDeleteBuffers(1, &mVbo);
eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE,
EGL_NO_CONTEXT);
eglDestroySurface(mDisplay, mSurface);
eglDestroyContext(mDisplay, mContext);
eglReleaseThread();
}
EGLImageKHR createOrGetEGLImage(ANativeWindowBuffer* buffer) {
auto it = mEGLImages.find(buffer);
if (it != mEGLImages.end()) return it->second;
EGLImageKHR image =
eglCreateImageKHR(mDisplay, 0, EGL_NATIVE_BUFFER_ANDROID,
(EGLClientBuffer)buffer, 0);
mEGLImages[buffer] = image;
return image;
}
GLuint compileShader(GLenum shaderType, const char* src) {
GLuint shader = glCreateShader(shaderType);
glShaderSource(shader, 1, (const GLchar* const*)&src, nullptr);
glCompileShader(shader);
GLint compileStatus;
glGetShaderiv(shader, GL_COMPILE_STATUS, &compileStatus);
if (compileStatus != GL_TRUE) {
GLsizei infoLogLength = 0;
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLogLength);
std::vector<char> infoLog(infoLogLength + 1, 0);
glGetShaderInfoLog(shader, infoLogLength, nullptr, infoLog.data());
E("Failed to compile shader. Info log: [%s]", infoLog.data());
}
return shader;
}
GLint compileAndLinkShaderProgram(const char* vshaderSrc,
const char* fshaderSrc) {
GLuint vshader = compileShader(GL_VERTEX_SHADER, vshaderSrc);
GLuint fshader = compileShader(GL_FRAGMENT_SHADER, fshaderSrc);
GLuint program = glCreateProgram();
glAttachShader(program, vshader);
glAttachShader(program, fshader);
glLinkProgram(program);
glDeleteShader(vshader);
glDeleteShader(fshader);
GLint linkStatus;
glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
glClearColor(0.0f, 0.0f, 1.0f, 0.0f);
if (linkStatus != GL_TRUE) {
GLsizei infoLogLength = 0;
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infoLogLength);
std::vector<char> infoLog(infoLogLength + 1, 0);
glGetProgramInfoLog(program, infoLogLength, nullptr,
infoLog.data());
E("Failed to link program. Info log: [%s]\n", infoLog.data());
}
return program;
}
// EGL/GLES related state
bool mInitialized = false;
EGLDisplay mDisplay;
EGLContext mContext;
EGLSurface mSurface;
GLuint mPosLoc;
GLuint mTexCoordLoc;
GLuint mProgram;
GLuint mTexture;
GLuint mVbo;
std::unordered_map<ANativeWindowBuffer*, EGLImageKHR> mEGLImages;
WorkerThread<Command> mWorkerThread;
};
ClientComposer::ClientComposer(AndroidWindow* composeWindow,
AndroidBufferQueue* fromApp,
AndroidBufferQueue* toApp)
: mImpl(new ClientComposer::Impl(composeWindow, fromApp, toApp)) {}
ClientComposer::~ClientComposer() = default;
void ClientComposer::join() {
mImpl->join();
}
void ClientComposer::advanceFrame() {
mImpl->advanceFrame();
}
} // namespace aemu