Migration of shibumi
This commit is contained in:
@@ -2,4 +2,5 @@
|
||||
|
||||
namespace natsukashii::core {
|
||||
void BaseCore::Run() {}
|
||||
void BaseCore::PollInputs(u32 windowID) {}
|
||||
}
|
||||
@@ -1,8 +1,13 @@
|
||||
#pragma once
|
||||
#include <common.hpp>
|
||||
|
||||
namespace natsukashii::core {
|
||||
struct BaseCore {
|
||||
virtual ~BaseCore() {}
|
||||
virtual ~BaseCore() = default;
|
||||
virtual void Run();
|
||||
virtual void PollInputs(u32);
|
||||
[[nodiscard]] bool& ShouldQuit() { return quit; }
|
||||
private:
|
||||
bool quit = false;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -12,6 +12,5 @@ add_library(cores
|
||||
Scheduler.hpp
|
||||
common.hpp
|
||||
util.hpp)
|
||||
target_include_directories(cores PRIVATE .)
|
||||
target_include_directories(cores PUBLIC ../../external)
|
||||
target_include_directories(cores PUBLIC . ../../external)
|
||||
target_link_libraries(cores PUBLIC gb n64)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include <Core.hpp>
|
||||
#include <SDL2/SDL_events.h>
|
||||
|
||||
namespace natsukashii::gb::core {
|
||||
Core::Core(const std::string& rom) {
|
||||
@@ -10,4 +11,10 @@ void Core::Run() {
|
||||
cpu.Step(mem);
|
||||
}
|
||||
}
|
||||
|
||||
void Core::PollInputs(u32 windowID) {
|
||||
SDL_Event event;
|
||||
SDL_PollEvent(&event);
|
||||
ShouldQuit() = event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_CLOSE && event.window.windowID == windowID;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ struct Core : BaseCore {
|
||||
~Core() override = default;
|
||||
explicit Core(const std::string&);
|
||||
void Run() override;
|
||||
void PollInputs(u32) override;
|
||||
private:
|
||||
Mem mem;
|
||||
Cpu cpu;
|
||||
|
||||
@@ -18,16 +18,14 @@ FetchContent_Declare(
|
||||
FetchContent_MakeAvailable(mio toml11)
|
||||
find_package(fmt REQUIRED)
|
||||
|
||||
add_subdirectory(core)
|
||||
|
||||
add_library(n64
|
||||
Core.hpp
|
||||
Core.cpp
|
||||
Cpu.hpp
|
||||
Cpu.cpp
|
||||
Mem.cpp
|
||||
Mem.hpp
|
||||
memory_regions.hpp
|
||||
../BaseCore.cpp
|
||||
../BaseCore.hpp)
|
||||
target_include_directories(n64 PRIVATE . .. ../../../external)
|
||||
target_include_directories(n64 PRIVATE . ..)
|
||||
target_include_directories(n64 PUBLIC ${mio_SOURCE_DIR}/include ${toml11_SOURCE_DIR}/include)
|
||||
target_link_libraries(n64 PRIVATE mio::mio toml11::toml11 fmt::fmt)
|
||||
target_link_libraries(n64 PRIVATE mio::mio toml11::toml11 fmt::fmt n64-core)
|
||||
|
||||
@@ -1,9 +1,18 @@
|
||||
#include <Core.hpp>
|
||||
#include <SDL2/SDL_events.h>
|
||||
|
||||
namespace natsukashii::n64::core {
|
||||
Core::Core(const std::string& rom) {}
|
||||
Core::Core(const std::string& rom) {
|
||||
mem.LoadROM(rom);
|
||||
}
|
||||
|
||||
void Core::Run() {
|
||||
|
||||
}
|
||||
|
||||
void Core::PollInputs(u32 windowID) {
|
||||
SDL_Event event;
|
||||
SDL_PollEvent(&event);
|
||||
ShouldQuit() = event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_CLOSE && event.window.windowID == windowID;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
#include <BaseCore.hpp>
|
||||
#include <Cpu.hpp>
|
||||
#include <Mem.hpp>
|
||||
#include "n64/core/Cpu.hpp"
|
||||
#include "n64/core/Mem.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace natsukashii::n64::core {
|
||||
@@ -10,5 +10,9 @@ struct Core : BaseCore {
|
||||
~Core() override = default;
|
||||
explicit Core(const std::string&);
|
||||
void Run() override;
|
||||
void PollInputs(u32) override;
|
||||
private:
|
||||
Mem mem;
|
||||
Cpu cpu;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
#include <Cpu.hpp>
|
||||
13
src/core/n64/core/CMakeLists.txt
Normal file
13
src/core/n64/core/CMakeLists.txt
Normal file
@@ -0,0 +1,13 @@
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
project(n64-core)
|
||||
|
||||
add_subdirectory(cpu)
|
||||
|
||||
add_library(n64-core
|
||||
Cpu.hpp
|
||||
Cpu.cpp
|
||||
Mem.cpp
|
||||
Mem.hpp RDP.cpp RDP.hpp)
|
||||
|
||||
target_include_directories(n64-core PRIVATE . .. ../../)
|
||||
target_link_libraries(n64-core PUBLIC n64-cpu)
|
||||
1
src/core/n64/core/Cpu.cpp
Normal file
1
src/core/n64/core/Cpu.cpp
Normal file
@@ -0,0 +1 @@
|
||||
#include "Cpu.hpp"
|
||||
8
src/core/n64/core/Cpu.hpp
Normal file
8
src/core/n64/core/Cpu.hpp
Normal file
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
#include "n64/core/cpu/Registers.hpp"
|
||||
|
||||
namespace natsukashii::n64::core {
|
||||
struct Cpu {
|
||||
Registers regs;
|
||||
};
|
||||
}
|
||||
@@ -18,13 +18,13 @@ void Mem::LoadROM(const std::string& filename) {
|
||||
|
||||
file.seekg(std::ios::end);
|
||||
auto size = file.tellg();
|
||||
auto size_adjusted = util::NextPow2(size);
|
||||
romMask = size_adjusted - 1;
|
||||
file.seekg(std::ios::beg);
|
||||
|
||||
std::vector<u8> rom;
|
||||
rom.reserve(size);
|
||||
rom.insert(rom.begin(),
|
||||
std::istream_iterator<u8>(file),
|
||||
std::istream_iterator<u8>());
|
||||
rom.reserve(size_adjusted);
|
||||
file.read(reinterpret_cast<char*>(rom.data()), size);
|
||||
|
||||
file.close();
|
||||
util::SwapN64Rom(size, rom.data());
|
||||
@@ -1,16 +1,17 @@
|
||||
#pragma once
|
||||
#include <common.hpp>
|
||||
#include <memory_regions.hpp>
|
||||
#include <n64/memory_regions.hpp>
|
||||
#include <vector>
|
||||
|
||||
namespace natsukashii::n64::core {
|
||||
struct Mem {
|
||||
~Mem() = default;
|
||||
Mem();
|
||||
void LoadROM(const std::string&);
|
||||
private:
|
||||
std::vector<u8> cart, rdram, sram;
|
||||
u8 dmem[DMEM_SIZE]{}, imem[IMEM_SIZE]{}, pif_ram[PIF_RAM_SIZE]{};
|
||||
u8 pif_bootrom[PIF_BOOTROM_SIZE]{};
|
||||
size_t rom_mask;
|
||||
u8 dmem[DMEM_SIZE]{}, imem[IMEM_SIZE]{}, pifRam[PIF_RAM_SIZE]{};
|
||||
u8 pifBootrom[PIF_BOOTROM_SIZE]{};
|
||||
size_t romMask;
|
||||
};
|
||||
}
|
||||
1
src/core/n64/core/RDP.cpp
Normal file
1
src/core/n64/core/RDP.cpp
Normal file
@@ -0,0 +1 @@
|
||||
#include <RDP.hpp>
|
||||
@@ -1,7 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
namespace natsukashii::n64::core {
|
||||
struct Cpu {
|
||||
|
||||
struct RDP {
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
} // natsukashii
|
||||
11
src/core/n64/core/cpu/CMakeLists.txt
Normal file
11
src/core/n64/core/cpu/CMakeLists.txt
Normal file
@@ -0,0 +1,11 @@
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
project(n64-cpu)
|
||||
|
||||
add_library(n64-cpu
|
||||
Registers.cpp
|
||||
Registers.hpp
|
||||
registers/Cop0.cpp
|
||||
registers/Cop0.hpp)
|
||||
|
||||
target_include_directories(n64-cpu PRIVATE . .. ../../ ../../../)
|
||||
target_include_directories(n64-cpu PUBLIC registers ../../../../../external)
|
||||
1
src/core/n64/core/cpu/Registers.cpp
Normal file
1
src/core/n64/core/cpu/Registers.cpp
Normal file
@@ -0,0 +1 @@
|
||||
#include <Registers.hpp>
|
||||
9
src/core/n64/core/cpu/Registers.hpp
Normal file
9
src/core/n64/core/cpu/Registers.hpp
Normal file
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
#include <Cop0.hpp>
|
||||
|
||||
namespace natsukashii::n64::core {
|
||||
struct Registers {
|
||||
s64 gpr[32];
|
||||
Cop0 cop0;
|
||||
};
|
||||
}
|
||||
17
src/core/n64/core/cpu/registers/Cop0.cpp
Normal file
17
src/core/n64/core/cpu/registers/Cop0.cpp
Normal file
@@ -0,0 +1,17 @@
|
||||
#include <Cop0.hpp>
|
||||
|
||||
namespace natsukashii::n64::core {
|
||||
Cop0::Cop0() {
|
||||
|
||||
}
|
||||
|
||||
template<class T>
|
||||
T Cop0::GetReg(u8 index) {
|
||||
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void Cop0::SetReg(u8 index, T val) {
|
||||
|
||||
}
|
||||
}
|
||||
168
src/core/n64/core/cpu/registers/Cop0.hpp
Normal file
168
src/core/n64/core/cpu/registers/Cop0.hpp
Normal file
@@ -0,0 +1,168 @@
|
||||
#pragma once
|
||||
#include <common.hpp>
|
||||
|
||||
namespace natsukashii::n64::core {
|
||||
#define STATUS_MASK 0xFF77FFFF
|
||||
|
||||
struct Cpu;
|
||||
struct Mem;
|
||||
|
||||
union Cop0Cause {
|
||||
u32 raw;
|
||||
struct {
|
||||
unsigned: 8;
|
||||
unsigned interruptPending: 8;
|
||||
unsigned: 16;
|
||||
} __attribute__((__packed__));
|
||||
struct {
|
||||
unsigned: 2;
|
||||
unsigned exceptionCode: 5;
|
||||
unsigned: 1;
|
||||
unsigned ip0: 1;
|
||||
unsigned ip1: 1;
|
||||
unsigned ip2: 1;
|
||||
unsigned ip3: 1;
|
||||
unsigned ip4: 1;
|
||||
unsigned ip5: 1;
|
||||
unsigned ip6: 1;
|
||||
unsigned ip7: 1;
|
||||
unsigned: 12;
|
||||
unsigned copError: 2;
|
||||
unsigned: 1;
|
||||
unsigned branchDelay: 1;
|
||||
} __attribute__((__packed__));
|
||||
};
|
||||
|
||||
union Cop0Status {
|
||||
struct {
|
||||
unsigned ie: 1;
|
||||
unsigned exl: 1;
|
||||
unsigned erl: 1;
|
||||
unsigned ksu: 2;
|
||||
unsigned ux: 1;
|
||||
unsigned sx: 1;
|
||||
unsigned kx: 1;
|
||||
unsigned im: 8;
|
||||
unsigned ds: 9;
|
||||
unsigned re: 1;
|
||||
unsigned fr: 1;
|
||||
unsigned rp: 1;
|
||||
unsigned cu0: 1;
|
||||
unsigned cu1: 1;
|
||||
unsigned cu2: 1;
|
||||
unsigned cu3: 1;
|
||||
} __attribute__((__packed__));
|
||||
struct {
|
||||
unsigned: 16;
|
||||
unsigned de: 1;
|
||||
unsigned ce: 1;
|
||||
unsigned ch: 1;
|
||||
unsigned: 1;
|
||||
unsigned sr: 1;
|
||||
unsigned ts: 1;
|
||||
unsigned bev: 1;
|
||||
unsigned: 1;
|
||||
unsigned its: 1;
|
||||
unsigned: 7;
|
||||
} __attribute__((__packed__));
|
||||
u32 raw;
|
||||
} __attribute__((__packed__));
|
||||
|
||||
union EntryLo {
|
||||
u32 raw;
|
||||
struct {
|
||||
unsigned g: 1;
|
||||
unsigned v: 1;
|
||||
unsigned d: 1;
|
||||
unsigned c: 3;
|
||||
unsigned pfn: 20;
|
||||
unsigned: 6;
|
||||
};
|
||||
};
|
||||
|
||||
union EntryHi {
|
||||
u64 raw;
|
||||
struct {
|
||||
u64 asid: 8;
|
||||
u64: 5;
|
||||
u64 vpn2: 27;
|
||||
u64 fill: 22;
|
||||
u64 r: 2;
|
||||
} __attribute__((__packed__));
|
||||
};
|
||||
|
||||
union PageMask {
|
||||
u32 raw;
|
||||
struct {
|
||||
unsigned: 13;
|
||||
unsigned mask: 12;
|
||||
unsigned: 7;
|
||||
};
|
||||
};
|
||||
|
||||
struct TLBEntry {
|
||||
EntryLo entryLo0, entryLo1;
|
||||
EntryHi entryHi;
|
||||
PageMask pageMask;
|
||||
};
|
||||
|
||||
enum TLBError : u8 {
|
||||
NONE,
|
||||
MISS,
|
||||
INVALID,
|
||||
MODIFICATION,
|
||||
DISALLOWED_ADDRESS
|
||||
};
|
||||
|
||||
enum TLBAccessType {
|
||||
LOAD, STORE
|
||||
};
|
||||
|
||||
union Context {
|
||||
u64 raw;
|
||||
struct {
|
||||
u64: 4;
|
||||
u64 badvpn2: 19;
|
||||
u64 ptebase: 41;
|
||||
};
|
||||
};
|
||||
|
||||
union XContext {
|
||||
u64 raw;
|
||||
struct {
|
||||
u64: 4;
|
||||
u64 badvpn2: 27;
|
||||
u64 r: 2;
|
||||
u64 ptebase: 31;
|
||||
} __attribute__((__packed__));
|
||||
};
|
||||
|
||||
struct Cop0 {
|
||||
Cop0();
|
||||
|
||||
template<class T>
|
||||
T GetReg(u8 index);
|
||||
|
||||
template<class T>
|
||||
void SetReg(u8 index, T val);
|
||||
|
||||
PageMask pageMask;
|
||||
EntryHi entryHi;
|
||||
EntryLo entryLo0, entryLo1;
|
||||
u32 index, random;
|
||||
Context context;
|
||||
u32 wired, r7;
|
||||
u64 badVaddr, count;
|
||||
u32 compare;
|
||||
Cop0Status status;
|
||||
Cop0Cause cause;
|
||||
u64 EPC;
|
||||
u32 PRId, Config, LLAddr, WatchLo, WatchHi;
|
||||
XContext xcontext;
|
||||
u32 r21, r22, r23, r24, r25, ParityError, CacheError, TagLo, TagHi;
|
||||
u64 ErrorEPC;
|
||||
u32 r31;
|
||||
TLBEntry tlb[32];
|
||||
TLBError tlbError;
|
||||
};
|
||||
}
|
||||
@@ -59,7 +59,7 @@ inline T ReadAccess(u8* data, u32 index) {
|
||||
static_assert(sizeof(T) != 2 && sizeof(T) != 4 && sizeof(T) != 8);
|
||||
T result = 0;
|
||||
memcpy(&result, &data[index], sizeof(T));
|
||||
return GetSwapFunc(result);
|
||||
return GetSwapFunc<T>(result);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
@@ -95,4 +95,16 @@ inline void SwapN64Rom(size_t size, u8* data) {
|
||||
panic("Unrecognized rom format! Make sure this is a valid Nintendo 64 ROM dump!\n");
|
||||
}
|
||||
}
|
||||
|
||||
inline size_t NextPow2(size_t num) {
|
||||
// Taken from "Bit Twiddling Hacks" by Sean Anderson:
|
||||
// https://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
|
||||
--num;
|
||||
num |= num >> 1;
|
||||
num |= num >> 2;
|
||||
num |= num >> 4;
|
||||
num |= num >> 8;
|
||||
num |= num >> 16;
|
||||
return num + 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,5 +11,5 @@ find_package(Qt5 COMPONENTS Widgets REQUIRED)
|
||||
|
||||
add_executable(natsukashii-qt Frontend.hpp Frontend.cpp main.cpp)
|
||||
|
||||
target_include_directories(natsukashii-qt PRIVATE . ../../core ../../core/gb ../../core/n64 ../../../external)
|
||||
target_include_directories(natsukashii-qt PRIVATE . ../../core ../../core/gb ../../core/n64)
|
||||
target_link_libraries(natsukashii-qt PRIVATE cores Qt5::Widgets)
|
||||
|
||||
@@ -1,7 +1,112 @@
|
||||
#include <Frontend.hpp>
|
||||
#include <QPainter>
|
||||
|
||||
namespace natsukashii::frontend {
|
||||
App::~App() {}
|
||||
|
||||
App::App() {}
|
||||
QVulkanWindowRenderer *VulkanWindow::createRenderer() {
|
||||
return new VulkanRenderer(this);
|
||||
}
|
||||
|
||||
VulkanRenderer::VulkanRenderer(QVulkanWindow *w)
|
||||
: m_window(w) { }
|
||||
|
||||
void VulkanRenderer::initResources() {
|
||||
m_devFuncs = m_window->vulkanInstance()->deviceFunctions(m_window->device());
|
||||
}
|
||||
|
||||
void VulkanRenderer::startNextFrame() {
|
||||
VkClearColorValue clearColor = {{ 0.0f, 0.0f, 0.0f, 1.0f }};
|
||||
VkClearDepthStencilValue clearDS = { 1.0f, 0 };
|
||||
VkClearValue clearValues[2];
|
||||
memset(clearValues, 0, sizeof(clearValues));
|
||||
clearValues[0].color = clearColor;
|
||||
clearValues[1].depthStencil = clearDS;
|
||||
|
||||
VkRenderPassBeginInfo rpBeginInfo;
|
||||
memset(&rpBeginInfo, 0, sizeof(rpBeginInfo));
|
||||
rpBeginInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
|
||||
rpBeginInfo.renderPass = m_window->defaultRenderPass();
|
||||
rpBeginInfo.framebuffer = m_window->currentFramebuffer();
|
||||
const QSize sz = m_window->swapChainImageSize();
|
||||
rpBeginInfo.renderArea.extent.width = sz.width();
|
||||
rpBeginInfo.renderArea.extent.height = sz.height();
|
||||
rpBeginInfo.clearValueCount = 2;
|
||||
rpBeginInfo.pClearValues = clearValues;
|
||||
VkCommandBuffer cmdBuf = m_window->currentCommandBuffer();
|
||||
m_devFuncs->vkCmdBeginRenderPass(cmdBuf, &rpBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
|
||||
|
||||
// do thing
|
||||
|
||||
m_devFuncs->vkCmdEndRenderPass(cmdBuf);
|
||||
|
||||
m_window->frameReady();
|
||||
m_window->requestUpdate(); // render continuously, throttled by the presentation rate
|
||||
}
|
||||
|
||||
OpenGLWindow::OpenGLWindow(QWindow *parent) : QWindow(parent) {
|
||||
setSurfaceType(QWindow::OpenGLSurface);
|
||||
}
|
||||
|
||||
void OpenGLWindow::render(QPainter *painter) {
|
||||
Q_UNUSED(painter);
|
||||
}
|
||||
|
||||
void OpenGLWindow::initialize() {}
|
||||
|
||||
void OpenGLWindow::render() {
|
||||
if (!m_device)
|
||||
m_device = new QOpenGLPaintDevice;
|
||||
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
|
||||
|
||||
m_device->setSize(size() * devicePixelRatio());
|
||||
m_device->setDevicePixelRatio(devicePixelRatio());
|
||||
|
||||
QPainter painter(m_device);
|
||||
render(&painter);
|
||||
}
|
||||
|
||||
bool OpenGLWindow::event(QEvent *event) {
|
||||
switch (event->type()) {
|
||||
case QEvent::UpdateRequest:
|
||||
renderNow();
|
||||
return true;
|
||||
default:
|
||||
return QWindow::event(event);
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLWindow::exposeEvent(QExposeEvent *event) {
|
||||
Q_UNUSED(event);
|
||||
|
||||
if (isExposed())
|
||||
renderNow();
|
||||
}
|
||||
|
||||
void OpenGLWindow::renderNow() {
|
||||
if (!isExposed())
|
||||
return;
|
||||
|
||||
bool needsInitialize = false;
|
||||
|
||||
if (!m_context) {
|
||||
m_context = new QOpenGLContext(this);
|
||||
m_context->setFormat(requestedFormat());
|
||||
m_context->create();
|
||||
|
||||
needsInitialize = true;
|
||||
}
|
||||
|
||||
m_context->makeCurrent(this);
|
||||
|
||||
if (needsInitialize) {
|
||||
initializeOpenGLFunctions();
|
||||
initialize();
|
||||
}
|
||||
|
||||
render();
|
||||
|
||||
m_context->swapBuffers(this);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,8 +1,63 @@
|
||||
#pragma once
|
||||
#include <QVulkanWindow>
|
||||
#include <QVulkanWindowRenderer>
|
||||
#include <QVulkanDeviceFunctions>
|
||||
#include <QOpenGLWindow>
|
||||
#include <QOpenGLFunctions>
|
||||
#include <QOpenGLPaintDevice>
|
||||
|
||||
namespace natsukashii::frontend {
|
||||
struct App {
|
||||
~App();
|
||||
App();
|
||||
void Run() {}
|
||||
class VulkanRenderer : public QVulkanWindowRenderer {
|
||||
public:
|
||||
VulkanRenderer(QVulkanWindow *w);
|
||||
|
||||
void initResources() override;
|
||||
void initSwapChainResources() override;
|
||||
void releaseSwapChainResources() override;
|
||||
void releaseResources() override;
|
||||
|
||||
void startNextFrame() override;
|
||||
|
||||
private:
|
||||
QVulkanWindow *m_window;
|
||||
QVulkanDeviceFunctions *m_devFuncs;
|
||||
};
|
||||
|
||||
class VulkanWindow : public QVulkanWindow {
|
||||
public:
|
||||
QVulkanWindowRenderer *createRenderer() override;
|
||||
};
|
||||
|
||||
class OpenGLWindow : protected QOpenGLFunctions, public QWindow {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit OpenGLWindow(QWindow *parent = nullptr);
|
||||
~OpenGLWindow();
|
||||
|
||||
void render(QPainter *painter);
|
||||
void render();
|
||||
|
||||
void initialize();
|
||||
|
||||
void setAnimating(bool animating);
|
||||
|
||||
public slots:
|
||||
void renderNow();
|
||||
|
||||
protected:
|
||||
bool event(QEvent *event) override;
|
||||
void exposeEvent(QExposeEvent *event) override;
|
||||
|
||||
private:
|
||||
QOpenGLContext *m_context = nullptr;
|
||||
QOpenGLPaintDevice *m_device = nullptr;
|
||||
};
|
||||
|
||||
class Window : public QWindow {
|
||||
public:
|
||||
Window() {}
|
||||
private:
|
||||
VulkanWindow vulkanWindow;
|
||||
OpenGLWindow openGlWindow;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
#include <Frontend.hpp>
|
||||
#include <util.hpp>
|
||||
#include <QGuiApplication>
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
natsukashii::frontend::App app;
|
||||
app.Run();
|
||||
return 0;
|
||||
QGuiApplication app(argc, argv);
|
||||
natsukashii::frontend::Window window;
|
||||
window.show();
|
||||
|
||||
return app.exec();
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
find_package(SDL2 REQUIRED)
|
||||
|
||||
add_executable(natsukashii-sdl Frontend.cpp Frontend.hpp main.cpp)
|
||||
add_executable(natsukashii-sdl Frontend.cpp Frontend.hpp main.cpp ParallelRDPWrapper.cpp ParallelRDPWrapper.hpp)
|
||||
|
||||
include(FetchContent)
|
||||
FetchContent_Declare(
|
||||
@@ -15,5 +15,7 @@ FetchContent_Declare(
|
||||
)
|
||||
FetchContent_MakeAvailable(argparse)
|
||||
|
||||
target_include_directories(natsukashii-sdl PRIVATE . ../../core ../../core/gb ../../core/n64 ../../../external)
|
||||
target_link_libraries(natsukashii-sdl PRIVATE cores argparse::argparse SDL2)
|
||||
add_subdirectory(../../../external temp)
|
||||
|
||||
target_include_directories(natsukashii-sdl PRIVATE . ../../core ../../core/gb ../../core/n64 ../../core/n64/core ../../core/n64/core/cpu/registers)
|
||||
target_link_libraries(natsukashii-sdl PRIVATE cores argparse::argparse SDL2 parallel-rdp)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <Frontend.hpp>
|
||||
#include <gb/Core.hpp>
|
||||
#include <n64/Core.hpp>
|
||||
#include <volk.h>
|
||||
|
||||
namespace natsukashii::frontend {
|
||||
using namespace natsukashii;
|
||||
@@ -11,14 +12,20 @@ App::~App() {
|
||||
}
|
||||
|
||||
App::App(const std::string& rom, const std::string& selectedCore) {
|
||||
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO);
|
||||
window = SDL_CreateWindow("natukashii", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 800, 600, SDL_WINDOW_SHOWN);
|
||||
renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
|
||||
id = SDL_GetWindowID(window);
|
||||
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_VIDEO_VULKAN);
|
||||
|
||||
if(selectedCore == "gb") {
|
||||
window = SDL_CreateWindow("natukashii", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 800, 600, SDL_WINDOW_RESIZABLE);
|
||||
renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
|
||||
SDL_RenderSetLogicalSize(renderer, 160, 144);
|
||||
id = SDL_GetWindowID(window);
|
||||
core = std::make_unique<gb::core::Core>(rom);
|
||||
} else if(selectedCore == "n64") {
|
||||
window = SDL_CreateWindow("natukashii", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 800, 600, SDL_WINDOW_RESIZABLE | SDL_WINDOW_VULKAN);
|
||||
id = SDL_GetWindowID(window);
|
||||
if(volkInitialize() != VK_SUCCESS) {
|
||||
util::panic("Failed to initialize Volk\n");
|
||||
}
|
||||
core = std::make_unique<n64::core::Core>(rom);
|
||||
} else {
|
||||
util::panic("Unimplemented core!");
|
||||
@@ -26,12 +33,9 @@ App::App(const std::string& rom, const std::string& selectedCore) {
|
||||
}
|
||||
|
||||
void App::Run() {
|
||||
while(!quit) {
|
||||
while(!core->ShouldQuit()) {
|
||||
core->Run();
|
||||
|
||||
SDL_Event event;
|
||||
SDL_PollEvent(&event);
|
||||
quit = event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_CLOSE && event.window.windowID == id;
|
||||
core->PollInputs(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
#define SDL_MAIN_HANDLED
|
||||
#include <SDL2/SDL.h>
|
||||
#include <SDL2/SDL_vulkan.h>
|
||||
#include <BaseCore.hpp>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
@@ -8,6 +9,7 @@
|
||||
|
||||
namespace natsukashii::frontend {
|
||||
using namespace natsukashii::core;
|
||||
|
||||
struct App {
|
||||
~App();
|
||||
App(const std::string&, const std::string&);
|
||||
@@ -15,8 +17,7 @@ struct App {
|
||||
private:
|
||||
SDL_Window *window = nullptr;
|
||||
SDL_Renderer *renderer = nullptr;
|
||||
Uint32 id;
|
||||
bool quit = false;
|
||||
u32 id;
|
||||
std::unique_ptr<BaseCore> core;
|
||||
};
|
||||
}
|
||||
|
||||
282
src/frontend/sdl/ParallelRDPWrapper.cpp
Normal file
282
src/frontend/sdl/ParallelRDPWrapper.cpp
Normal file
@@ -0,0 +1,282 @@
|
||||
#include <ParallelRDPWrapper.hpp>
|
||||
#include <RDP.hpp>
|
||||
#include <memory>
|
||||
#include <rdp_device.hpp>
|
||||
#include <SDL2/SDL_video.h>
|
||||
#include <SDL2/SDL_vulkan.h>
|
||||
#include <util.hpp>
|
||||
|
||||
using namespace Vulkan;
|
||||
using namespace natsukashii;
|
||||
using std::unique_ptr;
|
||||
using RDP::CommandProcessor;
|
||||
using RDP::CommandProcessorFlags;
|
||||
using RDP::VIRegister;
|
||||
|
||||
static CommandProcessor* command_processor;
|
||||
static WSI* wsi;
|
||||
|
||||
std::vector<Semaphore> acquire_semaphore;
|
||||
|
||||
VkQueue GetGraphicsQueue() {
|
||||
return wsi->get_context().get_graphics_queue();
|
||||
}
|
||||
|
||||
VkInstance GetVkInstance() {
|
||||
return wsi->get_context().get_instance();
|
||||
}
|
||||
|
||||
VkPhysicalDevice GetVkPhysicalDevice() {
|
||||
return wsi->get_device().get_physical_device();
|
||||
}
|
||||
|
||||
VkDevice GetVkDevice() {
|
||||
return wsi->get_device().get_device();
|
||||
}
|
||||
|
||||
uint32_t GetVkGraphicsQueueFamily() {
|
||||
return wsi->get_context().get_graphics_queue_family();
|
||||
}
|
||||
|
||||
VkFormat GetVkFormat() {
|
||||
return wsi->get_device().get_swapchain_view().get_format();
|
||||
}
|
||||
|
||||
CommandBufferHandle requested_command_buffer;
|
||||
|
||||
VkCommandBuffer GetVkCommandBuffer() {
|
||||
requested_command_buffer = wsi->get_device().request_command_buffer();
|
||||
return requested_command_buffer->get_command_buffer();
|
||||
}
|
||||
|
||||
void SubmitRequestedVkCommandBuffer() {
|
||||
wsi->get_device().submit(requested_command_buffer);
|
||||
}
|
||||
|
||||
bool IsFramerateUnlocked() {
|
||||
return wsi->get_present_mode() != PresentMode::SyncToVBlank;
|
||||
}
|
||||
|
||||
void SetFramerateUnlocked(bool unlocked) {
|
||||
if (unlocked) {
|
||||
wsi->set_present_mode(PresentMode::UnlockedForceTearing);
|
||||
} else {
|
||||
wsi->set_present_mode(PresentMode::SyncToVBlank);
|
||||
}
|
||||
}
|
||||
|
||||
class SDLWSIPlatform : public Vulkan::WSIPlatform {
|
||||
public:
|
||||
SDLWSIPlatform() = default;
|
||||
|
||||
std::vector<const char *> get_instance_extensions() override {
|
||||
const char* extensions[64];
|
||||
unsigned int num_extensions = 64;
|
||||
|
||||
if (!SDL_Vulkan_GetInstanceExtensions(window, &num_extensions, extensions)) {
|
||||
util::panic("SDL_Vulkan_GetInstanceExtensions failed: %s", SDL_GetError());
|
||||
}
|
||||
auto vec = std::vector<const char*>();
|
||||
|
||||
for (unsigned int i = 0; i < num_extensions; i++) {
|
||||
vec.push_back(extensions[i]);
|
||||
}
|
||||
|
||||
return vec;
|
||||
}
|
||||
|
||||
VkSurfaceKHR create_surface(VkInstance instance, VkPhysicalDevice gpu) override {
|
||||
VkSurfaceKHR vk_surface;
|
||||
if (!SDL_Vulkan_CreateSurface(window, instance, &vk_surface)) {
|
||||
util::panic("Failed to create Vulkan window surface: %s", SDL_GetError());
|
||||
}
|
||||
return vk_surface;
|
||||
}
|
||||
|
||||
uint32_t get_surface_width() override {
|
||||
return N64_SCREEN_X * SCREEN_SCALE;
|
||||
}
|
||||
|
||||
uint32_t get_surface_height() override {
|
||||
return N64_SCREEN_Y * SCREEN_SCALE;
|
||||
}
|
||||
|
||||
bool alive(Vulkan::WSI &wsi) override {
|
||||
return true;
|
||||
}
|
||||
|
||||
void poll_input() override {
|
||||
n64_poll_input();
|
||||
}
|
||||
|
||||
void event_frame_tick(double frame, double elapsed) override {
|
||||
n64_render_screen();
|
||||
}
|
||||
};
|
||||
|
||||
Program* fullscreen_quad_program;
|
||||
|
||||
void LoadParallelRDP(const u8* rdram) {
|
||||
wsi = new WSI();
|
||||
wsi->set_backbuffer_srgb(false);
|
||||
wsi->set_platform(new SDLWSIPlatform());
|
||||
Context::SystemHandles handles;
|
||||
if (!wsi->init(1, handles)) {
|
||||
util::panic("Failed to initialize WSI!");
|
||||
}
|
||||
|
||||
ResourceLayout vert_layout;
|
||||
ResourceLayout frag_layout;
|
||||
|
||||
vert_layout.input_mask = 1;
|
||||
vert_layout.output_mask = 1;
|
||||
|
||||
frag_layout.input_mask = 1;
|
||||
frag_layout.output_mask = 1;
|
||||
frag_layout.spec_constant_mask = 1;
|
||||
frag_layout.push_constant_size = 4 * sizeof(float);
|
||||
|
||||
frag_layout.sets[0].sampled_image_mask = 1;
|
||||
frag_layout.sets[0].fp_mask = 1;
|
||||
frag_layout.sets[0].array_size[0] = 1;
|
||||
|
||||
fullscreen_quad_program = wsi->get_device().request_program(fullscreen_quad_vert, sizeof(fullscreen_quad_vert), fullscreen_quad_frag, sizeof(fullscreen_quad_frag), &vert_layout, &frag_layout);
|
||||
|
||||
auto aligned_rdram = reinterpret_cast<uintptr_t>(rdram);
|
||||
uintptr_t offset = 0;
|
||||
|
||||
if (wsi->get_device().get_device_features().supports_external_memory_host)
|
||||
{
|
||||
size_t align = wsi->get_device().get_device_features().host_memory_properties.minImportedHostPointerAlignment;
|
||||
offset = aligned_rdram & (align - 1);
|
||||
aligned_rdram -= offset;
|
||||
}
|
||||
|
||||
CommandProcessorFlags flags = 1 << 1; // TODO configurable scaling
|
||||
|
||||
command_processor = new CommandProcessor(wsi->get_device(), reinterpret_cast<void *>(aligned_rdram),
|
||||
offset, 8 * 1024 * 1024, 4 * 1024 * 1024, flags);
|
||||
|
||||
if (!command_processor->device_is_supported()) {
|
||||
util::panic("This device probably does not support 8/16-bit storage. Make sure you're using up-to-date drivers!");
|
||||
}
|
||||
}
|
||||
|
||||
void draw_fullscreen_textured_quad(Util::IntrusivePtr<Image> image, Util::IntrusivePtr<CommandBuffer> cmd) {
|
||||
cmd->set_texture(0, 0, image->get_view(), Vulkan::StockSampler::LinearClamp);
|
||||
cmd->set_program(fullscreen_quad_program);
|
||||
cmd->set_quad_state();
|
||||
auto data = static_cast<float*>(cmd->allocate_vertex_data(0, 6 * sizeof(float), 2 * sizeof(float)));
|
||||
*data++ = -1.0f;
|
||||
*data++ = -3.0f;
|
||||
*data++ = -1.0f;
|
||||
*data++ = +1.0f;
|
||||
*data++ = +3.0f;
|
||||
*data++ = +1.0f;
|
||||
|
||||
int sdlWinWidth, sdlWinHeight;
|
||||
SDL_GetWindowSize(window, &sdlWinWidth, &sdlWinHeight);
|
||||
|
||||
float zoom = std::min(
|
||||
(float)sdlWinWidth / wsi->get_platform().get_surface_width(),
|
||||
(float)sdlWinHeight / wsi->get_platform().get_surface_height());
|
||||
|
||||
float width = (wsi->get_platform().get_surface_width() / (float)sdlWinWidth) * zoom;
|
||||
float height = (wsi->get_platform().get_surface_height() / (float)sdlWinHeight) * zoom;
|
||||
|
||||
float uniform_data[] = {
|
||||
// Size
|
||||
width, height,
|
||||
// Offset
|
||||
(1.0f - width) * 0.5f,
|
||||
(1.0f - height) * 0.5f};
|
||||
|
||||
cmd->push_constants(uniform_data, 0, sizeof(uniform_data));
|
||||
|
||||
cmd->set_vertex_attrib(0, 0, VK_FORMAT_R32G32_SFLOAT, 0);
|
||||
cmd->set_depth_test(false, false);
|
||||
cmd->set_depth_compare(VK_COMPARE_OP_ALWAYS);
|
||||
cmd->set_primitive_topology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST);
|
||||
cmd->draw(3, 1);
|
||||
}
|
||||
|
||||
void update_screen(Util::IntrusivePtr<Image> image) {
|
||||
wsi->begin_frame();
|
||||
|
||||
if (!image) {
|
||||
auto info = Vulkan::ImageCreateInfo::immutable_2d_image(N64_SCREEN_X * SCREEN_SCALE, N64_SCREEN_Y * SCREEN_SCALE, VK_FORMAT_R8G8B8A8_UNORM);
|
||||
info.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT |
|
||||
VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
||||
info.misc = IMAGE_MISC_MUTABLE_SRGB_BIT;
|
||||
info.initial_layout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
image = wsi->get_device().create_image(info);
|
||||
|
||||
auto cmd = wsi->get_device().request_command_buffer();
|
||||
|
||||
cmd->image_barrier(*image,
|
||||
VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0,
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT);
|
||||
cmd->clear_image(*image, {});
|
||||
cmd->image_barrier(*image,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, VK_ACCESS_SHADER_READ_BIT);
|
||||
wsi->get_device().submit(cmd);
|
||||
}
|
||||
|
||||
Util::IntrusivePtr<CommandBuffer> cmd = wsi->get_device().request_command_buffer();
|
||||
|
||||
cmd->begin_render_pass(wsi->get_device().get_swapchain_render_pass(SwapchainRenderPass::ColorOnly));
|
||||
draw_fullscreen_textured_quad(image, cmd);
|
||||
ImGui_ImplVulkan_RenderDrawData(imgui_frame(), cmd->get_command_buffer());
|
||||
cmd->end_render_pass();
|
||||
wsi->get_device().submit(cmd);
|
||||
wsi->end_frame();
|
||||
}
|
||||
|
||||
void update_screen_parallel_rdp() {
|
||||
if (unlikely(!command_processor)) {
|
||||
logfatal("Update screen without an initialized command processor");
|
||||
}
|
||||
|
||||
command_processor->set_vi_register(VIRegister::Control, n64sys.vi.status.raw);
|
||||
command_processor->set_vi_register(VIRegister::Origin, n64sys.vi.vi_origin);
|
||||
command_processor->set_vi_register(VIRegister::Width, n64sys.vi.vi_width);
|
||||
command_processor->set_vi_register(VIRegister::Intr, n64sys.vi.vi_v_intr);
|
||||
command_processor->set_vi_register(VIRegister::VCurrentLine, n64sys.vi.v_current);
|
||||
command_processor->set_vi_register(VIRegister::Timing, n64sys.vi.vi_burst.raw);
|
||||
command_processor->set_vi_register(VIRegister::VSync, n64sys.vi.vsync);
|
||||
command_processor->set_vi_register(VIRegister::HSync, n64sys.vi.hsync);
|
||||
command_processor->set_vi_register(VIRegister::Leap, n64sys.vi.leap);
|
||||
command_processor->set_vi_register(VIRegister::HStart, n64sys.vi.hstart.raw);
|
||||
command_processor->set_vi_register(VIRegister::VStart, n64sys.vi.vstart.raw);
|
||||
command_processor->set_vi_register(VIRegister::VBurst, n64sys.vi.vburst);
|
||||
command_processor->set_vi_register(VIRegister::XScale, n64sys.vi.xscale.raw);
|
||||
command_processor->set_vi_register(VIRegister::YScale, n64sys.vi.yscale.raw);
|
||||
|
||||
RDP::ScanoutOptions opts;
|
||||
opts.persist_frame_on_invalid_input = true;
|
||||
opts.vi.aa = true;
|
||||
opts.vi.scale = true;
|
||||
opts.vi.dither_filter = true;
|
||||
opts.vi.divot_filter = true;
|
||||
opts.vi.gamma_dither = true;
|
||||
opts.downscale_steps = true;
|
||||
opts.crop_overscan_pixels = true;
|
||||
Util::IntrusivePtr<Image> image = command_processor->scanout(opts);
|
||||
update_screen(image);
|
||||
command_processor->begin_frame_context();
|
||||
}
|
||||
|
||||
void update_screen_parallel_rdp_no_game() {
|
||||
update_screen(static_cast<Util::IntrusivePtr<Image>>(nullptr));
|
||||
}
|
||||
|
||||
void parallel_rdp_enqueue_command(int command_length, word* buffer) {
|
||||
command_processor->enqueue_command(command_length, buffer);
|
||||
}
|
||||
|
||||
void parallel_rdp_on_full_sync() {
|
||||
command_processor->wait_for_timeline(command_processor->signal_timeline());
|
||||
}
|
||||
19
src/frontend/sdl/ParallelRDPWrapper.hpp
Normal file
19
src/frontend/sdl/ParallelRDPWrapper.hpp
Normal file
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
#include <n64/Core.hpp>
|
||||
#include <wsi.hpp>
|
||||
|
||||
VkQueue GetGraphicsQueue();
|
||||
VkInstance GetVkInstance();
|
||||
VkPhysicalDevice GetVkPhysicalDevice();
|
||||
VkDevice GetVkDevice();
|
||||
uint32_t GetVkGraphicsQueueFamily();
|
||||
VkFormat GetVkFormat();
|
||||
VkCommandBuffer GetVkCommandBuffer();
|
||||
void SubmitRequestedVkCommandBuffer();
|
||||
void LoadParallelRdp();
|
||||
void UpdateScreenParallelRdp();
|
||||
void ParallelRdpEnqueueCommand(int command_length, u32* buffer);
|
||||
void ParallelRdpOnFullSync();
|
||||
void UpdateScreenParallelRdpNoGame();
|
||||
bool IsFramerateUnlocked();
|
||||
void SetFramerateUnlocked(bool unlocked);
|
||||
Reference in New Issue
Block a user