diff --git a/src/qt/CMakeLists.txt b/src/qt/CMakeLists.txt index 7a69b940d..6b4c7a7ca 100644 --- a/src/qt/CMakeLists.txt +++ b/src/qt/CMakeLists.txt @@ -179,7 +179,8 @@ if(WIN32) enable_language(RC) target_sources(86Box PUBLIC ../win/86Box-qt.rc) target_sources(plat PRIVATE win_joystick_rawinput.c) - target_link_libraries(86Box hid) + target_sources(ui PRIVATE qt_d3d9renderer.hpp qt_d3d9renderer.cpp) + target_link_libraries(86Box hid d3d9) # CMake 3.22 messed this up for clang/clang++ # See https://gitlab.kitware.com/cmake/cmake/-/issues/22611 diff --git a/src/qt/qt.c b/src/qt/qt.c index 19ced376e..68b204dbc 100644 --- a/src/qt/qt.c +++ b/src/qt/qt.c @@ -49,6 +49,8 @@ plat_vidapi(char* api) { return 3; } else if (!strcasecmp(api, "qt_vulkan")) { return 4; + } else if (!strcasecmp(api, "qt_d3d9")) { + return 5; } return 0; @@ -73,6 +75,9 @@ char* plat_vidapi_name(int api) { case 4: name = "qt_vulkan"; break; + case 5: + name = "qt_d3d9"; + break; default: fatal("Unknown renderer: %i\n", api); break; diff --git a/src/qt/qt_d3d9renderer.cpp b/src/qt/qt_d3d9renderer.cpp new file mode 100644 index 000000000..1ee24c7d7 --- /dev/null +++ b/src/qt/qt_d3d9renderer.cpp @@ -0,0 +1,167 @@ +#include "qt_d3d9renderer.hpp" +#include +#include + +extern "C" +{ +#include <86box/86box.h> +#include <86box/video.h> +} + +D3D9Renderer::D3D9Renderer(QWidget *parent) + : QWidget{parent}, RendererCommon() +{ + QPalette pal = palette(); + pal.setColor(QPalette::Window, Qt::black); + setAutoFillBackground(true); + setPalette(pal); + + setAttribute(Qt::WA_NativeWindow); + setAttribute(Qt::WA_PaintOnScreen); + setAttribute(Qt::WA_NoSystemBackground); + setAttribute(Qt::WA_OpaquePaintEvent); + + windowHandle = (HWND)winId(); + surfaceInUse = true; + + RendererCommon::parentWidget = parent; + + this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); +} + +D3D9Renderer::~D3D9Renderer() +{ + finalize(); +} + +void D3D9Renderer::finalize() +{ + if (!finalized) { + while (surfaceInUse) {} + finalized = true; + } + surfaceInUse = true; + if (d3d9surface) { d3d9surface->Release(); d3d9surface = nullptr;} + if (d3d9dev) { d3d9dev->Release(); d3d9dev = nullptr; } + if (d3d9) { d3d9->Release(); d3d9 = nullptr; }; +} + +void D3D9Renderer::hideEvent(QHideEvent *event) +{ + finalize(); +} + +void D3D9Renderer::showEvent(QShowEvent *event) +{ + params = {}; + + if (FAILED(Direct3DCreate9Ex(D3D_SDK_VERSION, &d3d9))) { + return error("Failed to create Direct3D 9 context"); + } + + params.Windowed = true; + params.SwapEffect = D3DSWAPEFFECT_FLIPEX; + params.BackBufferWidth = width(); + params.BackBufferHeight = height(); + params.BackBufferCount = 1; + params.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT; + params.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE; + params.hDeviceWindow = windowHandle; + + HRESULT result = d3d9->CreateDeviceEx(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, windowHandle, D3DCREATE_MULTITHREADED | D3DCREATE_HARDWARE_VERTEXPROCESSING, ¶ms, nullptr, &d3d9dev); + if (FAILED(result)) result = d3d9->CreateDeviceEx(D3DADAPTER_DEFAULT, D3DDEVTYPE_REF, windowHandle, D3DCREATE_MULTITHREADED | D3DCREATE_SOFTWARE_VERTEXPROCESSING, ¶ms, nullptr, &d3d9dev); + if (FAILED(result)) { + return error("Failed to create Direct3D 9 device"); + } + + result = d3d9dev->CreateOffscreenPlainSurface(2048, 2048, D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &d3d9surface, nullptr); + if (FAILED(result)) result = d3d9dev->CreateOffscreenPlainSurface(1024, 1024, D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &d3d9surface, nullptr); + if (FAILED(result)) { + return error("Failed to create Direct3D 9 surface"); + } + if (!alreadyInitialized) { + emit initialized(); + alreadyInitialized = true; + } + surfaceInUse = false; + finalized = false; +} + +void D3D9Renderer::paintEvent(QPaintEvent *event) +{ + IDirect3DSurface9* backbuffer = nullptr; + RECT srcRect, dstRect; + HRESULT result = d3d9dev->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &backbuffer); + + if (FAILED(result)) { + return; + } + + srcRect.top = source.top(); + srcRect.bottom = source.bottom(); + srcRect.left = source.left(); + srcRect.right = source.right(); + dstRect.top = destination.top(); + dstRect.bottom = destination.bottom(); + dstRect.left = destination.left(); + dstRect.right = destination.right(); + d3d9dev->BeginScene(); + while (surfaceInUse) {} + surfaceInUse = true; + d3d9dev->StretchRect(d3d9surface, &srcRect, backbuffer, &dstRect, video_filter_method == 0 ? D3DTEXF_POINT : D3DTEXF_LINEAR); + result = d3d9dev->EndScene(); + surfaceInUse = false; + if (SUCCEEDED(result)) { + if (FAILED(d3d9dev->PresentEx(nullptr, nullptr, 0, nullptr, D3DPRESENT_FORCEIMMEDIATE))) { + finalize(); + showEvent(nullptr); + } + } +} + +bool D3D9Renderer::event(QEvent *event) +{ + bool res = false; + if (!eventDelegate(event, res)) return QWidget::event(event); + return res; +} + +void D3D9Renderer::resizeEvent(QResizeEvent *event) +{ + onResize(width(), height()); + + params.BackBufferWidth = event->size().width(); + params.BackBufferHeight = event->size().height(); + if (d3d9dev) d3d9dev->Reset(¶ms); + QWidget::resizeEvent(event); +} + +void D3D9Renderer::blit(int x, int y, int w, int h) +{ + if ((x < 0) || (y < 0) || (w <= 0) || (h <= 0) || (w > 2048) || (h > 2048) || (buffer32 == NULL) || surfaceInUse) { + video_blit_complete(); + return; + } + source.setRect(x, y, w, h); + RECT srcRect; + D3DLOCKED_RECT lockRect; + srcRect.top = source.top(); + srcRect.bottom = source.bottom(); + srcRect.left = source.left(); + srcRect.right = source.right(); + + surfaceInUse = true; + if (screenshots) { + video_screenshot((uint32_t *) &(buffer32->line[y][x]), 0, 0, 2048); + } + if (SUCCEEDED(d3d9surface->LockRect(&lockRect, &srcRect, 0))) { + for (int y1 = 0; y1 < h; y1++) { + video_copy(((uint8_t*)lockRect.pBits) + (y1 * lockRect.Pitch), &(buffer32->line[y + y1][x]), w * 4); + } + video_blit_complete(); + d3d9surface->UnlockRect(); + } + else video_blit_complete(); + surfaceInUse = false; + QTimer::singleShot(0, this, [this] { this->update(); }); +} diff --git a/src/qt/qt_d3d9renderer.hpp b/src/qt/qt_d3d9renderer.hpp new file mode 100644 index 000000000..21ef0ac59 --- /dev/null +++ b/src/qt/qt_d3d9renderer.hpp @@ -0,0 +1,44 @@ +#ifndef D3D9RENDERER_HPP +#define D3D9RENDERER_HPP + +#include +#include "qt_renderercommon.hpp" + +#include +#include +#include + +class D3D9Renderer : public QWidget, public RendererCommon +{ + Q_OBJECT +public: + explicit D3D9Renderer(QWidget *parent = nullptr); + ~D3D9Renderer(); + bool hasBlitFunc() override { return true; } + void blit(int x, int y, int w, int h) override; + void finalize() override; + +protected: + void showEvent(QShowEvent* event) override; + void hideEvent(QHideEvent *event) override; + void resizeEvent(QResizeEvent *event) override; + void paintEvent(QPaintEvent *event) override; + bool event(QEvent* event) override; + QPaintEngine* paintEngine() const override { return nullptr; } + +signals: + void initialized(); + void error(QString); + +private: + HWND windowHandle = 0; + D3DPRESENT_PARAMETERS params{}; + IDirect3D9Ex* d3d9 = nullptr; + IDirect3DDevice9Ex* d3d9dev = nullptr; + IDirect3DSurface9* d3d9surface = nullptr; + + std::atomic surfaceInUse{false}, finalized{false}; + bool alreadyInitialized = false; +}; + +#endif // D3D9RENDERER_HPP diff --git a/src/qt/qt_mainwindow.cpp b/src/qt/qt_mainwindow.cpp index fff1cde05..f0db0f019 100644 --- a/src/qt/qt_mainwindow.cpp +++ b/src/qt/qt_mainwindow.cpp @@ -281,6 +281,10 @@ MainWindow::MainWindow(QWidget *parent) : ui->actionVulkan->setVisible(false); ui->actionOpenGL_3_0_Core->setVisible(false); } +#if !defined Q_OS_WINDOWS + ui->actionDirect3D_9->setVisible(false); + if (vid_api == 5) vid_api = 0; +#endif #if !QT_CONFIG(vulkan) if (vid_api == 4) vid_api = 0; @@ -295,6 +299,7 @@ MainWindow::MainWindow(QWidget *parent) : actGroup->addAction(ui->actionHardware_Renderer_OpenGL_ES); actGroup->addAction(ui->actionOpenGL_3_0_Core); actGroup->addAction(ui->actionVulkan); + actGroup->addAction(ui->actionDirect3D_9); actGroup->setExclusive(true); connect(actGroup, &QActionGroup::triggered, [this](QAction* action) { @@ -316,6 +321,9 @@ MainWindow::MainWindow(QWidget *parent) : case 4: ui->stackedWidget->switchRenderer(RendererStack::Renderer::Vulkan); break; + case 5: + ui->stackedWidget->switchRenderer(RendererStack::Renderer::Direct3D9); + break; } }); @@ -1341,6 +1349,7 @@ void MainWindow::processMacKeyboardInput(bool down, const QKeyEvent* event) { void MainWindow::on_actionFullscreen_triggered() { if (video_fullscreen > 0) { showNormal(); + if (vid_api == 5) ui->stackedWidget->switchRenderer(RendererStack::Renderer::Direct3D9); ui->menubar->show(); if (!hide_status_bar) ui->statusbar->show(); if (!hide_tool_bar) ui->toolBar->show(); diff --git a/src/qt/qt_mainwindow.ui b/src/qt/qt_mainwindow.ui index b26e11f55..6d48cb596 100644 --- a/src/qt/qt_mainwindow.ui +++ b/src/qt/qt_mainwindow.ui @@ -54,7 +54,7 @@ 0 0 724 - 23 + 21 @@ -105,6 +105,7 @@ + @@ -745,6 +746,17 @@ MCA devices... + + + true + + + Direct3D 9 + + + 5 + + diff --git a/src/qt/qt_renderercommon.hpp b/src/qt/qt_renderercommon.hpp index a966b730f..4d346a8e5 100644 --- a/src/qt/qt_renderercommon.hpp +++ b/src/qt/qt_renderercommon.hpp @@ -22,13 +22,16 @@ public: virtual uint32_t getBytesPerRow() { return 2048 * 4; } - virtual std::vector> getBuffers() = 0; + virtual std::vector> getBuffers() { std::vector> buffers; return buffers; } /* Does renderer implement options dialog */ virtual bool hasOptions() const { return false; } /* Returns options dialog for renderer */ virtual QDialog *getOptions(QWidget *parent) { return nullptr; } + virtual bool hasBlitFunc() { return false; } + virtual void blit(int x, int y, int w, int h) {} + protected: bool eventDelegate(QEvent *event, bool &result); diff --git a/src/qt/qt_rendererstack.cpp b/src/qt/qt_rendererstack.cpp index 576e675b5..5efd013cc 100644 --- a/src/qt/qt_rendererstack.cpp +++ b/src/qt/qt_rendererstack.cpp @@ -25,6 +25,9 @@ #include "qt_openglrenderer.hpp" #include "qt_softwarerenderer.hpp" #include "qt_vulkanwindowrenderer.hpp" +#ifdef Q_OS_WIN +#include "qt_d3d9renderer.hpp" +#endif #include "qt_mainwindow.hpp" #include "qt_util.hpp" @@ -223,12 +226,18 @@ RendererStack::switchRenderer(Renderer renderer) { startblit(); if (current) { + if (rendererWindow->hasBlitFunc()) { + while (directBlitting) {} + disconnect(this, &RendererStack::blit, this, &RendererStack::blitRenderer); + video_blit_complete(); + } 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); }); + connect(current.get(), &QObject::destroyed, [this, renderer](QObject *) { video_blit_complete(); createRenderer(renderer); video_blit_complete(); }); current.release()->deleteLater(); } else { @@ -292,6 +301,31 @@ RendererStack::createRenderer(Renderer renderer) current.reset(this->createWindowContainer(hw, this)); break; } +#ifdef Q_OS_WIN + case Renderer::Direct3D9: + { + this->createWinId(); + auto hw = new D3D9Renderer(this); + 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 = {}; + endblit(); + QTimer::singleShot(0, this, [this]() { switchRenderer(Renderer::Software); }); + }); + connect(hw, &D3D9Renderer::initialized, this, [this]() + { + qDebug() << "initialized"; + endblit(); + emit rendererChanged(); + }); + current.reset(hw); + break; + } +#endif #if QT_CONFIG(vulkan) case Renderer::Vulkan: { @@ -340,16 +374,31 @@ RendererStack::createRenderer(Renderer renderer) currentBuf = 0; - if (renderer != Renderer::OpenGL3 && renderer != Renderer::Vulkan) { + if (rendererWindow->hasBlitFunc()) { + connect(this, &RendererStack::blit, this, &RendererStack::blitRenderer, Qt::DirectConnection); + } + else { + 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::blitRenderer(int x, int y, int w, int h) +{ + directBlitting = true; + rendererWindow->blit(x, y, w, h); + directBlitting = false; +} + // called from blitter thread void -RendererStack::blit(int x, int y, int w, int h) +RendererStack::blitCommon(int x, int y, int w, int h) { if ((x < 0) || (y < 0) || (w <= 0) || (h <= 0) || (w > 2048) || (h > 2048) || (buffer32 == NULL) || imagebufs.empty() || std::get(imagebufs[currentBuf])->test_and_set()) { video_blit_complete(); diff --git a/src/qt/qt_rendererstack.hpp b/src/qt/qt_rendererstack.hpp index 45daf838d..f54cd6f66 100644 --- a/src/qt/qt_rendererstack.hpp +++ b/src/qt/qt_rendererstack.hpp @@ -45,7 +45,8 @@ public: OpenGL, OpenGLES, OpenGL3, - Vulkan + Vulkan, + Direct3D9 }; void switchRenderer(Renderer renderer); @@ -72,10 +73,12 @@ public: signals: void blitToRenderer(int buf_idx, int x, int y, int w, int h); + void blit(int x, int y, int w, int h); void rendererChanged(); public slots: - void blit(int x, int y, int w, int h); + void blitCommon(int x, int y, int w, int h); + void blitRenderer(int x, int y, int w, int h); void mousePoll(); private: @@ -98,6 +101,7 @@ private: RendererCommon *rendererWindow { nullptr }; std::unique_ptr current; + std::atomic directBlitting{false}; }; #endif // QT_RENDERERCONTAINER_HPP