/* * 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 #include #include #include #include #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(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(), 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> OpenGLRenderer::getBuffers() { std::vector> 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(); }