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