blob: ddf8dac7d4ff8b75cdebe24013b52140edb17b35 [file] [log] [blame]
/*
* Copyright 2019 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 "Renderer.h"
#define LOG_TAG "TestAppRender"
#include <vector>
#include <condition_variable>
#include <mutex>
#include <queue>
#include <thread>
#include <GLES2/gl2.h>
#include <android/native_window.h>
#include "Log.h"
#include "swappy/swappy.h"
#include "swappy/swappy_extra.h"
#include "tuningfork/tuningfork.h"
#include "Scene.h"
extern bool swappy_enabled;
using namespace std::chrono_literals;
namespace samples {
Renderer *Renderer::getInstance() {
static std::unique_ptr<Renderer> sRenderer = std::make_unique<Renderer>(ConstructorTag{});
return sRenderer.get();
}
void Renderer::setWindow(ANativeWindow *window, int32_t width, int32_t height) {
enqueue([=](State* state) {
state->clearSurface();
ALOGI("Creating window surface %dx%d", width, height);
if (!window) return;
state->surface =
eglCreateWindowSurface(state->display, state->config, window, NULL);
ANativeWindow_release(window);
if (!state->makeCurrent(state->surface)) {
ALOGE("Unable to eglMakeCurrent");
state->surface = EGL_NO_SURFACE;
return;
}
state->width = width;
state->height = height;
});
}
void Renderer::start() {
enqueue([this](State* state) {
state->isStarted = true;
requestDraw();
});
}
void Renderer::stop() {
enqueue([=](State *state) { state->isStarted = false; });
}
float Renderer::getAverageFps() {
return averageFps;
}
void Renderer::requestDraw() {
enqueue([this](State* state) { if (state->isStarted) draw(state);});
}
void Renderer::enqueue(Task task) {
std::lock_guard<std::mutex> lock(mQueueMutex);
mQueue.emplace(std::move(task));
mCondVar.notify_all();
}
void Renderer::launchThread() {
std::lock_guard<std::mutex> threadLock(mThreadMutex);
if (mThread.joinable()) {
terminateThread();
}
mThread = std::thread([this]() { threadMain(); });
}
void Renderer::terminateThread() REQUIRES(mThreadMutex) {
{
std::lock_guard<std::mutex> lock(mQueueMutex);
mIsActive = false;
mCondVar.notify_all();
}
mThread.join();
}
void Renderer::threadMain() {
pthread_setname_np(pthread_self(), "Renderer");
State state(MAX_N_SPHERES);
std::lock_guard<std::mutex> lock(mQueueMutex);
while (mIsActive) {
mCondVar.wait(mQueueMutex,
[this]() REQUIRES(mQueueMutex) {
return !mQueue.empty() || !mIsActive;
});
if (!mQueue.empty()) {
auto head = mQueue.front();
mQueue.pop();
// Drop the mutex while we execute
mQueueMutex.unlock();
head(&state);
mQueueMutex.lock();
}
}
}
Renderer::State::State(int nSpheres) : scene(nSpheres) {
display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
eglInitialize(display, 0, 0);
const EGLint configAttributes[] = {
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
EGL_BLUE_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_RED_SIZE, 8,
EGL_DEPTH_SIZE, 24,
EGL_NONE
};
EGLint numConfigs = 0;
eglChooseConfig(display, configAttributes, nullptr, 0, &numConfigs);
std::vector<EGLConfig> supportedConfigs(static_cast<size_t>(numConfigs));
eglChooseConfig(display, configAttributes, supportedConfigs.data(), numConfigs, &numConfigs);
// Choose a config, either a match if possible or the first config otherwise
const auto configMatches = [&](EGLConfig config) {
if (!configHasAttribute(EGL_RED_SIZE, 8)) return false;
if (!configHasAttribute(EGL_GREEN_SIZE, 8)) return false;
if (!configHasAttribute(EGL_BLUE_SIZE, 8)) return false;
return configHasAttribute(EGL_DEPTH_SIZE, 24);
};
const auto configIter = std::find_if(supportedConfigs.cbegin(), supportedConfigs.cend(),
configMatches);
config = (configIter != supportedConfigs.cend()) ? *configIter : supportedConfigs[0];
const EGLint contextAttributes[] = {
EGL_CONTEXT_CLIENT_VERSION, 2,
EGL_NONE
};
context = eglCreateContext(display, config, nullptr, contextAttributes);
}
Renderer::State::~State() {
clearSurface();
if (context != EGL_NO_CONTEXT) eglDestroyContext(display, context);
if (display != EGL_NO_DISPLAY) eglTerminate(display);
}
void Renderer::State::clearSurface() {
if (surface == EGL_NO_SURFACE) return;
makeCurrent(EGL_NO_SURFACE);
eglDestroySurface(display, surface);
surface = EGL_NO_SURFACE;
}
bool Renderer::State::configHasAttribute(EGLint attribute, EGLint value) {
EGLint outValue = 0;
EGLBoolean result = eglGetConfigAttrib(display, config, attribute, &outValue);
return result && (outValue == value);
}
EGLBoolean Renderer::State::makeCurrent(EGLSurface surface) {
return eglMakeCurrent(display, surface, surface, context);
}
// should be called once per draw as this function maintains the time delta between calls
void Renderer::calculateFps() {
static constexpr int FPS_SAMPLES = 10;
static std::chrono::steady_clock::time_point prev = std::chrono::steady_clock::now();
static float fpsSum = 0;
static int fpsCount = 0;
std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now();
fpsSum += 1.0f / ((now - prev).count() / 1e9f);
fpsCount++;
if (fpsCount == FPS_SAMPLES) {
averageFps = fpsSum / fpsCount;
fpsSum = 0;
fpsCount = 0;
}
prev = now;
}
void Renderer::setQuality(int num_spheres, int tesselation_percent) {
enqueue([=](State* state) {
state->scene.nSpheres = std::max(1,std::min(MAX_N_SPHERES, num_spheres));
mTesselation = std::max(0,std::min(100,tesselation_percent));
});
}
void Renderer::draw(State *state) {
// Don't render if we have no surface
if (state->surface == EGL_NO_SURFACE) {
// Sleep a bit so we don't churn too fast
std::this_thread::sleep_for(50ms);
requestDraw();
return;
}
if (swappy_enabled)
Swappy_recordFrameStart(state->display, state->surface);
calculateFps();
float deltaSeconds = state->swapIntervalNS / 1e9f;
auto dt = std::chrono::steady_clock::now() - state->lastUpdate;
if ( dt <= 100ms ) {
deltaSeconds = std::chrono::duration_cast<std::chrono::nanoseconds>(dt).count() / 1e9f;
}
state->lastUpdate += dt;
state->scene.tick(deltaSeconds);
// Just fill the screen with a color.
glClearColor(0.3f, 0.3f, 0.3f, 1.0f);
glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
glEnable(GL_CULL_FACE);
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL);
const float aspectRatio = static_cast<float>(state->width) / state->height;
state->scene.draw(aspectRatio, mTesselation);
if (swappy_enabled)
Swappy_swap(state->display, state->surface);
else {
TuningFork_frameTick(TFTICK_SYSCPU);
eglSwapBuffers(state->display, state->surface);
}
// If we're still started, request another frame
requestDraw();
}
} // namespace samples