qt: Initial OpenGL 3.0 renderer implementation

This commit is contained in:
ts-korhonen
2022-02-27 14:56:51 +02:00
parent f4587949a7
commit d2a9389ce7
15 changed files with 1558 additions and 209 deletions

View File

@@ -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

View File

@@ -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();
}

View File

@@ -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;

View File

@@ -154,6 +154,8 @@
<addaction name="actionRemember_size_and_position"/>
<addaction name="separator"/>
<addaction name="menuRenderer"/>
<addaction name="actionRenderer_options"/>
<addaction name="separator"/>
<addaction name="actionSpecify_dimensions"/>
<addaction name="actionForce_4_3_display_ratio"/>
<addaction name="menuWindow_scale_factor"/>
@@ -341,6 +343,9 @@
<property name="text">
<string>&amp;Qt (Software)</string>
</property>
<property name="vid_api" stdset="0">
<number>0</number>
</property>
</action>
<action name="actionHardware_Renderer_OpenGL">
<property name="checkable">
@@ -349,6 +354,9 @@
<property name="text">
<string>Qt (&amp;OpenGL)</string>
</property>
<property name="vid_api" stdset="0">
<number>1</number>
</property>
</action>
<action name="actionHardware_Renderer_OpenGL_ES">
<property name="checkable">
@@ -357,6 +365,9 @@
<property name="text">
<string>Qt (OpenGL &amp;ES)</string>
</property>
<property name="vid_api" stdset="0">
<number>2</number>
</property>
</action>
<action name="actionHide_status_bar">
<property name="checkable">
@@ -626,6 +637,9 @@
<property name="text">
<string>Open&amp;GL (3.0 Core)</string>
</property>
<property name="vid_api" stdset="0">
<number>3</number>
</property>
</action>
<action name="actionPreferences">
<property name="text">
@@ -700,6 +714,11 @@
<bool>false</bool>
</property>
</action>
<action name="actionRenderer_options">
<property name="text">
<string>Renderer options...</string>
</property>
</action>
</widget>
<customwidgets>
<customwidget>

207
src/qt/qt_opengloptions.cpp Normal file
View File

@@ -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 <QFile>
#include <QRegularExpression>
#include <QStringBuilder>
#include <stdexcept>
#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);
}

103
src/qt/qt_opengloptions.hpp Normal file
View File

@@ -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 <QFLags>
#include <QList>
#include <QObject>
#include <QOpenGLShaderProgram>
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<OpenGLShaderPass> 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<OpenGLShaderPass> m_shaders;
};
Q_DECLARE_OPERATORS_FOR_FLAGS(OpenGLOptions::ModifiedFlags)
#endif

View File

@@ -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 <QFileDialog>
#include <QMessageBox>
#include <QStringBuilder>
#include <stdexcept>
#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);
}

View File

@@ -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 <QDialog>
#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

View File

