qt: Initial OpenGL 3.0 renderer implementation
This commit is contained in:
406
src/qt/qt_openglrenderer.cpp
Normal file
406
src/qt/qt_openglrenderer.cpp
Normal file
@@ -0,0 +1,406 @@
|
||||
/*
|
||||
* 86Box A hypervisor and IBM PC system emulator that specializes in
|
||||
* running old operating systems and software designed for IBM
|
||||
* PC systems and compatibles from 1981 through fairly recent
|
||||
* system designs based on the PCI bus.
|
||||
*
|
||||
* This file is part of the 86Box distribution.
|
||||
*
|
||||
* OpenGL renderer for Qt
|
||||
*
|
||||
* Authors:
|
||||
* Teemu Korhonen
|
||||
*
|
||||
* Copyright 2022 Teemu Korhonen
|
||||
*/
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QMessageBox>
|
||||
#include <QOpenGLShaderProgram>
|
||||
#include <QSurfaceFormat>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include "qt_opengloptionsdialog.hpp"
|
||||
#include "qt_openglrenderer.hpp"
|
||||
|
||||
OpenGLRenderer::OpenGLRenderer(QWidget *parent)
|
||||
: QWindow(parent->windowHandle())
|
||||
, renderTimer(new QTimer(this))
|
||||
{
|
||||
renderTimer->setTimerType(Qt::PreciseTimer);
|
||||
/* TODO: need's more accuracy, maybe target 1ms earlier and spin yield */
|
||||
connect(renderTimer, &QTimer::timeout, this, &OpenGLRenderer::render);
|
||||
|
||||
buf_usage = std::vector<std::atomic_flag>(BUFFERCOUNT);
|
||||
for (auto &flag : buf_usage)
|
||||
flag.clear();
|
||||
|
||||
setSurfaceType(QWindow::OpenGLSurface);
|
||||
|
||||
QSurfaceFormat format;
|
||||
|
||||
format.setProfile(QSurfaceFormat::OpenGLContextProfile::CoreProfile);
|
||||
format.setMajorVersion(3);
|
||||
format.setMinorVersion(0);
|
||||
|
||||
setFormat(format);
|
||||
|
||||
context = new QOpenGLContext(this);
|
||||
|
||||
context->setFormat(format);
|
||||
|
||||
context->create();
|
||||
|
||||
parentWidget = parent;
|
||||
|
||||
source.setRect(0, 0, INIT_WIDTH, INIT_HEIGHT);
|
||||
}
|
||||
|
||||
OpenGLRenderer::~OpenGLRenderer()
|
||||
{
|
||||
finalize();
|
||||
}
|
||||
|
||||
void
|
||||
OpenGLRenderer::exposeEvent(QExposeEvent *event)
|
||||
{
|
||||
Q_UNUSED(event);
|
||||
|
||||
if (!isInitialized)
|
||||
initialize();
|
||||
}
|
||||
|
||||
void
|
||||
OpenGLRenderer::resizeEvent(QResizeEvent *event)
|
||||
{
|
||||
Q_UNUSED(event);
|
||||
|
||||
onResize(event->size().width(), event->size().height());
|
||||
|
||||
if (notReady())
|
||||
return;
|
||||
|
||||
context->makeCurrent(this);
|
||||
|
||||
glViewport(
|
||||
destination.x(),
|
||||
destination.y(),
|
||||
destination.width(),
|
||||
destination.height());
|
||||
}
|
||||
|
||||
bool
|
||||
OpenGLRenderer::event(QEvent *event)
|
||||
{
|
||||
Q_UNUSED(event);
|
||||
|
||||
bool res = false;
|
||||
if (!eventDelegate(event, res))
|
||||
return QWindow::event(event);
|
||||
return res;
|
||||
}
|
||||
|
||||
void
|
||||
OpenGLRenderer::initialize()
|
||||
{
|
||||
if (!context->makeCurrent(this) || !initializeOpenGLFunctions()) {
|
||||
/* TODO: This could be done much better */
|
||||
QMessageBox::critical((QWidget *) qApp->findChild<QWindow *>(), tr("Error initializing OpenGL"), tr("OpenGL functions could not be initialized. Falling back to software rendering."));
|
||||
context->doneCurrent();
|
||||
isFinalized = true;
|
||||
isInitialized = true;
|
||||
emit errorInitializing();
|
||||
return;
|
||||
}
|
||||
|
||||
setupExtensions();
|
||||
|
||||
setupBuffers();
|
||||
|
||||
/* Vertex, texture 2d coordinates and color (white) making a quad as triangle strip */
|
||||
const GLfloat surface[] = {
|
||||
-1.f, 1.f, 0.f, 0.f, 1.f, 1.f, 1.f, 1.f,
|
||||
1.f, 1.f, 1.f, 0.f, 1.f, 1.f, 1.f, 1.f,
|
||||
-1.f, -1.f, 0.f, 1.f, 1.f, 1.f, 1.f, 1.f,
|
||||
1.f, -1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f
|
||||
};
|
||||
|
||||
glGenVertexArrays(1, &vertexArrayID);
|
||||
|
||||
glBindVertexArray(vertexArrayID);
|
||||
|
||||
glGenBuffers(1, &vertexBufferID);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vertexBufferID);
|
||||
glBufferData(GL_ARRAY_BUFFER, sizeof(surface), surface, GL_STATIC_DRAW);
|
||||
|
||||
glGenTextures(1, &textureID);
|
||||
glBindTexture(GL_TEXTURE_2D, textureID);
|
||||
|
||||
const GLfloat border_color[] = { 0.f, 0.f, 0.f, 1.f };
|
||||
|
||||
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, border_color);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
|
||||
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, INIT_WIDTH, INIT_HEIGHT, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, NULL);
|
||||
|
||||
options = new OpenGLOptions(this, true);
|
||||
|
||||
applyOptions();
|
||||
|
||||
glClearColor(0.f, 0.f, 0.f, 1.f);
|
||||
|
||||
glViewport(
|
||||
destination.x(),
|
||||
destination.y(),
|
||||
destination.width(),
|
||||
destination.height());
|
||||
|
||||
isInitialized = true;
|
||||
|
||||
emit initialized();
|
||||
}
|
||||
|
||||
void
|
||||
OpenGLRenderer::finalize()
|
||||
{
|
||||
if (isFinalized)
|
||||
return;
|
||||
|
||||
context->makeCurrent(this);
|
||||
|
||||
if (hasBufferStorage)
|
||||
glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
|
||||
|
||||
glDeleteBuffers(1, &unpackBufferID);
|
||||
glDeleteTextures(1, &textureID);
|
||||
glDeleteBuffers(1, &vertexBufferID);
|
||||
glDeleteVertexArrays(1, &vertexArrayID);
|
||||
|
||||
if (!hasBufferStorage && unpackBuffer)
|
||||
free(unpackBuffer);
|
||||
|
||||
context->doneCurrent();
|
||||
|
||||
isFinalized = true;
|
||||
}
|
||||
|
||||
QDialog *
|
||||
OpenGLRenderer::getOptions(QWidget *parent)
|
||||
{
|
||||
auto dialog = new OpenGLOptionsDialog(parent, *options);
|
||||
|
||||
connect(dialog, &OpenGLOptionsDialog::optionsChanged, this, &OpenGLRenderer::updateOptions);
|
||||
|
||||
return dialog;
|
||||
}
|
||||
|
||||
void
|
||||
OpenGLRenderer::setupExtensions()
|
||||
{
|
||||
if (context->hasExtension("GL_ARB_buffer_storage")) {
|
||||
hasBufferStorage = true;
|
||||
|
||||
glBufferStorage = (PFNGLBUFFERSTORAGEPROC) context->getProcAddress("glBufferStorage");
|
||||
}
|
||||
|
||||
if (context->hasExtension("GL_ARB_debug_output")) {
|
||||
hasDebugOutput = true;
|
||||
|
||||
glDebugMessageControlARB = (PFNGLDEBUGMESSAGECONTROLARBPROC) context->getProcAddress("glDebugMessageControlARB");
|
||||
glDebugMessageInsertARB = (PFNGLDEBUGMESSAGEINSERTARBPROC) context->getProcAddress("glDebugMessageInsertARB");
|
||||
glDebugMessageCallbackARB = (PFNGLDEBUGMESSAGECALLBACKARBPROC) context->getProcAddress("glDebugMessageCallbackARB");
|
||||
glGetDebugMessageLogARB = (PFNGLGETDEBUGMESSAGELOGARBPROC) context->getProcAddress("glGetDebugMessageLogARB");
|
||||
}
|
||||
|
||||
if (context->hasExtension("GL_ARB_sync")) {
|
||||
hasSync = true;
|
||||
|
||||
glFenceSync = (PFNGLFENCESYNCPROC) context->getProcAddress("glFenceSync");
|
||||
glIsSync = (PFNGLISSYNCPROC) context->getProcAddress("glIsSync");
|
||||
glDeleteSync = (PFNGLDELETESYNCPROC) context->getProcAddress("glDeleteSync");
|
||||
glClientWaitSync = (PFNGLCLIENTWAITSYNCPROC) context->getProcAddress("glClientWaitSync");
|
||||
glWaitSync = (PFNGLWAITSYNCPROC) context->getProcAddress("glWaitSync");
|
||||
glGetInteger64v = (PFNGLGETINTEGER64VPROC) context->getProcAddress("glGetInteger64v");
|
||||
glGetSynciv = (PFNGLGETSYNCIVPROC) context->getProcAddress("glGetSynciv");
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
OpenGLRenderer::setupBuffers()
|
||||
{
|
||||
glGenBuffers(1, &unpackBufferID);
|
||||
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, unpackBufferID);
|
||||
|
||||
if (hasBufferStorage) {
|
||||
/* Create persistent buffer for pixel transfer. */
|
||||
glBufferStorage(GL_PIXEL_UNPACK_BUFFER, BUFFERBYTES * BUFFERCOUNT, NULL, GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT);
|
||||
|
||||
unpackBuffer = glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, BUFFERBYTES * BUFFERCOUNT, GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT);
|
||||
} else {
|
||||
/* Fallback; create our own buffer. */
|
||||
unpackBuffer = malloc(BUFFERBYTES * BUFFERCOUNT);
|
||||
|
||||
glBufferData(GL_PIXEL_UNPACK_BUFFER, BUFFERBYTES * BUFFERCOUNT, NULL, GL_STREAM_DRAW);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
OpenGLRenderer::applyOptions()
|
||||
{
|
||||
/* TODO: make something else; this was a bad idea. */
|
||||
|
||||
auto modified = options->modified();
|
||||
|
||||
if (modified.testFlag(OpenGLOptions::FrameRateModified) && options->framerate() > 0) {
|
||||
int interval = (int) ceilf(1000.f / (float) options->framerate());
|
||||
renderTimer->setInterval(std::chrono::milliseconds(interval));
|
||||
}
|
||||
|
||||
if (modified.testFlag(OpenGLOptions::RenderBehaviorModified)) {
|
||||
if (options->renderBehavior() == OpenGLOptions::TargetFramerate)
|
||||
renderTimer->start();
|
||||
else
|
||||
renderTimer->stop();
|
||||
}
|
||||
|
||||
if (modified.testFlag(OpenGLOptions::VsyncModified)) {
|
||||
auto format = this->format();
|
||||
format.setSwapInterval(options->vSync() ? 1 : 0);
|
||||
setFormat(format);
|
||||
context->setFormat(format);
|
||||
}
|
||||
|
||||
if (modified.testFlag(OpenGLOptions::FilterModified)) {
|
||||
GLint filter = options->filter() == OpenGLOptions::Linear ? GL_LINEAR : GL_NEAREST;
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
OpenGLRenderer::applyShader(const OpenGLShaderPass &shader)
|
||||
{
|
||||
if (!shader.shader->bind())
|
||||
return;
|
||||
|
||||
if (shader.vertex_coord != -1) {
|
||||
glEnableVertexAttribArray(shader.vertex_coord);
|
||||
glVertexAttribPointer(shader.vertex_coord, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), 0);
|
||||
}
|
||||
|
||||
if (shader.tex_coord != -1) {
|
||||
glEnableVertexAttribArray(shader.tex_coord);
|
||||
glVertexAttribPointer(shader.tex_coord, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (void *) (2 * sizeof(GLfloat)));
|
||||
}
|
||||
|
||||
if (shader.color != -1) {
|
||||
glEnableVertexAttribArray(shader.color);
|
||||
glVertexAttribPointer(shader.color, 4, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (void *) (4 * sizeof(GLfloat)));
|
||||
}
|
||||
|
||||
if (shader.mvp_matrix != -1) {
|
||||
static const GLfloat mvp[] = {
|
||||
1.f, 0.f, 0.f, 0.f,
|
||||
0.f, 1.f, 0.f, 0.f,
|
||||
0.f, 0.f, 1.f, 0.f,
|
||||
0.f, 0.f, 0.f, 1.f
|
||||
};
|
||||
glUniformMatrix4fv(shader.mvp_matrix, 1, GL_FALSE, mvp);
|
||||
}
|
||||
|
||||
if (shader.output_size != -1)
|
||||
glUniform2f(shader.output_size, destination.width(), destination.height());
|
||||
|
||||
if (shader.input_size != -1)
|
||||
glUniform2f(shader.input_size, source.width(), source.height());
|
||||
|
||||
if (shader.texture_size != -1)
|
||||
glUniform2f(shader.texture_size, source.width(), source.height());
|
||||
|
||||
if (shader.frame_count != -1)
|
||||
glUniform1i(shader.frame_count, frameCounter);
|
||||
}
|
||||
|
||||
void
|
||||
OpenGLRenderer::render()
|
||||
{
|
||||
context->makeCurrent(this);
|
||||
|
||||
if (options->isModified())
|
||||
applyOptions();
|
||||
|
||||
/* TODO: multiple shader passes */
|
||||
applyShader(options->shaders().first());
|
||||
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
||||
|
||||
context->swapBuffers(this);
|
||||
|
||||
frameCounter = (frameCounter + 1) & 1023;
|
||||
}
|
||||
|
||||
void
|
||||
OpenGLRenderer::updateOptions(OpenGLOptions *newOptions)
|
||||
{
|
||||
context->makeCurrent(this);
|
||||
|
||||
glUseProgram(0);
|
||||
|
||||
delete options;
|
||||
|
||||
options = newOptions;
|
||||
|
||||
options->setParent(this);
|
||||
|
||||
applyOptions();
|
||||
}
|
||||
|
||||
std::vector<std::tuple<uint8_t *, std::atomic_flag *>>
|
||||
OpenGLRenderer::getBuffers()
|
||||
{
|
||||
std::vector<std::tuple<uint8_t *, std::atomic_flag *>> buffers;
|
||||
|
||||
if (notReady() || !unpackBuffer)
|
||||
return buffers;
|
||||
|
||||
/* Split the buffer area */
|
||||
for (int i = 0; i < BUFFERCOUNT; i++) {
|
||||
buffers.push_back(std::make_tuple((uint8_t *) unpackBuffer + BUFFERBYTES * i, &buf_usage[i]));
|
||||
}
|
||||
|
||||
return buffers;
|
||||
}
|
||||
|
||||
void
|
||||
OpenGLRenderer::onBlit(int buf_idx, int x, int y, int w, int h)
|
||||
{
|
||||
if (notReady())
|
||||
return;
|
||||
|
||||
context->makeCurrent(this);
|
||||
|
||||
if (source.width() != w || source.height() != h) {
|
||||
source.setRect(0, 0, w, h);
|
||||
|
||||
/* Resize the texture */
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, source.width(), source.height(), 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, NULL);
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, unpackBufferID);
|
||||
}
|
||||
|
||||
glPixelStorei(GL_UNPACK_SKIP_PIXELS, BUFFERPIXELS * buf_idx + y * ROW_LENGTH + x);
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, ROW_LENGTH);
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w, h, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, NULL);
|
||||
|
||||
/* TODO: check if fence sync is implementable here and still has any benefit. */
|
||||
glFinish();
|
||||
|
||||
buf_usage[buf_idx].clear();
|
||||
|
||||
if (options->renderBehavior() == OpenGLOptions::SyncWithVideo)
|
||||
render();
|
||||
}
|
||||
Reference in New Issue
Block a user