Next Attempt (2024)!

-Still lots todo! but I have a (mostly) working version up and running!
This commit is contained in:
NPO 2024-09-25 13:18:13 -04:00
parent 2eb6d44c2c
commit 2f19ab8bb5
13 changed files with 956 additions and 2 deletions

1
.gitignore vendored
View File

@ -1,4 +1,5 @@
build*/
debug*/
bin
obj
*.depend

View File

@ -54,6 +54,8 @@ set(SOURCES_QT_SDL
LANDialog.cpp
NetplayDialog.cpp
LuaMain.cpp
)
if (APPLE)
@ -84,6 +86,7 @@ endif()
pkg_check_modules(SDL2 REQUIRED IMPORTED_TARGET sdl2)
pkg_check_modules(LibArchive REQUIRED IMPORTED_TARGET libarchive)
pkg_check_modules(Zstd REQUIRED IMPORTED_TARGET libzstd)
pkg_search_module(Lua REQUIRED IMPORTED_TARGET lua-5.4 lua-5.3 lua>=5.3)
fix_interface_includes(PkgConfig::SDL2 PkgConfig::LibArchive)
@ -178,7 +181,7 @@ else()
target_include_directories(melonDS PUBLIC ${Qt5Gui_PRIVATE_INCLUDE_DIRS})
endif()
target_link_libraries(melonDS PRIVATE core)
target_link_libraries(melonDS PRIVATE PkgConfig::SDL2 PkgConfig::LibArchive PkgConfig::Zstd)
target_link_libraries(melonDS PRIVATE PkgConfig::SDL2 PkgConfig::LibArchive PkgConfig::Zstd PkgConfig::Lua)
target_link_libraries(melonDS PRIVATE ${QT_LINK_LIBS} ${CMAKE_DL_LIBS})
if (WIN32)

View File

@ -136,6 +136,7 @@ public:
int getJoystickID() { return joystickID; }
SDL_Joystick* getJoystick() { return joystick; }
std::vector<int> keyStrokes;
private:
static int lastSep(const std::string& path);
std::string getAssetPath(bool gba, const std::string& configpath, const std::string& ext, const std::string& file);
@ -205,6 +206,7 @@ private:
static void audioCallback(void* data, Uint8* stream, int len);
static void micCallback(void* data, Uint8* stream, int len);
void onKeyPress(QKeyEvent* event);
void onKeyRelease(QKeyEvent* event);
void keyReleaseAll();
@ -254,6 +256,8 @@ public:
bool doLimitFPS;
int maxFPS;
bool doAudioSync;
melonDS::u32 getInputMask(){return inputMask;}
private:
std::unique_ptr<melonDS::Savestate> backupState;

View File

@ -220,6 +220,7 @@ void EmuInstance::onKeyPress(QKeyEvent* event)
{
int keyHK = getEventKeyVal(event);
int keyKP = keyHK;
keyStrokes.push_back(keyHK);
if (event->modifiers() != Qt::KeypadModifier)
keyKP &= ~event->modifiers();

View File

@ -56,6 +56,8 @@
#include "EmuInstance.h"
#include "LuaMain.h"
using namespace melonDS;
@ -439,6 +441,11 @@ void EmuThread::run()
}
handleMessages();
//Lua Script Stuff (-for now happens at the end of each frame regardless of emuStatus)
LuaScript::createLuaState();//Create LuaState if needed
LuaScript::luaUpdate(); //"_Update()" gets called in current lua script
}
file = Platform::OpenLocalFile("rtc.bin", Platform::FileMode::Write);

View File

@ -107,6 +107,12 @@ public:
void deinitContext();
void updateVideoSettings() { videoSettingsDirty = true; }
void onLuaPrint(const QString&);
void onLuaClearConsole();
void onLuaLoadState(const QString&);
void onLuaSaveState(const QString&);
void onLuaLayoutChange();
int FrontBuffer = 0;
QMutex FrontBufferLock;
@ -130,6 +136,11 @@ signals:
void syncVolumeLevel();
void signalLuaPrint(const QString&);
void signalLuaClearConsole();
void signalLuaSaveState(const QString&);
void signalLuaLoadState(const QString&);
private:
void handleMessages();

View File

