qt: add Direct3D 9 renderer
This commit is contained in:
@@ -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
|
||||
|
@@ -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;
|
||||
|
167
src/qt/qt_d3d9renderer.cpp
Normal file
167
src/qt/qt_d3d9renderer.cpp
Normal file
@@ -0,0 +1,167 @@
|
||||
#include "qt_d3d9renderer.hpp"
|
||||
#include <QResizeEvent>
|
||||
#include <QTimer>
|
||||
|
||||
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(); });
|
||||
}
|
44
src/qt/qt_d3d9renderer.hpp
Normal file
44
src/qt/qt_d3d9renderer.hpp
Normal file
@@ -0,0 +1,44 @@
|
||||
#ifndef D3D9RENDERER_HPP
|
||||
#define D3D9RENDERER_HPP
|
||||
|
||||
#include <QWidget>
|
||||
#include "qt_renderercommon.hpp"
|
||||
|
||||
#include <windows.h>
|
||||
#include <d3d9.h>
|
||||
#include <atomic>
|
||||
|
||||
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<bool> surfaceInUse{false}, finalized{false};
|
||||
bool alreadyInitialized = false;
|
||||
};
|
||||
|
||||
#endif // D3D9RENDERER_HPP
|
@@ -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();
|
||||
|
@@ -54,7 +54,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>724</width>
|
||||
<height>23</height>
|
||||
<height>21</height>
|
||||
</rect>
|
||||
</property>
|
||||
<widget class="QMenu" name="menuAction">
|
||||
@@ -105,6 +105,7 @@
|
||||
<addaction name="actionHardware_Renderer_OpenGL_ES"/>
|
||||
<addaction name="actionOpenGL_3_0_Core"/>
|
||||
<addaction name="actionVulkan"/>
|
||||
<addaction name="actionDirect3D_9"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuWindow_scale_factor">
|
||||
<property name="title">
|
||||
@@ -745,6 +746,17 @@
|
||||
<string>MCA devices...</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionDirect3D_9">
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Direct3D 9</string>
|
||||
</property>
|
||||
<property name="vid_api" stdset="0">
|
||||
<number>5</number>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
|
@@ -22,13 +22,16 @@ public:
|
||||
|
||||
virtual uint32_t getBytesPerRow() { return 2048 * 4; }
|
||||
|
||||
virtual std::vector<std::tuple<uint8_t *, std::atomic_flag *>> getBuffers() = 0;
|
||||
virtual std::vector<std::tuple<uint8_t *, std::atomic_flag *>> getBuffers() { std::vector<std::tuple<uint8_t*, std::atomic_flag*>> 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);
|
||||
|
||||
|
@@ -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<std::atomic_flag *>(imagebufs[currentBuf])->test_and_set()) {
|
||||
video_blit_complete();
|
||||
|
@@ -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<QWidget> current;
|
||||
std::atomic<bool> directBlitting{false};
|
||||
};
|
||||
|
||||
#endif // QT_RENDERERCONTAINER_HPP
|
||||
|
Reference in New Issue
Block a user