Files
86Box/src/qt/qt_rendererstack.cpp

577 lines
18 KiB
C++
Raw Normal View History

2022-02-07 15:00:02 +06:00
/*
2023-01-06 15:36:05 -05:00
* 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.
2022-02-07 15:00:02 +06:00
*
2023-01-06 15:36:05 -05:00
* This file is part of the 86Box distribution.
2022-02-07 15:00:02 +06:00
*
2023-01-06 15:36:05 -05:00
* Program settings UI module.
2022-02-07 15:00:02 +06:00
*
*
*
* Authors: Joakim L. Gilje <jgilje@jgilje.net>
* Cacodemon345
* Teemu Korhonen
*
2023-01-06 15:36:05 -05:00
* Copyright 2021 Joakim L. Gilje
* Copyright 2021-2021 Teemu Korhonen
* Copyright 2021-2022 Cacodemon345
2022-02-07 15:00:02 +06:00
*/
#include "qt_rendererstack.hpp"
#include "ui_qt_rendererstack.h"
#include "qt_hardwarerenderer.hpp"
#include "qt_openglrenderer.hpp"
#include "qt_softwarerenderer.hpp"
2022-04-21 16:32:46 +06:00
#include "qt_vulkanwindowrenderer.hpp"
2022-06-22 16:36:38 +06:00
#ifdef Q_OS_WIN
2022-11-19 08:49:04 -05:00
# include "qt_d3d9renderer.hpp"
2022-06-22 16:36:38 +06:00
#endif
#include "qt_mainwindow.hpp"
#include "qt_util.hpp"
#include "ui_qt_mainwindow.h"
2021-12-10 01:03:20 +06:00
#include "evdev_mouse.hpp"
2022-07-16 12:57:54 +06:00
#include <atomic>
2022-04-24 01:05:37 +06:00
#include <stdexcept>
2021-12-08 17:02:28 +06:00
#include <QScreen>
#include <QMessageBox>
2021-12-08 17:02:28 +06:00
#ifdef __APPLE__
# include <CoreGraphics/CoreGraphics.h>
#endif
extern "C" {
#include <86box/86box.h>
#include <86box/config.h>
#include <86box/plat.h>
#include <86box/video.h>
#include <86box/mouse.h>
}
2022-07-04 01:50:42 +06:00
struct mouseinputdata {
2023-01-03 15:42:57 +06:00
atomic_bool mouse_tablet_in_proximity;
2023-05-29 01:30:51 -04:00
std::atomic<double> x_abs;
std::atomic<double> y_abs;
2023-08-07 19:59:32 +02:00
char *mouse_type;
2022-07-04 01:50:42 +06:00
};
static mouseinputdata mousedata;
extern MainWindow *main_window;
2022-07-04 01:50:42 +06:00
RendererStack::RendererStack(QWidget *parent, int monitor_index)
: QStackedWidget(parent)
, ui(new Ui::RendererStack)
{
#ifdef Q_OS_WINDOWS
int raw = 1;
#else
int raw = 0;
#endif
ui->setupUi(this);
2022-07-04 01:50:42 +06:00
m_monitor_index = monitor_index;
#if defined __unix__ && !defined __HAIKU__
2023-08-08 16:17:01 +02:00
char auto_mouse_type[16];
mousedata.mouse_type = getenv("EMU86BOX_MOUSE");
2023-08-08 06:51:24 +02:00
if (!mousedata.mouse_type || (mousedata.mouse_type[0] == '\0') || !stricmp(mousedata.mouse_type, "auto")) {
2022-04-20 20:54:41 -03:00
if (QApplication::platformName().contains("wayland"))
strcpy(auto_mouse_type, "wayland");
2022-08-12 00:35:40 +06:00
else if (QApplication::platformName() == "eglfs")
strcpy(auto_mouse_type, "evdev");
else if (QApplication::platformName() == "xcb")
strcpy(auto_mouse_type, "xinput2");
2022-04-20 20:54:41 -03:00
else
auto_mouse_type[0] = '\0';
2023-08-07 19:59:32 +02:00
mousedata.mouse_type = auto_mouse_type;
2021-12-10 01:03:20 +06:00
}
2022-04-20 20:54:41 -03:00
# ifdef WAYLAND
2023-08-08 06:51:24 +02:00
if (!stricmp(mousedata.mouse_type, "wayland")) {
2022-04-21 13:56:39 -03:00
wl_init();
2022-11-19 08:49:04 -05:00
this->mouse_capture_func = wl_mouse_capture;
2022-04-21 13:56:39 -03:00
this->mouse_uncapture_func = wl_mouse_uncapture;
}
# endif
# ifdef EVDEV_INPUT
if (!stricmp(mousedata.mouse_type, "evdev")) {
2022-04-21 13:56:39 -03:00
evdev_init();
raw = 0;
}
2022-04-25 14:51:17 +06:00
# endif
2023-08-08 06:51:24 +02:00
if (!stricmp(mousedata.mouse_type, "xinput2")) {
2022-08-12 00:35:40 +06:00
extern void xinput2_init();
extern void xinput2_exit();
xinput2_init();
this->mouse_exit_func = xinput2_exit;
}
2021-12-10 01:03:20 +06:00
#endif
if (monitor_index == 0)
mouse_set_raw(raw);
}
RendererStack::~RendererStack()
{
2023-01-03 15:42:57 +06:00
QApplication::restoreOverrideCursor();
delete ui;
}
void
qt_mouse_capture(int on)
{
if (!on) {
mouse_capture = 0;
if (QApplication::overrideCursor()) QApplication::restoreOverrideCursor();
#ifdef __APPLE__
CGAssociateMouseAndMouseCursorPosition(true);
#endif
return;
}
mouse_capture = 1;
QApplication::setOverrideCursor(Qt::BlankCursor);
#ifdef __APPLE__
CGAssociateMouseAndMouseCursorPosition(false);
#endif
return;
}
2021-12-09 00:01:22 +06:00
int ignoreNextMouseEvent = 1;
void
RendererStack::mouseReleaseEvent(QMouseEvent *event)
{
if (this->geometry().contains(event->pos()) && (event->button() == Qt::LeftButton) && !mouse_capture && (isMouseDown & 1) && (kbd_req_capture || (mouse_get_buttons() != 0)) && (mouse_mode == 0)) {
plat_mouse_capture(1);
this->setCursor(Qt::BlankCursor);
if (!ignoreNextMouseEvent)
ignoreNextMouseEvent++; // Avoid jumping cursor when moved.
isMouseDown &= ~1;
return;
}
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 || (mouse_mode >= 1)) {
#ifdef Q_OS_WINDOWS
if (((m_monitor_index >= 1) && (mouse_mode >= 1) && mousedata.mouse_tablet_in_proximity) ||
((m_monitor_index < 1) && (mouse_mode >= 1)))
#else
#ifndef __APPLE__
if (((m_monitor_index >= 1) && (mouse_mode >= 1) && mousedata.mouse_tablet_in_proximity) ||
(m_monitor_index < 1))
#else
if ((m_monitor_index >= 1) && (mouse_mode >= 1) && mousedata.mouse_tablet_in_proximity)
#endif
#endif
mouse_set_buttons_ex(mouse_get_buttons_ex() & ~event->button());
}
isMouseDown &= ~1;
}
2022-07-16 12:57:54 +06:00
void
RendererStack::mousePressEvent(QMouseEvent *event)
{
isMouseDown |= 1;
if (mouse_capture || (mouse_mode >= 1)) {
#ifdef Q_OS_WINDOWS
if (((m_monitor_index >= 1) && (mouse_mode >= 1) && mousedata.mouse_tablet_in_proximity) ||
((m_monitor_index < 1) && (mouse_mode >= 1)))
#else
#ifndef __APPLE__
if (((m_monitor_index >= 1) && (mouse_mode >= 1) && mousedata.mouse_tablet_in_proximity) ||
(m_monitor_index < 1))
#else
if ((m_monitor_index >= 1) && (mouse_mode >= 1) && mousedata.mouse_tablet_in_proximity)
#endif
#endif
mouse_set_buttons_ex(mouse_get_buttons_ex() | event->button());
}
event->accept();
}
2022-07-16 12:57:54 +06:00
void
RendererStack::wheelEvent(QWheelEvent *event)
{
mouse_set_z(event->pixelDelta().y());
}
void
RendererStack::mouseMoveEvent(QMouseEvent *event)
{
if (QApplication::platformName().contains("wayland")) {
event->accept();
return;
}
if (!mouse_capture) {
event->ignore();
return;
}
#if defined __APPLE__ || defined _WIN32
event->accept();
return;
#else
static QPoint oldPos = QCursor::pos();
if (ignoreNextMouseEvent) {
oldPos = event->pos();
ignoreNextMouseEvent--;
event->accept();
return;
}
#if defined __unix__ && !defined __HAIKU__
2023-08-07 19:59:32 +02:00
if (!stricmp(mousedata.mouse_type, "wayland"))
mouse_scale(event->pos().x() - oldPos.x(), event->pos().y() - oldPos.y());
#endif
if (QApplication::platformName() == "eglfs") {
leaveEvent((QEvent *) event);
2021-12-09 00:01:22 +06:00
ignoreNextMouseEvent--;
}
2022-02-15 02:34:13 +06:00
QCursor::setPos(mapToGlobal(QPoint(width() / 2, height() / 2)));
ignoreNextMouseEvent = 2;
oldPos = event->pos();
#endif
if (m_monitor_index >= 1) {
if (mouse_mode >= 1) {
mouse_x_abs = mousedata.x_abs;
mouse_y_abs = mousedata.y_abs;
if (!mouse_tablet_in_proximity)
mouse_tablet_in_proximity = mousedata.mouse_tablet_in_proximity;
}
return;
}
#ifdef Q_OS_WINDOWS
if (mouse_mode == 0) {
mouse_x_abs = mousedata.x_abs;
mouse_y_abs = mousedata.y_abs;
return;
}
#endif
mouse_x_abs = mousedata.x_abs;
mouse_y_abs = mousedata.y_abs;
mouse_tablet_in_proximity = mousedata.mouse_tablet_in_proximity;
}
2023-01-03 15:42:57 +06:00
void
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
RendererStack::enterEvent(QEnterEvent *event)
#else
RendererStack::enterEvent(QEvent *event)
#endif
{
mousedata.mouse_tablet_in_proximity = 1;
if (mouse_mode == 1)
QApplication::setOverrideCursor(Qt::BlankCursor);
}
void
RendererStack::leaveEvent(QEvent *event)
{
2023-01-03 15:42:57 +06:00
mousedata.mouse_tablet_in_proximity = 0;
if (mouse_mode == 1 && QApplication::overrideCursor())
QApplication::restoreOverrideCursor();
if (QApplication::platformName().contains("wayland")) {
event->accept();
return;
}
if (!mouse_capture)
return;
QCursor::setPos(mapToGlobal(QPoint(width() / 2, height() / 2)));
ignoreNextMouseEvent = 2;
event->accept();
}
void
RendererStack::switchRenderer(Renderer renderer)
{
startblit();
if (current) {
if ((current_vid_api == Renderer::Direct3D9 && renderer != Renderer::Direct3D9)
2022-11-19 08:49:04 -05:00
|| (current_vid_api != Renderer::Direct3D9 && renderer == Renderer::Direct3D9)) {
rendererWindow->finalize();
if (rendererWindow->hasBlitFunc()) {
2022-11-19 08:49:04 -05:00
while (directBlitting) { }
connect(this, &RendererStack::blit, this, &RendererStack::blitDummy, Qt::DirectConnection);
disconnect(this, &RendererStack::blit, this, &RendererStack::blitRenderer);
} else {
connect(this, &RendererStack::blit, this, &RendererStack::blitDummy, Qt::DirectConnection);
disconnect(this, &RendererStack::blit, this, &RendererStack::blitCommon);
}
removeWidget(current.get());
disconnect(this, &RendererStack::blitToRenderer, nullptr, nullptr);
2022-06-22 16:36:38 +06:00
/* Create new renderer only after previous is destroyed! */
connect(current.get(), &QObject::destroyed, [this, renderer](QObject *) {
createRenderer(renderer);
disconnect(this, &RendererStack::blit, this, &RendererStack::blitDummy);
blitDummied = false;
2023-06-11 11:54:01 -04:00
QTimer::singleShot(1000, this, []() { blitDummied = false; });
});
rendererWindow->hasBlitFunc() ? current.reset() : current.release()->deleteLater();
} else {
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)
{
current_vid_api = renderer;
switch (renderer) {
default:
case Renderer::Software:
{
auto sw = new SoftwareRenderer(this);
rendererWindow = sw;
connect(this, &RendererStack::blitToRenderer, sw, &SoftwareRenderer::onBlit, Qt::QueuedConnection);
#ifdef __HAIKU__
current.reset(sw);
#else
current.reset(this->createWindowContainer(sw, this));
#endif
}
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 available only after initialization. */
imagebufs = rendererWindow->getBuffers();
endblit();
emit rendererChanged();
});
connect(hw, &OpenGLRenderer::errorInitializing, [=]() {
/* Renderer not could initialize, fallback to software. */
imagebufs = {};
QTimer::singleShot(0, this, [this]() { switchRenderer(Renderer::Software); });
});
current.reset(this->createWindowContainer(hw, this));
break;
}
2022-06-22 16:36:38 +06:00
#ifdef Q_OS_WIN
case Renderer::Direct3D9:
{
2022-11-19 08:49:04 -05:00
this->createWinId();
auto hw = new D3D9Renderer(this, m_monitor_index);
rendererWindow = hw;
connect(hw, &D3D9Renderer::error, this, [this](QString str) {
auto msgBox = new QMessageBox(QMessageBox::Critical, "86Box", QString("Failed to initialize D3D9 renderer. Falling back to software rendering.\n\n") + str, QMessageBox::Ok);
msgBox->setAttribute(Qt::WA_DeleteOnClose);
msgBox->show();
imagebufs = {};
QTimer::singleShot(0, this, [this]() { switchRenderer(Renderer::Software); });
});
connect(hw, &D3D9Renderer::initialized, this, [this]() {
endblit();
emit rendererChanged();
});
current.reset(hw);
break;
}
2022-06-22 16:36:38 +06:00
#endif
#if QT_CONFIG(vulkan)
2022-04-21 16:32:46 +06:00
case Renderer::Vulkan:
2022-11-19 08:49:04 -05:00
{
this->createWinId();
VulkanWindowRenderer *hw = nullptr;
try {
hw = new VulkanWindowRenderer(this);
} catch (std::runtime_error &e) {
auto msgBox = new QMessageBox(QMessageBox::Critical, "86Box", e.what() + QString("\nFalling back to software rendering."), QMessageBox::Ok);
msgBox->setAttribute(Qt::WA_DeleteOnClose);
msgBox->show();
imagebufs = {};
QTimer::singleShot(0, this, [this]() { switchRenderer(Renderer::Software); });
current.reset(nullptr);
break;
2023-05-29 01:30:51 -04:00
}
2022-11-19 08:49:04 -05:00
rendererWindow = hw;
connect(this, &RendererStack::blitToRenderer, hw, &VulkanWindowRenderer::onBlit, Qt::QueuedConnection);
connect(hw, &VulkanWindowRenderer::rendererInitialized, [=]() {
/* Buffers are available only after initialization. */
imagebufs = rendererWindow->getBuffers();
endblit();
emit rendererChanged();
});
connect(hw, &VulkanWindowRenderer::errorInitializing, [=]() {
/* Renderer could not initialize, fallback to software. */
auto msgBox = new QMessageBox(QMessageBox::Critical, "86Box", QString("Failed to initialize Vulkan renderer.\nFalling back to software rendering."), QMessageBox::Ok);
msgBox->setAttribute(Qt::WA_DeleteOnClose);
msgBox->show();
imagebufs = {};
QTimer::singleShot(0, this, [this]() { switchRenderer(Renderer::Software); });
});
current.reset(this->createWindowContainer(hw, this));
break;
2022-11-19 08:49:04 -05:00
}
#endif
}
2022-11-19 08:49:04 -05:00
if (current.get() == nullptr)
return;
current->setFocusPolicy(Qt::NoFocus);
current->setFocusProxy(this);
current->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
2022-11-01 00:56:31 +06:00
current->setStyleSheet("background-color: black");
addWidget(current.get());
this->setStyleSheet("background-color: black");
currentBuf = 0;
2022-06-22 16:36:38 +06:00
if (rendererWindow->hasBlitFunc()) {
connect(this, &RendererStack::blit, this, &RendererStack::blitRenderer, Qt::DirectConnection);
2022-11-19 08:49:04 -05:00
} else {
2022-06-22 16:36:38 +06:00
connect(this, &RendererStack::blit, this, &RendererStack::blitCommon, Qt::DirectConnection);
}
if (renderer != Renderer::OpenGL3 && renderer != Renderer::Vulkan && renderer != Renderer::Direct3D9) {
imagebufs = rendererWindow->getBuffers();
endblit();
emit rendererChanged();
}
}
void
RendererStack::blitDummy(int x, int y, int w, int h)
{
2022-07-04 01:50:42 +06:00
video_blit_complete_monitor(m_monitor_index);
blitDummied = true;
}
2022-06-22 16:36:38 +06:00
void
RendererStack::blitRenderer(int x, int y, int w, int h)
{
2022-11-19 08:49:04 -05:00
if (blitDummied) {
blitDummied = false;
video_blit_complete_monitor(m_monitor_index);
return;
}
2022-06-22 16:36:38 +06:00
directBlitting = true;
rendererWindow->blit(x, y, w, h);
directBlitting = false;
}
// called from blitter thread
void
2022-06-22 16:36:38 +06:00
RendererStack::blitCommon(int x, int y, int w, int h)
{
2022-07-31 23:31:59 +02:00
if (blitDummied || (x < 0) || (y < 0) || (w <= 0) || (h <= 0) || (w > 2048) || (h > 2048) || (monitors[m_monitor_index].target_buffer == NULL) || imagebufs.empty() || std::get<std::atomic_flag *>(imagebufs[currentBuf])->test_and_set()) {
2022-07-04 01:50:42 +06:00
video_blit_complete_monitor(m_monitor_index);
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++) {
2022-04-21 16:32:46 +06:00
auto scanline = imagebits + (y1 * rendererWindow->getBytesPerRow()) + (x * 4);
2022-07-04 01:50:42 +06:00
video_copy(scanline, &(monitors[m_monitor_index].target_buffer->line[y1][x]), w * 4);
}
2022-07-07 16:09:50 +06:00
if (monitors[m_monitor_index].mon_screenshots) {
video_screenshot_monitor((uint32_t *) imagebits, x, y, 2048, m_monitor_index);
}
2022-07-04 01:50:42 +06:00
video_blit_complete_monitor(m_monitor_index);
emit blitToRenderer(currentBuf, sx, sy, sw, sh);
currentBuf = (currentBuf + 1) % imagebufs.size();
}
2022-11-19 08:49:04 -05:00
void
RendererStack::closeEvent(QCloseEvent *event)
{
if (cpu_thread_run == 1 || is_quit == 0) {
event->accept();
main_window->ui->actionShow_non_primary_monitors->setChecked(false);
return;
}
event->ignore();
main_window->close();
}
2022-11-19 08:49:04 -05:00
void
RendererStack::changeEvent(QEvent *event)
{
if (m_monitor_index != 0 && isVisible()) {
monitor_settings[m_monitor_index].mon_window_maximized = isMaximized();
config_save();
}
}
2023-01-03 15:42:57 +06:00
bool
RendererStack::event(QEvent* event)
{
if (event->type() == QEvent::MouseMove) {
QMouseEvent* mouse_event = (QMouseEvent*)event;
if (mouse_mode >= 1) {
mousedata.x_abs = (mouse_event->localPos().x()) / (long double)width();
mousedata.y_abs = (mouse_event->localPos().y()) / (long double)height();
}
}
return QStackedWidget::event(event);
}
void
RendererStack::setFocusRenderer()
{
if (current)
current->setFocus();
}
void
RendererStack::onResize(int width, int height)
{
if (rendererWindow) {
rendererWindow->r_monitor_index = m_monitor_index;
rendererWindow->onResize(width, height);
}
}