Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Qt: Add live ubershader editor #533

Merged
merged 2 commits into from
Jul 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -457,11 +457,11 @@ if(NOT BUILD_HYDRA_CORE AND NOT BUILD_LIBRETRO_CORE)

set(FRONTEND_SOURCE_FILES src/panda_qt/main.cpp src/panda_qt/screen.cpp src/panda_qt/main_window.cpp src/panda_qt/about_window.cpp
src/panda_qt/config_window.cpp src/panda_qt/zep.cpp src/panda_qt/text_editor.cpp src/panda_qt/cheats_window.cpp src/panda_qt/mappings.cpp
src/panda_qt/patch_window.cpp src/panda_qt/elided_label.cpp
src/panda_qt/patch_window.cpp src/panda_qt/elided_label.cpp src/panda_qt/shader_editor.cpp
)
set(FRONTEND_HEADER_FILES include/panda_qt/screen.hpp include/panda_qt/main_window.hpp include/panda_qt/about_window.hpp
include/panda_qt/config_window.hpp include/panda_qt/text_editor.hpp include/panda_qt/cheats_window.hpp
include/panda_qt/patch_window.hpp include/panda_qt/elided_label.hpp
include/panda_qt/patch_window.hpp include/panda_qt/elided_label.hpp include/panda_qt/shader_editor.hpp
)

source_group("Source Files\\Qt" FILES ${FRONTEND_SOURCE_FILES})
Expand Down
7 changes: 4 additions & 3 deletions include/panda_qt/main_window.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "panda_qt/config_window.hpp"
#include "panda_qt/patch_window.hpp"
#include "panda_qt/screen.hpp"
#include "panda_qt/shader_editor.hpp"
#include "panda_qt/text_editor.hpp"
#include "services/hid.hpp"

Expand Down Expand Up @@ -48,6 +49,7 @@ class MainWindow : public QMainWindow {
EditCheat,
PressTouchscreen,
ReleaseTouchscreen,
ReloadUbershader,
};

