diff --git a/.github/workflows/cibuild.yml b/.github/workflows/cibuild.yml index 3891b51c..cecd4cde 100644 --- a/.github/workflows/cibuild.yml +++ b/.github/workflows/cibuild.yml @@ -1,6 +1,11 @@ name: CI -on: [push, workflow_dispatch] +on: + push: + branches: [master] + tags: ['v*'] + pull_request: + workflow_dispatch: permissions: contents: read @@ -14,6 +19,7 @@ env: CCACHE_BASEDIR: ${{ github.workspace }} CCACHE_COMPILERCHECK: content CCACHE_MAXSIZE: 2G + SDL_COMMIT: 683181b47cfabd293e3ea409f838915b8297a4fd jobs: build: @@ -24,45 +30,69 @@ jobs: with: submodules: recursive - - uses: nixbuild/nix-quick-install-action@v35 - with: - nix_conf: | - keep-env-derivations = true - keep-outputs = true + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y \ + build-essential gcc-14 g++-14 cmake ninja-build pkg-config ccache dpkg-dev file \ + libvulkan-dev mesa-vulkan-drivers libavcodec-dev libavutil-dev \ + spirv-tools spirv-headers libepoxy-dev libegl-dev libopengl-dev \ + libasound2-dev libpulse-dev libpipewire-0.3-dev libudev-dev libdbus-1-dev \ + libx11-dev libxext-dev libxrandr-dev libxcursor-dev libxfixes-dev \ + libxi-dev libxss-dev libxtst-dev libxkbcommon-dev libwayland-dev libdecor-0-dev - - name: Restore Nix store - id: nix-cache - uses: nix-community/cache-nix-action/restore@v7 + - name: Restore SDL3 + id: sdl-cache + uses: actions/cache/restore@v4 with: - primary-key: nix-ci-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('flake.nix', 'flake.lock') }} - restore-prefixes-first-match: nix-ci-${{ runner.os }}-${{ runner.arch }}- + path: ${{ runner.temp }}/sdl3 + key: sdl3-ubuntu-24.04-static-${{ env.SDL_COMMIT }} - - name: Prepare CI shell - run: nix develop .#ci --profile "$RUNNER_TEMP/nix-ci-profile" --command true + - name: Build SDL3 + if: steps.sdl-cache.outputs.cache-hit != 'true' + run: | + curl -fL "https://github.com/libsdl-org/SDL/archive/$SDL_COMMIT.tar.gz" | tar -xz -C "$RUNNER_TEMP" + cmake -S "$RUNNER_TEMP/SDL-$SDL_COMMIT" -B "$RUNNER_TEMP/sdl-build" -G Ninja \ + -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX="$RUNNER_TEMP/sdl3" \ + -DSDL_SHARED=OFF -DSDL_STATIC=ON -DSDL_TEST_LIBRARY=OFF -DSDL_TESTS=OFF + cmake --build "$RUNNER_TEMP/sdl-build" --parallel "$(nproc)" + cmake --install "$RUNNER_TEMP/sdl-build" - # Save the toolchain before building, so failures still leave a warm cache. - - name: Save Nix store - if: steps.nix-cache.outputs.hit-primary-key != 'true' - uses: nix-community/cache-nix-action/save@v7 + - name: Save SDL3 + if: steps.sdl-cache.outputs.cache-hit != 'true' + uses: actions/cache/save@v4 with: - primary-key: ${{ steps.nix-cache.outputs.primary-key }} - gc-max-store-size-linux: 5G + path: ${{ runner.temp }}/sdl3 + key: ${{ steps.sdl-cache.outputs.cache-primary-key }} - name: Restore ccache id: ccache uses: actions/cache/restore@v4 with: path: ${{ env.CCACHE_DIR }} - key: ccache-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('flake.lock') }}-${{ github.run_id }}-${{ github.run_attempt }} + key: ccache-ubuntu-24.04-${{ hashFiles('**/CMakeLists.txt', 'cmake/**', '.github/workflows/cibuild.yml') }}-${{ github.run_id }}-${{ github.run_attempt }} restore-keys: | - ccache-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('flake.lock') }}- - ccache-${{ runner.os }}-${{ runner.arch }}- + ccache-ubuntu-24.04-${{ hashFiles('**/CMakeLists.txt', 'cmake/**', '.github/workflows/cibuild.yml') }}- + ccache-ubuntu-24.04- - name: Configure id: configure - run: nix develop .#ci --command cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache + run: | + version="0.0.0+git.$(git rev-parse --short HEAD)" + if [[ "$GITHUB_REF_TYPE" == tag ]]; then + version="${GITHUB_REF_NAME#v}" + fi + cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_PREFIX_PATH="$RUNNER_TEMP/sdl3" -DCMAKE_INSTALL_PREFIX=/usr \ + -DCMAKE_C_COMPILER=gcc-14 -DCMAKE_CXX_COMPILER=g++-14 \ + -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ + -DDELTA_GPU_D3D12=OFF -DDELTA_PACKAGE_VERSION="$version" + for dependency in SPIRVTOOLS SPIRVHEADERS AVCODEC; do + grep -q "^${dependency}_FOUND:INTERNAL=1$" build/CMakeCache.txt + done + - name: Build - run: nix develop .#ci --command cmake --build build --parallel "$(nproc)" + run: cmake --build build --parallel "$(nproc)" -- -k 0 - name: Save ccache if: ${{ !cancelled() && steps.configure.outcome == 'success' }} @@ -72,12 +102,51 @@ jobs: key: ${{ steps.ccache.outputs.cache-primary-key }} - name: Test - run: nix develop .#ci --command ctest --test-dir build --output-on-failure - - name: ccache stats - if: ${{ !cancelled() && steps.configure.outcome == 'success' }} - run: nix develop .#ci --command ccache -s + env: + LIBGL_ALWAYS_SOFTWARE: '1' + EGL_PLATFORM: surfaceless + run: ctest --test-dir build --output-on-failure + + - name: Package + run: cpack --config build/CPackConfig.cmake -B dist - uses: actions/upload-artifact@v4 with: - name: ps4delta - path: build/delta/main/ps4delta + name: prosperity-ubuntu-24.04-amd64 + path: dist/*.deb + if-no-files-found: error + + install: + needs: build + runs-on: ubuntu-24.04 + container: ubuntu:24.04 + steps: + - uses: actions/download-artifact@v4 + with: + name: prosperity-ubuntu-24.04-amd64 + path: dist + - name: Install and smoke test + run: | + apt-get update + apt-get install -y ./dist/*.deb + ps4delta --version + ps4delta --help + test -f /usr/bin/game_profiles/README.md + test -f /usr/share/applications/ps4delta.desktop + ! ldd /usr/bin/ps4delta | grep -E 'not found|/nix/store' + + release: + if: startsWith(github.ref, 'refs/tags/v') + needs: install + runs-on: ubuntu-24.04 + permissions: + contents: write + steps: + - uses: actions/download-artifact@v4 + with: + name: prosperity-ubuntu-24.04-amd64 + path: dist + - name: Publish release + env: + GH_TOKEN: ${{ github.token }} + run: gh release create "$GITHUB_REF_NAME" dist/*.deb --repo "$GITHUB_REPOSITORY" --verify-tag --generate-notes diff --git a/CMakeLists.txt b/CMakeLists.txt index 808f346a..a3fc0066 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -137,3 +137,7 @@ if(NOT ANDROID) add_subdirectory(tools/archive_extract) add_subdirectory(tools/window_test) endif() + +if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + include(PackageLinux) +endif() diff --git a/cmake/PackageLinux.cmake b/cmake/PackageLinux.cmake new file mode 100644 index 00000000..ec9ffa85 --- /dev/null +++ b/cmake/PackageLinux.cmake @@ -0,0 +1,23 @@ +set(DELTA_PACKAGE_VERSION "0.0.0" CACHE STRING "Linux package version") + +set(CPACK_GENERATOR DEB) +set(CPACK_PACKAGE_NAME prosperity) +set(CPACK_PACKAGE_VERSION "${DELTA_PACKAGE_VERSION}") +set(CPACK_PACKAGE_CONTACT "Vincent Hengel ") +set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Prosperity PS4 and PS5 emulator") +set(CPACK_PACKAGING_INSTALL_PREFIX /usr) +set(CPACK_STRIP_FILES ON) +set(CPACK_COMPONENTS_ALL Runtime) +set(CPACK_DEB_COMPONENT_INSTALL ON) +set(CPACK_DEBIAN_RUNTIME_PACKAGE_NAME prosperity) +set(CPACK_DEBIAN_FILE_NAME DEB-DEFAULT) +set(CPACK_DEBIAN_PACKAGE_SECTION games) +set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON) +set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "https://github.com/Force67/prosperity") +# SDL and epoxy load these libraries at runtime, outside shlibdeps. +set(CPACK_DEBIAN_PACKAGE_DEPENDS + "libegl1, libopengl0, libasound2t64, libpulse0, libudev1, libdbus-1-3, libx11-6, libxext6, libxrandr2, libxcursor1, libxfixes3, libxi6, libxss1, libxtst6, libxkbcommon0, libwayland-client0, libwayland-cursor0, libwayland-egl1, libdecor-0-0") + +install(FILES "${CMAKE_SOURCE_DIR}/LICENSE" + DESTINATION share/doc/prosperity COMPONENT Runtime) +include(CPack) diff --git a/delta/gpu/gcn/gcn_tiling_shader.cc b/delta/gpu/gcn/gcn_tiling_shader.cc index 27ac2f3a..ed741351 100644 --- a/delta/gpu/gcn/gcn_tiling_shader.cc +++ b/delta/gpu/gcn/gcn_tiling_shader.cc @@ -1,6 +1,7 @@ #include "gpu/gcn/gcn_translate.h" #ifdef DELTA_HAVE_SPIRV_BACKEND +#include #include #include "base/containers/vector.h" #include "base/logging.h" diff --git a/delta/gpu/gcn/spirv/translate_mem.cc b/delta/gpu/gcn/spirv/translate_mem.cc index ed775973..e3ef1b9f 100644 --- a/delta/gpu/gcn/spirv/translate_mem.cc +++ b/delta/gpu/gcn/spirv/translate_mem.cc @@ -15,6 +15,7 @@ #ifdef DELTA_HAVE_SPIRV_BACKEND +#include #include #include "base/arch.h" diff --git a/delta/gpu/ps5/rdna/rdna_compute.cc b/delta/gpu/ps5/rdna/rdna_compute.cc index 523af235..74982c54 100644 --- a/delta/gpu/ps5/rdna/rdna_compute.cc +++ b/delta/gpu/ps5/rdna/rdna_compute.cc @@ -52,6 +52,7 @@ RecompileCompute(const u32*, } // namespace gpu::rdna #else +#include #include #include #include diff --git a/delta/gpu/ps5/rdna/rdna_resource.cc b/delta/gpu/ps5/rdna/rdna_resource.cc index 64607037..9506cac1 100644 --- a/delta/gpu/ps5/rdna/rdna_resource.cc +++ b/delta/gpu/ps5/rdna/rdna_resource.cc @@ -12,6 +12,7 @@ #include "gpu/gcn/gcn_detile.h" #include "gpu/guest_memory.h" +#include #include #include #include diff --git a/delta/gpu/tests/gcn_spirv_test.cc b/delta/gpu/tests/gcn_spirv_test.cc index b5952771..39460fca 100644 --- a/delta/gpu/tests/gcn_spirv_test.cc +++ b/delta/gpu/tests/gcn_spirv_test.cc @@ -72,6 +72,8 @@ bool HasBuiltin(const base::Vector& spirv, u32 builtin) { bool Recompile(base::Vector code) { code.push_back(kEndPgm); + // The recompiler scans 4096 dwords for shader metadata. + code.resize(4096); const u32 user_data[16] = {}; return gpu::gcn::Recompile(code.data(), nullptr, user_data, user_data).ok; } @@ -110,10 +112,10 @@ TEST(RdnaScalarReplay, CompareWritesOnlyItsSelectedMaskRegister) { TEST(RdnaSpirv, NoColorExportDoesNotSynthesizeWhiteOutput) { const u32 user_data[32] = {}; - const u32 vs[64] = {kEndPgm}; - const u32 no_export[64] = {kEndPgm}; - const u32 null_export[64] = {0xf8000890, 0, kEndPgm}; - const u32 color_export[64] = {0x7e0002f2, 0xf800080f, 0, + const u32 vs[4096] = {kEndPgm}; + const u32 no_export[4096] = {kEndPgm}; + const u32 null_export[4096] = {0xf8000890, 0, kEndPgm}; + const u32 color_export[4096] = {0x7e0002f2, 0xf800080f, 0, kEndPgm}; // v0=1; exp mrt0 v0,v0,v0,v0 for (const u32* ps : {no_export, null_export, color_export}) { gpu::rdna::NextProgramGeneration(); @@ -124,7 +126,7 @@ TEST(RdnaSpirv, NoColorExportDoesNotSynthesizeWhiteOutput) { } TEST(RdnaSpirv, ReloadedDescriptorUsesItsOwnGraphicsBuffer) { - const u32 vs[64] = { + const u32 vs[4096] = { 0xf4080404, 0xfa000000, // s_load_dwordx4 s[16:19], s[8:9], 0 0xe0300000, 0x80040000, // v0 = buffer A[0] 0xe0300004, 0x80040100, // v1 = buffer A[1], same descriptor @@ -405,8 +407,8 @@ TEST(GcnSpirv, PlansScalarLoadedCbufferDescriptor) { TEST(GcnSpirv, SeedsPixelPositionFromFragCoord) { const IsaScope base(gpu::gcn::IsaMode::kBase); - const u32 vs[] = {kEndPgm}; - const u32 ps[] = { + const u32 vs[4096] = {kEndPgm}; + const u32 ps[4096] = { 0x7e080f02, // v_cvt_u32_f32 v4, v2 0x7e0a0f03, // v_cvt_u32_f32 v5, v3 0xf800000f, 0x07060504, // exp mrt0 v4, v5, v6, v7 diff --git a/delta/gpu/tests/render_draw_test.cc b/delta/gpu/tests/render_draw_test.cc index ff41677f..f649ea3f 100644 --- a/delta/gpu/tests/render_draw_test.cc +++ b/delta/gpu/tests/render_draw_test.cc @@ -329,9 +329,9 @@ TEST(VkDraw, MeshWorkgroupsExpandInputPointsIntoTriangles) { auto& renderer = gpu::render::DefaultRenderer(); if (!gpu::render::Init(renderer) || !gpu::render::Device().caps().mesh_shader) GTEST_SKIP() << "Mesh shading is required"; - const u32 vs[64] = {0x7e000280, 0x7e0202f2, 0xf80008cf, 0x01000000, + const u32 vs[4096] = {0x7e000280, 0x7e0202f2, 0xf80008cf, 0x01000000, 0xbf810000}; - const u32 ps[64] = {0x7e0002f2, 0x7e020280, 0xf800080f, 0x00010100, + const u32 ps[4096] = {0x7e0002f2, 0x7e020280, 0xf800080f, 0x00010100, 0xbf810000}; const u32 user_data[32]{}; static auto program = gpu::rdna::Recompile(vs, ps, user_data, user_data); @@ -417,10 +417,10 @@ TEST(VkDraw, SplitNggStagesTransferLdsAndExportConnectivity) { GTEST_SKIP() << "Mesh shading is required"; if (std::strstr(gpu::render::Device().caps().device_name, "llvmpipe")) GTEST_SKIP() << "LLVMpipe crashes compiling the split NGG mesh shader"; - const u32 es[64] = {0x34000a82, // v0 = ES vertex ID * 4 + const u32 es[4096] = {0x34000a82, // v0 = ES vertex ID * 4 0xd8340000, 0x00000500, // LDS[v0] = ES vertex ID 0xbe802006}; // s_setpc_b64 s[6:7] -> separately bound GS - const u32 gs[64] = { + const u32 gs[4096] = { 0xd8d80000, 0x02000000, // v2 = LDS[primitive vertex offset] 0x7e000d02, // v0 = float(v2) 0x100000f0, 0x060000f1, // x = id * .5 - .5 @@ -433,7 +433,7 @@ TEST(VkDraw, SplitNggStagesTransferLdsAndExportConnectivity) { 0xf8000941, 0x00000004, 0xbefe04c1, 0xbefc03ff, 0x00001003, // allocation: 3 vertices, 1 primitive 0xbf900009, 0xbf810000}; - const u32 ps[64] = {0x7e0002f2, 0x7e020280, 0xf800080f, 0x00010100, + const u32 ps[4096] = {0x7e0002f2, 0x7e020280, 0xf800080f, 0x00010100, 0xbf810000}; const u32 user_data[32]{}; const gpu::rdna::NggConfig cfg{gs, 64, 3, 3, 1, 128}; @@ -484,7 +484,7 @@ TEST(VkDraw, UnifiedNggProgramExportsTriangleConnectivity) { 0xf8000941, 0x00000004, 0xbefe04c1, 0xbefc03ff, 0x00001003, // allocation: 3 vertices, 1 primitive 0xbf900009, 0xbf810000}; - const u32 ps[64] = {0x7e0002f2, 0x7e020280, 0xf800080f, 0x00010100, + const u32 ps[4096] = {0x7e0002f2, 0x7e020280, 0xf800080f, 0x00010100, 0xbf810000}; const u32 user_data[32]{}; const gpu::rdna::NggConfig cfg{gs, 64, 3, 3, 1, 128, 0, false}; @@ -631,7 +631,7 @@ TEST(VkDraw, MeshConstantWindowsExceedTheDynamicDescriptorLimit) { GTEST_SKIP() << "Mesh shading is required"; if (std::strstr(gpu::render::Device().caps().device_name, "llvmpipe")) GTEST_SKIP() << "LLVMpipe crashes compiling the mesh constant-window shader"; - const u32 es[64] = {0x34000a82, 0xd8340000, 0x00000500, 0xbe802006}; + const u32 es[4096] = {0x34000a82, 0xd8340000, 0x00000500, 0xbe802006}; base::Array gs{}; u32 pc = 0; // One scalar load in each of 17 disjoint constant-buffer windows. The @@ -648,7 +648,7 @@ TEST(VkDraw, MeshConstantWindowsExceedTheDynamicDescriptorLimit) { 0x00000004, 0xbefe04c1, 0xbefc03ff, 0x00001003, 0xbf900009, 0xbf810000}; base::Copy(body, body + base::ArraySize(body), gs.begin() + pc); - const u32 ps[64] = {0x7e0002f2, 0x7e020280, 0xf800080f, 0x00010100, + const u32 ps[4096] = {0x7e0002f2, 0x7e020280, 0xf800080f, 0x00010100, 0xbf810000}; const u32 user_data[32]{}; const gpu::rdna::NggConfig cfg{gs.data(), 64, 3, 3, 1, 128}; @@ -697,12 +697,12 @@ TEST(VkDraw, SplitNggUserWindowsAndHighPixelRegistersRemainDistinct) { GTEST_SKIP() << "Mesh shading is required"; if (std::strstr(gpu::render::Device().caps().device_name, "llvmpipe")) GTEST_SKIP() << "LLVMpipe crashes compiling NGG mesh shaders"; - const u32 es[64] = {0x34000a83, // v0 = vertex ID * 8 + const u32 es[4096] = {0x34000a83, // v0 = vertex ID * 8 0x7e0c0208, // v6 = ES user data in s8 0xd8340000, 0x00000500, // LDS[v0] = vertex ID 0xd8340004, 0x00000600, // LDS[v0+4] = ES offset 0xbe802006}; - const u32 gs[64] = {0x34000a83, // address matching the ES record + const u32 gs[4096] = {0x34000a83, // address matching the ES record 0xd8d80000, 0x02000000, 0xd8d80004, 0x06000000, 0x7e000d02, 0x100000f0, 0x060000f1, 0x06000000, 0x06000106, // x += GS user s0 + offset written by ES @@ -711,7 +711,7 @@ TEST(VkDraw, SplitNggUserWindowsAndHighPixelRegistersRemainDistinct) { 0x7e0802ff, 0x00200400, 0xbefe0481, 0xf8000941, 0x00000004, 0xbefe04c1, 0xbefc03ff, 0x00001003, 0xbf900009, 0xbf810000}; - const u32 ps[64] = {0x7e00021d, // red = user s29, beyond the old push window + const u32 ps[4096] = {0x7e00021d, // red = user s29, beyond the old push window 0x7e020280, 0xf800080f, 0x00010100, 0xbf810000}; u32 user_data[32]{}; user_data[0] = 0x3e800000; // ES contributes +.25; GS changes independently @@ -771,10 +771,10 @@ TEST(VkDraw, VertexAndPixelUserDataPastSixteenDoNotOverlap) { auto& renderer = gpu::render::DefaultRenderer(); if (!gpu::render::Init(renderer)) GTEST_SKIP() << "A Vulkan device is required"; - const u32 vs[64] = {0x7e00021f, 0x7e0202ff, 0x3d800000, + const u32 vs[4096] = {0x7e00021f, 0x7e0202ff, 0x3d800000, 0x7e040280, 0x7e0602f2, 0xf80008cf, 0x03020100, 0xbf810000}; // x = s31 (user23) - const u32 ps[64] = {0x7e00021d, 0x7e020280, 0xf800080f, 0x00010100, + const u32 ps[4096] = {0x7e00021d, 0x7e020280, 0xf800080f, 0x00010100, 0xbf810000}; // red = s29 const u32 user_data[32]{}; static const auto kProgram = @@ -815,9 +815,9 @@ TEST(VkDraw, PixelShaderUsesDppOperand) { auto& renderer = gpu::render::DefaultRenderer(); if (!gpu::render::Init(renderer)) GTEST_SKIP() << "A Vulkan device is required"; - const u32 vs[64] = {0x7e0002ff, 0x3d800000, 0x7e040280, 0x7e0602f2, + const u32 vs[4096] = {0x7e0002ff, 0x3d800000, 0x7e040280, 0x7e0602f2, 0xf80008cf, 0x03020000, 0xbf810000}; - const u32 ps[64] = {0x7e0002f2, // v0 = 1 + const u32 ps[4096] = {0x7e0002f2, // v0 = 1 0x7e0002fa, 0xff00e400, // v0 = quad_perm[0,1,2,3](v0) 0x7e020280, 0xf800080f, 0x00010100, 0xbf810000}; const u32 user_data[32]{}; @@ -865,7 +865,7 @@ TEST(VkDraw, SdwaAluWritesSelectedBytesAndPreservesOtherBits) { {2, 1, 0xbfe00000, 0xffff0000}, {3, 1, 0xbfe00000, 0xff000000}, {4, 1, 0xbfe00000, 0xffffffff}, {5, 1, 0xbfe00000, 0xffff0000}, {0, 1, 0x4301c000, 0xffffff81}, {4, 1, 0x449a5800, 0x000004d2}}; - const u32 vs[64] = {0x7e0002ff, 0x3d800000, 0x7e040280, 0x7e0602f2, + const u32 vs[4096] = {0x7e0002ff, 0x3d800000, 0x7e040280, 0x7e0602f2, 0xf80008cf, 0x03020000, 0xbf810000}; static base::Array programs; alignas(65536) static base::Array, @@ -875,7 +875,7 @@ TEST(VkDraw, SdwaAluWritesSelectedBytesAndPreservesOtherBits) { const auto& c = cases[i % base::ArraySize(cases)]; const bool binary = i >= base::ArraySize(cases); SCOPED_TRACE(i); - u32 ps[64] = {0x7e000200, 0x7e020201}; // old bits and float input + u32 ps[4096] = {0x7e000200, 0x7e020201}; // old bits and float input u32 pc = 2; if (binary) { ps[pc++] = 0x7e021101; // convert v1 to integer before the binary op @@ -920,10 +920,10 @@ TEST(VkDraw, NggWavesKeepIndependentBranchesAndFullBallots) { GTEST_SKIP() << "Mesh shading is required"; if (std::strstr(gpu::render::Device().caps().device_name, "llvmpipe")) GTEST_SKIP() << "LLVMpipe crashes compiling NGG mesh shaders"; - const u32 es[64] = {0x34000a82, 0xd8340000, 0x00000500, 0xbe802006}; + const u32 es[4096] = {0x34000a82, 0xd8340000, 0x00000500, 0xbe802006}; // Two guest waves take different scalar branches. Their triangles use // vertices in the upper half of each wave and a ballot spanning both halves. - const u32 gs[128] = { + const u32 gs[4096] = { 0x9380ff03, 0x00040018, // s0 = wave index from merged_wave_info 0xbf068000, // s_cmp_eq_u32 s0, 0 0xbf850003, // wave zero jumps to the negative offset @@ -958,7 +958,7 @@ TEST(VkDraw, NggWavesKeepIndependentBranchesAndFullBallots) { 0xf8000941, 0x00000004, 0xbefc03ff, 0x00002063, // 99 vertices, 2 // primitives 0xbf900009, 0xbf810000}; - const u32 ps[64] = {0x7e0002f2, 0x7e020280, 0xf800080f, 0x00010100, + const u32 ps[4096] = {0x7e0002f2, 0x7e020280, 0xf800080f, 0x00010100, 0xbf810000}; const u32 user_data[32]{}; const gpu::rdna::NggConfig cfg{gs, 128, 99, 128, 2, 128}; @@ -994,7 +994,7 @@ TEST(VkDraw, FetchedVerticesKeepNggVertexAndInstanceIds) { GTEST_SKIP() << "A Vulkan device is required"; // Read the launch IDs before an inline fetch overwrites v5. Two vertices // in two instances should cover four separate points, one per quadrant. - const u32 vs[64] = { + const u32 vs[4096] = { 0x7e280d05, 0x7e2a0d08, // v20=float(v5), v21=float(v8) 0xf4080404, 0, // s_load_dwordx4 s[16:19], s[8:9], 0 0xe0002000, 0x80040505, // buffer_load_format_x v5, v5, s[16:19], 0 @@ -1002,7 +1002,7 @@ TEST(VkDraw, FetchedVerticesKeepNggVertexAndInstanceIds) { 0x7e2c0280, // v22=0 0xf80008cf, 0x05161514, // position=(v20,v21,v22,v5) 0xbf810000}; - const u32 ps[64] = {0x7e0002f2, 0x7e020280, 0xf800080f, 0x00010100, + const u32 ps[4096] = {0x7e0002f2, 0x7e020280, 0xf800080f, 0x00010100, 0xbf810000}; const base::Array vertices = {1.f, 1.f}; const u64 vb = reinterpret_cast(vertices.data()); @@ -1051,12 +1051,12 @@ TEST(VkDraw, IndexedRawVerticesUseLoadedDescriptorStride) { auto& renderer = gpu::render::DefaultRenderer(); if (!gpu::render::Init(renderer)) GTEST_SKIP() << "A Vulkan device is required"; - const u32 vs[64] = { + const u32 vs[4096] = { 0xf4080404, 0xfa000000, // s[16:19] = descriptor from s[8:9] 0xe0302000, 0x80040005, // v0 = buffer[vertex ID], using descriptor // stride 0x7e020280, 0x7e040280, 0x7e0602f2, 0xf80008cf, 0x03020100, 0xbf810000}; - const u32 ps[64] = {0x7e0002f2, 0x7e020280, 0xf800080f, 0x00010100, + const u32 ps[4096] = {0x7e0002f2, 0x7e020280, 0xf800080f, 0x00010100, 0xbf810000}; const base::Array vertices = {-.5f, 99.f, .5f, 99.f}; const u64 vb = reinterpret_cast(vertices.data()); @@ -1176,9 +1176,9 @@ TEST(VkDraw, IndexedTypedLoadUsesLiveStrideAndInstructionFormat) { // Inline V# at s[8:11], indexed by v5. The instruction requests float4 // even though the descriptor advertises R32_UINT. Padding must not become // vertex data, and changing the stride must work with the same module. - const u32 vs[64] = {0xea6b2000, 0x80020005, 0xf80008cf, 0x03020100, + const u32 vs[4096] = {0xea6b2000, 0x80020005, 0xf80008cf, 0x03020100, 0xbf810000}; - const u32 ps[64] = {0x7e0002f2, 0x7e020280, 0xf800080f, 0x00010100, + const u32 ps[4096] = {0x7e0002f2, 0x7e020280, 0xf800080f, 0x00010100, 0xbf810000}; base::Array vertices; const u64 vb = reinterpret_cast(vertices.data()); @@ -1235,13 +1235,13 @@ TEST(VkDraw, BulkDescriptorLoadPreservesInteriorBufferStride) { auto& renderer = gpu::render::DefaultRenderer(); if (!gpu::render::Init(renderer)) GTEST_SKIP() << "A Vulkan device is required"; - const u32 vs[64] = { + const u32 vs[4096] = { 0xf4100204, 0xfa000000, // s[8:23] = four descriptors from s[8:9] 0xf4200804, 0xfa000000, // a scalar load uses the first descriptor 0xe0302000, 0x80040005, // v0 = buffer[vertex ID], using descriptor // stride 0x7e020280, 0x7e040280, 0x7e0602f2, 0xf80008cf, 0x03020100, 0xbf810000}; - const u32 ps[64] = {0x7e0002f2, 0x7e020280, 0xf800080f, 0x00010100, + const u32 ps[4096] = {0x7e0002f2, 0x7e020280, 0xf800080f, 0x00010100, 0xbf810000}; const base::Array vertices = {-.5f, 99.f, .5f, 99.f}; const u64 vb = reinterpret_cast(vertices.data()); @@ -1370,9 +1370,9 @@ TEST(VkDraw, PixelTypedLoadConvertsPackedDataWithByteOffsets) { auto& renderer = gpu::render::DefaultRenderer(); if (!gpu::render::Init(renderer)) GTEST_SKIP() << "A Vulkan device is required"; - const u32 vs[64] = {0x7e000280, 0x7e0202f2, 0xf80008cf, 0x01000000, + const u32 vs[4096] = {0x7e000280, 0x7e0202f2, 0xf80008cf, 0x01000000, 0xbf810000}; - const u32 ps[64] = { + const u32 ps[4096] = { 0x7e000284, // v0 = byte offset 4 0xe8001004 | (56u << 19) | (3u << 16), 0x88020000, // tbuffer_load_format_xyzw v[0:3], v0, s[8:11], 8 offen offset:4 @@ -1423,9 +1423,9 @@ TEST(VkDraw, PixelOneDimensionalSampleSurvivesAddtidSpill) { auto& renderer = gpu::render::DefaultRenderer(); if (!gpu::render::Init(renderer)) GTEST_SKIP() << "A Vulkan device is required"; - const u32 vs[64] = {0x7e000280, 0x7e0202f2, 0xf80008cf, 0x01000000, + const u32 vs[4096] = {0x7e000280, 0x7e0202f2, 0xf80008cf, 0x01000000, 0xbf810000}; - const u32 ps[64] = { + const u32 ps[4096] = { 0x7e0002f0, 0xf09c0f00, 0x00400400, // sample_lz 1D at x=.5 0xbefc03ff, 256, // M0 = 256 0xdac00000, 0x00000400, // spill red at M0 + lane*4 @@ -1484,12 +1484,12 @@ TEST(VkDraw, RawVertexBufferReadsPastOneMiB) { auto& renderer = gpu::render::DefaultRenderer(); if (!gpu::render::Init(renderer)) GTEST_SKIP() << "A Vulkan device is required"; - const u32 vs[64] = { + const u32 vs[4096] = { 0xd5690000, 0x00020aff, 0x001fffff, // v0 = vertex ID * (2M - 1) 0x4a000081, // v0 += 1 0xe0302000, 0x80020000, // v0 = raw buffer s[8:11][v0] 0x7e020280, 0x7e040280, 0x7e0602f2, 0xf80008cf, 0x03020100, 0xbf810000}; - const u32 ps[64] = {0x7e0002f2, 0x7e020280, 0xf800080f, 0x00010100, + const u32 ps[4096] = {0x7e0002f2, 0x7e020280, 0xf800080f, 0x00010100, 0xbf810000}; base::Vector vertices(4 * 1024 * 1024); const u32 indices[] = {1, 2 * 1024 * 1024, u32(vertices.size() - 1)}; @@ -1539,9 +1539,9 @@ TEST(VkDraw, SinglePointRendersWithAndWithoutIndices) { if (!gpu::render::Init(renderer)) GTEST_SKIP() << "A Vulkan device is required"; // A procedural point at the origin, shaded opaque red. - const u32 vs[64] = {0x7e000280, 0x7e0202f2, 0xf80008cf, 0x01000000, + const u32 vs[4096] = {0x7e000280, 0x7e0202f2, 0xf80008cf, 0x01000000, 0xbf810000}; - const u32 ps[64] = {0x7e0002f2, 0x7e020280, 0xf800080f, 0x00010100, + const u32 ps[4096] = {0x7e0002f2, 0x7e020280, 0xf800080f, 0x00010100, 0xbf810000}; const u32 user_data[32] = {}; static const auto kProgram = @@ -1631,9 +1631,9 @@ TEST(VkDraw, NarrowRenderTargetsRoundTripThroughCompute) { if (!gpu::render::Init(renderer)) GTEST_SKIP() << "A Vulkan device is required"; // A half-red point in an R8/R16 target, whose guest bytes remain zero. - const u32 vs[64] = {0x7e000280, 0x7e0202f2, 0xf80008cf, 0x01000000, + const u32 vs[4096] = {0x7e000280, 0x7e0202f2, 0xf80008cf, 0x01000000, 0xbf810000}; - const u32 ps[64] = {0x7e0002f0, 0x7e020280, 0xf800080f, 0x00010100, + const u32 ps[4096] = {0x7e0002f0, 0x7e020280, 0xf800080f, 0x00010100, 0xbf810000}; const u32 user_data[32] = {}; static const auto kProgram = diff --git a/delta/kern/guest_va_space.cc b/delta/kern/guest_va_space.cc index 9b92042f..a7c00b99 100644 --- a/delta/kern/guest_va_space.cc +++ b/delta/kern/guest_va_space.cc @@ -31,6 +31,8 @@ #include "kern/guest_va_space.h" +#include + #include "logger/logger.h" #include diff --git a/delta/kern/lv2/sys_mem.h b/delta/kern/lv2/sys_mem.h index 1c296879..c55dfff6 100644 --- a/delta/kern/lv2/sys_mem.h +++ b/delta/kern/lv2/sys_mem.h @@ -8,6 +8,8 @@ * in the root of the source tree. */ +#include + #include "base/arch.h" #include "guest_abi.h" diff --git a/delta/kern/module.h b/delta/kern/module.h index da448d3e..8c36f133 100644 --- a/delta/kern/module.h +++ b/delta/kern/module.h @@ -8,6 +8,8 @@ * in the root of the source tree. */ +#include + #include "base/arch.h" #include "base/atomic.h" #include "elf_types.h" diff --git a/delta/main/CMakeLists.txt b/delta/main/CMakeLists.txt index 6f07c9fb..33d089d4 100644 --- a/delta/main/CMakeLists.txt +++ b/delta/main/CMakeLists.txt @@ -70,17 +70,17 @@ endif() # On linux the .ico is useless, so install the converted png plus a desktop # entry that points at it. if(UNIX AND NOT APPLE) - install(TARGETS ps4delta RUNTIME DESTINATION bin) + install(TARGETS ps4delta RUNTIME DESTINATION bin COMPONENT Runtime) install(FILES _res/logo.png DESTINATION share/icons/hicolor/256x256/apps - RENAME ps4delta.png) + RENAME ps4delta.png COMPONENT Runtime) install(FILES _res/ps4delta.desktop - DESTINATION share/applications) + DESTINATION share/applications COMPONENT Runtime) install(DIRECTORY ${CMAKE_SOURCE_DIR}/game_profiles - DESTINATION bin) + DESTINATION bin COMPONENT Runtime) # Register the application/x-ps4-pkg MIME type so .pkg files open with us. install(FILES _res/ps4delta-pkg.xml - DESTINATION share/mime/packages) + DESTINATION share/mime/packages COMPONENT Runtime) endif() if(DELTA_BUILD_TESTS) diff --git a/delta/runtime/vprx/vprx.h b/delta/runtime/vprx/vprx.h index d5827e71..a6377b10 100644 --- a/delta/runtime/vprx/vprx.h +++ b/delta/runtime/vprx/vprx.h @@ -8,6 +8,8 @@ * in the root of the source tree. */ +#include + #include "base/arch.h" #include "logger/logger.h" #include "runtime/vprx/init_function.h" diff --git a/docs/building.md b/docs/building.md index dc3aa037..fbbaedca 100644 --- a/docs/building.md +++ b/docs/building.md @@ -21,18 +21,20 @@ Binary: `build/delta/main/ps4delta`. Next: [install system modules and run a game](installation.md). Keep the Nix shell open when running the binary. -CI uses `nix develop .#ci`, a build and software-rendering shell without the -debuggers, profiler tools, or custom RenderDoc build. Its Nix store cache is -keyed by the flake and runner architecture and saved before compilation. -ccache is restored across commits and saved even when compilation fails. -To reproduce CI locally: +## Ubuntu CI and release packages -```bash -nix develop .#ci -cmake -S . -B build/ci -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -cmake --build build/ci --parallel -ctest --test-dir build/ci --output-on-failure -``` +CI builds natively on Ubuntu 24.04 with GCC 14 and Ubuntu's development packages. +GCC 13 miscompiles the packed buffer format conversion at release optimization. +SDL3 3.4.2 is built from a pinned commit and linked statically. Vulkan and OpenGL +are enabled; D3D12 is disabled. The shader recompiler and FFmpeg video decoder +use Ubuntu's SPIRV-Tools and FFmpeg packages. + +Every successful build produces a `prosperity-ubuntu-24.04-amd64` artifact with a +`.deb` package. CI installs it in a clean Ubuntu 24.04 container and checks the +CLI and installed files. Pushing a `v*` tag publishes the tested package as a +GitHub release. Tag versions must be valid Debian versions, for example `v0.1.0`. + +Nix remains available for local development. ## Linux without Nix diff --git a/docs/installation.md b/docs/installation.md index 260481e2..5ab8062d 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -1,7 +1,20 @@ # Install modules and run -Build first: [building.md](building.md). Commands below run from the repository -root, inside `nix develop` if you built with Nix. +## Ubuntu 24.04 package + +Download the `.deb` from a GitHub release, then install it with: + +```bash +sudo apt install ./prosperity_*.deb +ps4delta +``` + +APT installs the runtime libraries. The package includes game profiles and desktop +integration. Supply your own firmware modules and games as described below. +Use `ps4delta` in place of `./build/delta/main/ps4delta` in the commands below. + +For a source build, see [building.md](building.md). Source-build commands run from +the repository root, inside `nix develop` if you built with Nix. ## Home screen (Linux)