@ -0,0 +1,628 @@
#include "LuaMain.h"
#include <filesystem>
#include <QDir>
#include <QFileDialog>
#include <QPushButton>
#include <QGuiApplication>
#include <QScrollBar>
#include <QPainter>
#include "types.h"
#include "NDS.h"
#include <SDL_joystick.h>
#include <NDS_Header.h>
#include "main.h"
// #include "Input.h" --Need to fix keyboard inputs
void EmuThread::onLuaPrint(const QString& string)
{
emit signalLuaPrint(string);
}
void EmuThread::onLuaClearConsole()
{
emit signalLuaClearConsole();
}
void EmuThread::onLuaLoadState(const QString& string)
{
emit signalLuaLoadState(string);
}
void EmuThread::onLuaLayoutChange()
{
//TODO:
//emit screenLayoutChange();
}
void EmuThread::onLuaSaveState(const QString& string)
{
emit signalLuaSaveState(string);
}
namespace LuaScript
{
LuaConsoleDialog* LuaDialog=nullptr;
//Currently only supporting one EmuInstance
EmuInstance* CurrentInstance = nullptr;
EmuThread* CurrentThread = nullptr;
LuaConsoleDialog::LuaConsoleDialog(QWidget* parent) : QDialog(parent)
{
QWidget* w = parent;
//Yoinked from ScreenPanel in Screen.cpp
for (;;)
{
mainWindow = qobject_cast<MainWindow*>(w);
if (mainWindow) break;
w = w->parentWidget();
if (!w) break;
}
CurrentInstance = mainWindow->getEmuInstance();
CurrentThread = CurrentInstance->getEmuThread();
console = new LuaConsole(this);
console->setGeometry(0,20,302,80);
bar = console->verticalScrollBar();
buttonPausePlay = new QPushButton("Pause/UnPause",this);
buttonPausePlay->setGeometry(0,0,100,20);
buttonStartStop = new QPushButton("Stop",this);
buttonStartStop->setGeometry(101,0,100,20);
buttonOpenScript = new QPushButton("OpenLuaFile",this);
buttonOpenScript->setGeometry(202,0,100,20);
connect(buttonOpenScript,&QPushButton::clicked,this,&LuaConsoleDialog::onOpenScript);
connect(buttonStartStop,&QPushButton::clicked,this,&LuaConsoleDialog::onStop);
connect(buttonPausePlay,&QPushButton::clicked,this,&LuaConsoleDialog::onPausePlay);
connect(CurrentThread,&EmuThread::signalLuaPrint,console,&LuaConsole::onGetText);
connect(CurrentThread,&EmuThread::signalLuaClearConsole,console,&LuaConsole::onClear);
this->setWindowTitle("Lua Script");
}
void LuaConsoleDialog::closeEvent(QCloseEvent *event)
{
onStop();
LuaDialog = nullptr;
LuaOverlays.clear();
event->accept();
}
void LuaConsoleDialog::onOpenScript()
{
QFileInfo file = QFileInfo(QFileDialog::getOpenFileName(this, "Load Lua Script",QDir::currentPath()));
if (!file.exists())
return;
currentScript = file;
FlagNewLua = true;
}
LuaConsole::LuaConsole(QWidget* parent)
{
this->setParent(parent);
}
void LuaConsole::onGetText(const QString& string)
{
this->appendPlainText(string);
QScrollBar* bar = verticalScrollBar();
bar->setValue(bar->maximum());
}
void LuaConsole::onClear()
{
this->clear();
}
void luaClearConsole()
{
LuaDialog->console->clear();
}
std::vector<LuaFunction*> LuaFunctionList; // List of all lua functions.
lua_State* MainLuaState = nullptr;
bool FlagPause = false;
bool FlagStop = false;
bool FlagNewLua = false;
LuaFunction::LuaFunction(luaFunctionPointer cf,const char* n,std::vector<LuaFunction*>* container)
{
this->cfunction = cf;
this->name = n;
container->push_back(this);
}
#define MELON_LUA_HOOK_INSTRUCTION_COUNT 50 //number of vm instructions between hook calls
void luaHookFunction(lua_State* L, lua_Debug *arg)
{
if(FlagStop and (arg->event == LUA_HOOKCOUNT))
luaL_error(L, "Force Stopped");
}
void createLuaState()
{
if(!FlagNewLua)
return;
printf("Creating LuaState");
LuaOverlays.clear();
FlagNewLua = false;
MainLuaState = nullptr;
std::string fileName = LuaDialog->currentScript.fileName().toStdString();
std::string filedir = LuaDialog->currentScript.dir().path().toStdString();
lua_State* L = luaL_newstate();
luaL_openlibs(L);
for(LuaFunction* function : LuaFunctionList)
lua_register(L,function->name,function->cfunction);
std::filesystem::current_path(filedir.c_str());
lua_sethook(L,&luaHookFunction,LUA_MASKCOUNT,MELON_LUA_HOOK_INSTRUCTION_COUNT);
FlagStop = false;
if (luaL_dofile(L,&fileName[0])==LUA_OK)
{
MainLuaState = L;
FlagPause = false;
}
else //Error loading script
{
CurrentThread->onLuaPrint(lua_tostring(L,-1));
MainLuaState = nullptr;
}
}
void LuaConsoleDialog::onStop()
{
FlagStop = true;
}
void LuaConsoleDialog::onPausePlay()
{
FlagPause = !FlagPause;
}
//Gets Called once a frame
void luaUpdate()
{
if(!MainLuaState || FlagPause)
return;
if (lua_getglobal(MainLuaState,"_Update")!=LUA_TFUNCTION)
{
CurrentThread->onLuaPrint("No \"_Update\" Function found, pausing script...");
FlagPause = true;
return;
}
if(lua_pcall(MainLuaState,0,0,0)!=0)
{
//Handel Errors
CurrentThread->onLuaPrint(lua_tostring(MainLuaState,-1));
MainLuaState = nullptr;
}
}
/*
* Front End Stuff
*/
OverlayCanvas::OverlayCanvas(int x,int y,int width,int height,bool isActive)
{
this->isActive=isActive;
buffer1 = new QImage(width,height,QImage::Format_ARGB32_Premultiplied);
buffer2 = new QImage(width,height,QImage::Format_ARGB32_Premultiplied);
buffer1->fill(0xffffff00); //initializes buffer with yellow pixels (probably should change this to transparent black pixels at some point...)
buffer2->fill(0xffffff00);
imageBuffer = buffer1;
displayBuffer = buffer2;
rectangle = QRect(x,y,width,height);
flipped = false;
GLTextureLoaded = false;
}
void OverlayCanvas::flip()
{
if (imageBuffer == buffer1)
{
imageBuffer = buffer2;
displayBuffer = buffer1;
}
else
{
imageBuffer = buffer1;
displayBuffer = buffer2;
}
flipped = true;
}
std::vector<OverlayCanvas> LuaOverlays;
OverlayCanvas* CurrentCanvas;
QHash<QString , QImage> ImageHash;
void luaResetOSD()
{
for (auto lo = LuaOverlays.begin(); lo != LuaOverlays.end();)
{
OverlayCanvas& overlay = *lo;
overlay.GLTextureLoaded = false;
lo++;
}
}
int RightPadding = 0;
int BottomPadding = 0;
int LeftPadding = 0;
int TopPadding = 0;
#define AddLuaFunction(functPointer,name)LuaFunction name(functPointer,#name,&LuaFunctionList)
/*
* Start Defining Lua Functions:
*/
namespace LuaFunctionDefinition
{
int lua_MelonPrint(lua_State* L)
{
QString string = luaL_checkstring(L,1);
CurrentThread->onLuaPrint(string);
return 0;
}
AddLuaFunction(lua_MelonPrint,MelonPrint);
int lua_MelonClear(lua_State* L)
{
CurrentThread->onLuaClearConsole();
return 0;
}
AddLuaFunction(lua_MelonClear,MelonClear);
enum ramInfo_ByteType
{
ramInfo_OneByte = 1,
ramInfo_TwoBytes = 2,
ramInfo_FourBytes = 4
};
melonDS::u32 GetMainRAMValueU(const melonDS::u32& addr, const ramInfo_ByteType& byteType)
{
melonDS::NDS* nds = CurrentInstance->getNDS();
switch (byteType)
{
case ramInfo_OneByte:
return *(melonDS::u8*)(nds->MainRAM + (addr&nds->MainRAMMask));
case ramInfo_TwoBytes:
return *(melonDS::u16*)(nds->MainRAM + (addr&nds->MainRAMMask));
case ramInfo_FourBytes:
return *(melonDS::u32*)(nds->MainRAM + (addr&nds->MainRAMMask));
default:
return 0;
}
}
melonDS::s32 GetMainRAMValueS(const melonDS::u32& addr, const ramInfo_ByteType& byteType)
{
melonDS::NDS* nds = CurrentInstance->getNDS();
switch (byteType)
{
case ramInfo_OneByte:
return *(melonDS::s8*)(nds->MainRAM + (addr&nds->MainRAMMask));
case ramInfo_TwoBytes:
return *(melonDS::s16*)(nds->MainRAM + (addr&nds->MainRAMMask));
case ramInfo_FourBytes:
return *(melonDS::s32*)(nds->MainRAM + (addr&nds->MainRAMMask));
default:
return 0;
}
}
int Lua_ReadDatau(lua_State* L,ramInfo_ByteType byteType)
{
melonDS::u32 address = luaL_checkinteger(L,1);
melonDS::u32 value = GetMainRAMValueU(address,byteType);
lua_pushinteger(L, value);
return 1;
}
int Lua_ReadDatas(lua_State* L,ramInfo_ByteType byteType)
{
melonDS::u32 address = luaL_checkinteger(L,1);
melonDS::s32 value = GetMainRAMValueS(address,byteType);
lua_pushinteger(L, value);
return 1;
}
int Lua_Readu8(lua_State* L)
{
return Lua_ReadDatau(L,ramInfo_OneByte);
}
AddLuaFunction(Lua_Readu8,Readu8);
int Lua_Readu16(lua_State* L)
{
return Lua_ReadDatau(L,ramInfo_TwoBytes);
}
AddLuaFunction(Lua_Readu16,Readu16);
int Lua_Readu32(lua_State* L)
{
return Lua_ReadDatau(L,ramInfo_FourBytes);
}
AddLuaFunction(Lua_Readu32,Readu32);
int Lua_Reads8(lua_State* L)
{
return Lua_ReadDatas(L,ramInfo_OneByte);
}
AddLuaFunction(Lua_Reads8,Reads8);
int Lua_Reads16(lua_State* L)
{
return Lua_ReadDatas(L,ramInfo_TwoBytes);
}
AddLuaFunction(Lua_Reads16,Reads16);
int Lua_Reads32(lua_State* L)
{
return Lua_ReadDatas(L,ramInfo_FourBytes);
}
AddLuaFunction(Lua_Reads32,Reads32);
int Lua_NDSTapDown(lua_State* L)
{
int x = luaL_checkinteger(L,1);
int y = luaL_checkinteger(L,2);
//TODO
//NDS::TouchScreen(x,y);
return 0;
}
AddLuaFunction(Lua_NDSTapDown,NDSTapDown);
int Lua_NDSTapUp(lua_State* L)
{
//TODO
//NDS::ReleaseScreen();
return 0;
}
AddLuaFunction(Lua_NDSTapUp,NDSTapUp);
int Lua_StateSave(lua_State* L)
{
QString filename = luaL_checkstring(L,1);
CurrentThread->onLuaSaveState(filename);
return 0;
}
AddLuaFunction(Lua_StateSave,StateSave);
int Lua_StateLoad(lua_State* L)
{
QString filename = luaL_checkstring(L,1);
CurrentThread->onLuaLoadState(filename);
return 0;
}
AddLuaFunction(Lua_StateLoad,StateLoad);
int Lua_getMouse(lua_State* L)
{
Qt::MouseButtons btns = QGuiApplication::mouseButtons();
QPoint pos = CurrentInstance->getMainWindow()->panel->mapFromGlobal(QCursor::pos(QGuiApplication::primaryScreen()));
const char* keys[6] = {"Left","Middle","Right","XButton1","XButton2","Wheel"};
bool vals[6] =
{
btns.testFlag(Qt::LeftButton),
btns.testFlag(Qt::MiddleButton),
btns.testFlag(Qt::RightButton),
btns.testFlag(Qt::XButton1),
btns.testFlag(Qt::XButton2),
false //TODO: add mouse wheel support
};
lua_createtable(L, 0, 8);
lua_pushinteger(L, pos.x());
lua_setfield(L, -2, "X");
lua_pushinteger(L, pos.y());
lua_setfield(L, -2, "Y");
for(int i=0;i<6;i++)
{
lua_pushboolean(L,vals[i]);
lua_setfield(L,-2,keys[i]);
}
return 1;//returns table describing the current pos and state of the mouse
}
AddLuaFunction(Lua_getMouse,GetMouse);
/*
* Front end lua functions
*/
//TODO: Lua Colors
//MakeCanvas(int x,int y,int width,int height,[int target,topScreen=0,bottomScreen=1,OSD(default)>=2)],[bool active = true])
int Lua_MakeCanvas(lua_State* L)
{
int x = luaL_checknumber(L,1);
int y = luaL_checknumber(L,2);
int w = luaL_checknumber(L,3);
int h = luaL_checknumber(L,4);
int t = luaL_optinteger(L,5,2);
bool a = 0 != luaL_optinteger(L,6,1);
OverlayCanvas canvas(x,y,w,h,a);
switch(t)
{
case 0:
canvas.target=LuaScript::CanvasTarget::TopScreen;
break;
case 1:
canvas.target=LuaScript::CanvasTarget::BottomScreen;
break;
default:
canvas.target=LuaScript::CanvasTarget::OSD;
}
lua_pushinteger(L,LuaOverlays.size());
LuaOverlays.push_back(canvas);
return 1; //returns index of the new overlay
}
AddLuaFunction(Lua_MakeCanvas,MakeCanvas);
int Lua_SetCanvas(lua_State* L) //SetCanvas(int index)
{
int index = luaL_checknumber(L,1);
CurrentCanvas = &LuaOverlays.at(index);
return 0;
}
AddLuaFunction(Lua_SetCanvas,SetCanvas);
int Lua_ClearOverlay(lua_State* L)
{
CurrentCanvas->imageBuffer->fill(0x00000000);
return 0;
}
AddLuaFunction(Lua_ClearOverlay,ClearOverlay);
int Lua_Flip(lua_State* L)
{
CurrentCanvas->flip();
return 0;
}
AddLuaFunction(Lua_Flip,Flip);
//text(int x, int y, string message, [u32 color = 'black'], [int fontsize = 9], [string fontfamily = Franklin Gothic Medium])
int Lua_text(lua_State* L)
{
int x = luaL_checknumber(L,1);
int y = luaL_checknumber(L,2);
const char* message = luaL_checklstring(L,3,NULL);
melonDS::u32 color = luaL_optnumber(L,4,0x00000000);
QString FontFamily = luaL_optlstring(L,6,"Franklin Gothic Medium",NULL);
int size = luaL_optnumber(L,5,9);
QPainter painter(CurrentCanvas->imageBuffer);
QFont font(FontFamily,size,0,false);
font.setStyleStrategy(QFont::NoAntialias);
font.setLetterSpacing(QFont::AbsoluteSpacing,-1);
painter.setFont(font);
painter.setPen(color);
painter.drawText(x,y,message);
return 0;
}
AddLuaFunction(Lua_text,Text);
int Lua_line(lua_State* L)
{
int x1 = luaL_checknumber(L,1);
int y1 = luaL_checknumber(L,2);
int x2 = luaL_checknumber(L,3);
int y2 = luaL_checknumber(L,4);
melonDS::u32 color = luaL_checknumber(L,5);
QPainter painter(CurrentCanvas->imageBuffer);
painter.setPen(color);
painter.drawLine(x1,y1,x2,y2);
return 0;
}
AddLuaFunction(Lua_line,Line);
int Lua_rect(lua_State* L)
{
melonDS::u32 color = luaL_checknumber(L,5);
int x = luaL_checknumber(L,1);
int y = luaL_checknumber(L,2);
int width = luaL_checknumber(L,3);
int height = luaL_checknumber(L,4);
QPainter painter(CurrentCanvas->imageBuffer);
painter.setPen(color);
painter.drawRect(x,y,width,height);
return 0;
}
AddLuaFunction(Lua_rect,Rect);
int Lua_fillrect(lua_State* L)
{
melonDS::u32 color = luaL_checknumber(L,5);
int x = luaL_checknumber(L,1);
int y = luaL_checknumber(L,2);
int width = luaL_checknumber(L,3);
int height = luaL_checknumber(L,4);
QPainter painter(CurrentCanvas->imageBuffer);
painter.setPen(color);
painter.fillRect(x,y,width,height,color);
return 0;
}
AddLuaFunction(Lua_fillrect,FillRect);
int Lua_keystrokes(lua_State* L)
{
lua_createtable(L,0,CurrentInstance->keyStrokes.size());
for (int i = 0; i<CurrentInstance->keyStrokes.size(); i++)
{
lua_pushinteger(L,CurrentInstance->keyStrokes.at(i));
lua_seti(L,-2,i);
}
CurrentInstance->keyStrokes.clear();
lua_createtable(L,0,1);
return 1;
}
AddLuaFunction(Lua_keystrokes,Keys);
//DrawImage(string path, int x, int y,[int source x=0], [int source y=0], [int source w=-1],[int source h=-1])
int Lua_drawImage(lua_State* L)
{
QString path = luaL_checklstring(L,1,NULL);
int x = luaL_checkinteger(L,2);
int y = luaL_checkinteger(L,3);
int sx = luaL_optinteger(L,4,0);
int sy = luaL_optinteger(L,5,0);
int sw = luaL_optinteger(L,6,-1);
int sh = luaL_optinteger(L,7,-1);
QPainter painter(CurrentCanvas->imageBuffer);
QImage image;
if(ImageHash.contains(path))
{
image=ImageHash[path];
}
else
{
image=QImage(path);
ImageHash[path] = image;
}
painter.drawImage(x,y,image,sx,sy,sw,sh);
return 0;
}
AddLuaFunction(Lua_drawImage,DrawImage);
int Lua_clearImageHash(lua_State* L)
{
ImageHash.clear();
return 0;
}
AddLuaFunction(Lua_clearImageHash,ClearHash);
int Lua_getJoy(lua_State* L)
{
//TODO:
melonDS::u32 buttonMask=CurrentInstance->getInputMask();//current button state.
const char* keys[12] =
{//Buttons in order of mask.
"A","B","Select","Start",
"Right","Left","Up","Down",
"R","L","X","Y"
};
lua_createtable(L, 0, 12);
for(melonDS::u32 i=0;i<12;i++)
{
lua_pushboolean(L,0 >= (buttonMask&(1<<i)));
lua_setfield(L,-2,keys[i]);
}
return 1;
}
AddLuaFunction(Lua_getJoy,GetJoy);
/*
int Lua_setPadding(lua_State* L) //TODO: Currently only works well with force integer scaling
{
LeftPadding = abs(luaL_checkinteger(L,1));
TopPadding = abs(luaL_checkinteger(L,2));
RightPadding = abs(luaL_checkinteger(L,3));
BottomPadding = abs(luaL_checkinteger(L,4));
CurrentThread->onLuaLayoutChange();
return 0;
}
AddLuaFunction(Lua_setPadding,SetPadding);
*/
}//LuaFunctionDefinition
}//LuaScript

