ES-DE/es-core/src/renderers/Renderer_GL21.cpp
Leon Styhre a7db474a64 Unified the OpenGL and OpenGL ES renderers and removed the fixed function pipeline.
Also made some other rendering optimizations and cleaned up some code.
2022-03-13 23:52:32 +01:00

503 lines
22 KiB
C++

// SPDX-License-Identifier: MIT
//
// EmulationStation Desktop Edition
// Renderer_GL21.cpp
//
// OpenGL / OpenGL ES renderer.
//
#include "Settings.h"
#include "Shader_GL21.h"
#include "renderers/Renderer.h"
#if defined(__APPLE__)
#include <chrono>
#endif
namespace Renderer
{
static SDL_GLContext sdlContext = nullptr;
static GLuint whiteTexture {0};
static GLuint postProcTexture1 {0};
static GLuint postProcTexture2 {0};
inline GLenum convertBlendFactor(const Blend::Factor blendFactor)
{
// clang-format off
switch (blendFactor) {
case Blend::ZERO: { return GL_ZERO; } break;
case Blend::ONE: { return GL_ONE; } break;
case Blend::SRC_COLOR: { return GL_SRC_COLOR; } break;
case Blend::ONE_MINUS_SRC_COLOR: { return GL_ONE_MINUS_SRC_COLOR; } break;
case Blend::SRC_ALPHA: { return GL_SRC_ALPHA; } break;
case Blend::ONE_MINUS_SRC_ALPHA: { return GL_ONE_MINUS_SRC_ALPHA; } break;
case Blend::DST_COLOR: { return GL_DST_COLOR; } break;
case Blend::ONE_MINUS_DST_COLOR: { return GL_ONE_MINUS_DST_COLOR; } break;
case Blend::DST_ALPHA: { return GL_DST_ALPHA; } break;
case Blend::ONE_MINUS_DST_ALPHA: { return GL_ONE_MINUS_DST_ALPHA; } break;
default: { return GL_ZERO; }
}
// clang-format on
}
inline GLenum convertTextureType(const Texture::Type type)
{
// clang-format off
switch (type) {
case Texture::RGBA: { return GL_RGBA; } break;
#if defined(USE_OPENGLES)
case Texture::BGRA: { return GL_BGRA_EXT; } break;
#else
case Texture::BGRA: { return GL_BGRA; } break;
#endif
case Texture::RED: { return GL_RED; } break;
default: { return GL_ZERO; }
}
// clang-format on
}
void setupWindow()
{
#if defined(USE_OPENGLES)
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
#else
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
#endif
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
}
bool createContext()
{
sdlContext = SDL_GL_CreateContext(getSDLWindow());
if (!sdlContext) {
LOG(LogError) << "Error creating OpenGL context. " << SDL_GetError();
return false;
}
#if defined(_WIN64)
glewInit();
#endif
SDL_GL_MakeCurrent(getSDLWindow(), sdlContext);
std::string vendor =
glGetString(GL_VENDOR) ? reinterpret_cast<const char*>(glGetString(GL_VENDOR)) : "";
std::string renderer =
glGetString(GL_RENDERER) ? reinterpret_cast<const char*>(glGetString(GL_RENDERER)) : "";
std::string version =
glGetString(GL_VERSION) ? reinterpret_cast<const char*>(glGetString(GL_VERSION)) : "";
std::string extensions = glGetString(GL_EXTENSIONS) ?
reinterpret_cast<const char*>(glGetString(GL_EXTENSIONS)) :
"";
LOG(LogInfo) << "GL vendor: " << vendor;
LOG(LogInfo) << "GL renderer: " << renderer;
LOG(LogInfo) << "GL version: " << version;
#if defined(_WIN64)
LOG(LogInfo) << "EmulationStation renderer: OpenGL 3.3 with GLEW";
#else
#if defined(USE_OPENGLES)
LOG(LogInfo) << "EmulationStation renderer: OpenGL ES 3.0";
#else
LOG(LogInfo) << "EmulationStation renderer: OpenGL 3.3";
#endif
#endif
#if !defined(USE_OPENGLES)
// TODO: Fix the issue that causes the first glClearColor function call to fail.
GL_CHECK_ERROR(glClearColor(0.0f, 0.0f, 0.0f, 1.0f));
#endif
GL_CHECK_ERROR(glClearColor(0.0f, 0.0f, 0.0f, 1.0f));
GL_CHECK_ERROR(glActiveTexture(GL_TEXTURE0));
GL_CHECK_ERROR(glEnable(GL_BLEND));
GL_CHECK_ERROR(glPixelStorei(GL_PACK_ALIGNMENT, 1));
GL_CHECK_ERROR(glPixelStorei(GL_UNPACK_ALIGNMENT, 1));
// These are used for the shader post processing.
GL_CHECK_ERROR(glGenFramebuffers(1, &shaderFBO1));
GL_CHECK_ERROR(glGenFramebuffers(1, &shaderFBO2));
GL_CHECK_ERROR(glGenBuffers(1, &vertexBuffer1));
GL_CHECK_ERROR(glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer1));
GL_CHECK_ERROR(glGenVertexArrays(1, &vertexBuffer2));
GL_CHECK_ERROR(glBindVertexArray(vertexBuffer2));
uint8_t data[4] = {255, 255, 255, 255};
whiteTexture = createTexture(Texture::RGBA, false, false, true, 1, 1, data);
postProcTexture1 = createTexture(Texture::RGBA, false, false, false,
static_cast<unsigned int>(getScreenWidth()),
static_cast<unsigned int>(getScreenHeight()), nullptr);
postProcTexture2 = createTexture(Texture::RGBA, false, false, false,
static_cast<unsigned int>(getScreenWidth()),
static_cast<unsigned int>(getScreenHeight()), nullptr);
// Attach textures to the shader framebuffers.
GL_CHECK_ERROR(glBindFramebuffer(GL_DRAW_FRAMEBUFFER, shaderFBO1));
GL_CHECK_ERROR(glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
postProcTexture1, 0));
GL_CHECK_ERROR(glBindFramebuffer(GL_DRAW_FRAMEBUFFER, shaderFBO2));
GL_CHECK_ERROR(glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
postProcTexture2, 0));
GL_CHECK_ERROR(glBindFramebuffer(GL_FRAMEBUFFER, 0));
return true;
}
void destroyContext()
{
GL_CHECK_ERROR(glDeleteFramebuffers(1, &shaderFBO1));
GL_CHECK_ERROR(glDeleteFramebuffers(1, &shaderFBO2));
destroyTexture(postProcTexture1);
destroyTexture(postProcTexture2);
destroyTexture(whiteTexture);
SDL_GL_DeleteContext(sdlContext);
sdlContext = nullptr;
}
unsigned int createTexture(const Texture::Type type,
const bool linearMinify,
const bool linearMagnify,
const bool repeat,
const unsigned int width,
const unsigned int height,
void* data)
{
const GLenum textureType = convertTextureType(type);
unsigned int texture;
GL_CHECK_ERROR(glGenTextures(1, &texture));
GL_CHECK_ERROR(glBindTexture(GL_TEXTURE_2D, texture));
GL_CHECK_ERROR(glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,
repeat ? static_cast<GLfloat>(GL_REPEAT) :
static_cast<GLfloat>(GL_CLAMP_TO_EDGE)));
GL_CHECK_ERROR(glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,
repeat ? static_cast<GLfloat>(GL_REPEAT) :
static_cast<GLfloat>(GL_CLAMP_TO_EDGE)));
GL_CHECK_ERROR(glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
linearMinify ? static_cast<GLfloat>(GL_LINEAR) :
static_cast<GLfloat>(GL_NEAREST)));
GL_CHECK_ERROR(glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
linearMagnify ? static_cast<GLfloat>(GL_LINEAR) :
static_cast<GLfloat>(GL_NEAREST)));
GL_CHECK_ERROR(glTexImage2D(GL_TEXTURE_2D, 0, textureType, width, height, 0, textureType,
GL_UNSIGNED_BYTE, data));
return texture;
}
void destroyTexture(const unsigned int texture)
{
GL_CHECK_ERROR(glDeleteTextures(1, &texture));
}
void updateTexture(const unsigned int texture,
const Texture::Type type,
const unsigned int x,
const unsigned int y,
const unsigned int width,
const unsigned int height,
void* data)
{
const GLenum textureType = convertTextureType(type);
GL_CHECK_ERROR(glBindTexture(GL_TEXTURE_2D, texture));
GL_CHECK_ERROR(glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, width, height, textureType,
GL_UNSIGNED_BYTE, data));
GL_CHECK_ERROR(glBindTexture(GL_TEXTURE_2D, whiteTexture));
}
void bindTexture(const unsigned int texture)
{
if (texture == 0)
GL_CHECK_ERROR(glBindTexture(GL_TEXTURE_2D, whiteTexture));
else
GL_CHECK_ERROR(glBindTexture(GL_TEXTURE_2D, texture));
}
void drawTriangleStrips(const Vertex* vertices,
const unsigned int numVertices,
const Blend::Factor srcBlendFactor,
const Blend::Factor dstBlendFactor)
{
const float width {vertices[3].position[0]};
const float height {vertices[3].position[1]};
GL_CHECK_ERROR(
glBlendFunc(convertBlendFactor(srcBlendFactor), convertBlendFactor(dstBlendFactor)));
if (vertices->shaders == 0 || vertices->shaders & SHADER_CORE) {
Shader* shader {getShaderProgram(SHADER_CORE)};
if (shader) {
shader->activateShaders();
shader->setModelViewProjectionMatrix(mTrans);
shader->setAttribPointers();
GL_CHECK_ERROR(glBufferData(GL_ARRAY_BUFFER, sizeof(Vertex) * numVertices, vertices,
GL_DYNAMIC_DRAW));
shader->setOpacity(vertices->opacity);
shader->setSaturation(vertices->saturation);
shader->setDimming(vertices->dimming);
shader->setBGRAToRGBA(vertices->convertBGRAToRGBA);
shader->setFont(vertices->font);
shader->setPostProcessing(vertices->postProcessing);
GL_CHECK_ERROR(glDrawArrays(GL_TRIANGLE_STRIP, 0, numVertices));
shader->deactivateShaders();
}
}
else if (vertices->shaders & SHADER_BLUR_HORIZONTAL) {
Shader* shader {getShaderProgram(SHADER_BLUR_HORIZONTAL)};
if (shader) {
shader->activateShaders();
shader->setModelViewProjectionMatrix(mTrans);
shader->setAttribPointers();
GL_CHECK_ERROR(glBufferData(GL_ARRAY_BUFFER, sizeof(Vertex) * numVertices, vertices,
GL_DYNAMIC_DRAW));
shader->setTextureSize({width, height});
GL_CHECK_ERROR(glDrawArrays(GL_TRIANGLE_STRIP, 0, numVertices));
shader->deactivateShaders();
}
return;
}
else if (vertices->shaders & SHADER_BLUR_VERTICAL) {
Shader* shader {getShaderProgram(SHADER_BLUR_VERTICAL)};
if (shader) {
shader->activateShaders();
shader->setModelViewProjectionMatrix(mTrans);
shader->setAttribPointers();
GL_CHECK_ERROR(glBufferData(GL_ARRAY_BUFFER, sizeof(Vertex) * numVertices, vertices,
GL_DYNAMIC_DRAW));
shader->setTextureSize({width, height});
GL_CHECK_ERROR(glDrawArrays(GL_TRIANGLE_STRIP, 0, numVertices));
shader->deactivateShaders();
}
return;
}
else if (vertices->shaders & SHADER_SCANLINES) {
Shader* shader {getShaderProgram(SHADER_SCANLINES)};
float shaderWidth {width * 1.2f};
// Scale the scanlines relative to screen resolution.
float screenHeightModifier {getScreenHeightModifier()};
float relativeHeight {height / getScreenHeight()};
float shaderHeight {0.0f};
if (relativeHeight == 1.0f) {
// Full screen.
float modifier {1.30f - (0.1f * screenHeightModifier)};
shaderHeight = height * modifier;
}
else {
// Portion of screen, e.g. gamelist view.
// Average the relative width and height to avoid applying exaggerated
// scanlines to videos with non-standard aspect ratios.
float relativeWidth {width / getScreenWidth()};
float relativeAdjustment {(relativeWidth + relativeHeight) / 2.0f};
float modifier {1.41f + relativeAdjustment / 7.0f - (0.14f * screenHeightModifier)};
shaderHeight = height * modifier;
}
if (shader) {
shader->activateShaders();
shader->setModelViewProjectionMatrix(mTrans);
shader->setAttribPointers();
GL_CHECK_ERROR(glBufferData(GL_ARRAY_BUFFER, sizeof(Vertex) * numVertices, vertices,
GL_DYNAMIC_DRAW));
shader->setOpacity(vertices->opacity);
shader->setTextureSize({shaderWidth, shaderHeight});
GL_CHECK_ERROR(glDrawArrays(GL_TRIANGLE_STRIP, 0, numVertices));
shader->deactivateShaders();
}
}
}
void setMatrix(const glm::mat4& matrix)
{
mTrans = matrix;
mTrans[3] = glm::round(mTrans[3]);
mTrans = getProjectionMatrix() * mTrans;
}
void setScissor(const Rect& scissor)
{
if ((scissor.x == 0) && (scissor.y == 0) && (scissor.w == 0) && (scissor.h == 0)) {
GL_CHECK_ERROR(glDisable(GL_SCISSOR_TEST));
}
else {
// glScissor starts at the bottom left of the window.
GL_CHECK_ERROR(glScissor(scissor.x,
static_cast<GLint>(getWindowHeight()) - scissor.y - scissor.h,
scissor.w, scissor.h));
GL_CHECK_ERROR(glEnable(GL_SCISSOR_TEST));
}
}
void setSwapInterval()
{
if (Settings::getInstance()->getBool("VSync")) {
// Adaptive VSync seems to be nonfunctional or having issues on some hardware
// and drivers, so only attempt to apply regular VSync.
if (SDL_GL_SetSwapInterval(1) == 0) {
LOG(LogInfo) << "Enabling VSync...";
}
else {
LOG(LogWarning) << "Could not enable VSync: " << SDL_GetError();
}
}
else {
SDL_GL_SetSwapInterval(0);
LOG(LogInfo) << "Disabling VSync...";
}
}
void swapBuffers()
{
#if defined(__APPLE__)
// On macOS when running in the background, the OpenGL driver apparently does not swap
// the frames which leads to a very fast swap time. This makes ES-DE use a lot of CPU
// resources which slows down the games significantly on slower machines. By introducing
// a delay if the swap time is very low we reduce CPU usage while still keeping the
// application functioning normally.
const auto beforeSwap = std::chrono::system_clock::now();
SDL_GL_SwapWindow(getSDLWindow());
if (std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() -
beforeSwap)
.count() < 3.0)
SDL_Delay(10);
#else
SDL_GL_SwapWindow(getSDLWindow());
#endif
GL_CHECK_ERROR(glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT));
}
void shaderPostprocessing(unsigned int shaders,
const Renderer::postProcessingParams& parameters,
unsigned char* textureRGBA)
{
Vertex vertices[4];
std::vector<unsigned int> shaderList;
float widthf {getScreenWidth()};
float heightf {getScreenHeight()};
GLuint width {static_cast<GLuint>(widthf)};
GLuint height {static_cast<GLuint>(heightf)};
// Set vertex positions and texture coordinates to full screen as all
// post-processing is applied to the complete screen area.
// clang-format off
vertices[0] = {{0.0f, 0.0f }, {0.0f, 1.0f}, 0xFFFFFFFF};
vertices[1] = {{0.0f, heightf}, {0.0f, 0.0f}, 0xFFFFFFFF};
vertices[2] = {{widthf, 0.0f }, {1.0f, 1.0f}, 0xFFFFFFFF};
vertices[3] = {{widthf, heightf}, {1.0f, 0.0f}, 0xFFFFFFFF};
// clang-format on
vertices->opacity = parameters.opacity;
vertices->saturation = parameters.saturation;
vertices->dimming = parameters.dimming;
vertices->postProcessing = true;
if (shaders & Renderer::SHADER_CORE)
shaderList.push_back(Renderer::SHADER_CORE);
if (shaders & Renderer::SHADER_BLUR_HORIZONTAL)
shaderList.push_back(Renderer::SHADER_BLUR_HORIZONTAL);
if (shaders & Renderer::SHADER_BLUR_VERTICAL)
shaderList.push_back(Renderer::SHADER_BLUR_VERTICAL);
if (shaders & Renderer::SHADER_SCANLINES)
shaderList.push_back(Renderer::SHADER_SCANLINES);
setMatrix(getIdentity());
bindTexture(postProcTexture1);
GL_CHECK_ERROR(glBindFramebuffer(GL_DRAW_FRAMEBUFFER, shaderFBO1));
// Blit the screen contents to postProcTexture.
GL_CHECK_ERROR(glBlitFramebuffer(0, 0, width, height, 0, 0, width, height,
GL_COLOR_BUFFER_BIT, GL_NEAREST));
GL_CHECK_ERROR(glBindFramebuffer(GL_DRAW_FRAMEBUFFER, shaderFBO2));
bool firstFBO {true};
int drawCalls {0};
for (size_t i = 0; i < shaderList.size(); ++i) {
vertices->shaders = shaderList[i];
int shaderPasses = 1;
// For the blur shaders there is an optional variable to set the number of passes
// to execute, which proportionally affects the blur amount.
if (shaderList[i] == Renderer::SHADER_BLUR_HORIZONTAL ||
shaderList[i] == Renderer::SHADER_BLUR_VERTICAL) {
shaderPasses = parameters.blurPasses;
}
for (int p = 0; p < shaderPasses; ++p) {
if (textureRGBA == nullptr && i == shaderList.size() - 1 && p == shaderPasses - 1) {
// If the screen is rotated and we're at an even number of drawcalls, then
// set the projection to a non-rotated state before making the last drawcall
// as the image would otherwise get rendered upside down.
if (getScreenRotated() && drawCalls % 2 == 0) {
mTrans = getIdentity();
mTrans[3] = glm::round(mTrans[3]);
mTrans = getProjectionMatrixNormal() * mTrans;
}
// If it's the last shader pass, then render directly to the default framebuffer
// to avoid having to make an expensive glBlitFramebuffer() call.
GL_CHECK_ERROR(glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0));
drawTriangleStrips(vertices, 4, Blend::SRC_ALPHA, Blend::ONE_MINUS_SRC_ALPHA);
break;
}
// Apply/render the shaders.
drawTriangleStrips(vertices, 4, Blend::SRC_ALPHA, Blend::ONE_MINUS_SRC_ALPHA);
++drawCalls;
if (firstFBO) {
bindTexture(postProcTexture2);
GL_CHECK_ERROR(glBindFramebuffer(GL_READ_FRAMEBUFFER, shaderFBO2));
GL_CHECK_ERROR(glBindFramebuffer(GL_DRAW_FRAMEBUFFER, shaderFBO1));
GL_CHECK_ERROR(glClear(GL_COLOR_BUFFER_BIT));
firstFBO = false;
}
else {
bindTexture(postProcTexture1);
GL_CHECK_ERROR(glBindFramebuffer(GL_READ_FRAMEBUFFER, shaderFBO1));
GL_CHECK_ERROR(glBindFramebuffer(GL_DRAW_FRAMEBUFFER, shaderFBO2));
GL_CHECK_ERROR(glClear(GL_COLOR_BUFFER_BIT));
firstFBO = true;
}
}
}
// If textureRGBA has an address, it means that the output should go to this
// texture rather than to the screen. The glReadPixels() function is slow, but
// since this will typically only run every now and then to create a cached
// screen texture, it doesn't really matter.
if (textureRGBA) {
if (firstFBO)
GL_CHECK_ERROR(glBindFramebuffer(GL_READ_FRAMEBUFFER, shaderFBO1));
else
GL_CHECK_ERROR(glBindFramebuffer(GL_READ_FRAMEBUFFER, shaderFBO2));
GL_CHECK_ERROR(
glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, textureRGBA));
GL_CHECK_ERROR(glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0));
}
if (getScreenRotated())
setMatrix(getIdentity());
GL_CHECK_ERROR(glBindFramebuffer(GL_READ_FRAMEBUFFER, 0));
}
} // namespace Renderer