diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000..4c39a334be --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,8 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + commit-message: + prefix: "ci" diff --git a/.github/workflows/build-xemu-win64-toolchain.yml b/.github/workflows/build-xemu-win64-toolchain.yml index deefd9c070..4257895759 100644 --- a/.github/workflows/build-xemu-win64-toolchain.yml +++ b/.github/workflows/build-xemu-win64-toolchain.yml @@ -21,10 +21,10 @@ jobs: runs-on: ubuntu-latest steps: - name: Clone tree - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - name: Extract image metadata (tags, labels) id: meta - uses: docker/metadata-action@v5 + uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} labels: | @@ -35,16 +35,16 @@ jobs: type=ref,event=branch type=sha - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3 - name: Login to GitHub Container Registry - uses: docker/login-action@v3 + uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3 if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push image - uses: docker/build-push-action@v5 + uses: docker/build-push-action@1dc73863535b631f98b2378be8619f83b136f4a0 # v5 with: context: ubuntu-win64-cross push: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b294100a46..967c48bb23 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -20,7 +20,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Clone tree - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 with: fetch-depth: 0 - name: Install dependencies @@ -50,7 +50,7 @@ jobs: ./scripts/archive-source.sh src.tar gzip -1 src.tar - name: Upload source package artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: src.tar.gz path: src.tar.gz @@ -79,18 +79,18 @@ jobs: artifact_name: xemu-win-aarch64-release arch: aarch64 env: - DOCKER_IMAGE_NAME: ghcr.io/xemu-project/xemu-win64-toolchain:sha-3bdb9e7 + DOCKER_IMAGE_NAME: ghcr.io/xemu-project/xemu-win64-toolchain:sha-0d06ce8 steps: - name: Download source package - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: src.tar.gz - name: Extract source package run: tar xf src.tar.gz - name: Initialize compiler cache id: cache - uses: actions/cache@v4 + uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4 with: path: /tmp/xemu-ccache key: cache-wincross-${{ runner.os }}-${{ matrix.arch }}-${{ matrix.configuration }}-${{ github.sha }} @@ -111,7 +111,7 @@ jobs: $DOCKER_IMAGE_NAME \ bash -c "ccache -z; ./build.sh -p win64-cross ${{ matrix.build_param }} && ccache -s" - name: Upload build artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: ${{ matrix.artifact_name }} path: dist @@ -140,7 +140,7 @@ jobs: arch: aarch64 steps: - name: Download artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: ${{ matrix.artifact_name }} path: ${{ matrix.artifact_name }} @@ -157,7 +157,7 @@ jobs: 7z a -tzip ../dist/${{ matrix.artifact_name }}.zip * "-xr!*.pdb" 7z a -tzip ../dist/${{ matrix.artifact_name }}-pdb.zip "-ir!*.pdb" - name: Upload build artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: ${{ matrix.artifact_name }}-pdb path: dist @@ -196,13 +196,13 @@ jobs: steps: - name: Initialize compiler cache id: cache - uses: actions/cache@v4 + uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4 with: path: /tmp/xemu-ccache key: cache-${{ runner.os }}-${{ matrix.arch }}-${{ matrix.configuration }}-${{ github.sha }} restore-keys: cache-${{ runner.os }}-${{ matrix.arch }}-${{ matrix.configuration }}- - name: Download source package - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: src.tar.gz - name: Extract source package @@ -245,6 +245,11 @@ jobs: echo -e "\n\nCompiler Cache Stats:" ccache -s + - name: Test + run: | + pushd src/build + ./pyvenv/bin/meson test --suite xbox + popd - name: Generate AppImage run: | wget --no-verbose https://github.com/linuxdeploy/linuxdeploy/releases/latest/download/linuxdeploy-${{ matrix.arch }}.AppImage @@ -266,7 +271,7 @@ jobs: run: | tar -czvf ${{ matrix.artifact_filename }} --transform "s#^dist#xemu#" dist - name: Upload build artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: ${{ matrix.artifact_name }} path: ${{ matrix.artifact_filename }} @@ -300,12 +305,12 @@ jobs: artifact_filename: xemu-macos-arm64-release.zip steps: - name: Download source package - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: src.tar.gz - name: Extract source package run: tar xf src.tar.gz - - uses: actions/setup-python@v5.1.0 + - uses: actions/setup-python@v5.6.0 with: python-version: '3.12' - name: Install dependencies @@ -319,7 +324,7 @@ jobs: pip install pyyaml requests - name: Initialize compiler, library cache id: cache - uses: actions/cache@v4 + uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4 with: path: | xemu-ccache @@ -339,7 +344,7 @@ jobs: zip -r ../${{ matrix.artifact_filename }} * popd - name: Upload build artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: ${{ matrix.artifact_name }} path: ${{ matrix.artifact_filename }} @@ -353,12 +358,12 @@ jobs: configuration: ["debug", "release"] steps: - name: Download x86_64 build - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: xemu-macos-x86_64-${{ matrix.configuration }} path: xemu-macos-x86_64-${{ matrix.configuration }} - name: Download arm64 build - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: xemu-macos-arm64-${{ matrix.configuration }} path: xemu-macos-arm64-${{ matrix.configuration }} @@ -382,7 +387,7 @@ jobs: zip -r ../xemu-macos-universal-${{ matrix.configuration }}.zip * popd - name: Upload build artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: xemu-macos-universal-${{ matrix.configuration }} path: xemu-macos-universal-${{ matrix.configuration }}.zip @@ -393,7 +398,7 @@ jobs: needs: [Ubuntu, macOSUniversal, Windows, WindowsPdb] steps: - name: Download artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: path: dist - name: Extract source package @@ -415,7 +420,7 @@ jobs: run: | cp dist/xemu-win-x86_64-release-pdb/xemu-win-x86_64-release.zip dist/xemu-win-x86_64-release-pdb/xemu-win-release.zip - name: Publish release - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@da05d552573ad5aba039eaac05058a918a7bf631 # v2.2.2 with: tag_name: v${{ env.XEMU_VERSION }} name: v${{ env.XEMU_VERSION }} @@ -439,7 +444,7 @@ jobs: dist/xemu-ubuntu-aarch64-debug/xemu/xemu-v${{ env.XEMU_VERSION }}-dbg-aarch64.AppImage dist/xemu-ubuntu-aarch64-release/xemu/xemu-v${{ env.XEMU_VERSION }}-aarch64.AppImage - name: Trigger website update - uses: benc-uk/workflow-dispatch@v1.2.2 + uses: benc-uk/workflow-dispatch@e2e5e9a103e331dad343f381a29e654aea3cf8fc # v1.2.4 with: workflow: build.yml repo: xemu-project/xemu-website @@ -457,7 +462,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Download source package - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: src.tar.gz - name: Extract source package @@ -476,7 +481,7 @@ jobs: -- Matt Borgerson $(date -R)" > debian/changelog popd - name: Deploy source archive to branch - uses: peaceiris/actions-gh-pages@v3 + uses: peaceiris/actions-gh-pages@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e # v3 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./src diff --git a/.github/workflows/bump-subproject-wraps.yml b/.github/workflows/bump-subproject-wraps.yml new file mode 100644 index 0000000000..ff7740d78f --- /dev/null +++ b/.github/workflows/bump-subproject-wraps.yml @@ -0,0 +1,74 @@ +name: Bump Meson subprojects + +on: + workflow_dispatch: + schedule: + - cron: '0 6 * * 1' + +permissions: + contents: write + pull-requests: write + +jobs: + bump_wraps: + name: "Bump Meson subprojects" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + + - name: Install the latest version of uv + uses: astral-sh/setup-uv@6b9c6063abd6010835644d4c2e1bef4cf5cd0fca # v6 + with: + enable-cache: false + + - name: Check for updates + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + uv run -s scripts/bump-subproject-wraps.py -m \ + subprojects/curl.wrap \ + subprojects/genconfig.wrap \ + subprojects/glslang.wrap \ + subprojects/imgui.wrap \ + subprojects/implot.wrap \ + subprojects/json.wrap \ + subprojects/nv2a_vsh_cpu.wrap \ + subprojects/SPIRV-Reflect.wrap \ + subprojects/tomlplusplus.wrap \ + subprojects/volk.wrap \ + subprojects/VulkanMemoryAllocator.wrap \ + subprojects/xxhash.wrap \ + > updated.json + + - name: Create PRs for updates + env: + GH_TOKEN: ${{ secrets.XEMU_ROBOT_TOKEN }} + run: | + set -euo pipefail + git config user.name "xemu-robot" + git config user.email "robot@xemu.app" + + jq -c '.[]' updated.json | while read -r item; do + path=$(echo "$item" | jq -r '.path') + file_basename=$(basename "$path") + name="${file_basename%%.*}" + + owner=$(echo "$item" | jq -r '.owner') + repo=$(echo "$item" | jq -r '.repo') + old_rev=$(echo "$item" | jq -r '.old_rev') + new_rev=$(echo "$item" | jq -r '.new_rev') + new_tag=$(echo "$item" | jq -r '.new_tag') + + echo "➤ Processing $name" + branch="sync/bump-${name//\//-}-${GITHUB_RUN_ID}" + + git switch --quiet -c "$branch" origin/master + git add "$path" + git commit -m "meson: Bump ${name} to ${new_tag}" + git push -u origin "$branch" + + gh pr create \ + --title "meson: Bump ${name} to ${new_tag}" \ + --body "Automatic bump of \`${name}\` to [${new_tag}](https://github.com/${owner}/${repo}/compare/${old_rev}..${new_rev})." \ + --base master + done diff --git a/.gitignore b/.gitignore index bfac196fad..e1927ae154 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,6 @@ .sdk .stgit-* .git-submodule-status -.clang-format .gdb_history cscope.* tags diff --git a/build.sh b/build.sh index d84b67ee4b..81fca2fb59 100755 --- a/build.sh +++ b/build.sh @@ -66,8 +66,14 @@ 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 + if [[ -e "${project_source_dir}/XEMU_VERSION" ]]; then + xemu_version="$(cat ${project_source_dir}/XEMU_VERSION | cut -f1 -d-)" + else + xemu_version="0.0.0" + fi + + plutil -replace CFBundleShortVersionString -string "${xemu_version}" dist/xemu.app/Contents/Info.plist + plutil -replace CFBundleVersion -string "${xemu_version}" 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 diff --git a/config_spec.yml b/config_spec.yml index d013c806ac..e95e6bddf7 100644 --- a/config_spec.yml +++ b/config_spec.yml @@ -54,6 +54,9 @@ input: auto_bind: type: bool default: true + allow_vibration: + type: bool + default: true background_input_capture: bool keyboard_controller_scancode_map: # Scancode reference : https://github.com/libsdl-org/SDL/blob/main/include/SDL_scancode.h diff --git a/debian/control b/debian/control index 3712442488..f4fef2cba7 100644 --- a/debian/control +++ b/debian/control @@ -6,6 +6,8 @@ Build-Depends: debhelper (>= 11), cmake, git, python3:any, + python3-pip, + python3-tomli, python3-yaml, python3-venv, ninja-build, diff --git a/hw/xbox/mcpx/apu.c b/hw/xbox/mcpx/apu.c index ecace96ce3..338e132956 100644 --- a/hw/xbox/mcpx/apu.c +++ b/hw/xbox/mcpx/apu.c @@ -154,6 +154,14 @@ typedef struct MCPXAPUState { int16_t apu_fifo_output[256][2]; // 1 EP frame (0x400 bytes), 8 buffered } MCPXAPUState; +static const struct { + hwaddr top, current, next; +} voice_list_regs[] = { + { NV_PAPU_TVL2D, NV_PAPU_CVL2D, NV_PAPU_NVL2D }, // 2D + { NV_PAPU_TVL3D, NV_PAPU_CVL3D, NV_PAPU_NVL3D }, // 3D + { NV_PAPU_TVLMP, NV_PAPU_CVLMP, NV_PAPU_NVLMP }, // MP +}; + static MCPXAPUState *g_state; // Used via debug handlers static struct McpxApuDebug g_dbg, g_dbg_cache; static int g_dbg_voice_monitor = -1; @@ -2571,7 +2579,7 @@ const VMStateDescription vmstate_vp_dsp_core_state = { VMSTATE_UINT32(num_inst, dsp_core_t), VMSTATE_UINT32(cur_inst_len, dsp_core_t), VMSTATE_UINT32(cur_inst, dsp_core_t), - VMSTATE_BOOL(executing_for_disasm, dsp_core_t), + VMSTATE_UNUSED(1), VMSTATE_UINT32(disasm_memory_ptr, dsp_core_t), VMSTATE_BOOL(exception_debugging, dsp_core_t), VMSTATE_UINT32(disasm_prev_inst_pc, dsp_core_t), diff --git a/hw/xbox/mcpx/apu_regs.h b/hw/xbox/mcpx/apu_regs.h index 7488c2e267..d745a69354 100644 --- a/hw/xbox/mcpx/apu_regs.h +++ b/hw/xbox/mcpx/apu_regs.h @@ -22,6 +22,8 @@ #ifndef MCPX_APU_REGS_H #define MCPX_APU_REGS_H +// clang-format off + #define NV_PAPU_ISTS 0x00001000 # define NV_PAPU_ISTS_GINTSTS (1 << 0) # define NV_PAPU_ISTS_FETINTSTS (1 << 4) @@ -122,15 +124,6 @@ #define NV_PAPU_EPPMEM 0x0000A000 #define NV_PAPU_EPRST 0x0000FFFC -static const struct { - hwaddr top, current, next; -} voice_list_regs[] = { - {NV_PAPU_TVL2D, NV_PAPU_CVL2D, NV_PAPU_NVL2D}, //2D - {NV_PAPU_TVL3D, NV_PAPU_CVL3D, NV_PAPU_NVL3D}, //3D - {NV_PAPU_TVLMP, NV_PAPU_CVLMP, NV_PAPU_NVLMP}, //MP -}; - - /* audio processor object / front-end messages */ #define NV1BA0_PIO_FREE 0x00000010 #define NV1BA0_PIO_SET_ANTECEDENT_VOICE 0x00000120 @@ -343,4 +336,6 @@ enum MCPX_HW_NOTIFIER { #define NV1BA0_NOTIFICATION_STATUS_DONE_SUCCESS 0x01 #define NV1BA0_NOTIFICATION_STATUS_IN_PROGRESS 0x80 +// clang-format on + #endif diff --git a/hw/xbox/mcpx/dsp/debug.c b/hw/xbox/mcpx/dsp/debug.c new file mode 100644 index 0000000000..e41a222bf9 --- /dev/null +++ b/hw/xbox/mcpx/dsp/debug.c @@ -0,0 +1,299 @@ +/* + * MCPX DSP emulator + * + * Copyright (c) 2015 espes + * Copyright (c) 2020-2025 Matt Borgerson + * + * Adapted from Hatari DSP M56001 emulation + * (C) 2001-2008 ARAnyM developer team + * Adaption to Hatari (C) 2008 by Thomas Huth + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "qemu/osdep.h" + +#include +#include +#include +#include + +#include "dsp_cpu.h" +#include "dsp_dma.h" +#include "dsp_state.h" + +#include "dsp.h" +#include "debug.h" + +#if DEBUG_DSP +#define BITMASK(x) ((1<<(x))-1) + +/** + * Output memory values between given addresses in given DSP address space. + * Return next DSP address value. + */ +uint32_t dsp_disasm_memory(DSPState* dsp, uint32_t dsp_memdump_addr, uint32_t dsp_memdump_upper, char space) +{ + uint32_t mem, value; + + for (mem = dsp_memdump_addr; mem <= dsp_memdump_upper; mem++) { + value = dsp_read_memory(dsp, space, mem); + printf("%04x %06x\n", mem, value); + } + return dsp_memdump_upper+1; +} + +/** + * Show information on DSP core state which isn't + * shown by any of the other commands (dd, dm, dr). + */ +void dsp_info(DSPState* dsp) +{ + int i, j; + const char *stackname[] = { "SSH", "SSL" }; + + printf("DSP core information:\n"); + + for (i = 0; i < ARRAY_SIZE(stackname); i++) { + printf("- %s stack:", stackname[i]); + for (j = 0; j < ARRAY_SIZE(dsp->core.stack[0]); j++) { + printf(" %04x", dsp->core.stack[i][j]); + } + printf("\n"); + } + + printf("- Interrupt IPL:"); + for (i = 0; i < ARRAY_SIZE(dsp->core.interrupt_ipl); i++) { + printf(" %04x", dsp->core.interrupt_ipl[i]); + } + printf("\n"); + + printf("- Pending ints: "); + for (i = 0; i < ARRAY_SIZE(dsp->core.interrupt_is_pending); i++) { + printf(" %04hx", dsp->core.interrupt_is_pending[i]); + } + printf("\n"); +} + +/** + * Show DSP register contents + */ +void dsp_print_registers(DSPState* dsp) +{ + uint32_t i; + + printf("A: A2: %02x A1: %06x A0: %06x\n", + dsp->core.registers[DSP_REG_A2], dsp->core.registers[DSP_REG_A1], dsp->core.registers[DSP_REG_A0]); + printf("B: B2: %02x B1: %06x B0: %06x\n", + dsp->core.registers[DSP_REG_B2], dsp->core.registers[DSP_REG_B1], dsp->core.registers[DSP_REG_B0]); + + printf("X: X1: %06x X0: %06x\n", dsp->core.registers[DSP_REG_X1], dsp->core.registers[DSP_REG_X0]); + printf("Y: Y1: %06x Y0: %06x\n", dsp->core.registers[DSP_REG_Y1], dsp->core.registers[DSP_REG_Y0]); + + for (i=0; i<8; i++) { + printf("R%01x: %04x N%01x: %04x M%01x: %04x\n", + i, dsp->core.registers[DSP_REG_R0+i], + i, dsp->core.registers[DSP_REG_N0+i], + i, dsp->core.registers[DSP_REG_M0+i]); + } + + printf("LA: %04x LC: %04x PC: %04x\n", dsp->core.registers[DSP_REG_LA], dsp->core.registers[DSP_REG_LC], dsp->core.pc); + printf("SR: %04x OMR: %02x\n", dsp->core.registers[DSP_REG_SR], dsp->core.registers[DSP_REG_OMR]); + printf("SP: %02x SSH: %04x SSL: %04x\n", + dsp->core.registers[DSP_REG_SP], dsp->core.registers[DSP_REG_SSH], dsp->core.registers[DSP_REG_SSL]); +} + + +/** + * Get given DSP register address and required bit mask. + * Works for A0-2, B0-2, LA, LC, M0-7, N0-7, R0-7, X0-1, Y0-1, PC, SR, SP, + * OMR, SSH & SSL registers, but note that the SP, SSH & SSL registers + * need special handling (in DSP*SetRegister()) when they are set. + * Return the register width in bits or zero for an error. + */ +int dsp_get_register_address(DSPState* dsp, const char *regname, uint32_t **addr, uint32_t *mask) +{ +#define MAX_REGNAME_LEN 4 + typedef struct { + const char name[MAX_REGNAME_LEN]; + uint32_t *addr; + size_t bits; + uint32_t mask; + } reg_addr_t; + + /* sorted by name so that this can be bisected */ + const reg_addr_t registers[] = { + + /* 56-bit A register */ + { "A0", &dsp->core.registers[DSP_REG_A0], 32, BITMASK(24) }, + { "A1", &dsp->core.registers[DSP_REG_A1], 32, BITMASK(24) }, + { "A2", &dsp->core.registers[DSP_REG_A2], 32, BITMASK(8) }, + + /* 56-bit B register */ + { "B0", &dsp->core.registers[DSP_REG_B0], 32, BITMASK(24) }, + { "B1", &dsp->core.registers[DSP_REG_B1], 32, BITMASK(24) }, + { "B2", &dsp->core.registers[DSP_REG_B2], 32, BITMASK(8) }, + + /* 16-bit LA & LC registers */ + { "LA", &dsp->core.registers[DSP_REG_LA], 32, BITMASK(16) }, + { "LC", &dsp->core.registers[DSP_REG_LC], 32, BITMASK(16) }, + + /* 16-bit M registers */ + { "M0", &dsp->core.registers[DSP_REG_M0], 32, BITMASK(16) }, + { "M1", &dsp->core.registers[DSP_REG_M1], 32, BITMASK(16) }, + { "M2", &dsp->core.registers[DSP_REG_M2], 32, BITMASK(16) }, + { "M3", &dsp->core.registers[DSP_REG_M3], 32, BITMASK(16) }, + { "M4", &dsp->core.registers[DSP_REG_M4], 32, BITMASK(16) }, + { "M5", &dsp->core.registers[DSP_REG_M5], 32, BITMASK(16) }, + { "M6", &dsp->core.registers[DSP_REG_M6], 32, BITMASK(16) }, + { "M7", &dsp->core.registers[DSP_REG_M7], 32, BITMASK(16) }, + + /* 16-bit N registers */ + { "N0", &dsp->core.registers[DSP_REG_N0], 32, BITMASK(16) }, + { "N1", &dsp->core.registers[DSP_REG_N1], 32, BITMASK(16) }, + { "N2", &dsp->core.registers[DSP_REG_N2], 32, BITMASK(16) }, + { "N3", &dsp->core.registers[DSP_REG_N3], 32, BITMASK(16) }, + { "N4", &dsp->core.registers[DSP_REG_N4], 32, BITMASK(16) }, + { "N5", &dsp->core.registers[DSP_REG_N5], 32, BITMASK(16) }, + { "N6", &dsp->core.registers[DSP_REG_N6], 32, BITMASK(16) }, + { "N7", &dsp->core.registers[DSP_REG_N7], 32, BITMASK(16) }, + + { "OMR", &dsp->core.registers[DSP_REG_OMR], 32, 0x5f }, + + /* 16-bit program counter */ + { "PC", (uint32_t*)(&dsp->core.pc), 24, BITMASK(24) }, + + /* 16-bit DSP R (address) registers */ + { "R0", &dsp->core.registers[DSP_REG_R0], 32, BITMASK(16) }, + { "R1", &dsp->core.registers[DSP_REG_R1], 32, BITMASK(16) }, + { "R2", &dsp->core.registers[DSP_REG_R2], 32, BITMASK(16) }, + { "R3", &dsp->core.registers[DSP_REG_R3], 32, BITMASK(16) }, + { "R4", &dsp->core.registers[DSP_REG_R4], 32, BITMASK(16) }, + { "R5", &dsp->core.registers[DSP_REG_R5], 32, BITMASK(16) }, + { "R6", &dsp->core.registers[DSP_REG_R6], 32, BITMASK(16) }, + { "R7", &dsp->core.registers[DSP_REG_R7], 32, BITMASK(16) }, + + { "SSH", &dsp->core.registers[DSP_REG_SSH], 32, BITMASK(16) }, + { "SSL", &dsp->core.registers[DSP_REG_SSL], 32, BITMASK(16) }, + { "SP", &dsp->core.registers[DSP_REG_SP], 32, BITMASK(6) }, + + /* 16-bit status register */ + { "SR", &dsp->core.registers[DSP_REG_SR], 32, 0xefff }, + + /* 48-bit X register */ + { "X0", &dsp->core.registers[DSP_REG_X0], 32, BITMASK(24) }, + { "X1", &dsp->core.registers[DSP_REG_X1], 32, BITMASK(24) }, + + /* 48-bit Y register */ + { "Y0", &dsp->core.registers[DSP_REG_Y0], 32, BITMASK(24) }, + { "Y1", &dsp->core.registers[DSP_REG_Y1], 32, BITMASK(24) } + }; + /* left, right, middle, direction */ + int l, r, m, dir = 0; + unsigned int i, len; + char reg[MAX_REGNAME_LEN]; + + for (i = 0; i < sizeof(reg) && regname[i]; i++) { + reg[i] = toupper(regname[i]); + } + if (i < 2 || regname[i]) { + /* too short or longer than any of the names */ + return 0; + } + len = i; + + /* bisect */ + l = 0; + r = ARRAY_SIZE(registers) - 1; + do { + m = (l+r) >> 1; + for (i = 0; i < len; i++) { + dir = (int)reg[i] - registers[m].name[i]; + if (dir) { + break; + } + } + if (dir == 0) { + *addr = registers[m].addr; + *mask = registers[m].mask; + return registers[m].bits; + } + if (dir < 0) { + r = m-1; + } else { + l = m+1; + } + } while (l <= r); +#undef MAX_REGNAME_LEN + return 0; +} + + +/** + * Set given DSP register value, return false if unknown register given + */ +bool dsp_disasm_set_register(DSPState* dsp, const char *arg, uint32_t value) +{ + uint32_t *addr, mask, sp_value; + int bits; + + /* first check registers needing special handling... */ + if (arg[0]=='S' || arg[0]=='s') { + if (arg[1]=='P' || arg[1]=='p') { + dsp->core.registers[DSP_REG_SP] = value & BITMASK(6); + value &= BITMASK(4); + dsp->core.registers[DSP_REG_SSH] = dsp->core.stack[0][value]; + dsp->core.registers[DSP_REG_SSL] = dsp->core.stack[1][value]; + return true; + } + if (arg[1]=='S' || arg[1]=='s') { + sp_value = dsp->core.registers[DSP_REG_SP] & BITMASK(4); + if (arg[2]=='H' || arg[2]=='h') { + if (sp_value == 0) { + dsp->core.registers[DSP_REG_SSH] = 0; + dsp->core.stack[0][sp_value] = 0; + } else { + dsp->core.registers[DSP_REG_SSH] = value & BITMASK(16); + dsp->core.stack[0][sp_value] = value & BITMASK(16); + } + return true; + } + if (arg[2]=='L' || arg[2]=='l') { + if (sp_value == 0) { + dsp->core.registers[DSP_REG_SSL] = 0; + dsp->core.stack[1][sp_value] = 0; + } else { + dsp->core.registers[DSP_REG_SSL] = value & BITMASK(16); + dsp->core.stack[1][sp_value] = value & BITMASK(16); + } + return true; + } + } + } + + /* ...then registers where address & mask are enough */ + bits = dsp_get_register_address(dsp, arg, &addr, &mask); + switch (bits) { + case 32: + *addr = value & mask; + return true; + case 16: + *(uint16_t*)addr = value & mask; + return true; + } + return false; +} + +#endif diff --git a/hw/xbox/mcpx/dsp/debug.h b/hw/xbox/mcpx/dsp/debug.h new file mode 100644 index 0000000000..befef229b1 --- /dev/null +++ b/hw/xbox/mcpx/dsp/debug.h @@ -0,0 +1,37 @@ +/* + * MCPX DSP emulator + + * Copyright (c) 2025 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef HW_XBOX_MCPX_DSP_DEBUG_H +#define HW_XBOX_MCPX_DSP_DEBUG_H + +#ifndef DEBUG_DSP +#define DEBUG_DSP 0 +#endif + +#define TRACE_DSP_DISASM 0 +#define TRACE_DSP_DISASM_REG 0 +#define TRACE_DSP_DISASM_MEM 0 + +#define DPRINTF(fmt, ...) \ + do { \ + if (DEBUG_DSP) fprintf(stderr, fmt, ## __VA_ARGS__); \ + } while (0) + +#endif diff --git a/hw/xbox/mcpx/dsp/dsp.c b/hw/xbox/mcpx/dsp/dsp.c index d1e3b2a9b3..32b9c12d40 100644 --- a/hw/xbox/mcpx/dsp/dsp.c +++ b/hw/xbox/mcpx/dsp/dsp.c @@ -2,6 +2,7 @@ * MCPX DSP emulator * * Copyright (c) 2015 espes + * Copyright (c) 2020-2025 Matt Borgerson * * Adapted from Hatari DSP M56001 emulation * (C) 2001-2008 ARAnyM developer team @@ -23,17 +24,12 @@ */ #include "qemu/osdep.h" - -#include -#include -#include -#include - #include "dsp_cpu.h" #include "dsp_dma.h" #include "dsp_state.h" - #include "dsp.h" +#include "debug.h" +#include "trace.h" /* Defines */ #define BITMASK(x) ((1<<(x))-1) @@ -42,16 +38,6 @@ #define INTERRUPT_START_FRAME (1 << 1) #define INTERRUPT_DMA_EOL (1 << 7) -// #define DEBUG_DSP - -#ifdef DEBUG_DSP -#define DPRINTF(fmt, ...) \ - do { fprintf(stderr, fmt, ## __VA_ARGS__); } while (0) -#else -#define DPRINTF(fmt, ...) \ - do { } while (0) -#endif - static uint32_t read_peripheral(dsp_core_t* core, uint32_t address); static void write_peripheral(dsp_core_t* core, uint32_t address, uint32_t value); @@ -91,8 +77,6 @@ void dsp_destroy(DSPState* dsp) static uint32_t read_peripheral(dsp_core_t* core, uint32_t address) { DSPState* dsp = container_of(core, DSPState, core); - DPRINTF("read_peripheral 0x%06x", address); - uint32_t v = 0xababa; switch(address) { case 0xFFFFB3: @@ -118,15 +102,13 @@ static uint32_t read_peripheral(dsp_core_t* core, uint32_t address) { break; } - DPRINTF(" -> 0x%06x\n", v); + trace_dsp_read_peripheral(address, v); return v; } static void write_peripheral(dsp_core_t* core, uint32_t address, uint32_t value) { DSPState* dsp = container_of(core, DSPState, core); - DPRINTF("write_peripheral [0x%06x] = 0x%06x\n", address, value); - switch(address) { case 0xFFFFC4: if (value & 1) { @@ -152,6 +134,8 @@ static void write_peripheral(dsp_core_t* core, uint32_t address, uint32_t value) dsp_dma_write(&dsp->dma, DMA_CONFIGURATION, value); break; } + + trace_dsp_write_peripheral(address, value); } @@ -201,7 +185,7 @@ void dsp_bootstrap(DSPState* dsp) (uint8_t*)dsp->core.pram, 0, 0x800*4, false); for (int i = 0; i < 0x800; i++) { if (dsp->core.pram[i] & 0xff000000) { - DPRINTF(stderr, "Bootstrap %04x: %08x\n", i, dsp->core.pram[i]); + DPRINTF("Bootstrap %04x: %08x\n", i, dsp->core.pram[i]); dsp->core.pram[i] &= 0x00ffffff; } } @@ -213,19 +197,6 @@ void dsp_start_frame(DSPState* dsp) dsp->interrupts |= INTERRUPT_START_FRAME; } -/** - * Disassemble DSP code between given addresses, return next PC address - */ -uint32_t dsp_disasm_address(DSPState* dsp, FILE *out, uint32_t lowerAdr, uint32_t UpperAdr) -{ - uint32_t dsp_pc; - - for (dsp_pc=lowerAdr; dsp_pc<=UpperAdr; dsp_pc++) { - dsp_pc += dsp56k_execute_one_disasm_instruction(&dsp->core, out, dsp_pc); - } - return dsp_pc; -} - uint32_t dsp_read_memory(DSPState* dsp, char space, uint32_t address) { int space_id; @@ -269,259 +240,3 @@ void dsp_write_memory(DSPState* dsp, char space, uint32_t address, uint32_t valu dsp56k_write_memory(&dsp->core, space_id, address, value); } - -/** - * Output memory values between given addresses in given DSP address space. - * Return next DSP address value. - */ -uint32_t dsp_disasm_memory(DSPState* dsp, uint32_t dsp_memdump_addr, uint32_t dsp_memdump_upper, char space) -{ - uint32_t mem, value; - - for (mem = dsp_memdump_addr; mem <= dsp_memdump_upper; mem++) { - value = dsp_read_memory(dsp, space, mem); - printf("%04x %06x\n", mem, value); - } - return dsp_memdump_upper+1; -} - -/** - * Show information on DSP core state which isn't - * shown by any of the other commands (dd, dm, dr). - */ -void dsp_info(DSPState* dsp) -{ - int i, j; - const char *stackname[] = { "SSH", "SSL" }; - - printf("DSP core information:\n"); - - for (i = 0; i < ARRAY_SIZE(stackname); i++) { - printf("- %s stack:", stackname[i]); - for (j = 0; j < ARRAY_SIZE(dsp->core.stack[0]); j++) { - printf(" %04x", dsp->core.stack[i][j]); - } - printf("\n"); - } - - printf("- Interrupt IPL:"); - for (i = 0; i < ARRAY_SIZE(dsp->core.interrupt_ipl); i++) { - printf(" %04x", dsp->core.interrupt_ipl[i]); - } - printf("\n"); - - printf("- Pending ints: "); - for (i = 0; i < ARRAY_SIZE(dsp->core.interrupt_is_pending); i++) { - printf(" %04hx", dsp->core.interrupt_is_pending[i]); - } - printf("\n"); -} - -/** - * Show DSP register contents - */ -void dsp_print_registers(DSPState* dsp) -{ - uint32_t i; - - printf("A: A2: %02x A1: %06x A0: %06x\n", - dsp->core.registers[DSP_REG_A2], dsp->core.registers[DSP_REG_A1], dsp->core.registers[DSP_REG_A0]); - printf("B: B2: %02x B1: %06x B0: %06x\n", - dsp->core.registers[DSP_REG_B2], dsp->core.registers[DSP_REG_B1], dsp->core.registers[DSP_REG_B0]); - - printf("X: X1: %06x X0: %06x\n", dsp->core.registers[DSP_REG_X1], dsp->core.registers[DSP_REG_X0]); - printf("Y: Y1: %06x Y0: %06x\n", dsp->core.registers[DSP_REG_Y1], dsp->core.registers[DSP_REG_Y0]); - - for (i=0; i<8; i++) { - printf("R%01x: %04x N%01x: %04x M%01x: %04x\n", - i, dsp->core.registers[DSP_REG_R0+i], - i, dsp->core.registers[DSP_REG_N0+i], - i, dsp->core.registers[DSP_REG_M0+i]); - } - - printf("LA: %04x LC: %04x PC: %04x\n", dsp->core.registers[DSP_REG_LA], dsp->core.registers[DSP_REG_LC], dsp->core.pc); - printf("SR: %04x OMR: %02x\n", dsp->core.registers[DSP_REG_SR], dsp->core.registers[DSP_REG_OMR]); - printf("SP: %02x SSH: %04x SSL: %04x\n", - dsp->core.registers[DSP_REG_SP], dsp->core.registers[DSP_REG_SSH], dsp->core.registers[DSP_REG_SSL]); -} - - -/** - * Get given DSP register address and required bit mask. - * Works for A0-2, B0-2, LA, LC, M0-7, N0-7, R0-7, X0-1, Y0-1, PC, SR, SP, - * OMR, SSH & SSL registers, but note that the SP, SSH & SSL registers - * need special handling (in DSP*SetRegister()) when they are set. - * Return the register width in bits or zero for an error. - */ -int dsp_get_register_address(DSPState* dsp, const char *regname, uint32_t **addr, uint32_t *mask) -{ -#define MAX_REGNAME_LEN 4 - typedef struct { - const char name[MAX_REGNAME_LEN]; - uint32_t *addr; - size_t bits; - uint32_t mask; - } reg_addr_t; - - /* sorted by name so that this can be bisected */ - const reg_addr_t registers[] = { - - /* 56-bit A register */ - { "A0", &dsp->core.registers[DSP_REG_A0], 32, BITMASK(24) }, - { "A1", &dsp->core.registers[DSP_REG_A1], 32, BITMASK(24) }, - { "A2", &dsp->core.registers[DSP_REG_A2], 32, BITMASK(8) }, - - /* 56-bit B register */ - { "B0", &dsp->core.registers[DSP_REG_B0], 32, BITMASK(24) }, - { "B1", &dsp->core.registers[DSP_REG_B1], 32, BITMASK(24) }, - { "B2", &dsp->core.registers[DSP_REG_B2], 32, BITMASK(8) }, - - /* 16-bit LA & LC registers */ - { "LA", &dsp->core.registers[DSP_REG_LA], 32, BITMASK(16) }, - { "LC", &dsp->core.registers[DSP_REG_LC], 32, BITMASK(16) }, - - /* 16-bit M registers */ - { "M0", &dsp->core.registers[DSP_REG_M0], 32, BITMASK(16) }, - { "M1", &dsp->core.registers[DSP_REG_M1], 32, BITMASK(16) }, - { "M2", &dsp->core.registers[DSP_REG_M2], 32, BITMASK(16) }, - { "M3", &dsp->core.registers[DSP_REG_M3], 32, BITMASK(16) }, - { "M4", &dsp->core.registers[DSP_REG_M4], 32, BITMASK(16) }, - { "M5", &dsp->core.registers[DSP_REG_M5], 32, BITMASK(16) }, - { "M6", &dsp->core.registers[DSP_REG_M6], 32, BITMASK(16) }, - { "M7", &dsp->core.registers[DSP_REG_M7], 32, BITMASK(16) }, - - /* 16-bit N registers */ - { "N0", &dsp->core.registers[DSP_REG_N0], 32, BITMASK(16) }, - { "N1", &dsp->core.registers[DSP_REG_N1], 32, BITMASK(16) }, - { "N2", &dsp->core.registers[DSP_REG_N2], 32, BITMASK(16) }, - { "N3", &dsp->core.registers[DSP_REG_N3], 32, BITMASK(16) }, - { "N4", &dsp->core.registers[DSP_REG_N4], 32, BITMASK(16) }, - { "N5", &dsp->core.registers[DSP_REG_N5], 32, BITMASK(16) }, - { "N6", &dsp->core.registers[DSP_REG_N6], 32, BITMASK(16) }, - { "N7", &dsp->core.registers[DSP_REG_N7], 32, BITMASK(16) }, - - { "OMR", &dsp->core.registers[DSP_REG_OMR], 32, 0x5f }, - - /* 16-bit program counter */ - { "PC", (uint32_t*)(&dsp->core.pc), 24, BITMASK(24) }, - - /* 16-bit DSP R (address) registers */ - { "R0", &dsp->core.registers[DSP_REG_R0], 32, BITMASK(16) }, - { "R1", &dsp->core.registers[DSP_REG_R1], 32, BITMASK(16) }, - { "R2", &dsp->core.registers[DSP_REG_R2], 32, BITMASK(16) }, - { "R3", &dsp->core.registers[DSP_REG_R3], 32, BITMASK(16) }, - { "R4", &dsp->core.registers[DSP_REG_R4], 32, BITMASK(16) }, - { "R5", &dsp->core.registers[DSP_REG_R5], 32, BITMASK(16) }, - { "R6", &dsp->core.registers[DSP_REG_R6], 32, BITMASK(16) }, - { "R7", &dsp->core.registers[DSP_REG_R7], 32, BITMASK(16) }, - - { "SSH", &dsp->core.registers[DSP_REG_SSH], 32, BITMASK(16) }, - { "SSL", &dsp->core.registers[DSP_REG_SSL], 32, BITMASK(16) }, - { "SP", &dsp->core.registers[DSP_REG_SP], 32, BITMASK(6) }, - - /* 16-bit status register */ - { "SR", &dsp->core.registers[DSP_REG_SR], 32, 0xefff }, - - /* 48-bit X register */ - { "X0", &dsp->core.registers[DSP_REG_X0], 32, BITMASK(24) }, - { "X1", &dsp->core.registers[DSP_REG_X1], 32, BITMASK(24) }, - - /* 48-bit Y register */ - { "Y0", &dsp->core.registers[DSP_REG_Y0], 32, BITMASK(24) }, - { "Y1", &dsp->core.registers[DSP_REG_Y1], 32, BITMASK(24) } - }; - /* left, right, middle, direction */ - int l, r, m, dir = 0; - unsigned int i, len; - char reg[MAX_REGNAME_LEN]; - - for (i = 0; i < sizeof(reg) && regname[i]; i++) { - reg[i] = toupper(regname[i]); - } - if (i < 2 || regname[i]) { - /* too short or longer than any of the names */ - return 0; - } - len = i; - - /* bisect */ - l = 0; - r = ARRAY_SIZE(registers) - 1; - do { - m = (l+r) >> 1; - for (i = 0; i < len; i++) { - dir = (int)reg[i] - registers[m].name[i]; - if (dir) { - break; - } - } - if (dir == 0) { - *addr = registers[m].addr; - *mask = registers[m].mask; - return registers[m].bits; - } - if (dir < 0) { - r = m-1; - } else { - l = m+1; - } - } while (l <= r); -#undef MAX_REGNAME_LEN - return 0; -} - - -/** - * Set given DSP register value, return false if unknown register given - */ -bool dsp_disasm_set_register(DSPState* dsp, const char *arg, uint32_t value) -{ - uint32_t *addr, mask, sp_value; - int bits; - - /* first check registers needing special handling... */ - if (arg[0]=='S' || arg[0]=='s') { - if (arg[1]=='P' || arg[1]=='p') { - dsp->core.registers[DSP_REG_SP] = value & BITMASK(6); - value &= BITMASK(4); - dsp->core.registers[DSP_REG_SSH] = dsp->core.stack[0][value]; - dsp->core.registers[DSP_REG_SSL] = dsp->core.stack[1][value]; - return true; - } - if (arg[1]=='S' || arg[1]=='s') { - sp_value = dsp->core.registers[DSP_REG_SP] & BITMASK(4); - if (arg[2]=='H' || arg[2]=='h') { - if (sp_value == 0) { - dsp->core.registers[DSP_REG_SSH] = 0; - dsp->core.stack[0][sp_value] = 0; - } else { - dsp->core.registers[DSP_REG_SSH] = value & BITMASK(16); - dsp->core.stack[0][sp_value] = value & BITMASK(16); - } - return true; - } - if (arg[2]=='L' || arg[2]=='l') { - if (sp_value == 0) { - dsp->core.registers[DSP_REG_SSL] = 0; - dsp->core.stack[1][sp_value] = 0; - } else { - dsp->core.registers[DSP_REG_SSL] = value & BITMASK(16); - dsp->core.stack[1][sp_value] = value & BITMASK(16); - } - return true; - } - } - } - - /* ...then registers where address & mask are enough */ - bits = dsp_get_register_address(dsp, arg, &addr, &mask); - switch (bits) { - case 32: - *addr = value & mask; - return true; - case 16: - *(uint16_t*)addr = value & mask; - return true; - } - return false; -} diff --git a/hw/xbox/mcpx/dsp/dsp.h b/hw/xbox/mcpx/dsp/dsp.h index 0b18fa0c45..d85e03d55e 100644 --- a/hw/xbox/mcpx/dsp/dsp.h +++ b/hw/xbox/mcpx/dsp/dsp.h @@ -36,7 +36,6 @@ typedef void (*dsp_scratch_rw_func)( typedef void (*dsp_fifo_rw_func)( void *opaque, uint8_t *ptr, unsigned int index, size_t len, bool dir); -/* Dsp commands */ DSPState *dsp_init(void *rw_opaque, dsp_scratch_rw_func scratch_rw, dsp_fifo_rw_func fifo_rw); @@ -49,16 +48,14 @@ void dsp_run(DSPState* dsp, int cycles); void dsp_bootstrap(DSPState* dsp); void dsp_start_frame(DSPState* dsp); - -/* Dsp Debugger commands */ uint32_t dsp_read_memory(DSPState* dsp, char space, uint32_t addr); void dsp_write_memory(DSPState* dsp, char space, uint32_t address, uint32_t value); -uint32_t dsp_disasm_memory(DSPState* dsp, uint32_t dsp_memdump_addr, uint32_t dsp_memdump_upper, char space); -uint32_t dsp_disasm_address(DSPState* dsp, FILE *out, uint32_t lowerAdr, uint32_t UpperAdr); + void dsp_info(DSPState* dsp); void dsp_print_registers(DSPState* dsp); int dsp_get_register_address(DSPState* dsp, const char *arg, uint32_t **addr, uint32_t *mask); +uint32_t dsp_disasm_memory(DSPState* dsp, uint32_t dsp_memdump_addr, uint32_t dsp_memdump_upper, char space); +uint32_t dsp_disasm_address(DSPState* dsp, FILE *out, uint32_t lowerAdr, uint32_t UpperAdr); bool dsp_disasm_set_register(DSPState* dsp, const char *arg, uint32_t value); - #endif /* DSP_H */ diff --git a/hw/xbox/mcpx/dsp/dsp_cpu.c b/hw/xbox/mcpx/dsp/dsp_cpu.c index 874dd494ce..380c07e1bb 100644 --- a/hw/xbox/mcpx/dsp/dsp_cpu.c +++ b/hw/xbox/mcpx/dsp/dsp_cpu.c @@ -2,6 +2,7 @@ * DSP56300 emulator * * Copyright (c) 2015 espes + * Copyright (c) 2020-2025 Matt Borgerson * * Adapted from Hatari DSP M56001 emulation * (C) 2003-2008 ARAnyM developer team @@ -23,20 +24,10 @@ */ #include "qemu/osdep.h" - -#include -#include -#include - #include "qemu/bswap.h" - #include "dsp_cpu.h" - -#define TRACE_DSP_DISASM 0 -#define TRACE_DSP_DISASM_REG 0 -#define TRACE_DSP_DISASM_MEM 0 - -#define DPRINTF(s, ...) printf(s, ## __VA_ARGS__) +#include "debug.h" +#include "trace.h" #define BITMASK(x) ((1<<(x))-1) @@ -80,18 +71,18 @@ static void dsp_rnd56(dsp_core_t* dsp, uint32_t *dest); static uint32_t dsp_signextend(int bits, uint32_t v); static const dsp_interrupt_t dsp_interrupt[12] = { - {DSP_INTER_RESET , 0x00, 0, "Reset"}, - {DSP_INTER_ILLEGAL , 0x3e, 0, "Illegal"}, - {DSP_INTER_STACK_ERROR , 0x02, 0, "Stack Error"}, - {DSP_INTER_TRACE , 0x04, 0, "Trace"}, - {DSP_INTER_SWI , 0x06, 0, "Swi"}, - {DSP_INTER_HOST_COMMAND , 0xff, 1, "Host Command"}, - {DSP_INTER_HOST_RCV_DATA, 0x20, 1, "Host receive"}, - {DSP_INTER_HOST_TRX_DATA, 0x22, 1, "Host transmit"}, - {DSP_INTER_SSI_RCV_DATA_E, 0x0e, 2, "SSI receive with exception"}, - {DSP_INTER_SSI_RCV_DATA , 0x0c, 2, "SSI receive"}, - {DSP_INTER_SSI_TRX_DATA_E, 0x12, 2, "SSI transmit with exception"}, - {DSP_INTER_SSI_TRX_DATA , 0x10, 2, "SSI tramsmit"} + { DSP_INTER_RESET, 0x00, 0, "Reset" }, + { DSP_INTER_ILLEGAL, 0x3e, 0, "Illegal" }, + { DSP_INTER_STACK_ERROR, 0x02, 0, "Stack Error" }, + { DSP_INTER_TRACE, 0x04, 0, "Trace" }, + { DSP_INTER_SWI, 0x06, 0, "Swi" }, + { DSP_INTER_HOST_COMMAND, 0xff, 1, "Host Command" }, + { DSP_INTER_HOST_RCV_DATA, 0x20, 1, "Host receive" }, + { DSP_INTER_HOST_TRX_DATA, 0x22, 1, "Host transmit" }, + { DSP_INTER_SSI_RCV_DATA_E, 0x0e, 2, "SSI receive with exception" }, + { DSP_INTER_SSI_RCV_DATA, 0x0c, 2, "SSI receive" }, + { DSP_INTER_SSI_TRX_DATA_E, 0x12, 2, "SSI transmit with exception" }, + { DSP_INTER_SSI_TRX_DATA, 0x10, 2, "SSI transmit" } }; static const int registers_tcc[16][2] = { @@ -138,9 +129,8 @@ static const int registers_mask[64] = { 16, 16, 16, 16 }; -#include "dsp_emu.inl" - -#include "dsp_dis.inl" +#include "dsp_emu.c.inc" +#include "dsp_dis.c.inc" typedef bool (*match_func_t)(uint32_t op); @@ -415,7 +405,6 @@ void dsp56k_reset_cpu(dsp_core_t* dsp) /* runtime shit */ - dsp->executing_for_disasm = false; // start_time = SDL_GetTicks(); dsp->num_inst = 0; @@ -462,7 +451,7 @@ static uint16_t disasm_instruction(dsp_core_t* dsp, dsp_trace_disasm_t mode) if (mode == DSP_TRACE_MODE) { if (dsp->disasm_prev_inst_pc == dsp->pc) { if (!dsp->disasm_is_looping) { - printf( "Looping on DSP instruction at PC = $%04x\n", dsp->disasm_prev_inst_pc); + DPRINTF("Looping on DSP instruction at PC = $%04x\n", dsp->disasm_prev_inst_pc); dsp->disasm_is_looping = true; } return 0; @@ -520,7 +509,7 @@ static void disasm_reg_compare(dsp_core_t* dsp) case DSP_REG_X1: case DSP_REG_Y0: case DSP_REG_Y1: - printf("\tReg: %s $%06x -> $%06x\n", + DPRINTF("\tReg: %s $%06x -> $%06x\n", registers_name[i], dsp->disasm_registers_save[i], dsp->registers[i]); break; case DSP_REG_R0: @@ -550,21 +539,21 @@ static void disasm_reg_compare(dsp_core_t* dsp) case DSP_REG_SR: case DSP_REG_LA: case DSP_REG_LC: - printf("\tReg: %s $%04x -> $%04x\n", + DPRINTF("\tReg: %s $%04x -> $%04x\n", registers_name[i], dsp->disasm_registers_save[i], dsp->registers[i]); break; case DSP_REG_OMR: case DSP_REG_SP: case DSP_REG_SSH: case DSP_REG_SSL: - printf("\tReg: %s $%02x -> $%02x\n", + DPRINTF("\tReg: %s $%02x -> $%02x\n", registers_name[i], dsp->disasm_registers_save[i], dsp->registers[i]); break; case DSP_REG_A0: case DSP_REG_A1: case DSP_REG_A2: if (bRegA == false) { - printf("\tReg: a $%02x:%06x:%06x -> $%02x:%06x:%06x\n", + DPRINTF("\tReg: a $%02x:%06x:%06x -> $%02x:%06x:%06x\n", dsp->disasm_registers_save[DSP_REG_A2], dsp->disasm_registers_save[DSP_REG_A1], dsp->disasm_registers_save[DSP_REG_A0], dsp->registers[DSP_REG_A2], dsp->registers[DSP_REG_A1], dsp->registers[DSP_REG_A0] ); @@ -575,7 +564,7 @@ static void disasm_reg_compare(dsp_core_t* dsp) case DSP_REG_B1: case DSP_REG_B2: if (bRegB == false) { - printf("\tReg: b $%02x:%06x:%06x -> $%02x:%06x:%06x\n", + DPRINTF("\tReg: b $%02x:%06x:%06x -> $%02x:%06x:%06x\n", dsp->disasm_registers_save[DSP_REG_B2], dsp->disasm_registers_save[DSP_REG_B1], dsp->disasm_registers_save[DSP_REG_B0], dsp->registers[DSP_REG_B2], dsp->registers[DSP_REG_B1], dsp->registers[DSP_REG_B0] ); @@ -587,70 +576,28 @@ static void disasm_reg_compare(dsp_core_t* dsp) #ifdef DSP_DISASM_REG_PC if (pc_save != dsp->pc) { - printf("\tReg: pc $%04x -> $%04x\n", pc_save, dsp->pc); + DPRINTF("\tReg: pc $%04x -> $%04x\n", pc_save, dsp->pc); } #endif } static const char* disasm_get_instruction_text(dsp_core_t* dsp) { - // const int len = sizeof(dsp->disasm_str_instr); - // uint64_t count, cycles; - // uint16_t cycle_diff; - // float percentage; - // int offset; - if (dsp->disasm_is_looping) { dsp->disasm_str_instr2[0] = 0; } - // if (dsp->disasm_cur_inst_len == 1) { - // offset = sprintf(dsp->disasm_str_instr2, "p:%04x %06x (%02d cyc) %-*s\n", dsp->disasm_prev_inst_pc, dsp->disasm_cur_inst, dsp->instr_cycle, len, dsp->disasm_str_instr); - // } else { - // offset = sprintf(dsp->disasm_str_instr2, "p:%04x %06x %06x (%02d cyc) %-*s\n", dsp->disasm_prev_inst_pc, dsp->disasm_cur_inst, read_memory_p(dsp, dsp->disasm_prev_inst_pc + 1), dsp->instr_cycle, len, dsp->disasm_str_instr); - // } - // if (offset > 2 && Profile_DspAddressData(dsp->disasm_prev_inst_pc, &percentage, &count, &cycles, &cycle_diff)) { - // offset -= 2; - // sprintf(str_instr2+offset, "%5.2f%% (%"PRId64", %"PRId64", %d)\n", - // percentage, count, cycles, cycle_diff); - // } + if (dsp->disasm_cur_inst_len == 1) { + snprintf(dsp->disasm_str_instr2, sizeof(dsp->disasm_str_instr2), "p:%04x %06x (%02d cyc) %s", dsp->disasm_prev_inst_pc, dsp->disasm_cur_inst, dsp->instr_cycle, dsp->disasm_str_instr); + } else { + snprintf(dsp->disasm_str_instr2, sizeof(dsp->disasm_str_instr2), "p:%04x %06x %06x (%02d cyc) %s", dsp->disasm_prev_inst_pc, dsp->disasm_cur_inst, read_memory_p(dsp, dsp->disasm_prev_inst_pc + 1), dsp->instr_cycle, dsp->disasm_str_instr); + } return dsp->disasm_str_instr2; } -/** - * Execute one instruction in trace mode at a given PC address. - * */ -uint16_t dsp56k_execute_one_disasm_instruction(dsp_core_t* dsp, FILE *out, uint32_t pc) -{ - dsp_core_t dsp_core_save; - - /* Set DSP in disasm mode */ - dsp->executing_for_disasm = true; - - /* Save DSP context before executing instruction */ - memcpy(&dsp_core_save, dsp, sizeof(dsp_core_t)); - - /* execute and disasm instruction */ - dsp->pc = pc; - - /* Disasm instruction */ - uint16_t instruction_length = disasm_instruction(dsp, DSP_DISASM_MODE) - 1; - - /* Execute instruction at address given in parameter to get the number of cycles it takes */ - dsp56k_execute_instruction(dsp); - - fprintf(out, "%s", disasm_get_instruction_text(dsp)); - - /* Restore DSP context after executing instruction */ - memcpy(dsp, &dsp_core_save, sizeof(dsp_core_t)); - - /* Unset DSP in disasm mode */ - dsp->executing_for_disasm = false; - - return instruction_length; -} - void dsp56k_execute_instruction(dsp_core_t* dsp) { + trace_dsp56k_execute_instruction(dsp->is_gp, dsp->pc); + uint32_t disasm_return = 0; dsp->disasm_memory_ptr = 0; @@ -661,17 +608,18 @@ void dsp56k_execute_instruction(dsp_core_t* dsp) dsp->cur_inst_len = 1; dsp->instr_cycle = 2; - /* Disasm current instruction ? (trace mode only) */ - if (TRACE_DSP_DISASM) { - /* Call disasm_instruction only when DSP is called in trace mode */ - if (!dsp->executing_for_disasm) { - disasm_return = disasm_instruction(dsp, DSP_TRACE_MODE); + bool tracing = TRACE_DSP_DISASM || trace_event_get_state(TRACE_DSP56K_EXECUTE_INSTRUCTION_DISASM); - if (disasm_return) { - printf( "%s", disasm_get_instruction_text(dsp)); + /* Disasm current instruction ? (trace mode only) */ + if (tracing) { + disasm_return = disasm_instruction(dsp, DSP_TRACE_MODE); + if (disasm_return) { + const char *text = disasm_get_instruction_text(dsp); + trace_dsp56k_execute_instruction_disasm(text); + if (TRACE_DSP_DISASM) { + DPRINTF("%s\n", text); } - if (disasm_return != 0 && TRACE_DSP_DISASM_REG) { - /* DSP regs trace enabled only if DSP DISASM is enabled */ + if (TRACE_DSP_DISASM_REG) { disasm_reg_save(dsp); } } @@ -686,7 +634,7 @@ void dsp56k_execute_instruction(dsp_core_t* dsp) if (op->emu_func) { op->emu_func(dsp); } else { - printf("%x - %s\n", dsp->cur_inst, op->name); + DPRINTF("%x - %s\n", dsp->cur_inst, op->name); emu_undefined(dsp); } } else { @@ -695,26 +643,18 @@ void dsp56k_execute_instruction(dsp_core_t* dsp) } /* Disasm current instruction ? (trace mode only) */ - if (TRACE_DSP_DISASM) { - /* Display only when DSP is called in trace mode */ - if (!dsp->executing_for_disasm) { - if (disasm_return != 0) { - // printf( "%s", disasm_get_instruction_text(dsp)); - - /* DSP regs trace enabled only if DSP DISASM is enabled */ - if (TRACE_DSP_DISASM_REG) - disasm_reg_compare(dsp); - - if (TRACE_DSP_DISASM_MEM) { - /* 1 memory change to display ? */ - if (dsp->disasm_memory_ptr == 1) - printf( "\t%s\n", dsp->str_disasm_memory[0]); - /* 2 memory changes to display ? */ - else if (dsp->disasm_memory_ptr == 2) { - printf( "\t%s\n", dsp->str_disasm_memory[0]); - printf( "\t%s\n", dsp->str_disasm_memory[1]); - } - } + if (tracing && disasm_return) { + if (TRACE_DSP_DISASM_REG) { + disasm_reg_compare(dsp); + } + if (TRACE_DSP_DISASM_MEM) { + /* 1 memory change to display ? */ + if (dsp->disasm_memory_ptr == 1) + DPRINTF("\t%s\n", dsp->str_disasm_memory[0]); + /* 2 memory changes to display ? */ + else if (dsp->disasm_memory_ptr == 2) { + DPRINTF("\t%s\n", dsp->str_disasm_memory[0]); + DPRINTF("\t%s\n", dsp->str_disasm_memory[1]); } } } @@ -734,7 +674,7 @@ void dsp56k_execute_instruction(dsp_core_t* dsp) /* Evaluate time after instructions have been executed to avoid asking too frequently */ uint32_t cur_time = SDL_GetTicks(); if (cur_time-start_time>1000) { - printf( "Dsp: %d i/s\n", (dsp->num_inst*1000)/(cur_time-start_time)); + DPRINTF("Dsp: %d i/s\n", (dsp->num_inst*1000)/(cur_time-start_time)); start_time=cur_time; dsp->num_inst=0; } @@ -1021,9 +961,6 @@ uint32_t dsp56k_read_memory(dsp_core_t* dsp, int space, uint32_t address) void dsp56k_write_memory(dsp_core_t* dsp, int space, uint32_t address, uint32_t value) { - assert((value & 0xFF000000) == 0); - assert((address & 0xFF000000) == 0); - if (TRACE_DSP_DISASM_MEM) write_memory_disasm(dsp, space, address, value); else @@ -1122,9 +1059,7 @@ static void dsp_write_reg(dsp_core_t* dsp, uint32_t numreg, uint32_t value) /* Stack underflow or overflow detected, raise interrupt */ dsp56k_add_interrupt(dsp, DSP_INTER_STACK_ERROR); dsp->registers[DSP_REG_SP] = value & (3<executing_for_disasm) { - printf( "Dsp: Stack Overflow or Underflow\n"); - } + DPRINTF("Dsp: Stack Overflow or Underflow\n"); if (dsp->exception_debugging) { assert(false); } @@ -1167,8 +1102,7 @@ static void dsp_stack_push(dsp_core_t* dsp, uint32_t curpc, uint32_t cursr, uint if ((stack_error==0) && (stack & (1<executing_for_disasm) - printf("Dsp: Stack Overflow\n"); + DPRINTF("Dsp: Stack Overflow\n"); if (dsp->exception_debugging) assert(false); } @@ -1204,8 +1138,7 @@ static void dsp_stack_pop(dsp_core_t* dsp, uint32_t *newpc, uint32_t *newsr) if ((stack_error==0) && (stack & (1<executing_for_disasm) - printf("Dsp: Stack underflow\n"); + DPRINTF("Dsp: Stack underflow\n"); if (dsp->exception_debugging) assert(false); } @@ -1458,5 +1391,3 @@ static uint32_t dsp_signextend(int bits, uint32_t v) { assert(shift > 0); return (uint32_t)(((int32_t)v << shift) >> shift); } - - diff --git a/hw/xbox/mcpx/dsp/dsp_cpu.h b/hw/xbox/mcpx/dsp/dsp_cpu.h index 6876b4d20a..4cffd2631d 100644 --- a/hw/xbox/mcpx/dsp/dsp_cpu.h +++ b/hw/xbox/mcpx/dsp/dsp_cpu.h @@ -29,115 +29,7 @@ #include #include -#define DSP_OMR_MA 0x00 -#define DSP_OMR_MB 0x01 -#define DSP_OMR_DE 0x02 -#define DSP_OMR_SD 0x06 -#define DSP_OMR_EA 0x07 - -#define DSP_SR_C 0x00 -#define DSP_SR_V 0x01 -#define DSP_SR_Z 0x02 -#define DSP_SR_N 0x03 -#define DSP_SR_U 0x04 -#define DSP_SR_E 0x05 -#define DSP_SR_L 0x06 - -#define DSP_SR_I0 0x08 -#define DSP_SR_I1 0x09 -#define DSP_SR_S0 0x0a -#define DSP_SR_S1 0x0b -#define DSP_SR_T 0x0d -#define DSP_SR_LF 0x0f - -#define DSP_SP_SE 0x04 -#define DSP_SP_UF 0x05 - -/* Registers numbers in dsp.registers[] */ -#define DSP_REG_X0 0x04 -#define DSP_REG_X1 0x05 -#define DSP_REG_Y0 0x06 -#define DSP_REG_Y1 0x07 -#define DSP_REG_A0 0x08 -#define DSP_REG_B0 0x09 -#define DSP_REG_A2 0x0a -#define DSP_REG_B2 0x0b -#define DSP_REG_A1 0x0c -#define DSP_REG_B1 0x0d -#define DSP_REG_A 0x0e -#define DSP_REG_B 0x0f - -#define DSP_REG_R0 0x10 -#define DSP_REG_R1 0x11 -#define DSP_REG_R2 0x12 -#define DSP_REG_R3 0x13 -#define DSP_REG_R4 0x14 -#define DSP_REG_R5 0x15 -#define DSP_REG_R6 0x16 -#define DSP_REG_R7 0x17 - -#define DSP_REG_N0 0x18 -#define DSP_REG_N1 0x19 -#define DSP_REG_N2 0x1a -#define DSP_REG_N3 0x1b -#define DSP_REG_N4 0x1c -#define DSP_REG_N5 0x1d -#define DSP_REG_N6 0x1e -#define DSP_REG_N7 0x1f - -#define DSP_REG_M0 0x20 -#define DSP_REG_M1 0x21 -#define DSP_REG_M2 0x22 -#define DSP_REG_M3 0x23 -#define DSP_REG_M4 0x24 -#define DSP_REG_M5 0x25 -#define DSP_REG_M6 0x26 -#define DSP_REG_M7 0x27 - -#define DSP_REG_SR 0x39 -#define DSP_REG_OMR 0x3a -#define DSP_REG_SP 0x3b -#define DSP_REG_SSH 0x3c -#define DSP_REG_SSL 0x3d -#define DSP_REG_LA 0x3e -#define DSP_REG_LC 0x3f - -#define DSP_REG_NULL 0x00 -#define DSP_REG_LCSAVE 0x30 - -#define DSP_REG_MAX 0x40 - -/* Memory spaces for dsp.ram[], dsp.rom[] */ -#define DSP_SPACE_X 0x00 -#define DSP_SPACE_Y 0x01 -#define DSP_SPACE_P 0x02 - -#define DSP_XRAM_SIZE 4096 -#define DSP_YRAM_SIZE 2048 -#define DSP_PRAM_SIZE 4096 - -#define DSP_MIXBUFFER_BASE 0x001400 -#define DSP_MIXBUFFER_SIZE 1024 - -#define DSP_PERIPH_BASE 0xFFFF80 -#define DSP_PERIPH_SIZE 128 - -#define DSP_INTERRUPT_NONE 0x0 -#define DSP_INTERRUPT_DISABLED 0x1 -#define DSP_INTERRUPT_LONG 0x2 - -#define DSP_INTER_RESET 0x0 -#define DSP_INTER_ILLEGAL 0x1 -#define DSP_INTER_STACK_ERROR 0x2 -#define DSP_INTER_TRACE 0x3 -#define DSP_INTER_SWI 0x4 -#define DSP_INTER_HOST_COMMAND 0x5 -#define DSP_INTER_HOST_RCV_DATA 0x6 -#define DSP_INTER_HOST_TRX_DATA 0x7 -#define DSP_INTER_SSI_RCV_DATA_E 0x8 -#define DSP_INTER_SSI_RCV_DATA 0x9 -#define DSP_INTER_SSI_TRX_DATA_E 0xa -#define DSP_INTER_SSI_TRX_DATA 0xb +#include "dsp_cpu_regs.h" typedef enum { DSP_TRACE_MODE, @@ -209,10 +101,6 @@ struct dsp_core_s { /* Current instruction */ uint32_t cur_inst; - /* DSP is in disasm mode ? */ - /* If yes, stack overflow, underflow and illegal instructions messages are not displayed */ - bool executing_for_disasm; - char str_disasm_memory[2][50]; /* Buffer for memory change text in disasm mode */ uint32_t disasm_memory_ptr; /* Pointer for memory change in disasm mode */ @@ -250,7 +138,6 @@ struct dsp_core_s { /* Functions */ void dsp56k_reset_cpu(dsp_core_t* dsp); /* Set dsp_core to use */ void dsp56k_execute_instruction(dsp_core_t* dsp); /* Execute 1 instruction */ -uint16_t dsp56k_execute_one_disasm_instruction(dsp_core_t* dsp, FILE *out, uint32_t pc); /* Execute 1 instruction in disasm mode */ uint32_t dsp56k_read_memory(dsp_core_t* dsp, int space, uint32_t address); void dsp56k_write_memory(dsp_core_t* dsp, int space, uint32_t address, uint32_t value); diff --git a/hw/xbox/mcpx/dsp/dsp_cpu_regs.h b/hw/xbox/mcpx/dsp/dsp_cpu_regs.h new file mode 100644 index 0000000000..75b2009b2f --- /dev/null +++ b/hw/xbox/mcpx/dsp/dsp_cpu_regs.h @@ -0,0 +1,138 @@ +/* + * DSP56300 emulator + * + * Copyright (c) 2015 espes + * + * Adapted from Hatari DSP M56001 emulation + * (C) 2003-2008 ARAnyM developer team + * Adaption to Hatari (C) 2008 by Thomas Huth + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef HW_XBOX_MCPX_DSP_DSP_CPU_REGS_H +#define HW_XBOX_MCPX_DSP_DSP_CPU_REGS_H + +#define DSP_OMR_MA 0x00 +#define DSP_OMR_MB 0x01 +#define DSP_OMR_DE 0x02 +#define DSP_OMR_SD 0x06 +#define DSP_OMR_EA 0x07 + +#define DSP_SR_C 0x00 +#define DSP_SR_V 0x01 +#define DSP_SR_Z 0x02 +#define DSP_SR_N 0x03 +#define DSP_SR_U 0x04 +#define DSP_SR_E 0x05 +#define DSP_SR_L 0x06 + +#define DSP_SR_I0 0x08 +#define DSP_SR_I1 0x09 +#define DSP_SR_S0 0x0a +#define DSP_SR_S1 0x0b +#define DSP_SR_T 0x0d +#define DSP_SR_LF 0x0f + +#define DSP_SP_SE 0x04 +#define DSP_SP_UF 0x05 + +/* Registers numbers in dsp.registers[] */ +#define DSP_REG_X0 0x04 +#define DSP_REG_X1 0x05 +#define DSP_REG_Y0 0x06 +#define DSP_REG_Y1 0x07 +#define DSP_REG_A0 0x08 +#define DSP_REG_B0 0x09 +#define DSP_REG_A2 0x0a +#define DSP_REG_B2 0x0b +#define DSP_REG_A1 0x0c +#define DSP_REG_B1 0x0d +#define DSP_REG_A 0x0e +#define DSP_REG_B 0x0f + +#define DSP_REG_R0 0x10 +#define DSP_REG_R1 0x11 +#define DSP_REG_R2 0x12 +#define DSP_REG_R3 0x13 +#define DSP_REG_R4 0x14 +#define DSP_REG_R5 0x15 +#define DSP_REG_R6 0x16 +#define DSP_REG_R7 0x17 + +#define DSP_REG_N0 0x18 +#define DSP_REG_N1 0x19 +#define DSP_REG_N2 0x1a +#define DSP_REG_N3 0x1b +#define DSP_REG_N4 0x1c +#define DSP_REG_N5 0x1d +#define DSP_REG_N6 0x1e +#define DSP_REG_N7 0x1f + +#define DSP_REG_M0 0x20 +#define DSP_REG_M1 0x21 +#define DSP_REG_M2 0x22 +#define DSP_REG_M3 0x23 +#define DSP_REG_M4 0x24 +#define DSP_REG_M5 0x25 +#define DSP_REG_M6 0x26 +#define DSP_REG_M7 0x27 + +#define DSP_REG_SR 0x39 +#define DSP_REG_OMR 0x3a +#define DSP_REG_SP 0x3b +#define DSP_REG_SSH 0x3c +#define DSP_REG_SSL 0x3d +#define DSP_REG_LA 0x3e +#define DSP_REG_LC 0x3f + +#define DSP_REG_NULL 0x00 +#define DSP_REG_LCSAVE 0x30 + +#define DSP_REG_MAX 0x40 + +/* Memory spaces for dsp.ram[], dsp.rom[] */ +#define DSP_SPACE_X 0x00 +#define DSP_SPACE_Y 0x01 +#define DSP_SPACE_P 0x02 + +#define DSP_XRAM_SIZE 4096 +#define DSP_YRAM_SIZE 2048 +#define DSP_PRAM_SIZE 4096 + +#define DSP_MIXBUFFER_BASE 0x001400 +#define DSP_MIXBUFFER_SIZE 1024 + +#define DSP_PERIPH_BASE 0xFFFF80 +#define DSP_PERIPH_SIZE 128 + +#define DSP_INTERRUPT_NONE 0x0 +#define DSP_INTERRUPT_DISABLED 0x1 +#define DSP_INTERRUPT_LONG 0x2 + +#define DSP_INTER_RESET 0x0 +#define DSP_INTER_ILLEGAL 0x1 +#define DSP_INTER_STACK_ERROR 0x2 +#define DSP_INTER_TRACE 0x3 +#define DSP_INTER_SWI 0x4 +#define DSP_INTER_HOST_COMMAND 0x5 +#define DSP_INTER_HOST_RCV_DATA 0x6 +#define DSP_INTER_HOST_TRX_DATA 0x7 +#define DSP_INTER_SSI_RCV_DATA_E 0x8 +#define DSP_INTER_SSI_RCV_DATA 0x9 +#define DSP_INTER_SSI_TRX_DATA_E 0xa +#define DSP_INTER_SSI_TRX_DATA 0xb + +#endif diff --git a/hw/xbox/mcpx/dsp/dsp_dis.inl b/hw/xbox/mcpx/dsp/dsp_dis.c.inc similarity index 100% rename from hw/xbox/mcpx/dsp/dsp_dis.inl rename to hw/xbox/mcpx/dsp/dsp_dis.c.inc diff --git a/hw/xbox/mcpx/dsp/dsp_dma.c b/hw/xbox/mcpx/dsp/dsp_dma.c index 377475fb76..98f7bc3cb7 100644 --- a/hw/xbox/mcpx/dsp/dsp_dma.c +++ b/hw/xbox/mcpx/dsp/dsp_dma.c @@ -18,48 +18,14 @@ * License along with this library; if not, see . */ -#include -#include -#include -#include - -#include +#include "qemu/osdep.h" #include "qemu/compiler.h" #include "dsp_dma.h" +#include "dsp_dma_regs.h" #include "dsp_state.h" - -#define DMA_CONFIGURATION_AUTOSTART (1 << 0) -#define DMA_CONFIGURATION_AUTOREADY (1 << 1) -#define DMA_CONFIGURATION_IOC_CLEAR (1 << 2) -#define DMA_CONFIGURATION_EOL_CLEAR (1 << 3) -#define DMA_CONFIGURATION_ERR_CLEAR (1 << 4) - -#define DMA_CONTROL_ACTION 0x7 -#define DMA_CONTROL_ACTION_NOP 0 -#define DMA_CONTROL_ACTION_START 1 -#define DMA_CONTROL_ACTION_STOP 2 -#define DMA_CONTROL_ACTION_FREEZE 3 -#define DMA_CONTROL_ACTION_UNFREEZE 4 -#define DMA_CONTROL_ACTION_ABORT 5 -#define DMA_CONTROL_FROZEN (1 << 3) -#define DMA_CONTROL_RUNNING (1 << 4) -#define DMA_CONTROL_STOPPED (1 << 5) - -#define NODE_POINTER_VAL 0x3fff -#define NODE_POINTER_EOL (1 << 14) - -#define NODE_CONTROL_DIRECTION (1 << 1) - - -// #define DEBUG #ifdef DEBUG -# define DPRINTF(s, ...) printf(s, ## __VA_ARGS__) -#else -# define DPRINTF(s, ...) do { } while (0) -#endif -#ifdef DEBUG const char *buffer_names[] = { "fifo0", /* 0x0 */ "fifo1", /* 0x1 */ @@ -98,8 +64,6 @@ const char *space_names[] = { #endif -#define MIN(a,b) (((a)<(b))?(a):(b)) - static void scratch_circular_copy( DSPDMAState *s, uint32_t scratch_base, @@ -142,8 +106,6 @@ static void dsp_dma_run(DSPDMAState *s) return; } - // DSPState *dsp = container_of(s, DSPState, dma); - while (!(s->next_block & NODE_POINTER_EOL)) { uint32_t addr = s->next_block & NODE_POINTER_VAL; uint32_t block_addr = 0; diff --git a/hw/xbox/mcpx/dsp/dsp_dma_regs.h b/hw/xbox/mcpx/dsp/dsp_dma_regs.h new file mode 100644 index 0000000000..a373cb7c14 --- /dev/null +++ b/hw/xbox/mcpx/dsp/dsp_dma_regs.h @@ -0,0 +1,46 @@ +/* + * MCPX DSP DMA + * + * Copyright (c) 2015 espes + * Copyright (c) 2020-2025 Matt Borgerson + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#ifndef HW_XBOX_MCPX_DSP_DSP_DMA_REGS_H +#define HW_XBOX_MCPX_DSP_DSP_DMA_REGS_H + +#define DMA_CONFIGURATION_AUTOSTART (1 << 0) +#define DMA_CONFIGURATION_AUTOREADY (1 << 1) +#define DMA_CONFIGURATION_IOC_CLEAR (1 << 2) +#define DMA_CONFIGURATION_EOL_CLEAR (1 << 3) +#define DMA_CONFIGURATION_ERR_CLEAR (1 << 4) + +#define DMA_CONTROL_ACTION 0x7 +#define DMA_CONTROL_ACTION_NOP 0 +#define DMA_CONTROL_ACTION_START 1 +#define DMA_CONTROL_ACTION_STOP 2 +#define DMA_CONTROL_ACTION_FREEZE 3 +#define DMA_CONTROL_ACTION_UNFREEZE 4 +#define DMA_CONTROL_ACTION_ABORT 5 +#define DMA_CONTROL_FROZEN (1 << 3) +#define DMA_CONTROL_RUNNING (1 << 4) +#define DMA_CONTROL_STOPPED (1 << 5) + +#define NODE_POINTER_VAL 0x3fff +#define NODE_POINTER_EOL (1 << 14) + +#define NODE_CONTROL_DIRECTION (1 << 1) + +#endif diff --git a/hw/xbox/mcpx/dsp/dsp_emu.inl b/hw/xbox/mcpx/dsp/dsp_emu.c.inc similarity index 99% rename from hw/xbox/mcpx/dsp/dsp_emu.inl rename to hw/xbox/mcpx/dsp/dsp_emu.c.inc index e509baa2d7..b67ea13398 100644 --- a/hw/xbox/mcpx/dsp/dsp_emu.inl +++ b/hw/xbox/mcpx/dsp/dsp_emu.c.inc @@ -27,15 +27,10 @@ typedef void (*emu_func_t)(dsp_core_t* dsp); static void emu_undefined(dsp_core_t* dsp) { - if (!dsp->executing_for_disasm) { - dsp->cur_inst_len = 0; - printf("Dsp: 0x%04x: 0x%06x Illegal instruction\n",dsp->pc, dsp->cur_inst); - /* Add some artificial CPU cycles to avoid being stuck in an infinite loop */ - dsp->instr_cycle += 100; - } else { - dsp->cur_inst_len = 1; - dsp->instr_cycle = 0; - } + dsp->cur_inst_len = 0; + printf("Dsp: 0x%04x: 0x%06x Illegal instruction\n",dsp->pc, dsp->cur_inst); + /* Add some artificial CPU cycles to avoid being stuck in an infinite loop */ + dsp->instr_cycle += 100; if (dsp->exception_debugging) { assert(false); } diff --git a/hw/xbox/mcpx/dsp/meson.build b/hw/xbox/mcpx/dsp/meson.build new file mode 100644 index 0000000000..4e06c59496 --- /dev/null +++ b/hw/xbox/mcpx/dsp/meson.build @@ -0,0 +1,2 @@ +libdsp = static_library('dsp', files(['debug.c', 'dsp.c', 'dsp_cpu.c', 'dsp_dma.c']) + genh) +dsp = declare_dependency(objects: libdsp.extract_all_objects(recursive: false)) diff --git a/hw/xbox/mcpx/dsp/trace-events b/hw/xbox/mcpx/dsp/trace-events new file mode 100644 index 0000000000..82f10a3f92 --- /dev/null +++ b/hw/xbox/mcpx/dsp/trace-events @@ -0,0 +1,9 @@ +# See docs/devel/tracing.rst for syntax documentation. + +# dsp.c +dsp_read_peripheral(uint32_t addr, uint32_t val) "addr 0x%"PRIx32" val 0x%"PRIx32 +dsp_write_peripheral(uint32_t addr, uint32_t val) "addr 0x%"PRIx32" val 0x%"PRIx32 + +# dsp_cpu.c +dsp56k_execute_instruction(uint32_t id, uint32_t pc) "[gp=%d]: pc=0x%"PRIx32 +dsp56k_execute_instruction_disasm(const char *disasm) "%s" diff --git a/hw/xbox/mcpx/dsp/trace.h b/hw/xbox/mcpx/dsp/trace.h new file mode 100644 index 0000000000..6ab110fe5b --- /dev/null +++ b/hw/xbox/mcpx/dsp/trace.h @@ -0,0 +1 @@ +#include "trace/trace-hw_xbox_mcpx_dsp.h" diff --git a/hw/xbox/mcpx/meson.build b/hw/xbox/mcpx/meson.build index fd87ef118e..5f6807337d 100644 --- a/hw/xbox/mcpx/meson.build +++ b/hw/xbox/mcpx/meson.build @@ -1,10 +1,9 @@ +subdir('dsp') + mcpx_ss = ss.source_set() -mcpx_ss.add(sdl, libsamplerate, files( +mcpx_ss.add(sdl, libsamplerate, dsp, files( 'apu.c', 'aci.c', - 'dsp/dsp.c', - 'dsp/dsp_cpu.c', - 'dsp/dsp_dma.c', )) specific_ss.add_all(mcpx_ss) \ No newline at end of file diff --git a/hw/xbox/nv2a/debug.h b/hw/xbox/nv2a/debug.h index 3873f94239..2dc4bece96 100644 --- a/hw/xbox/nv2a/debug.h +++ b/hw/xbox/nv2a/debug.h @@ -155,8 +155,9 @@ static inline void nv2a_profile_inc_counter(enum NV2A_PROF_COUNTERS_ENUM cnt) void nv2a_dbg_renderdoc_init(void); void *nv2a_dbg_renderdoc_get_api(void); bool nv2a_dbg_renderdoc_available(void); -void nv2a_dbg_renderdoc_capture_frames(int num_frames); +void nv2a_dbg_renderdoc_capture_frames(int num_frames, bool trace); extern int renderdoc_capture_frames; +extern bool renderdoc_trace_frames; #endif #ifdef __cplusplus diff --git a/hw/xbox/nv2a/nv2a.c b/hw/xbox/nv2a/nv2a.c index 0124a0d451..02cfbca3e9 100644 --- a/hw/xbox/nv2a/nv2a.c +++ b/hw/xbox/nv2a/nv2a.c @@ -423,7 +423,7 @@ const VMStateDescription vmstate_nv2a_pgraph_vertex_attributes = { static const VMStateDescription vmstate_nv2a = { .name = "nv2a", - .version_id = 2, + .version_id = 3, .minimum_version_id = 1, .post_save = nv2a_post_save, .post_load = nv2a_post_load, @@ -507,9 +507,11 @@ static const VMStateDescription vmstate_nv2a = { VMSTATE_BOOL_ARRAY(pgraph.ltc1_dirty, NV2AState, NV2A_LTC1_COUNT), VMSTATE_STRUCT_ARRAY(pgraph.vertex_attributes, NV2AState, NV2A_VERTEXSHADER_ATTRIBUTES, 1, vmstate_nv2a_pgraph_vertex_attributes, VertexAttribute), VMSTATE_UINT32(pgraph.inline_array_length, NV2AState), - VMSTATE_UINT32_ARRAY(pgraph.inline_array, NV2AState, NV2A_MAX_BATCH_LENGTH), + VMSTATE_UINT32_SUB_ARRAY(pgraph.inline_array, NV2AState, 0, NV2A_MAX_BATCH_LENGTH_V2), + VMSTATE_UINT32_SUB_ARRAY_V(pgraph.inline_array, NV2AState, NV2A_MAX_BATCH_LENGTH_V2, NV2A_MAX_BATCH_LENGTH - NV2A_MAX_BATCH_LENGTH_V2, 3), VMSTATE_UINT32(pgraph.inline_elements_length, NV2AState), // fixme - VMSTATE_UINT32_ARRAY(pgraph.inline_elements, NV2AState, NV2A_MAX_BATCH_LENGTH), + VMSTATE_UINT32_SUB_ARRAY(pgraph.inline_elements, NV2AState, 0, NV2A_MAX_BATCH_LENGTH_V2), + VMSTATE_UINT32_SUB_ARRAY_V(pgraph.inline_elements, NV2AState, NV2A_MAX_BATCH_LENGTH_V2, NV2A_MAX_BATCH_LENGTH - NV2A_MAX_BATCH_LENGTH_V2, 3), VMSTATE_UINT32(pgraph.inline_buffer_length, NV2AState), // fixme VMSTATE_UINT32(pgraph.draw_arrays_length, NV2AState), VMSTATE_UINT32(pgraph.draw_arrays_max_count, NV2AState), diff --git a/hw/xbox/nv2a/nv2a_regs.h b/hw/xbox/nv2a/nv2a_regs.h index 78a9091eb5..c1804a2cef 100644 --- a/hw/xbox/nv2a/nv2a_regs.h +++ b/hw/xbox/nv2a/nv2a_regs.h @@ -32,6 +32,8 @@ (v) |= ((__val) << ctz32(__mask)) & (__mask); \ }) +// clang-format off + #define NV_NUM_BLOCKS 21 #define NV_PMC 0 /* card master control */ #define NV_PBUS 1 /* bus control */ @@ -312,11 +314,15 @@ # define NV_PGRAPH_CSV0_D_SKIN_4 6 #define NV_PGRAPH_CSV0_C 0x00000FB8 # define NV_PGRAPH_CSV0_C_CHEOPS_PROGRAM_START 0x0000FF00 +# define NV_PGRAPH_CSV0_C_SPECULAR_ENABLE (1 << 16) +# define NV_PGRAPH_CSV0_C_ALPHA_FROM_MATERIAL_SPECULAR (1 << 17) +# define NV_PGRAPH_CSV0_C_SEPARATE_SPECULAR (1 << 18) # define NV_PGRAPH_CSV0_C_SPECULAR (3 << 19) # define NV_PGRAPH_CSV0_C_DIFFUSE (3 << 21) # define NV_PGRAPH_CSV0_C_AMBIENT (3 << 23) # define NV_PGRAPH_CSV0_C_EMISSION (3 << 25) # define NV_PGRAPH_CSV0_C_NORMALIZATION_ENABLE (1 << 27) +# define NV_PGRAPH_CSV0_C_LOCALEYE (1 << 30) # define NV_PGRAPH_CSV0_C_LIGHTING (1 << 31) #define NV_PGRAPH_CSV1_B 0x00000FBC #define NV_PGRAPH_CSV1_A 0x00000FC0 @@ -879,6 +885,10 @@ # define NV097_SET_CONTROL0_STENCIL_WRITE_ENABLE (1 << 0) # define NV097_SET_CONTROL0_Z_FORMAT (1 << 12) # define NV097_SET_CONTROL0_Z_PERSPECTIVE_ENABLE (1 << 16) +# define NV097_SET_LIGHT_CONTROL 0x00000294 +# define NV097_SET_LIGHT_CONTROL_SEPARATE_SPECULAR 1 +# define NV097_SET_LIGHT_CONTROL_LOCALEYE (1 << 16) +# define NV097_SET_LIGHT_CONTROL_ALPHA_FROM_MATERIAL_SPECULAR (1 << 17) # define NV097_SET_COLOR_MATERIAL 0x00000298 # define NV097_SET_FOG_MODE 0x0000029C # define NV097_SET_FOG_MODE_V_LINEAR 0x2601 @@ -1013,6 +1023,7 @@ # define NV097_SET_NORMALIZATION_ENABLE 0x000003A4 # define NV097_SET_MATERIAL_EMISSION 0x000003A8 # define NV097_SET_MATERIAL_ALPHA 0x000003B4 +# define NV097_SET_SPECULAR_ENABLE 0x000003B8 # define NV097_SET_LIGHT_ENABLE_MASK 0x000003BC # define NV097_SET_LIGHT_ENABLE_MASK_LIGHT0_OFF 0 # define NV097_SET_LIGHT_ENABLE_MASK_LIGHT0_INFINITE 1 @@ -1045,6 +1056,7 @@ # define NV097_SET_TEXGEN_VIEW_MODEL_LOCAL_VIEWER 0 # define NV097_SET_TEXGEN_VIEW_MODEL_INFINITE_VIEWER 1 # define NV097_SET_FOG_PLANE 0x000009D0 +# define NV097_SET_SPECULAR_PARAMS 0x000009E0 # define NV097_SET_SCENE_AMBIENT_COLOR 0x00000A10 # define NV097_SET_VIEWPORT_OFFSET 0x00000A20 # define NV097_SET_POINT_PARAMS 0x00000A30 @@ -1095,6 +1107,11 @@ # define NV097_SET_TEXCOORD3_4F 0x00001620 # define NV097_SET_TEXCOORD3_2S 0x00001610 # define NV097_SET_TEXCOORD3_4S 0x00001630 +# define NV097_SET_FOG_COORD 0x00001698 +# define NV097_SET_WEIGHT1F 0x0000169C +# define NV097_SET_WEIGHT2F 0x000016A0 +# define NV097_SET_WEIGHT3F 0x000016B0 +# define NV097_SET_WEIGHT4F 0x000016C0 # define NV097_SET_VERTEX_DATA_ARRAY_OFFSET 0x00001720 # define NV097_SET_VERTEX_DATA_ARRAY_FORMAT 0x00001760 # define NV097_SET_VERTEX_DATA_ARRAY_FORMAT_TYPE 0x0000000F @@ -1248,6 +1265,7 @@ # define NV097_SET_CLEAR_RECT_HORIZONTAL 0x00001D98 # define NV097_SET_CLEAR_RECT_VERTICAL 0x00001D9C # define NV097_SET_SPECULAR_FOG_FACTOR 0x00001E20 +# define NV097_SET_SPECULAR_PARAMS_BACK 0x00001E28 # define NV097_SET_COMBINER_COLOR_OCW 0x00001E40 # define NV097_SET_COMBINER_CONTROL 0x00001E60 # define NV097_SET_SHADOW_ZSLOPE_THRESHOLD 0x00001E68 @@ -1449,7 +1467,22 @@ #define NV2A_NUM_SUBCHANNELS 8 #define NV2A_CACHE1_SIZE 128 -#define NV2A_MAX_BATCH_LENGTH 0x1FFFF +/* This is a multi-use limit. Testing on an Xbox 1.0, it is possible to send + * arrays of at least 0x0FFFFF elements without issue, however sending + * NV097_DRAW_ARRAYS with a start value > 0xFFFF raises an exception implying + * that there may be a vertex limit. Since xemu uses batch length for vertex + * elements in NV097_INLINE_ARRAY the size should ideally be high enough to + * accommodate 0xFFFF vertices with maximum attributes specified. + * + * Retail games are known to send at least 0x410FA elements in a single draw, so + * a somewhat larger value is selected to balance memory use with real-world + * limits. + * + * NV2A_MAX_BATCH_LENGTH_V2 is the previous limit, for migration. + * FIXME: Remove NV2A_MAX_BATCH_LENGTH_V2 at some point in the future. + */ +#define NV2A_MAX_BATCH_LENGTH 0x07FFFF +#define NV2A_MAX_BATCH_LENGTH_V2 0x1FFFF #define NV2A_VERTEXSHADER_ATTRIBUTES 16 #define NV2A_MAX_TEXTURES 4 @@ -1463,4 +1496,6 @@ #define NV2A_CUBEMAP_FACE_ALIGNMENT 128 +// clang-format on + #endif diff --git a/hw/xbox/nv2a/pgraph/debug_renderdoc.c b/hw/xbox/nv2a/pgraph/debug_renderdoc.c index 273e307973..667f01a0c9 100644 --- a/hw/xbox/nv2a/pgraph/debug_renderdoc.c +++ b/hw/xbox/nv2a/pgraph/debug_renderdoc.c @@ -36,6 +36,7 @@ static RENDERDOC_API_1_6_0 *rdoc_api = NULL; int renderdoc_capture_frames = 0; +bool renderdoc_trace_frames = false; void nv2a_dbg_renderdoc_init(void) { @@ -89,7 +90,8 @@ bool nv2a_dbg_renderdoc_available(void) return rdoc_api != NULL; } -void nv2a_dbg_renderdoc_capture_frames(int num_frames) +void nv2a_dbg_renderdoc_capture_frames(int num_frames, bool trace) { renderdoc_capture_frames += num_frames; + renderdoc_trace_frames = trace; } diff --git a/hw/xbox/nv2a/pgraph/gl/constants.h b/hw/xbox/nv2a/pgraph/gl/constants.h index d78b0054e3..e3e78df8d1 100644 --- a/hw/xbox/nv2a/pgraph/gl/constants.h +++ b/hw/xbox/nv2a/pgraph/gl/constants.h @@ -298,7 +298,7 @@ static const SurfaceFormatInfo kelvin_surface_color_format_gl_map[] = { [NV097_SET_SURFACE_FORMAT_COLOR_LE_B8] = {1, GL_R8, GL_RED, GL_UNSIGNED_BYTE, GL_COLOR_ATTACHMENT0}, [NV097_SET_SURFACE_FORMAT_COLOR_LE_G8B8] = - {2, GL_RG8, GL_RG, GL_UNSIGNED_SHORT, GL_COLOR_ATTACHMENT0}, + {2, GL_RG8, GL_RG, GL_UNSIGNED_BYTE, GL_COLOR_ATTACHMENT0}, }; static const SurfaceFormatInfo kelvin_surface_zeta_float_format_gl_map[] = { diff --git a/hw/xbox/nv2a/pgraph/gl/debug.c b/hw/xbox/nv2a/pgraph/gl/debug.c index 968941dc7e..97803bc301 100644 --- a/hw/xbox/nv2a/pgraph/gl/debug.c +++ b/hw/xbox/nv2a/pgraph/gl/debug.c @@ -29,6 +29,8 @@ #include #ifdef CONFIG_RENDERDOC +#include "trace/control.h" + #pragma GCC diagnostic ignored "-Wstrict-prototypes" #include "thirdparty/renderdoc_app.h" #endif @@ -154,7 +156,8 @@ void gl_debug_frame_terminator(void) RENDERDOC_API_1_6_0 *rdoc_api = nv2a_dbg_renderdoc_get_api(); if (rdoc_api->IsTargetControlConnected()) { - if (rdoc_api->IsFrameCapturing()) { + bool capturing = rdoc_api->IsFrameCapturing(); + if (capturing && renderdoc_capture_frames == 0) { rdoc_api->EndFrameCapture(NULL, NULL); GLenum error = glGetError(); if (error != GL_NO_ERROR) { @@ -162,14 +165,23 @@ void gl_debug_frame_terminator(void) "Renderdoc EndFrameCapture triggered GL error 0x%X - ignoring\n", error); } + if (renderdoc_trace_frames) { + trace_enable_events("-nv2a_pgraph_*"); + renderdoc_trace_frames = false; + } } if (renderdoc_capture_frames > 0) { - rdoc_api->StartFrameCapture(NULL, NULL); - GLenum error = glGetError(); - if (error != GL_NO_ERROR) { - fprintf(stderr, - "Renderdoc StartFrameCapture triggered GL error 0x%X - ignoring\n", - error); + if (!capturing) { + if (renderdoc_trace_frames) { + trace_enable_events("nv2a_pgraph_*"); + } + rdoc_api->StartFrameCapture(NULL, NULL); + GLenum error = glGetError(); + if (error != GL_NO_ERROR) { + fprintf(stderr, + "Renderdoc StartFrameCapture triggered GL error 0x%X - ignoring\n", + error); + } } --renderdoc_capture_frames; } diff --git a/hw/xbox/nv2a/pgraph/gl/display.c b/hw/xbox/nv2a/pgraph/gl/display.c index 6d52a5c3b3..47400cbbd0 100644 --- a/hw/xbox/nv2a/pgraph/gl/display.c +++ b/hw/xbox/nv2a/pgraph/gl/display.c @@ -68,7 +68,7 @@ void pgraph_gl_init_display(NV2AState *d) "{\n" " vec2 texCoord = gl_FragCoord.xy/display_size;\n" " float rel = display_size.y/textureSize(tex, 0).y/line_offset;\n" - " texCoord.y = 1 + rel*(texCoord.y - 1);" + " texCoord.y = rel*(1.0f - texCoord.y);" " out_Color.rgba = texture(tex, texCoord);\n" " if (pvideo_enable) {\n" " vec2 screenCoord = gl_FragCoord.xy - 0.5;\n" diff --git a/hw/xbox/nv2a/pgraph/gl/draw.c b/hw/xbox/nv2a/pgraph/gl/draw.c index bfa92662e7..79c18040f9 100644 --- a/hw/xbox/nv2a/pgraph/gl/draw.c +++ b/hw/xbox/nv2a/pgraph/gl/draw.c @@ -92,7 +92,6 @@ void pgraph_gl_clear_surface(NV2AState *d, uint32_t parameter) scissor_height = ymax - ymin + 1; pgraph_apply_anti_aliasing_factor(pg, &xmin, &ymin); pgraph_apply_anti_aliasing_factor(pg, &scissor_width, &scissor_height); - ymin = pg->surface_binding_dim.height - (ymin + scissor_height); NV2A_DPRINTF("Translated clear rect to %d,%d - %d,%d\n", xmin, ymin, xmin + scissor_width - 1, ymin + scissor_height - 1); @@ -204,9 +203,10 @@ void pgraph_gl_draw_begin(NV2AState *d) } /* Front-face select */ + /* Winding is reverse here because clip-space y-coordinates are inverted */ glFrontFace(pgraph_reg_r(pg, NV_PGRAPH_SETUPRASTER) & NV_PGRAPH_SETUPRASTER_FRONTFACE - ? GL_CCW : GL_CW); + ? GL_CW : GL_CCW); /* Polygon offset */ /* FIXME: GL implementation-specific, maybe do this in VS? */ @@ -340,7 +340,6 @@ void pgraph_gl_draw_begin(NV2AState *d) pgraph_apply_anti_aliasing_factor(pg, &xmin, &ymin); pgraph_apply_anti_aliasing_factor(pg, &scissor_width, &scissor_height); - ymin = pg->surface_binding_dim.height - (ymin + scissor_height); pgraph_apply_scaling_factor(pg, &xmin, &ymin); pgraph_apply_scaling_factor(pg, &scissor_width, &scissor_height); diff --git a/hw/xbox/nv2a/pgraph/gl/renderer.h b/hw/xbox/nv2a/pgraph/gl/renderer.h index 3529006898..b9074d9644 100644 --- a/hw/xbox/nv2a/pgraph/gl/renderer.h +++ b/hw/xbox/nv2a/pgraph/gl/renderer.h @@ -111,7 +111,6 @@ typedef struct ShaderBinding { GLint vsh_constant_loc[NV2A_VERTEXSHADER_CONSTANTS]; uint32_t vsh_constants[NV2A_VERTEXSHADER_CONSTANTS][4]; - GLint inv_viewport_loc; GLint ltctxa_loc[NV2A_LTCTXA_COUNT]; GLint ltctxb_loc[NV2A_LTCTXB_COUNT]; GLint ltc1_loc[NV2A_LTC1_COUNT]; @@ -122,9 +121,11 @@ typedef struct ShaderBinding { GLint light_infinite_direction_loc[NV2A_MAX_LIGHTS]; GLint light_local_position_loc[NV2A_MAX_LIGHTS]; GLint light_local_attenuation_loc[NV2A_MAX_LIGHTS]; + int specular_power_loc; GLint clip_region_loc[8]; + GLint point_params_loc[8]; GLint material_alpha_loc; } ShaderBinding; diff --git a/hw/xbox/nv2a/pgraph/gl/shaders.c b/hw/xbox/nv2a/pgraph/gl/shaders.c index ad1c21f4a2..742e3c2881 100644 --- a/hw/xbox/nv2a/pgraph/gl/shaders.c +++ b/hw/xbox/nv2a/pgraph/gl/shaders.c @@ -158,7 +158,6 @@ static void update_shader_constant_locations(ShaderBinding *binding) binding->fog_color_loc = glGetUniformLocation(binding->gl_program, "fogColor"); binding->fog_param_loc = glGetUniformLocation(binding->gl_program, "fogParam"); - binding->inv_viewport_loc = glGetUniformLocation(binding->gl_program, "invViewport"); for (int i = 0; i < NV2A_LTCTXA_COUNT; i++) { snprintf(tmp, sizeof(tmp), "ltctxa[%d]", i); binding->ltctxa_loc[i] = glGetUniformLocation(binding->gl_program, tmp); @@ -190,11 +189,19 @@ static void update_shader_constant_locations(ShaderBinding *binding) binding->clip_region_loc[i] = glGetUniformLocation(binding->gl_program, tmp); } + for (int i = 0; i < 8; ++i) { + snprintf(tmp, sizeof(tmp), "pointParams[%d]", i); + binding->point_params_loc[i] = glGetUniformLocation(binding->gl_program, tmp); + } + if (binding->state.fixed_function) { binding->material_alpha_loc = glGetUniformLocation(binding->gl_program, "material_alpha"); + binding->specular_power_loc = + glGetUniformLocation(binding->gl_program, "specularPower"); } else { binding->material_alpha_loc = -1; + binding->specular_power_loc = -1; } } @@ -836,27 +843,9 @@ static void shader_update_constants(PGRAPHState *pg, ShaderBinding *binding, } } - /* estimate the viewport by assuming it matches the surface ... */ - unsigned int aa_width = 1, aa_height = 1; - pgraph_apply_anti_aliasing_factor(pg, &aa_width, &aa_height); - - float m11 = 0.5 * (pg->surface_binding_dim.width/aa_width); - float m22 = -0.5 * (pg->surface_binding_dim.height/aa_height); - float m33 = zmax; - float m41 = *(float*)&pg->vsh_constants[NV_IGRAPH_XF_XFCTX_VPOFF][0]; - float m42 = *(float*)&pg->vsh_constants[NV_IGRAPH_XF_XFCTX_VPOFF][1]; - - float invViewport[16] = { - 1.0/m11, 0, 0, 0, - 0, 1.0/m22, 0, 0, - 0, 0, 1.0/m33, 0, - -1.0+m41/m11, 1.0+m42/m22, 0, 1.0 - }; - - if (binding->inv_viewport_loc != -1) { - glUniformMatrix4fv(binding->inv_viewport_loc, - 1, GL_FALSE, &invViewport[0]); - } + if (binding->specular_power_loc != -1) { + glUniform1f(binding->specular_power_loc, pg->specular_power); + } } /* update vertex program constants */ @@ -935,12 +924,15 @@ static void shader_update_constants(PGRAPHState *pg, ShaderBinding *binding, pgraph_apply_scaling_factor(pg, &x_min, &y_min); pgraph_apply_scaling_factor(pg, &x_max, &y_max); - /* Translate for the GL viewport origin */ - int y_min_xlat = MAX((int)max_gl_height - (int)y_max, 0); - int y_max_xlat = MIN((int)max_gl_height - (int)y_min, max_gl_height); - glUniform4i(r->shader_binding->clip_region_loc[i], - x_min, y_min_xlat, x_max, y_max_xlat); + x_min, y_min, x_max, y_max); + } + + for (i = 0; i < 8; ++i) { + GLint loc = binding->point_params_loc[i]; + if (loc != -1) { + glUniform1f(loc, pg->point_params[i]); + } } if (binding->material_alpha_loc != -1) { diff --git a/hw/xbox/nv2a/pgraph/gl/surface.c b/hw/xbox/nv2a/pgraph/gl/surface.c index 53df185130..ab63dd4e3b 100644 --- a/hw/xbox/nv2a/pgraph/gl/surface.c +++ b/hw/xbox/nv2a/pgraph/gl/surface.c @@ -137,11 +137,7 @@ static void init_render_to_texture(PGRAPHState *pg) "layout(location = 0) out vec4 out_Color;\n" "void main()\n" "{\n" - " vec2 texCoord;\n" - " texCoord.x = gl_FragCoord.x;\n" - " texCoord.y = (surface_size.y - gl_FragCoord.y)\n" - " + (textureSize(tex,0).y - surface_size.y);\n" - " texCoord /= textureSize(tex,0).xy;\n" + " vec2 texCoord = gl_FragCoord.xy / textureSize(tex, 0).xy;\n" " out_Color.rgba = texture(tex, texCoord);\n" "}\n"; @@ -298,7 +294,7 @@ static void render_surface_to_texture_slow(NV2AState *d, size_t bufsize = width * height * surface->fmt.bytes_per_pixel; uint8_t *buf = g_malloc(bufsize); - surface_download_to_buffer(d, surface, false, true, false, buf); + surface_download_to_buffer(d, surface, false, false, false, buf); width = texture_shape->width; height = texture_shape->height; @@ -738,7 +734,7 @@ static void surface_download(NV2AState *d, SurfaceBinding *surface, bool force) nv2a_profile_inc_counter(NV2A_PROF_SURF_DOWNLOAD); - surface_download_to_buffer(d, surface, true, true, true, + surface_download_to_buffer(d, surface, true, false, true, d->vram_ptr + surface->vram_addr); memory_region_set_client_dirty(d->vram, surface->vram_addr, @@ -875,20 +871,26 @@ void pgraph_gl_upload_surface_data(NV2AState *d, SurfaceBinding *surface, surface->fmt.bytes_per_pixel); } - /* FIXME: Replace this flip/scaling */ + /* FIXME: Replace this scaling */ // This is VRAM so we can't do this inplace! - uint8_t *flipped_buf = (uint8_t *)g_malloc( - surface->height * surface->width * surface->fmt.bytes_per_pixel); - unsigned int irow; - for (irow = 0; irow < surface->height; irow++) { - memcpy(&flipped_buf[surface->width * (surface->height - irow - 1) - * surface->fmt.bytes_per_pixel], - &buf[surface->pitch * irow], - surface->width * surface->fmt.bytes_per_pixel); + uint8_t *optimal_buf = buf; + unsigned int optimal_pitch = surface->width * surface->fmt.bytes_per_pixel; + + if (surface->pitch != optimal_pitch) { + optimal_buf = (uint8_t *)g_malloc(surface->height * optimal_pitch); + + uint8_t *src = buf; + uint8_t *dst = optimal_buf; + unsigned int irow; + for (irow = 0; irow < surface->height; irow++) { + memcpy(dst, src, optimal_pitch); + src += surface->pitch; + dst += optimal_pitch; + } } - uint8_t *gl_read_buf = flipped_buf; + uint8_t *gl_read_buf = optimal_buf; unsigned int width = surface->width, height = surface->height; if (pg->surface_scale_factor > 1) { @@ -896,7 +898,7 @@ void pgraph_gl_upload_surface_data(NV2AState *d, SurfaceBinding *surface, pg->scale_buf = (uint8_t *)g_realloc( pg->scale_buf, width * height * surface->fmt.bytes_per_pixel); gl_read_buf = pg->scale_buf; - uint8_t *out = gl_read_buf, *in = flipped_buf; + uint8_t *out = gl_read_buf, *in = optimal_buf; surface_copy_expand(out, in, surface->width, surface->height, surface->fmt.bytes_per_pixel, d->pgraph.surface_scale_factor); @@ -915,7 +917,9 @@ void pgraph_gl_upload_surface_data(NV2AState *d, SurfaceBinding *surface, height, 0, surface->fmt.gl_format, surface->fmt.gl_type, gl_read_buf); glPixelStorei(GL_UNPACK_ALIGNMENT, prev_unpack_alignment); - g_free(flipped_buf); + if (optimal_buf != buf) { + g_free(optimal_buf); + } if (surface->swizzle) { g_free(buf); } diff --git a/hw/xbox/nv2a/pgraph/glsl/psh.c b/hw/xbox/nv2a/pgraph/glsl/psh.c index e3a566a347..893a8fbc9d 100644 --- a/hw/xbox/nv2a/pgraph/glsl/psh.c +++ b/hw/xbox/nv2a/pgraph/glsl/psh.c @@ -949,7 +949,7 @@ static MString* psh_convert(struct PixelShader *ps) switch (ps->tex_modes[i]) { case PS_TEXTUREMODES_NONE: - mstring_append_fmt(vars, "vec4 t%d = vec4(0.0); /* PS_TEXTUREMODES_NONE */\n", + mstring_append_fmt(vars, "vec4 t%d = vec4(0.0, 0.0, 0.0, 1.0); /* PS_TEXTUREMODES_NONE */\n", i); break; case PS_TEXTUREMODES_PROJECT2D: { @@ -1123,8 +1123,8 @@ static MString* psh_convert(struct PixelShader *ps) i, i-2, i-1, i); apply_border_adjustment(ps, vars, i, "dotSTR%d"); - mstring_append_fmt(vars, "vec4 t%d = texture(texSamp%d, dotSTR%d);\n", - i, i, i); + mstring_append_fmt(vars, "vec4 t%d = texture(texSamp%d, %s(dotSTR%d%s));\n", + i, i, tex_remap, i, ps->state.dim_tex[i] == 2 ? ".xy" : ""); break; case PS_TEXTUREMODES_DOT_STR_CUBE: assert(i == 3); diff --git a/hw/xbox/nv2a/pgraph/glsl/vsh-ff.c b/hw/xbox/nv2a/pgraph/glsl/vsh-ff.c index 2412826647..daf821f3f1 100644 --- a/hw/xbox/nv2a/pgraph/glsl/vsh-ff.c +++ b/hw/xbox/nv2a/pgraph/glsl/vsh-ff.c @@ -115,8 +115,6 @@ GLSL_DEFINE(sceneAmbientColor, GLSL_LTCTXA(NV_IGRAPH_XF_LTCTXA_FR_AMB) ".xyz") GLSL_DEFINE(materialEmissionColor, GLSL_LTCTXA(NV_IGRAPH_XF_LTCTXA_CM_COL) ".xyz") "\n" ); - mstring_append_fmt(uniforms, -"%smat4 invViewport;\n", u); /* Skinning */ unsigned int count; @@ -230,10 +228,16 @@ GLSL_DEFINE(materialEmissionColor, GLSL_LTCTXA(NV_IGRAPH_XF_LTCTXA_CM_COL) ".xyz } /* Lighting */ - if (state->lighting) { - + if (!state->lighting) { + mstring_append(body, " oD0 = diffuse;\n"); + mstring_append(body, " oD1 = specular;\n"); + mstring_append(body, " oB0 = backDiffuse;\n"); + mstring_append(body, " oB1 = backSpecular;\n"); + } else { //FIXME: Do 2 passes if we want 2 sided-lighting? + mstring_append_fmt(uniforms, "%sfloat specularPower;\n", u); + static char alpha_source_diffuse[] = "diffuse.a"; static char alpha_source_specular[] = "specular.a"; static char alpha_source_material[] = "material_alpha"; @@ -264,17 +268,17 @@ GLSL_DEFINE(materialEmissionColor, GLSL_LTCTXA(NV_IGRAPH_XF_LTCTXA_CM_COL) ".xyz mstring_append(body, "oD1 = vec4(0.0, 0.0, 0.0, specular.a);\n"); + if (state->local_eye) { + mstring_append(body, + "vec3 VPeye = normalize(eyePosition.xyz / eyePosition.w - tPosition.xyz / tPosition.w);\n" + ); + } + for (i = 0; i < NV2A_MAX_LIGHTS; i++) { if (state->light[i] == LIGHT_OFF) { continue; } - /* FIXME: It seems that we only have to handle the surface colors if - * they are not part of the material [= vertex colors]. - * If they are material the cpu will premultiply light - * colors - */ - mstring_append_fmt(body, "/* Light %d */ {\n", i); if (state->light[i] == LIGHT_LOCAL @@ -285,18 +289,20 @@ GLSL_DEFINE(materialEmissionColor, GLSL_LTCTXA(NV_IGRAPH_XF_LTCTXA_CM_COL) ".xyz "%svec3 lightLocalAttenuation%d;\n", u, i, u, i); mstring_append_fmt(body, - " vec3 VP = lightLocalPosition%d - tPosition.xyz/tPosition.w;\n" + " vec3 tPos = tPosition.xyz/tPosition.w;\n" + " vec3 VP = lightLocalPosition%d - tPos;\n" " float d = length(VP);\n" -//FIXME: if (d > lightLocalRange) { .. don't process this light .. } /* inclusive?! */ - what about directional lights? - " VP = normalize(VP);\n" - " float attenuation = 1.0 / (lightLocalAttenuation%d.x\n" - " + lightLocalAttenuation%d.y * d\n" - " + lightLocalAttenuation%d.z * d * d);\n" - " vec3 halfVector = normalize(VP + eyePosition.xyz / eyePosition.w);\n" /* FIXME: Not sure if eyePosition is correct */ - " float nDotVP = max(0.0, dot(tNormal, VP));\n" - " float nDotHV = max(0.0, dot(tNormal, halfVector));\n", - i, i, i, i); - + " if (d <= lightLocalRange(%d)) {\n" /* FIXME: Double check that range is inclusive */ + " VP = normalize(VP);\n" + " float attenuation = 1.0 / (lightLocalAttenuation%d.x\n" + " + lightLocalAttenuation%d.y * d\n" + " + lightLocalAttenuation%d.z * d * d);\n" + " vec3 halfVector = normalize(VP + %s);\n" + " float nDotVP = max(0.0, dot(tNormal, VP));\n" + " float nDotHV = max(0.0, dot(tNormal, halfVector));\n", + i, i, i, i, i, + state->local_eye ? "VPeye" : "vec3(0.0, 0.0, 0.0)" + ); } switch(state->light[i]) { @@ -309,15 +315,21 @@ GLSL_DEFINE(materialEmissionColor, GLSL_LTCTXA(NV_IGRAPH_XF_LTCTXA_CM_COL) ".xyz "%svec3 lightInfiniteDirection%d;\n", u, i, u, i); mstring_append_fmt(body, - " float attenuation = 1.0;\n" - " float nDotVP = max(0.0, dot(tNormal, normalize(vec3(lightInfiniteDirection%d))));\n" - " float nDotHV = max(0.0, dot(tNormal, vec3(lightInfiniteHalfVector%d)));\n", - i, i); - - /* FIXME: Do specular */ - - /* FIXME: tBackDiffuse */ - + " {\n" + " float attenuation = 1.0;\n" + " vec3 lightDirection = normalize(lightInfiniteDirection%d);\n" + " float nDotVP = max(0.0, dot(tNormal, lightDirection));\n", + i); + if (state->local_eye) { + mstring_append(body, + " float nDotHV = max(0.0, dot(tNormal, normalize(lightDirection + VPeye)));\n" + ); + } else { + mstring_append_fmt(body, + " float nDotHV = max(0.0, dot(tNormal, lightInfiniteHalfVector%d));\n", + i + ); + } break; case LIGHT_LOCAL: /* Everything done already */ @@ -325,18 +337,18 @@ GLSL_DEFINE(materialEmissionColor, GLSL_LTCTXA(NV_IGRAPH_XF_LTCTXA_CM_COL) ".xyz case LIGHT_SPOT: /* https://docs.microsoft.com/en-us/windows/win32/direct3d9/attenuation-and-spotlight-factor#spotlight-factor */ mstring_append_fmt(body, - " vec4 spotDir = lightSpotDirection(%d);\n" - " float invScale = 1/length(spotDir.xyz);\n" - " float cosHalfPhi = -invScale*spotDir.w;\n" - " float cosHalfTheta = invScale + cosHalfPhi;\n" - " float spotDirDotVP = dot(spotDir.xyz, VP);\n" - " float rho = invScale*spotDirDotVP;\n" - " if (rho > cosHalfTheta) {\n" - " } else if (rho <= cosHalfPhi) {\n" - " attenuation = 0.0;\n" - " } else {\n" - " attenuation *= spotDirDotVP + spotDir.w;\n" /* FIXME: lightSpotFalloff */ - " }\n", + " vec4 spotDir = lightSpotDirection(%d);\n" + " float invScale = 1/length(spotDir.xyz);\n" + " float cosHalfPhi = -invScale*spotDir.w;\n" + " float cosHalfTheta = invScale + cosHalfPhi;\n" + " float spotDirDotVP = dot(spotDir.xyz, VP);\n" + " float rho = invScale*spotDirDotVP;\n" + " if (rho > cosHalfTheta) {\n" + " } else if (rho <= cosHalfPhi) {\n" + " attenuation = 0.0;\n" + " } else {\n" + " attenuation *= spotDirDotVP + spotDir.w;\n" /* FIXME: lightSpotFalloff */ + " }\n", i); break; default: @@ -345,46 +357,82 @@ GLSL_DEFINE(materialEmissionColor, GLSL_LTCTXA(NV_IGRAPH_XF_LTCTXA_CM_COL) ".xyz } mstring_append_fmt(body, - " float pf;\n" - " if (nDotVP == 0.0) {\n" - " pf = 0.0;\n" - " } else {\n" - " pf = pow(nDotHV, /* specular(l, m, n, l1, m1, n1) */ 0.001);\n" - " }\n" - " vec3 lightAmbient = lightAmbientColor(%d) * attenuation;\n" - " vec3 lightDiffuse = lightDiffuseColor(%d) * attenuation * nDotVP;\n" - " vec3 lightSpecular = lightSpecularColor(%d) * pf;\n", + " float pf;\n" + " if (nDotVP == 0.0 || nDotHV == 0.0) {\n" + " pf = 0.0;\n" + " } else {\n" + " pf = pow(nDotHV, specularPower);\n" + " }\n" + " vec3 lightAmbient = lightAmbientColor(%d) * attenuation;\n" + " vec3 lightDiffuse = lightDiffuseColor(%d) * attenuation * nDotVP;\n" + " vec3 lightSpecular = lightSpecularColor(%d) * attenuation * pf;\n", i, i, i); mstring_append(body, - " oD0.xyz += lightAmbient;\n"); + " oD0.xyz += lightAmbient;\n"); switch (state->diffuse_src) { case MATERIAL_COLOR_SRC_MATERIAL: mstring_append(body, - " oD0.xyz += lightDiffuse;\n"); + " oD0.xyz += lightDiffuse;\n"); break; case MATERIAL_COLOR_SRC_DIFFUSE: mstring_append(body, - " oD0.xyz += diffuse.xyz * lightDiffuse;\n"); + " oD0.xyz += diffuse.xyz * lightDiffuse;\n"); break; case MATERIAL_COLOR_SRC_SPECULAR: mstring_append(body, - " oD0.xyz += specular.xyz * lightDiffuse;\n"); + " oD0.xyz += specular.xyz * lightDiffuse;\n"); break; } - mstring_append(body, - " oD1.xyz += specular.xyz * lightSpecular;\n"); + switch (state->specular_src) { + case MATERIAL_COLOR_SRC_MATERIAL: + mstring_append(body, + " oD1.xyz += lightSpecular;\n"); + break; + case MATERIAL_COLOR_SRC_DIFFUSE: + mstring_append(body, + " oD1.xyz += diffuse.xyz * lightSpecular;\n"); + break; + case MATERIAL_COLOR_SRC_SPECULAR: + mstring_append(body, + " oD1.xyz += specular.xyz * lightSpecular;\n"); + break; + } - mstring_append(body, "}\n"); + mstring_append(body, " }\n" + "}\n"); + } + + /* TODO: Implement two-sided lighting */ + mstring_append(body, " oB0 = backDiffuse;\n"); + mstring_append(body, " oB1 = backSpecular;\n"); + } + + if (!state->specular_enable) { + mstring_append(body, " oD1 = vec4(0.0, 0.0, 0.0, 1.0);\n"); + mstring_append(body, " oB1 = vec4(0.0, 0.0, 0.0, 1.0);\n"); + } else { + if (!state->separate_specular) { + if (state->lighting) { + mstring_append(body, + " oD0.xyz += oD1.xyz;\n" + " oB0.xyz += oB1.xyz;\n" + ); + } + mstring_append(body, + " oD1 = specular;\n" + " oB1 = backSpecular;\n" + ); + } + if (state->ignore_specular_alpha) { + mstring_append(body, + " oD1.a = 1.0;\n" + " oB1.a = 1.0;\n" + ); } - } else { - mstring_append(body, " oD0 = diffuse;\n"); - mstring_append(body, " oD1 = specular;\n"); } - mstring_append(body, " oB0 = backDiffuse;\n"); - mstring_append(body, " oB1 = backSpecular;\n"); /* Fog */ if (state->fog_enable) { @@ -409,7 +457,7 @@ GLSL_DEFINE(materialEmissionColor, GLSL_LTCTXA(NV_IGRAPH_XF_LTCTXA_CM_COL) ".xyz mstring_append(body, " float fogDistance = fogCoord;\n"); break; default: - assert(false); + assert(!"Invalid foggen mode"); break; } @@ -421,25 +469,24 @@ GLSL_DEFINE(materialEmissionColor, GLSL_LTCTXA(NV_IGRAPH_XF_LTCTXA_CM_COL) ".xyz } mstring_append(body, - " oPos = tPosition * compositeMat;\n" - " oPos.w = clampAwayZeroInf(oPos.w);\n" - " oPos = invViewport * oPos;\n" + " oPos = tPosition * compositeMat;\n" + " oPos.z = oPos.z / clipRange.y;\n" + " oPos.w = clampAwayZeroInf(oPos.w);\n" + " oPos.xy /= oPos.w;\n" + " oPos.xy += c[" stringify(NV_IGRAPH_XF_XFCTX_VPOFF) "].xy;\n" + " oPos.xy = roundScreenCoords(oPos.xy);\n" + " oPos.xy = (2.0f * oPos.xy - surfaceSize) / surfaceSize;\n" + " oPos.xy *= oPos.w;\n" ); - if (state->vulkan) { - mstring_append(body, " oPos.y *= -1;\n"); - } - /* FIXME: Testing */ if (state->point_params_enable) { - mstring_append_fmt( + mstring_append_fmt(uniforms, "%sfloat pointParams[8];\n", u); + mstring_append( body, " float d_e = length(position * modelViewMat0);\n" - " oPts.x = 1/sqrt(%f + %f*d_e + %f*d_e*d_e) + %f;\n", - state->point_params[0], state->point_params[1], state->point_params[2], - state->point_params[6]); - mstring_append_fmt(body, " oPts.x = min(oPts.x*%f + %f, 64.0) * %d;\n", - state->point_params[3], state->point_params[7], + " oPts.x = 1/sqrt(pointParams[0] + pointParams[1] * d_e + pointParams[2] * d_e * d_e) + pointParams[6];\n"); + mstring_append_fmt(body, " oPts.x = min(oPts.x * pointParams[3] + pointParams[7], 64.0) * %d;\n", state->surface_scale_factor); } else { mstring_append_fmt(body, " oPts.x = %f * %d;\n", state->point_size, diff --git a/hw/xbox/nv2a/pgraph/glsl/vsh-prog.c b/hw/xbox/nv2a/pgraph/glsl/vsh-prog.c index 0530e7ea7b..fd48979447 100644 --- a/hw/xbox/nv2a/pgraph/glsl/vsh-prog.c +++ b/hw/xbox/nv2a/pgraph/glsl/vsh-prog.c @@ -639,7 +639,7 @@ static const char* vsh_header = // 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 zero_components = sign(NaNToOne(src0)) * sign(NaNToOne(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" @@ -821,22 +821,12 @@ void pgraph_gen_vsh_prog_glsl(uint16_t version, assert(has_final); mstring_append(body, - /* the shaders leave the result in screen space, while - * opengl expects it in clip space. - * TODO: the pixel-center co-ordinate differences should handled + /* The shaders leave the result in screen space, while OpenGL expects it + * in clip space. */ - " oPos.x = 2.0 * (oPos.x - surfaceSize.x * 0.5) / surfaceSize.x;\n" - ); + " oPos.xy = roundScreenCoords(oPos.xy);\n" + " oPos.xy = (2.0f * oPos.xy - surfaceSize) / surfaceSize;\n" - if (vulkan) { - mstring_append(body, - " oPos.y = 2.0 * oPos.y / surfaceSize.y - 1.0;\n"); - } else { - mstring_append(body, " oPos.y = -2.0 * (oPos.y - surfaceSize.y * 0.5) " - "/ surfaceSize.y;\n"); - } - - mstring_append(body, " oPos.z = oPos.z / clipRange.y;\n" " oPos.w = clampAwayZeroInf(oPos.w);\n" diff --git a/hw/xbox/nv2a/pgraph/glsl/vsh.c b/hw/xbox/nv2a/pgraph/glsl/vsh.c index d92c8b2fb3..b6b7045186 100644 --- a/hw/xbox/nv2a/pgraph/glsl/vsh.c +++ b/hw/xbox/nv2a/pgraph/glsl/vsh.c @@ -34,7 +34,6 @@ MString *pgraph_gen_vsh_glsl(const ShaderState *state, bool prefix_outputs) mstring_append_fmt(output, "#version %d\n\n", state->vulkan ? 450 : 400); MString *header = mstring_from_str(""); - MString *uniforms = mstring_from_str(""); const char *u = state->vulkan ? "" : "uniform "; // FIXME: Remove @@ -82,6 +81,17 @@ MString *pgraph_gen_vsh_glsl(const ShaderState *state, bool prefix_outputs) " t = clamp(t, uintBitsToFloat(0xDF800000), uintBitsToFloat(0x9F800000));\n" " }\n" " return t;\n" + "}\n" + "\n" + "vec4 NaNToOne(vec4 src) {\n" + " return mix(src, vec4(1.0), isnan(src));\n" + "}\n" + "\n" + // Xbox NV2A rasterizer appears to have 4 bit precision fixed-point + // fractional part and to convert floating-point coordinates by + // by truncating (not flooring). + "vec2 roundScreenCoords(vec2 pos) {\n" + " return trunc(pos * 16.0f) / 16.0f;\n" "}\n"); pgraph_get_glsl_vtx_header(header, state->vulkan, state->smooth_shading, @@ -129,6 +139,7 @@ MString *pgraph_gen_vsh_glsl(const ShaderState *state, bool prefix_outputs) } } } + mstring_append(header, "\n"); MString *body = mstring_from_str("void main() {\n"); @@ -233,19 +244,17 @@ MString *pgraph_gen_vsh_glsl(const ShaderState *state, bool prefix_outputs) break; } - mstring_append(body, " oFog.xyzw = vec4(fogFactor);\n"); + mstring_append(body, " oFog = NaNToOne(vec4(fogFactor));\n"); } else { /* FIXME: Is the fog still calculated / passed somehow?! */ - mstring_append(body, " oFog.xyzw = vec4(1.0);\n"); + mstring_append(body, " oFog = vec4(1.0);\n"); } /* Set outputs */ mstring_append(body, "\n" - " vtxD0 = clamp(oD0, 0.0, 1.0);\n" - " vtxD1 = clamp(oD1, 0.0, 1.0);\n" - " vtxB0 = clamp(oB0, 0.0, 1.0);\n" - " vtxB1 = clamp(oB1, 0.0, 1.0);\n" + " vtxD0 = clamp(NaNToOne(oD0), 0.0, 1.0);\n" + " vtxB0 = clamp(NaNToOne(oB0), 0.0, 1.0);\n" " vtxFog = oFog.x;\n" " vtxT0 = oT0;\n" " vtxT1 = oT1;\n" @@ -254,6 +263,25 @@ MString *pgraph_gen_vsh_glsl(const ShaderState *state, bool prefix_outputs) " gl_PointSize = oPts.x;\n" ); + if (state->specular_enable) { + mstring_append(body, + " vtxD1 = clamp(NaNToOne(oD1), 0.0, 1.0);\n" + " vtxB1 = clamp(NaNToOne(oB1), 0.0, 1.0);\n" + ); + + if (state->ignore_specular_alpha) { + mstring_append(body, + " vtxD1.w = 1.0;\n" + " vtxB1.w = 1.0;\n" + ); + } + } else { + mstring_append(body, + " vtxD1 = vec4(0.0, 0.0, 0.0, 1.0);\n" + " vtxB1 = vec4(0.0, 0.0, 0.0, 1.0);\n" + ); + } + if (state->vulkan) { mstring_append(body, " gl_Position = oPos;\n" diff --git a/hw/xbox/nv2a/pgraph/methods.h b/hw/xbox/nv2a/pgraph/methods.h.inc similarity index 95% rename from hw/xbox/nv2a/pgraph/methods.h rename to hw/xbox/nv2a/pgraph/methods.h.inc index 380df07ea5..2f7b7b4198 100644 --- a/hw/xbox/nv2a/pgraph/methods.h +++ b/hw/xbox/nv2a/pgraph/methods.h.inc @@ -27,6 +27,7 @@ DEF_METHOD(NV097, SET_COMBINER_SPECULAR_FOG_CW0) DEF_METHOD(NV097, SET_COMBINER_SPECULAR_FOG_CW1) DEF_METHOD_CASE_4(NV097, SET_TEXTURE_ADDRESS, 64) DEF_METHOD(NV097, SET_CONTROL0) +DEF_METHOD(NV097, SET_LIGHT_CONTROL) DEF_METHOD(NV097, SET_COLOR_MATERIAL) DEF_METHOD(NV097, SET_FOG_MODE) DEF_METHOD(NV097, SET_FOG_GEN_MODE) @@ -78,6 +79,7 @@ DEF_METHOD(NV097, SET_FRONT_FACE) DEF_METHOD(NV097, SET_NORMALIZATION_ENABLE) DEF_METHOD_RANGE(NV097, SET_MATERIAL_EMISSION, 3) DEF_METHOD(NV097, SET_MATERIAL_ALPHA) +DEF_METHOD(NV097, SET_SPECULAR_ENABLE) DEF_METHOD(NV097, SET_LIGHT_ENABLE_MASK) DEF_METHOD_CASE_4(NV097, SET_TEXGEN_S, 16) DEF_METHOD_CASE_4(NV097, SET_TEXGEN_T, 16) @@ -94,6 +96,7 @@ DEF_METHOD_RANGE(NV097, SET_FOG_PARAMS, 3) DEF_METHOD_RANGE(NV097, SET_TEXGEN_PLANE_S, 4*4*4) DEF_METHOD(NV097, SET_TEXGEN_VIEW_MODEL) DEF_METHOD_RANGE(NV097, SET_FOG_PLANE, 4) +DEF_METHOD_RANGE(NV097, SET_SPECULAR_PARAMS, 6) DEF_METHOD_RANGE(NV097, SET_SCENE_AMBIENT_COLOR, 3) DEF_METHOD_RANGE(NV097, SET_VIEWPORT_OFFSET, 4) DEF_METHOD_RANGE(NV097, SET_POINT_PARAMS, 8) @@ -133,6 +136,11 @@ DEF_METHOD_RANGE(NV097, SET_TEXCOORD3_2F, 2) DEF_METHOD_RANGE(NV097, SET_TEXCOORD3_4F, 4) DEF_METHOD_RANGE(NV097, SET_TEXCOORD3_2S, 1) DEF_METHOD_RANGE(NV097, SET_TEXCOORD3_4S, 2) +DEF_METHOD(NV097, SET_FOG_COORD) +DEF_METHOD(NV097, SET_WEIGHT1F) +DEF_METHOD_RANGE(NV097, SET_WEIGHT2F, 2) +DEF_METHOD_RANGE(NV097, SET_WEIGHT3F, 3) +DEF_METHOD_RANGE(NV097, SET_WEIGHT4F, 4) DEF_METHOD_RANGE(NV097, SET_VERTEX_DATA_ARRAY_FORMAT, 16) DEF_METHOD_RANGE(NV097, SET_VERTEX_DATA_ARRAY_OFFSET, 16) DEF_METHOD(NV097, SET_LOGIC_OP_ENABLE) @@ -176,6 +184,7 @@ DEF_METHOD(NV097, CLEAR_SURFACE) DEF_METHOD(NV097, SET_CLEAR_RECT_HORIZONTAL) DEF_METHOD(NV097, SET_CLEAR_RECT_VERTICAL) DEF_METHOD_RANGE(NV097, SET_SPECULAR_FOG_FACTOR, 2) +DEF_METHOD_RANGE(NV097, SET_SPECULAR_PARAMS_BACK, 6) DEF_METHOD(NV097, SET_SHADER_CLIP_PLANE_MODE) DEF_METHOD_RANGE(NV097, SET_COMBINER_COLOR_OCW, 8) DEF_METHOD(NV097, SET_COMBINER_CONTROL) diff --git a/hw/xbox/nv2a/pgraph/pgraph.c b/hw/xbox/nv2a/pgraph/pgraph.c index 9db475796e..fc6420fff0 100644 --- a/hw/xbox/nv2a/pgraph/pgraph.c +++ b/hw/xbox/nv2a/pgraph/pgraph.c @@ -19,7 +19,9 @@ * License along with this library; if not, see . */ -#include "../nv2a_int.h" +#include + +#include "hw/xbox/nv2a/nv2a_int.h" #include "ui/xemu-notifications.h" #include "ui/xemu-settings.h" #include "util.h" @@ -431,7 +433,7 @@ unsigned int nv2a_get_surface_scale_factor(void) #define DEF_METHOD_CASE_4_OFFSET(gclass, name, offset, stride) /* Drop */ #define DEF_METHOD_CASE_4(gclass, name, stride) \ DEF_METHOD_PROTO(gclass, name); -#include "methods.h" +#include "methods.h.inc" #undef DEF_METHOD #undef DEF_METHOD_RANGE #undef DEF_METHOD_CASE_4_OFFSET @@ -485,7 +487,7 @@ static const struct { }, #define DEF_METHOD_CASE_4(gclass, name, stride) \ DEF_METHOD_CASE_4_OFFSET(gclass, name, 0, stride) -#include "methods.h" +#include "methods.h.inc" #undef DEF_METHOD #undef DEF_METHOD_RANGE #undef DEF_METHOD_CASE_4_OFFSET @@ -504,7 +506,7 @@ static const struct { #define DEF_METHOD_CASE_4(gclass, name, stride) \ static const size_t METHOD_RANGE_END_NAME(gclass, name) = \ METHOD_ADDR(gclass, name) + 4*stride; -#include "methods.h" +#include "methods.h.inc" #undef DEF_METHOD #undef DEF_METHOD_RANGE #undef DEF_METHOD_CASE_4_OFFSET @@ -1075,6 +1077,18 @@ DEF_METHOD(NV097, SET_CONTROL0) z_perspective); } +DEF_METHOD(NV097, SET_LIGHT_CONTROL) +{ + PG_SET_MASK(NV_PGRAPH_CSV0_C, NV_PGRAPH_CSV0_C_SEPARATE_SPECULAR, + (parameter & NV097_SET_LIGHT_CONTROL_SEPARATE_SPECULAR) != 0); + + PG_SET_MASK(NV_PGRAPH_CSV0_C, NV_PGRAPH_CSV0_C_LOCALEYE, + (parameter & NV097_SET_LIGHT_CONTROL_LOCALEYE) != 0); + + PG_SET_MASK(NV_PGRAPH_CSV0_C, NV_PGRAPH_CSV0_C_ALPHA_FROM_MATERIAL_SPECULAR, + (parameter & NV097_SET_LIGHT_CONTROL_ALPHA_FROM_MATERIAL_SPECULAR) != 0); +} + DEF_METHOD(NV097, SET_COLOR_MATERIAL) { PG_SET_MASK(NV_PGRAPH_CSV0_C, NV_PGRAPH_CSV0_C_EMISSION, @@ -1619,6 +1633,11 @@ DEF_METHOD(NV097, SET_MATERIAL_ALPHA) pg->material_alpha = *(float*)¶meter; } +DEF_METHOD(NV097, SET_SPECULAR_ENABLE) +{ + PG_SET_MASK(NV_PGRAPH_CSV0_C, NV_PGRAPH_CSV0_C_SPECULAR_ENABLE, parameter); +} + DEF_METHOD(NV097, SET_LIGHT_ENABLE_MASK) { PG_SET_MASK(NV_PGRAPH_CSV0_D, NV_PGRAPH_CSV0_D_LIGHTS, parameter); @@ -1786,6 +1805,113 @@ DEF_METHOD_INC(NV097, SET_FOG_PLANE) pg->vsh_constants_dirty[NV_IGRAPH_XF_XFCTX_FOG] = true; } +struct CurveCoefficients { + float a; + float b; + float c; +}; + +static const struct CurveCoefficients curve_coefficients[] = { + {1.000108475163, -9.838607076280, 54.829089549713}, + {1.199164441703, -3.292603784852, 7.799987995214}, + {8.653441252033, 29.189473787191, 43.586027561823}, + {-531.307758450301, 117.398468683934, 113.155490738338}, + {-4.662713151292, 1.221108944572, 1.217360986939}, + {-124.435242105211, 35.401219563514, 35.408114377045}, + {10672560.259502287954, 21565843.555823743343, 10894794.336297152564}, + {-51973801.463933646679, -104199997.554352939129, -52225454.356278456748}, + {972270.324080004124, 2025882.096547174733, 1054898.052467488218}, +}; + +static const float kCoefficient0StepPoints[] = { + -0.022553957999, // power = 1.25 + -0.421539008617, // power = 4.00 + -0.678715527058, // power = 9.00 + -0.838916420937, // power = 20.00 + -0.961754500866, // power = 90.00 + -0.990773200989, // power = 375.00 + -0.994858562946, // power = 650.00 + -0.996561050415, // power = 1000.00 + -0.999547004700, // power = 1250.00 +}; + +static float reconstruct_quadratic(float c0, const struct CurveCoefficients *coefficients) { + return coefficients->a + coefficients->b * c0 + coefficients->c * c0 * c0; +} + +static float reconstruct_saturation_growth_rate(float c0, const struct CurveCoefficients *coefficients) { + return (coefficients->a * c0) / (coefficients->b + coefficients->c * c0); +} + +static float (* const reconstruct_func_map[])(float, const struct CurveCoefficients *) = { + reconstruct_quadratic, // 1.0..1.25 max error 0.01 % + reconstruct_quadratic, // 1.25..4.0 max error 2.2 % + reconstruct_quadratic, // 4.0..9.0 max error 2.3 % + reconstruct_saturation_growth_rate, // 9.0..20.0 max error 1.4 % + reconstruct_saturation_growth_rate, // 20.0..90.0 max error 2.1 % + reconstruct_saturation_growth_rate, // 90.0..375.0 max error 2.8% + reconstruct_quadratic, // 375..650 max error 1.0 % + reconstruct_quadratic, // 650..1000 max error 1.7% + reconstruct_quadratic, // 1000..1250 max error 1.0% +}; + +static float reconstruct_specular_power(const float *params) { + // See https://github.com/dracc/xgu/blob/db3172d8c983629f0dc971092981846da22438ae/xgux.h#L279 + + // Values < 1.0 will result in a positive c1 and (c2 - c0 * 2) will be very + // close to the original value. + if (params[1] > 0.0f && params[2] < 1.0f) { + return params[2] - (params[0] * 2.0f); + } + + float c0 = params[0]; + float c3 = params[3]; + // FIXME: This handling is not correct, but is distinct without crashing. + // It does not appear possible for a DirectX-generated value to be positive, + // so while this differs from hardware behavior, it may be irrelevant in + // practice. + if (c0 > 0.0f || c3 > 0.0f) { + return 0.0001f; + } + + float reconstructed_power = 0.f; + for (uint32_t i = 0; i < sizeof(kCoefficient0StepPoints) / sizeof(kCoefficient0StepPoints[0]); ++i) { + if (c0 > kCoefficient0StepPoints[i]) { + reconstructed_power = reconstruct_func_map[i](c0, &curve_coefficients[i]); + break; + } + } + + float reconstructed_half_power = 0.f; + for (uint32_t i = 0; i < sizeof(kCoefficient0StepPoints) / sizeof(kCoefficient0StepPoints[0]); ++i) { + if (c3 > kCoefficient0StepPoints[i]) { + reconstructed_half_power = reconstruct_func_map[i](c3, &curve_coefficients[i]); + break; + } + } + + // The range can be extended beyond 1250 by using the half power params. This + // will only work for DirectX generated values, arbitrary params could + // erroneously trigger this. + // + // There are some very low power (~1) values that have inverted powers, but + // they are easily identified by comparatively high c0 parameters. + if (reconstructed_power == 0.f || (reconstructed_half_power > reconstructed_power && c0 < -0.1f)) { + return reconstructed_half_power * 2.f; + } + + return reconstructed_power; +} + +DEF_METHOD_INC(NV097, SET_SPECULAR_PARAMS) +{ + int slot = (method - NV097_SET_SPECULAR_PARAMS) / 4; + pg->specular_params[slot] = *(float *)¶meter; + if (slot == 5) { + pg->specular_power = reconstruct_specular_power(pg->specular_params); + } +} + DEF_METHOD_INC(NV097, SET_SCENE_AMBIENT_COLOR) { int slot = (method - NV097_SET_SCENE_AMBIENT_COLOR) / 4; @@ -2005,6 +2131,26 @@ DEF_METHOD_INC(NV097, SET_VERTEX4F) } } +DEF_METHOD(NV097, SET_FOG_COORD) +{ + VertexAttribute *attribute = &pg->vertex_attributes[NV2A_VERTEX_ATTR_FOG]; + pgraph_allocate_inline_buffer_vertices(pg, NV2A_VERTEX_ATTR_FOG); + attribute->inline_value[0] = *(float*)¶meter; + attribute->inline_value[1] = attribute->inline_value[0]; + attribute->inline_value[2] = attribute->inline_value[0]; + attribute->inline_value[3] = attribute->inline_value[0]; +} + +DEF_METHOD(NV097, SET_WEIGHT1F) +{ + VertexAttribute *attribute = &pg->vertex_attributes[NV2A_VERTEX_ATTR_WEIGHT]; + pgraph_allocate_inline_buffer_vertices(pg, NV2A_VERTEX_ATTR_WEIGHT); + attribute->inline_value[0] = *(float*)¶meter; + attribute->inline_value[1] = 0.f; + attribute->inline_value[2] = 0.f; + attribute->inline_value[3] = 1.f; +} + DEF_METHOD_INC(NV097, SET_NORMAL3S) { int slot = (method - NV097_SET_NORMAL3S) / 4; @@ -2139,7 +2285,6 @@ DEF_METHOD_INC(NV097, SET_TEXCOORD1_4F) SET_VERTEX_ATTRIBUTE_F(NV097_SET_TEXCOORD1_4F, NV2A_VERTEX_ATTR_TEXTURE1); } - DEF_METHOD_INC(NV097, SET_TEXCOORD2_4F) { SET_VERTEX_ATTRIBUTE_F(NV097_SET_TEXCOORD2_4F, NV2A_VERTEX_ATTR_TEXTURE2); @@ -2150,8 +2295,34 @@ DEF_METHOD_INC(NV097, SET_TEXCOORD3_4F) SET_VERTEX_ATTRIBUTE_F(NV097_SET_TEXCOORD3_4F, NV2A_VERTEX_ATTR_TEXTURE3); } +DEF_METHOD_INC(NV097, SET_WEIGHT4F) +{ + SET_VERTEX_ATTRIBUTE_F(NV097_SET_WEIGHT4F, NV2A_VERTEX_ATTR_WEIGHT); +} + #undef SET_VERTEX_ATTRIBUTE_F +DEF_METHOD_INC(NV097, SET_WEIGHT2F) +{ + int slot = (method - NV097_SET_WEIGHT2F) / 4; + VertexAttribute *attribute = + &pg->vertex_attributes[NV2A_VERTEX_ATTR_WEIGHT]; + pgraph_allocate_inline_buffer_vertices(pg, NV2A_VERTEX_ATTR_WEIGHT); + attribute->inline_value[slot] = *(float*)¶meter; + attribute->inline_value[2] = 0.0f; + attribute->inline_value[3] = 1.0f; +} + +DEF_METHOD_INC(NV097, SET_WEIGHT3F) +{ + int slot = (method - NV097_SET_WEIGHT3F) / 4; + VertexAttribute *attribute = + &pg->vertex_attributes[NV2A_VERTEX_ATTR_WEIGHT]; + pgraph_allocate_inline_buffer_vertices(pg, NV2A_VERTEX_ATTR_WEIGHT); + attribute->inline_value[slot] = *(float*)¶meter; + attribute->inline_value[3] = 1.0f; +} + #define SET_VERTEX_ATRIBUTE_TEX_2F(command, attr_index) \ do { \ int slot = (method - (command)) / 4; \ @@ -2521,7 +2692,11 @@ DEF_METHOD(NV097, DRAW_ARRAYS) int32_t count = GET_MASK(parameter, NV097_DRAW_ARRAYS_COUNT) + 1; if (pg->inline_elements_length) { - /* FIXME: Determine HW behavior for overflow case. */ + /* FIXME: HW throws an exception if the start index is > 0xFFFF. This + * would prevent this assert from firing for any reasonable choice of + * NV2A_MAX_BATCH_LENGTH (which must be larger to accommodate + * NV097_INLINE_ARRAY anyway) + */ assert((pg->inline_elements_length + count) < NV2A_MAX_BATCH_LENGTH); assert(!pg->draw_arrays_prevent_connect); @@ -2723,6 +2898,15 @@ DEF_METHOD_INC(NV097, SET_SPECULAR_FOG_FACTOR) pgraph_reg_w(pg, NV_PGRAPH_SPECFOGFACTOR0 + slot*4, parameter); } +DEF_METHOD_INC(NV097, SET_SPECULAR_PARAMS_BACK) +{ + int slot = (method - NV097_SET_SPECULAR_PARAMS_BACK) / 4; + pg->specular_params_back[slot] = *(float *)¶meter; + if (slot == 5) { + pg->specular_power_back = reconstruct_specular_power(pg->specular_params_back); + } +} + DEF_METHOD(NV097, SET_SHADER_CLIP_PLANE_MODE) { pgraph_reg_w(pg, NV_PGRAPH_SHADERCLIPMODE, parameter); @@ -3022,4 +3206,3 @@ void pgraph_pre_shutdown_wait(NV2AState *d) PGRAPHState *pg = &d->pgraph; pg->renderer->ops.pre_shutdown_wait(d); } - diff --git a/hw/xbox/nv2a/pgraph/pgraph.h b/hw/xbox/nv2a/pgraph/pgraph.h index 64b671e71d..c74e370bba 100644 --- a/hw/xbox/nv2a/pgraph/pgraph.h +++ b/hw/xbox/nv2a/pgraph/pgraph.h @@ -197,6 +197,11 @@ typedef struct PGRAPHState { float light_local_position[NV2A_MAX_LIGHTS][3]; float light_local_attenuation[NV2A_MAX_LIGHTS][3]; + float specular_params[6]; + float specular_power; + float specular_params_back[6]; + float specular_power_back; + float point_params[8]; VertexAttribute vertex_attributes[NV2A_VERTEXSHADER_ATTRIBUTES]; diff --git a/hw/xbox/nv2a/pgraph/profile.c b/hw/xbox/nv2a/pgraph/profile.c index 69a1b5bfbd..295042010d 100644 --- a/hw/xbox/nv2a/pgraph/profile.c +++ b/hw/xbox/nv2a/pgraph/profile.c @@ -17,7 +17,7 @@ * License along with this library; if not, see . */ -#include "../nv2a_int.h" +#include "hw/xbox/nv2a/nv2a_int.h" NV2AStats g_nv2a_stats; diff --git a/hw/xbox/nv2a/pgraph/rdi.c b/hw/xbox/nv2a/pgraph/rdi.c index 297c7a67c0..f540b240c6 100644 --- a/hw/xbox/nv2a/pgraph/rdi.c +++ b/hw/xbox/nv2a/pgraph/rdi.c @@ -19,7 +19,7 @@ * License along with this library; if not, see . */ -#include "../nv2a_int.h" +#include "hw/xbox/nv2a/nv2a_int.h" uint32_t pgraph_rdi_read(PGRAPHState *pg, unsigned int select, unsigned int address) diff --git a/hw/xbox/nv2a/pgraph/shaders.c b/hw/xbox/nv2a/pgraph/shaders.c index 6e13f2084c..cddbf450f0 100644 --- a/hw/xbox/nv2a/pgraph/shaders.c +++ b/hw/xbox/nv2a/pgraph/shaders.c @@ -69,6 +69,8 @@ ShaderState pgraph_get_shader_state(PGRAPHState *pg) pgraph_reg_r(pg, NV_PGRAPH_SHADOWCTL), NV_PGRAPH_SHADOWCTL_SHADOW_ZFUNC); state.fixed_function = fixed_function; + state.specular_enable = GET_MASK(pgraph_reg_r(pg, NV_PGRAPH_CSV0_C), + NV_PGRAPH_CSV0_C_SPECULAR_ENABLE); /* fixed function stuff */ if (fixed_function) { @@ -90,6 +92,16 @@ ShaderState pgraph_get_shader_state(PGRAPHState *pg) pgraph_reg_r(pg, NV_PGRAPH_CSV0_C), NV_PGRAPH_CSV0_C_SPECULAR); } + state.separate_specular = GET_MASK( + pgraph_reg_r(pg, NV_PGRAPH_CSV0_C), NV_PGRAPH_CSV0_C_SEPARATE_SPECULAR); + state.ignore_specular_alpha = !GET_MASK( + pgraph_reg_r(pg, NV_PGRAPH_CSV0_C), NV_PGRAPH_CSV0_C_ALPHA_FROM_MATERIAL_SPECULAR); + state.local_eye = GET_MASK( + pgraph_reg_r(pg, NV_PGRAPH_CSV0_C), NV_PGRAPH_CSV0_C_LOCALEYE); + + state.specular_power = pg->specular_power; + state.specular_power_back = pg->specular_power_back; + /* vertex program stuff */ state.vertex_program = vertex_program, state.z_perspective = pgraph_reg_r(pg, NV_PGRAPH_CONTROL_0) & diff --git a/hw/xbox/nv2a/pgraph/shaders.h b/hw/xbox/nv2a/pgraph/shaders.h index 71febe2e2f..4cc07e3a9d 100644 --- a/hw/xbox/nv2a/pgraph/shaders.h +++ b/hw/xbox/nv2a/pgraph/shaders.h @@ -79,10 +79,17 @@ typedef struct ShaderState { enum MaterialColorSource diffuse_src; enum MaterialColorSource specular_src; + bool separate_specular; + bool ignore_specular_alpha; + bool local_eye; + float specular_power; + float specular_power_back; + bool lighting; enum VshLight light[NV2A_MAX_LIGHTS]; bool fixed_function; + bool specular_enable; /* vertex program */ bool vertex_program; diff --git a/hw/xbox/nv2a/pgraph/vk/debug.c b/hw/xbox/nv2a/pgraph/vk/debug.c index 5c31c9f119..88327b0cca 100644 --- a/hw/xbox/nv2a/pgraph/vk/debug.c +++ b/hw/xbox/nv2a/pgraph/vk/debug.c @@ -25,6 +25,8 @@ #endif #ifdef CONFIG_RENDERDOC +#include "trace/control.h" + #pragma GCC diagnostic ignored "-Wstrict-prototypes" #include "thirdparty/renderdoc_app.h" #endif @@ -46,11 +48,21 @@ void pgraph_vk_debug_frame_terminator(void) PGRAPHVkState *r = g_nv2a->pgraph.vk_renderer_state; if (rdoc_api->IsTargetControlConnected()) { - if (rdoc_api->IsFrameCapturing()) { + bool capturing = rdoc_api->IsFrameCapturing(); + if (capturing && renderdoc_capture_frames == 0) { rdoc_api->EndFrameCapture(RENDERDOC_DEVICEPOINTER_FROM_VKINSTANCE(r->instance), 0); + if (renderdoc_trace_frames) { + trace_enable_events("-nv2a_pgraph_*"); + renderdoc_trace_frames = false; + } } if (renderdoc_capture_frames > 0) { - rdoc_api->StartFrameCapture(RENDERDOC_DEVICEPOINTER_FROM_VKINSTANCE(r->instance), 0); + if (!capturing) { + if (renderdoc_trace_frames) { + trace_enable_events("nv2a_pgraph_*"); + } + rdoc_api->StartFrameCapture(RENDERDOC_DEVICEPOINTER_FROM_VKINSTANCE(r->instance), 0); + } --renderdoc_capture_frames; } } diff --git a/hw/xbox/nv2a/pgraph/vk/renderer.h b/hw/xbox/nv2a/pgraph/vk/renderer.h index eb0726ac2f..91bbf0f31d 100644 --- a/hw/xbox/nv2a/pgraph/vk/renderer.h +++ b/hw/xbox/nv2a/pgraph/vk/renderer.h @@ -179,7 +179,6 @@ typedef struct ShaderBinding { int vsh_constant_loc; uint32_t vsh_constants[NV2A_VERTEXSHADER_CONSTANTS][4]; - int inv_viewport_loc; int ltctxa_loc; int ltctxb_loc; int ltc1_loc; @@ -190,9 +189,10 @@ typedef struct ShaderBinding { int light_infinite_direction_loc[NV2A_MAX_LIGHTS]; int light_local_position_loc[NV2A_MAX_LIGHTS]; int light_local_attenuation_loc[NV2A_MAX_LIGHTS]; + int specular_power_loc; + int point_params_loc; int clip_region_loc; - int material_alpha_loc; int uniform_attrs_loc; diff --git a/hw/xbox/nv2a/pgraph/vk/shaders.c b/hw/xbox/nv2a/pgraph/vk/shaders.c index 421a81ba60..0a6e8a2b5c 100644 --- a/hw/xbox/nv2a/pgraph/vk/shaders.c +++ b/hw/xbox/nv2a/pgraph/vk/shaders.c @@ -283,8 +283,6 @@ static void update_shader_constant_locations(ShaderBinding *binding) binding->fog_param_loc = uniform_index(&binding->vertex->uniforms, "fogParam"); - binding->inv_viewport_loc = - uniform_index(&binding->vertex->uniforms, "invViewport"); binding->ltctxa_loc = uniform_index(&binding->vertex->uniforms, "ltctxa"); binding->ltctxb_loc = uniform_index(&binding->vertex->uniforms, "ltctxb"); binding->ltc1_loc = uniform_index(&binding->vertex->uniforms, "ltc1"); @@ -308,11 +306,17 @@ static void update_shader_constant_locations(ShaderBinding *binding) binding->clip_region_loc = uniform_index(&binding->fragment->uniforms, "clipRegion"); + binding->point_params_loc = + uniform_index(&binding->vertex->uniforms, "pointParams"); + binding->material_alpha_loc = uniform_index(&binding->vertex->uniforms, "material_alpha"); binding->uniform_attrs_loc = uniform_index(&binding->vertex->uniforms, "inlineValue"); + + binding->specular_power_loc = + uniform_index(&binding->vertex->uniforms, "specularPower"); } static void shader_cache_entry_init(Lru *lru, LruNode *node, void *state) @@ -607,25 +611,9 @@ static void shader_update_constants(PGRAPHState *pg, ShaderBinding *binding, } } - /* estimate the viewport by assuming it matches the surface ... */ - unsigned int aa_width = 1, aa_height = 1; - pgraph_apply_anti_aliasing_factor(pg, &aa_width, &aa_height); - - float m11 = 0.5 * (pg->surface_binding_dim.width / aa_width); - float m22 = -0.5 * (pg->surface_binding_dim.height / aa_height); - float m33 = zmax; - float m41 = *(float *)&pg->vsh_constants[NV_IGRAPH_XF_XFCTX_VPOFF][0]; - float m42 = *(float *)&pg->vsh_constants[NV_IGRAPH_XF_XFCTX_VPOFF][1]; - - float invViewport[16] = { - 1.0 / m11, 0, 0, 0, 0, 1.0 / m22, 0, - 0, 0, 0, 1.0 / m33, 0, -1.0 + m41 / m11, 1.0 + m42 / m22, - 0, 1.0 - }; - - if (binding->inv_viewport_loc != -1) { - uniformMatrix4fv(&binding->vertex->uniforms, - binding->inv_viewport_loc, &invViewport[0]); + if (binding->specular_power_loc != -1) { + uniform1f(&binding->vertex->uniforms, binding->specular_power_loc, + pg->specular_power); } } @@ -712,6 +700,11 @@ static void shader_update_constants(PGRAPHState *pg, ShaderBinding *binding, uniform1iv(&binding->fragment->uniforms, binding->clip_region_loc, 8 * 4, (void *)clip_regions); + if (binding->point_params_loc != -1) { + uniform1iv(&binding->vertex->uniforms, binding->point_params_loc, + ARRAY_SIZE(pg->point_params), (void *)pg->point_params); + } + if (binding->material_alpha_loc != -1) { uniform1f(&binding->vertex->uniforms, binding->material_alpha_loc, pg->material_alpha); diff --git a/hw/xbox/nv2a/pgraph/vsh.h b/hw/xbox/nv2a/pgraph/vsh.h index 405b6c9aa6..36cac16edf 100644 --- a/hw/xbox/nv2a/pgraph/vsh.h +++ b/hw/xbox/nv2a/pgraph/vsh.h @@ -55,8 +55,6 @@ enum VshFoggen { FOGGEN_RADIAL, FOGGEN_PLANAR, FOGGEN_ABS_PLANAR, - FOGGEN_ERROR4, - FOGGEN_ERROR5, FOGGEN_FOG_X }; diff --git a/hw/xbox/nvnet_regs.h b/hw/xbox/nvnet_regs.h index 992f609937..7a1744f7bc 100644 --- a/hw/xbox/nvnet_regs.h +++ b/hw/xbox/nvnet_regs.h @@ -68,6 +68,8 @@ #ifndef HW_NVNET_REGS_H #define HW_NVNET_REGS_H +// clang-format on + #define DEV_NEED_LASTPACKET1 0x0001 #define DEV_IRQMASK_1 0x0002 #define DEV_IRQMASK_2 0x0004 @@ -291,4 +293,6 @@ enum { #define LPA_LPACK 0x4000 /* Link partner acked us */ #define LPA_NPAGE 0x8000 /* Next page bit */ +// clang-format off + #endif /* HW_NVNET_REGS_H */ diff --git a/include/migration/vmstate.h b/include/migration/vmstate.h index f313f2f408..eee7359f2d 100644 --- a/include/migration/vmstate.h +++ b/include/migration/vmstate.h @@ -1085,6 +1085,9 @@ extern const VMStateInfo vmstate_info_qlist; #define VMSTATE_UINT32_SUB_ARRAY(_f, _s, _start, _num) \ VMSTATE_SUB_ARRAY(_f, _s, _start, _num, 0, vmstate_info_uint32, uint32_t) +#define VMSTATE_UINT32_SUB_ARRAY_V(_f, _s, _start, _num, _v) \ + VMSTATE_SUB_ARRAY(_f, _s, _start, _num, _v, vmstate_info_uint32, uint32_t) + #define VMSTATE_UINT32_2DARRAY(_f, _s, _n1, _n2) \ VMSTATE_UINT32_2DARRAY_V(_f, _s, _n1, _n2, 0) diff --git a/meson.build b/meson.build index 78c1f84fe4..81bfb851e6 100644 --- a/meson.build +++ b/meson.build @@ -3772,6 +3772,7 @@ if have_system 'hw/remote', 'hw/xbox/nv2a', 'hw/xbox/mcpx', + 'hw/xbox/mcpx/dsp', 'hw/xbox', ] endif diff --git a/scripts/bump-subproject-wraps.py b/scripts/bump-subproject-wraps.py new file mode 100644 index 0000000000..f9768b366a --- /dev/null +++ b/scripts/bump-subproject-wraps.py @@ -0,0 +1,197 @@ +#!/usr/bin/env python +# /// script +# dependencies = ["requests"] +# /// +""" +Update Meson wrap file `revision` fields to point to latest release. +""" +from __future__ import annotations +import argparse +import configparser +import json +import logging +import os +import re +import sys +from pathlib import Path +from dataclasses import dataclass, asdict + +import requests + + +log = logging.getLogger(__name__) + + +SEMVER_RE = re.compile( + r""" + ^v? + (?P0|[1-9]\d*)\. + (?P0|[1-9]\d*)\. + (?P0|[1-9]\d*) + $""", + re.VERBOSE, +) + +ROOT = Path(__file__).resolve().parents[1] +WRAP_DIR = ROOT / "subprojects" +SESSION = requests.Session() +GH_TOKEN = os.getenv("GH_TOKEN", "") +if GH_TOKEN: + SESSION.headers["Authorization"] = f"Bearer {GH_TOKEN}" +SESSION.headers["Accept"] = "application/vnd.github+json" + + +def gh_sha_for_tag(owner: str, repo: str, tag: str) -> str: + data = SESSION.get( + f"https://api.github.com/repos/{owner}/{repo}/git/ref/tags/{tag}", timeout=30 + ).json() + + # First level: get the object it points to + obj_type = data["object"]["type"] + obj_sha = data["object"]["sha"] + + if obj_type == "commit": + # Lightweight tag + return obj_sha + elif obj_type == "tag": + # Annotated tag: need to dereference + tag_obj_url = data["object"]["url"] + tag_data = requests.get(tag_obj_url).json() + return tag_data["object"]["sha"] + else: + raise Exception(f"Unknown object type: {obj_type}") + + +def gh_latest_release( + owner: str, repo: str, pattern: re.Pattern +) -> None | tuple[str, str]: + """ + Return (tag_name, commit_sha) for the most recent matching release. + """ + releases = SESSION.get( + f"https://api.github.com/repos/{owner}/{repo}/releases", timeout=30 + ).json() + viable = [t for t in releases if pattern.match(t["tag_name"])] + + if not viable: + return None + + tag_name = viable[0]["tag_name"] + sha = gh_sha_for_tag(owner, repo, tag_name) + + return tag_name, sha + + +def gh_latest_tag(owner: str, repo: str, pattern: re.Pattern) -> tuple[str, str]: + """ + Return (tag_name, commit_sha) for the most recent matching tag. + """ + tags = SESSION.get( + f"https://api.github.com/repos/{owner}/{repo}/tags", timeout=30 + ).json() + viable = [t for t in tags if pattern.match(t["name"])] + + if not viable: + return None + + return viable[0]["name"], viable[0]["commit"]["sha"] + + +@dataclass +class UpdatedWrap: + path: str + owner: str + repo: str + old_rev: str + new_rev: str + new_tag: str + + +def update_wrap(path: Path) -> None | UpdatedWrap: + """ + Return (tag_name, commit_sha) if updated, otherwise None. + """ + cp = configparser.ConfigParser(interpolation=None) + cp.read(path, encoding="utf-8") + + if "wrap-git" not in cp: + # FIXME: Support wrap-file from wrapdb + return None + + w = cp["wrap-git"] + url = w.get("url", "") + rev = w.get("revision", "").strip() + m = re.match(r".*github\.com[:/](?P[^/]+)/(?P[^/.]+)(?:\.git)?", url) + if not (m and rev): + return None + + owner, repo = m.group("owner"), m.group("repo") + try: + pattern = cp.get("update", "tag_regex", fallback=None) + pattern = re.compile(pattern) if pattern else SEMVER_RE + + latest = gh_latest_release(owner, repo, pattern) + if latest is None: + log.info("Couldn't find latest release for %s/%s", owner, repo) + log.info("Searching for tags directly...") + latest = gh_latest_tag(owner, repo, pattern) + if latest is None: + log.info("Couldn't find latest tag for %s/%s", owner, repo) + return None + tag, sha = latest + except Exception as e: + log.exception(e) + return None + + if sha.startswith(rev): + log.info("%s already at %s (%s)", path.name, tag, sha) + return None + + log.info("%s updated to %s (%s)", path.name, tag, sha) + + w["revision"] = sha + + with open(path, "w", encoding="utf-8") as file: + cp.write(file) + + # XXX: ConfigParser writes two extra newlines. Trim the last one. + file.seek(file.tell() - 1, 0) + file.truncate() + + return UpdatedWrap(str(path), owner, repo, rev, sha, tag) + + +def main(): + ap = argparse.ArgumentParser() + ap.add_argument( + "--manifest", + "-m", + action="store_true", + default=False, + help="Print JSON-formatted updated manifest", + ) + ap.add_argument( + "wraps", nargs="*", help="Which wraps to update, or all if unspecified" + ) + args = ap.parse_args() + + wraps = args.wraps + if wraps: + wraps = [Path(p) for p in wraps] + else: + wraps = WRAP_DIR.glob("*.wrap") + + logging.basicConfig(level=logging.INFO) + + updated = [] + for wrap in wraps: + info = update_wrap(wrap) + if info: + updated.append(asdict(info)) + + if args.manifest: + json.dump(updated, sys.stdout, indent=2) + + +if __name__ == "__main__": + main() diff --git a/subprojects/SPIRV-Reflect.wrap b/subprojects/SPIRV-Reflect.wrap index dded924d1d..43ec680c93 100644 --- a/subprojects/SPIRV-Reflect.wrap +++ b/subprojects/SPIRV-Reflect.wrap @@ -1,4 +1,7 @@ [wrap-git] -url=https://github.com/KhronosGroup/SPIRV-Reflect -revision=vulkan-sdk-1.4.304.0 -depth=1 +url = https://github.com/KhronosGroup/SPIRV-Reflect +revision = c6c0f5c9796bdef40c55065d82e0df67c38a29a4 +depth = 1 + +[update] +tag_regex = ^(vulkan-sdk-)?[\d\.]+$ diff --git a/subprojects/VulkanMemoryAllocator.wrap b/subprojects/VulkanMemoryAllocator.wrap index b5f1c7f217..321b8c7bed 100644 --- a/subprojects/VulkanMemoryAllocator.wrap +++ b/subprojects/VulkanMemoryAllocator.wrap @@ -1,4 +1,4 @@ [wrap-git] -url=https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator -revision=v3.2.1 -depth=1 +url = https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator +revision = 1d8f600fd424278486eade7ed3e877c99f0846b1 +depth = 1 diff --git a/subprojects/glslang.wrap b/subprojects/glslang.wrap index 29b74d8249..5eb28687e5 100644 --- a/subprojects/glslang.wrap +++ b/subprojects/glslang.wrap @@ -1,4 +1,7 @@ [wrap-git] -url=https://github.com/KhronosGroup/glslang -revision=vulkan-sdk-1.4.304.0 -depth=1 +url = https://github.com/KhronosGroup/glslang +revision = fc9889c889561c5882e83819dcaffef5ed45529b +depth = 1 + +[update] +tag_regex = ^(vulkan-sdk-)?[\d\.]+$ diff --git a/subprojects/tomlplusplus.wrap b/subprojects/tomlplusplus.wrap index 4f2d59c52f..7f678ce951 100644 --- a/subprojects/tomlplusplus.wrap +++ b/subprojects/tomlplusplus.wrap @@ -1,4 +1,4 @@ [wrap-git] -url=https://github.com/marzer/tomlplusplus -revision=c635f218c0aefc801d9748841930365e54fe3089 -depth=1 +url = https://github.com/marzer/tomlplusplus +revision = 30172438cee64926dc41fdd9c11fb3ba5b2ba9de +depth = 1 diff --git a/subprojects/volk.wrap b/subprojects/volk.wrap index 46578b64ce..1cb25819be 100644 --- a/subprojects/volk.wrap +++ b/subprojects/volk.wrap @@ -1,4 +1,4 @@ [wrap-git] -url=https://github.com/zeux/volk -revision=1.4.304 -depth=1 +url = https://github.com/zeux/volk +revision = 0b17a763ba5643e32da1b2152f8140461b3b7345 +depth = 1 diff --git a/tests/meson.build b/tests/meson.build index 907a4c1c98..febbe02806 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -86,3 +86,4 @@ subdir('qapi-schema') subdir('qtest') subdir('migration') subdir('functional') +subdir('xbox') diff --git a/tests/xbox/dsp/data/Makefile b/tests/xbox/dsp/data/Makefile new file mode 100644 index 0000000000..8bd271a422 --- /dev/null +++ b/tests/xbox/dsp/data/Makefile @@ -0,0 +1,4 @@ +all: basic + +%: %.a56 + a56 -o $@ $< diff --git a/tests/xbox/dsp/data/basic b/tests/xbox/dsp/data/basic new file mode 100644 index 0000000000..7b11e507ea --- /dev/null +++ b/tests/xbox/dsp/data/basic @@ -0,0 +1,12 @@ +P 0000 0C0040 +P 0040 0AF080 +P 0041 000042 +P 0042 56F400 +P 0043 123456 +P 0044 567000 +P 0045 000003 +P 0046 08F484 +P 0047 000001 +P 0048 0C0042 +I 000040 start +I 000042 mainloop diff --git a/tests/xbox/dsp/data/basic.a56 b/tests/xbox/dsp/data/basic.a56 new file mode 100644 index 0000000000..0be7483dba --- /dev/null +++ b/tests/xbox/dsp/data/basic.a56 @@ -0,0 +1,12 @@ + org p:$0000 + jmp . + */ + +#include "qemu/osdep.h" +#include "hw/xbox/mcpx/dsp/dsp.h" + +static void scratch_rw(void *opaque, uint8_t *ptr, uint32_t addr, size_t len, bool dir) +{ + assert(!"Not implemented"); +} + +static void fifo_rw(void *opaque, uint8_t *ptr, unsigned int index, size_t len, bool dir) +{ + assert(!"Not implemented"); +} + +static void load_prog(DSPState *s, const char *path) +{ + FILE *file = fopen(path, "r"); + assert(file && "Error opening file"); + + char type, line[100], arg1[20], arg2[20]; + int addr, value; + + while (fgets(line, sizeof(line), file)) { + if (sscanf(line, "%c %s %s", &type, arg1, arg2) >= 2) { + switch (type) { + case 'P': + case 'X': + case 'Y': { + assert(sscanf(arg1, "%x", &addr) == 1); + assert(sscanf(arg2, "%x", &value) == 1); + dsp_write_memory(s, type, addr, value); + break; + } + } + } else { + printf("Invalid line: %s\n", line); + assert(0); + } + } + + fclose(file); +} + +static void test_dsp_basic(void) +{ + g_autofree gchar *path = g_test_build_filename(G_TEST_DIST, "data", "basic", NULL); + + DSPState *s = dsp_init(NULL, scratch_rw, fifo_rw); + + load_prog(s, path); + dsp_run(s, 1000); + + uint32_t v = dsp_read_memory(s, 'X', 3); + g_assert_cmphex(v, ==, 0x123456); + + dsp_destroy(s); +} + +int main(int argc, char **argv) +{ + g_test_init(&argc, &argv, NULL); + + g_test_add_func("/basic", test_dsp_basic); + + return g_test_run(); +} diff --git a/tests/xbox/meson.build b/tests/xbox/meson.build new file mode 100644 index 0000000000..e836218210 --- /dev/null +++ b/tests/xbox/meson.build @@ -0,0 +1 @@ +subdir('dsp') diff --git a/ubuntu-win64-cross/Dockerfile b/ubuntu-win64-cross/Dockerfile index 5a4753481a..0731f86501 100644 --- a/ubuntu-win64-cross/Dockerfile +++ b/ubuntu-win64-cross/Dockerfile @@ -6,10 +6,10 @@ FROM ubuntu:24.04 ENV MXE_PATH=/opt/mxe ENV MXE_REPO=https://github.com/mxe/mxe.git -ENV MXE_VERSION=866492387740cc6580ef516c8a746402be645453 +ENV MXE_VERSION=db7f5247eaab700f28bb9b9433d279e5958f5f01 ENV MXE_LLVM_MINGW_REPO=https://github.com/libvips/build-win64-mxe -ENV MXE_LLVM_MINGW_VERSION=32a2d0ecdfd3ea16bb4a99e333a8d5a3d63be491 +ENV MXE_LLVM_MINGW_VERSION=51fc5884d0c4a3be43a2f09fa36a464885918d3d ENV MXE_LLVM_MINGW_PATH=/opt/build-win64-mxe ARG PLUGIN_DIRS="${MXE_LLVM_MINGW_PATH} ${MXE_LLVM_MINGW_PATH}/build/plugins/llvm-mingw" diff --git a/ubuntu-win64-cross/vulkan-headers.mk b/ubuntu-win64-cross/vulkan-headers.mk index 01594d6c3a..3ea0f9d679 100644 --- a/ubuntu-win64-cross/vulkan-headers.mk +++ b/ubuntu-win64-cross/vulkan-headers.mk @@ -2,10 +2,10 @@ PKG := vulkan-headers $(PKG)_WEBSITE := https://github.com/KhronosGroup/Vulkan-Headers $(PKG)_DESCR := Vulkan-Headers $(PKG)_IGNORE := -$(PKG)_VERSION := vulkan-sdk-1.4.304.0 +$(PKG)_VERSION := vulkan-sdk-1.4.309.0 $(PKG)_SUBDIR := Vulkan-Headers-$($(PKG)_VERSION) $(PKG)_FILE := vulkan-headers-$($(PKG)_VERSION).tar.gz -$(PKG)_CHECKSUM := 46f8f5b6384a36c688e0c40d28d534df41d22de406493dfb5c9b7bcc29672613 +$(PKG)_CHECKSUM := 2bc1b4127950badc80212abf1edfa5c3b5032f3425edf37255863ba7592c1969 $(PKG)_URL := https://github.com/KhronosGroup/Vulkan-Headers/archive/refs/tags/$($(PKG)_VERSION).tar.gz $(PKG)_DEPS := cc diff --git a/ui/xemu-input.c b/ui/xemu-input.c index 31a51eda9d..de1db08f21 100644 --- a/ui/xemu-input.c +++ b/ui/xemu-input.c @@ -490,7 +490,7 @@ void xemu_input_update_sdl_controller_state(ControllerState *state) void xemu_input_update_rumble(ControllerState *state) { - if (!state->rumble_enabled) { + if (!state->rumble_enabled || !g_config.input.allow_vibration) { return; } diff --git a/ui/xui/main-menu.cc b/ui/xui/main-menu.cc index 9bb5dcf33f..ef3bb05c89 100644 --- a/ui/xui/main-menu.cc +++ b/ui/xui/main-menu.cc @@ -485,6 +485,8 @@ void MainMenuInputView::Draw() SectionTitle("Options"); Toggle("Auto-bind controllers", &g_config.input.auto_bind, "Bind newly connected controllers to any open port"); + Toggle("Controller vibration", &g_config.input.allow_vibration, + "Allows the controllers to vibrate"); Toggle("Background controller input capture", &g_config.input.background_input_capture, "Capture even if window is unfocused (requires restart)"); diff --git a/ui/xui/main.cc b/ui/xui/main.cc index 699805f113..07441f3a64 100644 --- a/ui/xui/main.cc +++ b/ui/xui/main.cc @@ -218,7 +218,7 @@ void xemu_hud_render(void) #if defined(CONFIG_RENDERDOC) if (g_capture_renderdoc_frame) { - nv2a_dbg_renderdoc_capture_frames(1); + nv2a_dbg_renderdoc_capture_frames(1, false); g_capture_renderdoc_frame = false; } #endif @@ -290,8 +290,10 @@ void xemu_hud_render(void) (ImGui::IsMouseClicked(ImGuiMouseButton_Right) && !ImGui::IsAnyItemFocused() && !ImGui::IsAnyItemHovered())) { g_scene_mgr.PushScene(g_popup_menu); + } else if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) { + xemu_toggle_fullscreen(); } - + bool mod_key_down = ImGui::IsKeyDown(ImGuiKey_ModShift); for (int f_key = 0; f_key < 4; ++f_key) { if (ImGui::IsKeyPressed((enum ImGuiKey)(ImGuiKey_F5 + f_key))) { diff --git a/ui/xui/menubar.cc b/ui/xui/menubar.cc index f0b6c1d5c2..83a0b08ae2 100644 --- a/ui/xui/menubar.cc +++ b/ui/xui/menubar.cc @@ -73,7 +73,9 @@ void ProcessKeyboardShortcuts(void) #ifdef CONFIG_RENDERDOC if (ImGui::IsKeyPressed(ImGuiKey_F10) && nv2a_dbg_renderdoc_available()) { - nv2a_dbg_renderdoc_capture_frames(1); + ImGuiIO& io = ImGui::GetIO(); + int num_frames = io.KeyShift ? 5 : 1; + nv2a_dbg_renderdoc_capture_frames(num_frames, io.KeyCtrl); } #endif }