View File

@ -0,0 +1,95 @@
#ifndef LUASCRIPT_H
#define LUASCRIPT_H
#include <QDialog>
#include <QPlainTextEdit>
#include <QFileInfo>
#include <lua.hpp>
#include "Window.h"
namespace LuaScript
{
class LuaConsole: public QPlainTextEdit
{
Q_OBJECT
public:
LuaConsole(QWidget* parent=nullptr);
public slots:
void onGetText(const QString& string);
void onClear();
};
class LuaConsoleDialog: public QDialog
{
Q_OBJECT
public:
LuaConsoleDialog(QWidget* parent);
LuaConsole* console;
QFileInfo currentScript;
QPushButton* buttonOpenScript;
QPushButton* buttonStartStop;
QPushButton* buttonPausePlay;
QScrollBar* bar;
protected:
void closeEvent(QCloseEvent *event) override;
MainWindow* mainWindow;
signals:
void signalNewLua();
void signalClosing();
public slots:
//void onStartStop();
void onOpenScript();
void onStop();
void onPausePlay();
};
//Based on ScreenLayout::GetScreenTransforms
enum CanvasTarget
{
TopScreen = 0,
BottomScreen = 1,
OSD = 2 //Used for drawing to OSD / non-screen target
};
struct OverlayCanvas
{
QImage* imageBuffer; // buffer edited by luascript
QImage* displayBuffer; //buffer displayed on screen
QImage* buffer1;
QImage* buffer2;
QRect rectangle;
CanvasTarget target = OSD;
bool isActive = true; // only active overlays are drawn
unsigned int GLTexture; // used by GL rendering
bool GLTextureLoaded;
OverlayCanvas(int x,int y,int w, int h, bool active);
void flip();//used to swap buffers
bool flipped; //used to signal update to graphics.
};
void luaResetOSD();
void luaUpdate();
void luaPrint(QString string);
void luaClearConsole();
void luaHookFunction(lua_State*,lua_Debug*);
extern QWidget* panel;
extern lua_State* MainLuaState;
extern bool FlagPause;
extern bool FlagStop;
extern bool FlagNewLua;
typedef int(*luaFunctionPointer)(lua_State*);
struct LuaFunction
{
luaFunctionPointer cfunction;
const char* name;
LuaFunction(luaFunctionPointer,const char*,std::vector<LuaFunction*>*);
};
extern LuaConsoleDialog* LuaDialog;
void createLuaState();
extern std::vector<OverlayCanvas> LuaOverlays;
extern OverlayCanvas* CurrentCanvas;
extern QHash<QString, QImage> ImageHash;
extern int RightPadding;
extern int BottomPadding;
extern int TopPadding;
extern int LeftPadding;
}
#endif