@@ -0,0 +1,280 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>OpenGLOptionsDialog</class>
<widget class="QDialog" name="OpenGLOptionsDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>320</height>
</rect>
</property>
<property name="windowTitle">
<string>OpenGL 3.0 renderer options</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Render behavior</string>
</property>
<layout class="QGridLayout" name="gridLayout_2" columnstretch="3,1">
<item row="1" column="0">
<widget class="QRadioButton" name="syncToFramerate">
<property name="text">
<string>Use target framerate:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QSpinBox" name="targetFps">
<property name="enabled">
<bool>false</bool>
</property>
<property name="suffix">
<string> fps</string>
</property>
<property name="minimum">
<number>15</number>
</property>
<property name="maximum">
<number>240</number>
</property>
<property name="value">
<number>60</number>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QCheckBox" name="vsync">
<property name="text">
<string>VSync</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QRadioButton" name="syncWithVideo">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Render each frame immediately, in sync with the emulated display.&lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-style:italic;&quot;&gt;This is the recommended option if the shaders in use don't utilize frametime for animated effects.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Synchronize with video</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QSlider" name="fpsSlider">
<property name="enabled">
<bool>false</bool>
</property>
<property name="minimum">
<number>15</number>
</property>
<property name="maximum">
<number>240</number>
</property>
<property name="value">
<number>60</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="invertedAppearance">
<bool>false</bool>
</property>
<property name="tickPosition">
<enum>QSlider::NoTicks</enum>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Shaders</string>
</property>
<layout class="QGridLayout" name="gridLayout" columnstretch="3,1">
<item row="2" column="1">
<widget class="QPushButton" name="removeShader">
<property name="text">
<string>Remove</string>
</property>
</widget>
</item>
<item row="3" column="1">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="0" rowspan="3">
<widget class="QTextEdit" name="shader">
<property name="readOnly">
<bool>true</bool>
</property>
<property name="placeholderText">
<string>No shader selected</string>
</property>
</widget>
</item>
<item row="1" column="1" alignment="Qt::AlignTop">
<widget class="QPushButton" name="addShader">
<property name="text">
<string>Browse...</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<tabstops>
<tabstop>syncWithVideo</tabstop>
<tabstop>syncToFramerate</tabstop>
<tabstop>fpsSlider</tabstop>
<tabstop>targetFps</tabstop>
<tabstop>vsync</tabstop>
<tabstop>shader</tabstop>
<tabstop>addShader</tabstop>
<tabstop>removeShader</tabstop>
</tabstops>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>OpenGLOptionsDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>257</x>
<y>310</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>OpenGLOptionsDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>325</x>
<y>310</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>syncToFramerate</sender>
<signal>toggled(bool)</signal>
<receiver>targetFps</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>140</x>
<y>71</y>
</hint>
<hint type="destinationlabel">
<x>380</x>
<y>98</y>
</hint>
</hints>
</connection>
<connection>
<sender>syncToFramerate</sender>
<signal>toggled(bool)</signal>
<receiver>fpsSlider</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>158</x>
<y>66</y>
</hint>
<hint type="destinationlabel">
<x>168</x>
<y>87</y>
</hint>
</hints>
</connection>
<connection>
<sender>fpsSlider</sender>
<signal>valueChanged(int)</signal>
<receiver>targetFps</receiver>
<slot>setValue(int)</slot>
<hints>
<hint type="sourcelabel">
<x>252</x>
<y>90</y>
</hint>
<hint type="destinationlabel">
<x>308</x>
<y>89</y>
</hint>
</hints>
</connection>
<connection>
<sender>targetFps</sender>
<signal>valueChanged(int)</signal>
<receiver>fpsSlider</receiver>
<slot>setValue(int)</slot>
<hints>
<hint type="sourcelabel">
<x>364</x>
<y>93</y>
</hint>
<hint type="destinationlabel">
<x>134</x>
<y>93</y>
</hint>
</hints>
</connection>
<connection>
<sender>removeShader</sender>
<signal>clicked()</signal>
<receiver>shader</receiver>
<slot>clear()</slot>
<hints>
<hint type="sourcelabel">
<x>333</x>
<y>201</y>
</hint>
<hint type="destinationlabel">
<x>235</x>
<y>208</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View 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();
}

View File

@@ -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 <QOpenGLContext>
#include <QOpenGLFunctions_3_0>
#include <QResizeEvent>
#include <QTimer>
#include <QWidget>
#include <QWindow>
#include <QtOpenGLExtensions/QOpenGLExtensions>
#include <atomic>
#include <tuple>
#include <vector>
#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<std::tuple<uint8_t *, std::atomic_flag *>> 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

View File

@@ -1,28 +1,37 @@
#pragma once
#include <QRect>
#include <QImage>
#include <QDialog>
#include <QEvent>
#include <QImage>
#include <QRect>
#include <QWidget>
#include <vector>
#include <tuple>
#include <atomic>
#include <memory>
#include <tuple>
#include <vector>
class QWidget;
class RendererCommon
{
class RendererCommon {
public:
RendererCommon();
void onResize(int width, int height);
virtual std::vector<std::tuple<uint8_t*, std::atomic_flag*>> 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<std::tuple<uint8_t *, std::atomic_flag *>> 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<std::atomic_flag> buf_usage;
};

View File

@@ -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 <QScreen>
#ifdef __APPLE__
#include <CoreGraphics/CoreGraphics.h>
# include <CoreGraphics/CoreGraphics.h>
#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<std::atomic_flag*>(imagebufs[currentBuf])->test_and_set())
{
if ((w <= 0) || (h <= 0) || (w > 2048) || (h > 2048) || (buffer32 == NULL) || std::get<std::atomic_flag *>(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<uint8_t*>(imagebufs[currentBuf]);
for (int y1 = y; y1 < (y + h); y1++)
{
sh = this->h = h;
uint8_t *imagebits = std::get<uint8_t *>(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);

View File

@@ -1,37 +1,41 @@
#ifndef QT_RENDERERCONTAINER_HPP
#define QT_RENDERERCONTAINER_HPP
#include <QStackedWidget>
#include <QKeyEvent>
#include <QDialog>
#include <QEvent>
#include <memory>
#include <vector>
#include <QKeyEvent>
#include <QStackedWidget>
#include <QWidget>
#include <atomic>
#include <memory>
#include <tuple>
#include <vector>
#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<std::tuple<uint8_t*, std::atomic_flag*>> imagebufs;
std::vector<std::tuple<uint8_t *, std::atomic_flag *>> imagebufs;
RendererCommon *rendererWindow { nullptr };
std::unique_ptr<QWidget> current;
friend class MainWindow;
};
#endif // QT_RENDERERCONTAINER_HPP

View File

@@ -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();
}