qt: add Direct3D 9 renderer

This commit is contained in:
Cacodemon345
2022-06-22 16:36:38 +06:00
parent f17888c4d6
commit 32b29e91ee
9 changed files with 302 additions and 8 deletions

View File

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

View File

@@ -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
View 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, &params, nullptr, &d3d9dev);
if (FAILED(result)) result = d3d9->CreateDeviceEx(D3DADAPTER_DEFAULT, D3DDEVTYPE_REF, windowHandle, D3DCREATE_MULTITHREADED | D3DCREATE_SOFTWARE_VERTEXPROCESSING, &params, 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(&params);
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(); });
}

View 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

View File

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

View File

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

View File

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

View File

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

View File

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