View File

@ -63,4 +63,46 @@ void main()
}
)";
//Fragment Shader for overlay copied from melonPrimeDS project.
const char* kScreenFS_overlay = R"(#version 140
uniform sampler2D OverlayTex;
smooth in vec2 fTexcoord;
uniform vec2 uOverlayPos;
uniform vec2 uOverlaySize;
uniform int uOverlayScreenType;
out vec4 oColor;
void main()
{
const vec2 dsSize = vec2(256.0, 193.0); // +1 on y for pixel gap
vec2 uv = fTexcoord * vec2(1.0, 2.0);
if (uOverlayScreenType < 1) {
// top screen
uv -= uOverlayPos / dsSize;
uv *= dsSize / uOverlaySize;
} else {
// bottom screen
uv -= vec2(0.0, 1.0);
uv -= (uOverlayPos + vec2(0.0, 1.0)) / dsSize;
uv *= dsSize / uOverlaySize;
}
vec4 pixel = texture(OverlayTex, uv);
pixel.rgb *= pixel.a;
if (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0) {
oColor = vec4(0.0, 0.0, 0.0, 0.0);
} else {
oColor = pixel.bgra;
}
}
)";
#endif // OSD_SHADERS_H

View File

@ -46,6 +46,8 @@
#include "main_shaders.h"
#include "OSD_shaders.h"
#include "font.h"
#include "LuaMain.h"
using namespace melonDS;
@ -667,13 +669,29 @@ void ScreenPanelNative::setupScreenLayout()
}
}
// From ScreenLayout::GetScreenTransforms
// TopScreen = 0
// BottomScreen = 1
// OSD / non-screen target = 2 (used by LuaScript stuff)
void ScreenPanelNative::drawOverlays(QPainter* painter,int type)
{
for (auto lo = LuaScript::LuaOverlays.begin(); lo != LuaScript::LuaOverlays.end();)
{
LuaScript::OverlayCanvas& overlay = *lo;
if ((overlay.target == type) && overlay.isActive)
painter->drawImage(overlay.rectangle,*overlay.displayBuffer);
lo++;
}
}
void ScreenPanelNative::paintEvent(QPaintEvent* event)
{
QPainter painter(this);
// fill background
painter.fillRect(event->rect(), QColor::fromRgb(0, 0, 0));
painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
auto emuThread = emuInstance->getEmuThread();
if (emuThread->emuIsActive())
@ -693,12 +711,15 @@ void ScreenPanelNative::paintEvent(QPaintEvent* event)
memcpy(screen[1].scanLine(0), nds->GPU.Framebuffer[frontbuf][1].get(), 256 * 192 * 4);
emuThread->FrontBufferLock.unlock();
painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
QRect screenrc(0, 0, 256, 192);
for (int i = 0; i < numScreens; i++)
{
painter.setTransform(screenTrans[i]);
painter.drawImage(screenrc, screen[screenKind[i]]);
if (osdEnabled)
drawOverlays(&painter,screenKind[i]);
}
}
@ -711,6 +732,8 @@ void ScreenPanelNative::paintEvent(QPaintEvent* event)
painter.resetTransform();
drawOverlays(&painter,LuaScript::CanvasTarget::OSD);
for (auto it = osdItems.begin(); it != osdItems.end(); )
{
OSDItem& item = *it;
@ -877,6 +900,21 @@ void ScreenPanelGL::initOpenGL()
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, (void*)(0));
transferLayout();
//TODO: Lookinto seeing if we can just re-use screen shader for this...
OpenGL::CompileVertexFragmentProgram(overlayShader,
kScreenVS,kScreenFS_overlay,
"OverlayShader",
{{"vPosition", 0}, {"vTexcoord", 1}},
{{"oColor", 0}});
glUseProgram(overlayShader);
overlayScreenSizeULoc = glGetUniformLocation(overlayShader, "uScreenSize");
overlayTransformULoc = glGetUniformLocation(overlayShader, "uTransform");
overlayPosULoc = glGetUniformLocation(overlayShader, "uOverlayPos");
overlaySizeULoc = glGetUniformLocation(overlayShader, "uOverlaySize");
overlayScreenTypeULoc = glGetUniformLocation(overlayShader, "uOverlayScreenType");
}
void ScreenPanelGL::deinitOpenGL()
@ -902,6 +940,16 @@ void ScreenPanelGL::deinitOpenGL()
glDeleteProgram(osdShader);
glDeleteProgram(overlayShader);
for (auto lo = LuaScript::LuaOverlays.begin(); lo != LuaScript::LuaOverlays.end();)
{
LuaScript::OverlayCanvas& overlay = *lo;
lo++;
if (!overlay.GLTextureLoaded)
continue;
glDeleteTextures(1,&overlay.GLTexture);
overlay.GLTextureLoaded=false;
}
glContext->DoneCurrent();
@ -943,6 +991,51 @@ void ScreenPanelGL::osdDeleteItem(OSDItem* item)
ScreenPanel::osdDeleteItem(item);
}
void ScreenPanelGL::drawOverlays(int type,int screen)
{
for (auto lo = LuaScript::LuaOverlays.begin(); lo != LuaScript::LuaOverlays.end();)
{
LuaScript::OverlayCanvas& overlay = *lo;
lo++;
if (!overlay.isActive || overlay.target != type)
continue;
if (!overlay.GLTextureLoaded)//Load texture if none loaded
{
glGenTextures(1,&overlay.GLTexture);
glBindTexture(GL_TEXTURE_2D, overlay.GLTexture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, overlay.rectangle.width(), overlay.rectangle.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, overlay.displayBuffer->bits());
overlay.GLTextureLoaded = true;
}
if (overlay.flipped) //Only update texture if needed
{
glBindTexture(GL_TEXTURE_2D, overlay.GLTexture);
glTexSubImage2D(GL_TEXTURE_2D,0,0,0,overlay.rectangle.width(),overlay.rectangle.height(),GL_RGBA,GL_UNSIGNED_BYTE,overlay.displayBuffer->bits());
overlay.flipped = false;
}
if(type == LuaScript::CanvasTarget::OSD) // OSD gets drawn differently then top or bottom screen target
{
glBindTexture(GL_TEXTURE_2D, overlay.GLTexture);
glUniform2i(osdPosULoc,overlay.rectangle.left(),overlay.rectangle.top());
glUniform2i(osdSizeULoc,overlay.rectangle.width(),overlay.rectangle.height());
glDrawArrays(GL_TRIANGLES, 0, 2*3);
continue;
}
glBindTexture(GL_TEXTURE_2D, overlay.GLTexture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glUniform2f(overlayPosULoc,overlay.rectangle.left(),overlay.rectangle.top());
glUniform2f(overlaySizeULoc,overlay.rectangle.width(),overlay.rectangle.height());
glUniform1i(overlayScreenTypeULoc, type);
glUniformMatrix2x3fv(overlayTransformULoc, 1, GL_TRUE,screenMatrix[screen]);
glDrawArrays(GL_TRIANGLES,type == 0 ? 0 : 2*3, 2*3);
}
}
void ScreenPanelGL::drawScreenGL()
{
if (!glContext) return;
@ -1015,6 +1108,29 @@ void ScreenPanelGL::drawScreenGL()
osdUpdate();
if (osdEnabled)
{
glUseProgram(overlayShader);
//Need to blend this layer onto the screen layer!
glEnable(GL_BLEND);
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
glUniform2f(overlayScreenSizeULoc, w / factor, h / factor);
screenSettingsLock.lock();
glBindBuffer(GL_ARRAY_BUFFER, screenVertexBuffer);
glBindVertexArray(screenVertexArray);
for(int i = 0;i<numScreens;i++){
drawOverlays(screenKind[i],i);
}
screenSettingsLock.unlock();
osdMutex.lock();
u32 y = kOSDMargin;
@ -1032,6 +1148,8 @@ void ScreenPanelGL::drawScreenGL()
glEnable(GL_BLEND);
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
drawOverlays(LuaScript::CanvasTarget::OSD,0);
for (auto it = osdItems.begin(); it != osdItems.end(); )
{
OSDItem& item = *it;

View File

@ -29,6 +29,7 @@
#include <QScreen>
#include <QCloseEvent>
#include <QTimer>
#include <QPainter>
#include "glad/glad.h"
#include "ScreenLayout.h"
@ -157,6 +158,7 @@ protected:
private:
void setupScreenLayout() override;
void drawOverlays(QPainter* painter,int type);
QImage screen[2];
QTransform screenTrans[kMaxScreenTransforms];
@ -195,6 +197,7 @@ protected:
private:
void setupScreenLayout() override;
void drawOverlays(int type,int screen);
std::unique_ptr<GL::Context> glContext;
@ -217,6 +220,11 @@ private:
void osdRenderItem(OSDItem* item) override;
void osdDeleteItem(OSDItem* item) override;
GLuint overlayShader;
GLuint overlayScreenSizeULoc, overlayTransformULoc;
GLuint overlayPosULoc, overlaySizeULoc, overlayScreenTypeULoc;
};
#endif // SCREEN_H

View File

@ -82,6 +82,8 @@
#include "ArchiveUtil.h"
#include "CameraManager.h"
#include "LuaMain.h"
using namespace melonDS;
@ -403,6 +405,12 @@ MainWindow::MainWindow(int id, EmuInstance* inst, QWidget* parent) :
actDateTime = menu->addAction("Date and time");
connect(actDateTime, &QAction::triggered, this, &MainWindow::onOpenDateTime);
menu->addSeparator();
actLuaScript = menu->addAction("Lua Script");
connect(actLuaScript,&QAction::triggered,this,&MainWindow::onOpenLuaScript);
menu->addSeparator();
actEnableCheats = menu->addAction("Enable cheats");
@ -1449,6 +1457,13 @@ void MainWindow::onEjectGBACart()
updateCartInserted(true);
}
void MainWindow::onLuaSaveState(const QString& filename)
{
emuThread->emuPause();
emuInstance->saveState(filename.toStdString());
emuThread->emuUnpause();
}
void MainWindow::onSaveState()
{
int slot = ((QAction*)sender())->data().toInt();
@ -1491,6 +1506,13 @@ void MainWindow::onSaveState()
emuThread->emuUnpause();
}
void MainWindow::onLuaLoadState(const QString& filename)
{
emuThread->emuPause();
emuInstance->loadState(filename.toStdString());
emuThread->emuUnpause();
}
void MainWindow::onLoadState()
{
int slot = ((QAction*)sender())->data().toInt();
@ -1657,6 +1679,16 @@ void MainWindow::onOpenPowerManagement()
PowerManagementDialog* dlg = PowerManagementDialog::openDlg(this);
}
void MainWindow::onOpenLuaScript()
{
if (LuaScript::LuaDialog) // only one at a time.
return;
LuaScript::LuaDialog = new LuaScript::LuaConsoleDialog(this);
LuaScript::LuaDialog->show();
//connect(emuThread,&EmuThread::signalLuaSaveState,mainWindow,&MainWindow::onLuaSaveState);
//connect(emuThread,&EmuThread::signalLuaLoadState,mainWindow,&MainWindow::onLuaLoadState);
}
void MainWindow::onEnableCheats(bool checked)
{
localCfg.SetBool("EnableCheats", checked);

View File

@ -152,7 +152,9 @@ private slots:
void onInsertGBACart();
void onInsertGBAAddon();
void onEjectGBACart();
void onLuaSaveState(const QString& filename);
void onSaveState();
void onLuaLoadState(const QString& filename);
void onLoadState();
void onUndoStateLoad();
void onImportSavefile();
@ -163,6 +165,7 @@ private slots:
void onStop();
void onFrameStep();
void onOpenPowerManagement();
void onOpenLuaScript();
void onOpenDateTime();
void onEnableCheats(bool checked);
void onSetupCheats();
@ -300,6 +303,7 @@ public:
#ifdef __APPLE__
QAction* actPreferences;
#endif
QAction* actLuaScript;
QAction* actInputConfig;
QAction* actVideoSettings;
QAction* actCameraSettings;