From 2f19ab8bb5943b7086579c5f02543c3e5b578e30 Mon Sep 17 00:00:00 2001 From: NPO <77460082+NPO-197@users.noreply.github.com> Date: Wed, 25 Sep 2024 13:18:13 -0400 Subject: [PATCH] Next Attempt (2024)! -Still lots todo! but I have a (mostly) working version up and running! --- .gitignore | 1 + src/frontend/qt_sdl/CMakeLists.txt | 5 +- src/frontend/qt_sdl/EmuInstance.h | 4 + src/frontend/qt_sdl/EmuInstanceInput.cpp | 1 + src/frontend/qt_sdl/EmuThread.cpp | 7 + src/frontend/qt_sdl/EmuThread.h | 11 + src/frontend/qt_sdl/LuaMain.cpp | 628 +++++++++++++++++++++++ src/frontend/qt_sdl/LuaMain.h | 95 ++++ src/frontend/qt_sdl/OSD_shaders.h | 42 ++ src/frontend/qt_sdl/Screen.cpp | 120 ++++- src/frontend/qt_sdl/Screen.h | 8 + src/frontend/qt_sdl/Window.cpp | 32 ++ src/frontend/qt_sdl/Window.h | 4 + 13 files changed, 956 insertions(+), 2 deletions(-) create mode 100644 src/frontend/qt_sdl/LuaMain.cpp create mode 100644 src/frontend/qt_sdl/LuaMain.h diff --git a/.gitignore b/.gitignore index d7001e4a..977af631 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ build*/ +debug*/ bin obj *.depend diff --git a/src/frontend/qt_sdl/CMakeLists.txt b/src/frontend/qt_sdl/CMakeLists.txt index 524fa13d..2f76581a 100644 --- a/src/frontend/qt_sdl/CMakeLists.txt +++ b/src/frontend/qt_sdl/CMakeLists.txt @@ -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) diff --git a/src/frontend/qt_sdl/EmuInstance.h b/src/frontend/qt_sdl/EmuInstance.h index 39c187c2..0a18e03d 100644 --- a/src/frontend/qt_sdl/EmuInstance.h +++ b/src/frontend/qt_sdl/EmuInstance.h @@ -136,6 +136,7 @@ public: int getJoystickID() { return joystickID; } SDL_Joystick* getJoystick() { return joystick; } + std::vector 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 backupState; diff --git a/src/frontend/qt_sdl/EmuInstanceInput.cpp b/src/frontend/qt_sdl/EmuInstanceInput.cpp index ddaca8f0..87b52e1f 100644 --- a/src/frontend/qt_sdl/EmuInstanceInput.cpp +++ b/src/frontend/qt_sdl/EmuInstanceInput.cpp @@ -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(); diff --git a/src/frontend/qt_sdl/EmuThread.cpp b/src/frontend/qt_sdl/EmuThread.cpp index ae66e1ba..d1848ed7 100644 --- a/src/frontend/qt_sdl/EmuThread.cpp +++ b/src/frontend/qt_sdl/EmuThread.cpp @@ -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); diff --git a/src/frontend/qt_sdl/EmuThread.h b/src/frontend/qt_sdl/EmuThread.h index cd36eb4c..0d1f8c0c 100644 --- a/src/frontend/qt_sdl/EmuThread.h +++ b/src/frontend/qt_sdl/EmuThread.h @@ -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(); diff --git a/src/frontend/qt_sdl/LuaMain.cpp b/src/frontend/qt_sdl/LuaMain.cpp new file mode 100644 index 00000000..25f519a1 --- /dev/null +++ b/src/frontend/qt_sdl/LuaMain.cpp @@ -0,0 +1,628 @@ +#include "LuaMain.h" +#include +#include +#include +#include +#include +#include +#include +#include "types.h" +#include "NDS.h" +#include +#include +#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(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 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* 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 LuaOverlays; +OverlayCanvas* CurrentCanvas; +QHash 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; ikeyStrokes.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<onLuaLayoutChange(); + return 0; +} +AddLuaFunction(Lua_setPadding,SetPadding); +*/ + + +}//LuaFunctionDefinition +}//LuaScript diff --git a/src/frontend/qt_sdl/LuaMain.h b/src/frontend/qt_sdl/LuaMain.h new file mode 100644 index 00000000..7903c0bf --- /dev/null +++ b/src/frontend/qt_sdl/LuaMain.h @@ -0,0 +1,95 @@ +#ifndef LUASCRIPT_H +#define LUASCRIPT_H +#include +#include +#include +#include +#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*); +}; +extern LuaConsoleDialog* LuaDialog; +void createLuaState(); +extern std::vector LuaOverlays; +extern OverlayCanvas* CurrentCanvas; +extern QHash ImageHash; +extern int RightPadding; +extern int BottomPadding; +extern int TopPadding; +extern int LeftPadding; +} +#endif diff --git a/src/frontend/qt_sdl/OSD_shaders.h b/src/frontend/qt_sdl/OSD_shaders.h index a2a6af70..9a92f28a 100644 --- a/src/frontend/qt_sdl/OSD_shaders.h +++ b/src/frontend/qt_sdl/OSD_shaders.h @@ -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 diff --git a/src/frontend/qt_sdl/Screen.cpp b/src/frontend/qt_sdl/Screen.cpp index 425d99b5..45ba6ecc 100644 --- a/src/frontend/qt_sdl/Screen.cpp +++ b/src/frontend/qt_sdl/Screen.cpp @@ -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 #include #include +#include #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 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 diff --git a/src/frontend/qt_sdl/Window.cpp b/src/frontend/qt_sdl/Window.cpp index d40a062e..d192fac5 100644 --- a/src/frontend/qt_sdl/Window.cpp +++ b/src/frontend/qt_sdl/Window.cpp @@ -82,6 +82,8 @@ #include "ArchiveUtil.h" #include "CameraManager.h" +#include "LuaMain.h" + using namespace melonDS; @@ -402,6 +404,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(); @@ -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); diff --git a/src/frontend/qt_sdl/Window.h b/src/frontend/qt_sdl/Window.h index 30d97b17..d5288cd6 100644 --- a/src/frontend/qt_sdl/Window.h +++ b/src/frontend/qt_sdl/Window.h @@ -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;