You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
395 lines
13 KiB
395 lines
13 KiB
/*
|
|
* 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.
|
|
*/
|
|
|
|
// TODO(b/129481165): remove the #pragma below and fix conversion issues
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wconversion"
|
|
|
|
#include <gui/BufferItemConsumer.h>
|
|
#include <gui/Surface.h>
|
|
|
|
#include <GLES3/gl3.h>
|
|
#include <math/vec2.h>
|
|
#include <math/vec3.h>
|
|
#include <math/vec4.h>
|
|
|
|
#include "BufferGenerator.h"
|
|
#include "BufferGeneratorShader.h"
|
|
|
|
namespace android {
|
|
|
|
/* Used to receive the surfaces and fences from egl. The egl buffers are thrown
|
|
* away. The fences are sent to the requester via a callback */
|
|
class SurfaceManager {
|
|
public:
|
|
/* Returns a fence from egl */
|
|
using BufferCallback = std::function<void(const sp<GraphicBuffer>& buffer, int32_t fence)>;
|
|
|
|
/* Listens for a new frame, detaches the buffer and returns the fence
|
|
* through saved callback. */
|
|
class BufferListener : public ConsumerBase::FrameAvailableListener {
|
|
public:
|
|
BufferListener(sp<IGraphicBufferConsumer> consumer, BufferCallback callback)
|
|
: mConsumer(consumer), mCallback(callback) {}
|
|
|
|
void onFrameAvailable(const BufferItem& /*item*/) {
|
|
BufferItem item;
|
|
|
|
if (mConsumer->acquireBuffer(&item, 0)) return;
|
|
if (mConsumer->detachBuffer(item.mSlot)) return;
|
|
|
|
mCallback(item.mGraphicBuffer, item.mFence->dup());
|
|
}
|
|
|
|
private:
|
|
sp<IGraphicBufferConsumer> mConsumer;
|
|
BufferCallback mCallback;
|
|
};
|
|
|
|
/* Creates a buffer listener that waits on a new frame from the buffer
|
|
* queue. */
|
|
void initialize(uint32_t width, uint32_t height, android_pixel_format_t format,
|
|
BufferCallback callback) {
|
|
sp<IGraphicBufferProducer> producer;
|
|
sp<IGraphicBufferConsumer> consumer;
|
|
BufferQueue::createBufferQueue(&producer, &consumer);
|
|
|
|
consumer->setDefaultBufferSize(width, height);
|
|
consumer->setDefaultBufferFormat(format);
|
|
|
|
mBufferItemConsumer = new BufferItemConsumer(consumer, GraphicBuffer::USAGE_HW_TEXTURE);
|
|
|
|
mListener = new BufferListener(consumer, callback);
|
|
mBufferItemConsumer->setFrameAvailableListener(mListener);
|
|
|
|
mSurface = new Surface(producer, true);
|
|
}
|
|
|
|
/* Used by Egl manager. The surface is never displayed. */
|
|
sp<Surface> getSurface() const { return mSurface; }
|
|
|
|
private:
|
|
sp<BufferItemConsumer> mBufferItemConsumer;
|
|
sp<BufferListener> mListener;
|
|
/* Used by Egl manager. The surface is never displayed */
|
|
sp<Surface> mSurface;
|
|
};
|
|
|
|
/* Used to generate valid fences. It is not possible to create a placeholder sync
|
|
* fence for testing. Egl can generate buffers along with a valid fence.
|
|
* The buffer cannot be guaranteed to be the same format across all devices so
|
|
* a CPU filled buffer is used instead. The Egl fence is used along with the
|
|
* CPU filled buffer. */
|
|
class EglManager {
|
|
public:
|
|
EglManager()
|
|
: mEglDisplay(EGL_NO_DISPLAY), mEglSurface(EGL_NO_SURFACE), mEglContext(EGL_NO_CONTEXT) {}
|
|
|
|
~EglManager() { cleanup(); }
|
|
|
|
int initialize(sp<Surface> surface) {
|
|
mSurface = surface;
|
|
|
|
mEglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
|
|
if (mEglDisplay == EGL_NO_DISPLAY) return false;
|
|
|
|
EGLint major;
|
|
EGLint minor;
|
|
if (!eglInitialize(mEglDisplay, &major, &minor)) {
|
|
ALOGW("Could not initialize EGL");
|
|
return false;
|
|
}
|
|
|
|
/* We're going to use a 1x1 pbuffer surface later on
|
|
* The configuration distance doesn't really matter for what we're
|
|
* trying to do */
|
|
EGLint configAttrs[] = {EGL_RENDERABLE_TYPE,
|
|
EGL_OPENGL_ES2_BIT,
|
|
EGL_RED_SIZE,
|
|
8,
|
|
EGL_GREEN_SIZE,
|
|
8,
|
|
EGL_BLUE_SIZE,
|
|
8,
|
|
EGL_ALPHA_SIZE,
|
|
0,
|
|
EGL_DEPTH_SIZE,
|
|
24,
|
|
EGL_STENCIL_SIZE,
|
|
0,
|
|
EGL_NONE};
|
|
|
|
EGLConfig configs[1];
|
|
EGLint configCnt;
|
|
if (!eglChooseConfig(mEglDisplay, configAttrs, configs, 1, &configCnt)) {
|
|
ALOGW("Could not select EGL configuration");
|
|
eglReleaseThread();
|
|
eglTerminate(mEglDisplay);
|
|
return false;
|
|
}
|
|
|
|
if (configCnt <= 0) {
|
|
ALOGW("Could not find EGL configuration");
|
|
eglReleaseThread();
|
|
eglTerminate(mEglDisplay);
|
|
return false;
|
|
}
|
|
|
|
/* These objects are initialized below but the default "null" values are
|
|
* used to cleanup properly at any point in the initialization sequence */
|
|
EGLint attrs[] = {EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE};
|
|
mEglContext = eglCreateContext(mEglDisplay, configs[0], EGL_NO_CONTEXT, attrs);
|
|
if (mEglContext == EGL_NO_CONTEXT) {
|
|
ALOGW("Could not create EGL context");
|
|
cleanup();
|
|
return false;
|
|
}
|
|
|
|
EGLint majorVersion;
|
|
if (!eglQueryContext(mEglDisplay, mEglContext, EGL_CONTEXT_CLIENT_VERSION, &majorVersion)) {
|
|
ALOGW("Could not query EGL version");
|
|
cleanup();
|
|
return false;
|
|
}
|
|
|
|
if (majorVersion != 3) {
|
|
ALOGW("Unsupported EGL version");
|
|
cleanup();
|
|
return false;
|
|
}
|
|
|
|
EGLint surfaceAttrs[] = {EGL_NONE};
|
|
mEglSurface = eglCreateWindowSurface(mEglDisplay, configs[0], mSurface.get(), surfaceAttrs);
|
|
if (mEglSurface == EGL_NO_SURFACE) {
|
|
ALOGW("Could not create EGL surface");
|
|
cleanup();
|
|
return false;
|
|
}
|
|
|
|
if (!eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
|
|
ALOGW("Could not change current EGL context");
|
|
cleanup();
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void makeCurrent() const { eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext); }
|
|
|
|
void present() const { eglSwapBuffers(mEglDisplay, mEglSurface); }
|
|
|
|
private:
|
|
void cleanup() {
|
|
if (mEglDisplay == EGL_NO_DISPLAY) return;
|
|
if (mEglSurface != EGL_NO_SURFACE) eglDestroySurface(mEglDisplay, mEglSurface);
|
|
if (mEglContext != EGL_NO_CONTEXT) eglDestroyContext(mEglDisplay, mEglContext);
|
|
|
|
eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
|
|
eglReleaseThread();
|
|
eglTerminate(mEglDisplay);
|
|
}
|
|
|
|
sp<Surface> mSurface;
|
|
EGLDisplay mEglDisplay;
|
|
EGLSurface mEglSurface;
|
|
EGLContext mEglContext;
|
|
};
|
|
|
|
class Program {
|
|
public:
|
|
~Program() {
|
|
if (mInitialized) {
|
|
glDetachShader(mProgram, mVertexShader);
|
|
glDetachShader(mProgram, mFragmentShader);
|
|
|
|
glDeleteShader(mVertexShader);
|
|
glDeleteShader(mFragmentShader);
|
|
|
|
glDeleteProgram(mProgram);
|
|
}
|
|
}
|
|
|
|
bool initialize(const char* vertex, const char* fragment) {
|
|
mVertexShader = buildShader(vertex, GL_VERTEX_SHADER);
|
|
if (!mVertexShader) {
|
|
return false;
|
|
}
|
|
|
|
mFragmentShader = buildShader(fragment, GL_FRAGMENT_SHADER);
|
|
if (!mFragmentShader) {
|
|
return false;
|
|
}
|
|
|
|
mProgram = glCreateProgram();
|
|
glAttachShader(mProgram, mVertexShader);
|
|
glAttachShader(mProgram, mFragmentShader);
|
|
|
|
glLinkProgram(mProgram);
|
|
|
|
GLint status;
|
|
glGetProgramiv(mProgram, GL_LINK_STATUS, &status);
|
|
if (status != GL_TRUE) {
|
|
GLint length = 0;
|
|
glGetProgramiv(mProgram, GL_INFO_LOG_LENGTH, &length);
|
|
if (length > 1) {
|
|
GLchar log[length];
|
|
glGetProgramInfoLog(mProgram, length, nullptr, &log[0]);
|
|
ALOGE("%s", log);
|
|
}
|
|
ALOGE("Error while linking shaders");
|
|
return false;
|
|
}
|
|
mInitialized = true;
|
|
return true;
|
|
}
|
|
|
|
void use() const { glUseProgram(mProgram); }
|
|
|
|
void bindVec4(GLint location, vec4 v) const { glUniform4f(location, v.x, v.y, v.z, v.w); }
|
|
|
|
void bindVec3(GLint location, const vec3* v, uint32_t count) const {
|
|
glUniform3fv(location, count, &(v->x));
|
|
}
|
|
|
|
void bindFloat(GLint location, float v) { glUniform1f(location, v); }
|
|
|
|
private:
|
|
GLuint buildShader(const char* source, GLenum type) const {
|
|
GLuint shader = glCreateShader(type);
|
|
glShaderSource(shader, 1, &source, nullptr);
|
|
glCompileShader(shader);
|
|
|
|
GLint status;
|
|
glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
|
|
if (status != GL_TRUE) {
|
|
ALOGE("Error while compiling shader of type 0x%x:\n===\n%s\n===", type, source);
|
|
// Some drivers return wrong values for GL_INFO_LOG_LENGTH
|
|
// use a fixed size instead
|
|
GLchar log[512];
|
|
glGetShaderInfoLog(shader, sizeof(log), nullptr, &log[0]);
|
|
ALOGE("Shader info log: %s", log);
|
|
return 0;
|
|
}
|
|
|
|
return shader;
|
|
}
|
|
|
|
GLuint mProgram = 0;
|
|
GLuint mVertexShader = 0;
|
|
GLuint mFragmentShader = 0;
|
|
bool mInitialized = false;
|
|
};
|
|
|
|
BufferGenerator::BufferGenerator()
|
|
: mSurfaceManager(new SurfaceManager), mEglManager(new EglManager), mProgram(new Program) {
|
|
mBufferSize.set(1000.0, 1000.0);
|
|
|
|
auto setBufferWithContext =
|
|
std::bind(setBuffer, std::placeholders::_1, std::placeholders::_2, this);
|
|
mSurfaceManager->initialize(mBufferSize.width, mBufferSize.height, HAL_PIXEL_FORMAT_RGBA_8888,
|
|
setBufferWithContext);
|
|
|
|
if (!mEglManager->initialize(mSurfaceManager->getSurface())) return;
|
|
|
|
mEglManager->makeCurrent();
|
|
|
|
if (!mProgram->initialize(VERTEX_SHADER, FRAGMENT_SHADER)) return;
|
|
mProgram->use();
|
|
mProgram->bindVec4(0,
|
|
vec4{mBufferSize.width, mBufferSize.height, 1.0f / mBufferSize.width,
|
|
1.0f / mBufferSize.height});
|
|
mProgram->bindVec3(2, &SPHERICAL_HARMONICS[0], 4);
|
|
|
|
glEnableVertexAttribArray(0);
|
|
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, &TRIANGLE[0]);
|
|
|
|
mInitialized = true;
|
|
}
|
|
|
|
BufferGenerator::~BufferGenerator() {
|
|
mEglManager->makeCurrent();
|
|
}
|
|
|
|
status_t BufferGenerator::get(sp<GraphicBuffer>* outBuffer, sp<Fence>* outFence) {
|
|
// mMutex is used to protect get() from getting called by multiple threads at the same time
|
|
static std::mutex mMutex;
|
|
std::lock_guard lock(mMutex);
|
|
|
|
if (!mInitialized) {
|
|
if (outBuffer) {
|
|
*outBuffer = nullptr;
|
|
}
|
|
if (*outFence) {
|
|
*outFence = nullptr;
|
|
}
|
|
return -EINVAL;
|
|
}
|
|
|
|
// Generate a buffer and fence. They will be returned through the setBuffer callback
|
|
mEglManager->makeCurrent();
|
|
|
|
glClear(GL_COLOR_BUFFER_BIT);
|
|
|
|
const std::chrono::duration<float> time = std::chrono::steady_clock::now() - mEpoch;
|
|
mProgram->bindFloat(1, time.count());
|
|
|
|
glDrawArrays(GL_TRIANGLES, 0, 3);
|
|
|
|
mPending = true;
|
|
mEglManager->present();
|
|
|
|
// Wait for the setBuffer callback
|
|
if (!mConditionVariable.wait_for(mMutex, std::chrono::seconds(2),
|
|
[this] { return !mPending; })) {
|
|
ALOGE("failed to set buffer and fence");
|
|
return -ETIME;
|
|
}
|
|
|
|
// Return buffer and fence
|
|
if (outBuffer) {
|
|
*outBuffer = mGraphicBuffer;
|
|
}
|
|
if (outFence) {
|
|
*outFence = new Fence(mFence);
|
|
} else {
|
|
close(mFence);
|
|
}
|
|
mGraphicBuffer = nullptr;
|
|
mFence = -1;
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
ui::Size BufferGenerator::getSize() {
|
|
return mBufferSize;
|
|
}
|
|
|
|
// static
|
|
void BufferGenerator::setBuffer(const sp<GraphicBuffer>& buffer, int32_t fence,
|
|
void* bufferGenerator) {
|
|
BufferGenerator* generator = static_cast<BufferGenerator*>(bufferGenerator);
|
|
generator->mGraphicBuffer = buffer;
|
|
generator->mFence = fence;
|
|
generator->mPending = false;
|
|
generator->mConditionVariable.notify_all();
|
|
}
|
|
|
|
} // namespace android
|
|
|
|
// TODO(b/129481165): remove the #pragma below and fix conversion issues
|
|
#pragma clang diagnostic pop // ignored "-Wconversion"
|