diff --git a/src/qt/CMakeLists.txt b/src/qt/CMakeLists.txt
index 94a7dadd9..1d9c4187e 100644
--- a/src/qt/CMakeLists.txt
+++ b/src/qt/CMakeLists.txt
@@ -49,6 +49,13 @@ add_library(ui STATIC
qt_softwarerenderer.hpp
qt_hardwarerenderer.cpp
qt_hardwarerenderer.hpp
+ qt_openglrenderer.cpp
+ qt_openglrenderer.hpp
+ qt_opengloptions.cpp
+ qt_opengloptions.hpp
+ qt_opengloptionsdialog.cpp
+ qt_opengloptionsdialog.hpp
+ qt_opengloptionsdialog.ui
qt_settings.cpp
qt_settings.hpp
diff --git a/src/qt/qt_mainwindow.cpp b/src/qt/qt_mainwindow.cpp
index 2f1b6ca6a..e9e7548cf 100644
--- a/src/qt/qt_mainwindow.cpp
+++ b/src/qt/qt_mainwindow.cpp
@@ -202,7 +202,7 @@ MainWindow::MainWindow(QWidget *parent) :
config_save();
if (QApplication::activeWindow() == this)
{
- ui->stackedWidget->current->setFocus();
+ ui->stackedWidget->setFocusRenderer();
}
});
@@ -236,30 +236,47 @@ MainWindow::MainWindow(QWidget *parent) :
fprintf(stderr, "OpenGL renderers are unsupported on EGLFS.\n");
vid_api = 0;
}
+
QActionGroup* actGroup = nullptr;
- switch (vid_api) {
- case 0:
- ui->stackedWidget->switchRenderer(RendererStack::Renderer::Software);
- ui->actionSoftware_Renderer->setChecked(true);
- break;
- case 1:
- ui->stackedWidget->switchRenderer(RendererStack::Renderer::OpenGL);
- ui->actionHardware_Renderer_OpenGL->setChecked(true);
- break;
- case 2:
- ui->stackedWidget->switchRenderer(RendererStack::Renderer::OpenGLES);
- ui->actionHardware_Renderer_OpenGL_ES->setChecked(true);
- break;
- case 3:
- ui->stackedWidget->switchRenderer(RendererStack::Renderer::OpenGL3);
- ui->actionOpenGL_3_0_Core->setChecked(true);
- break;
- }
+
actGroup = new QActionGroup(this);
actGroup->addAction(ui->actionSoftware_Renderer);
actGroup->addAction(ui->actionHardware_Renderer_OpenGL);
actGroup->addAction(ui->actionHardware_Renderer_OpenGL_ES);
actGroup->addAction(ui->actionOpenGL_3_0_Core);
+ actGroup->setExclusive(true);
+
+ connect(actGroup, &QActionGroup::triggered, [this](QAction* action) {
+ vid_api = action->property("vid_api").toInt();
+ switch (vid_api)
+ {
+ case 0:
+ ui->stackedWidget->switchRenderer(RendererStack::Renderer::Software);
+ break;
+ case 1:
+ ui->stackedWidget->switchRenderer(RendererStack::Renderer::OpenGL);
+ break;
+ case 2:
+ ui->stackedWidget->switchRenderer(RendererStack::Renderer::OpenGLES);
+ break;
+ case 3:
+ ui->stackedWidget->switchRenderer(RendererStack::Renderer::OpenGL3);
+ break;
+ }
+ });
+
+ connect(ui->stackedWidget, &RendererStack::rendererChanged, [this]() {
+ ui->actionRenderer_options->setVisible(ui->stackedWidget->hasOptions());
+ });
+
+ /* Trigger initial renderer switch */
+ for (auto action : actGroup->actions())
+ if (action->property("vid_api").toInt() == vid_api) {
+ action->setChecked(true);
+ emit actGroup->triggered(action);
+ break;
+ }
+
switch (scale) {
case 0:
ui->action0_5x->setChecked(true);
@@ -424,7 +441,7 @@ void MainWindow::closeEvent(QCloseEvent *event) {
QCheckBox *chkbox = new QCheckBox(tr("Don't show this message again"));
questionbox.setCheckBox(chkbox);
chkbox->setChecked(!confirm_exit);
- bool confirm_exit_temp = false;
+
QObject::connect(chkbox, &QCheckBox::stateChanged, [](int state) {
confirm_exit = (state == Qt::CheckState::Unchecked);
});
@@ -497,7 +514,7 @@ void MainWindow::on_actionHard_Reset_triggered() {
QCheckBox *chkbox = new QCheckBox(tr("Don't show this message again"));
questionbox.setCheckBox(chkbox);
chkbox->setChecked(!confirm_reset);
- bool confirm_exit_temp = false;
+
QObject::connect(chkbox, &QCheckBox::stateChanged, [](int state) {
confirm_reset = (state == Qt::CheckState::Unchecked);
});
@@ -1099,21 +1116,21 @@ void MainWindow::on_actionFullscreen_triggered() {
QCheckBox *chkbox = new QCheckBox(tr("Don't show this message again"));
questionbox.setCheckBox(chkbox);
chkbox->setChecked(!video_fullscreen_first);
- bool confirm_exit_temp = false;
+
QObject::connect(chkbox, &QCheckBox::stateChanged, [](int state) {
video_fullscreen_first = (state == Qt::CheckState::Unchecked);
});
questionbox.exec();
config_save();
}
+ video_fullscreen = 1;
setFixedSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX);
ui->menubar->hide();
ui->statusbar->hide();
ui->toolBar->hide();
showFullScreen();
- video_fullscreen = 1;
}
- ui->stackedWidget->rendererWindow->onResize(width(), height());
+ ui->stackedWidget->onResize(width(), height());
}
void MainWindow::getTitle_(wchar_t *title)
@@ -1216,30 +1233,6 @@ QSize MainWindow::getRenderWidgetSize()
return ui->stackedWidget->size();
}
-void MainWindow::on_actionSoftware_Renderer_triggered() {
- ui->stackedWidget->switchRenderer(RendererStack::Renderer::Software);
- ui->actionHardware_Renderer_OpenGL->setChecked(false);
- ui->actionHardware_Renderer_OpenGL_ES->setChecked(false);
- ui->actionOpenGL_3_0_Core->setChecked(false);
- vid_api = 0;
-}
-
-void MainWindow::on_actionHardware_Renderer_OpenGL_triggered() {
- ui->stackedWidget->switchRenderer(RendererStack::Renderer::OpenGL);
- ui->actionSoftware_Renderer->setChecked(false);
- ui->actionHardware_Renderer_OpenGL_ES->setChecked(false);
- ui->actionOpenGL_3_0_Core->setChecked(false);
- vid_api = 1;
-}
-
-void MainWindow::on_actionHardware_Renderer_OpenGL_ES_triggered() {
- ui->stackedWidget->switchRenderer(RendererStack::Renderer::OpenGLES);
- ui->actionSoftware_Renderer->setChecked(false);
- ui->actionHardware_Renderer_OpenGL->setChecked(false);
- ui->actionOpenGL_3_0_Core->setChecked(false);
- vid_api = 2;
-}
-
void MainWindow::focusInEvent(QFocusEvent* event)
{
this->grabKeyboard();
@@ -1335,8 +1328,7 @@ static void update_fullscreen_scale_checkboxes(Ui::MainWindow* ui, QAction* sele
if (video_fullscreen > 0) {
auto widget = ui->stackedWidget->currentWidget();
- auto rc = ui->stackedWidget->rendererWindow;
- rc->onResize(widget->width(), widget->height());
+ ui->stackedWidget->onResize(widget->width(), widget->height());
}
device_force_redraw();
@@ -1563,16 +1555,6 @@ void MainWindow::setSendKeyboardInput(bool enabled)
send_keyboard_input = enabled;
}
-void MainWindow::on_actionOpenGL_3_0_Core_triggered()
-{
- ui->stackedWidget->switchRenderer(RendererStack::Renderer::OpenGL3);
- ui->actionSoftware_Renderer->setChecked(false);
- ui->actionHardware_Renderer_OpenGL->setChecked(false);
- ui->actionHardware_Renderer_OpenGL_ES->setChecked(false);
- ui->actionOpenGL_3_0_Core->setChecked(true);
- vid_api = 3;
-}
-
void MainWindow::on_actionPreferences_triggered()
{
ProgSettings progsettings(this);
@@ -1618,3 +1600,11 @@ void MainWindow::changeEvent(QEvent* event)
#endif
QWidget::changeEvent(event);
}
+
+void MainWindow::on_actionRenderer_options_triggered()
+{
+ auto dlg = ui->stackedWidget->getOptions(this);
+
+ if (dlg)
+ dlg->exec();
+}
diff --git a/src/qt/qt_mainwindow.hpp b/src/qt/qt_mainwindow.hpp
index e6e7d3053..5b155079c 100644
--- a/src/qt/qt_mainwindow.hpp
+++ b/src/qt/qt_mainwindow.hpp
@@ -61,9 +61,6 @@ private slots:
void on_actionHard_Reset_triggered();
void on_actionRight_CTRL_is_left_ALT_triggered();
void on_actionKeyboard_requires_capture_triggered();
- void on_actionHardware_Renderer_OpenGL_ES_triggered();
- void on_actionHardware_Renderer_OpenGL_triggered();
- void on_actionSoftware_Renderer_triggered();
void on_actionResizable_window_triggered(bool checked);
void on_actionInverted_VGA_monitor_triggered();
void on_action0_5x_triggered();
@@ -96,19 +93,15 @@ private slots:
void on_actionHide_status_bar_triggered();
void on_actionHide_tool_bar_triggered();
void on_actionUpdate_status_bar_icons_triggered();
+ void on_actionTake_screenshot_triggered();
+ void on_actionSound_gain_triggered();
+ void on_actionPreferences_triggered();
+ void on_actionEnable_Discord_integration_triggered(bool checked);
+ void on_actionRenderer_options_triggered();
void refreshMediaMenu();
void showMessage_(const QString& header, const QString& message);
void getTitle_(wchar_t* title);
- void on_actionTake_screenshot_triggered();
-
- void on_actionSound_gain_triggered();
-
- void on_actionOpenGL_3_0_Core_triggered();
-
- void on_actionPreferences_triggered();
-
- void on_actionEnable_Discord_integration_triggered(bool checked);
protected:
void keyPressEvent(QKeyEvent* event) override;
diff --git a/src/qt/qt_mainwindow.ui b/src/qt/qt_mainwindow.ui
index 98bb6ae2d..69197d015 100644
--- a/src/qt/qt_mainwindow.ui
+++ b/src/qt/qt_mainwindow.ui
@@ -154,6 +154,8 @@
+
+
@@ -341,6 +343,9 @@
&Qt (Software)
+
+ 0
+
@@ -349,6 +354,9 @@
Qt (&OpenGL)
+
+ 1
+
@@ -357,6 +365,9 @@
Qt (OpenGL &ES)
+
+ 2
+
@@ -626,6 +637,9 @@
Open&GL (3.0 Core)
+
+ 3
+
@@ -700,6 +714,11 @@
false
+
+
+ Renderer options...
+
+
diff --git a/src/qt/qt_opengloptions.cpp b/src/qt/qt_opengloptions.cpp
new file mode 100644
index 000000000..1f2460794
--- /dev/null
+++ b/src/qt/qt_opengloptions.cpp
@@ -0,0 +1,207 @@
+/*
+ * 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 options for Qt
+ *
+ * Authors:
+ * Teemu Korhonen
+ *
+ * Copyright 2022 Teemu Korhonen
+ */
+
+#include
+#include
+#include
+
+#include
+
+#include "qt_opengloptions.hpp"
+
+extern "C" {
+#include <86box/86box.h>
+}
+
+/* Default vertex shader. */
+static const GLchar *vertex_shader = "#version 130\n\
+in vec2 VertexCoord;\n\
+in vec2 TexCoord;\n\
+out vec2 tex;\n\
+void main(){\n\
+ gl_Position = vec4(VertexCoord, 0.0, 1.0);\n\
+ tex = TexCoord;\n\
+}\n";
+
+/* Default fragment shader. */
+static const GLchar *fragment_shader = "#version 130\n\
+in vec2 tex;\n\
+uniform sampler2D texsampler;\n\
+out vec4 color;\n\
+void main() {\n\
+ color = texture(texsampler, tex);\n\
+}\n";
+
+OpenGLOptions::OpenGLOptions(QObject *parent, bool loadConfig)
+ : QObject(parent)
+{
+ if (!loadConfig)
+ return;
+
+ /* Initialize with config. */
+ m_vsync = video_vsync != 0;
+ m_framerate = video_framerate;
+
+ m_renderBehavior = video_framerate == -1
+ ? RenderBehaviorType::SyncWithVideo
+ : RenderBehaviorType::TargetFramerate;
+
+ m_filter = video_filter_method == 0
+ ? FilterType::Nearest
+ : FilterType::Linear;
+
+ QString shaderPath(video_shader);
+
+ if (shaderPath.isEmpty())
+ addDefaultShader();
+ else
+ addShader(shaderPath);
+
+ m_modified = ~ModifiedFlags {};
+}
+
+void
+OpenGLOptions::save()
+{
+ video_vsync = m_vsync ? 1 : 0;
+ video_framerate = m_renderBehavior == RenderBehaviorType::SyncWithVideo ? -1 : m_framerate;
+ video_filter_method = m_filter == FilterType::Nearest ? 0 : 1;
+
+ /* TODO: multiple shaders */
+ auto path = m_shaders.first().path.toLocal8Bit();
+
+ if (!path.isEmpty())
+ memcpy(video_shader, path.constData(), path.size());
+ else
+ video_shader[0] = '\0';
+}
+
+bool
+OpenGLOptions::isModified()
+{
+ /* Filter method is controlled externally */
+ auto newfilter = video_filter_method == 0
+ ? FilterType::Nearest
+ : FilterType::Linear;
+
+ if (m_filter != newfilter) {
+ m_filter = newfilter;
+ m_modified.setFlag(ModifiedFlag::FilterModified);
+ }
+
+ return m_modified != ModifiedFlags {};
+}
+
+OpenGLOptions::ModifiedFlags
+OpenGLOptions::modified()
+{
+ ModifiedFlags temp {};
+ std::swap(temp, m_modified);
+ return temp;
+}
+
+void
+OpenGLOptions::setRenderBehavior(RenderBehaviorType value)
+{
+ m_renderBehavior = value;
+ m_modified.setFlag(ModifiedFlag::RenderBehaviorModified);
+}
+
+void
+OpenGLOptions::setFrameRate(int value)
+{
+ m_framerate = value;
+ m_modified.setFlag(ModifiedFlag::FrameRateModified);
+}
+
+void
+OpenGLOptions::setVSync(bool value)
+{
+ m_vsync = value;
+ m_modified.setFlag(ModifiedFlag::VsyncModified);
+}
+
+void
+OpenGLOptions::setFilter(FilterType value)
+{
+ m_filter = value;
+ m_modified.setFlag(ModifiedFlag::FilterModified);
+}
+
+void
+OpenGLOptions::addShader(const QString &path)
+{
+ QFile shader_file(path);
+
+ if (!shader_file.open(QIODevice::ReadOnly | QIODevice::Text)) {
+ throw std::runtime_error(
+ QString(tr("Error opening \"%1\": %2"))
+ .arg(path)
+ .arg(shader_file.errorString())
+ .toStdString());
+ }
+
+ auto shader_text = QString(shader_file.readAll());
+
+ shader_file.close();
+
+ QRegularExpression version("^\\s*(#version\\s+\\w+)", QRegularExpression::MultilineOption);
+
+ auto match = version.match(shader_text);
+
+ QString version_line("#version 130");
+
+ if (match.hasMatch()) {
+ /* Extract existing version and remove it. */
+ version_line = match.captured(1);
+ shader_text.remove(version);
+ }
+
+ auto shader = new QOpenGLShaderProgram(this);
+
+ auto throw_shader_error = [path, shader](const QString &what) {
+ throw std::runtime_error(
+ QString(what % ":\n\n %2")
+ .arg(path)
+ .arg(shader->log())
+ .toStdString());
+ };
+
+ if (!shader->addShaderFromSourceCode(QOpenGLShader::Vertex, version_line % "\n#define VERTEX\n" % shader_text))
+ throw_shader_error(tr("Error compiling vertex shader in file \"%1\""));
+
+ if (!shader->addShaderFromSourceCode(QOpenGLShader::Fragment, version_line % "\n#define FRAGMENT\n" % shader_text))
+ throw_shader_error(tr("Error compiling fragment shader in file \"%1\""));
+
+ if (!shader->link())
+ throw_shader_error(tr("Error linking shader program in file \"%1\""));
+
+ m_shaders << OpenGLShaderPass(shader, path);
+
+ m_modified.setFlag(ModifiedFlag::ShadersModified);
+}
+
+void
+OpenGLOptions::addDefaultShader()
+{
+ auto shader = new QOpenGLShaderProgram(this);
+ shader->addShaderFromSourceCode(QOpenGLShader::Vertex, vertex_shader);
+ shader->addShaderFromSourceCode(QOpenGLShader::Fragment, fragment_shader);
+ shader->link();
+ m_shaders << OpenGLShaderPass(shader, QString());
+
+ m_modified.setFlag(ModifiedFlag::ShadersModified);
+}
diff --git a/src/qt/qt_opengloptions.hpp b/src/qt/qt_opengloptions.hpp
new file mode 100644
index 000000000..2cbb1f2b2
--- /dev/null
+++ b/src/qt/qt_opengloptions.hpp
@@ -0,0 +1,103 @@
+/*
+ * 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.
+ *
+ * Header for OpenGL renderer options
+ *
+ * Authors:
+ * Teemu Korhonen
+ *
+ * Copyright 2022 Teemu Korhonen
+ */
+
+#ifndef QT_OPENGLOPTIONS_HPP
+#define QT_OPENGLOPTIONS_HPP
+
+#include
+#include
+#include
+#include
+
+struct OpenGLShaderPass {
+ OpenGLShaderPass(QOpenGLShaderProgram *shader, const QString &path)
+ : shader(shader)
+ , path(path)
+ , vertex_coord(shader->attributeLocation("VertexCoord"))
+ , tex_coord(shader->attributeLocation("TexCoord"))
+ , color(shader->attributeLocation("Color"))
+ , mvp_matrix(shader->uniformLocation("MVPMatrix"))
+ , input_size(shader->uniformLocation("InputSize"))
+ , output_size(shader->uniformLocation("OutputSize"))
+ , texture_size(shader->uniformLocation("TextureSize"))
+ , frame_count(shader->uniformLocation("FrameCount"))
+ {
+ }
+
+ QOpenGLShaderProgram *shader;
+ const QString path;
+ const GLint vertex_coord;
+ const GLint tex_coord;
+ const GLint color;
+ const GLint mvp_matrix;
+ const GLint input_size;
+ const GLint output_size;
+ const GLint texture_size;
+ const GLint frame_count;
+};
+
+class OpenGLOptions : public QObject {
+ Q_OBJECT
+
+public:
+ enum ModifiedFlag {
+ RenderBehaviorModified = 0x01,
+ FrameRateModified = 0x02,
+ VsyncModified = 0x04,
+ FilterModified = 0x08,
+ ShadersModified = 0x10
+ };
+ Q_DECLARE_FLAGS(ModifiedFlags, ModifiedFlag)
+
+ enum RenderBehaviorType { SyncWithVideo,
+ TargetFramerate };
+
+ enum FilterType { Nearest,
+ Linear };
+
+ OpenGLOptions(QObject *parent = nullptr, bool loadConfig = false);
+
+ void save();
+ bool isModified();
+ ModifiedFlags modified();
+
+ RenderBehaviorType renderBehavior() const { return m_renderBehavior; }
+ int framerate() const { return m_framerate; }
+ bool vSync() const { return m_vsync; }
+ FilterType filter() const { return m_filter; }
+
+ QList shaders() const { return m_shaders; };
+
+ void setRenderBehavior(RenderBehaviorType value);
+ void setFrameRate(int value);
+ void setVSync(bool value);
+ void setFilter(FilterType value);
+
+ void addShader(const QString &path);
+ void addDefaultShader();
+
+private:
+ RenderBehaviorType m_renderBehavior = SyncWithVideo;
+ int m_framerate = -1;
+ bool m_vsync = false;
+ FilterType m_filter = Nearest;
+ ModifiedFlags m_modified = {};
+ QList m_shaders;
+};
+
+Q_DECLARE_OPERATORS_FOR_FLAGS(OpenGLOptions::ModifiedFlags)
+
+#endif
diff --git a/src/qt/qt_opengloptionsdialog.cpp b/src/qt/qt_opengloptionsdialog.cpp
new file mode 100644
index 000000000..4503e0b90
--- /dev/null
+++ b/src/qt/qt_opengloptionsdialog.cpp
@@ -0,0 +1,114 @@
+/*
+ * 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 options dialog for Qt
+ *
+ * Authors:
+ * Teemu Korhonen
+ *
+ * Copyright 2022 Teemu Korhonen
+ */
+
+#include
+#include
+#include
+
+#include
+
+#include "qt_opengloptionsdialog.hpp"
+#include "qt_util.hpp"
+#include "ui_qt_opengloptionsdialog.h"
+
+OpenGLOptionsDialog::OpenGLOptionsDialog(QWidget *parent, const OpenGLOptions &options)
+ : QDialog(parent)
+ , ui(new Ui::OpenGLOptionsDialog)
+{
+ ui->setupUi(this);
+
+ if (options.renderBehavior() == OpenGLOptions::SyncWithVideo)
+ ui->syncWithVideo->setChecked(true);
+ else {
+ ui->syncToFramerate->setChecked(true);
+ ui->targetFps->setValue(options.framerate());
+ }
+
+ ui->vsync->setChecked(options.vSync());
+
+ if (!options.shaders().isEmpty()) {
+ auto path = options.shaders().first().path;
+ if (!path.isEmpty())
+ ui->shader->setPlainText(path);
+ }
+}
+
+OpenGLOptionsDialog::~OpenGLOptionsDialog()
+{
+ delete ui;
+}
+
+void
+OpenGLOptionsDialog::accept()
+{
+ auto options = new OpenGLOptions();
+
+ options->setRenderBehavior(
+ ui->syncWithVideo->isChecked()
+ ? OpenGLOptions::SyncWithVideo
+ : OpenGLOptions::TargetFramerate);
+
+ options->setFrameRate(ui->targetFps->value());
+
+ options->setVSync(ui->vsync->isChecked());
+
+ auto shader = ui->shader->toPlainText();
+
+ try {
+
+ if (!shader.isEmpty())
+ options->addShader(shader);
+ else
+ options->addDefaultShader();
+
+ } catch (std::runtime_error &e) {
+ delete options;
+
+ QMessageBox msgBox(this);
+ msgBox.setWindowTitle(tr("Shader error"));
+ msgBox.setText(tr("Could not load shaders."));
+ msgBox.setInformativeText(tr("More information in details."));
+ msgBox.setDetailedText(e.what());
+ msgBox.setIcon(QMessageBox::Critical);
+ msgBox.setStandardButtons(QMessageBox::Close);
+ msgBox.setDefaultButton(QMessageBox::Close);
+ msgBox.setStyleSheet("QTextEdit { min-width: 45em; }");
+ msgBox.exec();
+
+ return;
+ }
+
+ options->save();
+
+ emit optionsChanged(options);
+
+ QDialog::accept();
+}
+
+void
+OpenGLOptionsDialog::on_addShader_clicked()
+{
+ auto shader = QFileDialog::getOpenFileName(
+ this,
+ QString(),
+ QString(),
+ tr("OpenGL Shaders") % util::DlgFilter({ "glsl" }, true));
+
+ if (shader.isNull())
+ return;
+
+ ui->shader->setPlainText(shader);
+}
diff --git a/src/qt/qt_opengloptionsdialog.hpp b/src/qt/qt_opengloptionsdialog.hpp
new file mode 100644
index 000000000..833c58471
--- /dev/null
+++ b/src/qt/qt_opengloptionsdialog.hpp
@@ -0,0 +1,48 @@
+/*
+ * 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.
+ *
+ * Header for OpenGL renderer options dialog
+ *
+ * Authors:
+ * Teemu Korhonen
+ *
+ * Copyright 2022 Teemu Korhonen
+ */
+
+#ifndef QT_OPENGLOPTIONSDIALOG_H
+#define QT_OPENGLOPTIONSDIALOG_H
+
+#include
+
+#include "qt_opengloptions.hpp"
+
+namespace Ui {
+class OpenGLOptionsDialog;
+}
+
+class OpenGLOptionsDialog : public QDialog {
+ Q_OBJECT
+
+public:
+ explicit OpenGLOptionsDialog(QWidget *parent, const OpenGLOptions &options);
+ ~OpenGLOptionsDialog();
+
+signals:
+ void optionsChanged(OpenGLOptions *options);
+
+public slots:
+ void accept() override;
+
+private:
+ Ui::OpenGLOptionsDialog *ui;
+
+private slots:
+ void on_addShader_clicked();
+};
+
+#endif // QT_OPENGLOPTIONSDIALOG_H
diff --git a/src/qt/qt_opengloptionsdialog.ui b/src/qt/qt_opengloptionsdialog.ui
new file mode 100644
index 000000000..a6f86b6c2
--- /dev/null
+++ b/src/qt/qt_opengloptionsdialog.ui
@@ -0,0 +1,280 @@
+
+
+ OpenGLOptionsDialog
+
+
+
+ 0
+ 0
+ 400
+ 320
+
+
+
+ OpenGL 3.0 renderer options
+
+
+ -
+
+
+ Render behavior
+
+
+
-
+
+
+ Use target framerate:
+
+
+
+ -
+
+
+ false
+
+
+ fps
+
+
+ 15
+
+
+ 240
+
+
+ 60
+
+
+
+ -
+
+
+ VSync
+
+
+
+ -
+
+
+ <html><head/><body><p>Render each frame immediately, in sync with the emulated display.</p><p><span style=" font-style:italic;">This is the recommended option if the shaders in use don't utilize frametime for animated effects.</span></p></body></html>
+
+
+ Synchronize with video
+
+
+ true
+
+
+
+ -
+
+
+ false
+
+
+ 15
+
+
+ 240
+
+
+ 60
+
+
+ Qt::Horizontal
+
+
+ false
+
+
+ QSlider::NoTicks
+
+
+
+
+
+
+ -
+
+
+ Shaders
+
+
+
-
+
+
+ Remove
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+ -
+
+
+ true
+
+
+ No shader selected
+
+
+
+ -
+
+
+ Browse...
+
+
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QDialogButtonBox::Cancel|QDialogButtonBox::Ok
+
+
+
+
+
+
+ syncWithVideo
+ syncToFramerate
+ fpsSlider
+ targetFps
+ vsync
+ shader
+ addShader
+ removeShader
+
+
+
+
+ buttonBox
+ accepted()
+ OpenGLOptionsDialog
+ accept()
+
+
+ 257
+ 310
+
+
+ 157
+ 274
+
+
+
+
+ buttonBox
+ rejected()
+ OpenGLOptionsDialog
+ reject()
+
+
+ 325
+ 310
+
+
+ 286
+ 274
+
+
+
+
+ syncToFramerate
+ toggled(bool)
+ targetFps
+ setEnabled(bool)
+
+
+ 140
+ 71
+
+
+ 380
+ 98
+
+
+
+
+ syncToFramerate
+ toggled(bool)
+ fpsSlider
+ setEnabled(bool)
+
+
+ 158
+ 66
+
+
+ 168
+ 87
+
+
+
+
+ fpsSlider
+ valueChanged(int)
+ targetFps
+ setValue(int)
+
+
+ 252
+ 90
+
+
+ 308
+ 89
+
+
+
+
+ targetFps
+ valueChanged(int)
+ fpsSlider
+ setValue(int)
+
+
+ 364
+ 93
+
+
+ 134
+ 93
+
+
+
+
+ removeShader
+ clicked()
+ shader
+ clear()
+
+
+ 333
+ 201
+
+
+ 235
+ 208
+
+
+
+
+
diff --git a/src/qt/qt_openglrenderer.cpp b/src/qt/qt_openglrenderer.cpp
new file mode 100644
index 000000000..206cf0a69
--- /dev/null
+++ b/src/qt/qt_openglrenderer.cpp
@@ -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
+#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();
+}
\ No newline at end of file
diff --git a/src/qt/qt_openglrenderer.hpp b/src/qt/qt_openglrenderer.hpp
new file mode 100644
index 000000000..1d8bc036d
--- /dev/null
+++ b/src/qt/qt_openglrenderer.hpp
@@ -0,0 +1,117 @@
+/*
+ * 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.
+ *
+ * Header file for OpenGL renderer
+ *
+ * Authors:
+ * Teemu Korhonen
+ *
+ * Copyright 2022 Teemu Korhonen
+ */
+
+#ifndef QT_OPENGLRENDERER_HPP
+#define QT_OPENGLRENDERER_HPP
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+#include "qt_opengloptions.hpp"
+#include "qt_renderercommon.hpp"
+
+class OpenGLRenderer : public QWindow, protected QOpenGLFunctions_3_0, public RendererCommon {
+ Q_OBJECT
+
+public:
+ QOpenGLContext *context;
+
+ OpenGLRenderer(QWidget *parent = nullptr);
+ ~OpenGLRenderer();
+
+ std::vector> getBuffers() override;
+
+ void finalize() override final;
+ bool hasOptions() const override { return true; }
+ QDialog *getOptions(QWidget *parent) override;
+
+signals:
+ void initialized();
+ void errorInitializing();
+
+public slots:
+ void onBlit(int buf_idx, int x, int y, int w, int h);
+
+protected:
+ void exposeEvent(QExposeEvent *event) override;
+ void resizeEvent(QResizeEvent *event) override;
+ bool event(QEvent *event) override;
+
+private:
+ static constexpr int INIT_WIDTH = 640;
+ static constexpr int INIT_HEIGHT = 400;
+ static constexpr int ROW_LENGTH = 2048;
+ static constexpr int BUFFERPIXELS = 4194304;
+ static constexpr int BUFFERBYTES = 16777216; /* Pixel is 4 bytes. */
+ static constexpr int BUFFERCOUNT = 3; /* How many buffers to use for pixel transfer (2-3 is commonly recommended). */
+
+ OpenGLOptions *options;
+ QTimer *renderTimer;
+
+ bool isInitialized = false;
+ bool isFinalized = false;
+
+ GLuint unpackBufferID = 0;
+ GLuint vertexArrayID = 0;
+ GLuint vertexBufferID = 0;
+ GLuint textureID = 0;
+ int frameCounter = 0;
+
+ void *unpackBuffer = nullptr;
+
+ void initialize();
+ void setupExtensions();
+ void setupBuffers();
+ void applyOptions();
+ void applyShader(const OpenGLShaderPass &shader);
+ bool notReady() const { return !isInitialized || isFinalized; }
+
+ /* GL_ARB_buffer_storage */
+ bool hasBufferStorage = false;
+ PFNGLBUFFERSTORAGEPROC glBufferStorage = nullptr;
+
+ /* GL_ARB_debug_output */
+ bool hasDebugOutput = false;
+ PFNGLDEBUGMESSAGECONTROLARBPROC glDebugMessageControlARB = nullptr;
+ PFNGLDEBUGMESSAGEINSERTARBPROC glDebugMessageInsertARB = nullptr;
+ PFNGLDEBUGMESSAGECALLBACKARBPROC glDebugMessageCallbackARB = nullptr;
+ PFNGLGETDEBUGMESSAGELOGARBPROC glGetDebugMessageLogARB = nullptr;
+
+ /* GL_ARB_sync */
+ bool hasSync = false;
+ PFNGLFENCESYNCPROC glFenceSync = nullptr;
+ PFNGLISSYNCPROC glIsSync = nullptr;
+ PFNGLDELETESYNCPROC glDeleteSync = nullptr;
+ PFNGLCLIENTWAITSYNCPROC glClientWaitSync = nullptr;
+ PFNGLWAITSYNCPROC glWaitSync = nullptr;
+ PFNGLGETINTEGER64VPROC glGetInteger64v = nullptr;
+ PFNGLGETSYNCIVPROC glGetSynciv = nullptr;
+
+private slots:
+ void render();
+ void updateOptions(OpenGLOptions *newOptions);
+};
+
+#endif
diff --git a/src/qt/qt_renderercommon.hpp b/src/qt/qt_renderercommon.hpp
index f55f3ee03..1f94781e4 100644
--- a/src/qt/qt_renderercommon.hpp
+++ b/src/qt/qt_renderercommon.hpp
@@ -1,28 +1,37 @@
#pragma once
-#include
-#include
+#include
#include
+#include
+#include
+#include
-#include
-#include
#include
#include
+#include
+#include
class QWidget;
-class RendererCommon
-{
+class RendererCommon {
public:
RendererCommon();
- void onResize(int width, int height);
- virtual std::vector> getBuffers() = 0;
-protected:
- bool eventDelegate(QEvent* event, bool& result);
+ void onResize(int width, int height);
+ virtual void finalize() { }
- QRect source, destination;
- QWidget* parentWidget{nullptr};
+ virtual std::vector> getBuffers() = 0;
+
+ /* Does renderer implement options dialog */
+ virtual bool hasOptions() const { return false; }
+ /* Returns options dialog for renderer */
+ virtual QDialog *getOptions(QWidget *parent) { return nullptr; }
+
+protected:
+ bool eventDelegate(QEvent *event, bool &result);
+
+ QRect source, destination;
+ QWidget *parentWidget { nullptr };
std::vector buf_usage;
};
diff --git a/src/qt/qt_rendererstack.cpp b/src/qt/qt_rendererstack.cpp
index 4788fa4df..be783864d 100644
--- a/src/qt/qt_rendererstack.cpp
+++ b/src/qt/qt_rendererstack.cpp
@@ -21,8 +21,9 @@
#include "qt_rendererstack.hpp"
#include "ui_qt_rendererstack.h"
-#include "qt_softwarerenderer.hpp"
#include "qt_hardwarerenderer.hpp"
+#include "qt_openglrenderer.hpp"
+#include "qt_softwarerenderer.hpp"
#include "qt_mainwindow.hpp"
#include "qt_util.hpp"
@@ -32,34 +33,33 @@
#include
#ifdef __APPLE__
-#include
+# include
#endif
-extern "C"
-{
+extern "C" {
#include <86box/mouse.h>
#include <86box/plat.h>
#include <86box/video.h>
}
-extern MainWindow* main_window;
-RendererStack::RendererStack(QWidget *parent) :
- QStackedWidget(parent),
- ui(new Ui::RendererStack)
+extern MainWindow *main_window;
+RendererStack::RendererStack(QWidget *parent)
+ : QStackedWidget(parent)
+ , ui(new Ui::RendererStack)
{
ui->setupUi(this);
#ifdef __unix__
-#ifdef WAYLAND
+# ifdef WAYLAND
if (QApplication::platformName().contains("wayland")) {
wl_init();
}
-#endif
-#ifdef EVDEV_INPUT
+# endif
+# ifdef EVDEV_INPUT
if (QApplication::platformName() == "eglfs") {
evdev_init();
}
-#endif
+# endif
if (QApplication::platformName() == "xcb") {
extern void xinput2_init();
xinput2_init();
@@ -76,8 +76,7 @@ extern "C" void macos_poll_mouse();
void
qt_mouse_capture(int on)
{
- if (!on)
- {
+ if (!on) {
mouse_capture = 0;
QApplication::setOverrideCursor(Qt::ArrowCursor);
#ifdef __APPLE__
@@ -93,162 +92,193 @@ qt_mouse_capture(int on)
return;
}
-void RendererStack::mousePoll()
+void
+RendererStack::mousePoll()
{
#ifdef __APPLE__
return macos_poll_mouse();
#else /* !defined __APPLE__ */
- mouse_x = mousedata.deltax;
- mouse_y = mousedata.deltay;
- mouse_z = mousedata.deltaz;
+ mouse_x = mousedata.deltax;
+ mouse_y = mousedata.deltay;
+ mouse_z = mousedata.deltaz;
mousedata.deltax = mousedata.deltay = mousedata.deltaz = 0;
- mouse_buttons = mousedata.mousebuttons;
+ mouse_buttons = mousedata.mousebuttons;
-#ifdef __unix__
-#ifdef WAYLAND
+# ifdef __unix__
+# ifdef WAYLAND
if (QApplication::platformName().contains("wayland"))
wl_mouse_poll();
-#endif
+# endif
-#ifdef EVDEV_INPUT
- if (QApplication::platformName() == "eglfs") evdev_mouse_poll();
+# ifdef EVDEV_INPUT
+ if (QApplication::platformName() == "eglfs")
+ evdev_mouse_poll();
else
-#endif
- if (QApplication::platformName() == "xcb")
- {
+# endif
+ if (QApplication::platformName() == "xcb") {
extern void xinput2_poll();
xinput2_poll();
}
-#endif /* defined __unix__ */
-#endif /* !defined __APPLE__ */
-
+# endif /* defined __unix__ */
+#endif /* !defined __APPLE__ */
}
int ignoreNextMouseEvent = 1;
-void RendererStack::mouseReleaseEvent(QMouseEvent *event)
+void
+RendererStack::mouseReleaseEvent(QMouseEvent *event)
{
- if (this->geometry().contains(event->pos()) && event->button() == Qt::LeftButton && !mouse_capture && (isMouseDown & 1))
- {
+ if (this->geometry().contains(event->pos()) && event->button() == Qt::LeftButton && !mouse_capture && (isMouseDown & 1)) {
plat_mouse_capture(1);
this->setCursor(Qt::BlankCursor);
- if (!ignoreNextMouseEvent) ignoreNextMouseEvent++; // Avoid jumping cursor when moved.
+ if (!ignoreNextMouseEvent)
+ ignoreNextMouseEvent++; // Avoid jumping cursor when moved.
isMouseDown &= ~1;
return;
}
- if (mouse_capture && event->button() == Qt::MiddleButton && mouse_get_buttons() < 3)
- {
+ if (mouse_capture && event->button() == Qt::MiddleButton && mouse_get_buttons() < 3) {
plat_mouse_capture(0);
this->setCursor(Qt::ArrowCursor);
isMouseDown &= ~1;
return;
}
- if (mouse_capture)
- {
+ if (mouse_capture) {
mousedata.mousebuttons &= ~event->button();
}
isMouseDown &= ~1;
}
-void RendererStack::mousePressEvent(QMouseEvent *event)
+void
+RendererStack::mousePressEvent(QMouseEvent *event)
{
isMouseDown |= 1;
- if (mouse_capture)
- {
+ if (mouse_capture) {
mousedata.mousebuttons |= event->button();
}
event->accept();
}
-void RendererStack::wheelEvent(QWheelEvent *event)
+void
+RendererStack::wheelEvent(QWheelEvent *event)
{
- if (mouse_capture)
- {
+ if (mouse_capture) {
mousedata.deltaz += event->pixelDelta().y();
}
}
-void RendererStack::mouseMoveEvent(QMouseEvent *event)
+void
+RendererStack::mouseMoveEvent(QMouseEvent *event)
{
- if (QApplication::platformName().contains("wayland"))
- {
+ if (QApplication::platformName().contains("wayland")) {
event->accept();
return;
}
- if (!mouse_capture) { event->ignore(); return; }
+ if (!mouse_capture) {
+ event->ignore();
+ return;
+ }
#ifdef __APPLE__
event->accept();
return;
#else
static QPoint oldPos = QCursor::pos();
- if (ignoreNextMouseEvent) { oldPos = event->pos(); ignoreNextMouseEvent--; event->accept(); return; }
+ if (ignoreNextMouseEvent) {
+ oldPos = event->pos();
+ ignoreNextMouseEvent--;
+ event->accept();
+ return;
+ }
mousedata.deltax += event->pos().x() - oldPos.x();
mousedata.deltay += event->pos().y() - oldPos.y();
- if (QApplication::platformName() == "eglfs")
- {
- leaveEvent((QEvent*)event);
+ if (QApplication::platformName() == "eglfs") {
+ leaveEvent((QEvent *) event);
ignoreNextMouseEvent--;
}
QCursor::setPos(mapToGlobal(QPoint(width() / 2, height() / 2)));
ignoreNextMouseEvent = 2;
- oldPos = event->pos();
+ oldPos = event->pos();
#endif
}
-void RendererStack::leaveEvent(QEvent* event)
+void
+RendererStack::leaveEvent(QEvent *event)
{
- if (QApplication::platformName().contains("wayland"))
- {
+ if (QApplication::platformName().contains("wayland")) {
event->accept();
return;
}
- if (!mouse_capture) return;
+ if (!mouse_capture)
+ return;
ignoreNextMouseEvent = 2;
event->accept();
}
-void RendererStack::switchRenderer(Renderer renderer) {
+void
+RendererStack::switchRenderer(Renderer renderer)
+{
startblit();
if (current) {
+ rendererWindow->finalize();
removeWidget(current.get());
- }
+ disconnect(this, &RendererStack::blitToRenderer, nullptr, nullptr);
+ /* Create new renderer only after previous is destroyed! */
+ connect(current.get(), &QObject::destroyed, [this, renderer](QObject *) { createRenderer(renderer); });
+
+ current.release()->deleteLater();
+ } else {
+ createRenderer(renderer);
+ }
+}
+
+void
+RendererStack::createRenderer(Renderer renderer)
+{
switch (renderer) {
- case Renderer::Software:
- {
- auto sw = new SoftwareRenderer(this);
- rendererWindow = sw;
- connect(this, &RendererStack::blitToRenderer, sw, &SoftwareRenderer::onBlit, Qt::QueuedConnection);
- current.reset(this->createWindowContainer(sw, this));
+ case Renderer::Software:
+ {
+ auto sw = new SoftwareRenderer(this);
+ rendererWindow = sw;
+ connect(this, &RendererStack::blitToRenderer, sw, &SoftwareRenderer::onBlit, Qt::QueuedConnection);
+ current.reset(this->createWindowContainer(sw, this));
+ }
+ break;
+ case Renderer::OpenGL:
+ {
+ this->createWinId();
+ auto hw = new HardwareRenderer(this);
+ rendererWindow = hw;
+ connect(this, &RendererStack::blitToRenderer, hw, &HardwareRenderer::onBlit, Qt::QueuedConnection);
+ current.reset(this->createWindowContainer(hw, this));
+ break;
+ }
+ case Renderer::OpenGLES:
+ {
+ this->createWinId();
+ auto hw = new HardwareRenderer(this, HardwareRenderer::RenderType::OpenGLES);
+ rendererWindow = hw;
+ connect(this, &RendererStack::blitToRenderer, hw, &HardwareRenderer::onBlit, Qt::QueuedConnection);
+ current.reset(this->createWindowContainer(hw, this));
+ break;
+ }
+ case Renderer::OpenGL3:
+ {
+ this->createWinId();
+ auto hw = new OpenGLRenderer(this);
+ rendererWindow = hw;
+ connect(this, &RendererStack::blitToRenderer, hw, &OpenGLRenderer::onBlit, Qt::QueuedConnection);
+ connect(hw, &OpenGLRenderer::initialized, [=]() {
+ /* Buffers are awailable only after initialization. */
+ imagebufs = rendererWindow->getBuffers();
+ endblit();
+ emit rendererChanged();
+ });
+ connect(hw, &OpenGLRenderer::errorInitializing, [=]() {
+ /* Renderer could initialize, fallback to software. */
+ endblit();
+ QTimer::singleShot(0, this, [this]() { switchRenderer(Renderer::Software); });
+ });
+ current.reset(this->createWindowContainer(hw, this));
+ break;
+ }
}
- break;
- case Renderer::OpenGL:
- {
- this->createWinId();
- auto hw = new HardwareRenderer(this);
- rendererWindow = hw;
- connect(this, &RendererStack::blitToRenderer, hw, &HardwareRenderer::onBlit, Qt::QueuedConnection);
- current.reset(this->createWindowContainer(hw, this));
- break;
- }
- case Renderer::OpenGLES:
- {
- this->createWinId();
- auto hw = new HardwareRenderer(this, HardwareRenderer::RenderType::OpenGLES);
- rendererWindow = hw;
- connect(this, &RendererStack::blitToRenderer, hw, &HardwareRenderer::onBlit, Qt::QueuedConnection);
- current.reset(this->createWindowContainer(hw, this));
- break;
- }
- case Renderer::OpenGL3:
- {
- this->createWinId();
- auto hw = new HardwareRenderer(this, HardwareRenderer::RenderType::OpenGL3);
- rendererWindow = hw;
- connect(this, &RendererStack::blitToRenderer, hw, &HardwareRenderer::onBlit, Qt::QueuedConnection);
- current.reset(this->createWindowContainer(hw, this));
- break;
- }
- }
-
- imagebufs = std::move(rendererWindow->getBuffers());
current->setFocusPolicy(Qt::NoFocus);
current->setFocusProxy(this);
@@ -256,31 +286,35 @@ void RendererStack::switchRenderer(Renderer renderer) {
this->setStyleSheet("background-color: black");
- endblit();
+ currentBuf = 0;
+
+ if (renderer != Renderer::OpenGL3) {
+ imagebufs = rendererWindow->getBuffers();
+ endblit();
+ emit rendererChanged();
+ }
}
// called from blitter thread
-void RendererStack::blit(int x, int y, int w, int h)
+void
+RendererStack::blit(int x, int y, int w, int h)
{
- if ((w <= 0) || (h <= 0) || (w > 2048) || (h > 2048) || (buffer32 == NULL) || std::get(imagebufs[currentBuf])->test_and_set())
- {
+ if ((w <= 0) || (h <= 0) || (w > 2048) || (h > 2048) || (buffer32 == NULL) || std::get(imagebufs[currentBuf])->test_and_set()) {
video_blit_complete();
return;
}
sx = x;
sy = y;
sw = this->w = w;
- sh = this->h = h;
- uint8_t* imagebits = std::get(imagebufs[currentBuf]);
- for (int y1 = y; y1 < (y + h); y1++)
- {
+ sh = this->h = h;
+ uint8_t *imagebits = std::get(imagebufs[currentBuf]);
+ for (int y1 = y; y1 < (y + h); y1++) {
auto scanline = imagebits + (y1 * (2048) * 4) + (x * 4);
video_copy(scanline, &(buffer32->line[y1][x]), w * 4);
}
- if (screenshots)
- {
- video_screenshot((uint32_t *)imagebits, x, y, 2048);
+ if (screenshots) {
+ video_screenshot((uint32_t *) imagebits, x, y, 2048);
}
video_blit_complete();
emit blitToRenderer(currentBuf, sx, sy, sw, sh);
diff --git a/src/qt/qt_rendererstack.hpp b/src/qt/qt_rendererstack.hpp
index 9bff340f0..9133a4927 100644
--- a/src/qt/qt_rendererstack.hpp
+++ b/src/qt/qt_rendererstack.hpp
@@ -1,37 +1,41 @@
#ifndef QT_RENDERERCONTAINER_HPP
#define QT_RENDERERCONTAINER_HPP
-#include
-#include
+#include
#include
-#include
-#include
+#include
+#include
+#include
+
#include
+#include
#include
+#include
+
+#include "qt_renderercommon.hpp"
namespace Ui {
class RendererStack;
}
class RendererCommon;
-class RendererStack : public QStackedWidget
-{
+class RendererStack : public QStackedWidget {
Q_OBJECT
public:
explicit RendererStack(QWidget *parent = nullptr);
~RendererStack();
- void mousePressEvent(QMouseEvent* event) override;
- void mouseReleaseEvent(QMouseEvent* event) override;
- void mouseMoveEvent(QMouseEvent* event) override;
+ void mousePressEvent(QMouseEvent *event) override;
+ void mouseReleaseEvent(QMouseEvent *event) override;
+ void mouseMoveEvent(QMouseEvent *event) override;
void wheelEvent(QWheelEvent *event) override;
void leaveEvent(QEvent *event) override;
- void keyPressEvent(QKeyEvent* event) override
+ void keyPressEvent(QKeyEvent *event) override
{
event->ignore();
}
- void keyReleaseEvent(QKeyEvent* event) override
+ void keyReleaseEvent(QKeyEvent *event) override
{
event->ignore();
}
@@ -44,15 +48,33 @@ public:
};
void switchRenderer(Renderer renderer);
- RendererCommon* rendererWindow{nullptr};
+ /* Does current renderer implement options dialog */
+ bool hasOptions() const { return rendererWindow ? rendererWindow->hasOptions() : false; }
+ /* Returns options dialog for current renderer */
+ QDialog *getOptions(QWidget *parent) { return rendererWindow ? rendererWindow->getOptions(parent) : nullptr; }
+
+ void setFocusRenderer()
+ {
+ if (current)
+ current->setFocus();
+ }
+ void onResize(int width, int height)
+ {
+ if (rendererWindow)
+ rendererWindow->onResize(width, height);
+ }
+
signals:
void blitToRenderer(int buf_idx, int x, int y, int w, int h);
+ void rendererChanged();
public slots:
void blit(int x, int y, int w, int h);
void mousePoll();
private:
+ void createRenderer(Renderer renderer);
+
Ui::RendererStack *ui;
struct mouseinputdata {
@@ -63,13 +85,13 @@ private:
int x, y, w, h, sx, sy, sw, sh;
- int currentBuf = 0;
+ int currentBuf = 0;
int isMouseDown = 0;
- std::vector> imagebufs;
+ std::vector> imagebufs;
+
+ RendererCommon *rendererWindow { nullptr };
std::unique_ptr current;
-
- friend class MainWindow;
};
#endif // QT_RENDERERCONTAINER_HPP
diff --git a/src/qt/qt_softwarerenderer.cpp b/src/qt/qt_softwarerenderer.cpp
index 3deeb1a9f..82d33a7cb 100644
--- a/src/qt/qt_softwarerenderer.cpp
+++ b/src/qt/qt_softwarerenderer.cpp
@@ -53,8 +53,8 @@ void SoftwareRenderer::onBlit(int buf_idx, int x, int y, int w, int h) {
cur_image = buf_idx;
buf_usage[(buf_idx + 1) % 2].clear();
-
- source.setRect(x, y, w, h),
+
+ source.setRect(x, y, w, h);
update();
}