diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index 9a5830e0..b2dda12d 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -28,7 +28,7 @@ jobs: - name: Set up vcpkg uses: lukka/run-vcpkg@v11 with: - vcpkgGitCommitId: 1de2026f28ead93ff1773e6e680387643e914ea1 + vcpkgGitCommitId: 3508985146f1b1d248c67ead13f8f54be5b4f5da - name: Build uses: lukka/run-cmake@v10 with: diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index 5470e7b8..30dbd2a8 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -4,7 +4,7 @@ on: push: branches: - master - - ci/vcpkg-update + - ci/* pull_request: branches: - master @@ -27,7 +27,7 @@ jobs: - name: Set up vcpkg uses: lukka/run-vcpkg@v11 with: - vcpkgGitCommitId: 1de2026f28ead93ff1773e6e680387643e914ea1 + vcpkgGitCommitId: 3508985146f1b1d248c67ead13f8f54be5b4f5da - name: Configure run: cmake --preset=release-mingw-x86_64 - name: Build diff --git a/cmake/ConfigureVcpkg.cmake b/cmake/ConfigureVcpkg.cmake index fa6e2b00..f8c33fd4 100644 --- a/cmake/ConfigureVcpkg.cmake +++ b/cmake/ConfigureVcpkg.cmake @@ -9,7 +9,7 @@ if (VCPKG_ROOT STREQUAL "${_DEFAULT_VCPKG_ROOT}") endif() FetchContent_Declare(vcpkg GIT_REPOSITORY "https://github.com/Microsoft/vcpkg.git" - GIT_TAG 2024.07.12 + GIT_TAG 2024.08.23 SOURCE_DIR "${CMAKE_SOURCE_DIR}/vcpkg") FetchContent_MakeAvailable(vcpkg) endif() @@ -25,6 +25,11 @@ else() option(USE_QT6 "Build using Qt 6 instead of 5" OFF) endif() +# Since the Linux build pulls in glib anyway, we can just use upstream libslirp +if (UNIX AND NOT APPLE) + option(USE_SYSTEM_LIBSLIRP "Use system libslirp instead of the bundled version" ON) +endif() + if (NOT USE_QT6) list(APPEND VCPKG_MANIFEST_FEATURES qt5) set(VCPKG_MANIFEST_NO_DEFAULT_FEATURES ON) @@ -62,6 +67,14 @@ if (USE_RECOMMENDED_TRIPLETS) # TODO Windows arm64 if possible set(_CAN_TARGET_AS_HOST ON) set(_WANTED_TRIPLET x64-mingw-static-release) + elseif(CMAKE_HOST_SYSTEM_NAME STREQUAL Linux) + # Can't really detect cross compiling here. + set(_CAN_TARGET_AS_HOST ON) + if (_HOST_PROCESSOR STREQUAL x86_64) + set(_WANTED_TRIPLET x64-linux-release) + elseif(_HOST_PROCESSOR STREQUAL "aarch64") + set(_WANTED_TRIPLET arm64-linux-release) + endif() endif() # Don't override it if the user set something else diff --git a/flake.lock b/flake.lock index 6e99d27f..9f0fe239 100644 --- a/flake.lock +++ b/flake.lock @@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1723175592, - "narHash": "sha256-M0xJ3FbDUc4fRZ84dPGx5VvgFsOzds77KiBMW/mMTnI=", + "lastModified": 1725432240, + "narHash": "sha256-+yj+xgsfZaErbfYM3T+QvEE2hU7UuE+Jf0fJCJ8uPS0=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "5e0ca22929f3342b19569b21b2f3462f053e497b", + "rev": "ad416d066ca1222956472ab7d0555a6946746a80", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 487bf197..8236ccd3 100644 --- a/flake.nix +++ b/flake.nix @@ -25,23 +25,23 @@ cmake ninja pkg-config - kdePackages.wrapQtAppsHook + qt6.wrapQtAppsHook ]; buildInputs = (with pkgs; [ - kdePackages.qtbase - kdePackages.qtmultimedia - extra-cmake-modules + qt6.qtbase + qt6.qtmultimedia SDL2 zstd libarchive libGL libslirp enet - ]) ++ optionals isLinux [ - pkgs.wayland - pkgs.kdePackages.qtwayland - ]; + ]) ++ optionals (!isDarwin) (with pkgs; [ + kdePackages.extra-cmake-modules + qt6.qtwayland + wayland + ]); cmakeFlags = [ (cmakeBool "USE_QT6" true) @@ -65,8 +65,27 @@ apps.default = flake-utils.lib.mkApp { drv = self.packages.${system}.default; }; - devShells.default = pkgs.mkShell { - inputsFrom = [ self.packages.${system}.default ]; + devShells = { + default = pkgs.mkShell { + inputsFrom = [ self.packages.${system}.default ]; + }; + + # Shell for building static melonDS release builds with vcpkg + # Use mkShellNoCC to ensure Nix's gcc/clang and stdlib isn't used + vcpkg = pkgs.mkShellNoCC { + packages = with pkgs; [ + autoconf + autoconf-archive + automake + cmake + cups.dev # Needed by qtbase despite not enabling print support + git + iconv.dev + libtool + ninja + pkg-config + ]; + }; }; } ); diff --git a/src/GPU3D.cpp b/src/GPU3D.cpp index 865b00a1..4a1426aa 100644 --- a/src/GPU3D.cpp +++ b/src/GPU3D.cpp @@ -274,6 +274,8 @@ void GPU3D::Reset() noexcept memset(MatEmission, 0, sizeof(MatSpecular)); UseShininessTable = false; + // Shininess table seems to be uninitialized garbage, at least on n3dsxl hw? + // Also doesn't seem to be cleared properly unless the system is fully powered off? memset(ShininessTable, 0, sizeof(ShininessTable)); PolygonAttr = 0; @@ -1459,67 +1461,86 @@ void GPU3D::CalculateLighting() noexcept TexCoords[1] = RawTexCoords[1] + (((s64)Normal[0]*TexMatrix[1] + (s64)Normal[1]*TexMatrix[5] + (s64)Normal[2]*TexMatrix[9]) >> 21); } - s32 normaltrans[3]; - normaltrans[0] = (Normal[0]*VecMatrix[0] + Normal[1]*VecMatrix[4] + Normal[2]*VecMatrix[8]) >> 12; - normaltrans[1] = (Normal[0]*VecMatrix[1] + Normal[1]*VecMatrix[5] + Normal[2]*VecMatrix[9]) >> 12; - normaltrans[2] = (Normal[0]*VecMatrix[2] + Normal[1]*VecMatrix[6] + Normal[2]*VecMatrix[10]) >> 12; - - VertexColor[0] = MatEmission[0]; - VertexColor[1] = MatEmission[1]; - VertexColor[2] = MatEmission[2]; + s32 normaltrans[3]; // should be 1 bit sign 10 bits frac + normaltrans[0] = ((Normal[0]*VecMatrix[0] + Normal[1]*VecMatrix[4] + Normal[2]*VecMatrix[8]) << 9) >> 21; + normaltrans[1] = ((Normal[0]*VecMatrix[1] + Normal[1]*VecMatrix[5] + Normal[2]*VecMatrix[9]) << 9) >> 21; + normaltrans[2] = ((Normal[0]*VecMatrix[2] + Normal[1]*VecMatrix[6] + Normal[2]*VecMatrix[10]) << 9) >> 21; s32 c = 0; + u32 vtxbuff[3] = + { + (u32)MatEmission[0] << 14, + (u32)MatEmission[1] << 14, + (u32)MatEmission[2] << 14 + }; for (int i = 0; i < 4; i++) { if (!(CurPolygonAttr & (1<1) - // according to some hardware tests - // * diffuse level is saturated to 255 - // * shininess level mirrors back to 0 and is ANDed with 0xFF, that before being squared - // TODO: check how it behaves when the computed shininess is >=0x200 + // (credit to azusa for working out most of the details of the diff. algorithm, and essentially the entire spec. algorithm) + + // calculate dot product + // bottom 9 bits are discarded after multiplying and before adding + s32 dot = ((LightDirection[i][0]*normaltrans[0]) >> 9) + + ((LightDirection[i][1]*normaltrans[1]) >> 9) + + ((LightDirection[i][2]*normaltrans[2]) >> 9); - s32 difflevel = (-(LightDirection[i][0]*normaltrans[0] + - LightDirection[i][1]*normaltrans[1] + - LightDirection[i][2]*normaltrans[2])) >> 10; - if (difflevel < 0) difflevel = 0; - else if (difflevel > 255) difflevel = 255; + s32 shinelevel; + if (dot > 0) + { + // -- diffuse lighting -- + + // convert dot to signed 11 bit int + // then we truncate the result of the multiplications to an unsigned 20 bits before adding to the vtx color + s32 diffdot = (dot << 21) >> 21; + vtxbuff[0] += (MatDiffuse[0] * LightColor[i][0] * diffdot) & 0xFFFFF; + vtxbuff[1] += (MatDiffuse[1] * LightColor[i][1] * diffdot) & 0xFFFFF; + vtxbuff[2] += (MatDiffuse[2] * LightColor[i][2] * diffdot) & 0xFFFFF; - s32 shinelevel = -(((LightDirection[i][0]>>1)*normaltrans[0] + - (LightDirection[i][1]>>1)*normaltrans[1] + - ((LightDirection[i][2]-0x200)>>1)*normaltrans[2]) >> 10); - if (shinelevel < 0) shinelevel = 0; - else if (shinelevel > 255) shinelevel = (0x100 - shinelevel) & 0xFF; - shinelevel = ((shinelevel * shinelevel) >> 7) - 0x100; // really (2*shinelevel*shinelevel)-1 - if (shinelevel < 0) shinelevel = 0; + // -- specular lighting -- + + // reuse the dot product from diffuse lighting + dot += normaltrans[2]; + // convert to s11, then square it, and truncate to 10 bits + dot = (dot << 21) >> 21; + dot = ((dot * dot) >> 10) & 0x3FF; + + // multiply dot and reciprocal, the subtract '1' + shinelevel = ((dot * SpecRecip[i]) >> 8) - (1<<9); + + if (shinelevel < 0) shinelevel = 0; + else + { + // sign extend to convert to signed 14 bit integer + shinelevel = (shinelevel << 18) >> 18; + if (shinelevel < 0) shinelevel = 0; // for some reason there seems to be a redundant check for <0? + else if (shinelevel > 0x1FF) shinelevel = 0x1FF; + } + } + else shinelevel = 0; + + // convert shinelevel to use for lookup in the shininess table if enabled. if (UseShininessTable) { - // checkme - shinelevel >>= 1; + shinelevel >>= 2; shinelevel = ShininessTable[shinelevel]; + shinelevel <<= 1; } - VertexColor[0] += ((MatSpecular[0] * LightColor[i][0] * shinelevel) >> 13); - VertexColor[0] += ((MatDiffuse[0] * LightColor[i][0] * difflevel) >> 13); - VertexColor[0] += ((MatAmbient[0] * LightColor[i][0]) >> 5); - - VertexColor[1] += ((MatSpecular[1] * LightColor[i][1] * shinelevel) >> 13); - VertexColor[1] += ((MatDiffuse[1] * LightColor[i][1] * difflevel) >> 13); - VertexColor[1] += ((MatAmbient[1] * LightColor[i][1]) >> 5); - - VertexColor[2] += ((MatSpecular[2] * LightColor[i][2] * shinelevel) >> 13); - VertexColor[2] += ((MatDiffuse[2] * LightColor[i][2] * difflevel) >> 13); - VertexColor[2] += ((MatAmbient[2] * LightColor[i][2]) >> 5); - - if (VertexColor[0] > 31) VertexColor[0] = 31; - if (VertexColor[1] > 31) VertexColor[1] = 31; - if (VertexColor[2] > 31) VertexColor[2] = 31; + // Note: ambient seems to be a plain bitshift + vtxbuff[0] += ((MatSpecular[0] * shinelevel) + (MatAmbient[0] << 9)) * LightColor[i][0]; + vtxbuff[1] += ((MatSpecular[1] * shinelevel) + (MatAmbient[1] << 9)) * LightColor[i][1]; + vtxbuff[2] += ((MatSpecular[2] * shinelevel) + (MatAmbient[2] << 9)) * LightColor[i][2]; c++; } + VertexColor[0] = (vtxbuff[0] >> 14 > 31) ? 31 : (vtxbuff[0] >> 14); + VertexColor[1] = (vtxbuff[1] >> 14 > 31) ? 31 : (vtxbuff[1] >> 14); + VertexColor[2] = (vtxbuff[2] >> 14 > 31) ? 31 : (vtxbuff[2] >> 14); + if (c < 1) c = 1; NormalPipeline = 7; AddCycles(c); @@ -2012,9 +2033,15 @@ void GPU3D::ExecuteCommand() noexcept dir[0] = (s16)((entry.Param & 0x000003FF) << 6) >> 6; dir[1] = (s16)((entry.Param & 0x000FFC00) >> 4) >> 6; dir[2] = (s16)((entry.Param & 0x3FF00000) >> 14) >> 6; - LightDirection[l][0] = (dir[0]*VecMatrix[0] + dir[1]*VecMatrix[4] + dir[2]*VecMatrix[8]) >> 12; - LightDirection[l][1] = (dir[0]*VecMatrix[1] + dir[1]*VecMatrix[5] + dir[2]*VecMatrix[9]) >> 12; - LightDirection[l][2] = (dir[0]*VecMatrix[2] + dir[1]*VecMatrix[6] + dir[2]*VecMatrix[10]) >> 12; + // the order of operations here is very specific: discard bottom 12 bits -> negate -> then sign extend to convert to 11 bit signed int + // except for when used to calculate the specular reciprocal; then it's: sign extend -> discard lsb -> negate. + LightDirection[l][0] = (-((dir[0]*VecMatrix[0] + dir[1]*VecMatrix[4] + dir[2]*VecMatrix[8] ) >> 12) << 21) >> 21; + LightDirection[l][1] = (-((dir[0]*VecMatrix[1] + dir[1]*VecMatrix[5] + dir[2]*VecMatrix[9] ) >> 12) << 21) >> 21; + LightDirection[l][2] = (-((dir[0]*VecMatrix[2] + dir[1]*VecMatrix[6] + dir[2]*VecMatrix[10]) >> 12) << 21) >> 21; + s32 den = -(((dir[0]*VecMatrix[2] + dir[1]*VecMatrix[6] + dir[2]*VecMatrix[10]) << 9) >> 21) + (1<<9); + + if (den == 0) SpecRecip[l] = 0; + else SpecRecip[l] = (1<<18) / den; } AddCycles(5); break; diff --git a/src/GPU3D.h b/src/GPU3D.h index a12d4bc0..d10df55f 100644 --- a/src/GPU3D.h +++ b/src/GPU3D.h @@ -286,6 +286,7 @@ public: s16 Normal[3] {}; s16 LightDirection[4][3] {}; + s32 SpecRecip[4] {}; u8 LightColor[4][3] {}; u8 MatDiffuse[3] {}; u8 MatAmbient[3] {}; diff --git a/src/ROMList.cpp b/src/ROMList.cpp index e4ce7d2a..a00b0b22 100644 --- a/src/ROMList.cpp +++ b/src/ROMList.cpp @@ -1830,7 +1830,7 @@ const ROMListEntry ROMList[] = {0x45564E43, 0x10000000, 0x00000005}, {0x45564F59, 0x00800000, 0x00000001}, {0x45565041, 0x00800000, 0x00000002}, - {0x45565042, 0x00800000, 0x00000004}, + {0x45565042, 0x00800000, 0x00000002}, {0x45565043, 0x04000000, 0x00000002}, {0x45565056, 0x04000000, 0x00000002}, {0x45565059, 0x04000000, 0x00000001}, @@ -6804,4 +6804,4 @@ const ROMListEntry ROMList[] = const size_t ROMListEntryCount = sizeof(ROMList) / sizeof(ROMListEntry); -} \ No newline at end of file +} diff --git a/src/debug/GdbCmds.cpp b/src/debug/GdbCmds.cpp index c4706138..c2b1f9c4 100644 --- a/src/debug/GdbCmds.cpp +++ b/src/debug/GdbCmds.cpp @@ -1,12 +1,13 @@ #include #include +#include #include "../CRC32.h" #include "../Platform.h" #include "hexutil.h" -#include "GdbProto.h" +#include "GdbStub.h" using namespace melonDS; using Platform::Log; @@ -878,6 +879,7 @@ ExecResult GdbStub::Handle_v_Stopped(GdbStub* stub, const u8* cmd, ssize_t len) ExecResult GdbStub::Handle_v_MustReplyEmpty(GdbStub* stub, const u8* cmd, ssize_t len) { + printf("must reply empty\n"); stub->Resp(NULL, 0); return ExecResult::Ok; } @@ -886,6 +888,7 @@ ExecResult GdbStub::Handle_v_Cont(GdbStub* stub, const u8* cmd, ssize_t len) { if (len < 1) { + printf("insufficient length"); stub->RespStr("E01"); return ExecResult::Ok; } @@ -902,6 +905,7 @@ ExecResult GdbStub::Handle_v_Cont(GdbStub* stub, const u8* cmd, ssize_t len) stub->RespStr("OK"); return ExecResult::MustBreak; default: + printf("invalid continue %c %s\n", cmd[0], cmd); stub->RespStr("E01"); return ExecResult::Ok; } diff --git a/src/debug/GdbProto.cpp b/src/debug/GdbProto.cpp index bd7e6e65..a69538db 100644 --- a/src/debug/GdbProto.cpp +++ b/src/debug/GdbProto.cpp @@ -5,12 +5,14 @@ #include #endif +#include #include #include #include #include #include #include +#include #ifndef _WIN32 #include @@ -21,8 +23,7 @@ #include "../Platform.h" #include "hexutil.h" -#include "GdbProto.h" - +#include "GdbStub.h" using namespace melonDS; using Platform::Log; @@ -42,87 +43,128 @@ namespace Gdb * vKill;pid * qRcmd? qSupported? */ -u8 Cmdbuf[GDBPROTO_BUFFER_CAPACITY]; -ssize_t Cmdlen; -namespace Proto + +Gdb::ReadResult GdbStub::TryParsePacket(size_t start, size_t& packetStart, size_t& packetSize, size_t& packetContentSize) { - -u8 PacketBuf[GDBPROTO_BUFFER_CAPACITY]; -u8 RespBuf[GDBPROTO_BUFFER_CAPACITY+5]; - -ReadResult MsgRecv(int connfd, u8 cmd_dest[/*static GDBPROTO_BUFFER_CAPACITY*/]) -{ - static ssize_t dataoff = 0; - - ssize_t recv_total = dataoff; - ssize_t cksumoff = -1; - u8 sum = 0; - - bool first = true; - - //printf("--- dataoff=%zd\n", dataoff); - if (dataoff != 0) { - printf("--- got preexisting: %s\n", PacketBuf); - - ssize_t datastart = 0; - while (true) + RecvBuffer[RecvBufferFilled] = '\0'; + //Log(LogLevel::Debug, "[GDB] Trying to parse packet %s %d %d\n", &RecvBuffer[0], start, RecvBufferFilled); + size_t i = start; + while (i < RecvBufferFilled) + { + char curChar = RecvBuffer[i++]; + if (curChar == '\x04') return ReadResult::Eof; + else if (curChar == '\x03') { - if (PacketBuf[datastart] == '\x04') return ReadResult::Eof; - else if (PacketBuf[datastart] == '+' || PacketBuf[datastart] == '-') + packetStart = i - 1; + packetSize = packetContentSize = 1; + return ReadResult::Break; + } + else if (curChar == '+' || curChar == '-') continue; + else if (curChar == '$') + { + packetStart = i; + uint8_t checksumGot = 0; + while (i < RecvBufferFilled) { - /*if (PacketBuf[datastart] == '+') SendAck(connfd); - else SendNak(connfd);*/ - ++datastart; - continue; - } - else if (PacketBuf[datastart] == '$') - { - ++datastart; - break; - } - else - { - __builtin_trap(); - return ReadResult::Wut; + curChar = RecvBuffer[i]; + if (curChar == '#' && i + 2 < RecvBufferFilled) + { + u8 checksumShould = (hex2nyb(RecvBuffer[i+1]) << 4) + | hex2nyb(RecvBuffer[i+2]); + + Log(LogLevel::Debug, "[GDB] found pkt, checksumGot: %02x vs %02x\n", checksumShould, checksumGot); + + if (checksumShould != checksumGot) + { + return ReadResult::CksumErr; + } + + packetContentSize = i - packetStart; + packetSize = packetContentSize + 3; + return ReadResult::CmdRecvd; + } + else + { + checksumGot += curChar; + } + + i++; } } - printf("--- datastart=%zd\n", datastart); - - for (ssize_t i = datastart; i < dataoff; ++i) + else { - if (PacketBuf[i] == '#') - { - cksumoff = i + 1; - printf("--- cksumoff=%zd\n", cksumoff); - break; - } - - sum += PacketBuf[i]; - } - - if (cksumoff >= 0) - { - recv_total = dataoff - datastart + 1; - dataoff = cksumoff + 2 - datastart + 1; - cksumoff -= datastart - 1; - - memmove(&PacketBuf[1], &PacketBuf[datastart], recv_total); - PacketBuf[0] = '$'; - PacketBuf[recv_total] = 0; - - printf("=== cksumoff=%zi recv_total=%zi datastart=%zi dataoff=%zi\n==> %s\n", - cksumoff, recv_total, datastart, dataoff, PacketBuf); - //break; + Log(LogLevel::Error, "[GDB] Received unknown character %c (%d)\n", curChar, curChar); + return ReadResult::Wut; } } - while (cksumoff < 0) - { - u8* pkt = &PacketBuf[dataoff]; - ssize_t n, blehoff = 0; + return ReadResult::NoPacket; +} - memset(pkt, 0, sizeof(PacketBuf) - dataoff); +Gdb::ReadResult GdbStub::ParseAndSetupPacket() +{ + // This complicated logic seems to be unfortunately necessary + // to handle the case of packet resends when we answered too slowly. + // GDB only expects a single response (as it assumes the previous packet was dropped) + size_t i = 0; + size_t prevPacketStart = SIZE_MAX, prevPacketSize, prevPacketContentSize; + size_t packetStart, packetSize, packetContentSize; + ReadResult result, prevResult; + while (true) + { + ReadResult result = TryParsePacket(i, packetStart, packetSize, packetContentSize); + if (result == ReadResult::NoPacket) + break; + if (result != ReadResult::CmdRecvd && result != ReadResult::Break) + return result; + + // looks like there is a different packet coming up + // so we quit here + if (prevPacketStart != SIZE_MAX && + (packetContentSize != prevPacketContentSize || + memcmp(&RecvBuffer[packetStart], &RecvBuffer[prevPacketStart], prevPacketContentSize) != 0)) + { + Log(LogLevel::Debug, "[GDB] found differing packet further back %zu %zu\n", packetContentSize, prevPacketContentSize); + break; + } + + i = packetStart + packetSize; + prevPacketStart = packetStart; + prevPacketSize = packetSize; + prevPacketContentSize = packetContentSize; + prevResult = result; + } + + if (prevPacketStart != SIZE_MAX) + { + memcpy(&Cmdbuf[0], &RecvBuffer[prevPacketStart], prevPacketContentSize); + Cmdbuf[prevPacketContentSize] = '\0'; + Cmdlen = static_cast(prevPacketContentSize); + + RecvBufferFilled -= prevPacketStart + prevPacketSize; + if (RecvBufferFilled > 0) + memmove(&RecvBuffer[0], &RecvBuffer[prevPacketStart + prevPacketSize], RecvBufferFilled); + + assert(prevResult == ReadResult::CmdRecvd || prevResult == ReadResult::Break); + return prevResult; + } + + assert(result == ReadResult::NoPacket); + return ReadResult::NoPacket; +} + +ReadResult GdbStub::MsgRecv() +{ + { + ReadResult result = ParseAndSetupPacket(); + if (result != ReadResult::NoPacket) + return result; + } + + bool first = true; + while (true) + { int flag = 0; #if MOCKTEST static bool FIRST = false; @@ -142,113 +184,35 @@ ReadResult MsgRecv(int connfd, u8 cmd_dest[/*static GDBPROTO_BUFFER_CAPACITY*/]) #else #ifndef _WIN32 if (first) flag |= MSG_DONTWAIT; - n = recv(connfd, pkt, sizeof(PacketBuf) - dataoff, flag); + ssize_t receivedNum = recv(ConnFd, &RecvBuffer[RecvBufferFilled], sizeof(RecvBuffer) - RecvBufferFilled, flag); + Log(LogLevel::Debug, "[GDB] receiving from stream %d\n", receivedNum); #else // fuck windows - n = recv(connfd, (char*)pkt, sizeof(PacketBuf) - dataoff, flag); + ssize_t receivedNum = recv(ConnFd, (char*)&RecvBuffer[RecvBufferFilled], sizeof(RecvBuffer) - RecvBufferFilled, flag); #endif #endif - if (n <= 0) + if (receivedNum <= 0) { if (first) return ReadResult::NoPacket; else { - Log(LogLevel::Debug, "[GDB] recv() error %zi, errno=%d (%s)\n", n, errno, strerror(errno)); + Log(LogLevel::Debug, "[GDB] recv() error %zi, errno=%d (%s)\n", receivedNum, errno, strerror(errno)); return ReadResult::Eof; } } + RecvBufferFilled += static_cast(receivedNum); - Log(LogLevel::Debug, "[GDB] recv() %zd bytes: '%s' (%02x)\n", n, pkt, pkt[0]); - first = false; - - do - { - if (dataoff == 0) - { - if (pkt[blehoff] == '\x04') return ReadResult::Eof; - else if (pkt[blehoff] == '\x03') return ReadResult::Break; - else if (pkt[blehoff] != '$') - { - ++blehoff; - --n; - } - else break; - - if (n == 0) goto next_outer; - } - } - while (true); - - if (blehoff > 0) - { - memmove(pkt, &pkt[blehoff], n - blehoff + 1); - n -= blehoff - 1; // ??? - } - - recv_total += n; - - Log(LogLevel::Debug, "[GDB] recv() after skipping: n=%zd, recv_total=%zd\n", n, recv_total); - - for (ssize_t i = (dataoff == 0) ? 1 : 0; i < n; ++i) - { - u8 v = pkt[i]; - if (v == '#') - { - cksumoff = dataoff + i + 1; - break; - } - - sum += pkt[i]; - } - - if (cksumoff < 0) - { - // oops, need more data - dataoff += n; - } - - next_outer:; + ReadResult result = ParseAndSetupPacket(); + if (result != ReadResult::NoPacket) + return result; } - - u8 ck = (hex2nyb(PacketBuf[cksumoff+0]) << 4) - | hex2nyb(PacketBuf[cksumoff+1]); - - Log(LogLevel::Debug, "[GDB] got pkt, checksum: %02x vs %02x\n", ck, sum); - - if (ck != sum) - { - //__builtin_trap(); - return ReadResult::CksumErr; - } - - if (cksumoff + 2 > recv_total) - { - Log(LogLevel::Error, "[GDB] BIG MISTAKE: %zi > %zi which shouldn't happen!\n", cksumoff + 2, recv_total); - //__builtin_trap(); - return ReadResult::Wut; - } - else - { - Cmdlen = cksumoff - 2; - memcpy(Cmdbuf, &PacketBuf[1], Cmdlen); - Cmdbuf[Cmdlen] = 0; - - if (cksumoff + 2 < recv_total) { - // huh, we have the start of the next packet - dataoff = recv_total - (cksumoff + 2); - memmove(PacketBuf, &PacketBuf[cksumoff + 2], (size_t)dataoff); - PacketBuf[dataoff] = 0; - Log(LogLevel::Debug, "[GDB] got more: cksumoff=%zd, recvtotal=%zd, remain=%zd\n==> %s\n", cksumoff, recv_total, dataoff, PacketBuf); - } - else dataoff = 0; - } - - return ReadResult::CmdRecvd; } -int SendAck(int connfd) +int GdbStub::SendAck() { + if (NoAck) return 1; + Log(LogLevel::Debug, "[GDB] send ack\n"); u8 v = '+'; #if MOCKTEST @@ -257,14 +221,16 @@ int SendAck(int connfd) #ifdef _WIN32 // fuck windows - return send(connfd, (const char*)&v, 1, 0); + return send(ConnFd, (const char*)&v, 1, 0); #else - return send(connfd, &v, 1, 0); + return send(ConnFd, &v, 1, 0); #endif } -int SendNak(int connfd) +int GdbStub::SendNak() { + if (NoAck) return 1; + Log(LogLevel::Debug, "[GDB] send nak\n"); u8 v = '-'; #if MOCKTEST @@ -273,13 +239,13 @@ int SendNak(int connfd) #ifdef _WIN32 // fuck windows - return send(connfd, (const char*)&v, 1, 0); + return send(ConnFd, (const char*)&v, 1, 0); #else - return send(connfd, &v, 1, 0); + return send(ConnFd, &v, 1, 0); #endif } -int WaitAckBlocking(int connfd, u8* ackp, int to_ms) +int GdbStub::WaitAckBlocking(u8* ackp, int to_ms) { #if MOCKTEST *ackp = '+'; @@ -289,18 +255,18 @@ int WaitAckBlocking(int connfd, u8* ackp, int to_ms) #ifdef _WIN32 fd_set infd, outfd, errfd; FD_ZERO(&infd); FD_ZERO(&outfd); FD_ZERO(&errfd); - FD_SET(connfd, &infd); + FD_SET(ConnFd, &infd); struct timeval to; to.tv_sec = to_ms / 1000; to.tv_usec = (to_ms % 1000) * 1000; - int r = select(connfd+1, &infd, &outfd, &errfd, &to); + int r = select(ConnFd+1, &infd, &outfd, &errfd, &to); - if (FD_ISSET(connfd, &errfd)) return -1; - else if (FD_ISSET(connfd, &infd)) + if (FD_ISSET(ConnFd, &errfd)) return -1; + else if (FD_ISSET(ConnFd, &infd)) { - r = recv(connfd, (char*)ackp, 1, 0); + r = recv(ConnFd, (char*)ackp, 1, 0); if (r < 0) return r; return 0; } @@ -309,7 +275,7 @@ int WaitAckBlocking(int connfd, u8* ackp, int to_ms) #else struct pollfd pfd; - pfd.fd = connfd; + pfd.fd = ConnFd; pfd.events = POLLIN; pfd.revents = 0; @@ -319,14 +285,14 @@ int WaitAckBlocking(int connfd, u8* ackp, int to_ms) if (pfd.revents & (POLLHUP|POLLERR)) return -69; - r = recv(connfd, ackp, 1, 0); + r = recv(ConnFd, ackp, 1, 0); if (r < 0) return r; return (r == 1) ? 0 : -1; #endif } -int Resp(int connfd, const u8* data1, size_t len1, const u8* data2, size_t len2, bool noack) +int GdbStub::Resp(const u8* data1, size_t len1, const u8* data2, size_t len2, bool noack) { u8 cksum = 0; int tries = 0; @@ -359,22 +325,22 @@ int Resp(int connfd, const u8* data1, size_t len1, const u8* data2, size_t len2, ssize_t r; u8 ack; - Log(LogLevel::Debug, "[GDB] send resp: '%s'\n", RespBuf); + Log(LogLevel::Debug, "[GDB] send resp: '%s'\n", &RespBuf[0]); #if MOCKTEST r = totallen+4; #else #ifdef _WIN32 - r = send(connfd, (const char*)RespBuf, totallen+4, 0); + r = send(ConnFd, (const char*)&RespBuf[0], totallen+4, 0); #else - r = send(connfd, RespBuf, totallen+4, 0); + r = send(ConnFd, &RespBuf[0], totallen+4, 0); #endif #endif if (r < 0) return r; if (noack) break; - r = WaitAckBlocking(connfd, &ack, 2000); - //Log(LogLevel::Debug, "[GDB] got ack: '%c'\n", ack); + r = WaitAckBlocking(&ack, 2000); + Log(LogLevel::Debug, "[GDB] got ack: '%c'\n", ack); if (r == 0 && ack == '+') break; ++tries; @@ -386,5 +352,4 @@ int Resp(int connfd, const u8* data1, size_t len1, const u8* data2, size_t len2, } -} diff --git a/src/debug/GdbProto.h b/src/debug/GdbProto.h deleted file mode 100644 index 68122f06..00000000 --- a/src/debug/GdbProto.h +++ /dev/null @@ -1,41 +0,0 @@ - -#ifndef GDBPROTO_H_ -#define GDBPROTO_H_ - -#include -#include - -#include "GdbStub.h" /* class GdbStub */ - - -#define MOCKTEST 0 - - -namespace Gdb { - -using namespace melonDS; -constexpr int GDBPROTO_BUFFER_CAPACITY = 1024+128; - -extern u8 Cmdbuf[GDBPROTO_BUFFER_CAPACITY]; -extern ssize_t Cmdlen; - -namespace Proto { - -extern u8 PacketBuf[GDBPROTO_BUFFER_CAPACITY]; -extern u8 RespBuf[GDBPROTO_BUFFER_CAPACITY+5]; - -Gdb::ReadResult MsgRecv(int connfd, u8 cmd_dest[/*static GDBPROTO_BUFFER_CAPACITY*/]); - -int SendAck(int connfd); -int SendNak(int connfd); - -int Resp(int connfd, const u8* data1, size_t len1, const u8* data2, size_t len2, bool noack); - -int WaitAckBlocking(int connfd, u8* ackp, int to_ms); - -} - -} - -#endif - diff --git a/src/debug/GdbStub.cpp b/src/debug/GdbStub.cpp index 53101cec..14a8670a 100644 --- a/src/debug/GdbStub.cpp +++ b/src/debug/GdbStub.cpp @@ -23,7 +23,7 @@ #include "../Platform.h" -#include "GdbProto.h" +#include "GdbStub.h" using namespace melonDS; using Platform::Log; @@ -304,7 +304,7 @@ StubState GdbStub::Poll(bool wait) if (ConnFd < 0) return StubState::NoConn; u8 a; - if (Proto::WaitAckBlocking(ConnFd, &a, 1000) < 0) + if (WaitAckBlocking(&a, 1000) < 0) { Log(LogLevel::Error, "[GDB] inital handshake: didn't receive inital ack!\n"); close(ConnFd); @@ -380,7 +380,7 @@ StubState GdbStub::Poll(bool wait) #endif #endif - ReadResult res = Proto::MsgRecv(ConnFd, Cmdbuf); + ReadResult res = MsgRecv(); switch (res) { @@ -422,11 +422,12 @@ ExecResult GdbStub::SubcmdExec(const u8* cmd, ssize_t len, const SubcmdHandler* // check if prefix matches if (!strncmp((const char*)cmd, handlers[i].SubStr, strlen(handlers[i].SubStr))) { - if (SendAck() < 0) + // ack should have already been sent by CmdExec + /*if (SendAck() < 0) { Log(LogLevel::Error, "[GDB] send packet ack failed!\n"); return ExecResult::NetErr; - } + }*/ return handlers[i].Handler(this, &cmd[strlen(handlers[i].SubStr)], len-strlen(handlers[i].SubStr)); } } @@ -444,7 +445,7 @@ ExecResult GdbStub::SubcmdExec(const u8* cmd, ssize_t len, const SubcmdHandler* ExecResult GdbStub::CmdExec(const CmdHandler* handlers) { - Log(LogLevel::Debug, "[GDB] command in: '%s'\n", Cmdbuf); + Log(LogLevel::Debug, "[GDB] command in: '%s'\n", &Cmdbuf[0]); for (size_t i = 0; handlers[i].Handler != NULL; ++i) { @@ -644,24 +645,13 @@ StubState GdbStub::CheckWatchpt(u32 addr, int kind, bool enter, bool stay) return StubState::CheckNoHit; } -int GdbStub::SendAck() -{ - if (NoAck) return 1; - return Proto::SendAck(ConnFd); -} -int GdbStub::SendNak() -{ - if (NoAck) return 1; - return Proto::SendNak(ConnFd); -} - int GdbStub::Resp(const u8* data1, size_t len1, const u8* data2, size_t len2) { - return Proto::Resp(ConnFd, data1, len1, data2, len2, NoAck); + return Resp(data1, len1, data2, len2, NoAck); } int GdbStub::RespC(const char* data1, size_t len1, const u8* data2, size_t len2) { - return Proto::Resp(ConnFd, (const u8*)data1, len1, data2, len2, NoAck); + return Resp((const u8*)data1, len1, data2, len2, NoAck); } #if defined(__GCC__) || defined(__clang__) __attribute__((__format__(printf, 2/*includes implicit this*/, 3))) @@ -670,19 +660,19 @@ int GdbStub::RespFmt(const char* fmt, ...) { va_list args; va_start(args, fmt); - int r = vsnprintf((char*)&Proto::RespBuf[1], sizeof(Proto::RespBuf)-5, fmt, args); + int r = vsnprintf((char*)&RespBuf[1], sizeof(RespBuf)-5, fmt, args); va_end(args); if (r < 0) return r; - if ((size_t)r >= sizeof(Proto::RespBuf)-5) + if ((size_t)r >= sizeof(RespBuf)-5) { Log(LogLevel::Error, "[GDB] truncated response in send_fmt()! (lost %zd bytes)\n", - (ssize_t)r - (ssize_t)(sizeof(Proto::RespBuf)-5)); - r = sizeof(Proto::RespBuf)-5; + (ssize_t)r - (ssize_t)(sizeof(RespBuf)-5)); + r = sizeof(RespBuf)-5; } - return Resp(&Proto::RespBuf[1], r); + return Resp(&RespBuf[1], r); } int GdbStub::RespStr(const char* str) diff --git a/src/debug/GdbStub.h b/src/debug/GdbStub.h index 6461a354..3e4a0efe 100644 --- a/src/debug/GdbStub.h +++ b/src/debug/GdbStub.h @@ -86,6 +86,8 @@ enum class ExecResult Continue }; +constexpr int GDBPROTO_BUFFER_CAPACITY = 1024+128; + class GdbStub; typedef ExecResult (*GdbProtoCmd)(GdbStub* stub, const u8* cmd, ssize_t len); @@ -141,9 +143,6 @@ public: Gdb::ExecResult CmdExec(const CmdHandler* handlers); public: - int SendAck(); - int SendNak(); - int Resp(const u8* data1, size_t len1, const u8* data2 = NULL, size_t len2 = 0); int RespC(const char* data1, size_t len1, const u8* data2 = NULL, size_t len2 = 0); #if defined(__GCC__) || defined(__clang__) @@ -158,7 +157,20 @@ private: void Disconnect(); StubState HandlePacket(); -private: + Gdb::ReadResult MsgRecv(); + + Gdb::ReadResult TryParsePacket(size_t start, size_t& packetStart, size_t& packetSize, size_t& packetContentSize); + Gdb::ReadResult ParseAndSetupPacket(); + + void SetupCommand(size_t packetStart, size_t packetSize); + + int SendAck(); + int SendNak(); + + int Resp(const u8* data1, size_t len1, const u8* data2, size_t len2, bool noack); + + int WaitAckBlocking(u8* ackp, int to_ms); + StubCallbacks* Cb; //struct sockaddr_in server, client; @@ -172,6 +184,13 @@ private: bool StatFlag; bool NoAck; + std::array RecvBuffer; + u32 RecvBufferFilled = 0; + std::array RespBuf; + + std::array Cmdbuf; + ssize_t Cmdlen; + std::map BpList; std::vector WpList; diff --git a/src/frontend/qt_sdl/CMakeLists.txt b/src/frontend/qt_sdl/CMakeLists.txt index 9cd784aa..524fa13d 100644 --- a/src/frontend/qt_sdl/CMakeLists.txt +++ b/src/frontend/qt_sdl/CMakeLists.txt @@ -160,7 +160,12 @@ if (BUILD_STATIC) if (WIN32 AND USE_QT6) qt_import_plugins(melonDS INCLUDE Qt::QModernWindowsStylePlugin) endif() - target_link_options(melonDS PRIVATE -static) + if (USE_VCPKG AND UNIX AND NOT APPLE) + pkg_check_modules(ALSA REQUIRED IMPORTED_TARGET alsa) + target_link_libraries(melonDS PRIVATE PkgConfig::ALSA) + else() + target_link_options(melonDS PRIVATE -static) + endif() endif() target_include_directories(melonDS PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}") diff --git a/src/frontend/qt_sdl/Config.cpp b/src/frontend/qt_sdl/Config.cpp index 5997d543..0a161b6f 100644 --- a/src/frontend/qt_sdl/Config.cpp +++ b/src/frontend/qt_sdl/Config.cpp @@ -55,7 +55,6 @@ DefaultList DefaultInts = {"Screen.VSyncInterval", 1}, {"3D.Renderer", renderer3D_Software}, {"3D.GL.ScaleFactor", 1}, - {"MaxFPS", 1000}, #ifdef JIT_ENABLED {"JIT.MaxBlockSize", 32}, #endif @@ -71,7 +70,7 @@ DefaultList DefaultInts = #ifdef GDBSTUB_ENABLED {"Instance*.Gdb.ARM7.Port", 3334}, {"Instance*.Gdb.ARM9.Port", 3333}, -#endif, +#endif {"LAN.HostNumPlayers", 16}, }; @@ -120,6 +119,13 @@ DefaultList DefaultStrings = {"Instance*.Firmware.Username", "melonDS"} }; +DefaultList DefaultDoubles = +{ + {"TargetFPS", 60.0}, + {"FastForwardFPS", 1000.0}, + {"SlowmoFPS", 30.0}, +}; + LegacyEntry LegacyFile[] = { {"Key_A", 0, "Keyboard.A", true}, @@ -153,7 +159,7 @@ LegacyEntry LegacyFile[] = {"HKKey_Pause", 0, "Keyboard.HK_Pause", true}, {"HKKey_Reset", 0, "Keyboard.HK_Reset", true}, {"HKKey_FastForward", 0, "Keyboard.HK_FastForward", true}, - {"HKKey_FastForwardToggle", 0, "Keyboard.HK_FastForwardToggle", true}, + {"HKKey_FastForwardToggle", 0, "Keyboard.HK_FrameLimitToggle", true}, {"HKKey_FullscreenToggle", 0, "Keyboard.HK_FullscreenToggle", true}, {"HKKey_SwapScreens", 0, "Keyboard.HK_SwapScreens", true}, {"HKKey_SwapScreenEmphasis", 0, "Keyboard.HK_SwapScreenEmphasis", true}, @@ -169,7 +175,7 @@ LegacyEntry LegacyFile[] = {"HKJoy_Pause", 0, "Joystick.HK_Pause", true}, {"HKJoy_Reset", 0, "Joystick.HK_Reset", true}, {"HKJoy_FastForward", 0, "Joystick.HK_FastForward", true}, - {"HKJoy_FastForwardToggle", 0, "Joystick.HK_FastForwardToggle", true}, + {"HKJoy_FastForwardToggle", 0, "Joystick.HK_FrameLimitToggle", true}, {"HKJoy_FullscreenToggle", 0, "Joystick.HK_FullscreenToggle", true}, {"HKJoy_SwapScreens", 0, "Joystick.HK_SwapScreens", true}, {"HKJoy_SwapScreenEmphasis", 0, "Joystick.HK_SwapScreenEmphasis", true}, @@ -434,6 +440,18 @@ std::string Array::GetString(const int id) return tval.as_string(); } +double Array::GetDouble(const int id) +{ + while (Data.size() < id+1) + Data.push_back(0.0); + + toml::value& tval = Data[id]; + if (!tval.is_floating()) + tval = 0.0; + + return tval.as_floating(); +} + void Array::SetInt(const int id, int val) { while (Data.size() < id+1) @@ -470,6 +488,15 @@ void Array::SetString(const int id, const std::string& val) tval = val; } +void Array::SetDouble(const int id, double val) +{ + while (Data.size() < id+1) + Data.push_back(0.0); + + toml::value& tval = Data[id]; + tval = val; +} + /*Table::Table()// : Data(toml::value()) { @@ -562,6 +589,15 @@ std::string Table::GetString(const std::string& path) return tval.as_string(); } +double Table::GetDouble(const std::string& path) +{ + toml::value& tval = ResolvePath(path); + if (!tval.is_floating()) + tval = FindDefault(path, 0.0, DefaultDoubles); + + return tval.as_floating(); +} + void Table::SetInt(const std::string& path, int val) { std::string rngkey = GetDefaultKey(PathPrefix+path); @@ -593,6 +629,12 @@ void Table::SetString(const std::string& path, const std::string& val) tval = val; } +void Table::SetDouble(const std::string& path, double val) +{ + toml::value& tval = ResolvePath(path); + tval = val; +} + toml::value& Table::ResolvePath(const std::string& path) { toml::value* ret = &Data; diff --git a/src/frontend/qt_sdl/Config.h b/src/frontend/qt_sdl/Config.h index 4ca88b46..9e6d3ea4 100644 --- a/src/frontend/qt_sdl/Config.h +++ b/src/frontend/qt_sdl/Config.h @@ -61,11 +61,13 @@ public: int64_t GetInt64(const int id); bool GetBool(const int id); std::string GetString(const int id); + double GetDouble(const int id); void SetInt(const int id, int val); void SetInt64(const int id, int64_t val); void SetBool(const int id, bool val); void SetString(const int id, const std::string& val); + void SetDouble(const int id, double val); // convenience @@ -99,11 +101,13 @@ public: int64_t GetInt64(const std::string& path); bool GetBool(const std::string& path); std::string GetString(const std::string& path); + double GetDouble(const std::string& path); void SetInt(const std::string& path, int val); void SetInt64(const std::string& path, int64_t val); void SetBool(const std::string& path, bool val); void SetString(const std::string& path, const std::string& val); + void SetDouble(const std::string& path, double val); // convenience diff --git a/src/frontend/qt_sdl/EmuInstance.cpp b/src/frontend/qt_sdl/EmuInstance.cpp index b29d2b09..54bae531 100644 --- a/src/frontend/qt_sdl/EmuInstance.cpp +++ b/src/frontend/qt_sdl/EmuInstance.cpp @@ -88,7 +88,31 @@ EmuInstance::EmuInstance(int inst) : deleting(false), cheatsOn = localCfg.GetBool("EnableCheats"); doLimitFPS = globalCfg.GetBool("LimitFPS"); - maxFPS = globalCfg.GetInt("MaxFPS"); + + double val = globalCfg.GetDouble("TargetFPS"); + if (val == 0.0) + { + Platform::Log(Platform::LogLevel::Error, "Target FPS in config invalid\n"); + targetFPS = 1.0 / 60.0; + } + else targetFPS = 1.0 / val; + + val = globalCfg.GetDouble("FastForwardFPS"); + if (val == 0.0) + { + Platform::Log(Platform::LogLevel::Error, "Fast-Forward FPS in config invalid\n"); + fastForwardFPS = 1.0 / 60.0; + } + else fastForwardFPS = 1.0 / val; + + val = globalCfg.GetDouble("SlowmoFPS"); + if (val == 0.0) + { + Platform::Log(Platform::LogLevel::Error, "Slow-Mo FPS in config invalid\n"); + slowmoFPS = 1.0 / 60.0; + } + else slowmoFPS = 1.0 / val; + doAudioSync = globalCfg.GetBool("AudioSync"); mpAudioMode = globalCfg.GetInt("MP.AudioMode"); @@ -1153,14 +1177,14 @@ bool EmuInstance::updateConsole(UpdateConsoleNDSArgs&& _ndsargs, UpdateConsoleGB #endif #ifdef GDBSTUB_ENABLED - Config::Table gdbopt = globalCfg.GetTable("Gdb"); + Config::Table gdbopt = localCfg.GetTable("Gdb"); GDBArgs _gdbargs { static_cast(gdbopt.GetInt("ARM7.Port")), static_cast(gdbopt.GetInt("ARM9.Port")), gdbopt.GetBool("ARM7.BreakOnStartup"), gdbopt.GetBool("ARM9.BreakOnStartup"), }; - auto gdbargs = gdbopt.GetBool("Enable") ? std::make_optional(_gdbargs) : std::nullopt; + auto gdbargs = gdbopt.GetBool("Enabled") ? std::make_optional(_gdbargs) : std::nullopt; #else optional gdbargs = std::nullopt; #endif diff --git a/src/frontend/qt_sdl/EmuInstance.h b/src/frontend/qt_sdl/EmuInstance.h index 39c187c2..04290f56 100644 --- a/src/frontend/qt_sdl/EmuInstance.h +++ b/src/frontend/qt_sdl/EmuInstance.h @@ -36,7 +36,7 @@ enum HK_Pause, HK_Reset, HK_FastForward, - HK_FastForwardToggle, + HK_FrameLimitToggle, HK_FullscreenToggle, HK_SwapScreens, HK_SwapScreenEmphasis, @@ -46,6 +46,9 @@ enum HK_PowerButton, HK_VolumeUp, HK_VolumeDown, + HK_SlowMo, + HK_FastForwardToggle, + HK_SlowMoToggle, HK_MAX }; @@ -252,7 +255,12 @@ public: std::unique_ptr firmwareSave; bool doLimitFPS; - int maxFPS; + double curFPS; + double targetFPS; + double fastForwardFPS; + double slowmoFPS; + bool fastForwardToggled; + bool slowmoToggled; bool doAudioSync; private: diff --git a/src/frontend/qt_sdl/EmuInstanceInput.cpp b/src/frontend/qt_sdl/EmuInstanceInput.cpp index ddaca8f0..bb06c242 100644 --- a/src/frontend/qt_sdl/EmuInstanceInput.cpp +++ b/src/frontend/qt_sdl/EmuInstanceInput.cpp @@ -47,7 +47,7 @@ const char* EmuInstance::hotkeyNames[HK_MAX] = "HK_Pause", "HK_Reset", "HK_FastForward", - "HK_FastForwardToggle", + "HK_FrameLimitToggle", "HK_FullscreenToggle", "HK_SwapScreens", "HK_SwapScreenEmphasis", @@ -56,7 +56,10 @@ const char* EmuInstance::hotkeyNames[HK_MAX] = "HK_FrameStep", "HK_PowerButton", "HK_VolumeUp", - "HK_VolumeDown" + "HK_VolumeDown", + "HK_SlowMo", + "HK_FastForwardToggle", + "HK_SlowMoToggle" }; diff --git a/src/frontend/qt_sdl/EmuSettingsDialog.cpp b/src/frontend/qt_sdl/EmuSettingsDialog.cpp index d612ac83..7a6c0f40 100644 --- a/src/frontend/qt_sdl/EmuSettingsDialog.cpp +++ b/src/frontend/qt_sdl/EmuSettingsDialog.cpp @@ -95,7 +95,7 @@ EmuSettingsDialog::EmuSettingsDialog(QWidget* parent) : QDialog(parent), ui(new #endif #ifdef GDBSTUB_ENABLED - ui->cbGdbEnabled->setChecked(cfg.GetBool("Gdb.Enabled")); + ui->cbGdbEnabled->setChecked(instcfg.GetBool("Gdb.Enabled")); ui->intGdbPortA7->setValue(instcfg.GetInt("Gdb.ARM7.Port")); ui->intGdbPortA9->setValue(instcfg.GetInt("Gdb.ARM9.Port")); ui->cbGdbBOSA7->setChecked(instcfg.GetBool("Gdb.ARM7.BreakOnStartup")); @@ -286,7 +286,7 @@ void EmuSettingsDialog::done(int r) cfg.SetBool("JIT.FastMemory", ui->chkJITFastMemory->isChecked()); #endif #ifdef GDBSTUB_ENABLED - cfg.SetBool("Gdb.Enabled", ui->cbGdbEnabled->isChecked()); + instcfg.SetBool("Gdb.Enabled", ui->cbGdbEnabled->isChecked()); instcfg.SetInt("Gdb.ARM7.Port", ui->intGdbPortA7->value()); instcfg.SetInt("Gdb.ARM9.Port", ui->intGdbPortA9->value()); instcfg.SetBool("Gdb.ARM7.BreakOnStartup", ui->cbGdbBOSA7->isChecked()); diff --git a/src/frontend/qt_sdl/EmuThread.cpp b/src/frontend/qt_sdl/EmuThread.cpp index ae66e1ba..4ce4efda 100644 --- a/src/frontend/qt_sdl/EmuThread.cpp +++ b/src/frontend/qt_sdl/EmuThread.cpp @@ -149,12 +149,17 @@ void EmuThread::run() char melontitle[100]; + bool fastforward = false; + bool slowmo = false; + emuInstance->fastForwardToggled = false; + emuInstance->slowmoToggled = false; + while (emuStatus != emuStatus_Exit) { MPInterface::Get().Process(); emuInstance->inputProcess(); - if (emuInstance->hotkeyPressed(HK_FastForwardToggle)) emit windowLimitFPSChange(); + if (emuInstance->hotkeyPressed(HK_FrameLimitToggle)) emit windowLimitFPSChange(); if (emuInstance->hotkeyPressed(HK_Pause)) emuTogglePause(); if (emuInstance->hotkeyPressed(HK_Reset)) emuReset(); @@ -332,22 +337,34 @@ void EmuThread::run() emit windowUpdate(); winUpdateCount = 0; } + + if (emuInstance->hotkeyPressed(HK_FastForwardToggle)) emuInstance->fastForwardToggled = !emuInstance->fastForwardToggled; + if (emuInstance->hotkeyPressed(HK_SlowMoToggle)) emuInstance->slowmoToggled = !emuInstance->slowmoToggled; - bool fastforward = emuInstance->hotkeyDown(HK_FastForward); + bool enablefastforward = emuInstance->hotkeyDown(HK_FastForward) | emuInstance->fastForwardToggled; + bool enableslowmo = emuInstance->hotkeyDown(HK_SlowMo) | emuInstance->slowmoToggled; if (useOpenGL) { - // when using OpenGL: when toggling fast-forward, change the vsync interval - if (emuInstance->hotkeyPressed(HK_FastForward)) + // when using OpenGL: when toggling fast-forward or slowmo, change the vsync interval + if ((enablefastforward || enableslowmo) && !(fastforward || slowmo)) { emuInstance->setVSyncGL(false); } - else if (emuInstance->hotkeyReleased(HK_FastForward)) + else if (!(enablefastforward || enableslowmo) && (fastforward || slowmo)) { emuInstance->setVSyncGL(true); } } + fastforward = enablefastforward; + slowmo = enableslowmo; + + if (slowmo) emuInstance->curFPS = emuInstance->slowmoFPS; + else if (fastforward) emuInstance->curFPS = emuInstance->fastForwardFPS; + else if (!emuInstance->doLimitFPS) emuInstance->curFPS = 1.0 / 1000.0; + else emuInstance->curFPS = emuInstance->targetFPS; + if (emuInstance->audioDSiVolumeSync && emuInstance->nds->ConsoleType == 1) { DSi* dsi = static_cast(emuInstance->nds); @@ -361,23 +378,19 @@ void EmuThread::run() emuInstance->audioVolume = volumeLevel * (256.0 / 31.0); } - if (emuInstance->doAudioSync && !fastforward) + if (emuInstance->doAudioSync && !(fastforward || slowmo)) emuInstance->audioSync(); double frametimeStep = nlines / (60.0 * 263.0); { - bool limitfps = emuInstance->doLimitFPS && !fastforward; - - double practicalFramelimit = limitfps ? frametimeStep : 1.0 / emuInstance->maxFPS; - double curtime = SDL_GetPerformanceCounter() * perfCountsSec; - frameLimitError += practicalFramelimit - (curtime - lastTime); - if (frameLimitError < -practicalFramelimit) - frameLimitError = -practicalFramelimit; - if (frameLimitError > practicalFramelimit) - frameLimitError = practicalFramelimit; + frameLimitError += emuInstance->curFPS - (curtime - lastTime); + if (frameLimitError < -emuInstance->curFPS) + frameLimitError = -emuInstance->curFPS; + if (frameLimitError > emuInstance->curFPS) + frameLimitError = emuInstance->curFPS; if (round(frameLimitError * 1000.0) > 0.0) { diff --git a/src/frontend/qt_sdl/InputConfig/InputConfigDialog.h b/src/frontend/qt_sdl/InputConfig/InputConfigDialog.h index 64986d62..3337228f 100644 --- a/src/frontend/qt_sdl/InputConfig/InputConfigDialog.h +++ b/src/frontend/qt_sdl/InputConfig/InputConfigDialog.h @@ -49,6 +49,9 @@ static constexpr std::initializer_list hk_general = HK_FrameStep, HK_FastForward, HK_FastForwardToggle, + HK_SlowMo, + HK_SlowMoToggle, + HK_FrameLimitToggle, HK_FullscreenToggle, HK_Lid, HK_Mic, @@ -65,6 +68,9 @@ static constexpr std::initializer_list hk_general_labels = "Reset", "Frame step", "Fast forward", + "Toggle fast forward", + "Slow mo", + "Toggle slow mo", "Toggle FPS limit", "Toggle fullscreen", "Close/open lid", diff --git a/src/frontend/qt_sdl/InterfaceSettingsDialog.cpp b/src/frontend/qt_sdl/InterfaceSettingsDialog.cpp index dfe30d12..2e7e75c9 100644 --- a/src/frontend/qt_sdl/InterfaceSettingsDialog.cpp +++ b/src/frontend/qt_sdl/InterfaceSettingsDialog.cpp @@ -39,7 +39,9 @@ InterfaceSettingsDialog::InterfaceSettingsDialog(QWidget* parent) : QDialog(pare ui->spinMouseHideSeconds->setEnabled(ui->cbMouseHide->isChecked()); ui->spinMouseHideSeconds->setValue(cfg.GetInt("MouseHideSeconds")); ui->cbPauseLostFocus->setChecked(cfg.GetBool("PauseLostFocus")); - ui->spinMaxFPS->setValue(cfg.GetInt("MaxFPS")); + ui->spinTargetFPS->setValue(cfg.GetDouble("TargetFPS")); + ui->spinFFW->setValue(cfg.GetDouble("FastForwardFPS")); + ui->spinSlow->setValue(cfg.GetDouble("SlowmoFPS")); const QList themeKeys = QStyleFactory::keys(); const QString currentTheme = qApp->style()->objectName(); @@ -65,6 +67,41 @@ void InterfaceSettingsDialog::on_cbMouseHide_clicked() ui->spinMouseHideSeconds->setEnabled(ui->cbMouseHide->isChecked()); } +void InterfaceSettingsDialog::on_pbClean_clicked() +{ + ui->spinTargetFPS->setValue(60.0000); +} + +void InterfaceSettingsDialog::on_pbAccurate_clicked() +{ + ui->spinTargetFPS->setValue(59.8261); +} + +void InterfaceSettingsDialog::on_pb2x_clicked() +{ + ui->spinFFW->setValue(ui->spinTargetFPS->value() * 2.0); +} + +void InterfaceSettingsDialog::on_pb3x_clicked() +{ + ui->spinFFW->setValue(ui->spinTargetFPS->value() * 3.0); +} + +void InterfaceSettingsDialog::on_pbMAX_clicked() +{ + ui->spinFFW->setValue(1000.0); +} + +void InterfaceSettingsDialog::on_pbHalf_clicked() +{ + ui->spinSlow->setValue(ui->spinTargetFPS->value() / 2.0); +} + +void InterfaceSettingsDialog::on_pbQuarter_clicked() +{ + ui->spinSlow->setValue(ui->spinTargetFPS->value() / 4.0); +} + void InterfaceSettingsDialog::done(int r) { if (r == QDialog::Accepted) @@ -74,7 +111,18 @@ void InterfaceSettingsDialog::done(int r) cfg.SetBool("MouseHide", ui->cbMouseHide->isChecked()); cfg.SetInt("MouseHideSeconds", ui->spinMouseHideSeconds->value()); cfg.SetBool("PauseLostFocus", ui->cbPauseLostFocus->isChecked()); - cfg.SetInt("MaxFPS", ui->spinMaxFPS->value()); + + double val = ui->spinTargetFPS->value(); + if (val == 0.0) cfg.SetDouble("TargetFPS", 0.0001); + else cfg.SetDouble("TargetFPS", val); + + val = ui->spinFFW->value(); + if (val == 0.0) cfg.SetDouble("FastForwardFPS", 0.0001); + else cfg.SetDouble("FastForwardFPS", val); + + val = ui->spinSlow->value(); + if (val == 0.0) cfg.SetDouble("SlowmoFPS", 0.0001); + else cfg.SetDouble("SlowmoFPS", val); QString themeName = ui->cbxUITheme->currentData().toString(); cfg.SetQString("UITheme", themeName); diff --git a/src/frontend/qt_sdl/InterfaceSettingsDialog.h b/src/frontend/qt_sdl/InterfaceSettingsDialog.h index bbc62df9..c960e560 100644 --- a/src/frontend/qt_sdl/InterfaceSettingsDialog.h +++ b/src/frontend/qt_sdl/InterfaceSettingsDialog.h @@ -60,6 +60,16 @@ private slots: void on_cbMouseHide_clicked(); + void on_pbClean_clicked(); + void on_pbAccurate_clicked(); + + void on_pb2x_clicked(); + void on_pb3x_clicked(); + void on_pbMAX_clicked(); + + void on_pbHalf_clicked(); + void on_pbQuarter_clicked(); + private: Ui::InterfaceSettingsDialog* ui; diff --git a/src/frontend/qt_sdl/InterfaceSettingsDialog.ui b/src/frontend/qt_sdl/InterfaceSettingsDialog.ui index 21d8434e..460a6e99 100644 --- a/src/frontend/qt_sdl/InterfaceSettingsDialog.ui +++ b/src/frontend/qt_sdl/InterfaceSettingsDialog.ui @@ -6,12 +6,12 @@ 0 0 - 337 - 275 + 389 + 356 - + 0 0 @@ -20,6 +20,9 @@ Interface settings - melonDS + + QLayout::SizeConstraint::SetFixedSize + @@ -95,32 +98,209 @@ Framerate - + - - - Fast-forward limit + + + 6 - - spinMaxFPS + + 2 - - - - - - FPS - - - 60 - - - 1000 - - - 1000 - - + + + + Target FPS + + + + + + + Fast-Forward + + + spinFFW + + + + + + + FPS + + + 4 + + + 0.000100000000000 + + + 1000.000000000000000 + + + 59.826099999999997 + + + + + + + 2 + + + + + + 63 + 16777215 + + + + 1/4 + + + + + + + + 62 + 16777215 + + + + 1/2 + + + + + + + + + FPS + + + 4 + + + 0.000100000000000 + + + 1000.000000000000000 + + + 29.913000000000000 + + + + + + + FPS + + + 4 + + + 0.000100000000000 + + + 1000.000000000000000 + + + 1000.000000000000000 + + + + + + + Slow-Mo + + + + + + + 2 + + + + + + 63 + 16777215 + + + + Accurate + + + + + + + + 62 + 16777215 + + + + Clean + + + + + + + + + 2 + + + + + + 41 + 16777215 + + + + 2x + + + + + + + + 41 + 16777215 + + + + 3x + + + + + + + + 41 + 16777215 + + + + MAX + + + + + + @@ -128,10 +308,10 @@ - Qt::Horizontal + Qt::Orientation::Horizontal - QDialogButtonBox::Cancel|QDialogButtonBox::Ok + QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok diff --git a/src/frontend/qt_sdl/Window.cpp b/src/frontend/qt_sdl/Window.cpp index 79b37e31..3020defd 100644 --- a/src/frontend/qt_sdl/Window.cpp +++ b/src/frontend/qt_sdl/Window.cpp @@ -780,6 +780,10 @@ void MainWindow::closeEvent(QCloseEvent* event) Config::Save(); emuInstance->deleteWindow(windowID, false); + + // emuInstance may be deleted + // prevent use after free from us + emuInstance = nullptr; QMainWindow::closeEvent(event); } @@ -970,7 +974,10 @@ void MainWindow::focusInEvent(QFocusEvent* event) void MainWindow::focusOutEvent(QFocusEvent* event) { - emuInstance->audioMute(); + // focusOutEvent is called through the window close event handler + // prevent use after free + if (emuInstance) + emuInstance->audioMute(); } void MainWindow::onAppStateChanged(Qt::ApplicationState state) @@ -1939,8 +1946,9 @@ void MainWindow::onOpenInterfaceSettings() void MainWindow::onUpdateInterfaceSettings() { pauseOnLostFocus = globalCfg.GetBool("PauseLostFocus"); - emuInstance->maxFPS = globalCfg.GetInt("MaxFPS"); - + emuInstance->targetFPS = 1.0 / globalCfg.GetDouble("TargetFPS"); + emuInstance->fastForwardFPS = 1.0 / globalCfg.GetDouble("FastForwardFPS"); + emuInstance->slowmoFPS = 1.0 / globalCfg.GetDouble("SlowmoFPS"); panel->setMouseHide(globalCfg.GetBool("MouseHide"), globalCfg.GetInt("MouseHideSeconds")*1000); } diff --git a/vcpkg.json b/vcpkg.json index eb8790c8..cd734cce 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -2,9 +2,22 @@ "default-features": ["qt6"], "dependencies": [ "sdl2", + { + "name": "sdl2", + "platform": "linux", + "features": [ "alsa" ] + }, "libarchive", "zstd", - "enet" + "enet", + { + "name": "ecm", + "platform": "linux" + }, + { + "name": "libslirp", + "platform": "linux" + } ], "features": { "qt6": { @@ -15,6 +28,12 @@ "default-features": false, "features": ["gui", "png", "thread", "widgets", "opengl", "zstd", "harfbuzz"] }, + { + "name": "qtbase", + "platform": "linux", + "default-features": false, + "features": ["dbus", "xcb", "xkb", "xcb-xlib", "freetype", "fontconfig"] + }, { "name": "qtbase", "host": true, @@ -24,6 +43,12 @@ "name": "qtmultimedia", "default-features": false }, + { + "name": "qtmultimedia", + "platform": "linux", + "features": ["gstreamer"], + "default-features": false + }, "qtsvg" ] },