// Tagged union representing our message queue messages
Expand Down Expand Up @@ -99,6 +101,7 @@ class MainWindow : public QMainWindow {
CheatsWindow* cheatsEditor;
TextEditorWindow* luaEditor;
PatchWindow* patchWindow;
ShaderEditorWindow* shaderEditor;

// We use SDL's game controller API since it's the sanest API that supports as many controllers as possible
SDL_GameController* gameController = nullptr;
Expand All @@ -110,9 +113,6 @@ class MainWindow : public QMainWindow {
void selectROM();
void dumpDspFirmware();
void dumpRomFS();
void openLuaEditor();
void openCheatsEditor();
void openPatchWindow();
void showAboutMenu();
void initControllers();
void pollControllers();
Expand All @@ -139,5 +139,6 @@ class MainWindow : public QMainWindow {
void mouseReleaseEvent(QMouseEvent* event) override;

void loadLuaScript(const std::string& code);
void reloadShader(const std::string& shader);
void editCheat(u32 handle, const std::vector<uint8_t>& cheat, const std::function<void(u32)>& callback);
};
27 changes: 27 additions & 0 deletions include/panda_qt/shader_editor.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#pragma once

#include <QApplication>
#include <QDialog>
#include <QWidget>
#include <string>

#include "zep.h"
#include "zep/mode_repl.h"
#include "zep/regress.h"

class ShaderEditorWindow : public QDialog {
Q_OBJECT

private:
Zep::ZepWidget_Qt zepWidget;
Zep::IZepReplProvider replProvider;
static constexpr float fontSize = 14.0f;

public:
// Whether this backend supports shader editor
bool supported = true;

ShaderEditorWindow(QWidget* parent, const std::string& filename, const std::string& initialText);
void setText(const std::string& text) { zepWidget.GetEditor().GetMRUBuffer()->SetText(text); }
void setEnable(bool enable);
};
8 changes: 8 additions & 0 deletions include/renderer.hpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#pragma once
#include <array>
#include <span>
#include <string>
#include <optional>

#include "PICA/pica_vertex.hpp"
Expand Down Expand Up @@ -66,6 +67,13 @@ class Renderer {
// This function does things like write back or cache necessary state before we delete our context
virtual void deinitGraphicsContext() = 0;

// Functions for hooking up the renderer core to the frontend's shader editor for editing ubershaders in real time
// SupportsShaderReload: Indicates whether the backend offers ubershader reload support or not
// GetUbershader/SetUbershader: Gets or sets the renderer's current ubershader
virtual bool supportsShaderReload() { return false; }
virtual std::string getUbershader() { return ""; }
virtual void setUbershader(const std::string& shader) {}

// Functions for initializing the graphics context for the Qt frontend, where we don't have the convenience of SDL_Window
#ifdef PANDA3DS_FRONTEND_QT
virtual void initGraphicsContext(GL::Context* context) { Helpers::panic("Tried to initialize incompatible renderer with GL context"); }
Expand Down
7 changes: 6 additions & 1 deletion include/renderer_gl/renderer_gl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,12 +82,17 @@ class RendererGL final : public Renderer {
void textureCopy(u32 inputAddr, u32 outputAddr, u32 totalBytes, u32 inputSize, u32 outputSize, u32 flags) override;
void drawVertices(PICA::PrimType primType, std::span<const PICA::Vertex> vertices) override; // Draw the given vertices
void deinitGraphicsContext() override;


virtual bool supportsShaderReload() override { return true; }
virtual std::string getUbershader() override;
virtual void setUbershader(const std::string& shader) override;

std::optional<ColourBuffer> getColourBuffer(u32 addr, PICA::ColorFmt format, u32 width, u32 height, bool createIfnotFound = true);

// Note: The caller is responsible for deleting the currently bound FBO before calling this
void setFBO(uint handle) { screenFramebuffer.m_handle = handle; }
void resetStateManager() { gl.reset(); }
void initUbershader(OpenGL::Program& program);

#ifdef PANDA3DS_FRONTEND_QT
virtual void initGraphicsContext([[maybe_unused]] GL::Context* context) override { initGraphicsContextInternal(); }
Expand Down
58 changes: 40 additions & 18 deletions src/core/renderer_gl/renderer_gl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,24 +57,7 @@ void RendererGL::initGraphicsContextInternal() {
OpenGL::Shader vert({vertexShaderSource.begin(), vertexShaderSource.size()}, OpenGL::Vertex);
OpenGL::Shader frag({fragmentShaderSource.begin(), fragmentShaderSource.size()}, OpenGL::Fragment);
triangleProgram.create({vert, frag});
gl.useProgram(triangleProgram);

textureEnvSourceLoc = OpenGL::uniformLocation(triangleProgram, "u_textureEnvSource");
textureEnvOperandLoc = OpenGL::uniformLocation(triangleProgram, "u_textureEnvOperand");
textureEnvCombinerLoc = OpenGL::uniformLocation(triangleProgram, "u_textureEnvCombiner");
textureEnvColorLoc = OpenGL::uniformLocation(triangleProgram, "u_textureEnvColor");
textureEnvScaleLoc = OpenGL::uniformLocation(triangleProgram, "u_textureEnvScale");

depthScaleLoc = OpenGL::uniformLocation(triangleProgram, "u_depthScale");
depthOffsetLoc = OpenGL::uniformLocation(triangleProgram, "u_depthOffset");
depthmapEnableLoc = OpenGL::uniformLocation(triangleProgram, "u_depthmapEnable");
picaRegLoc = OpenGL::uniformLocation(triangleProgram, "u_picaRegs");

// Init sampler objects. Texture 0 goes in texture unit 0, texture 1 in TU 1, texture 2 in TU 2, and the light maps go in TU 3
glUniform1i(OpenGL::uniformLocation(triangleProgram, "u_tex0"), 0);
glUniform1i(OpenGL::uniformLocation(triangleProgram, "u_tex1"), 1);
glUniform1i(OpenGL::uniformLocation(triangleProgram, "u_tex2"), 2);
glUniform1i(OpenGL::uniformLocation(triangleProgram, "u_tex_lighting_lut"), 3);
initUbershader(triangleProgram);

auto displayVertexShaderSource = gl_resources.open("opengl_display.vert");
auto displayFragmentShaderSource = gl_resources.open("opengl_display.frag");
Expand Down Expand Up @@ -812,4 +795,43 @@ void RendererGL::deinitGraphicsContext() {
// All other GL objects should be invalidated automatically and be recreated by the next call to initGraphicsContext
// TODO: Make it so that depth and colour buffers get written back to 3DS memory
printf("RendererGL::DeinitGraphicsContext called\n");
}

std::string RendererGL::getUbershader() {
auto gl_resources = cmrc::RendererGL::get_filesystem();
auto fragmentShader = gl_resources.open("opengl_fragment_shader.frag");

return std::string(fragmentShader.begin(), fragmentShader.end());
}

void RendererGL::setUbershader(const std::string& shader) {
auto gl_resources = cmrc::RendererGL::get_filesystem();
auto vertexShaderSource = gl_resources.open("opengl_vertex_shader.vert");

OpenGL::Shader vert({vertexShaderSource.begin(), vertexShaderSource.size()}, OpenGL::Vertex);
OpenGL::Shader frag(shader, OpenGL::Fragment);
triangleProgram.create({vert, frag});

initUbershader(triangleProgram);
}

void RendererGL::initUbershader(OpenGL::Program& program) {
gl.useProgram(program);

textureEnvSourceLoc = OpenGL::uniformLocation(program, "u_textureEnvSource");
textureEnvOperandLoc = OpenGL::uniformLocation(program, "u_textureEnvOperand");
textureEnvCombinerLoc = OpenGL::uniformLocation(program, "u_textureEnvCombiner");
textureEnvColorLoc = OpenGL::uniformLocation(program, "u_textureEnvColor");
textureEnvScaleLoc = OpenGL::uniformLocation(program, "u_textureEnvScale");

depthScaleLoc = OpenGL::uniformLocation(program, "u_depthScale");
depthOffsetLoc = OpenGL::uniformLocation(program, "u_depthOffset");
depthmapEnableLoc = OpenGL::uniformLocation(program, "u_depthmapEnable");
picaRegLoc = OpenGL::uniformLocation(program, "u_picaRegs");

// Init sampler objects. Texture 0 goes in texture unit 0, texture 1 in TU 1, texture 2 in TU 2, and the light maps go in TU 3
glUniform1i(OpenGL::uniformLocation(program, "u_tex0"), 0);
glUniform1i(OpenGL::uniformLocation(program, "u_tex1"), 1);
glUniform1i(OpenGL::uniformLocation(program, "u_tex2"), 2);
glUniform1i(OpenGL::uniformLocation(program, "u_tex_lighting_lut"), 3);
}
31 changes: 24 additions & 7 deletions src/panda_qt/main_window.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,14 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent)
auto luaEditorAction = toolsMenu->addAction(tr("Open Lua Editor"));
auto cheatsEditorAction = toolsMenu->addAction(tr("Open Cheats Editor"));
auto patchWindowAction = toolsMenu->addAction(tr("Open Patch Window"));
auto shaderEditorAction = toolsMenu->addAction(tr("Open Shader Editor"));
auto dumpDspFirmware = toolsMenu->addAction(tr("Dump loaded DSP firmware"));

connect(dumpRomFSAction, &QAction::triggered, this, &MainWindow::dumpRomFS);
connect(luaEditorAction, &QAction::triggered, this, &MainWindow::openLuaEditor);
connect(cheatsEditorAction, &QAction::triggered, this, &MainWindow::openCheatsEditor);
connect(patchWindowAction, &QAction::triggered, this, &MainWindow::openPatchWindow);
connect(luaEditorAction, &QAction::triggered, this, [this]() { luaEditor->show(); });
connect(shaderEditorAction, &QAction::triggered, this, [this]() { shaderEditor->show(); });
connect(cheatsEditorAction, &QAction::triggered, this, [this]() { cheatsEditor->show(); });
connect(patchWindowAction, &QAction::triggered, this, [this]() { patchWindow->show(); });
connect(dumpDspFirmware, &QAction::triggered, this, &MainWindow::dumpDspFirmware);

auto aboutAction = aboutMenu->addAction(tr("About Panda3DS"));
Expand All @@ -75,6 +77,12 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent)
cheatsEditor = new CheatsWindow(emu, {}, this);
patchWindow = new PatchWindow(this);
luaEditor = new TextEditorWindow(this, "script.lua", "");
shaderEditor = new ShaderEditorWindow(this, "shader.glsl", "");

shaderEditor->setEnable(emu->getRenderer()->supportsShaderReload());
if (shaderEditor->supported) {
shaderEditor->setText(emu->getRenderer()->getUbershader());
}

auto args = QCoreApplication::arguments();
if (args.size() > 1) {
Expand Down Expand Up @@ -294,10 +302,6 @@ void MainWindow::showAboutMenu() {
about.exec();
}

void MainWindow::openLuaEditor() { luaEditor->show(); }
void MainWindow::openCheatsEditor() { cheatsEditor->show(); }
void MainWindow::openPatchWindow() { patchWindow->show(); }

void MainWindow::dispatchMessage(const EmulatorMessage& message) {
switch (message.type) {
case MessageType::LoadROM:
Expand Down Expand Up @@ -351,6 +355,11 @@ void MainWindow::dispatchMessage(const EmulatorMessage& message) {
emu->getServiceManager().getHID().setTouchScreenPress(message.touchscreen.x, message.touchscreen.y);
break;
case MessageType::ReleaseTouchscreen: emu->getServiceManager().getHID().releaseTouchScreen(); break;

case MessageType::ReloadUbershader:
emu->getRenderer()->setUbershader(*message.string.str);
delete message.string.str;
break;
}
}

Expand Down Expand Up @@ -453,6 +462,14 @@ void MainWindow::loadLuaScript(const std::string& code) {
sendMessage(message);
}

void MainWindow::reloadShader(const std::string& shader) {
EmulatorMessage message{.type = MessageType::ReloadUbershader};

// Make a copy of the code on the heap to send via the message queue
message.string.str = new std::string(shader);
sendMessage(message);
}

void MainWindow::editCheat(u32 handle, const std::vector<uint8_t>& cheat, const std::function<void(u32)>& callback) {
EmulatorMessage message{.type = MessageType::EditCheat};

Expand Down
54 changes: 54 additions & 0 deletions src/panda_qt/shader_editor.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#include <QPushButton>
#include <QVBoxLayout>

#include "panda_qt/main_window.hpp"
#include "panda_qt/shader_editor.hpp"

using namespace Zep;

ShaderEditorWindow::ShaderEditorWindow(QWidget* parent, const std::string& filename, const std::string& initialText)
: QDialog(parent), zepWidget(this, qApp->applicationDirPath().toStdString(), fontSize) {
resize(600, 600);

// Register our extensions
ZepRegressExCommand::Register(zepWidget.GetEditor());
ZepReplExCommand::Register(zepWidget.GetEditor(), &replProvider);

// Default to standard mode instead of vim mode, initialize text box
zepWidget.GetEditor().InitWithText(filename, initialText);
zepWidget.GetEditor().SetGlobalMode(Zep::ZepMode_Standard::StaticName());

// Layout for widgets
QVBoxLayout* mainLayout = new QVBoxLayout();
setLayout(mainLayout);

QPushButton* button = new QPushButton(tr("Reload shader"), this);
button->setFixedSize(100, 20);

// When the Load Script button is pressed, send the current text to the MainWindow, which will upload it to the emulator's lua object
connect(button, &QPushButton::pressed, this, [this]() {
if (parentWidget()) {
auto buffer = zepWidget.GetEditor().GetMRUBuffer();
const std::string text = buffer->GetBufferText(buffer->Begin(), buffer->End());

static_cast<MainWindow*>(parentWidget())->reloadShader(text);
} else {
// This should be unreachable, only here for safety purposes
printf("Text editor does not have any parent widget, click doesn't work :(\n");
}
});

mainLayout->addWidget(button);
mainLayout->addWidget(&zepWidget);
}

void ShaderEditorWindow::setEnable(bool enable) {
supported = enable;

if (enable) {
setDisabled(false);
} else {
setDisabled(true);
setText("Shader editor window is not available for this renderer backend");
}
}
Loading