From 6b0c66c6a1ec77d7371d65d7a24afe729beaf3e0 Mon Sep 17 00:00:00 2001 From: wutno Date: Mon, 16 Jan 2023 22:20:52 -0500 Subject: [PATCH 01/39] ui: Add fullscreen exclusive mode --- config_spec.yml | 1 + ui/xemu.c | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/config_spec.yml b/config_spec.yml index 5511363ca3..cb300cce54 100644 --- a/config_spec.yml +++ b/config_spec.yml @@ -108,6 +108,7 @@ display: default: 1 window: fullscreen_on_startup: bool + fullscreen_exclusive: bool startup_size: type: enum values: [last_used, 640x480, 1280x720, 1280x800, 1280x960, 1920x1080, 2560x1440, 2560x1600, 2560x1920, 3840x2160] diff --git a/ui/xemu.c b/ui/xemu.c index 5f86b9bda9..f0af36cea9 100644 --- a/ui/xemu.c +++ b/ui/xemu.c @@ -380,7 +380,9 @@ static void set_full_screen(struct sdl2_console *scon, bool set) if (gui_fullscreen) { SDL_SetWindowFullscreen(scon->real_window, - SDL_WINDOW_FULLSCREEN_DESKTOP); + (g_config.display.window.fullscreen_exclusive ? + SDL_WINDOW_FULLSCREEN : + SDL_WINDOW_FULLSCREEN_DESKTOP)); gui_saved_grab = gui_grab; sdl_grab_start(scon); } else { From 8fbbe0f0f335e5b3a4da247c84c716cbdc65d1e5 Mon Sep 17 00:00:00 2001 From: Matt Borgerson Date: Tue, 17 Jan 2023 12:05:59 -0700 Subject: [PATCH 02/39] ci: Use bot to push releases and trigger site update Apparently using the default actions token will not allow triggering workflows. Use bot access token instead to publish the release. --- .github/workflows/build.yml | 1 + .github/workflows/trigger-website-update.yml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d290d83f7c..78cbaa6867 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -347,6 +347,7 @@ jobs: - name: Publish release uses: softprops/action-gh-release@v1 with: + token: ${{ secrets.XEMU_ROBOT_TOKEN }} tag_name: v${{ env.XEMU_VERSION }} name: v${{ env.XEMU_VERSION }} prerelease: false diff --git a/.github/workflows/trigger-website-update.yml b/.github/workflows/trigger-website-update.yml index 2430f346f9..51f8519be4 100644 --- a/.github/workflows/trigger-website-update.yml +++ b/.github/workflows/trigger-website-update.yml @@ -13,5 +13,5 @@ jobs: with: workflow: build.yml repo: xemu-project/xemu-website - token: ${{ secrets.XEMU_WEBSITE_TOKEN }} + token: ${{ secrets.XEMU_ROBOT_TOKEN }} ref: master From 328656dbd9a586d74567608c44cf9fc527c918ce Mon Sep 17 00:00:00 2001 From: Matt Borgerson Date: Tue, 17 Jan 2023 13:03:56 -0700 Subject: [PATCH 03/39] ci: Fix release double build --- .github/workflows/build.yml | 8 +++++++- .github/workflows/trigger-website-update.yml | 17 ----------------- 2 files changed, 7 insertions(+), 18 deletions(-) delete mode 100644 .github/workflows/trigger-website-update.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 78cbaa6867..f7db4c4a2c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -347,7 +347,6 @@ jobs: - name: Publish release uses: softprops/action-gh-release@v1 with: - token: ${{ secrets.XEMU_ROBOT_TOKEN }} tag_name: v${{ env.XEMU_VERSION }} name: v${{ env.XEMU_VERSION }} prerelease: false @@ -360,6 +359,13 @@ jobs: dist/xemu-macos-universal-debug/xemu-macos-universal-debug.zip dist/xemu-ubuntu-release/xemu/xemu-v${{ env.XEMU_VERSION }}-x86_64.AppImage dist/xemu-ubuntu-debug/xemu/xemu-v${{ env.XEMU_VERSION }}-dbg-x86_64.AppImage + - name: Trigger website update + uses: benc-uk/workflow-dispatch@v1.2.2 + with: + workflow: build.yml + repo: xemu-project/xemu-website + token: ${{ secrets.XEMU_ROBOT_TOKEN }} + ref: master # Sync archive version of source (including submodule code) to the # ppa-snapshot branch to work around limitations of the Launchpad platform, diff --git a/.github/workflows/trigger-website-update.yml b/.github/workflows/trigger-website-update.yml deleted file mode 100644 index 51f8519be4..0000000000 --- a/.github/workflows/trigger-website-update.yml +++ /dev/null @@ -1,17 +0,0 @@ -name: Trigger website update - -on: - release: - types: [published] - -jobs: - trigger-website-update: - name: Trigger website update - runs-on: ubuntu-latest - steps: - - uses: benc-uk/workflow-dispatch@v1.2.2 - with: - workflow: build.yml - repo: xemu-project/xemu-website - token: ${{ secrets.XEMU_ROBOT_TOKEN }} - ref: master From ca6e712ecd797c77ba501402cfcd64dde55d2c17 Mon Sep 17 00:00:00 2001 From: Julien Reichardt Date: Wed, 18 Jan 2023 20:17:10 +0100 Subject: [PATCH 04/39] xemu.appdata.xml: Add screenshots to appdata file --- xemu.appdata.xml | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/xemu.appdata.xml b/xemu.appdata.xml index 0c35f5c290..30d2f4908e 100644 --- a/xemu.appdata.xml +++ b/xemu.appdata.xml @@ -1,32 +1,41 @@ app.xemu.xemu - + xemu Original Xbox Emulator - + CC0-1.0 GPL-2.0-only - + pointing keyboard gamepad - +

A free and open-source application that emulates the original Microsoft Xbox game console. Supports connecting up to 4 controllers for local play, networking for multiplayer, resolution scaling, and more.

+ + https://xemu.app/screenshots/0.png + https://xemu.app/screenshots/1.png + https://xemu.app/screenshots/2.png + https://xemu.app/screenshots/3.png + https://xemu.app/screenshots/4.png + https://xemu.app/screenshots/5.png + + Game Emulator - + https://xemu.app https://github.com/xemu-project/xemu/issues https://xemu.app/docs @@ -37,5 +46,4 @@ xemu -
From c08183c54d3d9fa722d737d0a16f03182a51067b Mon Sep 17 00:00:00 2001 From: Matt Borgerson Date: Mon, 23 Jan 2023 03:24:21 -0700 Subject: [PATCH 05/39] xemu-win64-toolchain: Add libslirp v4.7.0 --- ubuntu-win64-cross/Dockerfile | 9 ++++++++ ubuntu-win64-cross/libslirp-1.patch | 34 +++++++++++++++++++++++++++++ ubuntu-win64-cross/libslirp.mk | 19 ++++++++++++++++ 3 files changed, 62 insertions(+) create mode 100644 ubuntu-win64-cross/libslirp-1.patch create mode 100644 ubuntu-win64-cross/libslirp.mk diff --git a/ubuntu-win64-cross/Dockerfile b/ubuntu-win64-cross/Dockerfile index b579248930..bd26ee8170 100644 --- a/ubuntu-win64-cross/Dockerfile +++ b/ubuntu-win64-cross/Dockerfile @@ -71,6 +71,15 @@ RUN V=1 MXE_VERBOSE=1 make -C /opt/mxe \ MXE_PLUGIN_DIRS=plugins/gcc10 \ sdl2 +COPY libslirp.mk /opt/mxe/src/libslirp.mk +COPY libslirp-1.patch /opt/mxe/src/libslirp-1.patch +RUN V=1 MXE_VERBOSE=1 make -C /opt/mxe \ + MXE_TARGETS=x86_64-w64-mingw32.static \ + MXE_PLUGIN_DIRS=plugins/gcc10 \ + libslirp + +RUN find /opt/mxe/usr -executable -type f -exec chmod a+x {} \; + ENV CROSSPREFIX=x86_64-w64-mingw32.static- ENV CROSSAR=${CROSSPREFIX}gcc-ar ENV PATH="/opt/mxe/.ccache/bin:/opt/mxe/usr/x86_64-pc-linux-gnu/bin:/opt/mxe/usr/bin:${PATH}" diff --git a/ubuntu-win64-cross/libslirp-1.patch b/ubuntu-win64-cross/libslirp-1.patch new file mode 100644 index 0000000000..eb4b6a328c --- /dev/null +++ b/ubuntu-win64-cross/libslirp-1.patch @@ -0,0 +1,34 @@ +From d1417aed0b335d03c67c998b17b72933764ccbe3 Mon Sep 17 00:00:00 2001 +From: Matt Borgerson +Date: Mon, 23 Jan 2023 03:06:46 -0700 +Subject: [PATCH] Rename pingtest's slirp_inet_aton + +--- + test/pingtest.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/test/pingtest.c b/test/pingtest.c +index 3bb0488..04b63ce 100644 +--- a/test/pingtest.c ++++ b/test/pingtest.c +@@ -24,7 +24,7 @@ + #ifdef _WIN32 + //#include + #include +-int slirp_inet_aton(const char *cp, struct in_addr *ia) ++int pingtest_inet_aton(const char *cp, struct in_addr *ia) + { + uint32_t addr = inet_addr(cp); + if (addr == 0xffffffff) { +@@ -33,7 +33,7 @@ int slirp_inet_aton(const char *cp, struct in_addr *ia) + ia->s_addr = addr; + return 1; + } +-#define inet_aton slirp_inet_aton ++#define inet_aton pingtest_inet_aton + #else + #include + #endif +-- +2.34.1 + diff --git a/ubuntu-win64-cross/libslirp.mk b/ubuntu-win64-cross/libslirp.mk new file mode 100644 index 0000000000..71449d14c6 --- /dev/null +++ b/ubuntu-win64-cross/libslirp.mk @@ -0,0 +1,19 @@ +# This file is part of MXE. See LICENSE.md for licensing information. + +PKG := libslirp +$(PKG)_WEBSITE := https://gitlab.freedesktop.org/slirp/libslirp +$(PKG)_IGNORE := +$(PKG)_VERSION := 4.7.0 +$(PKG)_SUBDIR := libslirp-v$($(PKG)_VERSION) +$(PKG)_FILE := libslirp-v$($(PKG)_VERSION).tar.gz +$(PKG)_CHECKSUM := 9398f0ec5a581d4e1cd6856b88ae83927e458d643788c3391a39e61b75db3d3b +$(PKG)_URL := https://gitlab.freedesktop.org/slirp/$(PKG)/-/archive/v$($(PKG)_VERSION)/$($(PKG)_FILE) +$(PKG)_DEPS := cc glib meson-wrapper + +define $(PKG)_BUILD + '$(MXE_MESON_WRAPPER)' $(MXE_MESON_OPTS) \ + $(PKG_MESON_OPTS) \ + --buildtype=plain \ + '$(BUILD_DIR)' '$(SOURCE_DIR)' + '$(MXE_NINJA)' -C '$(BUILD_DIR)' -j '$(JOBS)' install +endef From d0d3e7b4faa5d0b5f4481b970c4eb81d28e37632 Mon Sep 17 00:00:00 2001 From: Matt Borgerson Date: Mon, 23 Jan 2023 03:30:40 -0700 Subject: [PATCH 06/39] xemu-win64-toolchain/sdl2.mk: Specify PKG_FILE --- ubuntu-win64-cross/sdl2.mk | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ubuntu-win64-cross/sdl2.mk b/ubuntu-win64-cross/sdl2.mk index 793986def3..4ed53822b8 100644 --- a/ubuntu-win64-cross/sdl2.mk +++ b/ubuntu-win64-cross/sdl2.mk @@ -6,8 +6,9 @@ $(PKG)_DESCR := SDL2 $(PKG)_IGNORE := $(PKG)_VERSION := 2.26.2 $(PKG)_SUBDIR := SDL2-$($(PKG)_VERSION) -$(PKG)_URL := https://github.com/libsdl-org/SDL/releases/download/release-$($(PKG)_VERSION)/SDL2-$($(PKG)_VERSION).tar.gz +$(PKG)_FILE := SDL2-$($(PKG)_VERSION).tar.gz $(PKG)_CHECKSUM := 95d39bc3de037fbdfa722623737340648de4f180a601b0afad27645d150b99e0 +$(PKG)_URL := https://github.com/libsdl-org/SDL/releases/download/release-$($(PKG)_VERSION)/$($(PKG)_FILE) $(PKG)_GH_CONF := libsdl-org/SDL/releases/tag,release-,, $(PKG)_DEPS := cc libiconv libsamplerate From d8fa50e524c22f85ecb2e43108fd6a5501744351 Mon Sep 17 00:00:00 2001 From: Dustin Holden Date: Tue, 14 Feb 2023 18:23:49 -0500 Subject: [PATCH 07/39] smc: Implement read/write of SMC error code storage --- hw/xbox/smbus_xbox_smc.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/hw/xbox/smbus_xbox_smc.c b/hw/xbox/smbus_xbox_smc.c index cc09ad3bda..c7cf02e3e5 100644 --- a/hw/xbox/smbus_xbox_smc.c +++ b/hw/xbox/smbus_xbox_smc.c @@ -78,6 +78,8 @@ #define SMC_REG_BOARDTEMP 0x0a #define SMC_REG_TRAYEJECT 0x0c #define SMC_REG_INTACK 0x0d +#define SMC_REG_ERROR_WRITE 0x0e +#define SMC_REG_ERROR_READ 0x0f #define SMC_REG_INTSTATUS 0x11 #define SMC_REG_INTSTATUS_POWER 0x01 #define SMC_REG_INTSTATUS_TRAYCLOSED 0x02 @@ -102,6 +104,7 @@ typedef struct SMBusSMCDevice { uint8_t avpack_reg; uint8_t intstatus_reg; uint8_t scratch_reg; + uint8_t error_reg; } SMBusSMCDevice; static void smc_quick_cmd(SMBusDevice *dev, uint8_t read) @@ -137,6 +140,10 @@ static int smc_write_data(SMBusDevice *dev, uint8_t *buf, uint8_t len) } break; + case SMC_REG_ERROR_WRITE: + smc->error_reg = buf[0]; + break; + case SMC_REG_SCRATCH: smc->scratch_reg = buf[0]; break; @@ -177,6 +184,9 @@ static uint8_t smc_receive_byte(SMBusDevice *dev) case SMC_REG_AVPACK: return smc->avpack_reg; + case SMC_REG_ERROR_READ: + return smc->error_reg; + case SMC_REG_INTSTATUS: { uint8_t r = smc->intstatus_reg; smc->intstatus_reg = 0; // FIXME: Confirm clear on read @@ -252,6 +262,7 @@ static void smbus_smc_realize(DeviceState *dev, Error **errp) smc->intstatus_reg = 0; smc->scratch_reg = 0; smc->cmd = 0; + smc->error_reg = 0; if (object_property_get_bool(qdev_get_machine(), "short-animation", NULL)) { smc->scratch_reg = SMC_REG_SCRATCH_SHORT_ANIMATION; From 065c74a00fe402f65147e9b0d1a1eeac1b4b609f Mon Sep 17 00:00:00 2001 From: wutno Date: Wed, 29 Dec 2021 03:38:04 -0500 Subject: [PATCH 08/39] nv2a: Don't manually set NV_PFB_CFG0 reg --- hw/xbox/nv2a/pfb.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/hw/xbox/nv2a/pfb.c b/hw/xbox/nv2a/pfb.c index ef2d7528af..147c468510 100644 --- a/hw/xbox/nv2a/pfb.c +++ b/hw/xbox/nv2a/pfb.c @@ -27,10 +27,6 @@ uint64_t pfb_read(void *opaque, hwaddr addr, unsigned int size) uint64_t r = 0; switch (addr) { - case NV_PFB_CFG0: - /* 3-4 memory partitions. The debug bios checks this. */ - r = 3; - break; case NV_PFB_CSTATUS: r = memory_region_size(d->vram); break; From 4a99fd0f1855fc52e15885812abd3ee4bb2b3847 Mon Sep 17 00:00:00 2001 From: Antonio Abbatangelo Date: Tue, 21 Mar 2023 01:10:50 -0400 Subject: [PATCH 09/39] ui: Add clear button to file picker --- ui/xui/widgets.cc | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/ui/xui/widgets.cc b/ui/xui/widgets.cc index cea342f079..3ce59537b2 100644 --- a/ui/xui/widgets.cc +++ b/ui/xui/widgets.cc @@ -306,12 +306,14 @@ bool FilePicker(const char *str_id, const char **buf, const char *filters, ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32_BLACK_TRANS); ImGuiStyle &style = ImGui::GetStyle(); ImVec2 p = ImGui::GetCursorScreenPos(); + ImVec2 cursor = ImGui::GetCursorPos(); const char *desc = strlen(*buf) ? *buf : "(None Selected)"; ImVec2 bb(ImGui::GetColumnWidth(), GetWidgetTitleDescriptionHeight(str_id, desc)); ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0, 0)); ImGui::PushID(str_id); bool status = ImGui::Button("###file_button", bb); + ImGui::SetItemAllowOverlap(); if (status) { int flags = NOC_FILE_DIALOG_OPEN; if (dir) flags |= NOC_FILE_DIALOG_DIR; @@ -336,9 +338,32 @@ bool FilePicker(const char *str_id, const char **buf, const char *filters, ImGui::PushFont(g_font_mgr.m_menu_font); const char *icon = dir ? ICON_FA_FOLDER : ICON_FA_FILE; ImVec2 ts_icon = ImGui::CalcTextSize(icon); - draw_list->AddText(ImVec2(p1.x - style.FramePadding.x - ts_icon.x, - p0.y + (p1.y - p0.y - ts_icon.y) / 2), - ImGui::GetColorU32(ImGuiCol_Text), icon); + ImVec2 icon_pos = ImVec2(p1.x - style.FramePadding.x - ts_icon.x, + p0.y + (p1.y - p0.y - ts_icon.y) / 2); + draw_list->AddText(icon_pos, ImGui::GetColorU32(ImGuiCol_Text), icon); + + ImVec2 ts_clear_icon = ImGui::CalcTextSize(ICON_FA_XMARK); + ts_clear_icon.x += 2 * style.FramePadding.x; + ImVec2 clear_icon_pos = ImVec2(cursor.x + bb.x - ts_icon.x - ts_clear_icon.x, cursor.y); + + auto prev_pos = ImGui::GetCursorPos(); + ImGui::SetCursorPos(clear_icon_pos); + + char *clear_button_id = g_strdup_printf("%s_clear", str_id); + ImGui::PushID(clear_button_id); + + bool clear = ImGui::Button(ICON_FA_XMARK, ImVec2(ts_clear_icon.x, bb.y)); + if (clear) { + free((void*)*buf); + *buf = strdup(""); + changed = true; + } + + ImGui::PopID(); + g_free(clear_button_id); + + ImGui::SetCursorPos(prev_pos); + ImGui::PopFont(); ImGui::PopStyleColor(); From 9723b435fb1a6f8617323a2b99b8ad48980d98a8 Mon Sep 17 00:00:00 2001 From: Erik Abair Date: Wed, 8 Jun 2022 15:40:28 -0700 Subject: [PATCH 10/39] nv2a: Make multiplication by 0 match HW behavior. Fixes #1008 The nv2a returns 0 for anything multiplied by zero, including exceptional values such as Inf and NaN. Desktop GPUs do not enforce this, leading to conditions where NaNs wipe out calculations and lead to erroneous behavior. [Test](https://github.com/abaire/nxdk_vsh_tests/blob/main/src/tests/americasarmyshader.cpp) [HW Results](https://github.com/abaire/nxdk_vsh_tests_golden_results/wiki/Results-AmericasArmyShader) --- hw/xbox/nv2a/vsh.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/hw/xbox/nv2a/vsh.c b/hw/xbox/nv2a/vsh.c index 87318419c9..5f338e7368 100644 --- a/hw/xbox/nv2a/vsh.c +++ b/hw/xbox/nv2a/vsh.c @@ -636,7 +636,16 @@ static const char* vsh_header = "#define MUL(dest, mask, src0, src1) dest.mask = _MUL(_in(src0), _in(src1)).mask\n" "vec4 _MUL(vec4 src0, vec4 src1)\n" "{\n" - " return src0 * src1;\n" + // Unfortunately mix() falls victim to the same handling of exceptional + // (inf/NaN) handling as a multiply, so per-component comparisons are used + // to guarantee HW behavior (anything * 0 must == 0). + " vec4 zero_components = sign(src0) * sign(src1);\n" + " vec4 ret = src0 * src1;\n" + " if (zero_components.x == 0.0) { ret.x = 0.0; }\n" + " if (zero_components.y == 0.0) { ret.y = 0.0; }\n" + " if (zero_components.z == 0.0) { ret.z = 0.0; }\n" + " if (zero_components.w == 0.0) { ret.w = 0.0; }\n" + " return ret;\n" "}\n" "\n" "#define ADD(dest, mask, src0, src1) dest.mask = _ADD(_in(src0), _in(src1)).mask\n" @@ -648,7 +657,7 @@ static const char* vsh_header = "#define MAD(dest, mask, src0, src1, src2) dest.mask = _MAD(_in(src0), _in(src1), _in(src2)).mask\n" "vec4 _MAD(vec4 src0, vec4 src1, vec4 src2)\n" "{\n" - " return src0 * src1 + src2;\n" + " return _MUL(src0, src1) + src2;\n" "}\n" "\n" "#define DP3(dest, mask, src0, src1) dest.mask = _DP3(_in(src0), _in(src1)).mask\n" From 9e1f5f2f19a8b0e880e034d455b359d426f6d386 Mon Sep 17 00:00:00 2001 From: Florin9doi Date: Sat, 1 Apr 2023 01:05:03 +0300 Subject: [PATCH 11/39] Set correct version for macOS bundles Closes #1344 --- build.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/build.sh b/build.sh index 10d40de3bc..48c1aa6a28 100755 --- a/build.sh +++ b/build.sh @@ -77,6 +77,9 @@ package_macos() { cp Info.plist dist/xemu.app/Contents/ + plutil -replace CFBundleShortVersionString -string $(cat ${project_source_dir}/XEMU_VERSION | cut -f1 -d-) dist/xemu.app/Contents/Info.plist + plutil -replace CFBundleVersion -string $(cat ${project_source_dir}/XEMU_VERSION | cut -f1 -d-) dist/xemu.app/Contents/Info.plist + codesign --force --deep --preserve-metadata=entitlements,requirements,flags,runtime --sign - "${exe_path}" python3 ./scripts/gen-license.py --version-file=macos-libs/$target_arch/INSTALLED > dist/LICENSE.txt } From 8bea1e38e5ce6f8188b0df0d2fd4cbf3a0eecdc2 Mon Sep 17 00:00:00 2001 From: Matt Borgerson Date: Fri, 27 Jan 2023 09:48:28 -0700 Subject: [PATCH 12/39] ui: Fix FilePicker UAF --- ui/xui/widgets.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/ui/xui/widgets.cc b/ui/xui/widgets.cc index 3ce59537b2..34a122ce6c 100644 --- a/ui/xui/widgets.cc +++ b/ui/xui/widgets.cc @@ -322,6 +322,7 @@ bool FilePicker(const char *str_id, const char **buf, const char *filters, if (new_path) { free((void*)*buf); *buf = strdup(new_path); + desc = *buf; changed = true; } } From 546fe068de83c188afda8d0c4a880f9f3b0eeb74 Mon Sep 17 00:00:00 2001 From: Antonio Abbatangelo Date: Thu, 11 May 2023 23:35:05 -0400 Subject: [PATCH 13/39] nv2a: Ignore nop draws in SET_BEGIN_END_OP_END --- hw/xbox/nv2a/pgraph.c | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/hw/xbox/nv2a/pgraph.c b/hw/xbox/nv2a/pgraph.c index 6132465377..eda2ae5693 100644 --- a/hw/xbox/nv2a/pgraph.c +++ b/hw/xbox/nv2a/pgraph.c @@ -2841,12 +2841,25 @@ DEF_METHOD(NV097, SET_BEGIN_END) bool depth_test = control_0 & NV_PGRAPH_CONTROL_0_ZENABLE; bool stencil_test = pg->regs[NV_PGRAPH_CONTROL_1] & NV_PGRAPH_CONTROL_1_STENCIL_TEST_ENABLE; + bool is_nop_draw = !(color_write || depth_test || stencil_test); if (parameter == NV097_SET_BEGIN_END_OP_END) { if (pg->primitive_mode == PRIM_TYPE_INVALID) { NV2A_DPRINTF("End without Begin!\n"); } nv2a_profile_inc_counter(NV2A_PROF_BEGIN_ENDS); + + if (is_nop_draw) { + // FIXME: Check PGRAPH register 0x880. + // HW uses bit 11 in 0x880 to enable or disable a color/zeta limit + // check that will raise an exception in the case that a draw should + // modify the color and/or zeta buffer but the target(s) are masked + // off. This check only seems to trigger during the fragment + // processing, it is legal to attempt a draw that is entirely + // clipped regardless of 0x880. See xemu#635 for context. + return; + } + pgraph_flush_draw(d); /* End of visibility testing */ @@ -2878,14 +2891,7 @@ DEF_METHOD(NV097, SET_BEGIN_END) pgraph_update_surface(d, true, true, depth_test || stencil_test); pgraph_reset_inline_buffers(pg); - if (!(color_write || depth_test || stencil_test)) { - // FIXME: Check PGRAPH register 0x880. - // HW uses bit 11 in 0x880 to enable or disable a color/zeta limit - // check that will raise an exception in the case that a draw should - // modify the color and/or zeta buffer but the target(s) are masked - // off. This check only seems to trigger during the fragment - // processing, it is legal to attempt a draw that is entirely - // clipped regardless of 0x880. See xemu#635 for context. + if (is_nop_draw) { return; } From fe5160e8598d726568edb2ec07bbe7adfca46573 Mon Sep 17 00:00:00 2001 From: Erik Abair Date: Sun, 4 Jun 2023 09:49:31 -0700 Subject: [PATCH 14/39] build: Discover latest macOS SDK instead of hardcoding. --- build.sh | 66 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/build.sh b/build.sh index 48c1aa6a28..196ae9c52e 100755 --- a/build.sh +++ b/build.sh @@ -173,6 +173,32 @@ else opts="--enable-lto" fi +most_recent_macosx_sdk_ver () { + local min_ver="${1}" + local macos_sdk_base=/Library/Developer/CommandLineTools/SDKs + local sdks=("${macos_sdk_base}"/MacOSX[0-9]*.[0-9]*.sdk) + for i in "${!sdks[@]}"; do + local newval="${sdks[i]##${macos_sdk_base}/MacOSX}" + sdks[$i]="${newval%%.sdk}" + done + + IFS=$'\n' sdks=($(sort -nr <<<"${sdks[*]}")) + unset IFS + + local newest_sdk_ver="${sdks[0]}" + + local sdk_path="${macos_sdk_base}/MacOSX${newest_sdk_ver}.sdk" + if ! test -d "${sdk_path}"; then + echo "" + return + fi + + if ! LC_ALL=C awk 'BEGIN {exit ('${newest_sdk_ver}' < '${min_ver}')}'; then + echo "" + return + fi + echo "${sdk_path}" +} case "$platform" in # Adjust compilation options based on platform Linux) @@ -183,47 +209,21 @@ case "$platform" in # Adjust compilation options based on platform ;; Darwin) echo "Compiling for MacOS for $target_arch..." - sdk_base=/Library/Developer/CommandLineTools/SDKs/ - sdk_macos_10_14="${sdk_base}/MacOSX10.14.sdk" - sdk_macos_10_15="${sdk_base}/MacOSX10.15.sdk" - sdk_macos_11_1="${sdk_base}/MacOSX11.1.sdk" - sdk_macos_11_3="${sdk_base}/MacOSX11.3.sdk" - sdk_macos_12_0="${sdk_base}/MacOSX12.0.sdk" - sdk_macos_12_1="${sdk_base}/MacOSX12.1.sdk" if [ "$target_arch" == "arm64" ]; then macos_min_ver=11.3 - if test -d "$sdk_macos_12_1"; then - sdk="$sdk_macos_12_1" - elif test -d "$sdk_macos_12_0"; then - sdk="$sdk_macos_12_0" - elif test -d "$sdk_macos_11_3"; then - sdk="$sdk_macos_11_3" - else - echo "SDK not found. Install Xcode Command Line Tools" - exit 1 - fi elif [ "$target_arch" == "x86_64" ]; then macos_min_ver=10.13 - if test -d "$sdk_macos_12_1"; then - sdk="$sdk_macos_12_1" - elif test -d "$sdk_macos_12_0"; then - sdk="$sdk_macos_12_0" - elif test -d "$sdk_macos_11_3"; then - sdk="$sdk_macos_11_3" - elif test -d "$sdk_macos_11_1"; then - sdk="$sdk_macos_11_1" - elif test -d "$sdk_macos_10_15"; then - sdk="$sdk_macos_10_15" - elif test -d "$sdk_macos_10_14"; then - sdk="$sdk_macos_10_14" - else - echo "SDK not found. Install Xcode Command Line Tools" - exit 1 - fi else echo "Unsupported arch $target_arch" exit 1 fi + + sdk="$(most_recent_macosx_sdk_ver ${macos_min_ver})" + if [[ -z "${sdk}" ]]; then + echo "SDK >= ${macos_min_ver} not found. Install Xcode Command Line Tools" + exit 1 + fi + python3 ./scripts/download-macos-libs.py ${target_arch} lib_prefix=${PWD}/macos-libs/${target_arch}/opt/local export CFLAGS="-arch ${target_arch} \ From 9c9f1e83ebdfaf720a1a1ba84c55e7ab8ca2d662 Mon Sep 17 00:00:00 2001 From: Antonio Abbatangelo Date: Tue, 5 Apr 2022 03:22:59 -0400 Subject: [PATCH 15/39] ui: Add snapshot management UI --- block.c | 6 + block/file-win32.c | 16 ++ config_spec.yml | 7 + include/block/block.h | 4 + migration/savevm.c | 15 ++ ui/meson.build | 2 + ui/thirdparty/meson.build | 1 + ui/xemu-settings.h | 6 +- ui/xemu-snapshots.c | 319 +++++++++++++++++++++++++++ ui/xemu-snapshots.h | 79 +++++++ ui/xemu-thumbnail.cc | 92 ++++++++ ui/xemu.c | 3 + ui/xui/actions.cc | 28 ++- ui/xui/actions.hh | 1 + ui/xui/common.hh | 1 + ui/xui/gl-helpers.cc | 54 +++-- ui/xui/gl-helpers.hh | 5 +- ui/xui/main-menu.cc | 446 +++++++++++++++++++++++++++++--------- ui/xui/main-menu.hh | 25 ++- ui/xui/main.cc | 9 + ui/xui/menubar.cc | 44 ++++ ui/xui/popup-menu.cc | 15 +- 22 files changed, 1052 insertions(+), 126 deletions(-) create mode 100644 ui/xemu-snapshots.c create mode 100644 ui/xemu-snapshots.h create mode 100644 ui/xemu-thumbnail.cc diff --git a/block.c b/block.c index e97ce0b1c8..b3a12fb318 100644 --- a/block.c +++ b/block.c @@ -2599,6 +2599,12 @@ static void bdrv_default_perms_for_storage(BlockDriverState *bs, BdrvChild *c, shared |= BLK_PERM_WRITE | BLK_PERM_RESIZE; } +#ifdef XBOX + if (bs->open_flags & BDRV_O_RO_WRITE_SHARE) { + shared |= BLK_PERM_WRITE | BLK_PERM_RESIZE; + } +#endif + *nperm = perm; *nshared = shared; } diff --git a/block/file-win32.c b/block/file-win32.c index 053496afeb..8a8ab436e7 100644 --- a/block/file-win32.c +++ b/block/file-win32.c @@ -36,6 +36,7 @@ #include "qapi/qmp/qstring.h" #include #include +#include #define FTYPE_FILE 0 #define FTYPE_CD 1 @@ -341,6 +342,9 @@ static int raw_open(BlockDriverState *bs, QDict *options, int flags, bool use_aio; OnOffAuto locking; int ret; +#ifdef XBOX + int sharing_flags; +#endif s->type = FTYPE_FILE; @@ -396,9 +400,21 @@ static int raw_open(BlockDriverState *bs, QDict *options, int flags, if (!filename) { goto fail; } + +#ifdef XBOX + sharing_flags = FILE_SHARE_READ; + if (flags & BDRV_O_RO_WRITE_SHARE) { + assert(access_flags == GENERIC_READ); + sharing_flags = FILE_SHARE_READ | FILE_SHARE_WRITE; + } + s->hfile = CreateFileW(wfilename, access_flags, + sharing_flags, NULL, + OPEN_EXISTING, overlapped, NULL); +#else s->hfile = CreateFileW(wfilename, access_flags, FILE_SHARE_READ, NULL, OPEN_EXISTING, overlapped, NULL); +#endif g_free(wfilename); if (s->hfile == INVALID_HANDLE_VALUE) { int err = GetLastError(); diff --git a/config_spec.yml b/config_spec.yml index cb300cce54..c43f5732f8 100644 --- a/config_spec.yml +++ b/config_spec.yml @@ -11,6 +11,13 @@ general: # throttle_io: bool last_viewed_menu_index: integer user_token: string + snapshots: + shortcuts: + f5: string + f6: string + f7: string + f8: string + filter_current_game: bool input: bindings: diff --git a/include/block/block.h b/include/block/block.h index 3477290f9a..eccd27e884 100644 --- a/include/block/block.h +++ b/include/block/block.h @@ -123,6 +123,10 @@ typedef struct HDGeometry { #define BDRV_O_AUTO_RDONLY 0x20000 /* degrade to read-only if opening read-write fails */ #define BDRV_O_IO_URING 0x40000 /* use io_uring instead of the thread pool */ +#ifdef XBOX +#define BDRV_O_RO_WRITE_SHARE 0x80000 /* allow the file to open RO alongside an existing RW handle */ +#endif + #define BDRV_O_CACHE_MASK (BDRV_O_NOCACHE | BDRV_O_NO_FLUSH) diff --git a/migration/savevm.c b/migration/savevm.c index 7b7b64bd13..a2d1350239 100644 --- a/migration/savevm.c +++ b/migration/savevm.c @@ -67,6 +67,8 @@ #include "qemu/yank.h" #include "yank_functions.h" +#include "ui/xemu-snapshots.h" + const unsigned int postcopy_ram_discard_version; /* Subcommands for QEMU_VM_COMMAND */ @@ -1570,6 +1572,9 @@ static int qemu_savevm_state(QEMUFile *f, Error **errp) ms->to_dst_file = f; qemu_mutex_unlock_iothread(); +#ifdef XBOX + xemu_snapshots_save_extra_data(f); +#endif qemu_savevm_state_header(f); qemu_savevm_state_setup(f); qemu_mutex_lock_iothread(); @@ -2691,6 +2696,12 @@ int qemu_loadvm_state(QEMUFile *f) return -EINVAL; } +#ifdef XBOX + if (!xemu_snapshots_offset_extra_data(f)) { + return -EINVAL; + } +#endif + ret = qemu_loadvm_state_header(f); if (ret) { return ret; @@ -3086,6 +3097,10 @@ bool delete_snapshot(const char *name, bool has_devices, return false; } +#ifdef XBOX + xemu_snapshots_mark_dirty(); +#endif + return true; } diff --git a/ui/meson.build b/ui/meson.build index 02064602e7..8306d0857b 100644 --- a/ui/meson.build +++ b/ui/meson.build @@ -26,6 +26,8 @@ xemu_ss.add(files( 'xemu.c', 'xemu-data.c', + 'xemu-snapshots.c', + 'xemu-thumbnail.cc', )) subdir('xui') diff --git a/ui/thirdparty/meson.build b/ui/thirdparty/meson.build index 803efe465c..528b9f40be 100644 --- a/ui/thirdparty/meson.build +++ b/ui/thirdparty/meson.build @@ -5,6 +5,7 @@ imgui_files = files( 'imgui/imgui_widgets.cpp', 'imgui/backends/imgui_impl_sdl.cpp', 'imgui/backends/imgui_impl_opengl3.cpp', + 'imgui/misc/cpp/imgui_stdlib.cpp', #'imgui/imgui_demo.cpp', ) diff --git a/ui/xemu-settings.h b/ui/xemu-settings.h index 32cb6987dd..491e5ce8de 100644 --- a/ui/xemu-settings.h +++ b/ui/xemu-settings.h @@ -26,6 +26,7 @@ #include #include +#include #ifdef __cplusplus extern "C" { @@ -58,8 +59,9 @@ void xemu_settings_save(void); static inline void xemu_settings_set_string(const char **str, const char *new_str) { - free((char*)*str); - *str = strdup(new_str); + assert(new_str); + free((char*)*str); + *str = strdup(new_str); } void add_net_nat_forward_ports(int host, int guest, CONFIG_NET_NAT_FORWARD_PORTS_PROTOCOL protocol); diff --git a/ui/xemu-snapshots.c b/ui/xemu-snapshots.c new file mode 100644 index 0000000000..becb0dd859 --- /dev/null +++ b/ui/xemu-snapshots.c @@ -0,0 +1,319 @@ +/* + * xemu User Interface + * + * Copyright (C) 2020-2022 Matt Borgerson + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "xemu-snapshots.h" +#include "xemu-settings.h" +#include "xemu-xbe.h" + +#include +#include + +#include "block/aio.h" +#include "block/block_int.h" +#include "block/qapi.h" +#include "block/qdict.h" +#include "migration/qemu-file.h" +#include "migration/snapshot.h" +#include "qapi/error.h" +#include "sysemu/runstate.h" +#include "qemu-common.h" + +#include "ui/console.h" +#include "ui/input.h" + +static QEMUSnapshotInfo *xemu_snapshots_metadata = NULL; +static XemuSnapshotData *xemu_snapshots_extra_data = NULL; +static int xemu_snapshots_len = 0; +static bool xemu_snapshots_dirty = true; + +const char **g_snapshot_shortcut_index_key_map[] = { + &g_config.general.snapshots.shortcuts.f5, + &g_config.general.snapshots.shortcuts.f6, + &g_config.general.snapshots.shortcuts.f7, + &g_config.general.snapshots.shortcuts.f8, +}; + +static bool xemu_snapshots_load_thumbnail(BlockDriverState *bs_ro, + XemuSnapshotData *data, + int64_t *offset) +{ + int res = bdrv_load_vmstate(bs_ro, (uint8_t *)&data->thumbnail, *offset, + sizeof(TextureBuffer) - + sizeof(data->thumbnail.buffer)); + if (res != sizeof(TextureBuffer) - sizeof(data->thumbnail.buffer)) + return false; + *offset += res; + + data->thumbnail.buffer = g_malloc(data->thumbnail.size); + + res = bdrv_load_vmstate(bs_ro, (uint8_t *)data->thumbnail.buffer, *offset, + data->thumbnail.size); + if (res != data->thumbnail.size) { + return false; + } + *offset += res; + + return true; +} + +static void xemu_snapshots_load_data(BlockDriverState *bs_ro, + QEMUSnapshotInfo *info, + XemuSnapshotData *data, Error **err) +{ + int res; + XemuSnapshotHeader header; + int64_t offset = 0; + + data->xbe_title_present = false; + data->thumbnail_present = false; + res = bdrv_snapshot_load_tmp(bs_ro, info->id_str, info->name, err); + if (res < 0) + return; + + res = bdrv_load_vmstate(bs_ro, (uint8_t *)&header, offset, sizeof(header)); + if (res != sizeof(header)) + goto error; + offset += res; + + if (header.magic != XEMU_SNAPSHOT_DATA_MAGIC) + goto error; + + res = bdrv_load_vmstate(bs_ro, (uint8_t *)&data->xbe_title_len, offset, + sizeof(data->xbe_title_len)); + if (res != sizeof(data->xbe_title_len)) + goto error; + offset += res; + + data->xbe_title = (char *)g_malloc(data->xbe_title_len); + + res = bdrv_load_vmstate(bs_ro, (uint8_t *)data->xbe_title, offset, + data->xbe_title_len); + if (res != data->xbe_title_len) + goto error; + offset += res; + + data->xbe_title_present = (offset <= sizeof(header) + header.size); + + if (offset == sizeof(header) + header.size) + return; + + if (!xemu_snapshots_load_thumbnail(bs_ro, data, &offset)) { + goto error; + } + + data->thumbnail_present = (offset <= sizeof(header) + header.size); + + if (data->thumbnail_present) { + glGenTextures(1, &data->gl_thumbnail); + xemu_snapshots_render_thumbnail(data->gl_thumbnail, &data->thumbnail); + } + return; + +error: + g_free(data->xbe_title); + data->xbe_title_present = false; + + g_free(data->thumbnail.buffer); + data->thumbnail_present = false; +} + +static void xemu_snapshots_all_load_data(QEMUSnapshotInfo **info, + XemuSnapshotData **data, + int snapshots_len, Error **err) +{ + BlockDriverState *bs_ro; + QDict *opts = qdict_new(); + + assert(info && data); + + if (*data) { + for (int i = 0; i < xemu_snapshots_len; ++i) { + if ((*data)[i].xbe_title_present) { + g_free((*data)[i].xbe_title); + } + + if ((*data)[i].thumbnail_present) { + g_free((*data)[i].thumbnail.buffer); + glDeleteTextures(1, &((*data)[i].gl_thumbnail)); + } + } + g_free(*data); + } + + *data = + (XemuSnapshotData *)g_malloc(sizeof(XemuSnapshotData) * snapshots_len); + memset(*data, 0, sizeof(XemuSnapshotData) * snapshots_len); + + qdict_put_bool(opts, BDRV_OPT_READ_ONLY, true); + bs_ro = bdrv_open(g_config.sys.files.hdd_path, NULL, opts, + BDRV_O_RO_WRITE_SHARE | BDRV_O_AUTO_RDONLY, err); + if (!bs_ro) { + return; + } + + for (int i = 0; i < snapshots_len; ++i) { + xemu_snapshots_load_data(bs_ro, (*info) + i, (*data) + i, err); + if (*err) { + break; + } + } + + bdrv_flush(bs_ro); + bdrv_drain(bs_ro); + bdrv_unref(bs_ro); + assert(bs_ro->refcnt == 0); + if (!(*err)) + xemu_snapshots_dirty = false; +} + +int xemu_snapshots_list(QEMUSnapshotInfo **info, XemuSnapshotData **extra_data, + Error **err) +{ + BlockDriverState *bs; + AioContext *aio_context; + int snapshots_len; + assert(err); + + if (!xemu_snapshots_dirty && xemu_snapshots_extra_data && + xemu_snapshots_metadata) { + goto done; + } + + if (xemu_snapshots_metadata) + g_free(xemu_snapshots_metadata); + + bs = bdrv_all_find_vmstate_bs(NULL, false, NULL, err); + if (!bs) { + return -1; + } + + aio_context = bdrv_get_aio_context(bs); + + aio_context_acquire(aio_context); + snapshots_len = bdrv_snapshot_list(bs, &xemu_snapshots_metadata); + aio_context_release(aio_context); + xemu_snapshots_all_load_data(&xemu_snapshots_metadata, + &xemu_snapshots_extra_data, snapshots_len, + err); + if (*err) { + return -1; + } + + xemu_snapshots_len = snapshots_len; + +done: + if (info) { + *info = xemu_snapshots_metadata; + } + + if (extra_data) { + *extra_data = xemu_snapshots_extra_data; + } + + return xemu_snapshots_len; +} + +void xemu_snapshots_load(const char *vm_name, Error **err) +{ + bool vm_running = runstate_is_running(); + vm_stop(RUN_STATE_RESTORE_VM); + if (load_snapshot(vm_name, NULL, false, NULL, err) && vm_running) { + vm_start(); + } +} + +void xemu_snapshots_save(const char *vm_name, Error **err) +{ + save_snapshot(vm_name, true, NULL, false, NULL, err); +} + +void xemu_snapshots_delete(const char *vm_name, Error **err) +{ + delete_snapshot(vm_name, false, NULL, err); +} + +void xemu_snapshots_save_extra_data(QEMUFile *f) +{ + struct xbe *xbe_data = xemu_get_xbe_info(); + + int64_t xbe_title_len = 0; + char *xbe_title = g_utf16_to_utf8(xbe_data->cert->m_title_name, 40, NULL, + &xbe_title_len, NULL); + xbe_title_len++; + + XemuSnapshotHeader header = { XEMU_SNAPSHOT_DATA_MAGIC, 0 }; + + header.size += sizeof(xbe_title_len); + header.size += xbe_title_len; + + TextureBuffer *thumbnail = xemu_snapshots_extract_thumbnail(); + if (thumbnail && thumbnail->buffer) { + header.size += sizeof(TextureBuffer) - sizeof(thumbnail->buffer); + header.size += thumbnail->size; + } + + qemu_put_buffer(f, (const uint8_t *)&header, sizeof(header)); + qemu_put_buffer(f, (const uint8_t *)&xbe_title_len, sizeof(xbe_title_len)); + qemu_put_buffer(f, (const uint8_t *)xbe_title, xbe_title_len); + + if (thumbnail && thumbnail->buffer) { + qemu_put_buffer(f, (const uint8_t *)thumbnail, + sizeof(TextureBuffer) - sizeof(thumbnail->buffer)); + qemu_put_buffer(f, (const uint8_t *)thumbnail->buffer, thumbnail->size); + } + + g_free(xbe_title); + + if (thumbnail && thumbnail->buffer) { + g_free(thumbnail->buffer); + } + + g_free(thumbnail); + + xemu_snapshots_dirty = true; +} + +bool xemu_snapshots_offset_extra_data(QEMUFile *f) +{ + size_t ret; + XemuSnapshotHeader header; + ret = qemu_get_buffer(f, (uint8_t *)&header, sizeof(header)); + if (ret != sizeof(header)) { + return false; + } + + if (header.magic == XEMU_SNAPSHOT_DATA_MAGIC) { + /* + * qemu_file_skip only works if you aren't skipping past its buffer. + * Unfortunately, it's not usable here. + */ + void *buf = g_malloc(header.size); + qemu_get_buffer(f, buf, header.size); + g_free(buf); + } else { + qemu_file_skip(f, -((int)sizeof(header))); + } + + return true; +} + +void xemu_snapshots_mark_dirty(void) +{ + xemu_snapshots_dirty = true; +} diff --git a/ui/xemu-snapshots.h b/ui/xemu-snapshots.h new file mode 100644 index 0000000000..2f6dc4182f --- /dev/null +++ b/ui/xemu-snapshots.h @@ -0,0 +1,79 @@ +/* + * xemu User Interface + * + * Copyright (C) 2020-2022 Matt Borgerson + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef XEMU_SNAPSHOTS_H +#define XEMU_SNAPSHOTS_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "qemu/osdep.h" +#include "block/snapshot.h" + +#define XEMU_SNAPSHOT_DATA_MAGIC 0x78656d75 +#define XEMU_SNAPSHOT_HEIGHT 120 +#define XEMU_SNAPSHOT_WIDTH 160 + +extern const char **g_snapshot_shortcut_index_key_map[]; + +#pragma pack(1) +typedef struct TextureBuffer { + int channels; + unsigned long size; + void *buffer; +} TextureBuffer; +#pragma pack() + +typedef struct XemuSnapshotHeader { + uint32_t magic; + uint32_t size; +} XemuSnapshotHeader; + +typedef struct XemuSnapshotData { + int64_t xbe_title_len; + char *xbe_title; + bool xbe_title_present; + TextureBuffer thumbnail; + bool thumbnail_present; + unsigned int gl_thumbnail; +} XemuSnapshotData; + +// Implemented in xemu-snapshots.c +int xemu_snapshots_list(QEMUSnapshotInfo **info, XemuSnapshotData **extra_data, + Error **err); +void xemu_snapshots_load(const char *vm_name, Error **err); +void xemu_snapshots_save(const char *vm_name, Error **err); +void xemu_snapshots_delete(const char *vm_name, Error **err); + +void xemu_snapshots_save_extra_data(QEMUFile *f); +bool xemu_snapshots_offset_extra_data(QEMUFile *f); +void xemu_snapshots_mark_dirty(void); + +// Implemented in xemu-thumbnail.cc +void xemu_snapshots_set_framebuffer_texture(unsigned int tex, bool flip); +void xemu_snapshots_render_thumbnail(unsigned int tex, + TextureBuffer *thumbnail); +TextureBuffer *xemu_snapshots_extract_thumbnail(void); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/ui/xemu-thumbnail.cc b/ui/xemu-thumbnail.cc new file mode 100644 index 0000000000..968d1c8c2b --- /dev/null +++ b/ui/xemu-thumbnail.cc @@ -0,0 +1,92 @@ +/* + * xemu User Interface + * + * Copyright (C) 2020-2022 Matt Borgerson + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include + +#include "xemu-snapshots.h" +#include "xui/gl-helpers.hh" + +static GLuint display_tex = 0; +static bool display_flip = false; + +void xemu_snapshots_set_framebuffer_texture(GLuint tex, bool flip) +{ + display_tex = tex; + display_flip = flip; +} + +void xemu_snapshots_render_thumbnail(GLuint tex, TextureBuffer *thumbnail) +{ + std::vector pixels; + unsigned int width, height, channels; + if (fpng::fpng_decode_memory( + thumbnail->buffer, thumbnail->size, pixels, width, height, channels, + thumbnail->channels) != fpng::FPNG_DECODE_SUCCESS) { + return; + } + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, tex); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, + GL_UNSIGNED_BYTE, pixels.data()); +} + +TextureBuffer *xemu_snapshots_extract_thumbnail() +{ + /* + * Avoids crashing if a snapshot is made on a thread with no GL context + * Normally, this is not an issue, but it is better to fail safe than assert + * here. + * FIXME: Allow for dispatching a thumbnail request to the UI thread to + * remove this altogether. + */ + if (!SDL_GL_GetCurrentContext() || display_tex == 0) { + return NULL; + } + + // Render at 2x the base size to account for potential UI scaling + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, display_tex); + int thumbnail_width = XEMU_SNAPSHOT_WIDTH * 2; + int tex_width; + int tex_height; + glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &tex_width); + glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &tex_height); + int thumbnail_height = (int)(((float)tex_height / (float)tex_width) * (float)thumbnail_width); + + std::vector png; + if (!ExtractFramebufferPixels(display_tex, display_flip, png, thumbnail_width, thumbnail_height)) { + return NULL; + } + + TextureBuffer *thumbnail = (TextureBuffer *)g_malloc(sizeof(TextureBuffer)); + thumbnail->buffer = g_malloc(png.size() * sizeof(uint8_t)); + + thumbnail->channels = 3; + thumbnail->size = png.size() * sizeof(uint8_t); + memcpy(thumbnail->buffer, png.data(), thumbnail->size); + return thumbnail; +} diff --git a/ui/xemu.c b/ui/xemu.c index f0af36cea9..4cb9891b20 100644 --- a/ui/xemu.c +++ b/ui/xemu.c @@ -47,6 +47,7 @@ #include "xemu-input.h" #include "xemu-settings.h" // #include "xemu-shaders.h" +#include "xemu-snapshots.h" #include "xemu-version.h" #include "xemu-os-utils.h" @@ -1199,6 +1200,7 @@ void sdl2_gl_refresh(DisplayChangeListener *dcl) glClearColor(0, 0, 0, 0); glClear(GL_COLOR_BUFFER_BIT); + xemu_snapshots_set_framebuffer_texture(tex, flip_required); xemu_hud_set_framebuffer_texture(tex, flip_required); xemu_hud_render(); @@ -1548,6 +1550,7 @@ int main(int argc, char **argv) while (1) { sdl2_gl_refresh(&sdl2_console[0].dcl); + assert(glGetError() == GL_NO_ERROR); } // rcu_unregister_thread(); diff --git a/ui/xui/actions.cc b/ui/xui/actions.cc index e4d9e49005..f38be7dcb5 100644 --- a/ui/xui/actions.cc +++ b/ui/xui/actions.cc @@ -19,6 +19,8 @@ #include "common.hh" #include "misc.hh" #include "xemu-hud.h" +#include "../xemu-snapshots.h" +#include "../xemu-notifications.h" void ActionEjectDisc(void) { @@ -62,4 +64,28 @@ void ActionShutdown(void) void ActionScreenshot(void) { g_screenshot_pending = true; -} \ No newline at end of file +} + +void ActionActivateBoundSnapshot(int slot, bool save) +{ + assert(slot < 4 && slot >= 0); + const char *snapshot_name = *(g_snapshot_shortcut_index_key_map[slot]); + if (!snapshot_name || !(snapshot_name[0])) { + char *msg = g_strdup_printf("F%d is not bound to a snapshot", slot + 5); + xemu_queue_notification(msg); + g_free(msg); + return; + } + + Error *err = NULL; + if (save) { + xemu_snapshots_save(snapshot_name, &err); + } else { + xemu_snapshots_load(snapshot_name, &err); + } + + if (err) { + xemu_queue_error_message(error_get_pretty(err)); + error_free(err); + } +} diff --git a/ui/xui/actions.hh b/ui/xui/actions.hh index 2ac680af78..60235bf373 100644 --- a/ui/xui/actions.hh +++ b/ui/xui/actions.hh @@ -24,3 +24,4 @@ void ActionTogglePause(); void ActionReset(); void ActionShutdown(); void ActionScreenshot(); +void ActionActivateBoundSnapshot(int slot, bool save); diff --git a/ui/xui/common.hh b/ui/xui/common.hh index c937ed5deb..13240c84e4 100644 --- a/ui/xui/common.hh +++ b/ui/xui/common.hh @@ -28,6 +28,7 @@ #include #include #include +#include #include extern "C" { diff --git a/ui/xui/gl-helpers.cc b/ui/xui/gl-helpers.cc index 94d8d21ac9..a7ce17261f 100644 --- a/ui/xui/gl-helpers.cc +++ b/ui/xui/gl-helpers.cc @@ -26,12 +26,14 @@ #include "data/controller_mask.png.h" #include "data/logo_sdf.png.h" #include "ui/shader/xemu-logo-frag.h" +#include "data/xemu_64x64.png.h" #include "notifications.hh" Fbo *controller_fbo, *logo_fbo; GLuint g_controller_tex, - g_logo_tex; + g_logo_tex, + g_icon_tex; enum class ShaderType { Blit, @@ -155,10 +157,10 @@ static GLuint InitTexture(unsigned char *data, int width, int height, return tex; } -static GLuint LoadTextureFromMemory(const unsigned char *buf, unsigned int size) +static GLuint LoadTextureFromMemory(const unsigned char *buf, unsigned int size, bool flip=true) { // Flip vertically so textures are loaded according to GL convention. - stbi_set_flip_vertically_on_load(1); + stbi_set_flip_vertically_on_load(flip); int width, height, channels = 0; unsigned char *data = stbi_load_from_memory(buf, size, &width, &height, &channels, 4); @@ -442,6 +444,8 @@ void InitCustomRendering(void) g_logo_shader = NewDecalShader(ShaderType::Logo); logo_fbo = new Fbo(512, 512); + g_icon_tex = LoadTextureFromMemory(xemu_64x64_data, xemu_64x64_size, false); + g_framebuffer_shader = NewDecalShader(ShaderType::BlitGamma); } @@ -657,7 +661,7 @@ void RenderLogo(uint32_t time) glUseProgram(0); } -void RenderFramebuffer(GLint tex, int width, int height, bool flip) +void RenderFramebuffer(GLint tex, int width, int height, bool flip, bool apply_scaling_factor) { glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, tex); @@ -668,7 +672,10 @@ void RenderFramebuffer(GLint tex, int width, int height, bool flip) // Calculate scaling factors float scale[2]; - if (g_config.display.ui.fit == CONFIG_DISPLAY_UI_FIT_STRETCH) { + if (!apply_scaling_factor) { + scale[0] = 1.0; + scale[1] = 1.0; + } else if (g_config.display.ui.fit == CONFIG_DISPLAY_UI_FIT_STRETCH) { // Stretch to fit scale[0] = 1.0; scale[1] = 1.0; @@ -720,19 +727,27 @@ void RenderFramebuffer(GLint tex, int width, int height, bool flip) glDrawElements(GL_TRIANGLE_FAN, 4, GL_UNSIGNED_INT, NULL); } -void SaveScreenshot(GLuint tex, bool flip) +bool ExtractFramebufferPixels(GLuint tex, bool flip, std::vector &png, int width, int height) { - int width, height; glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, tex); - glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &width); - glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &height); - glBindTexture(GL_TEXTURE_2D, 0); + assert((width == 0 && height == 0) || (width > 0 && height > 0)); - if (g_config.display.ui.fit == CONFIG_DISPLAY_UI_FIT_SCALE_16_9) { - width = height * (16.0f / 9.0f); - } else if (g_config.display.ui.fit == CONFIG_DISPLAY_UI_FIT_SCALE_4_3) { - width = height * (4.0f / 3.0f); + bool params_from_tex = false; + if (width <= 0 && height <= 0) { + glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &width); + glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &height); + params_from_tex = true; + } + glBindTexture(GL_TEXTURE_2D, 0); + assert(width > 0 && height > 0); + + if (params_from_tex) { + if (g_config.display.ui.fit == CONFIG_DISPLAY_UI_FIT_SCALE_16_9) { + width = height * (16.0f / 9.0f); + } else if (g_config.display.ui.fit == CONFIG_DISPLAY_UI_FIT_SCALE_4_3) { + width = height * (4.0f / 3.0f); + } } std::vector pixels; @@ -742,7 +757,7 @@ void SaveScreenshot(GLuint tex, bool flip) fbo.Target(); bool blend = glIsEnabled(GL_BLEND); if (blend) glDisable(GL_BLEND); - RenderFramebuffer(tex, width, height, !flip); + RenderFramebuffer(tex, width, height, !flip, params_from_tex); if (blend) glEnable(GL_BLEND); glPixelStorei(GL_PACK_ROW_LENGTH, width); glPixelStorei(GL_PACK_IMAGE_HEIGHT, height); @@ -750,10 +765,15 @@ void SaveScreenshot(GLuint tex, bool flip) glReadPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, pixels.data()); fbo.Restore(); - char fname[128]; + return fpng::fpng_encode_image_to_memory(pixels.data(), width, height, 3, png); +} + +void SaveScreenshot(GLuint tex, bool flip) +{ Error *err = NULL; + char fname[128]; std::vector png; - if (fpng::fpng_encode_image_to_memory(pixels.data(), width, height, 3, png)) { + if (ExtractFramebufferPixels(tex, flip, png)) { time_t t = time(NULL); struct tm *tmp = localtime(&t); if (tmp) { diff --git a/ui/xui/gl-helpers.hh b/ui/xui/gl-helpers.hh index 9891c5fdc8..f881f9fd5b 100644 --- a/ui/xui/gl-helpers.hh +++ b/ui/xui/gl-helpers.hh @@ -17,6 +17,7 @@ // along with this program. If not, see . // #pragma once +#include #include "common.hh" #include "../xemu-input.h" @@ -38,6 +39,7 @@ public: }; extern Fbo *controller_fbo, *logo_fbo; +extern GLuint g_icon_tex; void InitCustomRendering(void); void RenderLogo(uint32_t time); @@ -45,5 +47,6 @@ void RenderController(float frame_x, float frame_y, uint32_t primary_color, uint32_t secondary_color, ControllerState *state); void RenderControllerPort(float frame_x, float frame_y, int i, uint32_t port_color); -void RenderFramebuffer(GLint tex, int width, int height, bool flip); +void RenderFramebuffer(GLint tex, int width, int height, bool flip, bool apply_scaling_factor = true); +bool ExtractFramebufferPixels(GLuint tex, bool flip, std::vector &png, int width = 0, int height = 0); void SaveScreenshot(GLuint tex, bool flip); diff --git a/ui/xui/main-menu.cc b/ui/xui/main-menu.cc index 19d12f9cc7..b011a4cb43 100644 --- a/ui/xui/main-menu.cc +++ b/ui/xui/main-menu.cc @@ -27,6 +27,7 @@ #include "misc.hh" #include "gl-helpers.hh" #include "reporting.hh" +#include "qapi/error.h" #include "../xemu-input.h" #include "../xemu-notifications.h" @@ -34,6 +35,7 @@ #include "../xemu-monitor.h" #include "../xemu-version.h" #include "../xemu-net.h" +#include "../xemu-snapshots.h" #include "../xemu-os-utils.h" #include "../xemu-xbe.h" @@ -639,115 +641,356 @@ void MainMenuNetworkView::DrawUdpOptions(bool appearing) ImGui::PopFont(); } -#if 0 -class MainMenuSnapshotsView : public virtual MainMenuTabView +void MainMenuSnapshotsView::Load() { -protected: - GLuint screenshot; + Error *err = NULL; -public: - void initScreenshot() - { - if (screenshot == 0) { - glGenTextures(1, &screenshot); - int w, h, n; - stbi_set_flip_vertically_on_load(0); - unsigned char *data = stbi_load("./data/screenshot.png", &w, &h, &n, 4); - assert(n == 4); - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, screenshot); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); - stbi_image_free(data); + if (!m_load_failed) { + m_snapshots_len = xemu_snapshots_list(&m_snapshots, &m_extra_data, &err); + } + + if (err) { + m_load_failed = true; + xemu_queue_error_message(error_get_pretty(err)); + error_free(err); + m_snapshots_len = 0; + } + + struct xbe *xbe = xemu_get_xbe_info(); + if (xbe && xbe->cert->m_titleid != m_current_title_id) { + g_free(m_current_title_name); + m_current_title_name = g_utf16_to_utf8(xbe->cert->m_title_name, 40, NULL, NULL, NULL); + m_current_title_id = xbe->cert->m_titleid; + } +} + +MainMenuSnapshotsView::MainMenuSnapshotsView(): MainMenuTabView() +{ + xemu_snapshots_mark_dirty(); + m_load_failed = false; + + m_search_regex = NULL; + m_current_title_name = NULL; + m_current_title_id = 0; + +} + +MainMenuSnapshotsView::~MainMenuSnapshotsView() +{ + g_free(m_snapshots); + g_free(m_extra_data); + xemu_snapshots_mark_dirty(); + + g_free(m_current_title_name); + g_free(m_search_regex); +} + +void MainMenuSnapshotsView::SnapshotBigButton(QEMUSnapshotInfo *snapshot, const char *title_name, GLuint screenshot) +{ + Error *err = NULL; + int current_snapshot_binding = -1; + for (int i = 0; i < 4; ++i) { + if (g_strcmp0(*(g_snapshot_shortcut_index_key_map[i]), snapshot->name) == 0) { + assert(current_snapshot_binding == -1); + current_snapshot_binding = i; } } - void snapshotBigButton(const char *name, const char *title_name, GLuint screenshot) - { - ImGuiStyle &style = ImGui::GetStyle(); - ImVec2 pos = ImGui::GetCursorPos(); - ImDrawList *draw_list = ImGui::GetWindowDrawList(); + ImGuiStyle &style = ImGui::GetStyle(); + ImVec2 pos = ImGui::GetCursorPos(); + ImDrawList *draw_list = ImGui::GetWindowDrawList(); - ImGui::PushFont(g_font_mgr.m_menuFont); - const char *icon = ICON_FA_CIRCLE_XMARK; - ImVec2 ts_icon = ImGui::CalcTextSize(icon); - ts_icon.x += 2*style.FramePadding.x; - ImGui::PopFont(); + ImGui::PushFont(g_font_mgr.m_menu_font); + const char *close_icon = ICON_FA_CIRCLE_XMARK; + ImVec2 ts_close_icon = ImGui::CalcTextSize(close_icon); + ts_close_icon.x += 2*style.FramePadding.x; + ImGui::PopFont(); - ImGui::PushFont(g_font_mgr.m_menuFontSmall); - ImVec2 ts_sub = ImGui::CalcTextSize(name); - ImGui::PopFont(); + ImGui::PushFont(g_font_mgr.m_menu_font); + const char *save_icon = ICON_FA_FLOPPY_DISK; + ImVec2 ts_save_icon = ImGui::CalcTextSize(save_icon); + ts_save_icon.x += 2*style.FramePadding.x; + ImGui::PopFont(); - ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0, 0)); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, g_viewport_mgr.scale(ImVec2(5, 5))); - ImGui::PushFont(g_font_mgr.m_menuFontMedium); + ImGui::PushFont(g_font_mgr.m_menu_font); + const char *binding_icon = ICON_FA_KEYBOARD; + ImVec2 ts_binding_icon = ImGui::CalcTextSize(binding_icon); + ts_binding_icon.x += 2*style.FramePadding.x; + ImGui::PopFont(); - ImVec2 ts_title = ImGui::CalcTextSize(name); - ImVec2 thumbnail_size = g_viewport_mgr.scale(ImVec2(160, 120)); - ImVec2 thumbnail_pos(style.FramePadding.x, style.FramePadding.y); - ImVec2 text_pos(thumbnail_pos.x + thumbnail_size.x + style.FramePadding.x * 2, thumbnail_pos.y); - ImVec2 subtext_pos(text_pos.x, text_pos.y + ts_title.y + style.FramePadding.x); + ImGui::PushFont(g_font_mgr.m_menu_font_small); + ImVec2 ts_sub = ImGui::CalcTextSize(snapshot->name); + ImGui::PopFont(); - ImGui::Button("###button", ImVec2(ImGui::GetContentRegionAvail().x, fmax(thumbnail_size.y + style.FramePadding.y * 2, - ts_title.y + ts_sub.y + style.FramePadding.y * 3))); - ImGui::PopFont(); - const ImVec2 sz = ImGui::GetItemRectSize(); - const ImVec2 p0 = ImGui::GetItemRectMin(); - const ImVec2 p1 = ImGui::GetItemRectMax(); - ts_icon.y = sz.y; + ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0, 0)); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, g_viewport_mgr.Scale(ImVec2(5, 5))); + ImGui::PushFont(g_font_mgr.m_menu_font_medium); - // Snapshot thumbnail - ImGui::SetItemAllowOverlap(); - ImGui::SameLine(); - ImGui::SetCursorPosX(pos.x + thumbnail_pos.x); - ImGui::SetCursorPosY(pos.y + thumbnail_pos.y); - ImGui::Image((ImTextureID)screenshot, thumbnail_size, ImVec2(0,0), ImVec2(1,1)); + ImVec2 ts_title = ImGui::CalcTextSize(snapshot->name); + ImVec2 thumbnail_size = g_viewport_mgr.Scale(ImVec2(XEMU_SNAPSHOT_WIDTH, XEMU_SNAPSHOT_HEIGHT)); + ImVec2 thumbnail_pos(style.FramePadding.x, style.FramePadding.y); + ImVec2 name_pos(thumbnail_pos.x + thumbnail_size.x + style.FramePadding.x * 2, thumbnail_pos.y); + ImVec2 date_pos(name_pos.x, name_pos.y + ts_title.y + style.FramePadding.x); + ImVec2 title_pos(name_pos.x, date_pos.y + ts_title.y + style.FramePadding.x); + ImVec2 binding_pos(name_pos.x, title_pos.y + ts_title.y + style.FramePadding.x); - draw_list->PushClipRect(p0, p1, true); + bool load = ImGui::Button("###button", ImVec2(ImGui::GetContentRegionAvail().x, fmax(thumbnail_size.y + style.FramePadding.y * 2, + ts_title.y + ts_sub.y + style.FramePadding.y * 3))); + ImGui::PopFont(); + const ImVec2 sz = ImGui::GetItemRectSize(); + const ImVec2 p0 = ImGui::GetItemRectMin(); + const ImVec2 p1 = ImGui::GetItemRectMax(); + ts_close_icon.y = sz.y / 1; + ts_save_icon.y = sz.y / 1; + ts_binding_icon.y = sz.y / 1; - // Snapshot title - ImGui::PushFont(g_font_mgr.m_menuFontMedium); - draw_list->AddText(ImVec2(p0.x + text_pos.x, p0.y + text_pos.y), IM_COL32(255, 255, 255, 255), name); - ImGui::PopFont(); - - // Snapshot subtitle - ImGui::PushFont(g_font_mgr.m_menuFontSmall); - draw_list->AddText(ImVec2(p0.x + subtext_pos.x, p0.y + subtext_pos.y), IM_COL32(255, 255, 255, 200), title_name); - ImGui::PopFont(); - - draw_list->PopClipRect(); - - // Delete button - ImGui::SameLine(); - ImGui::SetCursorPosY(pos.y); - ImGui::SetCursorPosX(pos.x + sz.x - ts_icon.x); - ImGui::PushFont(g_font_mgr.m_menuFont); - ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0, 0)); - ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32_BLACK_TRANS); - ImGui::Button(icon, ts_icon); - ImGui::PopStyleColor(); - ImGui::PopStyleVar(1); - ImGui::PopFont(); - ImGui::PopStyleVar(2); - } - - void Draw() - { - initScreenshot(); - for (int i = 0; i < 15; i++) { - char buf[64]; - snprintf(buf, sizeof(buf), "%s", "Apr 9 2022 19:44"); - ImGui::PushID(i); - snapshotBigButton(buf, "Halo: Combat Evolved", screenshot); - ImGui::PopID(); + if (load) { + xemu_snapshots_load(snapshot->name, &err); + if (err) { + xemu_queue_error_message(error_get_pretty(err)); + error_free(err); } } -}; -#endif + + // Snapshot thumbnail + ImGui::SetItemAllowOverlap(); + ImGui::SameLine(); + ImGui::SetCursorPosX(pos.x + thumbnail_pos.x); + ImGui::SetCursorPosY(pos.y + thumbnail_pos.y); + if (screenshot > 0) { + ImGui::Image((ImTextureID)(uint64_t)screenshot, thumbnail_size); + } else { + ImGui::Image((ImTextureID)(uint64_t)g_icon_tex, thumbnail_size); + } + + draw_list->PushClipRect(p0, p1, true); + + // Snapshot title + ImGui::PushFont(g_font_mgr.m_menu_font_medium); + draw_list->AddText(ImVec2(p0.x + name_pos.x, p0.y + name_pos.y), IM_COL32(255, 255, 255, 255), snapshot->name); + ImGui::PopFont(); + + // Snapshot date + g_autoptr(GDateTime) date = g_date_time_new_from_unix_local(snapshot->date_sec); + char *date_buf = g_date_time_format(date, "%Y-%m-%d %H:%M:%S"); + ImGui::PushFont(g_font_mgr.m_menu_font_small); + draw_list->AddText(ImVec2(p0.x + date_pos.x, p0.y + date_pos.y), IM_COL32(255, 255, 255, 200), date_buf); + ImGui::PopFont(); + g_free(date_buf); + + // Snapshot title + ImGui::PushFont(g_font_mgr.m_menu_font_small); + draw_list->AddText(ImVec2(p0.x + title_pos.x, p0.y + title_pos.y), IM_COL32(255, 255, 255, 200), title_name); + ImGui::PopFont(); + + // Snapshot binding + ImGui::PushFont(g_font_mgr.m_menu_font_small); + if (current_snapshot_binding != -1) { + char *binding_text = g_strdup_printf("Bound to F%d", current_snapshot_binding + 5); + draw_list->AddText(ImVec2(p0.x + binding_pos.x, p0.y + binding_pos.y), IM_COL32(255, 255, 255, 200), binding_text); + g_free(binding_text); + } else { + ImGui::Text("Not Bound"); + draw_list->AddText(ImVec2(p0.x + binding_pos.x, p0.y + binding_pos.y), IM_COL32(255, 255, 255, 200), "Not Bound"); + } + + ImGui::PopFont(); + + draw_list->PopClipRect(); + + // Delete button + ImGui::SameLine(); + ImGui::SetCursorPosY(pos.y); + ImGui::SetCursorPosX(pos.x + sz.x - ts_close_icon.x); + ImGui::PushFont(g_font_mgr.m_menu_font); + ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0, 0)); + ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32_BLACK_TRANS); + if (ImGui::Button(close_icon, ts_close_icon)) { + xemu_snapshots_delete(snapshot->name, &err); + if (err) { + xemu_queue_error_message(error_get_pretty(err)); + error_free(err); + } + } + ImGui::PopStyleColor(); + ImGui::PopStyleVar(1); + ImGui::PopFont(); + + // Save button + ImGui::SameLine(); + ImGui::SetCursorPosY(pos.y); + ImGui::SetCursorPosX(pos.x + sz.x - ts_save_icon.x - ts_close_icon.x); + ImGui::PushFont(g_font_mgr.m_menu_font); + ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0, 0)); + ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32_BLACK_TRANS); + if (ImGui::Button(save_icon, ts_save_icon)) { + xemu_snapshots_save(snapshot->name, &err); + if (err) { + xemu_queue_error_message(error_get_pretty(err)); + error_free(err); + } + } + ImGui::PopStyleColor(); + ImGui::PopStyleVar(1); + ImGui::PopFont(); + + // Bind button + ImGui::SameLine(); + ImGui::SetCursorPosY(pos.y); + ImGui::SetCursorPosX(pos.x + sz.x - ts_binding_icon.x - ts_save_icon.x - ts_close_icon.x); + ImGui::PushFont(g_font_mgr.m_menu_font); + ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0, 0)); + ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32_BLACK_TRANS); + if (ImGui::Button(binding_icon, ts_binding_icon)) { + ImGui::OpenPopup("Bind Snapshot Key"); + } + + if (ImGui::BeginPopupContextItem("Bind Snapshot Key")) { + for (int i = 0; i < 4; ++i) { + char *item_name = g_strdup_printf("Bind to F%d", i + 5); + + if (ImGui::MenuItem(item_name)) { + if (current_snapshot_binding >= 0) { + xemu_settings_set_string(g_snapshot_shortcut_index_key_map[current_snapshot_binding], ""); + } + xemu_settings_set_string(g_snapshot_shortcut_index_key_map[i], snapshot->name); + current_snapshot_binding = i; + + ImGui::CloseCurrentPopup(); + } + + g_free(item_name); + } + + if (current_snapshot_binding >= 0) { + ImGui::Separator(); + if (ImGui::MenuItem("Unbind")) { + xemu_settings_set_string(g_snapshot_shortcut_index_key_map[current_snapshot_binding], ""); + current_snapshot_binding = -1; + } + } + + ImGui::EndPopup(); + } + + ImGui::PopStyleColor(); + ImGui::PopStyleVar(1); + ImGui::PopFont(); + + + ImGui::PopStyleVar(2); +} + +static int MainMenuSnapshotsViewUpdateSearchBox(ImGuiInputTextCallbackData *data) +{ + GError *gerr = NULL; + MainMenuSnapshotsView *win = (MainMenuSnapshotsView*)data->UserData; + + if (win->m_search_regex) g_free(win->m_search_regex); + if (data->BufTextLen == 0) { + win->m_search_regex = NULL; + return 0; + } + + char *buf = g_strdup_printf("(.*)%s(.*)", data->Buf); + + win->m_search_regex = g_regex_new(buf, (GRegexCompileFlags)0, (GRegexMatchFlags)0, &gerr); + g_free(buf); + if (gerr) { + win->m_search_regex = NULL; + return 1; + } + + return 0; +} + +void MainMenuSnapshotsView::Draw() +{ + Load(); + SectionTitle("Snapshots"); + ImGui::Checkbox("Filter by current title", &g_config.general.snapshots.filter_current_game); + ImGui::InputTextWithHint("##search", "Filter by name...", &m_search_buf, ImGuiInputTextFlags_CallbackEdit, + &MainMenuSnapshotsViewUpdateSearchBox, this); + + ImGui::InputTextWithHint("##create", "Create new snapshot", &m_create_buf); + + bool snapshot_with_create_name_exists = false; + for (int i = 0; i < m_snapshots_len; ++i) { + if (g_strcmp0(m_create_buf.c_str(), m_snapshots[i].name) == 0) { + snapshot_with_create_name_exists = true; + break; + } + } + + ImGui::SameLine(); + if (ImGui::Button(snapshot_with_create_name_exists ? "Save" : "Create") && !m_create_buf.empty()) { + xemu_snapshots_save(m_create_buf.c_str(), NULL); + m_create_buf.clear(); + } + + if (snapshot_with_create_name_exists && ImGui::IsItemHovered()) { + ImGui::SetTooltip("A snapshot with the name \"%s\" already exists. This button will overwrite the existing snapshot.", m_create_buf.c_str()); + } + + bool search_buf_equal = false; + for (int i = m_snapshots_len - 1; i >= 0; i--) { + if (g_config.general.snapshots.filter_current_game && m_extra_data[i].xbe_title_present && + (strcmp(m_current_title_name, m_extra_data[i].xbe_title) != 0)) { + continue; + } + + if (m_search_regex) { + GMatchInfo *match; + bool keep_entry = false; + + g_regex_match(m_search_regex, m_snapshots[i].name, (GRegexMatchFlags)0, &match); + keep_entry |= g_match_info_matches(match); + g_match_info_free(match); + + if (m_extra_data[i].xbe_title_present) { + g_regex_match(m_search_regex, m_extra_data[i].xbe_title, (GRegexMatchFlags)0, &match); + keep_entry |= g_match_info_matches(match); + g_free(match); + } + + if (!keep_entry) { + continue; + } + } + + search_buf_equal |= g_strcmp0(m_search_buf.c_str(), m_snapshots[i].name) == 0; + + ImGui::PushID(i); + GLuint thumbnail = 0; + if (m_extra_data[i].thumbnail_present) { + thumbnail = m_extra_data[i].gl_thumbnail; + } + SnapshotBigButton( + m_snapshots + i, + m_extra_data[i].xbe_title_present ? m_extra_data[i].xbe_title : "Unknown", + thumbnail + ); + ImGui::PopID(); + } + + /* Snapshot names are unique, don't give option to create new one if it exists already */ + if (!search_buf_equal && !m_search_buf.empty()) { + char *new_snapshot = g_strdup_printf("Create Snapshot '%s'", m_search_buf.c_str()); + ImGui::PushFont(g_font_mgr.m_menu_font_small); + ImVec2 new_snapshot_size = ImGui::CalcTextSize(new_snapshot); + ImGui::SetCursorPosX((ImGui::GetContentRegionAvail().x - new_snapshot_size.x)/2); + if (ImGui::Button(new_snapshot)) { + Error *err = NULL; + xemu_snapshots_save(m_search_buf.c_str(), &err); + if (err) { + xemu_queue_error_message(error_get_pretty(err)); + error_free(err); + } + } + ImGui::PopFont(); + g_free(new_snapshot); + } +} MainMenuSystemView::MainMenuSystemView() : m_dirty(false) { @@ -929,7 +1172,7 @@ MainMenuScene::MainMenuScene() m_display_button("Display", ICON_FA_TV), m_audio_button("Audio", ICON_FA_VOLUME_HIGH), m_network_button("Network", ICON_FA_NETWORK_WIRED), - // m_snapshots_button("Snapshots", ICON_FA_CLOCK_ROTATE_LEFT), + m_snapshots_button("Snapshots", ICON_FA_CLOCK_ROTATE_LEFT), m_system_button("System", ICON_FA_MICROCHIP), m_about_button("About", ICON_FA_CIRCLE_INFO) { @@ -940,7 +1183,7 @@ MainMenuScene::MainMenuScene() m_tabs.push_back(&m_display_button); m_tabs.push_back(&m_audio_button); m_tabs.push_back(&m_network_button); - // m_tabs.push_back(&m_snapshots_button); + m_tabs.push_back(&m_snapshots_button); m_tabs.push_back(&m_system_button); m_tabs.push_back(&m_about_button); @@ -949,7 +1192,7 @@ MainMenuScene::MainMenuScene() m_views.push_back(&m_display_view); m_views.push_back(&m_audio_view); m_views.push_back(&m_network_view); - // m_views.push_back(&m_snapshots_view); + m_views.push_back(&m_snapshots_view); m_views.push_back(&m_system_view); m_views.push_back(&m_about_view); @@ -977,15 +1220,18 @@ void MainMenuScene::ShowNetwork() { SetNextViewIndexWithFocus(4); } -// void MainMenuScene::showSnapshots() { SetNextViewIndexWithFocus(5); } -void MainMenuScene::ShowSystem() +void MainMenuScene::ShowSnapshots() { SetNextViewIndexWithFocus(5); } -void MainMenuScene::ShowAbout() +void MainMenuScene::ShowSystem() { SetNextViewIndexWithFocus(6); } +void MainMenuScene::ShowAbout() +{ + SetNextViewIndexWithFocus(7); +} void MainMenuScene::SetNextViewIndexWithFocus(int i) { diff --git a/ui/xui/main-menu.hh b/ui/xui/main-menu.hh index ced9db147a..2b469c1112 100644 --- a/ui/xui/main-menu.hh +++ b/ui/xui/main-menu.hh @@ -24,6 +24,7 @@ #include "widgets.hh" #include "scene.hh" #include "scene-components.hh" +#include "../xemu-snapshots.h" extern "C" { #include "net/pcap.h" @@ -102,8 +103,24 @@ public: class MainMenuSnapshotsView : public virtual MainMenuTabView { +protected: + QEMUSnapshotInfo *m_snapshots; + XemuSnapshotData *m_extra_data; + int m_snapshots_len; + uint32_t m_current_title_id; + char *m_current_title_name; + std::string m_search_buf; + std::string m_create_buf; + bool m_load_failed; + +private: + void Load(); + public: - void SnapshotBigButton(const char *name, const char *title_name, + GRegex *m_search_regex; + MainMenuSnapshotsView(); + ~MainMenuSnapshotsView(); + void SnapshotBigButton(QEMUSnapshotInfo *snapshot, const char *title_name, GLuint screenshot); void Draw() override; }; @@ -153,7 +170,7 @@ protected: m_display_button, m_audio_button, m_network_button, - // m_snapshots_button, + m_snapshots_button, m_system_button, m_about_button; std::vector m_views; @@ -162,7 +179,7 @@ protected: MainMenuDisplayView m_display_view; MainMenuAudioView m_audio_view; MainMenuNetworkView m_network_view; - // MainMenuSnapshotsView m_snapshots_view; + MainMenuSnapshotsView m_snapshots_view; MainMenuSystemView m_system_view; MainMenuAboutView m_about_view; @@ -174,7 +191,7 @@ public: void ShowDisplay(); void ShowAudio(); void ShowNetwork(); - // void ShowSnapshots(); + void ShowSnapshots(); void ShowSystem(); void ShowAbout(); void SetNextViewIndexWithFocus(int i); diff --git a/ui/xui/main.cc b/ui/xui/main.cc index e46255d8ef..e027c409a9 100644 --- a/ui/xui/main.cc +++ b/ui/xui/main.cc @@ -31,6 +31,7 @@ #include #include +#include "actions.hh" #include "common.hh" #include "xemu-hud.h" #include "misc.hh" @@ -277,6 +278,14 @@ void xemu_hud_render(void) !ImGui::IsAnyItemFocused() && !ImGui::IsAnyItemHovered())) { g_scene_mgr.PushScene(g_popup_menu); } + + bool mod_key_down = ImGui::IsKeyDown(ImGuiKey_ModShift); + for (int f_key = 0; f_key < 4; ++f_key) { + if (ImGui::IsKeyPressed(f_key + ImGuiKey_F5)) { + ActionActivateBoundSnapshot(f_key, mod_key_down); + break; + } + } } first_boot_window.Draw(); diff --git a/ui/xui/menubar.cc b/ui/xui/menubar.cc index 0540631d10..71c2eab69a 100644 --- a/ui/xui/menubar.cc +++ b/ui/xui/menubar.cc @@ -16,6 +16,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . // +#include "ui/xemu-notifications.h" #include "common.hh" #include "main-menu.hh" #include "menubar.hh" @@ -88,6 +89,48 @@ void ShowMainMenu() if (ImGui::MenuItem(running ? "Pause" : "Resume", SHORTCUT_MENU_TEXT(P))) ActionTogglePause(); if (ImGui::MenuItem("Screenshot", "F12")) ActionScreenshot(); + if (ImGui::BeginMenu("Snapshot")) { + if (ImGui::MenuItem("Create Snapshot")) { + xemu_snapshots_save(NULL, NULL); + xemu_queue_notification("Created new snapshot"); + } + + for (int i = 0; i < 4; ++i) { + char *hotkey = g_strdup_printf("Shift+F%d", i + 5); + + char *load_name; + char *save_name; + + assert(g_snapshot_shortcut_index_key_map[i]); + bool bound = *(g_snapshot_shortcut_index_key_map[i]) && + (**(g_snapshot_shortcut_index_key_map[i]) != 0); + + if (bound) { + load_name = g_strdup_printf("Load '%s'", *(g_snapshot_shortcut_index_key_map[i])); + save_name = g_strdup_printf("Save '%s'", *(g_snapshot_shortcut_index_key_map[i])); + } else { + load_name = g_strdup_printf("Load F%d (Unbound)", i + 5); + save_name = g_strdup_printf("Save F%d (Unbound)", i + 5); + } + + ImGui::Separator(); + + if (ImGui::MenuItem(load_name, hotkey + sizeof("Shift+") - 1, false, bound)) { + ActionActivateBoundSnapshot(i, false); + } + + if (ImGui::MenuItem(save_name, hotkey, false, bound)) { + ActionActivateBoundSnapshot(i, false); + } + + g_free(hotkey); + g_free(load_name); + g_free(save_name); + } + + ImGui::EndMenu(); + } + ImGui::Separator(); if (ImGui::MenuItem("Eject Disc", SHORTCUT_MENU_TEXT(E))) ActionEjectDisc(); @@ -101,6 +144,7 @@ void ShowMainMenu() if (ImGui::MenuItem(" Display")) g_main_menu.ShowDisplay(); if (ImGui::MenuItem(" Audio")) g_main_menu.ShowAudio(); if (ImGui::MenuItem(" Network")) g_main_menu.ShowNetwork(); + if (ImGui::MenuItem(" Snapshots")) g_main_menu.ShowSnapshots(); if (ImGui::MenuItem(" System")) g_main_menu.ShowSystem(); ImGui::Separator(); diff --git a/ui/xui/popup-menu.cc b/ui/xui/popup-menu.cc index 6cad92e2a1..cf6fc3f130 100644 --- a/ui/xui/popup-menu.cc +++ b/ui/xui/popup-menu.cc @@ -16,6 +16,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . // +#include "ui/xemu-notifications.h" #include #include #include "misc.hh" @@ -27,6 +28,8 @@ #include "input-manager.hh" #include "xemu-hud.h" #include "IconsFontAwesome6.h" +#include "../xemu-snapshots.h" +#include "main-menu.hh" PopupMenuItemDelegate::~PopupMenuItemDelegate() {} void PopupMenuItemDelegate::PushMenu(PopupMenu &menu) {} @@ -269,7 +272,7 @@ public: } }; -extern Scene g_main_menu; +extern MainMenuScene g_main_menu; class SettingsPopupMenu : public virtual PopupMenu { protected: @@ -292,6 +295,11 @@ public: nav.PushFocus(); nav.PushMenu(display_mode); } + if (PopupMenuButton("Snapshots...", ICON_FA_CLOCK_ROTATE_LEFT)) { + nav.ClearMenuStack(); + g_scene_mgr.PushScene(g_main_menu); + g_main_menu.ShowSnapshots(); + } if (PopupMenuButton("All settings...", ICON_FA_SLIDERS)) { nav.ClearMenuStack(); g_scene_mgr.PushScene(g_main_menu); @@ -338,6 +346,11 @@ public: ActionScreenshot(); pop = true; } + if (PopupMenuButton("Save Snapshot", ICON_FA_DOWNLOAD)) { + xemu_snapshots_save(NULL, NULL); + xemu_queue_notification("Created new snapshot"); + pop = true; + } if (PopupMenuButton("Eject Disc", ICON_FA_EJECT)) { ActionEjectDisc(); pop = true; From 348b45b436ffd8eb2df2cd27b60570273d0df211 Mon Sep 17 00:00:00 2001 From: Matt Borgerson Date: Sat, 3 Jun 2023 22:22:24 -0700 Subject: [PATCH 16/39] ui: Support widescreen thumbnails --- ui/xemu-snapshots.h | 4 +- ui/xemu-thumbnail.cc | 15 ++---- ui/xui/gl-helpers.cc | 106 +++++++++++++++++++++++++------------------ ui/xui/gl-helpers.hh | 6 ++- ui/xui/main-menu.cc | 24 +++++++++- 5 files changed, 93 insertions(+), 62 deletions(-) diff --git a/ui/xemu-snapshots.h b/ui/xemu-snapshots.h index 2f6dc4182f..792b9d3c4f 100644 --- a/ui/xemu-snapshots.h +++ b/ui/xemu-snapshots.h @@ -28,8 +28,8 @@ extern "C" { #include "block/snapshot.h" #define XEMU_SNAPSHOT_DATA_MAGIC 0x78656d75 -#define XEMU_SNAPSHOT_HEIGHT 120 -#define XEMU_SNAPSHOT_WIDTH 160 +#define XEMU_SNAPSHOT_THUMBNAIL_WIDTH 160 +#define XEMU_SNAPSHOT_THUMBNAIL_HEIGHT 120 extern const char **g_snapshot_shortcut_index_key_map[]; diff --git a/ui/xemu-thumbnail.cc b/ui/xemu-thumbnail.cc index 968d1c8c2b..86df3ef2b4 100644 --- a/ui/xemu-thumbnail.cc +++ b/ui/xemu-thumbnail.cc @@ -67,24 +67,15 @@ TextureBuffer *xemu_snapshots_extract_thumbnail() return NULL; } - // Render at 2x the base size to account for potential UI scaling - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, display_tex); - int thumbnail_width = XEMU_SNAPSHOT_WIDTH * 2; - int tex_width; - int tex_height; - glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &tex_width); - glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &tex_height); - int thumbnail_height = (int)(((float)tex_height / (float)tex_width) * (float)thumbnail_width); - std::vector png; - if (!ExtractFramebufferPixels(display_tex, display_flip, png, thumbnail_width, thumbnail_height)) { + if (!RenderFramebufferToPng(display_tex, display_flip, png, + 2 * XEMU_SNAPSHOT_THUMBNAIL_WIDTH, + 2 * XEMU_SNAPSHOT_THUMBNAIL_HEIGHT)) { return NULL; } TextureBuffer *thumbnail = (TextureBuffer *)g_malloc(sizeof(TextureBuffer)); thumbnail->buffer = g_malloc(png.size() * sizeof(uint8_t)); - thumbnail->channels = 3; thumbnail->size = png.size() * sizeof(uint8_t); memcpy(thumbnail->buffer, png.data(), thumbnail->size); diff --git a/ui/xui/gl-helpers.cc b/ui/xui/gl-helpers.cc index a7ce17261f..996f263726 100644 --- a/ui/xui/gl-helpers.cc +++ b/ui/xui/gl-helpers.cc @@ -661,21 +661,60 @@ void RenderLogo(uint32_t time) glUseProgram(0); } -void RenderFramebuffer(GLint tex, int width, int height, bool flip, bool apply_scaling_factor) +// Scale proportionally to fit in +void ScaleDimensions(int src_width, int src_height, int max_width, int max_height, int *out_width, int *out_height) +{ + float w_ratio = (float)max_width/(float)max_height; + float t_ratio = (float)src_width/(float)src_height; + + if (w_ratio >= t_ratio) { + *out_width = (float)max_width * t_ratio/w_ratio; + *out_height = max_height; + } else { + *out_width = max_width; + *out_height = (float)max_height * w_ratio/t_ratio; + } +} + +void RenderFramebuffer(GLint tex, int width, int height, bool flip, float scale[2]) { glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, tex); + DecalShader *s = g_framebuffer_shader; + s->flip = flip; + glViewport(0, 0, width, height); + glUseProgram(s->prog); + glBindVertexArray(s->vao); + glUniform1i(s->flipy_loc, s->flip); + glUniform4f(s->scale_offset_loc, scale[0], scale[1], 0, 0); + glUniform4f(s->tex_scale_offset_loc, 1.0, 1.0, 0, 0); + glUniform1i(s->tex_loc, 0); + + const uint8_t *palette = nv2a_get_dac_palette(); + for (int i = 0; i < 256; i++) { + uint32_t e = (palette[i * 3 + 2] << 16) | (palette[i * 3 + 1] << 8) | + palette[i * 3]; + glUniform1ui(s->palette_loc[i], e); + } + + glClearColor(0, 0, 0, 0); + glClear(GL_COLOR_BUFFER_BIT); + glDrawElements(GL_TRIANGLE_FAN, 4, GL_UNSIGNED_INT, NULL); +} + +void RenderFramebuffer(GLint tex, int width, int height, bool flip) +{ int tw, th; + float scale[2]; + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, tex); glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &tw); glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &th); // Calculate scaling factors - float scale[2]; - if (!apply_scaling_factor) { - scale[0] = 1.0; - scale[1] = 1.0; - } else if (g_config.display.ui.fit == CONFIG_DISPLAY_UI_FIT_STRETCH) { + if (g_config.display.ui.fit == CONFIG_DISPLAY_UI_FIT_STRETCH) { // Stretch to fit scale[0] = 1.0; scale[1] = 1.0; @@ -705,50 +744,27 @@ void RenderFramebuffer(GLint tex, int width, int height, bool flip, bool apply_s } } - DecalShader *s = g_framebuffer_shader; - s->flip = flip; - glViewport(0, 0, width, height); - glUseProgram(s->prog); - glBindVertexArray(s->vao); - glUniform1i(s->flipy_loc, s->flip); - glUniform4f(s->scale_offset_loc, scale[0], scale[1], 0, 0); - glUniform4f(s->tex_scale_offset_loc, 1.0, 1.0, 0, 0); - glUniform1i(s->tex_loc, 0); - - const uint8_t *palette = nv2a_get_dac_palette(); - for (int i = 0; i < 256; i++) { - uint32_t e = (palette[i * 3 + 2] << 16) | (palette[i * 3 + 1] << 8) | - palette[i * 3]; - glUniform1ui(s->palette_loc[i], e); - } - - glClearColor(0, 0, 0, 0); - glClear(GL_COLOR_BUFFER_BIT); - glDrawElements(GL_TRIANGLE_FAN, 4, GL_UNSIGNED_INT, NULL); + RenderFramebuffer(tex, width, height, flip, scale); } -bool ExtractFramebufferPixels(GLuint tex, bool flip, std::vector &png, int width, int height) +bool RenderFramebufferToPng(GLuint tex, bool flip, std::vector &png, int max_width, int max_height) { + int width, height; + glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, tex); - assert((width == 0 && height == 0) || (width > 0 && height > 0)); + glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &width); + glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &height); - bool params_from_tex = false; - if (width <= 0 && height <= 0) { - glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &width); - glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &height); - params_from_tex = true; + if (g_config.display.ui.fit == CONFIG_DISPLAY_UI_FIT_SCALE_16_9) { + width = height * (16.0f / 9.0f); + } else if (g_config.display.ui.fit == CONFIG_DISPLAY_UI_FIT_SCALE_4_3) { + width = height * (4.0f / 3.0f); } - glBindTexture(GL_TEXTURE_2D, 0); - assert(width > 0 && height > 0); - if (params_from_tex) { - if (g_config.display.ui.fit == CONFIG_DISPLAY_UI_FIT_SCALE_16_9) { - width = height * (16.0f / 9.0f); - } else if (g_config.display.ui.fit == CONFIG_DISPLAY_UI_FIT_SCALE_4_3) { - width = height * (4.0f / 3.0f); - } - } + if (!max_width) max_width = width; + if (!max_height) max_height = height; + ScaleDimensions(width, height, max_width, max_height, &width, &height); std::vector pixels; pixels.resize(width * height * 3); @@ -757,7 +773,8 @@ bool ExtractFramebufferPixels(GLuint tex, bool flip, std::vector &png, fbo.Target(); bool blend = glIsEnabled(GL_BLEND); if (blend) glDisable(GL_BLEND); - RenderFramebuffer(tex, width, height, !flip, params_from_tex); + float scale[2] = {1.0, 1.0}; + RenderFramebuffer(tex, width, height, !flip, scale); if (blend) glEnable(GL_BLEND); glPixelStorei(GL_PACK_ROW_LENGTH, width); glPixelStorei(GL_PACK_IMAGE_HEIGHT, height); @@ -773,7 +790,8 @@ void SaveScreenshot(GLuint tex, bool flip) Error *err = NULL; char fname[128]; std::vector png; - if (ExtractFramebufferPixels(tex, flip, png)) { + + if (RenderFramebufferToPng(tex, flip, png)) { time_t t = time(NULL); struct tm *tmp = localtime(&t); if (tmp) { diff --git a/ui/xui/gl-helpers.hh b/ui/xui/gl-helpers.hh index f881f9fd5b..ce5c00b6a4 100644 --- a/ui/xui/gl-helpers.hh +++ b/ui/xui/gl-helpers.hh @@ -47,6 +47,8 @@ void RenderController(float frame_x, float frame_y, uint32_t primary_color, uint32_t secondary_color, ControllerState *state); void RenderControllerPort(float frame_x, float frame_y, int i, uint32_t port_color); -void RenderFramebuffer(GLint tex, int width, int height, bool flip, bool apply_scaling_factor = true); -bool ExtractFramebufferPixels(GLuint tex, bool flip, std::vector &png, int width = 0, int height = 0); +void RenderFramebuffer(GLint tex, int width, int height, bool flip); +void RenderFramebuffer(GLint tex, int width, int height, bool flip, float scale[2]); +bool RenderFramebufferToPng(GLuint tex, bool flip, std::vector &png, int max_width = 0, int max_height = 0); void SaveScreenshot(GLuint tex, bool flip); +void ScaleDimensions(int src_width, int src_height, int max_width, int max_height, int *out_width, int *out_height); \ No newline at end of file diff --git a/ui/xui/main-menu.cc b/ui/xui/main-menu.cc index b011a4cb43..715b585cfd 100644 --- a/ui/xui/main-menu.cc +++ b/ui/xui/main-menu.cc @@ -727,7 +727,7 @@ void MainMenuSnapshotsView::SnapshotBigButton(QEMUSnapshotInfo *snapshot, const ImGui::PushFont(g_font_mgr.m_menu_font_medium); ImVec2 ts_title = ImGui::CalcTextSize(snapshot->name); - ImVec2 thumbnail_size = g_viewport_mgr.Scale(ImVec2(XEMU_SNAPSHOT_WIDTH, XEMU_SNAPSHOT_HEIGHT)); + ImVec2 thumbnail_size = g_viewport_mgr.Scale(ImVec2(XEMU_SNAPSHOT_THUMBNAIL_WIDTH, XEMU_SNAPSHOT_THUMBNAIL_HEIGHT)); ImVec2 thumbnail_pos(style.FramePadding.x, style.FramePadding.y); ImVec2 name_pos(thumbnail_pos.x + thumbnail_size.x + style.FramePadding.x * 2, thumbnail_pos.y); ImVec2 date_pos(name_pos.x, name_pos.y + ts_title.y + style.FramePadding.x); @@ -758,7 +758,27 @@ void MainMenuSnapshotsView::SnapshotBigButton(QEMUSnapshotInfo *snapshot, const ImGui::SetCursorPosX(pos.x + thumbnail_pos.x); ImGui::SetCursorPosY(pos.y + thumbnail_pos.y); if (screenshot > 0) { - ImGui::Image((ImTextureID)(uint64_t)screenshot, thumbnail_size); + int thumbnail_width, thumbnail_height; + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, screenshot); + glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &thumbnail_width); + glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &thumbnail_height); + + // Draw black background behind thumbnail + draw_list->AddRectFilled(ImVec2(p0.x + thumbnail_pos.x, p0.y + thumbnail_pos.y), + ImVec2(p0.x + thumbnail_pos.x + thumbnail_size.x, p0.y + thumbnail_pos.y + thumbnail_size.y), + IM_COL32_BLACK); + + // Center the thumbnail + int scaled_width, scaled_height; + ScaleDimensions(thumbnail_width, thumbnail_height, thumbnail_size.x, thumbnail_size.y, &scaled_width, &scaled_height); + ImVec2 img_pos = ImGui::GetCursorPos(); + img_pos.x += (thumbnail_size.x - scaled_width) / 2; + img_pos.y += (thumbnail_size.y - scaled_height) / 2; + ImGui::SetCursorPos(img_pos); + + ImGui::Image((ImTextureID)(uint64_t)screenshot, ImVec2(scaled_width, scaled_height)); } else { ImGui::Image((ImTextureID)(uint64_t)g_icon_tex, thumbnail_size); } From 60a7f55ac47583fe018c700d5183c47f50b6f95d Mon Sep 17 00:00:00 2001 From: Matt Borgerson Date: Sat, 3 Jun 2023 22:22:24 -0700 Subject: [PATCH 17/39] ui: Scale snapshot thumbnail placeholder properly --- ui/xui/gl-helpers.hh | 2 +- ui/xui/main-menu.cc | 45 ++++++++++++++++++++++---------------------- 2 files changed, 24 insertions(+), 23 deletions(-) diff --git a/ui/xui/gl-helpers.hh b/ui/xui/gl-helpers.hh index ce5c00b6a4..7401540816 100644 --- a/ui/xui/gl-helpers.hh +++ b/ui/xui/gl-helpers.hh @@ -51,4 +51,4 @@ void RenderFramebuffer(GLint tex, int width, int height, bool flip); void RenderFramebuffer(GLint tex, int width, int height, bool flip, float scale[2]); bool RenderFramebufferToPng(GLuint tex, bool flip, std::vector &png, int max_width = 0, int max_height = 0); void SaveScreenshot(GLuint tex, bool flip); -void ScaleDimensions(int src_width, int src_height, int max_width, int max_height, int *out_width, int *out_height); \ No newline at end of file +void ScaleDimensions(int src_width, int src_height, int max_width, int max_height, int *out_width, int *out_height); diff --git a/ui/xui/main-menu.cc b/ui/xui/main-menu.cc index 715b585cfd..bdef623c2f 100644 --- a/ui/xui/main-menu.cc +++ b/ui/xui/main-menu.cc @@ -757,32 +757,33 @@ void MainMenuSnapshotsView::SnapshotBigButton(QEMUSnapshotInfo *snapshot, const ImGui::SameLine(); ImGui::SetCursorPosX(pos.x + thumbnail_pos.x); ImGui::SetCursorPosY(pos.y + thumbnail_pos.y); - if (screenshot > 0) { - int thumbnail_width, thumbnail_height; - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, screenshot); - glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &thumbnail_width); - glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &thumbnail_height); + int thumbnail_width, thumbnail_height; - // Draw black background behind thumbnail - draw_list->AddRectFilled(ImVec2(p0.x + thumbnail_pos.x, p0.y + thumbnail_pos.y), - ImVec2(p0.x + thumbnail_pos.x + thumbnail_size.x, p0.y + thumbnail_pos.y + thumbnail_size.y), - IM_COL32_BLACK); - - // Center the thumbnail - int scaled_width, scaled_height; - ScaleDimensions(thumbnail_width, thumbnail_height, thumbnail_size.x, thumbnail_size.y, &scaled_width, &scaled_height); - ImVec2 img_pos = ImGui::GetCursorPos(); - img_pos.x += (thumbnail_size.x - scaled_width) / 2; - img_pos.y += (thumbnail_size.y - scaled_height) / 2; - ImGui::SetCursorPos(img_pos); - - ImGui::Image((ImTextureID)(uint64_t)screenshot, ImVec2(scaled_width, scaled_height)); - } else { - ImGui::Image((ImTextureID)(uint64_t)g_icon_tex, thumbnail_size); + if (!screenshot) { + screenshot = g_icon_tex; } + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, screenshot); + glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &thumbnail_width); + glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &thumbnail_height); + + // Draw black background behind thumbnail + draw_list->AddRectFilled(ImVec2(p0.x + thumbnail_pos.x, p0.y + thumbnail_pos.y), + ImVec2(p0.x + thumbnail_pos.x + thumbnail_size.x, p0.y + thumbnail_pos.y + thumbnail_size.y), + IM_COL32_BLACK); + + // Center the thumbnail + int scaled_width, scaled_height; + ScaleDimensions(thumbnail_width, thumbnail_height, thumbnail_size.x, thumbnail_size.y, &scaled_width, &scaled_height); + ImVec2 img_pos = ImGui::GetCursorPos(); + img_pos.x += (thumbnail_size.x - scaled_width) / 2; + img_pos.y += (thumbnail_size.y - scaled_height) / 2; + ImGui::SetCursorPos(img_pos); + + ImGui::Image((ImTextureID)(uint64_t)screenshot, ImVec2(scaled_width, scaled_height)); + draw_list->PushClipRect(p0, p1, true); // Snapshot title From f62bfc95e359b243cca002fdefb63114f9c2efaa Mon Sep 17 00:00:00 2001 From: Matt Borgerson Date: Sat, 3 Jun 2023 22:22:24 -0700 Subject: [PATCH 18/39] ui: Improve extra snapshot data storage - Store snapshot extra data in BE, per convention - Add a version field for back compat - Some minor refactoring and renaming for clarity --- ui/xemu-snapshots.c | 207 ++++++++++++++++++------------------------- ui/xemu-snapshots.h | 33 ++----- ui/xemu-thumbnail.cc | 23 +++-- ui/xui/main-menu.cc | 16 ++-- 4 files changed, 114 insertions(+), 165 deletions(-) diff --git a/ui/xemu-snapshots.c b/ui/xemu-snapshots.c index becb0dd859..ec719f99e9 100644 --- a/ui/xemu-snapshots.c +++ b/ui/xemu-snapshots.c @@ -49,88 +49,65 @@ const char **g_snapshot_shortcut_index_key_map[] = { &g_config.general.snapshots.shortcuts.f8, }; -static bool xemu_snapshots_load_thumbnail(BlockDriverState *bs_ro, - XemuSnapshotData *data, - int64_t *offset) -{ - int res = bdrv_load_vmstate(bs_ro, (uint8_t *)&data->thumbnail, *offset, - sizeof(TextureBuffer) - - sizeof(data->thumbnail.buffer)); - if (res != sizeof(TextureBuffer) - sizeof(data->thumbnail.buffer)) - return false; - *offset += res; - - data->thumbnail.buffer = g_malloc(data->thumbnail.size); - - res = bdrv_load_vmstate(bs_ro, (uint8_t *)data->thumbnail.buffer, *offset, - data->thumbnail.size); - if (res != data->thumbnail.size) { - return false; - } - *offset += res; - - return true; -} - static void xemu_snapshots_load_data(BlockDriverState *bs_ro, QEMUSnapshotInfo *info, XemuSnapshotData *data, Error **err) { - int res; - XemuSnapshotHeader header; + data->xbe_title_name = NULL; + data->gl_thumbnail = 0; + + int res = bdrv_snapshot_load_tmp(bs_ro, info->id_str, info->name, err); + if (res < 0) { + return; + } + + uint32_t header[3]; int64_t offset = 0; - - data->xbe_title_present = false; - data->thumbnail_present = false; - res = bdrv_snapshot_load_tmp(bs_ro, info->id_str, info->name, err); - if (res < 0) - return; - res = bdrv_load_vmstate(bs_ro, (uint8_t *)&header, offset, sizeof(header)); - if (res != sizeof(header)) - goto error; - offset += res; - - if (header.magic != XEMU_SNAPSHOT_DATA_MAGIC) - goto error; - - res = bdrv_load_vmstate(bs_ro, (uint8_t *)&data->xbe_title_len, offset, - sizeof(data->xbe_title_len)); - if (res != sizeof(data->xbe_title_len)) - goto error; - offset += res; - - data->xbe_title = (char *)g_malloc(data->xbe_title_len); - - res = bdrv_load_vmstate(bs_ro, (uint8_t *)data->xbe_title, offset, - data->xbe_title_len); - if (res != data->xbe_title_len) - goto error; - offset += res; - - data->xbe_title_present = (offset <= sizeof(header) + header.size); - - if (offset == sizeof(header) + header.size) + if (res != sizeof(header)) { return; + } + offset += res; - if (!xemu_snapshots_load_thumbnail(bs_ro, data, &offset)) { - goto error; + if (be32_to_cpu(header[0]) != XEMU_SNAPSHOT_DATA_MAGIC || + be32_to_cpu(header[1]) != XEMU_SNAPSHOT_DATA_VERSION) { + return; } - data->thumbnail_present = (offset <= sizeof(header) + header.size); - - if (data->thumbnail_present) { - glGenTextures(1, &data->gl_thumbnail); - xemu_snapshots_render_thumbnail(data->gl_thumbnail, &data->thumbnail); + size_t size = be32_to_cpu(header[2]); + uint8_t *buf = g_malloc(size); + res = bdrv_load_vmstate(bs_ro, buf, offset, size); + if (res != size) { + g_free(buf); + return; } - return; -error: - g_free(data->xbe_title); - data->xbe_title_present = false; + const size_t xbe_title_name_size = buf[0]; + offset = 1; - g_free(data->thumbnail.buffer); - data->thumbnail_present = false; + if (xbe_title_name_size) { + data->xbe_title_name = (char *)g_malloc(xbe_title_name_size + 1); + memcpy(data->xbe_title_name, &buf[offset], xbe_title_name_size); + data->xbe_title_name[xbe_title_name_size] = 0; + offset += xbe_title_name_size; + } + + const size_t thumbnail_size = be32_to_cpu(*(uint32_t *)&buf[offset]); + offset += 4; + + if (thumbnail_size) { + GLuint thumbnail; + glGenTextures(1, &thumbnail); + if (xemu_snapshots_load_png_to_texture(thumbnail, &buf[offset], + thumbnail_size)) { + data->gl_thumbnail = thumbnail; + } else { + glDeleteTextures(1, &thumbnail); + } + offset += thumbnail_size; + } + + g_free(buf); } static void xemu_snapshots_all_load_data(QEMUSnapshotInfo **info, @@ -144,12 +121,8 @@ static void xemu_snapshots_all_load_data(QEMUSnapshotInfo **info, if (*data) { for (int i = 0; i < xemu_snapshots_len; ++i) { - if ((*data)[i].xbe_title_present) { - g_free((*data)[i].xbe_title); - } - - if ((*data)[i].thumbnail_present) { - g_free((*data)[i].thumbnail.buffer); + g_free((*data)[i].xbe_title_name); + if ((*data)[i].gl_thumbnail) { glDeleteTextures(1, &((*data)[i].gl_thumbnail)); } } @@ -250,65 +223,61 @@ void xemu_snapshots_delete(const char *vm_name, Error **err) void xemu_snapshots_save_extra_data(QEMUFile *f) { + size_t xbe_title_name_size = 0; + char *xbe_title_name = NULL; struct xbe *xbe_data = xemu_get_xbe_info(); - - int64_t xbe_title_len = 0; - char *xbe_title = g_utf16_to_utf8(xbe_data->cert->m_title_name, 40, NULL, - &xbe_title_len, NULL); - xbe_title_len++; - - XemuSnapshotHeader header = { XEMU_SNAPSHOT_DATA_MAGIC, 0 }; - - header.size += sizeof(xbe_title_len); - header.size += xbe_title_len; - - TextureBuffer *thumbnail = xemu_snapshots_extract_thumbnail(); - if (thumbnail && thumbnail->buffer) { - header.size += sizeof(TextureBuffer) - sizeof(thumbnail->buffer); - header.size += thumbnail->size; + if (xbe_data && xbe_data->cert) { + glong items_written = 0; + xbe_title_name = g_utf16_to_utf8(xbe_data->cert->m_title_name, 40, NULL, &items_written, NULL); + if (xbe_title_name) { + xbe_title_name_size = items_written; + } } - qemu_put_buffer(f, (const uint8_t *)&header, sizeof(header)); - qemu_put_buffer(f, (const uint8_t *)&xbe_title_len, sizeof(xbe_title_len)); - qemu_put_buffer(f, (const uint8_t *)xbe_title, xbe_title_len); + size_t thumbnail_size = 0; + void *thumbnail_buf = xemu_snapshots_create_framebuffer_thumbnail_png(&thumbnail_size); - if (thumbnail && thumbnail->buffer) { - qemu_put_buffer(f, (const uint8_t *)thumbnail, - sizeof(TextureBuffer) - sizeof(thumbnail->buffer)); - qemu_put_buffer(f, (const uint8_t *)thumbnail->buffer, thumbnail->size); + qemu_put_be32(f, XEMU_SNAPSHOT_DATA_MAGIC); + qemu_put_be32(f, XEMU_SNAPSHOT_DATA_VERSION); + qemu_put_be32(f, 1 + xbe_title_name_size + 4 + thumbnail_size); + + qemu_put_byte(f, xbe_title_name_size); + if (xbe_title_name_size) { + qemu_put_buffer(f, (const uint8_t *)xbe_title_name, xbe_title_name_size); + g_free(xbe_title_name); } - g_free(xbe_title); - - if (thumbnail && thumbnail->buffer) { - g_free(thumbnail->buffer); + qemu_put_be32(f, thumbnail_size); + if (thumbnail_size) { + qemu_put_buffer(f, (const uint8_t *)thumbnail_buf, thumbnail_size); + g_free(thumbnail_buf); } - g_free(thumbnail); - xemu_snapshots_dirty = true; } bool xemu_snapshots_offset_extra_data(QEMUFile *f) { - size_t ret; - XemuSnapshotHeader header; - ret = qemu_get_buffer(f, (uint8_t *)&header, sizeof(header)); - if (ret != sizeof(header)) { - return false; + unsigned int v; + uint32_t version; + uint32_t size; + + v = qemu_get_be32(f); + if (v != XEMU_SNAPSHOT_DATA_MAGIC) { + qemu_file_skip(f, -4); + return true; } - if (header.magic == XEMU_SNAPSHOT_DATA_MAGIC) { - /* - * qemu_file_skip only works if you aren't skipping past its buffer. - * Unfortunately, it's not usable here. - */ - void *buf = g_malloc(header.size); - qemu_get_buffer(f, buf, header.size); - g_free(buf); - } else { - qemu_file_skip(f, -((int)sizeof(header))); - } + version = qemu_get_be32(f); + (void)version; + + /* qemu_file_skip only works if you aren't skipping past internal buffer limit. + * Unfortunately, it's not usable here. + */ + size = qemu_get_be32(f); + void *buf = g_malloc(size); + qemu_get_buffer(f, buf, size); + g_free(buf); return true; } diff --git a/ui/xemu-snapshots.h b/ui/xemu-snapshots.h index 792b9d3c4f..9a33e687b4 100644 --- a/ui/xemu-snapshots.h +++ b/ui/xemu-snapshots.h @@ -26,33 +26,19 @@ extern "C" { #include "qemu/osdep.h" #include "block/snapshot.h" +#include + +#define XEMU_SNAPSHOT_DATA_MAGIC 0x78656d75 // 'xemu' +#define XEMU_SNAPSHOT_DATA_VERSION 1 -#define XEMU_SNAPSHOT_DATA_MAGIC 0x78656d75 #define XEMU_SNAPSHOT_THUMBNAIL_WIDTH 160 #define XEMU_SNAPSHOT_THUMBNAIL_HEIGHT 120 extern const char **g_snapshot_shortcut_index_key_map[]; -#pragma pack(1) -typedef struct TextureBuffer { - int channels; - unsigned long size; - void *buffer; -} TextureBuffer; -#pragma pack() - -typedef struct XemuSnapshotHeader { - uint32_t magic; - uint32_t size; -} XemuSnapshotHeader; - typedef struct XemuSnapshotData { - int64_t xbe_title_len; - char *xbe_title; - bool xbe_title_present; - TextureBuffer thumbnail; - bool thumbnail_present; - unsigned int gl_thumbnail; + char *xbe_title_name; + GLuint gl_thumbnail; } XemuSnapshotData; // Implemented in xemu-snapshots.c @@ -67,10 +53,9 @@ bool xemu_snapshots_offset_extra_data(QEMUFile *f); void xemu_snapshots_mark_dirty(void); // Implemented in xemu-thumbnail.cc -void xemu_snapshots_set_framebuffer_texture(unsigned int tex, bool flip); -void xemu_snapshots_render_thumbnail(unsigned int tex, - TextureBuffer *thumbnail); -TextureBuffer *xemu_snapshots_extract_thumbnail(void); +void xemu_snapshots_set_framebuffer_texture(GLuint tex, bool flip); +bool xemu_snapshots_load_png_to_texture(GLuint tex, void *buf, size_t size); +void *xemu_snapshots_create_framebuffer_thumbnail_png(size_t *size); #ifdef __cplusplus } diff --git a/ui/xemu-thumbnail.cc b/ui/xemu-thumbnail.cc index 86df3ef2b4..592408393c 100644 --- a/ui/xemu-thumbnail.cc +++ b/ui/xemu-thumbnail.cc @@ -33,14 +33,13 @@ void xemu_snapshots_set_framebuffer_texture(GLuint tex, bool flip) display_flip = flip; } -void xemu_snapshots_render_thumbnail(GLuint tex, TextureBuffer *thumbnail) +bool xemu_snapshots_load_png_to_texture(GLuint tex, void *buf, size_t size) { std::vector pixels; unsigned int width, height, channels; - if (fpng::fpng_decode_memory( - thumbnail->buffer, thumbnail->size, pixels, width, height, channels, - thumbnail->channels) != fpng::FPNG_DECODE_SUCCESS) { - return; + if (fpng::fpng_decode_memory(buf, size, pixels, width, height, channels, + 3) != fpng::FPNG_DECODE_SUCCESS) { + return false; } glActiveTexture(GL_TEXTURE0); @@ -52,9 +51,11 @@ void xemu_snapshots_render_thumbnail(GLuint tex, TextureBuffer *thumbnail) glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, pixels.data()); + + return true; } -TextureBuffer *xemu_snapshots_extract_thumbnail() +void *xemu_snapshots_create_framebuffer_thumbnail_png(size_t *size) { /* * Avoids crashing if a snapshot is made on a thread with no GL context @@ -74,10 +75,8 @@ TextureBuffer *xemu_snapshots_extract_thumbnail() return NULL; } - TextureBuffer *thumbnail = (TextureBuffer *)g_malloc(sizeof(TextureBuffer)); - thumbnail->buffer = g_malloc(png.size() * sizeof(uint8_t)); - thumbnail->channels = 3; - thumbnail->size = png.size() * sizeof(uint8_t); - memcpy(thumbnail->buffer, png.data(), thumbnail->size); - return thumbnail; + void *buf = g_malloc(png.size()); + memcpy(buf, png.data(), png.size()); + *size = png.size(); + return buf; } diff --git a/ui/xui/main-menu.cc b/ui/xui/main-menu.cc index bdef623c2f..4d174102c7 100644 --- a/ui/xui/main-menu.cc +++ b/ui/xui/main-menu.cc @@ -955,8 +955,8 @@ void MainMenuSnapshotsView::Draw() bool search_buf_equal = false; for (int i = m_snapshots_len - 1; i >= 0; i--) { - if (g_config.general.snapshots.filter_current_game && m_extra_data[i].xbe_title_present && - (strcmp(m_current_title_name, m_extra_data[i].xbe_title) != 0)) { + if (g_config.general.snapshots.filter_current_game && m_extra_data[i].xbe_title_name && + (strcmp(m_current_title_name, m_extra_data[i].xbe_title_name) != 0)) { continue; } @@ -968,8 +968,8 @@ void MainMenuSnapshotsView::Draw() keep_entry |= g_match_info_matches(match); g_match_info_free(match); - if (m_extra_data[i].xbe_title_present) { - g_regex_match(m_search_regex, m_extra_data[i].xbe_title, (GRegexMatchFlags)0, &match); + if (m_extra_data[i].xbe_title_name) { + g_regex_match(m_search_regex, m_extra_data[i].xbe_title_name, (GRegexMatchFlags)0, &match); keep_entry |= g_match_info_matches(match); g_free(match); } @@ -982,14 +982,10 @@ void MainMenuSnapshotsView::Draw() search_buf_equal |= g_strcmp0(m_search_buf.c_str(), m_snapshots[i].name) == 0; ImGui::PushID(i); - GLuint thumbnail = 0; - if (m_extra_data[i].thumbnail_present) { - thumbnail = m_extra_data[i].gl_thumbnail; - } SnapshotBigButton( m_snapshots + i, - m_extra_data[i].xbe_title_present ? m_extra_data[i].xbe_title : "Unknown", - thumbnail + m_extra_data[i].xbe_title_name ? m_extra_data[i].xbe_title_name : "Unknown", + m_extra_data[i].gl_thumbnail ); ImGui::PopID(); } From d7cbda508111f83c38bf10d8999d9e599e05aae9 Mon Sep 17 00:00:00 2001 From: Matt Borgerson Date: Sat, 3 Jun 2023 22:22:24 -0700 Subject: [PATCH 19/39] ui: Allow clicking snapshot Create button without name --- ui/xui/main-menu.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/xui/main-menu.cc b/ui/xui/main-menu.cc index 4d174102c7..a255cd7dc1 100644 --- a/ui/xui/main-menu.cc +++ b/ui/xui/main-menu.cc @@ -944,8 +944,8 @@ void MainMenuSnapshotsView::Draw() } ImGui::SameLine(); - if (ImGui::Button(snapshot_with_create_name_exists ? "Save" : "Create") && !m_create_buf.empty()) { - xemu_snapshots_save(m_create_buf.c_str(), NULL); + if (ImGui::Button(snapshot_with_create_name_exists ? "Save" : "Create")) { + xemu_snapshots_save(m_create_buf.empty() ? NULL : m_create_buf.c_str(), NULL); m_create_buf.clear(); } From d147280cd1db8a8665e293328a23174a8fa1f809 Mon Sep 17 00:00:00 2001 From: Matt Borgerson Date: Sat, 3 Jun 2023 22:22:24 -0700 Subject: [PATCH 20/39] ui: Drop extra snapshot create button --- ui/xui/main-menu.cc | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/ui/xui/main-menu.cc b/ui/xui/main-menu.cc index a255cd7dc1..dbd524b5e0 100644 --- a/ui/xui/main-menu.cc +++ b/ui/xui/main-menu.cc @@ -953,7 +953,6 @@ void MainMenuSnapshotsView::Draw() ImGui::SetTooltip("A snapshot with the name \"%s\" already exists. This button will overwrite the existing snapshot.", m_create_buf.c_str()); } - bool search_buf_equal = false; for (int i = m_snapshots_len - 1; i >= 0; i--) { if (g_config.general.snapshots.filter_current_game && m_extra_data[i].xbe_title_name && (strcmp(m_current_title_name, m_extra_data[i].xbe_title_name) != 0)) { @@ -979,8 +978,6 @@ void MainMenuSnapshotsView::Draw() } } - search_buf_equal |= g_strcmp0(m_search_buf.c_str(), m_snapshots[i].name) == 0; - ImGui::PushID(i); SnapshotBigButton( m_snapshots + i, @@ -989,24 +986,6 @@ void MainMenuSnapshotsView::Draw() ); ImGui::PopID(); } - - /* Snapshot names are unique, don't give option to create new one if it exists already */ - if (!search_buf_equal && !m_search_buf.empty()) { - char *new_snapshot = g_strdup_printf("Create Snapshot '%s'", m_search_buf.c_str()); - ImGui::PushFont(g_font_mgr.m_menu_font_small); - ImVec2 new_snapshot_size = ImGui::CalcTextSize(new_snapshot); - ImGui::SetCursorPosX((ImGui::GetContentRegionAvail().x - new_snapshot_size.x)/2); - if (ImGui::Button(new_snapshot)) { - Error *err = NULL; - xemu_snapshots_save(m_search_buf.c_str(), &err); - if (err) { - xemu_queue_error_message(error_get_pretty(err)); - error_free(err); - } - } - ImGui::PopFont(); - g_free(new_snapshot); - } } MainMenuSystemView::MainMenuSystemView() : m_dirty(false) From 4104b3d540e3c4a266109ccd46014d802d035742 Mon Sep 17 00:00:00 2001 From: Matt Borgerson Date: Sat, 3 Jun 2023 22:22:24 -0700 Subject: [PATCH 21/39] ui: Change snapshot 'Save' button to 'Replace' for clarity --- ui/xui/main-menu.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/xui/main-menu.cc b/ui/xui/main-menu.cc index dbd524b5e0..6ee37a1a39 100644 --- a/ui/xui/main-menu.cc +++ b/ui/xui/main-menu.cc @@ -944,7 +944,7 @@ void MainMenuSnapshotsView::Draw() } ImGui::SameLine(); - if (ImGui::Button(snapshot_with_create_name_exists ? "Save" : "Create")) { + if (ImGui::Button(snapshot_with_create_name_exists ? "Replace" : "Create")) { xemu_snapshots_save(m_create_buf.empty() ? NULL : m_create_buf.c_str(), NULL); m_create_buf.clear(); } From 458de1758aa94953140c5acb387dbb87b4ee539f Mon Sep 17 00:00:00 2001 From: Matt Borgerson Date: Sat, 3 Jun 2023 22:22:24 -0700 Subject: [PATCH 22/39] ui: Unify snapshot filter and title name search box --- ui/xui/main-menu.cc | 12 +++++------- ui/xui/main-menu.hh | 1 - 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/ui/xui/main-menu.cc b/ui/xui/main-menu.cc index 6ee37a1a39..238649d3e4 100644 --- a/ui/xui/main-menu.cc +++ b/ui/xui/main-menu.cc @@ -930,14 +930,12 @@ void MainMenuSnapshotsView::Draw() Load(); SectionTitle("Snapshots"); ImGui::Checkbox("Filter by current title", &g_config.general.snapshots.filter_current_game); - ImGui::InputTextWithHint("##search", "Filter by name...", &m_search_buf, ImGuiInputTextFlags_CallbackEdit, + ImGui::InputTextWithHint("##search", "Search...", &m_search_buf, ImGuiInputTextFlags_CallbackEdit, &MainMenuSnapshotsViewUpdateSearchBox, this); - ImGui::InputTextWithHint("##create", "Create new snapshot", &m_create_buf); - bool snapshot_with_create_name_exists = false; for (int i = 0; i < m_snapshots_len; ++i) { - if (g_strcmp0(m_create_buf.c_str(), m_snapshots[i].name) == 0) { + if (g_strcmp0(m_search_buf.c_str(), m_snapshots[i].name) == 0) { snapshot_with_create_name_exists = true; break; } @@ -945,12 +943,12 @@ void MainMenuSnapshotsView::Draw() ImGui::SameLine(); if (ImGui::Button(snapshot_with_create_name_exists ? "Replace" : "Create")) { - xemu_snapshots_save(m_create_buf.empty() ? NULL : m_create_buf.c_str(), NULL); - m_create_buf.clear(); + xemu_snapshots_save(m_search_buf.empty() ? NULL : m_search_buf.c_str(), NULL); + m_search_buf.clear(); } if (snapshot_with_create_name_exists && ImGui::IsItemHovered()) { - ImGui::SetTooltip("A snapshot with the name \"%s\" already exists. This button will overwrite the existing snapshot.", m_create_buf.c_str()); + ImGui::SetTooltip("A snapshot with the name \"%s\" already exists. This button will overwrite the existing snapshot.", m_search_buf.c_str()); } for (int i = m_snapshots_len - 1; i >= 0; i--) { diff --git a/ui/xui/main-menu.hh b/ui/xui/main-menu.hh index 2b469c1112..df5e85973f 100644 --- a/ui/xui/main-menu.hh +++ b/ui/xui/main-menu.hh @@ -110,7 +110,6 @@ protected: uint32_t m_current_title_id; char *m_current_title_name; std::string m_search_buf; - std::string m_create_buf; bool m_load_failed; private: From 86cdc0a9abb4415c68140f2771aaf990e9eb0483 Mon Sep 17 00:00:00 2001 From: Matt Borgerson Date: Sat, 3 Jun 2023 22:22:24 -0700 Subject: [PATCH 23/39] ui: Use stylized toggle for snapshot filter by title --- ui/xui/main-menu.cc | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ui/xui/main-menu.cc b/ui/xui/main-menu.cc index 238649d3e4..4c8f56233b 100644 --- a/ui/xui/main-menu.cc +++ b/ui/xui/main-menu.cc @@ -929,7 +929,9 @@ void MainMenuSnapshotsView::Draw() { Load(); SectionTitle("Snapshots"); - ImGui::Checkbox("Filter by current title", &g_config.general.snapshots.filter_current_game); + + Toggle("Filter by current title", &g_config.general.snapshots.filter_current_game); + ImGui::InputTextWithHint("##search", "Search...", &m_search_buf, ImGuiInputTextFlags_CallbackEdit, &MainMenuSnapshotsViewUpdateSearchBox, this); @@ -951,6 +953,7 @@ void MainMenuSnapshotsView::Draw() ImGui::SetTooltip("A snapshot with the name \"%s\" already exists. This button will overwrite the existing snapshot.", m_search_buf.c_str()); } + for (int i = m_snapshots_len - 1; i >= 0; i--) { if (g_config.general.snapshots.filter_current_game && m_extra_data[i].xbe_title_name && (strcmp(m_current_title_name, m_extra_data[i].xbe_title_name) != 0)) { From a96d0c5f795cfb52eff86fc981aa98122145d27b Mon Sep 17 00:00:00 2001 From: Matt Borgerson Date: Sat, 3 Jun 2023 22:22:24 -0700 Subject: [PATCH 24/39] ui: Stretch snapshot search box --- ui/xui/main-menu.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ui/xui/main-menu.cc b/ui/xui/main-menu.cc index 4c8f56233b..1c8d990c5d 100644 --- a/ui/xui/main-menu.cc +++ b/ui/xui/main-menu.cc @@ -932,6 +932,7 @@ void MainMenuSnapshotsView::Draw() Toggle("Filter by current title", &g_config.general.snapshots.filter_current_game); + ImGui::SetNextItemWidth(ImGui::GetColumnWidth() * 0.8); ImGui::InputTextWithHint("##search", "Search...", &m_search_buf, ImGuiInputTextFlags_CallbackEdit, &MainMenuSnapshotsViewUpdateSearchBox, this); @@ -944,7 +945,7 @@ void MainMenuSnapshotsView::Draw() } ImGui::SameLine(); - if (ImGui::Button(snapshot_with_create_name_exists ? "Replace" : "Create")) { + if (ImGui::Button(snapshot_with_create_name_exists ? "Replace" : "Create", ImVec2(-FLT_MIN, 0))) { xemu_snapshots_save(m_search_buf.empty() ? NULL : m_search_buf.c_str(), NULL); m_search_buf.clear(); } From 2cc2df0be726b78725be39ae0ac2f9b70b538c37 Mon Sep 17 00:00:00 2001 From: Matt Borgerson Date: Sat, 3 Jun 2023 22:22:24 -0700 Subject: [PATCH 25/39] ui: Move all snapshot actions into a context menu --- ui/xui/main-menu.cc | 194 +++++++++++++++++--------------------------- 1 file changed, 73 insertions(+), 121 deletions(-) diff --git a/ui/xui/main-menu.cc b/ui/xui/main-menu.cc index 1c8d990c5d..f4bcc86020 100644 --- a/ui/xui/main-menu.cc +++ b/ui/xui/main-menu.cc @@ -685,7 +685,7 @@ MainMenuSnapshotsView::~MainMenuSnapshotsView() g_free(m_search_regex); } -void MainMenuSnapshotsView::SnapshotBigButton(QEMUSnapshotInfo *snapshot, const char *title_name, GLuint screenshot) +void MainMenuSnapshotsView::SnapshotBigButton(QEMUSnapshotInfo *snapshot, const char *title_name, GLuint thumbnail) { Error *err = NULL; int current_snapshot_binding = -1; @@ -700,24 +700,6 @@ void MainMenuSnapshotsView::SnapshotBigButton(QEMUSnapshotInfo *snapshot, const ImVec2 pos = ImGui::GetCursorPos(); ImDrawList *draw_list = ImGui::GetWindowDrawList(); - ImGui::PushFont(g_font_mgr.m_menu_font); - const char *close_icon = ICON_FA_CIRCLE_XMARK; - ImVec2 ts_close_icon = ImGui::CalcTextSize(close_icon); - ts_close_icon.x += 2*style.FramePadding.x; - ImGui::PopFont(); - - ImGui::PushFont(g_font_mgr.m_menu_font); - const char *save_icon = ICON_FA_FLOPPY_DISK; - ImVec2 ts_save_icon = ImGui::CalcTextSize(save_icon); - ts_save_icon.x += 2*style.FramePadding.x; - ImGui::PopFont(); - - ImGui::PushFont(g_font_mgr.m_menu_font); - const char *binding_icon = ICON_FA_KEYBOARD; - ImVec2 ts_binding_icon = ImGui::CalcTextSize(binding_icon); - ts_binding_icon.x += 2*style.FramePadding.x; - ImGui::PopFont(); - ImGui::PushFont(g_font_mgr.m_menu_font_small); ImVec2 ts_sub = ImGui::CalcTextSize(snapshot->name); ImGui::PopFont(); @@ -730,20 +712,11 @@ void MainMenuSnapshotsView::SnapshotBigButton(QEMUSnapshotInfo *snapshot, const ImVec2 thumbnail_size = g_viewport_mgr.Scale(ImVec2(XEMU_SNAPSHOT_THUMBNAIL_WIDTH, XEMU_SNAPSHOT_THUMBNAIL_HEIGHT)); ImVec2 thumbnail_pos(style.FramePadding.x, style.FramePadding.y); ImVec2 name_pos(thumbnail_pos.x + thumbnail_size.x + style.FramePadding.x * 2, thumbnail_pos.y); - ImVec2 date_pos(name_pos.x, name_pos.y + ts_title.y + style.FramePadding.x); - ImVec2 title_pos(name_pos.x, date_pos.y + ts_title.y + style.FramePadding.x); - ImVec2 binding_pos(name_pos.x, title_pos.y + ts_title.y + style.FramePadding.x); - - bool load = ImGui::Button("###button", ImVec2(ImGui::GetContentRegionAvail().x, fmax(thumbnail_size.y + style.FramePadding.y * 2, - ts_title.y + ts_sub.y + style.FramePadding.y * 3))); - ImGui::PopFont(); - const ImVec2 sz = ImGui::GetItemRectSize(); - const ImVec2 p0 = ImGui::GetItemRectMin(); - const ImVec2 p1 = ImGui::GetItemRectMax(); - ts_close_icon.y = sz.y / 1; - ts_save_icon.y = sz.y / 1; - ts_binding_icon.y = sz.y / 1; + ImVec2 title_pos(name_pos.x, name_pos.y + ts_title.y + style.FramePadding.x); + ImVec2 date_pos(name_pos.x, title_pos.y + ts_title.y + style.FramePadding.x); + ImVec2 binding_pos(name_pos.x, date_pos.y + ts_title.y + style.FramePadding.x); + bool load = ImGui::Button("###button", ImVec2(-FLT_MIN, fmax(thumbnail_size.y + style.FramePadding.y * 2, ts_title.y + ts_sub.y + style.FramePadding.y * 3))); if (load) { xemu_snapshots_load(snapshot->name, &err); if (err) { @@ -751,21 +724,29 @@ void MainMenuSnapshotsView::SnapshotBigButton(QEMUSnapshotInfo *snapshot, const error_free(err); } } + bool options_key_pressed = ImGui::IsKeyPressed(ImGuiKey_GamepadFaceLeft); + bool show_snapshot_context_menu = ImGui::IsItemHovered() && + (ImGui::IsMouseReleased(ImGuiMouseButton_Right) || + options_key_pressed); + + ImVec2 next_pos = ImGui::GetCursorPos(); + const ImVec2 p0 = ImGui::GetItemRectMin(); + const ImVec2 p1 = ImGui::GetItemRectMax(); + ImGui::PopFont(); + + draw_list->PushClipRect(p0, p1, true); // Snapshot thumbnail ImGui::SetItemAllowOverlap(); - ImGui::SameLine(); - ImGui::SetCursorPosX(pos.x + thumbnail_pos.x); - ImGui::SetCursorPosY(pos.y + thumbnail_pos.y); + ImGui::SetCursorPos(ImVec2(pos.x + thumbnail_pos.x, pos.y + thumbnail_pos.y)); - int thumbnail_width, thumbnail_height; - - if (!screenshot) { - screenshot = g_icon_tex; + if (!thumbnail) { + thumbnail = g_icon_tex; } glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, screenshot); + glBindTexture(GL_TEXTURE_2D, thumbnail); + int thumbnail_width, thumbnail_height; glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &thumbnail_width); glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &thumbnail_height); @@ -774,132 +755,104 @@ void MainMenuSnapshotsView::SnapshotBigButton(QEMUSnapshotInfo *snapshot, const ImVec2(p0.x + thumbnail_pos.x + thumbnail_size.x, p0.y + thumbnail_pos.y + thumbnail_size.y), IM_COL32_BLACK); - // Center the thumbnail + // Draw centered thumbnail int scaled_width, scaled_height; ScaleDimensions(thumbnail_width, thumbnail_height, thumbnail_size.x, thumbnail_size.y, &scaled_width, &scaled_height); ImVec2 img_pos = ImGui::GetCursorPos(); img_pos.x += (thumbnail_size.x - scaled_width) / 2; img_pos.y += (thumbnail_size.y - scaled_height) / 2; ImGui::SetCursorPos(img_pos); - - ImGui::Image((ImTextureID)(uint64_t)screenshot, ImVec2(scaled_width, scaled_height)); - - draw_list->PushClipRect(p0, p1, true); + ImGui::Image((ImTextureID)(uint64_t)thumbnail, ImVec2(scaled_width, scaled_height)); // Snapshot title ImGui::PushFont(g_font_mgr.m_menu_font_medium); draw_list->AddText(ImVec2(p0.x + name_pos.x, p0.y + name_pos.y), IM_COL32(255, 255, 255, 255), snapshot->name); ImGui::PopFont(); + // Snapshot XBE title name + ImGui::PushFont(g_font_mgr.m_menu_font_small); + draw_list->AddText(ImVec2(p0.x + title_pos.x, p0.y + title_pos.y), IM_COL32(255, 255, 255, 200), title_name); + // Snapshot date g_autoptr(GDateTime) date = g_date_time_new_from_unix_local(snapshot->date_sec); char *date_buf = g_date_time_format(date, "%Y-%m-%d %H:%M:%S"); - ImGui::PushFont(g_font_mgr.m_menu_font_small); draw_list->AddText(ImVec2(p0.x + date_pos.x, p0.y + date_pos.y), IM_COL32(255, 255, 255, 200), date_buf); - ImGui::PopFont(); g_free(date_buf); - // Snapshot title - ImGui::PushFont(g_font_mgr.m_menu_font_small); - draw_list->AddText(ImVec2(p0.x + title_pos.x, p0.y + title_pos.y), IM_COL32(255, 255, 255, 200), title_name); - ImGui::PopFont(); - - // Snapshot binding - ImGui::PushFont(g_font_mgr.m_menu_font_small); + // Snapshot keyboard binding if (current_snapshot_binding != -1) { char *binding_text = g_strdup_printf("Bound to F%d", current_snapshot_binding + 5); draw_list->AddText(ImVec2(p0.x + binding_pos.x, p0.y + binding_pos.y), IM_COL32(255, 255, 255, 200), binding_text); g_free(binding_text); - } else { - ImGui::Text("Not Bound"); - draw_list->AddText(ImVec2(p0.x + binding_pos.x, p0.y + binding_pos.y), IM_COL32(255, 255, 255, 200), "Not Bound"); } - - ImGui::PopFont(); + ImGui::PopFont(); draw_list->PopClipRect(); - // Delete button - ImGui::SameLine(); - ImGui::SetCursorPosY(pos.y); - ImGui::SetCursorPosX(pos.x + sz.x - ts_close_icon.x); - ImGui::PushFont(g_font_mgr.m_menu_font); - ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0, 0)); - ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32_BLACK_TRANS); - if (ImGui::Button(close_icon, ts_close_icon)) { - xemu_snapshots_delete(snapshot->name, &err); - if (err) { - xemu_queue_error_message(error_get_pretty(err)); - error_free(err); + if (show_snapshot_context_menu) { + if (options_key_pressed) { + ImGui::SetNextWindowPos(p0); } + ImGui::OpenPopup("Snapshot Options"); } - ImGui::PopStyleColor(); - ImGui::PopStyleVar(1); - ImGui::PopFont(); - // Save button - ImGui::SameLine(); - ImGui::SetCursorPosY(pos.y); - ImGui::SetCursorPosX(pos.x + sz.x - ts_save_icon.x - ts_close_icon.x); - ImGui::PushFont(g_font_mgr.m_menu_font); - ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0, 0)); - ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32_BLACK_TRANS); - if (ImGui::Button(save_icon, ts_save_icon)) { - xemu_snapshots_save(snapshot->name, &err); - if (err) { - xemu_queue_error_message(error_get_pretty(err)); - error_free(err); + if (ImGui::BeginPopupContextItem("Snapshot Options")) { + if (ImGui::MenuItem("Load")) { + xemu_snapshots_load(snapshot->name, &err); + if (err) { + xemu_queue_error_message(error_get_pretty(err)); + error_free(err); + } } - } - ImGui::PopStyleColor(); - ImGui::PopStyleVar(1); - ImGui::PopFont(); - // Bind button - ImGui::SameLine(); - ImGui::SetCursorPosY(pos.y); - ImGui::SetCursorPosX(pos.x + sz.x - ts_binding_icon.x - ts_save_icon.x - ts_close_icon.x); - ImGui::PushFont(g_font_mgr.m_menu_font); - ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0, 0)); - ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32_BLACK_TRANS); - if (ImGui::Button(binding_icon, ts_binding_icon)) { - ImGui::OpenPopup("Bind Snapshot Key"); - } + if (ImGui::BeginMenu("Keybinding")) { + for (int i = 0; i < 4; ++i) { + char *item_name = g_strdup_printf("Bind to F%d", i + 5); - if (ImGui::BeginPopupContextItem("Bind Snapshot Key")) { - for (int i = 0; i < 4; ++i) { - char *item_name = g_strdup_printf("Bind to F%d", i + 5); + if (ImGui::MenuItem(item_name)) { + if (current_snapshot_binding >= 0) { + xemu_settings_set_string(g_snapshot_shortcut_index_key_map[current_snapshot_binding], ""); + } + xemu_settings_set_string(g_snapshot_shortcut_index_key_map[i], snapshot->name); + current_snapshot_binding = i; - if (ImGui::MenuItem(item_name)) { - if (current_snapshot_binding >= 0) { - xemu_settings_set_string(g_snapshot_shortcut_index_key_map[current_snapshot_binding], ""); + ImGui::CloseCurrentPopup(); } - xemu_settings_set_string(g_snapshot_shortcut_index_key_map[i], snapshot->name); - current_snapshot_binding = i; - ImGui::CloseCurrentPopup(); + g_free(item_name); } - g_free(item_name); + if (current_snapshot_binding >= 0) { + if (ImGui::MenuItem("Unbind")) { + xemu_settings_set_string(g_snapshot_shortcut_index_key_map[current_snapshot_binding], ""); + current_snapshot_binding = -1; + } + } + ImGui::EndMenu(); } + + ImGui::Separator(); - if (current_snapshot_binding >= 0) { - ImGui::Separator(); - if (ImGui::MenuItem("Unbind")) { - xemu_settings_set_string(g_snapshot_shortcut_index_key_map[current_snapshot_binding], ""); - current_snapshot_binding = -1; + if (ImGui::MenuItem("Replace")) { + xemu_snapshots_save(snapshot->name, &err); + if (err) { + xemu_queue_error_message(error_get_pretty(err)); + error_free(err); + } + } + if (ImGui::MenuItem("Delete")) { + xemu_snapshots_delete(snapshot->name, &err); + if (err) { + xemu_queue_error_message(error_get_pretty(err)); + error_free(err); } } ImGui::EndPopup(); } - ImGui::PopStyleColor(); - ImGui::PopStyleVar(1); - ImGui::PopFont(); - - ImGui::PopStyleVar(2); + ImGui::SetCursorPos(next_pos); } static int MainMenuSnapshotsViewUpdateSearchBox(ImGuiInputTextCallbackData *data) @@ -954,7 +907,6 @@ void MainMenuSnapshotsView::Draw() ImGui::SetTooltip("A snapshot with the name \"%s\" already exists. This button will overwrite the existing snapshot.", m_search_buf.c_str()); } - for (int i = m_snapshots_len - 1; i >= 0; i--) { if (g_config.general.snapshots.filter_current_game && m_extra_data[i].xbe_title_name && (strcmp(m_current_title_name, m_extra_data[i].xbe_title_name) != 0)) { From 756e423eac8c620c8a02e8c53a603665f48490c2 Mon Sep 17 00:00:00 2001 From: Matt Borgerson Date: Sat, 3 Jun 2023 22:22:24 -0700 Subject: [PATCH 26/39] ui: Also clear search regex after creating snapshot --- ui/xui/main-menu.cc | 20 ++++++++++++++++---- ui/xui/main-menu.hh | 1 + 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/ui/xui/main-menu.cc b/ui/xui/main-menu.cc index f4bcc86020..3f7d3b6d1f 100644 --- a/ui/xui/main-menu.cc +++ b/ui/xui/main-menu.cc @@ -855,19 +855,31 @@ void MainMenuSnapshotsView::SnapshotBigButton(QEMUSnapshotInfo *snapshot, const ImGui::SetCursorPos(next_pos); } +void MainMenuSnapshotsView::ClearSearch() +{ + m_search_buf.clear(); + + if (m_search_regex) { + g_free(m_search_regex); + m_search_regex = NULL; + } +} + static int MainMenuSnapshotsViewUpdateSearchBox(ImGuiInputTextCallbackData *data) { GError *gerr = NULL; MainMenuSnapshotsView *win = (MainMenuSnapshotsView*)data->UserData; - if (win->m_search_regex) g_free(win->m_search_regex); - if (data->BufTextLen == 0) { + if (win->m_search_regex) { + g_free(win->m_search_regex); win->m_search_regex = NULL; + } + + if (data->BufTextLen == 0) { return 0; } char *buf = g_strdup_printf("(.*)%s(.*)", data->Buf); - win->m_search_regex = g_regex_new(buf, (GRegexCompileFlags)0, (GRegexMatchFlags)0, &gerr); g_free(buf); if (gerr) { @@ -900,7 +912,7 @@ void MainMenuSnapshotsView::Draw() ImGui::SameLine(); if (ImGui::Button(snapshot_with_create_name_exists ? "Replace" : "Create", ImVec2(-FLT_MIN, 0))) { xemu_snapshots_save(m_search_buf.empty() ? NULL : m_search_buf.c_str(), NULL); - m_search_buf.clear(); + ClearSearch(); } if (snapshot_with_create_name_exists && ImGui::IsItemHovered()) { diff --git a/ui/xui/main-menu.hh b/ui/xui/main-menu.hh index df5e85973f..7eb015e4d7 100644 --- a/ui/xui/main-menu.hh +++ b/ui/xui/main-menu.hh @@ -114,6 +114,7 @@ protected: private: void Load(); + void ClearSearch(); public: GRegex *m_search_regex; From 0155721cfead77274389f0a3275e9f5732ab4bb7 Mon Sep 17 00:00:00 2001 From: Matt Borgerson Date: Sun, 4 Jun 2023 16:23:34 -0700 Subject: [PATCH 27/39] ui: Create SnapshotManager, check snapshot disc image path --- ui/xemu-snapshots.c | 59 +++++++- ui/xemu-snapshots.h | 2 + ui/xemu.c | 21 ++- ui/xui/actions.cc | 26 +++- ui/xui/actions.hh | 1 + ui/xui/main-menu.cc | 278 +++++++++++++++++-------------------- ui/xui/main-menu.hh | 9 +- ui/xui/main.cc | 4 + ui/xui/meson.build | 1 + ui/xui/snapshot-manager.cc | 163 ++++++++++++++++++++++ ui/xui/snapshot-manager.hh | 47 +++++++ ui/xui/xemu-hud.h | 4 +- 12 files changed, 440 insertions(+), 175 deletions(-) create mode 100644 ui/xui/snapshot-manager.cc create mode 100644 ui/xui/snapshot-manager.hh diff --git a/ui/xemu-snapshots.c b/ui/xemu-snapshots.c index ec719f99e9..5a695257c1 100644 --- a/ui/xemu-snapshots.c +++ b/ui/xemu-snapshots.c @@ -31,6 +31,7 @@ #include "migration/qemu-file.h" #include "migration/snapshot.h" #include "qapi/error.h" +#include "qapi/qapi-commands-block.h" #include "sysemu/runstate.h" #include "qemu-common.h" @@ -53,6 +54,7 @@ static void xemu_snapshots_load_data(BlockDriverState *bs_ro, QEMUSnapshotInfo *info, XemuSnapshotData *data, Error **err) { + data->disc_path = NULL; data->xbe_title_name = NULL; data->gl_thumbnail = 0; @@ -82,11 +84,28 @@ static void xemu_snapshots_load_data(BlockDriverState *bs_ro, return; } - const size_t xbe_title_name_size = buf[0]; - offset = 1; + assert(size >= 9); + + offset = 0; + + const size_t disc_path_size = be32_to_cpu(*(uint32_t *)&buf[offset]); + offset += 4; + + if (disc_path_size) { + data->disc_path = (char *)g_malloc(disc_path_size + 1); + assert(size >= (offset + disc_path_size)); + memcpy(data->disc_path, &buf[offset], disc_path_size); + data->disc_path[disc_path_size] = 0; + offset += disc_path_size; + } + + assert(size >= (offset + 4)); + const size_t xbe_title_name_size = buf[offset]; + offset += 1; if (xbe_title_name_size) { data->xbe_title_name = (char *)g_malloc(xbe_title_name_size + 1); + assert(size >= (offset + xbe_title_name_size)); memcpy(data->xbe_title_name, &buf[offset], xbe_title_name_size); data->xbe_title_name[xbe_title_name_size] = 0; offset += xbe_title_name_size; @@ -98,6 +117,7 @@ static void xemu_snapshots_load_data(BlockDriverState *bs_ro, if (thumbnail_size) { GLuint thumbnail; glGenTextures(1, &thumbnail); + assert(size >= (offset + thumbnail_size)); if (xemu_snapshots_load_png_to_texture(thumbnail, &buf[offset], thumbnail_size)) { data->gl_thumbnail = thumbnail; @@ -202,6 +222,30 @@ done: return xemu_snapshots_len; } +char *xemu_get_currently_loaded_disc_path(void) +{ + char *file = NULL; + BlockInfoList *block_list, *info; + + block_list = qmp_query_block(NULL); + + for (info = block_list; info; info = info->next) { + if (strcmp("ide0-cd1", info->value->device)) { + continue; + } + + if (info->value->has_inserted) { + BlockDeviceInfo *inserted = info->value->inserted; + if (inserted->has_node_name) { + file = g_strdup(inserted->file); + } + } + } + + qapi_free_BlockInfoList(block_list); + return file; +} + void xemu_snapshots_load(const char *vm_name, Error **err) { bool vm_running = runstate_is_running(); @@ -223,6 +267,9 @@ void xemu_snapshots_delete(const char *vm_name, Error **err) void xemu_snapshots_save_extra_data(QEMUFile *f) { + char *path = xemu_get_currently_loaded_disc_path(); + size_t path_size = path ? strlen(path) : 0; + size_t xbe_title_name_size = 0; char *xbe_title_name = NULL; struct xbe *xbe_data = xemu_get_xbe_info(); @@ -239,7 +286,13 @@ void xemu_snapshots_save_extra_data(QEMUFile *f) qemu_put_be32(f, XEMU_SNAPSHOT_DATA_MAGIC); qemu_put_be32(f, XEMU_SNAPSHOT_DATA_VERSION); - qemu_put_be32(f, 1 + xbe_title_name_size + 4 + thumbnail_size); + qemu_put_be32(f, 4 + path_size + 1 + xbe_title_name_size + 4 + thumbnail_size); + + qemu_put_be32(f, path_size); + if (path_size) { + qemu_put_buffer(f, (const uint8_t *)path, path_size); + g_free(path); + } qemu_put_byte(f, xbe_title_name_size); if (xbe_title_name_size) { diff --git a/ui/xemu-snapshots.h b/ui/xemu-snapshots.h index 9a33e687b4..a9809117a2 100644 --- a/ui/xemu-snapshots.h +++ b/ui/xemu-snapshots.h @@ -37,11 +37,13 @@ extern "C" { extern const char **g_snapshot_shortcut_index_key_map[]; typedef struct XemuSnapshotData { + char *disc_path; char *xbe_title_name; GLuint gl_thumbnail; } XemuSnapshotData; // Implemented in xemu-snapshots.c +char *xemu_get_currently_loaded_disc_path(void); int xemu_snapshots_list(QEMUSnapshotInfo **info, XemuSnapshotData **extra_data, Error **err); void xemu_snapshots_load(const char *vm_name, Error **err); diff --git a/ui/xemu.c b/ui/xemu.c index 4cb9891b20..1f009b1104 100644 --- a/ui/xemu.c +++ b/ui/xemu.c @@ -55,6 +55,7 @@ #include "hw/xbox/smbus.h" // For eject, drive tray #include "hw/xbox/nv2a/nv2a.h" +#include "ui/xemu-notifications.h" #include @@ -1556,26 +1557,34 @@ int main(int argc, char **argv) // rcu_unregister_thread(); } -void xemu_eject_disc(void) +void xemu_eject_disc(Error **errp) { + Error *error = NULL; + xbox_smc_eject_button(); // Xbox software may request that the drive open, but do it now anyway - Error *err = NULL; - qmp_eject(true, "ide0-cd1", false, NULL, true, false, &err); + qmp_eject(true, "ide0-cd1", false, NULL, true, false, &error); + if (error) { + error_propagate(errp, error); + } xbox_smc_update_tray_state(); } -void xemu_load_disc(const char *path) +void xemu_load_disc(const char *path, Error **errp) { + Error *error = NULL; + // Ensure an eject sequence is always triggered so Xbox software reloads xbox_smc_eject_button(); - Error *err = NULL; qmp_blockdev_change_medium(true, "ide0-cd1", false, NULL, path, false, "", false, 0, - &err); + &error); + if (error) { + error_propagate(errp, error); + } xbox_smc_update_tray_state(); } diff --git a/ui/xui/actions.cc b/ui/xui/actions.cc index f38be7dcb5..f6e8cb29ec 100644 --- a/ui/xui/actions.cc +++ b/ui/xui/actions.cc @@ -17,19 +17,29 @@ // along with this program. If not, see . // #include "common.hh" +#include "actions.hh" #include "misc.hh" #include "xemu-hud.h" #include "../xemu-snapshots.h" #include "../xemu-notifications.h" +#include "snapshot-manager.hh" void ActionEjectDisc(void) { xemu_settings_set_string(&g_config.sys.files.dvd_path, ""); - xemu_eject_disc(); + + Error *err = NULL; + xemu_eject_disc(&err); + if (err) { + xemu_queue_error_message(error_get_pretty(err)); + error_free(err); + } } void ActionLoadDisc(void) { + Error *err = NULL; + const char *iso_file_filters = ".iso Files\0*.iso\0All Files\0*.*\0"; const char *new_disc_path = PausedFileOpen(NOC_FILE_DIALOG_OPEN, iso_file_filters, @@ -39,7 +49,12 @@ void ActionLoadDisc(void) return; } xemu_settings_set_string(&g_config.sys.files.dvd_path, new_disc_path); - xemu_load_disc(new_disc_path); + + xemu_load_disc(new_disc_path, &err); + if (err) { + xemu_queue_error_message(error_get_pretty(err)); + error_free(err); + } } void ActionTogglePause(void) @@ -81,7 +96,7 @@ void ActionActivateBoundSnapshot(int slot, bool save) if (save) { xemu_snapshots_save(snapshot_name, &err); } else { - xemu_snapshots_load(snapshot_name, &err); + ActionLoadSnapshotChecked(snapshot_name); } if (err) { @@ -89,3 +104,8 @@ void ActionActivateBoundSnapshot(int slot, bool save) error_free(err); } } + +void ActionLoadSnapshotChecked(const char *name) +{ + g_snapshot_mgr.LoadSnapshotChecked(name); +} diff --git a/ui/xui/actions.hh b/ui/xui/actions.hh index 60235bf373..08aedd3c9b 100644 --- a/ui/xui/actions.hh +++ b/ui/xui/actions.hh @@ -25,3 +25,4 @@ void ActionReset(); void ActionShutdown(); void ActionScreenshot(); void ActionActivateBoundSnapshot(int slot, bool save); +void ActionLoadSnapshotChecked(const char *name); diff --git a/ui/xui/main-menu.cc b/ui/xui/main-menu.cc index 3f7d3b6d1f..0d3ca34885 100644 --- a/ui/xui/main-menu.cc +++ b/ui/xui/main-menu.cc @@ -22,12 +22,14 @@ #include "main-menu.hh" #include "font-manager.hh" #include "input-manager.hh" +#include "snapshot-manager.hh" #include "viewport-manager.hh" #include "xemu-hud.h" #include "misc.hh" #include "gl-helpers.hh" #include "reporting.hh" #include "qapi/error.h" +#include "actions.hh" #include "../xemu-input.h" #include "../xemu-notifications.h" @@ -35,7 +37,6 @@ #include "../xemu-monitor.h" #include "../xemu-version.h" #include "../xemu-net.h" -#include "../xemu-snapshots.h" #include "../xemu-os-utils.h" #include "../xemu-xbe.h" @@ -641,63 +642,24 @@ void MainMenuNetworkView::DrawUdpOptions(bool appearing) ImGui::PopFont(); } -void MainMenuSnapshotsView::Load() -{ - Error *err = NULL; - - if (!m_load_failed) { - m_snapshots_len = xemu_snapshots_list(&m_snapshots, &m_extra_data, &err); - } - - if (err) { - m_load_failed = true; - xemu_queue_error_message(error_get_pretty(err)); - error_free(err); - m_snapshots_len = 0; - } - - struct xbe *xbe = xemu_get_xbe_info(); - if (xbe && xbe->cert->m_titleid != m_current_title_id) { - g_free(m_current_title_name); - m_current_title_name = g_utf16_to_utf8(xbe->cert->m_title_name, 40, NULL, NULL, NULL); - m_current_title_id = xbe->cert->m_titleid; - } -} - -MainMenuSnapshotsView::MainMenuSnapshotsView(): MainMenuTabView() +MainMenuSnapshotsView::MainMenuSnapshotsView() : MainMenuTabView() { xemu_snapshots_mark_dirty(); - m_load_failed = false; m_search_regex = NULL; m_current_title_name = NULL; m_current_title_id = 0; - } MainMenuSnapshotsView::~MainMenuSnapshotsView() { - g_free(m_snapshots); - g_free(m_extra_data); - xemu_snapshots_mark_dirty(); - g_free(m_current_title_name); g_free(m_search_regex); } -void MainMenuSnapshotsView::SnapshotBigButton(QEMUSnapshotInfo *snapshot, const char *title_name, GLuint thumbnail) +bool MainMenuSnapshotsView::BigSnapshotButton(QEMUSnapshotInfo *snapshot, XemuSnapshotData *data, int current_snapshot_binding) { - Error *err = NULL; - int current_snapshot_binding = -1; - for (int i = 0; i < 4; ++i) { - if (g_strcmp0(*(g_snapshot_shortcut_index_key_map[i]), snapshot->name) == 0) { - assert(current_snapshot_binding == -1); - current_snapshot_binding = i; - } - } - ImGuiStyle &style = ImGui::GetStyle(); - ImVec2 pos = ImGui::GetCursorPos(); ImDrawList *draw_list = ImGui::GetWindowDrawList(); ImGui::PushFont(g_font_mgr.m_menu_font_small); @@ -706,6 +668,7 @@ void MainMenuSnapshotsView::SnapshotBigButton(QEMUSnapshotInfo *snapshot, const ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0, 0)); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, g_viewport_mgr.Scale(ImVec2(5, 5))); + ImGui::PushFont(g_font_mgr.m_menu_font_medium); ImVec2 ts_title = ImGui::CalcTextSize(snapshot->name); @@ -715,54 +678,36 @@ void MainMenuSnapshotsView::SnapshotBigButton(QEMUSnapshotInfo *snapshot, const ImVec2 title_pos(name_pos.x, name_pos.y + ts_title.y + style.FramePadding.x); ImVec2 date_pos(name_pos.x, title_pos.y + ts_title.y + style.FramePadding.x); ImVec2 binding_pos(name_pos.x, date_pos.y + ts_title.y + style.FramePadding.x); + ImVec2 button_size(-FLT_MIN, fmax(thumbnail_size.y + style.FramePadding.y * 2, ts_title.y + ts_sub.y + style.FramePadding.y * 3)); - bool load = ImGui::Button("###button", ImVec2(-FLT_MIN, fmax(thumbnail_size.y + style.FramePadding.y * 2, ts_title.y + ts_sub.y + style.FramePadding.y * 3))); - if (load) { - xemu_snapshots_load(snapshot->name, &err); - if (err) { - xemu_queue_error_message(error_get_pretty(err)); - error_free(err); - } - } - bool options_key_pressed = ImGui::IsKeyPressed(ImGuiKey_GamepadFaceLeft); - bool show_snapshot_context_menu = ImGui::IsItemHovered() && - (ImGui::IsMouseReleased(ImGuiMouseButton_Right) || - options_key_pressed); + bool load = ImGui::Button("###button", button_size); - ImVec2 next_pos = ImGui::GetCursorPos(); - const ImVec2 p0 = ImGui::GetItemRectMin(); - const ImVec2 p1 = ImGui::GetItemRectMax(); ImGui::PopFont(); + const ImVec2 p0 = ImGui::GetItemRectMin(); + const ImVec2 p1 = ImGui::GetItemRectMax(); draw_list->PushClipRect(p0, p1, true); // Snapshot thumbnail - ImGui::SetItemAllowOverlap(); - ImGui::SetCursorPos(ImVec2(pos.x + thumbnail_pos.x, pos.y + thumbnail_pos.y)); - - if (!thumbnail) { - thumbnail = g_icon_tex; - } - + GLuint thumbnail = data->gl_thumbnail ? data->gl_thumbnail : g_icon_tex; + int thumbnail_width, thumbnail_height; glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, thumbnail); - int thumbnail_width, thumbnail_height; glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &thumbnail_width); glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &thumbnail_height); // Draw black background behind thumbnail - draw_list->AddRectFilled(ImVec2(p0.x + thumbnail_pos.x, p0.y + thumbnail_pos.y), - ImVec2(p0.x + thumbnail_pos.x + thumbnail_size.x, p0.y + thumbnail_pos.y + thumbnail_size.y), - IM_COL32_BLACK); + ImVec2 thumbnail_min(p0.x + thumbnail_pos.x, p0.y + thumbnail_pos.y); + ImVec2 thumbnail_max(thumbnail_min.x + thumbnail_size.x, thumbnail_min.y + thumbnail_size.y); + draw_list->AddRectFilled(thumbnail_min, thumbnail_max, IM_COL32_BLACK); - // Draw centered thumbnail + // Draw centered thumbnail image int scaled_width, scaled_height; ScaleDimensions(thumbnail_width, thumbnail_height, thumbnail_size.x, thumbnail_size.y, &scaled_width, &scaled_height); - ImVec2 img_pos = ImGui::GetCursorPos(); - img_pos.x += (thumbnail_size.x - scaled_width) / 2; - img_pos.y += (thumbnail_size.y - scaled_height) / 2; - ImGui::SetCursorPos(img_pos); - ImGui::Image((ImTextureID)(uint64_t)thumbnail, ImVec2(scaled_width, scaled_height)); + ImVec2 img_min = ImVec2(thumbnail_min.x + (thumbnail_size.x - scaled_width) / 2, + thumbnail_min.y + (thumbnail_size.y - scaled_height) / 2); + ImVec2 img_max = ImVec2(img_min.x + scaled_width, img_min.y + scaled_height); + draw_list->AddImage((ImTextureID)(uint64_t)thumbnail, img_min, img_max); // Snapshot title ImGui::PushFont(g_font_mgr.m_menu_font_medium); @@ -771,6 +716,7 @@ void MainMenuSnapshotsView::SnapshotBigButton(QEMUSnapshotInfo *snapshot, const // Snapshot XBE title name ImGui::PushFont(g_font_mgr.m_menu_font_small); + const char *title_name = data->xbe_title_name ? data->xbe_title_name : "(Unknown XBE Title Name)"; draw_list->AddText(ImVec2(p0.x + title_pos.x, p0.y + title_pos.y), IM_COL32(255, 255, 255, 200), title_name); // Snapshot date @@ -788,71 +734,9 @@ void MainMenuSnapshotsView::SnapshotBigButton(QEMUSnapshotInfo *snapshot, const ImGui::PopFont(); draw_list->PopClipRect(); - - if (show_snapshot_context_menu) { - if (options_key_pressed) { - ImGui::SetNextWindowPos(p0); - } - ImGui::OpenPopup("Snapshot Options"); - } - - if (ImGui::BeginPopupContextItem("Snapshot Options")) { - if (ImGui::MenuItem("Load")) { - xemu_snapshots_load(snapshot->name, &err); - if (err) { - xemu_queue_error_message(error_get_pretty(err)); - error_free(err); - } - } - - if (ImGui::BeginMenu("Keybinding")) { - for (int i = 0; i < 4; ++i) { - char *item_name = g_strdup_printf("Bind to F%d", i + 5); - - if (ImGui::MenuItem(item_name)) { - if (current_snapshot_binding >= 0) { - xemu_settings_set_string(g_snapshot_shortcut_index_key_map[current_snapshot_binding], ""); - } - xemu_settings_set_string(g_snapshot_shortcut_index_key_map[i], snapshot->name); - current_snapshot_binding = i; - - ImGui::CloseCurrentPopup(); - } - - g_free(item_name); - } - - if (current_snapshot_binding >= 0) { - if (ImGui::MenuItem("Unbind")) { - xemu_settings_set_string(g_snapshot_shortcut_index_key_map[current_snapshot_binding], ""); - current_snapshot_binding = -1; - } - } - ImGui::EndMenu(); - } - - ImGui::Separator(); - - if (ImGui::MenuItem("Replace")) { - xemu_snapshots_save(snapshot->name, &err); - if (err) { - xemu_queue_error_message(error_get_pretty(err)); - error_free(err); - } - } - if (ImGui::MenuItem("Delete")) { - xemu_snapshots_delete(snapshot->name, &err); - if (err) { - xemu_queue_error_message(error_get_pretty(err)); - error_free(err); - } - } - - ImGui::EndPopup(); - } - ImGui::PopStyleVar(2); - ImGui::SetCursorPos(next_pos); + + return load; } void MainMenuSnapshotsView::ClearSearch() @@ -892,18 +776,26 @@ static int MainMenuSnapshotsViewUpdateSearchBox(ImGuiInputTextCallbackData *data void MainMenuSnapshotsView::Draw() { - Load(); - SectionTitle("Snapshots"); + g_snapshot_mgr.Refresh(); - Toggle("Filter by current title", &g_config.general.snapshots.filter_current_game); + struct xbe *xbe = xemu_get_xbe_info(); + if (xbe && xbe->cert->m_titleid != m_current_title_id) { + g_free(m_current_title_name); + m_current_title_name = g_utf16_to_utf8(xbe->cert->m_title_name, 40, NULL, NULL, NULL); + m_current_title_id = xbe->cert->m_titleid; + } + + SectionTitle("Snapshots"); + Toggle("Filter by current title", &g_config.general.snapshots.filter_current_game, + "Only display snapshots created while running the currently running XBE"); ImGui::SetNextItemWidth(ImGui::GetColumnWidth() * 0.8); ImGui::InputTextWithHint("##search", "Search...", &m_search_buf, ImGuiInputTextFlags_CallbackEdit, &MainMenuSnapshotsViewUpdateSearchBox, this); bool snapshot_with_create_name_exists = false; - for (int i = 0; i < m_snapshots_len; ++i) { - if (g_strcmp0(m_search_buf.c_str(), m_snapshots[i].name) == 0) { + for (int i = 0; i < g_snapshot_mgr.m_snapshots_len; ++i) { + if (g_strcmp0(m_search_buf.c_str(), g_snapshot_mgr.m_snapshots[i].name) == 0) { snapshot_with_create_name_exists = true; break; } @@ -919,9 +811,9 @@ void MainMenuSnapshotsView::Draw() ImGui::SetTooltip("A snapshot with the name \"%s\" already exists. This button will overwrite the existing snapshot.", m_search_buf.c_str()); } - for (int i = m_snapshots_len - 1; i >= 0; i--) { - if (g_config.general.snapshots.filter_current_game && m_extra_data[i].xbe_title_name && - (strcmp(m_current_title_name, m_extra_data[i].xbe_title_name) != 0)) { + for (int i = g_snapshot_mgr.m_snapshots_len - 1; i >= 0; i--) { + if (g_config.general.snapshots.filter_current_game && g_snapshot_mgr.m_extra_data[i].xbe_title_name && + (strcmp(m_current_title_name, g_snapshot_mgr.m_extra_data[i].xbe_title_name) != 0)) { continue; } @@ -929,12 +821,12 @@ void MainMenuSnapshotsView::Draw() GMatchInfo *match; bool keep_entry = false; - g_regex_match(m_search_regex, m_snapshots[i].name, (GRegexMatchFlags)0, &match); + g_regex_match(m_search_regex, g_snapshot_mgr.m_snapshots[i].name, (GRegexMatchFlags)0, &match); keep_entry |= g_match_info_matches(match); g_match_info_free(match); - if (m_extra_data[i].xbe_title_name) { - g_regex_match(m_search_regex, m_extra_data[i].xbe_title_name, (GRegexMatchFlags)0, &match); + if (g_snapshot_mgr.m_extra_data[i].xbe_title_name) { + g_regex_match(m_search_regex, g_snapshot_mgr.m_extra_data[i].xbe_title_name, (GRegexMatchFlags)0, &match); keep_entry |= g_match_info_matches(match); g_free(match); } @@ -944,16 +836,94 @@ void MainMenuSnapshotsView::Draw() } } + QEMUSnapshotInfo *snapshot = &g_snapshot_mgr.m_snapshots[i]; + XemuSnapshotData *data = &g_snapshot_mgr.m_extra_data[i]; + + int current_snapshot_binding = -1; + for (int i = 0; i < 4; ++i) { + if (g_strcmp0(*(g_snapshot_shortcut_index_key_map[i]), snapshot->name) == 0) { + assert(current_snapshot_binding == -1); + current_snapshot_binding = i; + } + } + ImGui::PushID(i); - SnapshotBigButton( - m_snapshots + i, - m_extra_data[i].xbe_title_name ? m_extra_data[i].xbe_title_name : "Unknown", - m_extra_data[i].gl_thumbnail - ); + + ImVec2 pos = ImGui::GetCursorScreenPos(); + bool load = BigSnapshotButton(snapshot, data, current_snapshot_binding); + + // FIXME: Provide context menu control annotation + if (ImGui::IsItemHovered() && ImGui::IsKeyPressed(ImGuiKey_GamepadFaceLeft)) { + ImGui::SetNextWindowPos(pos); + ImGui::OpenPopup("Snapshot Options"); + } + + DrawSnapshotContextMenu(snapshot, data, current_snapshot_binding); + ImGui::PopID(); + + if (load) { + ActionLoadSnapshotChecked(snapshot->name); + } } } +void MainMenuSnapshotsView::DrawSnapshotContextMenu(QEMUSnapshotInfo *snapshot, XemuSnapshotData *data, int current_snapshot_binding) +{ + if (!ImGui::BeginPopupContextItem("Snapshot Options")) { + return; + } + + if (ImGui::MenuItem("Load")) { + ActionLoadSnapshotChecked(snapshot->name); + } + + if (ImGui::BeginMenu("Keybinding")) { + for (int i = 0; i < 4; ++i) { + char *item_name = g_strdup_printf("Bind to F%d", i + 5); + + if (ImGui::MenuItem(item_name)) { + if (current_snapshot_binding >= 0) { + xemu_settings_set_string(g_snapshot_shortcut_index_key_map[current_snapshot_binding], ""); + } + xemu_settings_set_string(g_snapshot_shortcut_index_key_map[i], snapshot->name); + current_snapshot_binding = i; + + ImGui::CloseCurrentPopup(); + } + + g_free(item_name); + } + + if (current_snapshot_binding >= 0) { + if (ImGui::MenuItem("Unbind")) { + xemu_settings_set_string(g_snapshot_shortcut_index_key_map[current_snapshot_binding], ""); + current_snapshot_binding = -1; + } + } + ImGui::EndMenu(); + } + + ImGui::Separator(); + + Error *err = NULL; + + if (ImGui::MenuItem("Replace")) { + xemu_snapshots_save(snapshot->name, &err); + } + + if (ImGui::MenuItem("Delete")) { + xemu_snapshots_delete(snapshot->name, &err); + } + + if (err) { + xemu_queue_error_message(error_get_pretty(err)); + error_free(err); + } + + ImGui::EndPopup(); +} + MainMenuSystemView::MainMenuSystemView() : m_dirty(false) { } diff --git a/ui/xui/main-menu.hh b/ui/xui/main-menu.hh index 7eb015e4d7..0c769a3c5b 100644 --- a/ui/xui/main-menu.hh +++ b/ui/xui/main-menu.hh @@ -104,24 +104,19 @@ public: class MainMenuSnapshotsView : public virtual MainMenuTabView { protected: - QEMUSnapshotInfo *m_snapshots; - XemuSnapshotData *m_extra_data; - int m_snapshots_len; uint32_t m_current_title_id; char *m_current_title_name; std::string m_search_buf; - bool m_load_failed; private: - void Load(); void ClearSearch(); + void DrawSnapshotContextMenu(QEMUSnapshotInfo *snapshot, XemuSnapshotData *data, int current_snapshot_binding); public: GRegex *m_search_regex; MainMenuSnapshotsView(); ~MainMenuSnapshotsView(); - void SnapshotBigButton(QEMUSnapshotInfo *snapshot, const char *title_name, - GLuint screenshot); + bool BigSnapshotButton(QEMUSnapshotInfo *snapshot, XemuSnapshotData *data, int current_snapshot_binding); void Draw() override; }; diff --git a/ui/xui/main.cc b/ui/xui/main.cc index e027c409a9..4680dad29b 100644 --- a/ui/xui/main.cc +++ b/ui/xui/main.cc @@ -37,6 +37,7 @@ #include "misc.hh" #include "gl-helpers.hh" #include "input-manager.hh" +#include "snapshot-manager.hh" #include "viewport-manager.hh" #include "font-manager.hh" #include "scene.hh" @@ -54,6 +55,8 @@ #endif bool g_screenshot_pending; +const char *g_snapshot_pending_load_name; + float g_main_menu_height; static ImGuiStyle g_base_style; @@ -298,6 +301,7 @@ void xemu_hud_render(void) #endif g_scene_mgr.Draw(); if (!first_boot_window.is_open) notification_manager.Draw(); + g_snapshot_mgr.Draw(); // static bool show_demo = true; // if (show_demo) ImGui::ShowDemoWindow(&show_demo); diff --git a/ui/xui/meson.build b/ui/xui/meson.build index 088e68c764..2fe5b2c64a 100644 --- a/ui/xui/meson.build +++ b/ui/xui/meson.build @@ -16,6 +16,7 @@ xemu_ss.add(files( 'scene-components.cc', 'scene-manager.cc', 'scene.cc', + 'snapshot-manager.cc', 'viewport-manager.cc', 'welcome.cc', 'widgets.cc', diff --git a/ui/xui/snapshot-manager.cc b/ui/xui/snapshot-manager.cc new file mode 100644 index 0000000000..5362e3f00f --- /dev/null +++ b/ui/xui/snapshot-manager.cc @@ -0,0 +1,163 @@ +// +// xemu User Interface +// +// Copyright (C) 2020-2022 Matt Borgerson +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "common.hh" +#include "notifications.hh" +#include "snapshot-manager.hh" +#include "xemu-hud.h" + +SnapshotManager g_snapshot_mgr; + +SnapshotManager::SnapshotManager() +{ + m_snapshots = NULL; + m_extra_data = NULL; + m_load_failed = false; + m_open_pending = false; + m_snapshots_len = 0; +} + +SnapshotManager::~SnapshotManager() +{ + g_free(m_snapshots); + g_free(m_extra_data); + xemu_snapshots_mark_dirty(); +} + +void SnapshotManager::Refresh() +{ + Error *err = NULL; + + if (!m_load_failed) { + m_snapshots_len = xemu_snapshots_list(&m_snapshots, &m_extra_data, &err); + } + + if (err) { + m_load_failed = true; + xemu_queue_error_message(error_get_pretty(err)); + error_free(err); + m_snapshots_len = 0; + } +} + +void SnapshotManager::LoadSnapshotChecked(const char *name) +{ + Refresh(); + + XemuSnapshotData *data = NULL; + for (int i = 0; i < m_snapshots_len; i++) { + if (!strcmp(m_snapshots[i].name, name)) { + data = &m_extra_data[i]; + break; + } + } + + if (data == NULL) { + return; + } + + char *current_disc_path = xemu_get_currently_loaded_disc_path(); + if (data->disc_path && (!current_disc_path || strcmp(current_disc_path, data->disc_path))) { + if (current_disc_path) { + m_current_disc_path = current_disc_path; + } else { + m_current_disc_path.clear(); + } + m_target_disc_path = data->disc_path; + m_pending_load_name = name; + m_open_pending = true; + } else { + if (!data->disc_path) { + xemu_eject_disc(NULL); + } + LoadSnapshot(name); + } + + if (current_disc_path) { + g_free(current_disc_path); + } +} + +void SnapshotManager::LoadSnapshot(const char *name) +{ + Error *err = NULL; + + xemu_snapshots_load(name, &err); + + if (err) { + xemu_queue_error_message(error_get_pretty(err)); + error_free(err); + } +} + +void SnapshotManager::Draw() +{ + DrawSnapshotDiscLoadDialog(); +} + +void SnapshotManager::DrawSnapshotDiscLoadDialog() +{ + if (m_open_pending) { + ImGui::OpenPopup("DVD Drive Image"); + m_open_pending = false; + } + + if (!ImGui::BeginPopupModal("DVD Drive Image", NULL, ImGuiWindowFlags_AlwaysAutoResize)) { + return; + } + + ImGui::Text("The DVD drive disc image mounted when the snapshot was created does not appear to be loaded:"); + ImGui::Spacing(); + ImGui::Indent(); + ImGui::Text("Current Image: %s", m_current_disc_path.length() ? m_current_disc_path.c_str() : "(None)"); + ImGui::Text("Expected Image: %s", m_target_disc_path.length() ? m_target_disc_path.c_str() : "(None)"); + ImGui::Unindent(); + ImGui::Spacing(); + ImGui::Text("Would you like to load it now?"); + + ImGui::Dummy(ImVec2(0,16)); + + if (ImGui::Button("Yes", ImVec2(120, 0))) { + xemu_eject_disc(NULL); + + Error *err = NULL; + xemu_load_disc(m_target_disc_path.c_str(), &err); + if (err) { + xemu_queue_error_message(error_get_pretty(err)); + error_free(err); + } else { + LoadSnapshot(m_pending_load_name.c_str()); + } + + ImGui::CloseCurrentPopup(); + } + + ImGui::SameLine(); + if (ImGui::Button("No", ImVec2(120, 0))) { + LoadSnapshot(m_pending_load_name.c_str()); + ImGui::CloseCurrentPopup(); + } + + ImGui::SameLine(); + if (ImGui::Button("Cancel", ImVec2(120, 0))) { + ImGui::CloseCurrentPopup(); + } + + ImGui::EndPopup(); +} diff --git a/ui/xui/snapshot-manager.hh b/ui/xui/snapshot-manager.hh new file mode 100644 index 0000000000..6966d85f85 --- /dev/null +++ b/ui/xui/snapshot-manager.hh @@ -0,0 +1,47 @@ +// +// xemu User Interface +// +// Copyright (C) 2020-2022 Matt Borgerson +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#pragma once +#include +#include "../xemu-snapshots.h" + +class SnapshotManager +{ +public: + QEMUSnapshotInfo *m_snapshots; + XemuSnapshotData *m_extra_data; + bool m_load_failed; + bool m_open_pending; + int m_snapshots_len; + + std::string m_pending_load_name; + std::string m_current_disc_path; + std::string m_target_disc_path; + + SnapshotManager(); + ~SnapshotManager(); + void Refresh(); + void LoadSnapshot(const char *name); + void LoadSnapshotChecked(const char *name); + + void Draw(); + void DrawSnapshotDiscLoadDialog(); +}; + +extern SnapshotManager g_snapshot_mgr; diff --git a/ui/xui/xemu-hud.h b/ui/xui/xemu-hud.h index a510c85bf6..aa6e3d9acf 100644 --- a/ui/xui/xemu-hud.h +++ b/ui/xui/xemu-hud.h @@ -33,8 +33,8 @@ extern "C" { int xemu_is_fullscreen(void); void xemu_monitor_init(void); void xemu_toggle_fullscreen(void); -void xemu_eject_disc(void); -void xemu_load_disc(const char *path); +void xemu_eject_disc(Error **errp); +void xemu_load_disc(const char *path, Error **errp); // Implemented in xemu_hud.cc void xemu_hud_init(SDL_Window *window, void *sdl_gl_context); From 6fbb0dfbcd674d202c1ff51800ec01bf9b55c431 Mon Sep 17 00:00:00 2001 From: Matt Borgerson Date: Sun, 4 Jun 2023 16:55:00 -0700 Subject: [PATCH 28/39] ui: Clarify snapshot search/name placeholder text --- ui/xui/main-menu.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ui/xui/main-menu.cc b/ui/xui/main-menu.cc index 0d3ca34885..6bac3db2db 100644 --- a/ui/xui/main-menu.cc +++ b/ui/xui/main-menu.cc @@ -790,7 +790,8 @@ void MainMenuSnapshotsView::Draw() "Only display snapshots created while running the currently running XBE"); ImGui::SetNextItemWidth(ImGui::GetColumnWidth() * 0.8); - ImGui::InputTextWithHint("##search", "Search...", &m_search_buf, ImGuiInputTextFlags_CallbackEdit, + ImGui::InputTextWithHint("##search", "Search or name new snapshot...", + &m_search_buf, ImGuiInputTextFlags_CallbackEdit, &MainMenuSnapshotsViewUpdateSearchBox, this); bool snapshot_with_create_name_exists = false; From df7e8c23a20e0d0d6b80ca56584b354fc4d3ac5d Mon Sep 17 00:00:00 2001 From: Matt Borgerson Date: Sun, 4 Jun 2023 16:59:03 -0700 Subject: [PATCH 29/39] ui: Use larger font for snapshot search text and create button --- ui/xui/main-menu.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ui/xui/main-menu.cc b/ui/xui/main-menu.cc index 6bac3db2db..2d6f18978a 100644 --- a/ui/xui/main-menu.cc +++ b/ui/xui/main-menu.cc @@ -790,6 +790,7 @@ void MainMenuSnapshotsView::Draw() "Only display snapshots created while running the currently running XBE"); ImGui::SetNextItemWidth(ImGui::GetColumnWidth() * 0.8); + ImGui::PushFont(g_font_mgr.m_menu_font_small); ImGui::InputTextWithHint("##search", "Search or name new snapshot...", &m_search_buf, ImGuiInputTextFlags_CallbackEdit, &MainMenuSnapshotsViewUpdateSearchBox, this); @@ -811,6 +812,7 @@ void MainMenuSnapshotsView::Draw() if (snapshot_with_create_name_exists && ImGui::IsItemHovered()) { ImGui::SetTooltip("A snapshot with the name \"%s\" already exists. This button will overwrite the existing snapshot.", m_search_buf.c_str()); } + ImGui::PopFont(); for (int i = g_snapshot_mgr.m_snapshots_len - 1; i >= 0; i--) { if (g_config.general.snapshots.filter_current_game && g_snapshot_mgr.m_extra_data[i].xbe_title_name && From 386a114c015e121c317b5f0422b81794408dd26b Mon Sep 17 00:00:00 2001 From: Matt Borgerson Date: Sun, 4 Jun 2023 17:12:44 -0700 Subject: [PATCH 30/39] ui: Show placeholder text when no snapshots are displayed --- ui/xui/main-menu.cc | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/ui/xui/main-menu.cc b/ui/xui/main-menu.cc index 2d6f18978a..dee90317be 100644 --- a/ui/xui/main-menu.cc +++ b/ui/xui/main-menu.cc @@ -814,6 +814,8 @@ void MainMenuSnapshotsView::Draw() } ImGui::PopFont(); + bool at_least_one_snapshot_displayed = false; + for (int i = g_snapshot_mgr.m_snapshots_len - 1; i >= 0; i--) { if (g_config.general.snapshots.filter_current_game && g_snapshot_mgr.m_extra_data[i].xbe_title_name && (strcmp(m_current_title_name, g_snapshot_mgr.m_extra_data[i].xbe_title_name) != 0)) { @@ -868,6 +870,26 @@ void MainMenuSnapshotsView::Draw() if (load) { ActionLoadSnapshotChecked(snapshot->name); } + + at_least_one_snapshot_displayed = true; + } + + if (!at_least_one_snapshot_displayed) { + ImGui::Dummy(g_viewport_mgr.Scale(ImVec2(0, 16))); + const char *msg; + if (g_snapshot_mgr.m_snapshots_len) { + if (!m_search_buf.empty()) { + msg = "Press Create to create new snapshot"; + } else { + msg = "No snapshots match filter criteria"; + } + } else { + msg = "No snapshots to display"; + } + ImVec2 dim = ImGui::CalcTextSize(msg); + ImVec2 cur = ImGui::GetCursorPos(); + ImGui::SetCursorPosX(cur.x + (ImGui::GetColumnWidth()-dim.x)/2); + ImGui::TextColored(ImVec4(0.94f, 0.94f, 0.94f, 0.70f), "%s", msg); } } From 487cc7f5914c0d8e37ae3bc112d94f6355bdfa56 Mon Sep 17 00:00:00 2001 From: Matt Borgerson Date: Sun, 4 Jun 2023 17:17:26 -0700 Subject: [PATCH 31/39] ui: Make snapshot Replace button red --- ui/xui/main-menu.cc | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ui/xui/main-menu.cc b/ui/xui/main-menu.cc index dee90317be..79ab0dbb67 100644 --- a/ui/xui/main-menu.cc +++ b/ui/xui/main-menu.cc @@ -804,10 +804,18 @@ void MainMenuSnapshotsView::Draw() } ImGui::SameLine(); + if (snapshot_with_create_name_exists) { + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.8, 0, 0, 1)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(1, 0, 0, 1)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(1, 0, 0, 1)); + } if (ImGui::Button(snapshot_with_create_name_exists ? "Replace" : "Create", ImVec2(-FLT_MIN, 0))) { xemu_snapshots_save(m_search_buf.empty() ? NULL : m_search_buf.c_str(), NULL); ClearSearch(); } + if (snapshot_with_create_name_exists) { + ImGui::PopStyleColor(3); + } if (snapshot_with_create_name_exists && ImGui::IsItemHovered()) { ImGui::SetTooltip("A snapshot with the name \"%s\" already exists. This button will overwrite the existing snapshot.", m_search_buf.c_str()); From 7e6ca1097b1f80006b792eac31d9a46454fb5bd3 Mon Sep 17 00:00:00 2001 From: Matt Borgerson Date: Sun, 4 Jun 2023 18:17:55 -0700 Subject: [PATCH 32/39] ui: Only check for xbe if snapshot xbe filter is on --- ui/xui/main-menu.cc | 31 +++++++++++++++++++++---------- ui/xui/main-menu.hh | 2 +- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/ui/xui/main-menu.cc b/ui/xui/main-menu.cc index 79ab0dbb67..c99f9ed10b 100644 --- a/ui/xui/main-menu.cc +++ b/ui/xui/main-menu.cc @@ -647,13 +647,11 @@ MainMenuSnapshotsView::MainMenuSnapshotsView() : MainMenuTabView() xemu_snapshots_mark_dirty(); m_search_regex = NULL; - m_current_title_name = NULL; m_current_title_id = 0; } MainMenuSnapshotsView::~MainMenuSnapshotsView() { - g_free(m_current_title_name); g_free(m_search_regex); } @@ -778,17 +776,30 @@ void MainMenuSnapshotsView::Draw() { g_snapshot_mgr.Refresh(); - struct xbe *xbe = xemu_get_xbe_info(); - if (xbe && xbe->cert->m_titleid != m_current_title_id) { - g_free(m_current_title_name); - m_current_title_name = g_utf16_to_utf8(xbe->cert->m_title_name, 40, NULL, NULL, NULL); - m_current_title_id = xbe->cert->m_titleid; - } - SectionTitle("Snapshots"); Toggle("Filter by current title", &g_config.general.snapshots.filter_current_game, "Only display snapshots created while running the currently running XBE"); + if (g_config.general.snapshots.filter_current_game) { + struct xbe *xbe = xemu_get_xbe_info(); + if (xbe && xbe->cert) { + if (xbe->cert->m_titleid != m_current_title_id) { + char *title_name = g_utf16_to_utf8(xbe->cert->m_title_name, 40, NULL, NULL, NULL); + if (title_name) { + m_current_title_name = title_name; + g_free(title_name); + } else { + m_current_title_name.clear(); + } + + m_current_title_id = xbe->cert->m_titleid; + } + } else { + m_current_title_name.clear(); + m_current_title_id = 0; + } + } + ImGui::SetNextItemWidth(ImGui::GetColumnWidth() * 0.8); ImGui::PushFont(g_font_mgr.m_menu_font_small); ImGui::InputTextWithHint("##search", "Search or name new snapshot...", @@ -826,7 +837,7 @@ void MainMenuSnapshotsView::Draw() for (int i = g_snapshot_mgr.m_snapshots_len - 1; i >= 0; i--) { if (g_config.general.snapshots.filter_current_game && g_snapshot_mgr.m_extra_data[i].xbe_title_name && - (strcmp(m_current_title_name, g_snapshot_mgr.m_extra_data[i].xbe_title_name) != 0)) { + m_current_title_name.size() && strcmp(m_current_title_name.c_str(), g_snapshot_mgr.m_extra_data[i].xbe_title_name)) { continue; } diff --git a/ui/xui/main-menu.hh b/ui/xui/main-menu.hh index 0c769a3c5b..63c4040fd0 100644 --- a/ui/xui/main-menu.hh +++ b/ui/xui/main-menu.hh @@ -105,7 +105,7 @@ class MainMenuSnapshotsView : public virtual MainMenuTabView { protected: uint32_t m_current_title_id; - char *m_current_title_name; + std::string m_current_title_name; std::string m_search_buf; private: From 5ac1dd1e98a72df8e7e267175e893dbec35b0915 Mon Sep 17 00:00:00 2001 From: Matt Borgerson Date: Sun, 4 Jun 2023 19:19:42 -0700 Subject: [PATCH 33/39] ui: Fix MainMenuSnapshotsView member visibility --- ui/xui/main-menu.cc | 4 ++-- ui/xui/main-menu.hh | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/ui/xui/main-menu.cc b/ui/xui/main-menu.cc index c99f9ed10b..c14c4bde47 100644 --- a/ui/xui/main-menu.cc +++ b/ui/xui/main-menu.cc @@ -747,7 +747,7 @@ void MainMenuSnapshotsView::ClearSearch() } } -static int MainMenuSnapshotsViewUpdateSearchBox(ImGuiInputTextCallbackData *data) +int MainMenuSnapshotsView::OnSearchTextUpdate(ImGuiInputTextCallbackData *data) { GError *gerr = NULL; MainMenuSnapshotsView *win = (MainMenuSnapshotsView*)data->UserData; @@ -804,7 +804,7 @@ void MainMenuSnapshotsView::Draw() ImGui::PushFont(g_font_mgr.m_menu_font_small); ImGui::InputTextWithHint("##search", "Search or name new snapshot...", &m_search_buf, ImGuiInputTextFlags_CallbackEdit, - &MainMenuSnapshotsViewUpdateSearchBox, this); + &OnSearchTextUpdate, this); bool snapshot_with_create_name_exists = false; for (int i = 0; i < g_snapshot_mgr.m_snapshots_len; ++i) { diff --git a/ui/xui/main-menu.hh b/ui/xui/main-menu.hh index 63c4040fd0..7be564701c 100644 --- a/ui/xui/main-menu.hh +++ b/ui/xui/main-menu.hh @@ -104,20 +104,21 @@ public: class MainMenuSnapshotsView : public virtual MainMenuTabView { protected: + GRegex *m_search_regex; uint32_t m_current_title_id; std::string m_current_title_name; std::string m_search_buf; -private: void ClearSearch(); void DrawSnapshotContextMenu(QEMUSnapshotInfo *snapshot, XemuSnapshotData *data, int current_snapshot_binding); + bool BigSnapshotButton(QEMUSnapshotInfo *snapshot, XemuSnapshotData *data, int current_snapshot_binding); + static int OnSearchTextUpdate(ImGuiInputTextCallbackData *data); public: - GRegex *m_search_regex; MainMenuSnapshotsView(); ~MainMenuSnapshotsView(); - bool BigSnapshotButton(QEMUSnapshotInfo *snapshot, XemuSnapshotData *data, int current_snapshot_binding); void Draw() override; + }; class MainMenuSystemView : public virtual MainMenuTabView From d557a294feb3ecae8ec22adca0909e2d3adcc459 Mon Sep 17 00:00:00 2001 From: Matt Borgerson Date: Sun, 4 Jun 2023 19:35:04 -0700 Subject: [PATCH 34/39] ui: Move dvd path settings update to lower level for now --- ui/xemu.c | 4 ++++ ui/xui/actions.cc | 3 --- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/ui/xemu.c b/ui/xemu.c index 1f009b1104..261dfd93f2 100644 --- a/ui/xemu.c +++ b/ui/xemu.c @@ -1562,6 +1562,7 @@ void xemu_eject_disc(Error **errp) Error *error = NULL; xbox_smc_eject_button(); + xemu_settings_set_string(&g_config.sys.files.dvd_path, ""); // Xbox software may request that the drive open, but do it now anyway qmp_eject(true, "ide0-cd1", false, NULL, true, false, &error); @@ -1578,12 +1579,15 @@ void xemu_load_disc(const char *path, Error **errp) // Ensure an eject sequence is always triggered so Xbox software reloads xbox_smc_eject_button(); + xemu_settings_set_string(&g_config.sys.files.dvd_path, ""); qmp_blockdev_change_medium(true, "ide0-cd1", false, NULL, path, false, "", false, 0, &error); if (error) { error_propagate(errp, error); + } else { + xemu_settings_set_string(&g_config.sys.files.dvd_path, path); } xbox_smc_update_tray_state(); diff --git a/ui/xui/actions.cc b/ui/xui/actions.cc index f6e8cb29ec..dc2c830c50 100644 --- a/ui/xui/actions.cc +++ b/ui/xui/actions.cc @@ -26,8 +26,6 @@ void ActionEjectDisc(void) { - xemu_settings_set_string(&g_config.sys.files.dvd_path, ""); - Error *err = NULL; xemu_eject_disc(&err); if (err) { @@ -48,7 +46,6 @@ void ActionLoadDisc(void) /* Cancelled */ return; } - xemu_settings_set_string(&g_config.sys.files.dvd_path, new_disc_path); xemu_load_disc(new_disc_path, &err); if (err) { From 5cd1e3cbca211c08289275c391cabd6406310731 Mon Sep 17 00:00:00 2001 From: Erik Abair Date: Fri, 15 Jul 2022 23:59:01 -0700 Subject: [PATCH 35/39] nv2a: (Probably partial) handling for `1D7C` Implements handling for the unknown 0x1D7C command in order to match observed behavior in the MS Dashboard and Tenchu: Return from Darkness. Setting 1D7C's low bit appears to disable the line and poly smoothing commands. Fixes #1162 [Test](https://github.com/abaire/nxdk_pgraph_tests/blob/main/src/tests/three_d_primitive_tests.cpp) [HW Results](https://github.com/abaire/nxdk_pgraph_tests_golden_results/wiki/Results-3D_primitive) --- hw/xbox/nv2a/nv2a.c | 1 + hw/xbox/nv2a/nv2a_int.h | 2 ++ hw/xbox/nv2a/nv2a_regs.h | 2 ++ hw/xbox/nv2a/pgraph.c | 19 +++++++++++++++---- hw/xbox/nv2a/pgraph_methods.h | 1 + 5 files changed, 21 insertions(+), 4 deletions(-) diff --git a/hw/xbox/nv2a/nv2a.c b/hw/xbox/nv2a/nv2a.c index e09fc8f890..5aebb1190a 100644 --- a/hw/xbox/nv2a/nv2a.c +++ b/hw/xbox/nv2a/nv2a.c @@ -517,6 +517,7 @@ static const VMStateDescription vmstate_nv2a = { VMSTATE_INT32_ARRAY(pgraph.gl_draw_arrays_start, NV2AState, 1250), VMSTATE_INT32_ARRAY(pgraph.gl_draw_arrays_count, NV2AState, 1250), VMSTATE_UINT32_ARRAY(pgraph.regs, NV2AState, 0x2000), + VMSTATE_BOOL(pgraph.smoothing_enabled, NV2AState), VMSTATE_UINT32(pmc.pending_interrupts, NV2AState), VMSTATE_UINT32(pmc.enabled_interrupts, NV2AState), VMSTATE_UINT32(pfifo.pending_interrupts, NV2AState), diff --git a/hw/xbox/nv2a/nv2a_int.h b/hw/xbox/nv2a/nv2a_int.h index 65233ff265..11e00e71c3 100644 --- a/hw/xbox/nv2a/nv2a_int.h +++ b/hw/xbox/nv2a/nv2a_int.h @@ -360,6 +360,8 @@ typedef struct PGRAPHState { bool ltc1_dirty[NV2A_LTC1_COUNT]; float material_alpha; + // FIXME: Find the correct register for this. + bool smoothing_enabled; // should figure out where these are in lighting context float light_infinite_half_vector[NV2A_MAX_LIGHTS][3]; diff --git a/hw/xbox/nv2a/nv2a_regs.h b/hw/xbox/nv2a/nv2a_regs.h index 12ffad87f9..ba49827983 100644 --- a/hw/xbox/nv2a/nv2a_regs.h +++ b/hw/xbox/nv2a/nv2a_regs.h @@ -1220,6 +1220,8 @@ # define NV097_SET_ZMIN_MAX_CONTROL_ZCLAMP_EN 0x000000F0 # define NV097_SET_ZMIN_MAX_CONTROL_ZCLAMP_EN_CULL 0 # define NV097_SET_ZMIN_MAX_CONTROL_ZCLAMP_EN_CLAMP 1 +# define NV097_SET_SMOOTHING_CONTROL 0x00001D7C +# define NV097_SET_SMOOTHING_CONTROL_DISABLE 0x00000001 # define NV097_SET_ZSTENCIL_CLEAR_VALUE 0x00001D8C # define NV097_SET_COLOR_CLEAR_VALUE 0x00001D90 # define NV097_CLEAR_SURFACE 0x00001D94 diff --git a/hw/xbox/nv2a/pgraph.c b/hw/xbox/nv2a/pgraph.c index eda2ae5693..c6fdb15b14 100644 --- a/hw/xbox/nv2a/pgraph.c +++ b/hw/xbox/nv2a/pgraph.c @@ -3049,14 +3049,16 @@ DEF_METHOD(NV097, SET_BEGIN_END) glEnable(GL_PROGRAM_POINT_SIZE); /* Edge Antialiasing */ - if (pg->regs[NV_PGRAPH_SETUPRASTER] & - NV_PGRAPH_SETUPRASTER_LINESMOOTHENABLE) { + if (pg->smoothing_enabled + && pg->regs[NV_PGRAPH_SETUPRASTER] & + NV_PGRAPH_SETUPRASTER_LINESMOOTHENABLE) { glEnable(GL_LINE_SMOOTH); } else { glDisable(GL_LINE_SMOOTH); } - if (pg->regs[NV_PGRAPH_SETUPRASTER] & - NV_PGRAPH_SETUPRASTER_POLYSMOOTHENABLE) { + if (pg->smoothing_enabled + && pg->regs[NV_PGRAPH_SETUPRASTER] & + NV_PGRAPH_SETUPRASTER_POLYSMOOTHENABLE) { glEnable(GL_POLYGON_SMOOTH); } else { glDisable(GL_POLYGON_SMOOTH); @@ -3457,6 +3459,14 @@ DEF_METHOD(NV097, SET_ZMIN_MAX_CONTROL) } } +DEF_METHOD(NV097, SET_SMOOTHING_CONTROL) +{ + // FIXME: Find the correct register for this. + pg->smoothing_enabled = !GET_MASK(parameter, + NV097_SET_SMOOTHING_CONTROL_DISABLE); + // FIXME: Handle the remaining bits (observed values 0xFFFF0000, 0xFFFF0001) +} + DEF_METHOD(NV097, SET_ZSTENCIL_CLEAR_VALUE) { pg->regs[NV_PGRAPH_ZSTENCILCLEARVALUE] = parameter; @@ -4027,6 +4037,7 @@ void pgraph_init(NV2AState *d) shader_cache_init(pg); pg->material_alpha = 0.0f; + pg->smoothing_enabled = false; SET_MASK(pg->regs[NV_PGRAPH_CONTROL_3], NV_PGRAPH_CONTROL_3_SHADEMODE, NV_PGRAPH_CONTROL_3_SHADEMODE_SMOOTH); pg->primitive_mode = PRIM_TYPE_INVALID; diff --git a/hw/xbox/nv2a/pgraph_methods.h b/hw/xbox/nv2a/pgraph_methods.h index b3de47959d..ab47f9966e 100644 --- a/hw/xbox/nv2a/pgraph_methods.h +++ b/hw/xbox/nv2a/pgraph_methods.h @@ -169,6 +169,7 @@ DEF_METHOD_RANGE(NV097, SET_VERTEX_DATA4S_M, 32) DEF_METHOD(NV097, SET_SEMAPHORE_OFFSET) DEF_METHOD(NV097, BACK_END_WRITE_SEMAPHORE_RELEASE) DEF_METHOD(NV097, SET_ZMIN_MAX_CONTROL) +DEF_METHOD(NV097, SET_SMOOTHING_CONTROL) DEF_METHOD(NV097, SET_ZSTENCIL_CLEAR_VALUE) DEF_METHOD(NV097, SET_COLOR_CLEAR_VALUE) DEF_METHOD(NV097, CLEAR_SURFACE) From bb05a4f18181e45dbf18a09dc0a1a9a509f39e38 Mon Sep 17 00:00:00 2001 From: Matt Borgerson Date: Sun, 11 Jun 2023 17:58:43 -0700 Subject: [PATCH 36/39] nv2a: Fix SET_ANTI_ALIASING_CONTROL - Rename from SET_SMOOTHING_CONTROL - Use correct register --- hw/xbox/nv2a/nv2a.c | 1 - hw/xbox/nv2a/nv2a_int.h | 2 -- hw/xbox/nv2a/nv2a_regs.h | 6 ++++-- hw/xbox/nv2a/pgraph.c | 20 +++++++++----------- hw/xbox/nv2a/pgraph_methods.h | 2 +- 5 files changed, 14 insertions(+), 17 deletions(-) diff --git a/hw/xbox/nv2a/nv2a.c b/hw/xbox/nv2a/nv2a.c index 5aebb1190a..e09fc8f890 100644 --- a/hw/xbox/nv2a/nv2a.c +++ b/hw/xbox/nv2a/nv2a.c @@ -517,7 +517,6 @@ static const VMStateDescription vmstate_nv2a = { VMSTATE_INT32_ARRAY(pgraph.gl_draw_arrays_start, NV2AState, 1250), VMSTATE_INT32_ARRAY(pgraph.gl_draw_arrays_count, NV2AState, 1250), VMSTATE_UINT32_ARRAY(pgraph.regs, NV2AState, 0x2000), - VMSTATE_BOOL(pgraph.smoothing_enabled, NV2AState), VMSTATE_UINT32(pmc.pending_interrupts, NV2AState), VMSTATE_UINT32(pmc.enabled_interrupts, NV2AState), VMSTATE_UINT32(pfifo.pending_interrupts, NV2AState), diff --git a/hw/xbox/nv2a/nv2a_int.h b/hw/xbox/nv2a/nv2a_int.h index 11e00e71c3..65233ff265 100644 --- a/hw/xbox/nv2a/nv2a_int.h +++ b/hw/xbox/nv2a/nv2a_int.h @@ -360,8 +360,6 @@ typedef struct PGRAPHState { bool ltc1_dirty[NV2A_LTC1_COUNT]; float material_alpha; - // FIXME: Find the correct register for this. - bool smoothing_enabled; // should figure out where these are in lighting context float light_infinite_half_vector[NV2A_MAX_LIGHTS][3]; diff --git a/hw/xbox/nv2a/nv2a_regs.h b/hw/xbox/nv2a/nv2a_regs.h index ba49827983..108db8f716 100644 --- a/hw/xbox/nv2a/nv2a_regs.h +++ b/hw/xbox/nv2a/nv2a_regs.h @@ -335,6 +335,8 @@ # define NV_PGRAPH_CHEOPS_OFFSET_PROG_LD_PTR 0x000000FF # define NV_PGRAPH_CHEOPS_OFFSET_CONST_LD_PTR 0x0000FF00 #define NV_PGRAPH_DMA_STATE 0x00001034 +#define NV_PGRAPH_ANTIALIASING 0x00001800 +# define NV_PGRAPH_ANTIALIASING_ENABLE (1 << 0) #define NV_PGRAPH_BLEND 0x00001804 # define NV_PGRAPH_BLEND_EQN 0x00000007 # define NV_PGRAPH_BLEND_EN (1 << 3) @@ -1220,8 +1222,8 @@ # define NV097_SET_ZMIN_MAX_CONTROL_ZCLAMP_EN 0x000000F0 # define NV097_SET_ZMIN_MAX_CONTROL_ZCLAMP_EN_CULL 0 # define NV097_SET_ZMIN_MAX_CONTROL_ZCLAMP_EN_CLAMP 1 -# define NV097_SET_SMOOTHING_CONTROL 0x00001D7C -# define NV097_SET_SMOOTHING_CONTROL_DISABLE 0x00000001 +# define NV097_SET_ANTI_ALIASING_CONTROL 0x00001D7C +# define NV097_SET_ANTI_ALIASING_CONTROL_ENABLE (1 << 0) # define NV097_SET_ZSTENCIL_CLEAR_VALUE 0x00001D8C # define NV097_SET_COLOR_CLEAR_VALUE 0x00001D90 # define NV097_CLEAR_SURFACE 0x00001D94 diff --git a/hw/xbox/nv2a/pgraph.c b/hw/xbox/nv2a/pgraph.c index c6fdb15b14..a9bdb1ab67 100644 --- a/hw/xbox/nv2a/pgraph.c +++ b/hw/xbox/nv2a/pgraph.c @@ -3048,17 +3048,17 @@ DEF_METHOD(NV097, SET_BEGIN_END) glEnable(GL_PROGRAM_POINT_SIZE); + bool anti_aliasing = GET_MASK(pg->regs[NV_PGRAPH_ANTIALIASING], NV_PGRAPH_ANTIALIASING_ENABLE); + /* Edge Antialiasing */ - if (pg->smoothing_enabled - && pg->regs[NV_PGRAPH_SETUPRASTER] & - NV_PGRAPH_SETUPRASTER_LINESMOOTHENABLE) { + if (!anti_aliasing && pg->regs[NV_PGRAPH_SETUPRASTER] & + NV_PGRAPH_SETUPRASTER_LINESMOOTHENABLE) { glEnable(GL_LINE_SMOOTH); } else { glDisable(GL_LINE_SMOOTH); } - if (pg->smoothing_enabled - && pg->regs[NV_PGRAPH_SETUPRASTER] & - NV_PGRAPH_SETUPRASTER_POLYSMOOTHENABLE) { + if (!anti_aliasing && pg->regs[NV_PGRAPH_SETUPRASTER] & + NV_PGRAPH_SETUPRASTER_POLYSMOOTHENABLE) { glEnable(GL_POLYGON_SMOOTH); } else { glDisable(GL_POLYGON_SMOOTH); @@ -3459,11 +3459,10 @@ DEF_METHOD(NV097, SET_ZMIN_MAX_CONTROL) } } -DEF_METHOD(NV097, SET_SMOOTHING_CONTROL) +DEF_METHOD(NV097, SET_ANTI_ALIASING_CONTROL) { - // FIXME: Find the correct register for this. - pg->smoothing_enabled = !GET_MASK(parameter, - NV097_SET_SMOOTHING_CONTROL_DISABLE); + SET_MASK(pg->regs[NV_PGRAPH_ANTIALIASING], NV_PGRAPH_ANTIALIASING_ENABLE, + GET_MASK(parameter, NV097_SET_ANTI_ALIASING_CONTROL_ENABLE)); // FIXME: Handle the remaining bits (observed values 0xFFFF0000, 0xFFFF0001) } @@ -4037,7 +4036,6 @@ void pgraph_init(NV2AState *d) shader_cache_init(pg); pg->material_alpha = 0.0f; - pg->smoothing_enabled = false; SET_MASK(pg->regs[NV_PGRAPH_CONTROL_3], NV_PGRAPH_CONTROL_3_SHADEMODE, NV_PGRAPH_CONTROL_3_SHADEMODE_SMOOTH); pg->primitive_mode = PRIM_TYPE_INVALID; diff --git a/hw/xbox/nv2a/pgraph_methods.h b/hw/xbox/nv2a/pgraph_methods.h index ab47f9966e..380df07ea5 100644 --- a/hw/xbox/nv2a/pgraph_methods.h +++ b/hw/xbox/nv2a/pgraph_methods.h @@ -169,7 +169,7 @@ DEF_METHOD_RANGE(NV097, SET_VERTEX_DATA4S_M, 32) DEF_METHOD(NV097, SET_SEMAPHORE_OFFSET) DEF_METHOD(NV097, BACK_END_WRITE_SEMAPHORE_RELEASE) DEF_METHOD(NV097, SET_ZMIN_MAX_CONTROL) -DEF_METHOD(NV097, SET_SMOOTHING_CONTROL) +DEF_METHOD(NV097, SET_ANTI_ALIASING_CONTROL) DEF_METHOD(NV097, SET_ZSTENCIL_CLEAR_VALUE) DEF_METHOD(NV097, SET_COLOR_CLEAR_VALUE) DEF_METHOD(NV097, CLEAR_SURFACE) From af3f832fd79b5dd4a4cd188a46243bcce41bfb52 Mon Sep 17 00:00:00 2001 From: Fabx <30447649+Fabxx@users.noreply.github.com> Date: Mon, 12 Jun 2023 20:04:02 +0200 Subject: [PATCH 37/39] ui: Get Windows product and build number --- ui/xemu-os-utils-windows.c | 40 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/ui/xemu-os-utils-windows.c b/ui/xemu-os-utils-windows.c index 8fc323746e..58c763e4af 100644 --- a/ui/xemu-os-utils-windows.c +++ b/ui/xemu-os-utils-windows.c @@ -19,13 +19,49 @@ #include "xemu-os-utils.h" #include +#include +#include +#include + +static const char *get_windows_build_info(void) +{ + WCHAR current_build[1024], product_name[1024]; + WCHAR build_size = 1024, product_size = 1024; + + if (RegGetValueW(HKEY_LOCAL_MACHINE, + L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", + L"ProductName", RRF_RT_REG_SZ, (LPVOID)NULL, &product_name, + (LPDWORD)&product_size) != ERROR_SUCCESS) { + return "Windows"; + } + + if ((RegGetValueW(HKEY_LOCAL_MACHINE, + L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", + L"DisplayVersion", RRF_RT_REG_SZ, (LPVOID)NULL, + ¤t_build, (LPDWORD)&build_size) == ERROR_SUCCESS) || + (RegGetValueW(HKEY_LOCAL_MACHINE, + L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", + L"CSDVersion", RRF_RT_REG_SZ, (LPVOID)NULL, + ¤t_build, (LPDWORD)&build_size) == ERROR_SUCCESS)) { + return g_strdup_printf("%ls %ls", product_name, current_build); + } + + return g_strdup_printf("%ls", product_name); +} const char *xemu_get_os_info(void) { - return "Windows"; + static const char *buffer = NULL; + + if (buffer == NULL) { + buffer = get_windows_build_info(); + } + + return buffer; } + void xemu_open_web_browser(const char *url) { - ShellExecute(0, "open", url, 0, 0 , SW_SHOW); + ShellExecute(0, "open", url, 0, 0, SW_SHOW); } From 129c48dd6e5306a96950902758a89c352dfd7520 Mon Sep 17 00:00:00 2001 From: mborgerson Date: Wed, 14 Jun 2023 03:36:47 -0700 Subject: [PATCH 38/39] ui: Blank screen when VGA SCREEN_OFF is set --- hw/xbox/nv2a/nv2a.h | 1 + hw/xbox/nv2a/pgraph.c | 5 +++++ ui/xui/gl-helpers.cc | 5 ++++- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/hw/xbox/nv2a/nv2a.h b/hw/xbox/nv2a/nv2a.h index 9e3b0206f6..35b63749e4 100644 --- a/hw/xbox/nv2a/nv2a.h +++ b/hw/xbox/nv2a/nv2a.h @@ -27,5 +27,6 @@ int nv2a_get_framebuffer_surface(void); void nv2a_set_surface_scale_factor(unsigned int scale); unsigned int nv2a_get_surface_scale_factor(void); const uint8_t *nv2a_get_dac_palette(void); +int nv2a_get_screen_off(void); #endif diff --git a/hw/xbox/nv2a/pgraph.c b/hw/xbox/nv2a/pgraph.c index a9bdb1ab67..f40fcaf689 100644 --- a/hw/xbox/nv2a/pgraph.c +++ b/hw/xbox/nv2a/pgraph.c @@ -5350,6 +5350,11 @@ const uint8_t *nv2a_get_dac_palette(void) return g_nv2a->puserdac.palette; } +int nv2a_get_screen_off(void) +{ + return g_nv2a->vga.sr[VGA_SEQ_CLOCK_MODE] & VGA_SR01_SCREEN_OFF; +} + int nv2a_get_framebuffer_surface(void) { NV2AState *d = g_nv2a; diff --git a/ui/xui/gl-helpers.cc b/ui/xui/gl-helpers.cc index 996f263726..12e7eb6077 100644 --- a/ui/xui/gl-helpers.cc +++ b/ui/xui/gl-helpers.cc @@ -700,7 +700,10 @@ void RenderFramebuffer(GLint tex, int width, int height, bool flip, float scale[ glClearColor(0, 0, 0, 0); glClear(GL_COLOR_BUFFER_BIT); - glDrawElements(GL_TRIANGLE_FAN, 4, GL_UNSIGNED_INT, NULL); + + if (!nv2a_get_screen_off()) { + glDrawElements(GL_TRIANGLE_FAN, 4, GL_UNSIGNED_INT, NULL); + } } void RenderFramebuffer(GLint tex, int width, int height, bool flip) From 0ee7502c23b3c7c964c5295ff283284bfc1526d2 Mon Sep 17 00:00:00 2001 From: Matt Borgerson Date: Sat, 17 Jun 2023 23:32:18 -0700 Subject: [PATCH 39/39] ui: Make aspect ratio config independent, add autodetect --- config_spec.yml | 6 +++++- dtc | 2 +- hw/xbox/acpi_xbox.c | 9 +++++++++ meson | 2 +- ui/meson.build | 1 + ui/xemu-widescreen.c | 37 +++++++++++++++++++++++++++++++++++++ ui/xemu-widescreen.h | 40 ++++++++++++++++++++++++++++++++++++++++ ui/xui/gl-helpers.cc | 37 ++++++++++++++++++++----------------- ui/xui/main-menu.cc | 8 ++++++-- ui/xui/menubar.cc | 5 +++-- ui/xui/popup-menu.cc | 29 ++++++++++++++++++++++++++++- 11 files changed, 151 insertions(+), 25 deletions(-) create mode 100644 ui/xemu-widescreen.c create mode 100644 ui/xemu-widescreen.h diff --git a/config_spec.yml b/config_spec.yml index c43f5732f8..8410264e61 100644 --- a/config_spec.yml +++ b/config_spec.yml @@ -138,8 +138,12 @@ display: default: true fit: type: enum - values: [center, scale, scale_16_9, scale_4_3, stretch] + values: [center, scale, stretch] default: scale + aspect_ratio: + type: enum + values: [native, auto, 4x3, 16x9] + default: auto scale: type: integer default: 1 diff --git a/dtc b/dtc index 85e5d83984..b6910bec11 160000 --- a/dtc +++ b/dtc @@ -1 +1 @@ -Subproject commit 85e5d839847af54efab170f2b1331b2a6421e647 +Subproject commit b6910bec11614980a21e46fbccc35934b671bd81 diff --git a/hw/xbox/acpi_xbox.c b/hw/xbox/acpi_xbox.c index 39b1dc5e87..3ff0e16f54 100644 --- a/hw/xbox/acpi_xbox.c +++ b/hw/xbox/acpi_xbox.c @@ -30,6 +30,7 @@ #include "hw/xbox/xbox_pci.h" #include "hw/xbox/acpi_xbox.h" #include "migration/vmstate.h" +#include "ui/xemu-widescreen.h" // #define DEBUG #ifdef DEBUG @@ -44,6 +45,8 @@ #define XBOX_PM_GPIO_BASE 0xC0 #define XBOX_PM_GPIO_LEN 26 +#define XBOX_PM_GPIO_ASPECT_RATIO 0x16 + static int field_pin; static uint64_t xbox_pm_gpio_read(void *opaque, hwaddr addr, unsigned width) @@ -66,6 +69,12 @@ static void xbox_pm_gpio_write(void *opaque, hwaddr addr, uint64_t val, unsigned width) { XBOX_DPRINTF("pm gpio write [0x%llx] = 0x%llx\n", addr, val); + + if (addr == XBOX_PM_GPIO_ASPECT_RATIO) { + xemu_set_widescreen(val == 5); + } + + // FIXME: Add GPIO to VM state } static const MemoryRegionOps xbox_pm_gpio_ops = { diff --git a/meson b/meson index 776acd2a80..3a9b285a55 160000 --- a/meson +++ b/meson @@ -1 +1 @@ -Subproject commit 776acd2a805c9b42b4f0375150977df42130317f +Subproject commit 3a9b285a55b91b53b2acda987192274352ecb5be diff --git a/ui/meson.build b/ui/meson.build index 8306d0857b..9b54489518 100644 --- a/ui/meson.build +++ b/ui/meson.build @@ -28,6 +28,7 @@ xemu_ss.add(files( 'xemu-data.c', 'xemu-snapshots.c', 'xemu-thumbnail.cc', + 'xemu-widescreen.c', )) subdir('xui') diff --git a/ui/xemu-widescreen.c b/ui/xemu-widescreen.c new file mode 100644 index 0000000000..bde81a3853 --- /dev/null +++ b/ui/xemu-widescreen.c @@ -0,0 +1,37 @@ +/* + * xemu wide screen handler + * + * Copyright (c) 2023 Matt Borgerson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "xemu-widescreen.h" + +static bool g_widescreen = false; + +void xemu_set_widescreen(bool widescreen) +{ + g_widescreen = widescreen; +} + +bool xemu_get_widescreen(void) +{ + return g_widescreen; +} diff --git a/ui/xemu-widescreen.h b/ui/xemu-widescreen.h new file mode 100644 index 0000000000..02da430ef9 --- /dev/null +++ b/ui/xemu-widescreen.h @@ -0,0 +1,40 @@ +/* + * xemu wide screen handler + * + * Copyright (c) 2023 Matt Borgerson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef XEMU_WIDESCREEN +#define XEMU_WIDESCREEN + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +void xemu_set_widescreen(bool widescreen); +bool xemu_get_widescreen(void); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/ui/xui/gl-helpers.cc b/ui/xui/gl-helpers.cc index 12e7eb6077..4155ff2097 100644 --- a/ui/xui/gl-helpers.cc +++ b/ui/xui/gl-helpers.cc @@ -28,6 +28,7 @@ #include "ui/shader/xemu-logo-frag.h" #include "data/xemu_64x64.png.h" #include "notifications.hh" +#include "ui/xemu-widescreen.h" Fbo *controller_fbo, *logo_fbo; @@ -706,6 +707,21 @@ void RenderFramebuffer(GLint tex, int width, int height, bool flip, float scale[ } } +float GetDisplayAspectRatio(int width, int height) +{ + switch (g_config.display.ui.aspect_ratio) { + case CONFIG_DISPLAY_UI_ASPECT_RATIO_NATIVE: + return (float)width/(float)height; + case CONFIG_DISPLAY_UI_ASPECT_RATIO_16X9: + return 16.0f/9.0f; + case CONFIG_DISPLAY_UI_ASPECT_RATIO_4X3: + return 4.0f/3.0f; + case CONFIG_DISPLAY_UI_ASPECT_RATIO_AUTO: + default: + return xemu_get_widescreen() ? 16.0f/9.0f : 4.0f/3.0f; + } +} + void RenderFramebuffer(GLint tex, int width, int height, bool flip) { int tw, th; @@ -723,20 +739,11 @@ void RenderFramebuffer(GLint tex, int width, int height, bool flip) scale[1] = 1.0; } else if (g_config.display.ui.fit == CONFIG_DISPLAY_UI_FIT_CENTER) { // Centered - scale[0] = (float)tw/(float)width; + float t_ratio = GetDisplayAspectRatio(tw, th); + scale[0] = t_ratio*(float)th/(float)width; scale[1] = (float)th/(float)height; } else { - float t_ratio; - if (g_config.display.ui.fit == CONFIG_DISPLAY_UI_FIT_SCALE_16_9) { - // Scale to fit window using a fixed 16:9 aspect ratio - t_ratio = 16.0f/9.0f; - } else if (g_config.display.ui.fit == CONFIG_DISPLAY_UI_FIT_SCALE_4_3) { - t_ratio = 4.0f/3.0f; - } else { - // Scale to fit, preserving framebuffer aspect ratio - t_ratio = (float)tw/(float)th; - } - + float t_ratio = GetDisplayAspectRatio(tw, th); float w_ratio = (float)width/(float)height; if (w_ratio >= t_ratio) { scale[0] = t_ratio/w_ratio; @@ -759,11 +766,7 @@ bool RenderFramebufferToPng(GLuint tex, bool flip, std::vector &png, in glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &width); glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &height); - if (g_config.display.ui.fit == CONFIG_DISPLAY_UI_FIT_SCALE_16_9) { - width = height * (16.0f / 9.0f); - } else if (g_config.display.ui.fit == CONFIG_DISPLAY_UI_FIT_SCALE_4_3) { - width = height * (4.0f / 3.0f); - } + width = height * GetDisplayAspectRatio(width, height); if (!max_width) max_width = width; if (!max_height) max_height = height; diff --git a/ui/xui/main-menu.cc b/ui/xui/main-menu.cc index c14c4bde47..2940d10760 100644 --- a/ui/xui/main-menu.cc +++ b/ui/xui/main-menu.cc @@ -340,10 +340,14 @@ void MainMenuDisplayView::Draw() ChevronCombo("Display mode", &g_config.display.ui.fit, "Center\0" "Scale\0" - "Scale (Widescreen 16:9)\0" - "Scale (4:3)\0" "Stretch\0", "Select how the framebuffer should fit or scale into the window"); + ChevronCombo("Aspect ratio", &g_config.display.ui.aspect_ratio, + "Native\0" + "Auto (Default)\0" + "4:3\0" + "16:9\0", + "Select the displayed aspect ratio"); } void MainMenuAudioView::Draw() diff --git a/ui/xui/menubar.cc b/ui/xui/menubar.cc index 71c2eab69a..9ce8a4a0c7 100644 --- a/ui/xui/menubar.cc +++ b/ui/xui/menubar.cc @@ -191,11 +191,12 @@ void ShowMainMenu() } ImGui::Combo("Display Mode", &g_config.display.ui.fit, - "Center\0Scale\0Scale (Widescreen 16:9)\0Scale " - "(4:3)\0Stretch\0"); + "Center\0Scale\0Stretch\0"); ImGui::SameLine(); HelpMarker("Controls how the rendered content should be scaled " "into the window"); + ImGui::Combo("Aspect Ratio", &g_config.display.ui.aspect_ratio, + "Native\0Auto\0""4:3\0""16:9\0"); if (ImGui::MenuItem("Fullscreen", SHORTCUT_MENU_TEXT(Alt + F), xemu_is_fullscreen(), true)) { xemu_toggle_fullscreen(); diff --git a/ui/xui/popup-menu.cc b/ui/xui/popup-menu.cc index cf6fc3f130..dcaf5e9b94 100644 --- a/ui/xui/popup-menu.cc +++ b/ui/xui/popup-menu.cc @@ -258,7 +258,7 @@ public: bool DrawItems(PopupMenuItemDelegate &nav) override { const char *values[] = { - "Center", "Scale", "Scale (Widescreen 16:9)", "Scale (4:3)", "Stretch" + "Center", "Scale", "Stretch" }; for (int i = 0; i < CONFIG_DISPLAY_UI_FIT__COUNT; i++) { @@ -272,11 +272,34 @@ public: } }; +class AspectRatioPopupMenu : public virtual PopupMenu { +public: + bool DrawItems(PopupMenuItemDelegate &nav) override + { + const char *values[] = { + "Native", + "Auto (Default)", + "4:3", + "16:9" + }; + + for (int i = 0; i < CONFIG_DISPLAY_UI_ASPECT_RATIO__COUNT; i++) { + bool selected = g_config.display.ui.aspect_ratio == i; + if (m_focus && selected) ImGui::SetKeyboardFocusHere(); + if (PopupMenuCheck(values[i], "", selected)) + g_config.display.ui.aspect_ratio = i; + } + + return false; + } +}; + extern MainMenuScene g_main_menu; class SettingsPopupMenu : public virtual PopupMenu { protected: DisplayModePopupMenu display_mode; + AspectRatioPopupMenu aspect_ratio; public: bool DrawItems(PopupMenuItemDelegate &nav) override @@ -295,6 +318,10 @@ public: nav.PushFocus(); nav.PushMenu(display_mode); } + if (PopupMenuSubmenuButton("Aspect Ratio", ICON_FA_EXPAND)) { + nav.PushFocus(); + nav.PushMenu(aspect_ratio); + } if (PopupMenuButton("Snapshots...", ICON_FA_CLOCK_ROTATE_LEFT)) { nav.ClearMenuStack(); g_scene_mgr.PushScene(g_main_menu);