Compare commits

...
Author SHA1 Message Date
ClaxtenandGitHub e91dd39cb0 Drop redundant PROT_NONE tracking in reserve paths for Linux (#122)
src: platform: Linux: Drop redundant PROT_NONE tracking in reserve paths

* Some UE4 games, such as The Pathless, reserve a 512 GiB virtual address range during libc startup.
  Tracking every 4 KiB page causes a long delay and is unnecessary since the range is already PROT_NONE,
  and untracked pages are treated as NoAccess.

Signed-off-by: Claxten <claxten10@gmail.com>
2026-07-30 00:00:21 +02:00
nmzik cc76827e63 Fix vertex buffer ranges crossing memory mappings 2026-07-29 21:05:24 +02:00
nmzik 832bc84100 fix(shader): stabilize scalar provenance phis in cyclic CFGs 2026-07-29 21:05:24 +02:00
nmzik 65a0f0baa7 NpManager ABI 2026-07-29 21:05:24 +02:00
nmzik b9ae2537ef renderer: broaden compatibility 2026-07-29 18:47:26 +02:00
nmzik 0b9edaa721 graphics: broaden storage image atomic compatibility 2026-07-29 18:47:24 +02:00
nmzik 8a244677d7 fix(renderer): resolve delayed GPU page faults through buffer and texture caches 2026-07-29 18:47:19 +02:00
nmzikandGitHub f6e01e5403 Optimize bulk memory invalidation (#124)
Build and Release KytyPS5 / Build KytyPS5 (Windows) (push) Canceled after 0s
Build and Release KytyPS5 / Build KytyPS5 (macOS) (push) Canceled after 0s
Build and Release KytyPS5 / Build KytyPS5 (Linux) (push) Canceled after 0s
Build and Release KytyPS5 / Release KytyPS5 (push) Canceled after 0s
* Per page -> per range search (optimization)
2026-07-29 05:09:36 +02:00
nmzikandGitHub 861729fc6c Optimize texture cache tracking (#123)
Optimize texture cache page tracking
2026-07-29 03:37:09 +02:00
nmzik 687ce025c6 KernelOpen: minor fix 2026-07-29 00:31:09 +02:00
nmzik a21d1aaa47 fix 2026-07-29 00:31:09 +02:00
nmzik ec11f31aa6 fix graphical bug (PPSA17221) 2026-07-29 00:31:08 +02:00
nmzik aeceaff028 new ABIs + one stub 2026-07-29 00:31:08 +02:00
nmzik e76f2d1af8 new ABIs 2026-07-29 00:31:08 +02:00
nmzik 64845e2294 new ABI, broaden support 2026-07-29 00:31:08 +02:00
nmzik 4b9030bd3d ABI fixes: libNet, libSaveData (rewrite the legacy Kyty implementation) 2026-07-29 00:30:24 +02:00
nmzik a6b61d7aa0 refactor(graphics): split renderer and recompiler into focused modules 2026-07-29 00:30:24 +02:00
Stefanos CostaandGitHub 2b9cba457e Linux: port the emulator to a working state on Linux (#117)
Linux: port the emulator to a working state
2026-07-29 00:25:58 +02:00
nmzikandGitHub 3dc793d859 Fix badges (#120)
Build and Release KytyPS5 / Build KytyPS5 (Windows) (push) Waiting to run
Build and Release KytyPS5 / Build KytyPS5 (macOS) (push) Waiting to run
Build and Release KytyPS5 / Release KytyPS5 (push) Blocked by required conditions
Build KytyPS5 (Linux) / build (push) Canceled after 0s
docs: fix build badges
2026-07-28 19:40:14 +02:00
nmzikandGitHub 01d78b71fe CI: release Windows and macOS archives (#119)
* ci: release Windows and macOS archives

* docs: note experimental macOS support
2026-07-28 19:33:21 +02:00
4d79f19089 macOS (Apple Silicon) support: native build running under Rosetta 2 with MoltenVK (#102)
* macos: POSIX platform layer (host fault handler, virtual memory)

- hostException: Mach/POSIX signal-based host fault handler mirroring the
  Windows vectored handler (SIGSEGV/SIGBUS/SIGILL), behind
  '#elif defined(__APPLE__)'. Windows and Linux branches are untouched.
- sysLinuxVirtual: mach_vm_region-based is_mapped (no /proc/self/maps on
  macOS), PTHREAD_MUTEX_NORMAL, and a MAP_FIXED carve-in-place allocator that
  never leaves an unmapped hole for dyld/Rosetta/Metal to claim. All
  __APPLE__-guarded; the original Linux allocator path is preserved verbatim.
- sysLinuxDbg/sysLinuxFileIO: libgen.h for basename(), and a POSIX
  opendir/readdir implementation of SysFileGetDents (previously a stub).

* macos: guest kernel backing, threads, and POSIX libs

- memory/memoryAddressSpace: anonymous shm_open backing (macOS has no
  memfd_create) and re-reserve-on-unmap so a guest MAP_FIXED remap never
  destroys a host mapping (__APPLE__-guarded). Drops host <sys/mman.h> MAP_*
  macros so the guest's constexpr MAP_* constants compile (no-op on Windows).
- pthread: undef the host PTHREAD_STACK_MIN macro; on non-Windows, pin the
  stack-switch asm's guest rsp/rbp to callee-saved r14/r15 (the template
  clobbers r12/r13); __APPLE__ no-op for the absent pthread_condattr_setclock.
  The Windows stack-switch asm is byte-identical.
- network: define SOCKET/INVALID_SOCKET for the non-Windows paths (were
  undefined at 20+ use sites), and rename the non-Windows kernel_clock_*
  helpers to the CamelCase names the callers already use. Both repair the
  shared non-Windows build. Integer reinterpret_cast -> static_cast.

* loader: pin stack-switch asm operands to callee-saved registers

RunEntry switches to the guest stack with an asm template that clobbers
r12/r13 before consuming its inputs. With plain "r" constraints the compiler
may place func/guest_rsp/guest_rbp into r12/r13, so 'callq *func' jumps
through the saved host rsp -- a latent miscompile on every platform that any
unrelated codegen change can trigger. Pin the inputs to rbx/r14/r15, which the
template never touches and the SysV callee preserves.

General correctness fix (not macOS-specific); the same fix is applied to
pthread RunOnGuestStack.

* macos: Vulkan/MoltenVK graphics enablement

- pageManager: GPU write-tracking via Mach VM queries + mprotect (mprotect
  cannot report the previous protection, so the expected-old check is dropped),
  thread id via pthread_mach_thread_np, 4 KB page-size check -- __APPLE__-guarded.
- shaders/renderDraw/vulkanWindow: MoltenVK lacks VK_EXT_color_write_enable,
  VK_EXT_depth_clip_enable and depthBounds; fall back to static color-write
  masks and default depth clipping, request VK_KHR_portability_subset, and
  reuse the graphics queue for present when only one queue is exposed. Non-Apple
  pipeline/extension setup is unchanged.
- window: optional borderless window (KYTY_BORDERLESS) to sidestep a Rosetta
  NSException in macOS window chrome.

* graphics: macOS thread identity for region tracking

Region-ownership locks resolve the current thread via GetCurrentThreadId()
on Windows and EXIT elsewhere. Use the Mach thread port on macOS (nonzero
per-thread id; 0 stays the no-owner sentinel).

* macos: marshal AppKit window operations to the main thread

The present worker thread shows the window, updates its icon and title, and
recreates a lost Vulkan surface. AppKit traps with 'Must only be used from the
main thread' when these run off the main thread. Route them through a small
task queue drained by the SDL main loop (an SDL_USEREVENT wakes the loop when
it is blocked in SDL_WaitEvent). Title updates are fire-and-forget; showing
the window and surface recreation wait for completion.

Windows and Linux call the SDL functions directly, as before.

* build: ignore the _Build output directory

_Build is the conventional out-of-tree build location (only _Build/vscode-clang
was ignored); a stray git add could sweep build artifacts into a commit.

* macos: isolate stack-switch workaround

---------

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-07-28 19:03:15 +02:00
8fe4765f9e ci: add macOS build workflow (#114)
Build KytyPS5 (Linux) / build (push) Waiting to run
Build KytyPS5 / build (push) Waiting to run
Build KytyPS5 / release (push) Blocked by required conditions
Build KytyPS5 (macOS) / build (push) Has been cancelled
* ci: add macOS build workflow

* macos: add POSIX compatibility guards

* ci: select Xcode 26 for macOS

* macos: add kernel POSIX compatibility

* launcher: support macOS process startup

* ci: fix macOS architecture verification

* ci: cache glslang on Windows

---------

Co-authored-by: Abdullah K. <akjee204@gmail.com>
2026-07-27 22:01:50 +02:00
156 changed files with 4997 additions and 1478 deletions
-82
View File
@@ -1,82 +0,0 @@
name: Build KytyPS5 (Linux)
on:
workflow_dispatch:
push:
branches: [ main, master ]
pull_request:
jobs:
build:
runs-on: ubuntu-24.04
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: recursive
- name: Install build dependencies
shell: bash
run: |
sudo apt-get update
sudo apt-get install --no-install-recommends --yes \
clang \
glslang-tools \
libgl1-mesa-dev \
libx11-dev \
libxcursor-dev \
libxext-dev \
libxfixes-dev \
libxi-dev \
libxrandr-dev \
libxss-dev \
lld \
ninja-build
- name: Install Qt 6.10.3
uses: jurplel/install-qt-action@v4
with:
version: "6.10.3"
host: linux
target: desktop
arch: linux_gcc_64
cache: true
- name: Verify toolchain
shell: bash
run: |
git --version
cmake --version
ninja --version
clang++ --version
ld.lld --version
glslangValidator --version
echo "Qt6_DIR=$Qt6_DIR"
- name: Configure
shell: bash
run: |
cmake -S src -B _Build/linux \
-G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_C_COMPILER=clang \
-DCMAKE_CXX_COMPILER=clang++ \
-DCMAKE_PREFIX_PATH="$Qt6_DIR"
- name: Build
shell: bash
run: |
cmake --build _Build/linux --target launcher --parallel
- name: Install
shell: bash
run: |
cmake --install _Build/linux --prefix _Build/linux/install
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: KytyPS5-Linux
path: _Build/linux/install/**
if-no-files-found: error
+293 -15
View File
@@ -1,4 +1,4 @@
name: Build KytyPS5 name: Build and Release KytyPS5
on: on:
workflow_dispatch: workflow_dispatch:
@@ -7,7 +7,8 @@ on:
pull_request: pull_request:
jobs: jobs:
build: windows:
name: Build KytyPS5 (Windows)
runs-on: windows-2022 runs-on: windows-2022
steps: steps:
@@ -22,11 +23,31 @@ jobs:
- name: Setup Ninja - name: Setup Ninja
uses: seanmiddleditch/gha-setup-ninja@v5 uses: seanmiddleditch/gha-setup-ninja@v5
- name: Install glslang - name: Locate vcpkg
id: vcpkg
shell: pwsh shell: pwsh
run: | run: |
$vcpkgRoot = Split-Path (Get-Command vcpkg).Source $vcpkgRoot = Split-Path (Get-Command vcpkg).Source
vcpkg install glslang[tools,opt]:x64-windows $portHash = (Get-FileHash "$vcpkgRoot\ports\glslang\vcpkg.json" -Algorithm SHA256).Hash
"root=$vcpkgRoot" |
Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append
"port_hash=$($portHash.ToLowerInvariant())" |
Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append
- name: Cache glslang
id: glslang_cache
uses: actions/cache@v4
with:
path: ${{ steps.vcpkg.outputs.root }}\installed\x64-windows
key: glslang-${{ runner.os }}-${{ runner.arch }}-${{ steps.vcpkg.outputs.port_hash }}-tools-opt
- name: Install glslang
shell: pwsh
run: |
$vcpkgRoot = "${{ steps.vcpkg.outputs.root }}"
if ("${{ steps.glslang_cache.outputs.cache-hit }}" -ne "true") {
vcpkg install glslang[tools,opt]:x64-windows
}
"$vcpkgRoot\installed\x64-windows\tools\glslang" | "$vcpkgRoot\installed\x64-windows\tools\glslang" |
Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
@@ -69,45 +90,302 @@ jobs:
run: | run: |
cmake --install _Build/windows --prefix _Build/windows/install cmake --install _Build/windows --prefix _Build/windows/install
- name: Upload Artifacts - name: Upload Windows artifacts
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: KytyPS5 name: KytyPS5-Windows-x64
path: _Build/windows/install/** path: _Build/windows/install/**
if-no-files-found: error if-no-files-found: error
macos:
name: Build KytyPS5 (macOS)
runs-on: macos-15
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: recursive
- name: Select Xcode 26
shell: bash
run: |
xcode_path="$(ls -d /Applications/Xcode_26*.app | tail -n 1)"
test -n "$xcode_path"
sudo xcode-select --switch "$xcode_path/Contents/Developer"
xcodebuild -version
- name: Install build dependencies
shell: bash
run: brew install glslang ninja
- name: Install Qt 6.10.3
uses: jurplel/install-qt-action@v4
with:
version: "6.10.3"
host: mac
target: desktop
arch: clang_64
cache: true
- name: Verify toolchain
shell: bash
run: |
git --version
cmake --version
ninja --version
clang++ --version
glslangValidator --version
echo "Host architecture: $(uname -m)"
echo "QT_ROOT_DIR=$QT_ROOT_DIR"
- name: Configure
shell: bash
run: |
cmake -S src -B _Build/macos \
-G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_OSX_ARCHITECTURES=x86_64 \
-DCMAKE_C_COMPILER=clang \
-DCMAKE_CXX_COMPILER=clang++ \
-DCMAKE_PREFIX_PATH="$QT_ROOT_DIR"
- name: Build
shell: bash
run: |
cmake --build _Build/macos --target launcher --parallel
- name: Install
shell: bash
run: |
cmake --install _Build/macos --prefix _Build/macos/install
- name: Bundle MoltenVK
shell: bash
env:
MOLTENVK_VERSION: v1.4.2
MOLTENVK_SHA256: f95765a6229cb7b915990a2890ce12ebe36a730b021545d3d52ae69ce4c4024e
run: |
archive="$RUNNER_TEMP/MoltenVK-macos.tar"
package="$RUNNER_TEMP/MoltenVK"
curl --fail --location --retry 3 \
--output "$archive" \
"https://github.com/KhronosGroup/MoltenVK/releases/download/$MOLTENVK_VERSION/MoltenVK-macos.tar"
echo "$MOLTENVK_SHA256 $archive" | shasum -a 256 --check
tar -xf "$archive" -C "$RUNNER_TEMP"
install -m 755 \
"$package/MoltenVK/dynamic/dylib/macOS/libMoltenVK.dylib" \
_Build/macos/install/libMoltenVK.dylib
install -m 644 "$package/LICENSE" _Build/macos/install/LICENSE.MoltenVK
codesign --force --sign - --timestamp=none _Build/macos/install/libMoltenVK.dylib
- name: Verify artifacts
shell: bash
run: |
file _Build/macos/install/launcher
file _Build/macos/install/kyty_emulator
file _Build/macos/install/libMoltenVK.dylib
lipo _Build/macos/install/launcher -verify_arch x86_64
lipo _Build/macos/install/kyty_emulator -verify_arch x86_64
lipo _Build/macos/install/libMoltenVK.dylib -verify_arch x86_64
codesign --verify --strict _Build/macos/install/kyty_emulator
codesign --verify --strict _Build/macos/install/libMoltenVK.dylib
- name: Upload macOS artifacts
uses: actions/upload-artifact@v4
with:
name: KytyPS5-macOS-x86_64
path: _Build/macos/install/**
if-no-files-found: error
linux:
name: Build KytyPS5 (Linux)
runs-on: ubuntu-24.04
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: recursive
- name: Install build dependencies
shell: bash
run: |
sudo apt-get update
sudo apt-get install --no-install-recommends --yes \
clang \
glslang-tools \
libasound2-dev \
libdbus-1-dev \
libgl1-mesa-dev \
libpulse-dev \
libudev-dev \
libwayland-dev \
libx11-dev \
libxcursor-dev \
libxext-dev \
libxfixes-dev \
libxi-dev \
libxkbcommon-dev \
libxrandr-dev \
libxss-dev \
lld \
ninja-build \
wayland-protocols
- name: Install Qt 6.10.3
uses: jurplel/install-qt-action@v4
with:
version: "6.10.3"
host: linux
target: desktop
arch: linux_gcc_64
cache: true
- name: Verify toolchain
shell: bash
run: |
git --version
cmake --version
ninja --version
clang++ --version
ld.lld --version
glslangValidator --version
echo "Qt6_DIR=$Qt6_DIR"
- name: Configure
shell: bash
run: |
mkdir -p _Build
cmake -S src -B _Build/linux \
-G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_C_COMPILER=clang \
-DCMAKE_CXX_COMPILER=clang++ \
-DCMAKE_PREFIX_PATH="$Qt6_DIR" 2>&1 | tee _Build/configure.log
exit "${PIPESTATUS[0]}"
- name: Verify SDL2 backends
shell: bash
run: |
status=0
for feature in SDL_ALSA SDL_PULSEAUDIO SDL_WAYLAND SDL_X11 SDL_LIBUDEV SDL_DBUS; do
if grep -qE "^-- ${feature} +\\(Wanted: ON\\): ON" _Build/configure.log; then
echo "ok ${feature}"
else
echo "FAIL ${feature} is not enabled"
status=1
fi
done
exit "$status"
- name: Build
shell: bash
run: |
cmake --build _Build/linux \
--target launcher page_manager_tests memory_tracker_tests \
--parallel
- name: Test
shell: bash
run: |
ctest --test-dir _Build/linux --output-on-failure \
-R '^(page_manager|memory_tracker)$'
- name: Install
shell: bash
run: |
cmake --install _Build/linux --prefix _Build/linux/install
- name: Verify artifacts
shell: bash
run: |
file _Build/linux/install/launcher
file _Build/linux/install/kyty_emulator
file _Build/linux/install/kyty_emulator | grep -q "ELF 64-bit LSB .*x86-64"
ldd _Build/linux/install/kyty_emulator > /dev/null
readelf -d _Build/linux/install/launcher | grep -q 'RPATH.*\$ORIGIN/lib'
while IFS= read -r -d '' binary; do
while read -r dependency; do
test -e "_Build/linux/install/lib/$dependency"
done < <(
readelf -d "$binary" |
sed -n 's/.*Shared library: \[\(libQt6[^]]*\|libicu[^]]*\)\].*/\1/p'
)
done < <(
find _Build/linux/install/launcher _Build/linux/install/plugins \
-type f \( -name launcher -o -name '*.so' \) -print0
)
_Build/linux/install/kyty_emulator --help > /dev/null
- name: Upload Linux artifacts
uses: actions/upload-artifact@v4
with:
name: KytyPS5-Linux-x86_64
path: _Build/linux/install/**
if-no-files-found: error
release: release:
if: github.event_name == 'push' name: Release KytyPS5
needs: build if: github.event_name == 'push' && github.repository == 'KytyPS5/KytyPS5'
needs: [windows, macos, linux]
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: permissions:
contents: write contents: write
steps: steps:
- name: Download build - name: Download builds
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
with: with:
name: KytyPS5 path: artifacts
path: KytyPS5
- name: Set release name - name: Set release name
shell: bash shell: bash
run: | run: |
echo "RELEASE_NAME=KytyPS5-$(date -u +'%Y-%m-%d')-${GITHUB_SHA::7}" >> "$GITHUB_ENV" echo "RELEASE_NAME=KytyPS5-$(date -u +'%Y-%m-%d')-${GITHUB_SHA::7}" >> "$GITHUB_ENV"
- name: Package build - name: Package builds
shell: bash shell: bash
run: zip -r "$RELEASE_NAME.zip" KytyPS5 run: |
windows_dir="artifacts/KytyPS5-Windows-x64"
macos_dir="artifacts/KytyPS5-macOS-x86_64"
linux_dir="artifacts/KytyPS5-Linux-x86_64"
test -d "$windows_dir"
test -d "$macos_dir"
test -d "$linux_dir"
chmod a+x \
"$macos_dir/launcher" \
"$macos_dir/kyty_emulator" \
"$macos_dir/libMoltenVK.dylib" \
"$linux_dir/launcher" \
"$linux_dir/kyty_emulator"
(
cd "$windows_dir"
zip -r "$GITHUB_WORKSPACE/$RELEASE_NAME-Windows-x64.zip" .
)
(
cd "$macos_dir"
zip -r "$GITHUB_WORKSPACE/$RELEASE_NAME-macOS-x86_64.zip" .
)
tar -C "$linux_dir" -czf \
"$GITHUB_WORKSPACE/$RELEASE_NAME-Linux-x86_64.tar.gz" .
- name: Create release - name: Create release
shell: bash shell: bash
env: env:
GH_TOKEN: ${{ github.token }} GH_TOKEN: ${{ github.token }}
run: | run: |
assets=(
"$RELEASE_NAME-Windows-x64.zip"
"$RELEASE_NAME-macOS-x86_64.zip"
"$RELEASE_NAME-Linux-x86_64.tar.gz"
)
if gh release view "$RELEASE_NAME" --repo "$GITHUB_REPOSITORY" > /dev/null 2>&1; then if gh release view "$RELEASE_NAME" --repo "$GITHUB_REPOSITORY" > /dev/null 2>&1; then
gh release upload "$RELEASE_NAME" "$RELEASE_NAME.zip" --clobber --repo "$GITHUB_REPOSITORY" gh release upload "$RELEASE_NAME" "${assets[@]}" \
--clobber \
--repo "$GITHUB_REPOSITORY"
else else
gh release create "$RELEASE_NAME" "$RELEASE_NAME.zip" \ gh release create "$RELEASE_NAME" "${assets[@]}" \
--generate-notes \ --generate-notes \
--repo "$GITHUB_REPOSITORY" \ --repo "$GITHUB_REPOSITORY" \
--target "$GITHUB_SHA" \ --target "$GITHUB_SHA" \
+2 -1
View File
@@ -2,4 +2,5 @@
.vs/ .vs/
.idea/ .idea/
build/ build/
_Build/vscode-clang/ _Build/vscode-clang/
_Build/
+65 -14
View File
@@ -1,15 +1,16 @@
# KytyPS5 # KytyPS5
[![Windows Build](https://github.com/KytyPS5/KytyPS5/actions/workflows/build.yml/badge.svg)](https://github.com/KytyPS5/KytyPS5/actions/workflows/build.yml) [![Build KytyPS5 (Windows)](https://img.shields.io/github/actions/workflow/status/KytyPS5/KytyPS5/build.yml?branch=main&event=push&label=Build%20KytyPS5%20%28Windows%29)](https://github.com/KytyPS5/KytyPS5/actions/workflows/build.yml)
[![Linux Build](https://github.com/KytyPS5/KytyPS5/actions/workflows/build-linux.yml/badge.svg)](https://github.com/KytyPS5/KytyPS5/actions/workflows/build-linux.yml) [![Build KytyPS5 (Linux)](https://img.shields.io/github/actions/workflow/status/KytyPS5/KytyPS5/build.yml?branch=main&event=push&label=Build%20KytyPS5%20%28Linux%29)](https://github.com/KytyPS5/KytyPS5/actions/workflows/build.yml)
[![Platform](https://img.shields.io/badge/platform-Windows%20x64-0078D4.svg)](#system-requirements) [![Build KytyPS5 (macOS)](https://img.shields.io/github/actions/workflow/status/KytyPS5/KytyPS5/build.yml?branch=main&event=push&label=Build%20KytyPS5%20%28macOS%29)](https://github.com/KytyPS5/KytyPS5/actions/workflows/build.yml)
[![Platform](https://img.shields.io/badge/platform-Windows%20x64%20%7C%20Linux%20x64%20%7C%20macOS%20x86__64-0078D4.svg)](#system-requirements)
[![Status](https://img.shields.io/badge/status-early%20development-orange.svg)](#current-status) [![Status](https://img.shields.io/badge/status-early%20development-orange.svg)](#current-status)
[![License](https://img.shields.io/badge/license-GPL--2.0-blue.svg)](LICENSE) [![License](https://img.shields.io/badge/license-GPL--2.0-blue.svg)](LICENSE)
KytyPS5 is a free and open-source PlayStation 5 emulator written in C++ for Windows. It is based on KytyPS5 is a free and open-source PlayStation 5 emulator written in C++ for Windows and Linux,
a heavily modified version of [Kyty](https://github.com/InoriRus/Kyty). The project is in an early with experimental macOS support. It is based on a heavily modified version of
stage of development, so compatibility is limited and behavior may change significantly between [Kyty](https://github.com/InoriRus/Kyty). The project is in an early stage of development, so
builds. compatibility is limited and behavior may change significantly between builds.
> [!IMPORTANT] > [!IMPORTANT]
> KytyPS5 is not affiliated with Sony Interactive Entertainment or PlayStation. The project does > KytyPS5 is not affiliated with Sony Interactive Entertainment or PlayStation. The project does
@@ -23,7 +24,11 @@ KytyPS5 can boot 2D games and a selection of 3D games, including titles built wi
Development is focused on compatibility and boot reliability. Development is focused on compatibility and boot reliability.
Linux support is planned, but Windows is the only supported platform at this time. Windows is the primary platform and receives the most testing. Linux builds and runs; see
[Building on Linux](#building-on-linux).
macOS support is experimental. Compatibility with the same games on Windows and macOS has not yet
been tested.
## Bugs and Issues ## Bugs and Issues
@@ -60,8 +65,9 @@ graphical glitches, low compatibility, and poor performance.
Testing games and submitting detailed bug reports are useful ways to contribute. Search existing Testing games and submitting detailed bug reports are useful ways to contribute. Search existing
issues first, then use the **Game Emulation Bug Report** template and attach the complete log file. issues first, then use the **Game Emulation Bug Report** template and attach the complete log file.
Code contributions should be focused, build successfully on Windows, and include relevant tests Code contributions should be focused, build successfully on the platforms they touch, and include
where practical. Because KytyPS5 is still evolving quickly, consider opening an issue before relevant tests where practical. Windows is the primary target, so a change that alters shared code
should not regress it; changes confined to a platform's own code paths only need to build there. Because KytyPS5 is still evolving quickly, consider opening an issue before
starting a large change. starting a large change.
### Formatting ### Formatting
@@ -96,11 +102,11 @@ the Vulkan/SPIR-V validation rules.
### System requirements ### System requirements
- Windows 10 version 1803 - Windows 10 version 1803, or a current Linux distribution
- A 64-bit x86 processor - A 64-bit x86 processor
- A Vulkan 1.3-capable GPU with current drivers - A Vulkan 1.3-capable GPU with current drivers
### Build requirements ### Build requirements (Windows)
- Git - Git
- CMake 3.12 or newer - CMake 3.12 or newer
@@ -134,11 +140,48 @@ cmake --install _Build/windows --prefix _Build/windows/install
The finished application and its runtime dependencies will be placed in The finished application and its runtime dependencies will be placed in
`_Build/windows/install`. `_Build/windows/install`.
### Building on Linux
Install the toolchain and the libraries the bundled SDL2 needs. Without the audio, Wayland and
udev development packages SDL2 quietly configures itself without those backends, and the resulting
build has no working sound and no gamepad hotplug:
```bash
sudo apt-get install --no-install-recommends \
clang lld ninja-build cmake git glslang-tools \
libgl1-mesa-dev libx11-dev libxcursor-dev libxext-dev libxfixes-dev \
libxi-dev libxrandr-dev libxss-dev libxkbcommon-dev \
libasound2-dev libpulse-dev libudev-dev libdbus-1-dev libwayland-dev wayland-protocols
```
Qt 6 (Concurrent, Network, Widgets) is also required — either the distribution packages
(`qt6-base-dev`) or an official Qt installation.
```bash
git submodule update --init --recursive
cmake -S src -B _Build/linux -G Ninja -DCMAKE_BUILD_TYPE=Release \
-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ \
-DCMAKE_PREFIX_PATH="$Qt6_DIR"
cmake --build _Build/linux --target launcher --parallel
cmake --install _Build/linux --prefix _Build/linux/install
```
The install step copies the Qt libraries and plugins next to the binaries, so
`_Build/linux/install` runs without a matching system Qt.
As on Windows, the MSVC compiler is not used; Clang is required. `cl.exe` is rejected at configure
time.
Note that the CMake source root is `src`, not the repository root.
### Visual Studio Code ### Visual Studio Code
A ready-made Visual Studio Code setup is included in [`.vscode`](.vscode). It configures CMake A ready-made Visual Studio Code setup is included in [`.vscode`](.vscode). It configures CMake
Tools to build the project with Ninja and `clang-cl` and provides launch profiles for both Tools to build the project with Ninja and `clang-cl` and provides launch profiles for both
`launcher.exe` and `kyty_emulator.exe`. `launcher.exe` and `kyty_emulator.exe`. It is Windows-only: VS Code settings cannot select a
compiler per platform, so on Linux configure from the command line as shown above.
Before using it: Before using it:
@@ -160,6 +203,10 @@ To use the graphical launcher:
.\_Build\windows\install\launcher.exe .\_Build\windows\install\launcher.exe
``` ```
```bash
./_Build/linux/install/launcher
```
On first launch, add one or more game folders in the global settings. The launcher searches those On first launch, add one or more game folders in the global settings. The launcher searches those
folders recursively for game directories containing `eboot.bin`. Select a detected game and run it folders recursively for game directories containing `eboot.bin`. Select a detected game and run it
from the game list. from the game list.
@@ -170,7 +217,11 @@ The emulator can also be started directly with a legally obtained game directory
.\_Build\windows\install\kyty_emulator.exe --game "D:\Games\ExampleGame" .\_Build\windows\install\kyty_emulator.exe --game "D:\Games\ExampleGame"
``` ```
Run `kyty_emulator.exe --help` to see the available graphics, logging, validation, profiling, and ```bash
./_Build/linux/install/kyty_emulator --game "/games/ExampleGame"
```
Run `kyty_emulator --help` to see the available graphics, logging, validation, profiling, and
debugging options. debugging options.
### AI Use ### AI Use
+69 -29
View File
@@ -158,16 +158,24 @@ file(GLOB kyty_emulator_src CONFIGURE_DEPENDS
graphics/host_gpu/*.h graphics/host_gpu/*.h
graphics/host_gpu/renderer/*.cpp graphics/host_gpu/renderer/*.cpp
graphics/host_gpu/renderer/*.h graphics/host_gpu/renderer/*.h
graphics/host_gpu/renderer/cache/*.cpp
graphics/host_gpu/renderer/cache/*.h
graphics/host_gpu/renderer/image/*.cpp
graphics/host_gpu/renderer/image/*.h
graphics/host_gpu/renderer/pipeline/*.cpp
graphics/host_gpu/renderer/pipeline/*.h
graphics/shader/*.cpp graphics/shader/*.cpp
graphics/shader/*.h graphics/shader/*.h
graphics/shader/recompiler/*.cpp graphics/shader/recompiler/*.cpp
graphics/shader/recompiler/*.h graphics/shader/recompiler/*.h
graphics/shader/recompiler/shaderIR/*.cpp graphics/shader/recompiler/cfg/*.cpp
graphics/shader/recompiler/shaderIR/*.h graphics/shader/recompiler/cfg/*.h
graphics/shader/recompiler/spirvEmitter/*.cpp graphics/shader/recompiler/decompiler/*.cpp
graphics/shader/recompiler/spirvEmitter/*.h graphics/shader/recompiler/decompiler/*.h
graphics/host_gpu/objects/*.cpp graphics/shader/recompiler/emitter/*.cpp
graphics/host_gpu/objects/*.h graphics/shader/recompiler/emitter/*.h
graphics/shader/recompiler/ir/*.cpp
graphics/shader/recompiler/ir/*.h
graphics/presentation/*.cpp graphics/presentation/*.cpp
graphics/presentation/*.h graphics/presentation/*.h
graphics/presentation/window/*.cpp graphics/presentation/window/*.cpp
@@ -251,6 +259,26 @@ endif()
set(kyty_emulator_link_libraries common Vulkan::Headers spirv-tools-opt spirv-tools SDL2-static xxhash FFmpeg::ffmpeg fmt::fmt nlohmann_json::nlohmann_json LibAtrac9) set(kyty_emulator_link_libraries common Vulkan::Headers spirv-tools-opt spirv-tools SDL2-static xxhash FFmpeg::ffmpeg fmt::fmt nlohmann_json::nlohmann_json LibAtrac9)
# Linux system libraries required by the static FFmpeg archive.
if(LINUX)
find_package(Threads REQUIRED)
list(APPEND kyty_emulator_link_libraries m ${CMAKE_DL_LIBS} Threads::Threads)
# Optional FFmpeg dependencies.
find_package(ZLIB)
if(ZLIB_FOUND)
list(APPEND kyty_emulator_link_libraries ZLIB::ZLIB)
endif()
find_library(KYTY_BZ2_LIBRARY bz2)
if(KYTY_BZ2_LIBRARY)
list(APPEND kyty_emulator_link_libraries ${KYTY_BZ2_LIBRARY})
endif()
find_library(KYTY_LZMA_LIBRARY lzma)
if(KYTY_LZMA_LIBRARY)
list(APPEND kyty_emulator_link_libraries ${KYTY_LZMA_LIBRARY})
endif()
endif()
set(inc_headers set(inc_headers
${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}
${KYTY_THIRD_PARTY_DIR}/SDL2/include ${KYTY_THIRD_PARTY_DIR}/SDL2/include
@@ -291,8 +319,8 @@ add_kyty_full_emulator_test(shader_cfg_tests ../tests/shaderCfgTests.cpp)
add_executable(scalar_provenance_tests EXCLUDE_FROM_ALL add_executable(scalar_provenance_tests EXCLUDE_FROM_ALL
../tests/ScalarProvenanceTests.cpp ../tests/ScalarProvenanceTests.cpp
graphics/host_gpu/hostMemory.cpp graphics/host_gpu/hostMemory.cpp
graphics/shader/recompiler/ScalarProvenance.cpp graphics/shader/recompiler/ir/ScalarProvenance.cpp
graphics/shader/recompiler/SrtWalker.cpp graphics/shader/recompiler/ir/SrtWalker.cpp
) )
target_link_libraries(scalar_provenance_tests fmt::fmt) target_link_libraries(scalar_provenance_tests fmt::fmt)
target_include_directories(scalar_provenance_tests PRIVATE ${inc_headers}) target_include_directories(scalar_provenance_tests PRIVATE ${inc_headers})
@@ -324,9 +352,9 @@ add_executable(shader_stage_runtime_tests EXCLUDE_FROM_ALL
graphics/guest_gpu/gpu_format.cpp graphics/guest_gpu/gpu_format.cpp
graphics/host_gpu/hostMemory.cpp graphics/host_gpu/hostMemory.cpp
graphics/shader/shaderStageRuntime.cpp graphics/shader/shaderStageRuntime.cpp
graphics/shader/recompiler/ResourceMaterialization.cpp graphics/shader/recompiler/ir/ResourceMaterialization.cpp
graphics/shader/recompiler/ScalarProvenance.cpp graphics/shader/recompiler/ir/ScalarProvenance.cpp
graphics/shader/recompiler/SrtWalker.cpp graphics/shader/recompiler/ir/SrtWalker.cpp
) )
target_link_libraries(shader_stage_runtime_tests fmt::fmt) target_link_libraries(shader_stage_runtime_tests fmt::fmt)
target_include_directories(shader_stage_runtime_tests PRIVATE ${inc_headers}) target_include_directories(shader_stage_runtime_tests PRIVATE ${inc_headers})
@@ -335,20 +363,20 @@ add_executable(resource_tracking_tests EXCLUDE_FROM_ALL
../tests/ResourceTrackingTests.cpp ../tests/ResourceTrackingTests.cpp
graphics/guest_gpu/gpu_format.cpp graphics/guest_gpu/gpu_format.cpp
graphics/host_gpu/hostMemory.cpp graphics/host_gpu/hostMemory.cpp
graphics/shader/recompiler/ScalarProvenance.cpp graphics/shader/recompiler/ir/ScalarProvenance.cpp
graphics/shader/recompiler/SrtWalker.cpp graphics/shader/recompiler/ir/SrtWalker.cpp
graphics/shader/recompiler/SrtPatcher.cpp graphics/shader/recompiler/ir/SrtPatcher.cpp
graphics/shader/recompiler/ResourceTracking.cpp graphics/shader/recompiler/ir/ResourceTracking.cpp
graphics/shader/recompiler/ResourceMaterialization.cpp graphics/shader/recompiler/ir/ResourceMaterialization.cpp
graphics/shader/recompiler/ShaderInfoCollection.cpp graphics/shader/recompiler/ir/ShaderInfoCollection.cpp
graphics/shader/recompiler/BindingLayout.cpp graphics/shader/recompiler/ir/BindingLayout.cpp
) )
target_link_libraries(resource_tracking_tests fmt::fmt) target_link_libraries(resource_tracking_tests fmt::fmt)
target_include_directories(resource_tracking_tests PRIVATE ${inc_headers}) target_include_directories(resource_tracking_tests PRIVATE ${inc_headers})
add_executable(resource_mutex_tests EXCLUDE_FROM_ALL add_executable(resource_mutex_tests EXCLUDE_FROM_ALL
../tests/ResourceMutexTests.cpp ../tests/ResourceMutexTests.cpp
graphics/host_gpu/renderer/resourceMutex.cpp graphics/host_gpu/renderer/cache/resourceMutex.cpp
) )
target_link_libraries(resource_mutex_tests common) target_link_libraries(resource_mutex_tests common)
target_include_directories(resource_mutex_tests PRIVATE ${inc_headers}) target_include_directories(resource_mutex_tests PRIVATE ${inc_headers})
@@ -394,6 +422,14 @@ add_kyty_full_emulator_test(virtual_memory_allocation_tests ../tests/VirtualMemo
target_compile_definitions(virtual_memory_allocation_tests PRIVATE target_compile_definitions(virtual_memory_allocation_tests PRIVATE
KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS=1) KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS=1)
# These tests use exceptions.
if(NOT KYTY_CLANG_CL)
foreach(kyty_exception_test scalar_provenance_tests resource_tracking_tests
virtual_memory_allocation_tests)
target_compile_options(${kyty_exception_test} PRIVATE -fexceptions)
endforeach()
endif()
if(BUILD_TESTING) if(BUILD_TESTING)
add_test(NAME image_page_table COMMAND $<TARGET_FILE:image_page_table_tests>) add_test(NAME image_page_table COMMAND $<TARGET_FILE:image_page_table_tests>)
add_test(NAME memory_tracker COMMAND $<TARGET_FILE:memory_tracker_tests>) add_test(NAME memory_tracker COMMAND $<TARGET_FILE:memory_tracker_tests>)
@@ -410,23 +446,25 @@ if(BUILD_TESTING)
add_test(NAME gpu_tiler add_test(NAME gpu_tiler
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --gpu-tiler-only) COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --gpu-tiler-only)
add_test(NAME texture_cache_layered_image
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --layered-image-only)
add_test(NAME texture_cache_image_views
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --image-view-cache-only)
add_test(NAME texture_cache_storage_sampled
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --storage-sampled-only)
add_test(NAME texture_cache_depth_readback
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --depth-readback-only)
add_test(NAME buffer_cache_dirty_gc
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --buffer-cache-gc-only)
if(WIN32) if(WIN32)
# These tests still depend on the Windows multisample-depth path.
add_test(NAME texture_cache_image_overlap add_test(NAME texture_cache_image_overlap
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --image-overlap-only) COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --image-overlap-only)
add_test(NAME texture_cache_htile_clear add_test(NAME texture_cache_htile_clear
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --htile-clear-only) COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --htile-clear-only)
add_test(NAME texture_cache_layered_image
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --layered-image-only)
add_test(NAME texture_cache_image_views
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --image-view-cache-only)
add_test(NAME texture_cache_storage_sampled
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --storage-sampled-only)
add_test(NAME texture_cache_depth_readback
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --depth-readback-only)
add_test(NAME buffer_cache_ranges add_test(NAME buffer_cache_ranges
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --buffer-cache-range-only) COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --buffer-cache-range-only)
add_test(NAME buffer_cache_dirty_gc
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --buffer-cache-gc-only)
endif() endif()
endif() endif()
@@ -446,6 +484,8 @@ endif()
if (CLANG AND NOT KYTY_CLANG_CL) if (CLANG AND NOT KYTY_CLANG_CL)
target_link_libraries(kyty_emulator pthread) target_link_libraries(kyty_emulator pthread)
endif() endif()
# dlopen/dlsym/dladdr for RenderDoc.
target_link_libraries(kyty_emulator ${CMAKE_DL_LIBS})
target_include_directories(kyty_emulator PRIVATE ${inc_headers}) target_include_directories(kyty_emulator PRIVATE ${inc_headers})
clang_tidy_check(kyty_emulator "" "${check_headers}" "${inc_headers}") clang_tidy_check(kyty_emulator "" "${check_headers}" "${inc_headers}")
+217 -9
View File
@@ -6,6 +6,14 @@
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
#include <windows.h> // IWYU pragma: keep #include <windows.h> // IWYU pragma: keep
#elif defined(__APPLE__)
#include <csignal>
#include <sys/ucontext.h>
#else
#include <csignal>
#include <initializer_list>
#include <ucontext.h> // IWYU pragma: keep
#include <unistd.h>
#endif #endif
// IWYU pragma: no_include <errhandlingapi.h> // IWYU pragma: no_include <errhandlingapi.h>
@@ -16,7 +24,7 @@
namespace Common::HostException { namespace Common::HostException {
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS #if !defined(__APPLE__)
static std::atomic<Handler> g_handler {nullptr}; static std::atomic<Handler> g_handler {nullptr};
static std::atomic_uint32_t g_install_state {0}; static std::atomic_uint32_t g_install_state {0};
@@ -30,7 +38,9 @@ static_assert(decltype(g_install_state)::is_always_lock_free);
std::fputs(reason != nullptr ? reason : "unspecified", stderr); std::fputs(reason != nullptr ? reason : "unspecified", stderr);
std::fputc('\n', stderr); std::fputc('\n', stderr);
std::fflush(stderr); std::fflush(stderr);
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
TerminateProcess(GetCurrentProcess(), static_cast<UINT>(EXCEPTION_NONCONTINUABLE_EXCEPTION)); TerminateProcess(GetCurrentProcess(), static_cast<UINT>(EXCEPTION_NONCONTINUABLE_EXCEPTION));
#endif
std::_Exit(321); std::_Exit(321);
} }
@@ -48,6 +58,21 @@ public:
KYTY_CLASS_NO_COPY(FilterScope); KYTY_CLASS_NO_COPY(FilterScope);
}; };
static Handler LoadInstalledHandler() noexcept {
if (g_install_state.load(std::memory_order_acquire) == 0) {
FailFast("host exception handler is not installed");
}
const auto handler = g_handler.load(std::memory_order_acquire);
if (handler == nullptr) {
FailFast("host exception callback is null");
}
return handler;
}
#endif
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
static LONG WINAPI ExceptionFilter(PEXCEPTION_POINTERS exception) { static LONG WINAPI ExceptionFilter(PEXCEPTION_POINTERS exception) {
FilterScope filter_scope; FilterScope filter_scope;
@@ -106,22 +131,176 @@ static LONG WINAPI ExceptionFilter(PEXCEPTION_POINTERS exception) {
info.r14 = exception->ContextRecord->R14; info.r14 = exception->ContextRecord->R14;
info.r15 = exception->ContextRecord->R15; info.r15 = exception->ContextRecord->R15;
if (g_install_state.load(std::memory_order_acquire) == 0) { const auto handler = LoadInstalledHandler();
FailFast("host exception handler is not installed");
return handler(info) ? EXCEPTION_CONTINUE_EXECUTION : EXCEPTION_CONTINUE_SEARCH;
}
#elif defined(__APPLE__)
static std::atomic<Handler> g_handler {nullptr};
static std::atomic_uint32_t g_install_state {0};
static thread_local bool g_in_exception_filter = false;
static_assert(decltype(g_handler)::is_always_lock_free);
static_assert(decltype(g_install_state)::is_always_lock_free);
[[noreturn]] static void FailFast(const char* reason) noexcept {
std::fputs("HostException fail-fast: ", stderr);
std::fputs(reason != nullptr ? reason : "unspecified", stderr);
std::fputc('\n', stderr);
std::fflush(stderr);
std::_Exit(321);
}
// Translate the x86-64 page-fault error code (mcontext __es.__err) into an access type.
// bit 1 (0x2) = write, bit 4 (0x10) = instruction fetch, otherwise a read.
static AccessViolationType DecodeAccess(uint64_t err) {
if ((err & 0x10u) != 0) {
return AccessViolationType::Execute;
} }
if ((err & 0x2u) != 0) {
return AccessViolationType::Write;
}
return AccessViolationType::Read;
}
// POSIX signal handler that mirrors the Windows vectored handler: build an ExceptionInfo
// from the mcontext and dispatch. A resolved fault (handler returns true) simply returns,
// re-executing the faulting instruction against the now-fixed protection. An unresolved
// fault restores the default disposition so the retry terminates the process.
static void SignalHandler(int sig, siginfo_t* si, void* uctx) {
if (g_in_exception_filter) {
FailFast("nested exception while resolving a host fault");
}
g_in_exception_filter = true;
auto* uc = static_cast<ucontext_t*>(uctx);
const auto* mc = uc->uc_mcontext;
const auto& ss = mc->__ss;
ExceptionInfo info {};
info.exception_address = ss.__rip;
info.native_code = static_cast<uint32_t>(si->si_code);
info.native_context = uctx;
if (sig == SIGILL) {
info.type = ExceptionType::IllegalInstruction;
} else {
info.type = ExceptionType::AccessViolation;
info.access_violation_type = DecodeAccess(mc->__es.__err);
info.access_violation_vaddr = reinterpret_cast<uint64_t>(si->si_addr);
}
info.rax = ss.__rax;
info.rbx = ss.__rbx;
info.rcx = ss.__rcx;
info.rdx = ss.__rdx;
info.rsi = ss.__rsi;
info.rdi = ss.__rdi;
info.rbp = ss.__rbp;
info.rsp = ss.__rsp;
info.r8 = ss.__r8;
info.r9 = ss.__r9;
info.r10 = ss.__r10;
info.r11 = ss.__r11;
info.r12 = ss.__r12;
info.r13 = ss.__r13;
info.r14 = ss.__r14;
info.r15 = ss.__r15;
const auto handler = g_handler.load(std::memory_order_acquire); const auto handler = g_handler.load(std::memory_order_acquire);
if (handler == nullptr) { if (handler == nullptr) {
FailFast("host exception callback is null"); FailFast("host exception callback is null");
} }
return handler(info) ? EXCEPTION_CONTINUE_EXECUTION : EXCEPTION_CONTINUE_SEARCH; const bool resolved = handler(info);
g_in_exception_filter = false;
if (resolved) {
return; // retry the faulting instruction against the fixed mapping
}
// Unresolved: restore the default action so the re-executed instruction terminates.
struct sigaction dfl {};
dfl.sa_handler = SIG_DFL;
sigemptyset(&dfl.sa_mask);
sigaction(sig, &dfl, nullptr);
}
#else
// x86-64 page-fault error bits.
constexpr uint64_t PAGE_FAULT_ERROR_WRITE = 0x02;
constexpr uint64_t PAGE_FAULT_ERROR_INSTRUCTION = 0x10;
// Let the kernel handle an unresolved fault on retry.
static void ChainToDefault(int signal_number) noexcept {
struct sigaction restore {};
restore.sa_handler = SIG_DFL;
sigemptyset(&restore.sa_mask);
restore.sa_flags = 0;
::sigaction(signal_number, &restore, nullptr);
}
static void SignalHandler(int signal_number, siginfo_t* signal_info, void* native_context) {
FilterScope filter_scope;
auto* context = static_cast<ucontext_t*>(native_context);
auto* gregs = context->uc_mcontext.gregs;
ExceptionInfo info {};
info.exception_address = static_cast<uint64_t>(gregs[REG_RIP]);
info.native_code = static_cast<uint32_t>(signal_number);
info.native_context = context;
if (signal_number == SIGSEGV || signal_number == SIGBUS) {
info.type = ExceptionType::AccessViolation;
const auto error_code = static_cast<uint64_t>(gregs[REG_ERR]);
if ((error_code & PAGE_FAULT_ERROR_INSTRUCTION) != 0) {
info.access_violation_type = AccessViolationType::Execute;
} else if ((error_code & PAGE_FAULT_ERROR_WRITE) != 0) {
info.access_violation_type = AccessViolationType::Write;
} else {
info.access_violation_type = AccessViolationType::Read;
}
info.access_violation_vaddr = reinterpret_cast<uint64_t>(signal_info->si_addr);
} else if (signal_number == SIGILL) {
info.type = ExceptionType::IllegalInstruction;
} else {
ChainToDefault(signal_number);
return;
}
info.rax = static_cast<uint64_t>(gregs[REG_RAX]);
info.rbx = static_cast<uint64_t>(gregs[REG_RBX]);
info.rcx = static_cast<uint64_t>(gregs[REG_RCX]);
info.rdx = static_cast<uint64_t>(gregs[REG_RDX]);
info.rsi = static_cast<uint64_t>(gregs[REG_RSI]);
info.rdi = static_cast<uint64_t>(gregs[REG_RDI]);
info.rbp = static_cast<uint64_t>(gregs[REG_RBP]);
info.rsp = static_cast<uint64_t>(gregs[REG_RSP]);
info.r8 = static_cast<uint64_t>(gregs[REG_R8]);
info.r9 = static_cast<uint64_t>(gregs[REG_R9]);
info.r10 = static_cast<uint64_t>(gregs[REG_R10]);
info.r11 = static_cast<uint64_t>(gregs[REG_R11]);
info.r12 = static_cast<uint64_t>(gregs[REG_R12]);
info.r13 = static_cast<uint64_t>(gregs[REG_R13]);
info.r14 = static_cast<uint64_t>(gregs[REG_R14]);
info.r15 = static_cast<uint64_t>(gregs[REG_R15]);
const auto handler = LoadInstalledHandler();
if (handler(info)) {
return;
}
ChainToDefault(signal_number);
} }
#endif #endif
bool InstallHandler(Handler handler) { bool InstallHandler(Handler handler) {
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
if (handler == nullptr) { if (handler == nullptr) {
return false; return false;
} }
@@ -133,19 +312,48 @@ bool InstallHandler(Handler handler) {
g_handler.store(handler, std::memory_order_release); g_handler.store(handler, std::memory_order_release);
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
if (AddVectoredExceptionHandler(1, ExceptionFilter) == nullptr) { if (AddVectoredExceptionHandler(1, ExceptionFilter) == nullptr) {
g_handler.store(nullptr, std::memory_order_release); g_handler.store(nullptr, std::memory_order_release);
g_install_state.store(0, std::memory_order_release); g_install_state.store(0, std::memory_order_release);
printf("AddVectoredExceptionHandler() failed\n"); printf("AddVectoredExceptionHandler() failed\n");
return false; return false;
} }
#elif defined(__APPLE__)
struct sigaction sa {};
sa.sa_sigaction = SignalHandler;
sa.sa_flags = SA_SIGINFO;
sigemptyset(&sa.sa_mask);
// macOS raises SIGBUS for protection faults on some paths and SIGSEGV on others;
// SIGILL covers instructions the host cannot execute (routed to the x64 emulator).
bool ok = sigaction(SIGSEGV, &sa, nullptr) == 0 && sigaction(SIGBUS, &sa, nullptr) == 0 &&
sigaction(SIGILL, &sa, nullptr) == 0;
if (!ok) {
g_handler.store(nullptr, std::memory_order_release);
g_install_state.store(0, std::memory_order_release);
printf("sigaction() failed to install the host fault handler\n");
return false;
}
#else
struct sigaction action {};
action.sa_sigaction = SignalHandler;
sigemptyset(&action.sa_mask);
// Fault resolution needs the normal thread stack.
action.sa_flags = SA_SIGINFO | SA_RESTART;
for (const int signal_number: {SIGSEGV, SIGBUS, SIGILL}) {
if (::sigaction(signal_number, &action, nullptr) != 0) {
g_handler.store(nullptr, std::memory_order_release);
g_install_state.store(0, std::memory_order_release);
printf("sigaction(%d) failed\n", signal_number);
return false;
}
}
#endif
g_install_state.store(2, std::memory_order_release); g_install_state.store(2, std::memory_order_release);
return true; return true;
#else
(void)handler;
return false;
#endif
} }
} // namespace Common::HostException } // namespace Common::HostException
+4
View File
@@ -23,6 +23,10 @@ struct sys_dbg_stack_info_t {
size_t commited_size; size_t commited_size;
size_t total_size; size_t total_size;
size_t code_size; size_t code_size;
// Full stack reservation reported by pthread.
uintptr_t reserved_addr;
size_t reserved_size;
#endif #endif
}; };
+79 -4
View File
@@ -8,12 +8,57 @@
#include <cstdlib> #include <cstdlib>
#include <cstring> #include <cstring>
#include <execinfo.h>
#include <pthread.h>
#include <sys/param.h> #include <sys/param.h>
#include <sys/types.h> #include <sys/types.h>
#include <unistd.h> #include <unistd.h>
#if defined(__APPLE__)
#include <libgen.h> // POSIX basename() lives here on macOS, not in <cstring>
#endif
void SysStackWalk(void** /*stack*/, int* depth) { // Avoid unwinding a guest-owned stack.
*depth = 0; static bool OnOwnStack() {
const char* probe = reinterpret_cast<const char*>(&probe);
pthread_attr_t attr {};
#if defined(__APPLE__)
const auto* top = static_cast<const char*>(pthread_get_stackaddr_np(pthread_self()));
const auto size = pthread_get_stacksize_np(pthread_self());
(void)attr;
return top != nullptr && size != 0 && probe < top && probe >= top - size;
#else
if (pthread_getattr_np(pthread_self(), &attr) != 0) {
return false;
}
void* base = nullptr;
size_t size = 0;
const bool ok =
pthread_attr_getstack(&attr, &base, &size) == 0 && base != nullptr && size != 0;
pthread_attr_destroy(&attr);
if (!ok) {
return false;
}
const auto* low = static_cast<const char*>(base);
return probe >= low && probe < low + size;
#endif
}
void SysStackWalk(void** stack, int* depth) {
if (stack == nullptr || depth == nullptr || *depth <= 0) {
if (depth != nullptr) {
*depth = 0;
}
return;
}
if (!OnOwnStack()) {
*depth = 0;
return;
}
const int n = ::backtrace(stack, *depth);
*depth = (n < 0 ? 0 : n);
} }
void SysStackUsagePrint(sys_dbg_stack_info_t& stack) { void SysStackUsagePrint(sys_dbg_stack_info_t& stack) {
@@ -30,6 +75,33 @@ void SysStackUsage(sys_dbg_stack_info_t& s) {
[[maybe_unused]] int result = 0; [[maybe_unused]] int result = 0;
memset(&s, 0, sizeof(sys_dbg_stack_info_t));
// Record the reservation before the Linux /proc walk.
{
pthread_attr_t self_attr {};
#if defined(__APPLE__)
void* stack_top = pthread_get_stackaddr_np(pthread_self());
const size_t stack_size = pthread_get_stacksize_np(pthread_self());
if (stack_top != nullptr && stack_size != 0) {
s.reserved_addr = reinterpret_cast<uintptr_t>(stack_top) - stack_size;
s.reserved_size = stack_size;
}
(void)self_attr;
#else
if (pthread_getattr_np(pthread_self(), &self_attr) == 0) {
void* stack_base = nullptr;
size_t stack_size = 0;
if (pthread_attr_getstack(&self_attr, &stack_base, &stack_size) == 0 &&
stack_base != nullptr && stack_size != 0) {
s.reserved_addr = reinterpret_cast<uintptr_t>(stack_base);
s.reserved_size = stack_size;
}
pthread_attr_destroy(&self_attr);
}
#endif
}
char str[1024]; char str[1024];
char str2[1024]; char str2[1024];
result = sprintf(str, "/proc/%d/exe", static_cast<int>(pid)); result = sprintf(str, "/proc/%d/exe", static_cast<int>(pid));
@@ -43,8 +115,6 @@ void SysStackUsage(sys_dbg_stack_info_t& s) {
result = sprintf(str, "/proc/%d/maps", static_cast<int>(pid)); result = sprintf(str, "/proc/%d/maps", static_cast<int>(pid));
memset(&s, 0, sizeof(sys_dbg_stack_info_t));
FILE* f = fopen(str, "r"); FILE* f = fopen(str, "r");
if (f == nullptr) { if (f == nullptr) {
@@ -118,6 +188,11 @@ void SysStackUsage(sys_dbg_stack_info_t& s) {
} }
result = fclose(f); result = fclose(f);
if (s.reserved_addr == 0) {
s.reserved_addr = s.addr;
s.reserved_size = s.total_size;
}
} }
#endif #endif
+193 -26
View File
@@ -11,7 +11,11 @@
#include <cerrno> #include <cerrno>
#include <cstdlib> #include <cstdlib>
#include <dirent.h>
#include <fcntl.h>
#include <filesystem>
#include <sys/stat.h> #include <sys/stat.h>
#include <system_error>
#include <unistd.h> #include <unistd.h>
#include <utime.h> #include <utime.h>
@@ -39,10 +43,43 @@ struct sys_file_t {
}; };
}; };
// Darwin uses BSD timestamp member names.
#if defined(__APPLE__)
#define KYTY_STAT_ATIME_NS(st) ((st).st_atimespec.tv_nsec)
#define KYTY_STAT_MTIME_NS(st) ((st).st_mtimespec.tv_nsec)
#else
#define KYTY_STAT_ATIME_NS(st) ((st).st_atim.tv_nsec)
#define KYTY_STAT_MTIME_NS(st) ((st).st_mtim.tv_nsec)
#endif
static std::filesystem::path get_internal_name(const std::filesystem::path& name) { static std::filesystem::path get_internal_name(const std::filesystem::path& name) {
return name.is_absolute() ? name : (std::filesystem::path(".") / name); return name.is_absolute() ? name : (std::filesystem::path(".") / name);
} }
// Pass access-pattern hints to the host.
static void apply_cache_hint(FILE* f, sys_file_cache_type_t cache_type) {
if (f == nullptr) {
return;
}
#if !defined(__APPLE__)
int advice = POSIX_FADV_NORMAL;
switch (cache_type) {
case SYS_FILE_CACHE_RANDOM_ACCESS: advice = POSIX_FADV_RANDOM; break;
case SYS_FILE_CACHE_SEQUENTIAL_SCAN: advice = POSIX_FADV_SEQUENTIAL; break;
case SYS_FILE_CACHE_AUTO:
default: return;
}
::posix_fadvise(fileno(f), 0, 0, advice);
#else
if (cache_type == SYS_FILE_CACHE_SEQUENTIAL_SCAN) {
::fcntl(fileno(f), F_RDAHEAD, 1);
} else if (cache_type == SYS_FILE_CACHE_RANDOM_ACCESS) {
::fcntl(fileno(f), F_RDAHEAD, 0);
}
#endif
}
void SysFileRead(void* data, uint32_t size, sys_file_t& f, uint32_t* bytes_read) { void SysFileRead(void* data, uint32_t size, sys_file_t& f, uint32_t* bytes_read) {
if (f.type == SYS_FILE_FILE) { if (f.type == SYS_FILE_FILE) {
size_t w = fread(data, 1, size, f.f); size_t w = fread(data, 1, size, f.f);
@@ -136,7 +173,7 @@ sys_file_t* SysFileCreate(const std::filesystem::path& file_name) {
} }
sys_file_t* SysFileOpenR(const std::filesystem::path& file_name, sys_file_t* SysFileOpenR(const std::filesystem::path& file_name,
sys_file_cache_type_t /*cache_type*/) { sys_file_cache_type_t cache_type) {
auto* ret = new sys_file_t; auto* ret = new sys_file_t;
ret->type = SYS_FILE_FILE; ret->type = SYS_FILE_FILE;
@@ -150,6 +187,8 @@ sys_file_t* SysFileOpenR(const std::filesystem::path& file_name,
ret->type = SYS_FILE_ERROR; ret->type = SYS_FILE_ERROR;
} }
apply_cache_hint(f, cache_type);
ret->f = f; ret->f = f;
return ret; return ret;
@@ -180,7 +219,7 @@ sys_file_t* SysFileCreate() {
} }
sys_file_t* SysFileOpenW(const std::filesystem::path& file_name, sys_file_t* SysFileOpenW(const std::filesystem::path& file_name,
sys_file_cache_type_t /*cache_type*/) { sys_file_cache_type_t cache_type) {
auto* ret = new sys_file_t; auto* ret = new sys_file_t;
auto real_name = get_internal_name(file_name); auto real_name = get_internal_name(file_name);
@@ -194,13 +233,15 @@ sys_file_t* SysFileOpenW(const std::filesystem::path& file_name,
ret->type = SYS_FILE_FILE; ret->type = SYS_FILE_FILE;
} }
apply_cache_hint(f, cache_type);
ret->f = f; ret->f = f;
return ret; return ret;
} }
sys_file_t* SysFileOpenRw(const std::filesystem::path& file_name, sys_file_t* SysFileOpenRw(const std::filesystem::path& file_name,
sys_file_cache_type_t /*cache_type*/) { sys_file_cache_type_t cache_type) {
auto* ret = new sys_file_t; auto* ret = new sys_file_t;
auto real_name = get_internal_name(file_name); auto real_name = get_internal_name(file_name);
@@ -214,6 +255,8 @@ sys_file_t* SysFileOpenRw(const std::filesystem::path& file_name,
ret->type = SYS_FILE_FILE; ret->type = SYS_FILE_FILE;
} }
apply_cache_hint(f, cache_type);
ret->f = f; ret->f = f;
return ret; return ret;
@@ -239,11 +282,17 @@ uint64_t SysFileSize(sys_file_t& f) {
[[maybe_unused]] int result = 0; [[maybe_unused]] int result = 0;
if (f.type == SYS_FILE_FILE) { if (f.type == SYS_FILE_FILE) {
uint32_t pos = ftell(f.f); // Preserve sizes above 4 GiB.
result = fseek(f.f, 0, SEEK_END); const off_t pos = ftello(f.f);
uint32_t size = ftell(f.f); if (pos < 0) {
result = fseek(f.f, pos, SEEK_SET); return 0;
return size; }
if (fseeko(f.f, 0, SEEK_END) != 0) {
return 0;
}
const off_t size = ftello(f.f);
result = fseeko(f.f, pos, SEEK_SET);
return (size < 0 ? 0 : static_cast<uint64_t>(size));
} }
if (f.type == SYS_FILE_MEMORY_STAT || f.type == SYS_FILE_MEMORY_DYN) { if (f.type == SYS_FILE_MEMORY_STAT || f.type == SYS_FILE_MEMORY_DYN) {
@@ -260,8 +309,18 @@ uint64_t SysFileSize(const std::filesystem::path& file_name) {
return size; return size;
} }
bool SysFileTruncate(sys_file_t& /*f*/, uint64_t /*size*/) { bool SysFileTruncate(sys_file_t& f, uint64_t size) {
return false; bool ok = false;
if (f.type == SYS_FILE_FILE) {
// Flush before resizing and restore the caller's position.
const auto position = ftell(f.f);
fflush(f.f);
ok = (ftruncate(fileno(f.f), static_cast<off_t>(size)) == 0);
if (position >= 0) {
fseek(f.f, position, SEEK_SET);
}
}
return ok;
} }
bool SysFileUnlink(sys_file_t& /*f*/, const std::filesystem::path& name) { bool SysFileUnlink(sys_file_t& /*f*/, const std::filesystem::path& name) {
@@ -382,6 +441,7 @@ SysFileTimeStruct SysFileGetLastAccessTimeUtc(const std::filesystem::path& name)
} else { } else {
r.is_invalid = false; r.is_invalid = false;
r.time = s.st_atime; r.time = s.st_atime;
r.nanos = KYTY_STAT_ATIME_NS(s);
} }
return r; return r;
@@ -400,6 +460,7 @@ SysFileTimeStruct SysFileGetLastWriteTimeUtc(const std::filesystem::path& name)
} else { } else {
r.is_invalid = false; r.is_invalid = false;
r.time = s.st_mtime; r.time = s.st_mtime;
r.nanos = KYTY_STAT_MTIME_NS(s);
} }
return r; return r;
@@ -419,13 +480,36 @@ void SysFileGetLastAccessAndWriteTimeUtc(const std::filesystem::path& name, SysF
a.is_invalid = false; a.is_invalid = false;
w.is_invalid = false; w.is_invalid = false;
a.time = s.st_atime; a.time = s.st_atime;
a.nanos = KYTY_STAT_ATIME_NS(s);
w.time = s.st_mtime; w.time = s.st_mtime;
w.nanos = KYTY_STAT_MTIME_NS(s);
} }
} }
void SysFileGetLastAccessAndWriteTimeUtc(sys_file_t& /*f*/, SysFileTimeStruct& /*a*/, void SysFileGetLastAccessAndWriteTimeUtc(sys_file_t& f, SysFileTimeStruct& a,
SysFileTimeStruct& /*w*/) { SysFileTimeStruct& w) {
EXIT("not implemented\n"); if (f.type == SYS_FILE_FILE) {
struct stat s {};
const bool ok = (0 == fstat(fileno(f.f), &s));
a.is_invalid = w.is_invalid = !ok;
if (ok) {
a.time = s.st_atime;
a.nanos = KYTY_STAT_ATIME_NS(s);
w.time = s.st_mtime;
w.nanos = KYTY_STAT_MTIME_NS(s);
}
} else if (f.type == SYS_FILE_MEMORY_STAT || f.type == SYS_FILE_MEMORY_DYN) {
// Memory-backed files use the current time.
SysTimeStruct t {};
SysGetSystemTimeUtc(t);
SysSystemToFileTimeUtc(t, a);
SysSystemToFileTimeUtc(t, w);
} else {
a.is_invalid = w.is_invalid = true;
}
} }
bool SysFileSetLastAccessTimeUtc(const std::filesystem::path& name, SysFileTimeStruct& access) { bool SysFileSetLastAccessTimeUtc(const std::filesystem::path& name, SysFileTimeStruct& access) {
@@ -531,27 +615,110 @@ bool SysFileSetLastAccessAndWriteTimeUtc(const std::filesystem::path& name,
// } // }
} }
void SysFileFindFiles(const std::filesystem::path& /*path*/, // Recursively collect regular files.
std::vector<sys_file_find_t>& /*out*/) { void SysFileFindFiles(const std::filesystem::path& path, std::vector<sys_file_find_t>& out) {
EXIT("not implemented\n"); auto real_path = get_internal_name(path);
DIR* dir = opendir(real_path.string().c_str());
if (dir == nullptr) {
return;
}
for (const dirent* entry = readdir(dir); entry != nullptr; entry = readdir(dir)) {
const std::string file_name(entry->d_name);
if (file_name == "." || file_name == "..") {
continue;
}
auto child = real_path / file_name;
struct stat s {};
// lstat, so a symlink is never followed into a cycle during the recursive walk.
if (0 != lstat(child.string().c_str(), &s)) {
continue;
}
if (S_ISDIR(s.st_mode)) {
SysFileFindFiles(child, out);
} else if (S_ISREG(s.st_mode)) {
sys_file_find_t r {};
r.path_with_name = child;
r.size = static_cast<uint64_t>(s.st_size);
r.last_access_time.is_invalid = false;
r.last_access_time.time = s.st_atime;
r.last_access_time.nanos = KYTY_STAT_ATIME_NS(s);
r.last_write_time.is_invalid = false;
r.last_write_time.time = s.st_mtime;
r.last_write_time.nanos = KYTY_STAT_MTIME_NS(s);
out.push_back(r);
}
}
closedir(dir);
} }
void SysFileGetDents(const std::filesystem::path& /*path*/, std::vector<sys_dir_entry_t>& /*out*/) { // Keep "." and ".." to match FindFirstFileW.
EXIT("not implemented\n"); void SysFileGetDents(const std::filesystem::path& path, std::vector<sys_dir_entry_t>& out) {
auto real_path = get_internal_name(path);
DIR* dir = opendir(real_path.string().c_str());
if (dir == nullptr) {
return;
}
for (const dirent* entry = readdir(dir); entry != nullptr; entry = readdir(dir)) {
sys_dir_entry_t r {};
r.name = entry->d_name;
if (entry->d_type == DT_UNKNOWN) {
// Some filesystems do not populate d_type.
struct stat s {};
r.is_file = 0 == lstat((real_path / r.name).string().c_str(), &s) && S_ISREG(s.st_mode);
} else {
r.is_file = entry->d_type != DT_DIR;
}
out.push_back(r);
}
closedir(dir);
} }
bool SysFileCopyFile(const std::filesystem::path& /*src*/, const std::filesystem::path& /*dst*/) { bool SysFileCopyFile(const std::filesystem::path& src, const std::filesystem::path& dst) {
EXIT("not implemented\n"); std::error_code error;
return false; return std::filesystem::copy_file(get_internal_name(src), get_internal_name(dst),
std::filesystem::copy_options::overwrite_existing, error) &&
!error;
} }
bool SysFileMoveFile(const std::filesystem::path& /*src*/, const std::filesystem::path& /*dst*/) { bool SysFileMoveFile(const std::filesystem::path& src, const std::filesystem::path& dst) {
EXIT("not implemented\n"); auto real_src = get_internal_name(src);
return false; auto real_dst = get_internal_name(dst);
// Match MoveFileW: fail when the destination exists.
std::error_code error;
if (std::filesystem::exists(real_dst, error)) {
return false;
}
return 0 == rename(real_src.string().c_str(), real_dst.string().c_str());
} }
void SysFileRemoveReadonly(const std::filesystem::path& /*name*/) { void SysFileRemoveReadonly(const std::filesystem::path& name) {
EXIT("not implemented\n"); auto real_name = get_internal_name(name);
auto real_name_str = real_name.string();
struct stat s {};
if (0 != stat(real_name_str.c_str(), &s)) {
return;
}
chmod(real_name_str.c_str(), s.st_mode | S_IWUSR);
} }
#endif #endif
+209 -33
View File
@@ -8,9 +8,16 @@
#include "common/platform/sysVirtual.h" #include "common/platform/sysVirtual.h"
#include "common/virtualMemory.h" #include "common/virtualMemory.h"
#include <atomic>
#include <map> #include <map>
#include <pthread.h> #include <pthread.h>
#include <sys/mman.h> #include <sys/mman.h>
#include <unistd.h>
#if defined(__APPLE__)
#include <mach/mach.h>
#include <mach/mach_vm.h>
#endif
// IWYU pragma: no_include <asm/mman-common.h> // IWYU pragma: no_include <asm/mman-common.h>
// IWYU pragma: no_include <asm/mman.h> // IWYU pragma: no_include <asm/mman.h>
@@ -31,8 +38,8 @@ void SysVirtualInit() {
pthread_mutexattr_t attr {}; pthread_mutexattr_t attr {};
pthread_mutexattr_init(&attr); pthread_mutexattr_init(&attr);
#if KYTY_PLATFORM == KYTY_PLATFORM_LINUX #if KYTY_PLATFORM == KYTY_PLATFORM_LINUX && !defined(__APPLE__)
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_FAST_NP); pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_FAST_NP); // glibc-only fast mutex
#else #else
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL); pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL);
#endif #endif
@@ -76,6 +83,70 @@ static VirtualMemory::Mode get_protection_flag(int mode) {
} }
} }
// Keep automatic mappings inside the guest and GPU-addressable low window.
#ifdef KYTY_FIXED_NOREPLACE
static constexpr uintptr_t LOW_ARENA_LIMIT = 0x000000FC00000000ULL; // libc mspace window ceiling
static constexpr uintptr_t LOW_ARENA_FLOOR = 0x000000A000000000ULL; // 640 GiB
static constexpr uintptr_t LOW_ARENA_GRAIN = 0x0000000000010000ULL; // 64 KiB
static_assert(LOW_ARENA_LIMIT <= 0x0000010000000000ULL,
"arena must stay inside the GPU page tracker's 1<<40 window");
static_assert(LOW_ARENA_FLOOR < LOW_ARENA_LIMIT, "arena floor must sit below its ceiling");
static std::atomic<uintptr_t> g_low_arena_next {LOW_ARENA_LIMIT};
#endif
// Caller holds g_virtual_mutex.
static void record_alloc(uintptr_t addr, size_t size) {
auto next = g_allocs->upper_bound(addr);
if (next != g_allocs->begin()) {
auto it = std::prev(next);
const auto alloc_addr = it->first;
const auto alloc_end = alloc_addr + it->second;
if (alloc_addr <= addr && addr + size <= alloc_end) {
g_allocs->erase(it);
if (alloc_addr < addr) {
(*g_allocs)[alloc_addr] = addr - alloc_addr;
}
if (addr + size < alloc_end) {
(*g_allocs)[addr + size] = alloc_end - (addr + size);
}
}
}
(*g_allocs)[addr] = size;
}
#ifdef KYTY_FIXED_NOREPLACE
static uintptr_t align_up_to(uintptr_t addr, uint64_t alignment) {
return (addr + alignment - 1) & ~(alignment - 1);
}
#endif
// Freed arena addresses are not reused while GPU caches remain keyed by address.
static void* map_anonymous(uintptr_t addr, size_t size, int protect, int flags) {
if (addr != 0) {
return mmap(reinterpret_cast<void*>(addr), size, protect, flags, -1, 0); // NOLINT
}
#ifdef KYTY_FIXED_NOREPLACE
const auto step = align_up_to(size, LOW_ARENA_GRAIN);
for (int attempt = 0; attempt < 256; attempt++) {
const auto top = g_low_arena_next.fetch_sub(step, std::memory_order_relaxed);
if (top < step || top - step < LOW_ARENA_FLOOR) {
break;
}
const auto hint = (top - step) & ~(LOW_ARENA_GRAIN - 1);
void* ptr = mmap(reinterpret_cast<void*>(hint), size, protect,
flags | MAP_FIXED_NOREPLACE, -1, 0); // NOLINT
if (ptr != MAP_FAILED) {
return ptr;
}
}
#endif
return mmap(nullptr, size, protect, flags, -1, 0); // NOLINT
}
uint64_t SysVirtualAlloc(uint64_t address, uint64_t size, VirtualMemory::Mode mode) { uint64_t SysVirtualAlloc(uint64_t address, uint64_t size, VirtualMemory::Mode mode) {
EXIT_IF(g_allocs == nullptr); EXIT_IF(g_allocs == nullptr);
@@ -83,14 +154,13 @@ uint64_t SysVirtualAlloc(uint64_t address, uint64_t size, VirtualMemory::Mode mo
int protect = get_protection_flag(mode); int protect = get_protection_flag(mode);
void* ptr = void* ptr = map_anonymous(addr, size, protect, MAP_PRIVATE | MAP_ANON);
mmap(reinterpret_cast<void*>(addr), size, protect, MAP_PRIVATE | MAP_ANON, -1, 0); // NOLINT
auto ret_addr = reinterpret_cast<uintptr_t>(ptr); auto ret_addr = reinterpret_cast<uintptr_t>(ptr);
if (ptr != MAP_FAILED) { if (ptr != MAP_FAILED) {
pthread_mutex_lock(&g_virtual_mutex); pthread_mutex_lock(&g_virtual_mutex);
(*g_allocs)[ret_addr] = size; record_alloc(ret_addr, size);
uintptr_t page_start = ret_addr >> 12u; uintptr_t page_start = ret_addr >> 12u;
uintptr_t page_end = (ret_addr + size - 1) >> 12u; uintptr_t page_end = (ret_addr + size - 1) >> 12u;
for (uintptr_t page = page_start; page <= page_end; page++) { for (uintptr_t page = page_start; page <= page_end; page++) {
@@ -117,18 +187,43 @@ uint64_t SysVirtualAllocAligned(uint64_t address, uint64_t size, VirtualMemory::
auto addr = static_cast<uintptr_t>(address); auto addr = static_cast<uintptr_t>(address);
int protect = get_protection_flag(mode); int protect = get_protection_flag(mode);
void* ptr = void* ptr = map_anonymous(addr, size, protect, MAP_PRIVATE | MAP_ANON);
mmap(reinterpret_cast<void*>(addr), size, protect, MAP_PRIVATE | MAP_ANON, -1, 0); // NOLINT
auto ret_addr = reinterpret_cast<uintptr_t>(ptr); auto ret_addr = reinterpret_cast<uintptr_t>(ptr);
if (ptr != MAP_FAILED && ((ret_addr & (alignment - 1)) != 0)) { if (ptr != MAP_FAILED && ((ret_addr & (alignment - 1)) != 0)) {
munmap(ptr, size); munmap(ptr, size);
ptr = mmap(reinterpret_cast<void*>(addr), size + alignment, protect, ptr = map_anonymous(addr, size + alignment, protect,
MAP_PRIVATE | MAP_ANON | MAP_NORESERVE, -1, 0); // NOLINT MAP_PRIVATE | MAP_ANON | MAP_NORESERVE);
ret_addr = reinterpret_cast<uintptr_t>(ptr); ret_addr = reinterpret_cast<uintptr_t>(ptr);
if (ptr != MAP_FAILED) { if (ptr != MAP_FAILED) {
#if defined(__APPLE__)
// Carve the aligned subrange out of the live mapping with MAP_FIXED (in-place
// replacement) and trim the slack; never munmap the whole range first, or a
// concurrent host mapping (dyld, Rosetta, Metal) could claim the hole and be
// destroyed by the MAP_FIXED. Other platforms keep the original path below.
auto aligned_addr = align_up(ret_addr, alignment);
// NOLINTNEXTLINE
void* fixed = mmap(reinterpret_cast<void*>(aligned_addr), size, protect,
MAP_FIXED | MAP_PRIVATE | MAP_ANON, -1, 0);
if (fixed == MAP_FAILED) {
munmap(ptr, size + alignment);
ret_addr = 0;
ptr = MAP_FAILED;
} else {
if (aligned_addr > ret_addr) {
munmap(reinterpret_cast<void*>(ret_addr), aligned_addr - ret_addr);
}
const uintptr_t tail_start = aligned_addr + size;
const uintptr_t resv_end = ret_addr + size + alignment;
if (resv_end > tail_start) {
munmap(reinterpret_cast<void*>(tail_start), resv_end - tail_start);
}
ptr = fixed;
ret_addr = aligned_addr;
}
#else
munmap(ptr, size + alignment); munmap(ptr, size + alignment);
auto aligned_addr = align_up(ret_addr, alignment); auto aligned_addr = align_up(ret_addr, alignment);
#ifdef KYTY_FIXED_NOREPLACE #ifdef KYTY_FIXED_NOREPLACE
@@ -146,6 +241,7 @@ uint64_t SysVirtualAllocAligned(uint64_t address, uint64_t size, VirtualMemory::
ret_addr = 0; ret_addr = 0;
ptr = MAP_FAILED; ptr = MAP_FAILED;
} }
#endif
} }
} }
@@ -154,7 +250,7 @@ uint64_t SysVirtualAllocAligned(uint64_t address, uint64_t size, VirtualMemory::
} }
pthread_mutex_lock(&g_virtual_mutex); pthread_mutex_lock(&g_virtual_mutex);
(*g_allocs)[ret_addr] = size; record_alloc(ret_addr, size);
uintptr_t page_start = ret_addr >> 12u; uintptr_t page_start = ret_addr >> 12u;
uintptr_t page_end = (ret_addr + size - 1) >> 12u; uintptr_t page_end = (ret_addr + size - 1) >> 12u;
for (uintptr_t page = page_start; page <= page_end; page++) { for (uintptr_t page = page_start; page <= page_end; page++) {
@@ -165,6 +261,27 @@ uint64_t SysVirtualAllocAligned(uint64_t address, uint64_t size, VirtualMemory::
return ret_addr; return ret_addr;
} }
#if defined(__APPLE__)
// macOS has no /proc/self/maps; query the Mach VM map directly. mach_vm_region returns
// the first mapped region at or above `region_addr`; if it begins before the end of the
// requested range, the range overlaps an existing mapping.
static bool is_mapped(void* ptr, size_t length) {
auto query_addr = reinterpret_cast<mach_vm_address_t>(ptr);
mach_vm_address_t region_addr = query_addr;
mach_vm_size_t region_size = 0;
vm_region_basic_info_data_64_t info {};
mach_msg_type_number_t count = VM_REGION_BASIC_INFO_COUNT_64;
mach_port_t object_name = MACH_PORT_NULL;
kern_return_t kr =
mach_vm_region(mach_task_self(), &region_addr, &region_size, VM_REGION_BASIC_INFO_64,
reinterpret_cast<vm_region_info_t>(&info), &count, &object_name);
if (kr != KERN_SUCCESS) {
return false; // no region at or above the address → unmapped
}
return region_addr < (query_addr + length);
}
#else
static bool is_mapped(void* ptr, size_t length) { static bool is_mapped(void* ptr, size_t length) {
FILE* file = fopen("/proc/self/maps", "r"); FILE* file = fopen("/proc/self/maps", "r");
char line[1024]; char line[1024];
@@ -189,6 +306,7 @@ static bool is_mapped(void* ptr, size_t length) {
fclose(file); fclose(file);
return ret; return ret;
} }
#endif
bool SysVirtualAllocFixed(uint64_t address, uint64_t size, VirtualMemory::Mode mode) { bool SysVirtualAllocFixed(uint64_t address, uint64_t size, VirtualMemory::Mode mode) {
EXIT_IF(g_allocs == nullptr); EXIT_IF(g_allocs == nullptr);
@@ -218,7 +336,7 @@ bool SysVirtualAllocFixed(uint64_t address, uint64_t size, VirtualMemory::Mode m
if (ptr != MAP_FAILED) { if (ptr != MAP_FAILED) {
pthread_mutex_lock(&g_virtual_mutex); pthread_mutex_lock(&g_virtual_mutex);
(*g_allocs)[ret_addr] = size; record_alloc(ret_addr, size);
uintptr_t page_start = ret_addr >> 12u; uintptr_t page_start = ret_addr >> 12u;
uintptr_t page_end = (ret_addr + size - 1) >> 12u; uintptr_t page_end = (ret_addr + size - 1) >> 12u;
for (uintptr_t page = page_start; page <= page_end; page++) { for (uintptr_t page = page_start; page <= page_end; page++) {
@@ -249,18 +367,44 @@ uint64_t SysVirtualReserveAligned(uint64_t address, uint64_t size, uint64_t alig
auto addr = static_cast<uintptr_t>(address); auto addr = static_cast<uintptr_t>(address);
void* ptr = mmap(reinterpret_cast<void*>(addr), size, PROT_NONE, void* ptr = map_anonymous(addr, size, PROT_NONE, MAP_PRIVATE | MAP_ANON | MAP_NORESERVE);
MAP_PRIVATE | MAP_ANON | MAP_NORESERVE, -1, 0); // NOLINT
auto ret_addr = reinterpret_cast<uintptr_t>(ptr); auto ret_addr = reinterpret_cast<uintptr_t>(ptr);
if (ptr != MAP_FAILED && ((ret_addr & (alignment - 1)) != 0)) { if (ptr != MAP_FAILED && ((ret_addr & (alignment - 1)) != 0)) {
munmap(ptr, size); munmap(ptr, size);
ptr = mmap(reinterpret_cast<void*>(addr), size + alignment, PROT_NONE, ptr = map_anonymous(addr, size + alignment, PROT_NONE,
MAP_PRIVATE | MAP_ANON | MAP_NORESERVE, -1, 0); // NOLINT MAP_PRIVATE | MAP_ANON | MAP_NORESERVE);
ret_addr = reinterpret_cast<uintptr_t>(ptr); ret_addr = reinterpret_cast<uintptr_t>(ptr);
if (ptr != MAP_FAILED) { if (ptr != MAP_FAILED) {
#if defined(__APPLE__)
// Carve the aligned subrange out of the live reservation with MAP_FIXED (an
// in-place replacement), then trim the slack. The range must never be
// returned to the OS in between: another thread (dyld, Rosetta, Metal,
// malloc) could claim the hole, and the subsequent MAP_FIXED would silently
// destroy its mapping. Other platforms keep the original path below.
auto aligned_addr = align_up(ret_addr, alignment);
// NOLINTNEXTLINE
void* fixed = mmap(reinterpret_cast<void*>(aligned_addr), size, PROT_NONE,
MAP_FIXED | MAP_PRIVATE | MAP_ANON | MAP_NORESERVE, -1, 0);
if (fixed == MAP_FAILED) {
munmap(ptr, size + alignment);
ret_addr = 0;
ptr = MAP_FAILED;
} else {
if (aligned_addr > ret_addr) {
munmap(reinterpret_cast<void*>(ret_addr), aligned_addr - ret_addr);
}
const uintptr_t tail_start = aligned_addr + size;
const uintptr_t resv_end = ret_addr + size + alignment;
if (resv_end > tail_start) {
munmap(reinterpret_cast<void*>(tail_start), resv_end - tail_start);
}
ptr = fixed;
ret_addr = aligned_addr;
}
#else
munmap(ptr, size + alignment); munmap(ptr, size + alignment);
auto aligned_addr = align_up(ret_addr, alignment); auto aligned_addr = align_up(ret_addr, alignment);
#ifdef KYTY_FIXED_NOREPLACE #ifdef KYTY_FIXED_NOREPLACE
@@ -278,6 +422,7 @@ uint64_t SysVirtualReserveAligned(uint64_t address, uint64_t size, uint64_t alig
ret_addr = 0; ret_addr = 0;
ptr = MAP_FAILED; ptr = MAP_FAILED;
} }
#endif
} }
} }
@@ -286,12 +431,7 @@ uint64_t SysVirtualReserveAligned(uint64_t address, uint64_t size, uint64_t alig
} }
pthread_mutex_lock(&g_virtual_mutex); pthread_mutex_lock(&g_virtual_mutex);
(*g_allocs)[ret_addr] = size; record_alloc(ret_addr, size);
uintptr_t page_start = ret_addr >> 12u;
uintptr_t page_end = (ret_addr + size - 1) >> 12u;
for (uintptr_t page = page_start; page <= page_end; page++) {
(*g_protects)[page] = PROT_NONE;
}
pthread_mutex_unlock(&g_virtual_mutex); pthread_mutex_unlock(&g_virtual_mutex);
return ret_addr; return ret_addr;
@@ -324,12 +464,7 @@ bool SysVirtualReserveFixed(uint64_t address, uint64_t size) {
if (ptr != MAP_FAILED) { if (ptr != MAP_FAILED) {
pthread_mutex_lock(&g_virtual_mutex); pthread_mutex_lock(&g_virtual_mutex);
(*g_allocs)[ret_addr] = size; record_alloc(ret_addr, size);
uintptr_t page_start = ret_addr >> 12u;
uintptr_t page_end = (ret_addr + size - 1) >> 12u;
for (uintptr_t page = page_start; page <= page_end; page++) {
(*g_protects)[page] = PROT_NONE;
}
pthread_mutex_unlock(&g_virtual_mutex); pthread_mutex_unlock(&g_virtual_mutex);
return true; return true;
@@ -339,7 +474,29 @@ bool SysVirtualReserveFixed(uint64_t address, uint64_t size) {
} }
bool SysVirtualDecommit(uint64_t address, uint64_t size) { bool SysVirtualDecommit(uint64_t address, uint64_t size) {
return SysVirtualProtect(address, size, VirtualMemory::Mode::NoAccess); // Drop physical pages while preserving the reservation.
if (!SysVirtualProtect(address, size, VirtualMemory::Mode::NoAccess)) {
return false;
}
if (size != 0) {
#if defined(__APPLE__)
constexpr int RECLAIM_ADVICE = MADV_FREE;
#else
constexpr int RECLAIM_ADVICE = MADV_DONTNEED;
#endif
const auto page_size = static_cast<uintptr_t>(sysconf(_SC_PAGESIZE));
if (page_size != 0) {
// Do not discard pages outside the requested range.
const auto begin = (static_cast<uintptr_t>(address) + page_size - 1) & ~(page_size - 1);
const auto end = (static_cast<uintptr_t>(address) + size) & ~(page_size - 1);
if (end > begin) {
::madvise(reinterpret_cast<void*>(begin), end - begin, RECLAIM_ADVICE);
}
}
}
return true;
} }
bool SysVirtualFree(uint64_t address) { bool SysVirtualFree(uint64_t address) {
@@ -391,15 +548,34 @@ bool SysVirtualFreeRange(uint64_t address, uint64_t size) {
pthread_mutex_unlock(&g_virtual_mutex); pthread_mutex_unlock(&g_virtual_mutex);
return false; return false;
} }
auto it = std::prev(next);
const auto alloc_addr = it->first; // A reservation may have been split into several adjacent records.
const auto alloc_end = alloc_addr + it->second; auto first = std::prev(next);
if (addr < alloc_addr || end > alloc_end || munmap(reinterpret_cast<void*>(addr), size) != 0) { const auto alloc_addr = first->first;
if (addr < alloc_addr || alloc_addr + first->second <= addr) {
pthread_mutex_unlock(&g_virtual_mutex); pthread_mutex_unlock(&g_virtual_mutex);
return false; return false;
} }
g_allocs->erase(it); auto last = first;
uintptr_t cursor = alloc_addr + first->second;
while (cursor < end) {
auto following = std::next(last);
if (following == g_allocs->end() || following->first != cursor) {
pthread_mutex_unlock(&g_virtual_mutex);
return false;
}
last = following;
cursor = following->first + following->second;
}
const auto alloc_end = cursor;
if (munmap(reinterpret_cast<void*>(addr), size) != 0) {
pthread_mutex_unlock(&g_virtual_mutex);
return false;
}
g_allocs->erase(first, std::next(last));
if (alloc_addr < addr) { if (alloc_addr < addr) {
(*g_allocs)[alloc_addr] = addr - alloc_addr; (*g_allocs)[alloc_addr] = addr - alloc_addr;
} }
+11 -7
View File
@@ -27,7 +27,9 @@ struct SysFileTimeStruct {
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
FILETIME time; FILETIME time;
#elif KYTY_PLATFORM == KYTY_PLATFORM_LINUX #elif KYTY_PLATFORM == KYTY_PLATFORM_LINUX
// Nanoseconds preserve sub-second file timestamps.
time_t time; time_t time;
long nanos;
#endif #endif
bool is_invalid; bool is_invalid;
}; };
@@ -139,7 +141,7 @@ inline void SysFileToSystemTimeUtc(const SysFileTimeStruct& f, SysTimeStruct& t)
t.Hour = i.tm_hour; t.Hour = i.tm_hour;
t.Minute = i.tm_min; t.Minute = i.tm_min;
t.Second = (i.tm_sec == 60 ? 59 : i.tm_sec); t.Second = (i.tm_sec == 60 ? 59 : i.tm_sec);
t.Milliseconds = 0; t.Milliseconds = static_cast<uint16_t>((f.nanos / 1000000) % 1000);
} }
inline void SysTimeTToSystem(time_t t, SysTimeStruct& s) { inline void SysTimeTToSystem(time_t t, SysTimeStruct& s) {
@@ -168,10 +170,11 @@ inline void SysSystemToFileTimeUtc(const SysTimeStruct& f, SysFileTimeStruct& t)
// Retrieves the current local date and time. // Retrieves the current local date and time.
inline void SysGetSystemTime(SysTimeStruct& t) { inline void SysGetSystemTime(SysTimeStruct& t) {
time_t st {}; // Preserve millisecond precision.
timespec now {};
struct tm i {}; struct tm i {};
if (time(&st) == static_cast<time_t>(-1) || localtime_r(&st, &i) == nullptr) { if (clock_gettime(CLOCK_REALTIME, &now) != 0 || localtime_r(&now.tv_sec, &i) == nullptr) {
t.is_invalid = true; t.is_invalid = true;
return; return;
} }
@@ -183,15 +186,16 @@ inline void SysGetSystemTime(SysTimeStruct& t) {
t.Hour = i.tm_hour; t.Hour = i.tm_hour;
t.Minute = i.tm_min; t.Minute = i.tm_min;
t.Second = (i.tm_sec == 60 ? 59 : i.tm_sec); t.Second = (i.tm_sec == 60 ? 59 : i.tm_sec);
t.Milliseconds = 0; t.Milliseconds = static_cast<uint16_t>((now.tv_nsec / 1000000) % 1000);
} }
// Retrieves the current system date and time in Coordinated Universal Time (UTC). // Retrieves the current system date and time in Coordinated Universal Time (UTC).
inline void SysGetSystemTimeUtc(SysTimeStruct& t) { inline void SysGetSystemTimeUtc(SysTimeStruct& t) {
time_t st {}; // Preserve millisecond precision.
timespec now {};
struct tm i {}; struct tm i {};
if (time(&st) == static_cast<time_t>(-1) || gmtime_r(&st, &i) == nullptr) { if (clock_gettime(CLOCK_REALTIME, &now) != 0 || gmtime_r(&now.tv_sec, &i) == nullptr) {
t.is_invalid = true; t.is_invalid = true;
return; return;
} }
@@ -203,7 +207,7 @@ inline void SysGetSystemTimeUtc(SysTimeStruct& t) {
t.Hour = i.tm_hour; t.Hour = i.tm_hour;
t.Minute = i.tm_min; t.Minute = i.tm_min;
t.Second = (i.tm_sec == 60 ? 59 : i.tm_sec); t.Second = (i.tm_sec == 60 ? 59 : i.tm_sec);
t.Milliseconds = 0; t.Milliseconds = static_cast<uint16_t>((now.tv_nsec / 1000000) % 1000);
} }
inline void SysQueryPerformanceFrequency(uint64_t* freq) { inline void SysQueryPerformanceFrequency(uint64_t* freq) {
+47
View File
@@ -7,6 +7,7 @@
#include <atomic> #include <atomic>
#include <chrono> // IWYU pragma: keep #include <chrono> // IWYU pragma: keep
#include <condition_variable> // IWYU pragma: keep #include <condition_variable> // IWYU pragma: keep
#include <cerrno>
#include <mutex> #include <mutex>
#include <vector> #include <vector>
@@ -14,6 +15,12 @@
#define KYTY_WIN_CS #define KYTY_WIN_CS
#endif #endif
// macOS has no clock_nanosleep.
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS && !defined(__APPLE__)
#define KYTY_POSIX_HIGH_RES_SLEEP
#include <ctime>
#endif
#include <sstream> #include <sstream>
#include <string> #include <string>
#include <thread> #include <thread>
@@ -121,6 +128,42 @@ static SleepConditionVariableCS_func_t ResolveSleepConditionVariableCS() {
#endif #endif
#ifdef KYTY_POSIX_HIGH_RES_SLEEP
// Spin for very short waits; use an absolute deadline for longer waits.
static void SleepHighResolutionNanos(uint64_t nanos) {
if (nanos == 0) {
return;
}
constexpr uint64_t NANOS_PER_SEC = 1000000000;
constexpr uint64_t SPIN_LIMIT_NS = 50000; // below this a context switch dominates
timespec deadline {};
if (clock_gettime(CLOCK_MONOTONIC, &deadline) != 0) {
std::this_thread::sleep_for(std::chrono::nanoseconds(nanos));
return;
}
auto target_nsec = static_cast<uint64_t>(deadline.tv_nsec) + nanos;
deadline.tv_sec += static_cast<time_t>(target_nsec / NANOS_PER_SEC);
deadline.tv_nsec = static_cast<long>(target_nsec % NANOS_PER_SEC);
if (nanos <= SPIN_LIMIT_NS) {
timespec now {};
do {
if (clock_gettime(CLOCK_MONOTONIC, &now) != 0) {
return;
}
} while (now.tv_sec < deadline.tv_sec ||
(now.tv_sec == deadline.tv_sec && now.tv_nsec < deadline.tv_nsec));
return;
}
while (clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &deadline, nullptr) == EINTR) {
}
}
#endif
namespace Common { namespace Common {
using thread_id_t = std::thread::id; using thread_id_t = std::thread::id;
@@ -249,6 +292,8 @@ void Thread::Sleep(uint32_t millis) {
void Thread::SleepMicro(uint32_t micros) { void Thread::SleepMicro(uint32_t micros) {
#ifdef KYTY_WIN_CS #ifdef KYTY_WIN_CS
SleepHighResolution100ns(static_cast<uint64_t>(micros) * 10); SleepHighResolution100ns(static_cast<uint64_t>(micros) * 10);
#elif defined(KYTY_POSIX_HIGH_RES_SLEEP)
SleepHighResolutionNanos(static_cast<uint64_t>(micros) * 1000);
#else #else
std::this_thread::sleep_for(std::chrono::microseconds(micros)); std::this_thread::sleep_for(std::chrono::microseconds(micros));
#endif #endif
@@ -257,6 +302,8 @@ void Thread::SleepMicro(uint32_t micros) {
void Thread::SleepNano(uint64_t nanos) { void Thread::SleepNano(uint64_t nanos) {
#ifdef KYTY_WIN_CS #ifdef KYTY_WIN_CS
SleepHighResolution100ns((nanos + 99) / 100); SleepHighResolution100ns((nanos + 99) / 100);
#elif defined(KYTY_POSIX_HIGH_RES_SLEEP)
SleepHighResolutionNanos(nanos);
#else #else
std::this_thread::sleep_for(std::chrono::nanoseconds(nanos)); std::this_thread::sleep_for(std::chrono::nanoseconds(nanos));
#endif #endif
@@ -65,10 +65,23 @@ constexpr uint32_t GcrKnownMask = GcrGl2MetadataInvalidate | GcrGl0V
GcrGl2Writeback | GcrOrder012 | GcrOrder210; GcrGl2Writeback | GcrOrder012 | GcrOrder210;
constexpr uint32_t RegisterSelectorMask = 0x70000000u; constexpr uint32_t RegisterSelectorMask = 0x70000000u;
uint32_t NormalizeRegisterOffset(uint32_t raw_offset) { constexpr uint32_t NormalizeRegisterOffset(uint32_t raw_offset) {
return (raw_offset & ~RegisterSelectorMask); return raw_offset & ~RegisterSelectorMask;
} }
// Indirect Cx descriptors retain their selector. Selector 1 offsets 0..31 address the
// SPI_PS_INPUT_CNTL register bank; ordinary context-register offsets remain unchanged.
constexpr uint32_t DecodeIndirectCxRegisterOffset(uint32_t raw_offset) {
const auto offset = NormalizeRegisterOffset(raw_offset);
return (raw_offset & RegisterSelectorMask) == Pm4::CX_PS_SHADER_USAGE_BASE && offset < 32u
? Pm4::SPI_PS_INPUT_CNTL_0 + offset
: offset;
}
static_assert(DecodeIndirectCxRegisterOffset(Pm4::CX_PS_SHADER_USAGE_BASE + 2u) ==
Pm4::SPI_PS_INPUT_CNTL_0 + 2u);
static_assert(DecodeIndirectCxRegisterOffset(Pm4::DB_Z_INFO) == Pm4::DB_Z_INFO);
bool ReleaseMemGcrNeedsBarrier(uint32_t eop_event_type, uint32_t gcr_cntl) { bool ReleaseMemGcrNeedsBarrier(uint32_t eop_event_type, uint32_t gcr_cntl) {
return eop_event_type != 0x28u || return eop_event_type != 0x28u ||
(gcr_cntl & (GcrGl2MetadataInvalidate | GcrGl0VectorInvalidate | GcrGl1Invalidate | (gcr_cntl & (GcrGl2MetadataInvalidate | GcrGl0VectorInvalidate | GcrGl1Invalidate |
@@ -2321,14 +2334,18 @@ KYTY_CP_OP_PARSER(CpOpIndirectCxRegs) {
EXIT("indirect CX registers have null address, num_regs = %" PRIu32 "\n", indirect_num_dw); EXIT("indirect CX registers have null address, num_regs = %" PRIu32 "\n", indirect_num_dw);
} }
for (uint32_t i = 0; i < indirect_num_dw; i++, indirect_buffer += 2) { for (uint32_t i = 0; i < indirect_num_dw; i++, indirect_buffer += 2) {
auto cmd_offset = indirect_buffer[0]; // Keep the encoded offset for packet control values, and use the decoded offset only
auto value = indirect_buffer[1]; // for register dispatch.
auto raw_cmd_offset = indirect_buffer[0];
auto cmd_offset = DecodeIndirectCxRegisterOffset(raw_cmd_offset);
auto value = indirect_buffer[1];
if (HwCtxTrySetFakeRegister(cmd_offset, value)) { if (HwCtxTrySetFakeRegister(cmd_offset, value)) {
continue; continue;
} }
if (cmd_offset == 0xffffffffu) { // The sentinel is an encoded descriptor value and must be checked before normalization.
if (raw_cmd_offset == 0xffffffffu) {
static bool logged = false; static bool logged = false;
if (!logged) { if (!logged) {
LOGF("\t temporary: skipping indirect CX sentinel pair offset = 0xffffffff, value " LOGF("\t temporary: skipping indirect CX sentinel pair offset = 0xffffffff, value "
+3
View File
@@ -385,6 +385,9 @@ constexpr uint32_t SPI_SHADER_POS_FORMAT = 0x1C3;
constexpr uint32_t SPI_SHADER_Z_FORMAT = 0x1C4; constexpr uint32_t SPI_SHADER_Z_FORMAT = 0x1C4;
constexpr uint32_t SPI_SHADER_COL_FORMAT = 0x1C5; constexpr uint32_t SPI_SHADER_COL_FORMAT = 0x1C5;
// Indirect Cx descriptor selector for the 32-entry PS input-control register bank.
constexpr uint32_t CX_PS_SHADER_USAGE_BASE = 0x10000000u;
constexpr uint32_t CB_BLEND0_CONTROL = 0x1E0; constexpr uint32_t CB_BLEND0_CONTROL = 0x1E0;
constexpr uint32_t CB_BLEND0_CONTROL_COLOR_SRCBLEND_SHIFT = 0; constexpr uint32_t CB_BLEND0_CONTROL_COLOR_SRCBLEND_SHIFT = 0;
constexpr uint32_t CB_BLEND0_CONTROL_COLOR_SRCBLEND_MASK = 0x1F; constexpr uint32_t CB_BLEND0_CONTROL_COLOR_SRCBLEND_MASK = 0x1F;
+43 -2
View File
@@ -38,10 +38,51 @@ public:
bool downloaded) noexcept; bool downloaded) noexcept;
[[nodiscard]] bool InvalidateRegion(uint64_t vaddr, uint64_t size, [[nodiscard]] bool InvalidateRegion(uint64_t vaddr, uint64_t size,
PageFaultPhase phase) noexcept; PageFaultPhase phase) noexcept;
template <typename Flush>
void InvalidateRegion(uint64_t vaddr, uint64_t size, Flush&& on_flush) {
static_assert(std::is_invocable_v<Flush&>);
CheckNotInUploadCallback();
ValidateRange(vaddr, size);
const auto update_cpu_state = [this, vaddr, size] {
std::lock_guard access(m_access_mutex);
std::vector<RegionManager*> managers;
Iterate<false>(vaddr, size, [&](RegionManager* manager, uint64_t, uint64_t) {
managers.push_back(manager);
});
std::vector<std::unique_lock<TrackingSpinLock>> locks;
locks.reserve(managers.size());
for (auto* manager: managers) {
locks.emplace_back(manager->lock);
}
const bool gpu_modified = Iterate<false>(
vaddr, size, [](RegionManager* manager, uint64_t offset, uint64_t bytes) {
return manager->IsModified<DirtySource::Gpu>(offset, bytes);
});
if (gpu_modified) {
return true;
}
Iterate<false>(vaddr, size,
[](RegionManager* manager, uint64_t offset, uint64_t bytes) {
const auto changed = manager->ChangeState<DirtySource::Cpu, true>(
manager->GetCpuAddr() + offset, bytes);
manager->ApplyProtection(changed, false);
});
return false;
};
if (!update_cpu_state()) {
return;
}
std::forward<Flush>(on_flush)();
if (update_cpu_state()) {
EXIT("memory invalidation retained GPU-owned pages\n");
}
}
[[nodiscard]] bool InvalidateVirtualGpuWrite(PageFaultAccess access, uint64_t vaddr, [[nodiscard]] bool InvalidateVirtualGpuWrite(PageFaultAccess access, uint64_t vaddr,
uint64_t size, PageFaultPhase phase) noexcept; uint64_t size, PageFaultPhase phase) noexcept;
void ValidateGpuDirtyPages(const RangeSet& dirty, uint64_t vaddr, uint64_t size, void ValidateGpuDirtyPages(const RangeSet& dirty, uint64_t vaddr, uint64_t size,
const char* operation) const noexcept; const char* operation) const noexcept;
void ValidateGpuDirtyOwnership(const RangeSet& dirty, uint64_t vaddr, uint64_t size, void ValidateGpuDirtyOwnership(const RangeSet& dirty, uint64_t vaddr, uint64_t size,
const char* operation); const char* operation);
+558 -148
View File
@@ -2,6 +2,7 @@
#include "graphics/host_gpu/regionDefinitions.h" #include "graphics/host_gpu/regionDefinitions.h"
#include <algorithm>
#include <array> #include <array>
#include <atomic> #include <atomic>
#include <cstdarg> #include <cstdarg>
@@ -19,6 +20,20 @@
#include <windows.h> #include <windows.h>
#undef min #undef min
#undef max #undef max
#elif defined(__APPLE__)
#include <mach/mach.h>
#include <mach/mach_vm.h>
#include <pthread.h>
#include <sys/mman.h>
#include <unistd.h>
#else
#include <cerrno>
#include <cstring>
#include <execinfo.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <unistd.h>
#endif #endif
namespace Libs::Graphics { namespace Libs::Graphics {
@@ -28,16 +43,58 @@ constexpr uint64_t PAGE_SIZE = TRACKER_PAGE_SIZE;
constexpr uint64_t REGION_SIZE = TRACKER_REGION_SIZE; constexpr uint64_t REGION_SIZE = TRACKER_REGION_SIZE;
constexpr uint64_t ADDRESS_SIZE = TRACKER_ADDRESS_SIZE; constexpr uint64_t ADDRESS_SIZE = TRACKER_ADDRESS_SIZE;
constexpr uint64_t REGION_COUNT = ADDRESS_SIZE / REGION_SIZE; constexpr uint64_t REGION_COUNT = ADDRESS_SIZE / REGION_SIZE;
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
// The tracker reuses Win32 memory-protection tags as internal page-state values (on
// Windows they come from <windows.h> and are what VirtualQuery returns). Mirror the
// canonical Win32 numeric values so the shared state-machine logic is identical.
constexpr uint32_t PAGE_NOACCESS = 0x01;
constexpr uint32_t PAGE_READONLY = 0x02;
constexpr uint32_t PAGE_READWRITE = 0x04;
#endif
constexpr uint64_t REGION_PAGES = REGION_SIZE / PAGE_SIZE; constexpr uint64_t REGION_PAGES = REGION_SIZE / PAGE_SIZE;
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS constexpr uint32_t NO_ACCESS_PROTECTION = PAGE_NOACCESS;
constexpr uint32_t NO_ACCESS_PROTECTION = PAGE_NOACCESS; constexpr uint32_t READ_ONLY_PROTECTION = PAGE_READONLY;
constexpr uint32_t READ_ONLY_PROTECTION = PAGE_READONLY;
constexpr uint32_t READ_WRITE_PROTECTION = PAGE_READWRITE; constexpr uint32_t READ_WRITE_PROTECTION = PAGE_READWRITE;
#else
constexpr uint32_t NO_ACCESS_PROTECTION = 0; #if defined(__APPLE__)
constexpr uint32_t READ_ONLY_PROTECTION = 1; // Map the tracker's Win32-style protection tags to POSIX mprotect flags.
constexpr uint32_t READ_WRITE_PROTECTION = 2; static int PageProtToPosix(uint32_t protection) {
switch (protection) {
case PAGE_NOACCESS: return PROT_NONE;
case PAGE_READONLY: return PROT_READ;
case PAGE_READWRITE: return PROT_READ | PROT_WRITE;
default: return PROT_NONE;
}
}
// Query the current protection of the page containing vaddr via the Mach VM map and
// collapse it to the tracker's read/write tags (execute is irrelevant to write tracking).
static uint32_t MachQueryPageProt(uint64_t vaddr) {
auto region_addr = static_cast<mach_vm_address_t>(vaddr);
mach_vm_size_t region_size = 0;
vm_region_basic_info_data_64_t info {};
mach_msg_type_number_t count = VM_REGION_BASIC_INFO_COUNT_64;
mach_port_t object_name = MACH_PORT_NULL;
kern_return_t kr =
mach_vm_region(mach_task_self(), &region_addr, &region_size, VM_REGION_BASIC_INFO_64,
reinterpret_cast<vm_region_info_t>(&info), &count, &object_name);
if (kr != KERN_SUCCESS || region_addr > vaddr) {
return PAGE_NOACCESS; // no region covering vaddr
}
if ((info.protection & VM_PROT_WRITE) != 0) {
return PAGE_READWRITE;
}
if ((info.protection & VM_PROT_READ) != 0) {
return PAGE_READONLY;
}
return PAGE_NOACCESS;
}
#elif defined(__linux__)
// Zero is the unknown protection sentinel.
constexpr uint32_t UNKNOWN_PROTECTION = 0;
#endif #endif
thread_local bool g_in_fault_resolution = false; thread_local bool g_in_fault_resolution = false;
@@ -56,6 +113,10 @@ thread_local bool g_in_fault_resolution = false;
std::fprintf(stderr, " frame[%u]=0x%016" PRIxPTR " image_rva=0x%016" PRIxPTR "\n", i, std::fprintf(stderr, " frame[%u]=0x%016" PRIxPTR " image_rva=0x%016" PRIxPTR "\n", i,
address, address >= image_base ? address - image_base : 0); address, address >= image_base ? address - image_base : 0);
} }
#elif !defined(__APPLE__)
void* frames[16] {};
const int frame_count = ::backtrace(frames, static_cast<int>(std::size(frames)));
::backtrace_symbols_fd(frames, frame_count, STDERR_FILENO);
#endif #endif
std::fflush(stderr); std::fflush(stderr);
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
@@ -78,11 +139,146 @@ thread_local bool g_in_fault_resolution = false;
uint32_t CurrentThread() noexcept { uint32_t CurrentThread() noexcept {
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
return GetCurrentThreadId(); return GetCurrentThreadId();
#elif defined(__APPLE__)
return static_cast<uint32_t>(pthread_mach_thread_np(pthread_self()));
#elif defined(__linux__)
static thread_local const uint32_t tid = [] {
const auto raw = static_cast<uint32_t>(::syscall(SYS_gettid));
if (raw == 0) {
FailFast("gettid returned the reserved zero owner token");
}
return raw;
}();
return tid;
#else #else
FailFast(); FailFast("page tracking thread identity is unsupported on this platform");
#endif #endif
} }
#if defined(__linux__)
int ToHostProtection(uint32_t protection) {
switch (protection) {
case NO_ACCESS_PROTECTION: return PROT_NONE;
case READ_ONLY_PROTECTION: return PROT_READ;
case READ_WRITE_PROTECTION: return PROT_READ | PROT_WRITE;
default: Fatal("unmappable protection 0x%08" PRIx32, protection);
}
}
struct HostMapping {
uint64_t end = 0;
uint32_t protection = UNKNOWN_PROTECTION;
};
// Async-signal-safe lookup in the address-ordered /proc/self/maps.
HostMapping QueryHostMapping(uint64_t vaddr) noexcept {
int fd = ::open("/proc/self/maps", O_RDONLY | O_CLOEXEC); // NOLINT
if (fd < 0) {
return {};
}
enum class Field { Start, End, Perms, Rest };
HostMapping result {};
auto field = Field::Start;
uint64_t start = 0;
uint64_t end = 0;
char perms[4] = {};
uint32_t perms_len = 0;
bool line_valid = true;
char buffer[8192];
for (bool done = false; !done;) {
const auto got = ::read(fd, buffer, sizeof(buffer));
if (got < 0) {
if (errno == EINTR) {
continue;
}
break;
}
if (got == 0) {
break;
}
for (ssize_t i = 0; i < got && !done; i++) {
const char c = buffer[i];
if (c == '\n') {
field = Field::Start;
start = 0;
end = 0;
perms_len = 0;
line_valid = true;
continue;
}
if (!line_valid) {
continue;
}
switch (field) {
case Field::Start:
case Field::End: {
uint64_t digit = 0;
if (c >= '0' && c <= '9') {
digit = static_cast<uint64_t>(c - '0');
} else if (c >= 'a' && c <= 'f') {
digit = static_cast<uint64_t>(c - 'a') + 10;
} else if (c == '-' && field == Field::Start) {
field = Field::End;
break;
} else if (c == ' ' && field == Field::End) {
field = Field::Perms;
perms_len = 0;
break;
} else {
line_valid = false;
break;
}
auto& value = (field == Field::Start ? start : end);
value = (value << 4u) | digit;
break;
}
case Field::Perms: {
if (c != ' ') {
if (perms_len < sizeof(perms)) {
perms[perms_len] = c;
}
perms_len++;
break;
}
if (vaddr < start) {
done = true;
} else if (vaddr < end && perms_len >= 2) {
result.end = end;
result.protection = perms[1] == 'w' ? READ_WRITE_PROTECTION
: perms[0] == 'r' ? READ_ONLY_PROTECTION
: NO_ACCESS_PROTECTION;
done = true;
} else {
field = Field::Rest;
}
break;
}
case Field::Rest: break;
}
}
}
::close(fd);
return result;
}
uint32_t QueryHostProtection(uint64_t vaddr) noexcept {
return QueryHostMapping(vaddr).protection;
}
#endif
class SpinGuard final { class SpinGuard final {
public: public:
explicit SpinGuard(std::atomic_flag& lock): m_lock(lock) { explicit SpinGuard(std::atomic_flag& lock): m_lock(lock) {
@@ -116,24 +312,48 @@ uint64_t PageEnd(uint64_t vaddr, uint64_t size) {
struct PageManager::Impl { struct PageManager::Impl {
struct PageState { struct PageState {
std::atomic_flag lock = ATOMIC_FLAG_INIT; std::atomic_flag lock = ATOMIC_FLAG_INIT;
uint32_t mappings = 0; uint32_t mappings = 0;
uint32_t gpu_read_mappings = 0; uint32_t gpu_read_mappings = 0;
uint32_t gpu_write_mappings = 0; uint32_t gpu_write_mappings = 0;
uint32_t write_watchers = 0; uint32_t write_watchers = 0;
uint32_t access_watchers = 0; uint32_t access_watchers = 0;
uint32_t original_protection = 0; uint32_t original_protection = 0;
uint32_t backing_writer = 0; uint32_t backing_writer = 0;
bool resolving = false; #if defined(__linux__)
bool resolving_read_write = false; // Shadow the protection applied through Protect().
bool late_read_pending = false; uint32_t current_protection = UNKNOWN_PROTECTION;
bool late_write_pending = false; #endif
bool resolving = false;
bool resolving_read_write = false;
bool late_read_pending = false;
bool late_write_pending = false;
}; };
struct Region { struct Region {
std::array<PageState, REGION_PAGES> pages; std::array<PageState, REGION_PAGES> pages;
}; };
class PageRangeGuard final {
public:
explicit PageRangeGuard(std::span<PageState*> pages): m_pages(pages) {
for (auto* page: m_pages) {
while (page->lock.test_and_set(std::memory_order_acquire)) {
std::atomic_signal_fence(std::memory_order_seq_cst);
}
}
}
~PageRangeGuard() {
for (auto it = m_pages.rbegin(); it != m_pages.rend(); ++it) {
(*it)->lock.clear(std::memory_order_release);
}
}
KYTY_CLASS_NO_COPY(PageRangeGuard);
private:
std::span<PageState*> m_pages;
};
Impl(PageFaultHandler handler, void* context): fault_handler(handler), fault_context(context) { Impl(PageFaultHandler handler, void* context): fault_handler(handler), fault_context(context) {
if (fault_handler == nullptr) { if (fault_handler == nullptr) {
Fatal("null fault handler"); Fatal("null fault handler");
@@ -145,8 +365,16 @@ struct PageManager::Impl {
Fatal("unsupported host page size 0x%08" PRIx32, Fatal("unsupported host page size 0x%08" PRIx32,
static_cast<uint32_t>(info.dwPageSize)); static_cast<uint32_t>(info.dwPageSize));
} }
#elif defined(__APPLE__)
// Under Rosetta the host page size is 4 KB, matching TRACKER_PAGE_SIZE.
if (static_cast<uint64_t>(getpagesize()) != PAGE_SIZE) {
Fatal("unsupported host page size 0x%08" PRIx32, static_cast<uint32_t>(getpagesize()));
}
#else #else
Fatal("page-fault invalidation is not implemented on this platform"); const auto host_page_size = ::sysconf(_SC_PAGESIZE);
if (host_page_size < 0 || static_cast<uint64_t>(host_page_size) != PAGE_SIZE) {
Fatal("unsupported host page size %ld", static_cast<long>(host_page_size));
}
#endif #endif
regions = std::make_unique<std::atomic<Region*>[]>(REGION_COUNT); regions = std::make_unique<std::atomic<Region*>[]>(REGION_COUNT);
for (uint64_t i = 0; i < REGION_COUNT; i++) { for (uint64_t i = 0; i < REGION_COUNT; i++) {
@@ -207,31 +435,61 @@ struct PageManager::Impl {
if (old_protection == NO_ACCESS_PROTECTION && new_protection != NO_ACCESS_PROTECTION) { if (old_protection == NO_ACCESS_PROTECTION && new_protection != NO_ACCESS_PROTECTION) {
page.late_read_pending = true; page.late_read_pending = true;
} }
if ((old_protection == NO_ACCESS_PROTECTION || if ((old_protection == NO_ACCESS_PROTECTION || old_protection == READ_ONLY_PROTECTION) &&
old_protection == READ_ONLY_PROTECTION) &&
new_protection == READ_WRITE_PROTECTION) { new_protection == READ_WRITE_PROTECTION) {
page.late_write_pending = true; page.late_write_pending = true;
} }
} }
static uint32_t QueryProtection(uint64_t vaddr) { static void ValidateInitialProtection(std::span<PageState*> pages, uint64_t vaddr) {
const auto end = vaddr + pages.size() * PAGE_SIZE;
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
MEMORY_BASIC_INFORMATION info {}; for (auto address = vaddr; address < end;) {
if (VirtualQuery(reinterpret_cast<const void*>(static_cast<uintptr_t>(vaddr)), &info, MEMORY_BASIC_INFORMATION info {};
sizeof(info)) == 0 || if (VirtualQuery(reinterpret_cast<const void*>(static_cast<uintptr_t>(address)), &info,
info.State != MEM_COMMIT || info.Protect != PAGE_READWRITE) { sizeof(info)) == 0 ||
Fatal("basic path requires PAGE_READWRITE at 0x%016" PRIx64 " (state=0x%08" PRIx32 info.State != MEM_COMMIT || info.Protect != PAGE_READWRITE) {
", protection=0x%08" PRIx32 ")", Fatal("basic path requires PAGE_READWRITE at 0x%016" PRIx64 " (state=0x%08" PRIx32
vaddr, static_cast<uint32_t>(info.State), static_cast<uint32_t>(info.Protect)); ", protection=0x%08" PRIx32 ")",
address, static_cast<uint32_t>(info.State),
static_cast<uint32_t>(info.Protect));
}
const auto region_end = reinterpret_cast<uint64_t>(info.BaseAddress) + info.RegionSize;
if (region_end <= address) {
Fatal("VirtualQuery returned an invalid region at 0x%016" PRIx64, address);
}
address = std::min(end, region_end);
}
#elif defined(__APPLE__)
for (auto address = vaddr; address < end; address += PAGE_SIZE) {
const uint32_t protection = MachQueryPageProt(address);
if (protection != PAGE_READWRITE) {
Fatal("basic path requires PAGE_READWRITE at 0x%016" PRIx64
" (protection=0x%08" PRIx32 ")",
address, protection);
}
} }
return info.Protect;
#else #else
(void)vaddr; for (auto address = vaddr; address < end;) {
Fatal("page query is unsupported on this platform"); const auto mapping = QueryHostMapping(address);
if (mapping.protection != READ_WRITE_PROTECTION || mapping.end <= address) {
Fatal("basic path requires a read/write mapping at 0x%016" PRIx64
" (protection=0x%08" PRIx32 ")",
address, mapping.protection);
}
address = std::min(end, mapping.end);
}
for (auto* page: pages) {
page->current_protection = READ_WRITE_PROTECTION;
}
#endif #endif
for (auto* page: pages) {
page->original_protection = READ_WRITE_PROTECTION;
}
} }
static bool AllowsAccess(uint64_t vaddr, PageFaultAccess access) noexcept { static bool AllowsAccess([[maybe_unused]] const PageState& page, uint64_t vaddr,
PageFaultAccess access) noexcept {
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
MEMORY_BASIC_INFORMATION info {}; MEMORY_BASIC_INFORMATION info {};
if (VirtualQuery(reinterpret_cast<const void*>(static_cast<uintptr_t>(vaddr)), &info, if (VirtualQuery(reinterpret_cast<const void*>(static_cast<uintptr_t>(vaddr)), &info,
@@ -245,34 +503,149 @@ struct PageManager::Impl {
case PageFaultAccess::Write: return info.Protect == PAGE_READWRITE; case PageFaultAccess::Write: return info.Protect == PAGE_READWRITE;
default: return false; default: return false;
} }
#elif defined(__APPLE__)
const uint32_t protection = MachQueryPageProt(vaddr);
switch (access) {
case PageFaultAccess::Read:
return protection == PAGE_READONLY || protection == PAGE_READWRITE;
case PageFaultAccess::Write: return protection == PAGE_READWRITE;
default: return false;
}
#else #else
(void)vaddr; const auto permitted = [](uint32_t protection, PageFaultAccess wanted) {
return false; switch (wanted) {
case PageFaultAccess::Read:
return protection == READ_ONLY_PROTECTION ||
protection == READ_WRITE_PROTECTION;
case PageFaultAccess::Write: return protection == READ_WRITE_PROTECTION;
default: return false;
}
};
if (!permitted(page.current_protection, access)) {
return false;
}
return permitted(QueryHostProtection(vaddr), access);
#endif #endif
} }
static void Protect(uint64_t vaddr, uint32_t protection, uint32_t expected_old, static void ProtectRange(std::span<PageState*> pages, uint64_t vaddr, uint32_t protection,
bool fault_path) noexcept { std::span<const uint32_t> expected_old, bool fault_path) noexcept {
const auto size = pages.size() * PAGE_SIZE;
if (pages.size() != expected_old.size()) {
FailFast("protection range state size mismatch");
}
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
DWORD old_protection = 0; struct HostRange {
if (VirtualProtect(reinterpret_cast<void*>(static_cast<uintptr_t>(vaddr)), PAGE_SIZE, uint64_t begin = 0;
protection, &old_protection) == 0 || uint64_t end = 0;
old_protection != expected_old) { };
if (fault_path) { std::vector<HostRange> host_ranges;
FailFast("VirtualProtect fault transition did not match expected protection"); const auto end = vaddr + size;
for (auto address = vaddr; address < end;) {
MEMORY_BASIC_INFORMATION info {};
if (VirtualQuery(reinterpret_cast<const void*>(static_cast<uintptr_t>(address)), &info,
sizeof(info)) == 0 ||
info.State != MEM_COMMIT) {
if (fault_path) {
FailFast("VirtualProtect fault transition did not match expected protection");
}
Fatal("invalid protection transition at 0x%016" PRIx64 ", state=0x%08" PRIx32
", new=0x%08" PRIx32,
address, static_cast<uint32_t>(info.State), protection);
} }
Fatal("invalid protection transition at 0x%016" PRIx64 ", old=0x%08" PRIx32 const auto region_end = reinterpret_cast<uint64_t>(info.BaseAddress) + info.RegionSize;
", expected=0x%08" PRIx32 ", new=0x%08" PRIx32, const auto query_end = std::min(end, region_end);
vaddr, static_cast<uint32_t>(old_protection), expected_old, protection); if (query_end <= address) {
if (fault_path) {
FailFast("VirtualQuery returned an invalid fault transition region");
}
Fatal("VirtualQuery returned an invalid region at 0x%016" PRIx64, address);
}
const auto first_page = static_cast<size_t>((address - vaddr) / PAGE_SIZE);
const auto last_page =
static_cast<size_t>((query_end - vaddr + PAGE_SIZE - 1) / PAGE_SIZE);
for (auto page = first_page; page < last_page; page++) {
if (info.Protect != expected_old[page]) {
if (fault_path) {
FailFast(
"VirtualProtect fault transition did not match expected protection");
}
Fatal("invalid protection transition at 0x%016" PRIx64 ", actual=0x%08" PRIx32
", expected=0x%08" PRIx32 ", new=0x%08" PRIx32,
vaddr + page * PAGE_SIZE, static_cast<uint32_t>(info.Protect),
expected_old[page], protection);
}
}
const auto allocation = reinterpret_cast<uint64_t>(info.AllocationBase);
if (host_ranges.empty() || allocation != host_ranges.back().begin) {
host_ranges.push_back({allocation, query_end});
} else {
host_ranges.back().end = query_end;
}
address = query_end;
}
for (auto range: host_ranges) {
range.begin = std::max(range.begin, vaddr);
DWORD old_protection = 0;
const auto first_page = static_cast<size_t>((range.begin - vaddr) / PAGE_SIZE);
if (VirtualProtect(reinterpret_cast<void*>(static_cast<uintptr_t>(range.begin)),
range.end - range.begin, protection, &old_protection) == 0 ||
old_protection != expected_old[first_page]) {
if (fault_path) {
FailFast("VirtualProtect fault transition did not match expected protection");
}
Fatal("invalid protection transition at 0x%016" PRIx64 ", old=0x%08" PRIx32
", expected=0x%08" PRIx32 ", new=0x%08" PRIx32,
range.begin, static_cast<uint32_t>(old_protection), expected_old[first_page],
protection);
}
}
#elif defined(__APPLE__)
// mprotect cannot report the previous protection, so the expected_old comparison
// is dropped; the tracker is the sole mutator of these pages and drives the
// transition from its own shadow state.
(void)expected_old;
if (mprotect(reinterpret_cast<void*>(static_cast<uintptr_t>(vaddr)), size,
PageProtToPosix(protection)) != 0) {
if (fault_path) {
FailFast("mprotect fault transition failed");
}
Fatal("mprotect failed at 0x%016" PRIx64 ", new=0x%08" PRIx32, vaddr, protection);
} }
#else #else
(void)vaddr; for (size_t i = 0; i < pages.size(); i++) {
(void)protection; const auto actual = pages[i]->current_protection;
(void)fault_path; if (actual != UNKNOWN_PROTECTION && actual != expected_old[i]) {
FailFast("page protection is unsupported on this platform"); if (fault_path) {
FailFast("mprotect fault transition did not match expected protection");
}
Fatal("invalid protection transition at 0x%016" PRIx64 ", old=0x%08" PRIx32
", expected=0x%08" PRIx32 ", new=0x%08" PRIx32,
vaddr + i * PAGE_SIZE, actual, expected_old[i], protection);
}
}
if (::mprotect(reinterpret_cast<void*>(static_cast<uintptr_t>(vaddr)), size,
ToHostProtection(protection)) != 0) {
if (fault_path) {
FailFast("mprotect failed on the fault path");
}
Fatal("mprotect failed at 0x%016" PRIx64 ", new=0x%08" PRIx32 " (%s)", vaddr,
protection, std::strerror(errno));
}
for (auto* page: pages) {
page->current_protection = protection;
}
#endif #endif
} }
static void Protect(PageState& page, uint64_t vaddr, uint32_t protection, uint32_t expected_old,
bool fault_path) noexcept {
PageState* pages[] = {&page};
uint32_t expected[] = {expected_old};
ProtectRange(pages, vaddr, protection, expected, fault_path);
}
std::unique_ptr<std::atomic<Region*>[]> regions; std::unique_ptr<std::atomic<Region*>[]> regions;
std::vector<std::unique_ptr<Region>> region_storage; std::vector<std::unique_ptr<Region>> region_storage;
std::mutex region_mutex; std::mutex region_mutex;
@@ -326,26 +699,6 @@ bool PageManager::IsMapped(uint64_t vaddr, uint64_t size) const noexcept {
return true; return true;
} }
bool PageManager::HasAnyMapping(uint64_t vaddr, uint64_t size) const noexcept {
if (g_in_fault_resolution || vaddr == 0 || size == 0 || vaddr >= ADDRESS_SIZE ||
size > ADDRESS_SIZE - vaddr) {
return false;
}
const auto end = PageEnd(vaddr, size);
for (auto page_vaddr = PageStart(vaddr); page_vaddr < end; page_vaddr += PAGE_SIZE) {
auto* region = m_impl->FindRegion(page_vaddr);
if (region == nullptr) {
continue;
}
auto& page = m_impl->GetPage(*region, page_vaddr);
SpinGuard lock(page.lock);
if (page.mappings != 0) {
return true;
}
}
return false;
}
bool PageManager::HasGpuAccess(uint64_t vaddr, uint64_t size, GpuAccess access) const noexcept { bool PageManager::HasGpuAccess(uint64_t vaddr, uint64_t size, GpuAccess access) const noexcept {
if (access != GpuAccess::Read && access != GpuAccess::Write && access != GpuAccess::ReadWrite) { if (access != GpuAccess::Read && access != GpuAccess::Write && access != GpuAccess::ReadWrite) {
FailFast("HasGpuAccess received an invalid GPU access mode"); FailFast("HasGpuAccess received an invalid GPU access mode");
@@ -376,65 +729,134 @@ void PageManager::UpdatePageWatchers(bool track, uint64_t vaddr, uint64_t size,
if (mode != PageWatchMode::Write && mode != PageWatchMode::ReadWrite) { if (mode != PageWatchMode::Write && mode != PageWatchMode::ReadWrite) {
Fatal("invalid watcher mode"); Fatal("invalid watcher mode");
} }
const auto end = PageEnd(vaddr, size); const auto begin = PageStart(vaddr);
for (auto page_vaddr = PageStart(vaddr); page_vaddr < end; page_vaddr += PAGE_SIZE) { const auto end = PageEnd(vaddr, size);
auto* region = for (auto chunk_begin = begin; chunk_begin < end;) {
track ? m_impl->GetOrCreateRegion(page_vaddr) : m_impl->FindRegion(page_vaddr); const auto chunk_end = std::min(end, (chunk_begin / REGION_SIZE + 1) * REGION_SIZE);
auto* region =
track ? m_impl->GetOrCreateRegion(chunk_begin) : m_impl->FindRegion(chunk_begin);
if (region == nullptr) { if (region == nullptr) {
Fatal("untracking unknown page 0x%016" PRIx64, page_vaddr); Fatal("untracking unknown page 0x%016" PRIx64, chunk_begin);
} }
auto& page = m_impl->GetPage(*region, page_vaddr);
SpinGuard lock(page.lock); const auto page_count = static_cast<size_t>((chunk_end - chunk_begin) / PAGE_SIZE);
if (page.resolving && track) { std::vector<Impl::PageState*> pages;
FailFast("new page watcher raced active fault resolution"); pages.reserve(page_count);
for (auto address = chunk_begin; address < chunk_end; address += PAGE_SIZE) {
pages.push_back(&m_impl->GetPage(*region, address));
} }
if (page.mappings == 0) { Impl::PageRangeGuard lock(pages);
Fatal("watching unmapped page 0x%016" PRIx64, page_vaddr);
std::vector<uint8_t> first_watchers(page_count);
for (size_t i = 0; i < page_count; i++) {
auto& page = *pages[i];
const auto address = chunk_begin + i * PAGE_SIZE;
if (page.resolving && track) {
FailFast("new page watcher raced active fault resolution");
}
if (page.mappings == 0) {
Fatal("watching unmapped page 0x%016" PRIx64, address);
}
auto& watchers =
(mode == PageWatchMode::ReadWrite ? page.access_watchers : page.write_watchers);
if (track) {
if (watchers == std::numeric_limits<uint32_t>::max()) {
Fatal("watcher overflow at 0x%016" PRIx64, address);
}
first_watchers[i] = page.write_watchers == 0 && page.access_watchers == 0;
} else {
if (watchers == 0) {
Fatal("watcher underflow at 0x%016" PRIx64, address);
}
if (page.backing_writer != 0 && page.backing_writer != CurrentThread()) {
Fatal("backing write ownership changed at 0x%016" PRIx64, address);
}
}
} }
auto& watchers =
(mode == PageWatchMode::ReadWrite ? page.access_watchers : page.write_watchers);
if (track) { if (track) {
if (watchers == std::numeric_limits<uint32_t>::max()) { for (size_t first = 0; first < page_count;) {
Fatal("watcher overflow at 0x%016" PRIx64, page_vaddr); while (first < page_count && first_watchers[first] == 0) {
} first++;
const bool first_watcher = page.write_watchers == 0 && page.access_watchers == 0; }
if (first_watcher) { auto last = first;
page.original_protection = Impl::QueryProtection(page_vaddr); while (last < page_count && first_watchers[last] != 0) {
} last++;
const auto old_protection = Impl::WatcherProtection(page); }
watchers++; if (first != last) {
const auto new_protection = Impl::WatcherProtection(page); Impl::ValidateInitialProtection(std::span {pages}.subspan(first, last - first),
if (new_protection != old_protection) { chunk_begin + first * PAGE_SIZE);
Impl::Protect(page_vaddr, new_protection, old_protection, false); }
} first = last;
switch (new_protection) {
case NO_ACCESS_PROTECTION:
page.late_read_pending = false;
page.late_write_pending = false;
break;
case READ_ONLY_PROTECTION: page.late_write_pending = false; break;
default: break;
}
} else {
if (watchers == 0) {
Fatal("watcher underflow at 0x%016" PRIx64, page_vaddr);
}
if (page.backing_writer != 0 && page.backing_writer != CurrentThread()) {
Fatal("backing write ownership changed at 0x%016" PRIx64, page_vaddr);
}
const auto old_protection = Impl::WatcherProtection(page);
watchers--;
const auto new_protection = Impl::WatcherProtection(page);
if (page.backing_writer == 0 && new_protection != old_protection) {
Impl::Protect(page_vaddr, new_protection, old_protection, false);
}
if (page.backing_writer == 0) {
Impl::PublishDelayedFaults(page, old_protection, new_protection);
}
if (page.backing_writer == 0 && page.write_watchers == 0 && page.access_watchers == 0) {
page.original_protection = 0;
} }
} }
std::vector<uint32_t> old_protections(page_count);
std::vector<uint32_t> new_protections(page_count);
std::vector<uint8_t> transitions(page_count);
for (size_t i = 0; i < page_count; i++) {
auto& page = *pages[i];
auto& watchers =
(mode == PageWatchMode::ReadWrite ? page.access_watchers : page.write_watchers);
const auto old_protection = Impl::WatcherProtection(page);
if (track) {
watchers++;
} else {
watchers--;
}
const auto new_protection = Impl::WatcherProtection(page);
old_protections[i] = old_protection;
new_protections[i] = new_protection;
if (new_protection != old_protection && (track || page.backing_writer == 0)) {
transitions[i] = 1;
}
}
for (size_t first = 0; first < page_count;) {
while (first < page_count && transitions[first] == 0) {
first++;
}
if (first == page_count) {
break;
}
const auto protection = new_protections[first];
auto current = first + 1;
auto last = current;
for (; current < page_count && new_protections[current] == protection; current++) {
if (old_protections[current] != new_protections[current] &&
transitions[current] == 0) {
break;
}
if (transitions[current] != 0) {
last = current + 1;
}
}
Impl::ProtectRange(std::span {pages}.subspan(first, last - first),
chunk_begin + first * PAGE_SIZE, protection,
std::span {old_protections}.subspan(first, last - first), false);
first = current;
}
for (size_t i = 0; i < page_count; i++) {
auto& page = *pages[i];
const auto protection = new_protections[i];
if (track) {
switch (protection) {
case NO_ACCESS_PROTECTION:
page.late_read_pending = false;
page.late_write_pending = false;
break;
case READ_ONLY_PROTECTION: page.late_write_pending = false; break;
default: break;
}
} else if (page.backing_writer == 0) {
Impl::PublishDelayedFaults(page, old_protections[i], protection);
if (page.write_watchers == 0 && page.access_watchers == 0) {
page.original_protection = 0;
}
}
}
chunk_begin = chunk_end;
} }
} }
@@ -459,6 +881,12 @@ void PageManager::OnGpuMap(uint64_t vaddr, uint64_t size, GpuAccess access) {
page.mappings++; page.mappings++;
page.gpu_read_mappings += gpu_read ? 1u : 0u; page.gpu_read_mappings += gpu_read ? 1u : 0u;
page.gpu_write_mappings += gpu_write ? 1u : 0u; page.gpu_write_mappings += gpu_write ? 1u : 0u;
#if defined(__linux__)
// New guest mappings start read/write.
if (page.current_protection == UNKNOWN_PROTECTION) {
page.current_protection = READ_WRITE_PROTECTION;
}
#endif
} }
} }
@@ -580,7 +1008,7 @@ void PageManager::EndBackingWrite(uint64_t vaddr, uint64_t size) noexcept {
const auto old_protection = NO_ACCESS_PROTECTION; const auto old_protection = NO_ACCESS_PROTECTION;
const auto new_protection = Impl::WatcherProtection(page); const auto new_protection = Impl::WatcherProtection(page);
if (new_protection != old_protection) { if (new_protection != old_protection) {
Impl::Protect(address, new_protection, old_protection, false); Impl::Protect(page, address, new_protection, old_protection, false);
} }
Impl::PublishDelayedFaults(page, old_protection, new_protection); Impl::PublishDelayedFaults(page, old_protection, new_protection);
if (page.write_watchers == 0 && page.access_watchers == 0) { if (page.write_watchers == 0 && page.access_watchers == 0) {
@@ -605,12 +1033,12 @@ bool PageManager::HandleFault(PageFaultAccess access, uint64_t fault_vaddr) noex
while (true) { while (true) {
SpinGuard lock(page.lock); SpinGuard lock(page.lock);
if (access == PageFaultAccess::Read && page.late_read_pending && if (access == PageFaultAccess::Read && page.late_read_pending &&
Impl::AllowsAccess(fault_vaddr, access)) { Impl::AllowsAccess(page, fault_vaddr, access)) {
page.late_read_pending = false; page.late_read_pending = false;
return true; return true;
} }
if (access == PageFaultAccess::Write && page.late_write_pending && if (access == PageFaultAccess::Write && page.late_write_pending &&
Impl::AllowsAccess(fault_vaddr, access)) { Impl::AllowsAccess(page, fault_vaddr, access)) {
page.late_write_pending = false; page.late_write_pending = false;
return true; return true;
} }
@@ -632,7 +1060,7 @@ bool PageManager::HandleFault(PageFaultAccess access, uint64_t fault_vaddr) noex
} }
bool& pending = (access == PageFaultAccess::Read ? page.late_read_pending bool& pending = (access == PageFaultAccess::Read ? page.late_read_pending
: page.late_write_pending); : page.late_write_pending);
const bool allowed = Impl::AllowsAccess(fault_vaddr, access); const bool allowed = Impl::AllowsAccess(page, fault_vaddr, access);
pending = false; pending = false;
if (waited && !allowed) { if (waited && !allowed) {
FailFast("page remained inaccessible after waiting for its resolver"); FailFast("page remained inaccessible after waiting for its resolver");
@@ -681,12 +1109,12 @@ bool PageManager::HandleFault(PageFaultAccess access, uint64_t fault_vaddr) noex
page.write_watchers = 0; page.write_watchers = 0;
} }
const auto restored_protection = Impl::WatcherProtection(page); const auto restored_protection = Impl::WatcherProtection(page);
Impl::Protect(PageStart(fault_vaddr), restored_protection, old_protection, true); Impl::Protect(page, PageStart(fault_vaddr), restored_protection, old_protection, true);
if (page.write_watchers == 0) { if (page.write_watchers == 0) {
page.original_protection = 0; page.original_protection = 0;
} }
Impl::PublishDelayedFaults(page, old_protection, restored_protection); Impl::PublishDelayedFaults(page, old_protection, restored_protection);
} else if (!Impl::AllowsAccess(fault_vaddr, access)) { } else if (!Impl::AllowsAccess(page, fault_vaddr, access)) {
FailFast("fault completion left the page inaccessible"); FailFast("fault completion left the page inaccessible");
} }
page.resolving = false; page.resolving = false;
@@ -702,22 +1130,4 @@ bool PageManager::HandleFault(PageFaultAccess access, uint64_t fault_vaddr) noex
return true; return true;
} }
bool PageManager::HandleWriteRange(uint64_t vaddr, uint64_t size) noexcept {
if (g_in_fault_resolution || vaddr == 0 || size == 0 || vaddr >= ADDRESS_SIZE ||
size > ADDRESS_SIZE - vaddr) {
return false;
}
const auto end = PageEnd(vaddr, size);
for (auto page_vaddr = PageStart(vaddr); page_vaddr < end; page_vaddr += PAGE_SIZE) {
if (!IsMapped(page_vaddr, 1)) {
continue;
}
const auto fault_vaddr = std::max(page_vaddr, vaddr);
if (!HandleFault(PageFaultAccess::Write, fault_vaddr)) {
return false;
}
}
return true;
}
} // namespace Libs::Graphics } // namespace Libs::Graphics
+1 -3
View File
@@ -41,7 +41,6 @@ public:
[[nodiscard]] uint64_t GetPageSize() const; [[nodiscard]] uint64_t GetPageSize() const;
[[nodiscard]] bool IsTracked(uint64_t vaddr) const noexcept; [[nodiscard]] bool IsTracked(uint64_t vaddr) const noexcept;
[[nodiscard]] bool IsMapped(uint64_t vaddr, uint64_t size) const noexcept; [[nodiscard]] bool IsMapped(uint64_t vaddr, uint64_t size) const noexcept;
[[nodiscard]] bool HasAnyMapping(uint64_t vaddr, uint64_t size) const noexcept;
[[nodiscard]] bool HasGpuAccess(uint64_t vaddr, uint64_t size, GpuAccess access) const noexcept; [[nodiscard]] bool HasGpuAccess(uint64_t vaddr, uint64_t size, GpuAccess access) const noexcept;
void UpdatePageWatchers(bool track, uint64_t vaddr, uint64_t size, void UpdatePageWatchers(bool track, uint64_t vaddr, uint64_t size,
@@ -50,9 +49,8 @@ public:
void OnGpuUnmap(uint64_t vaddr, uint64_t size, GpuAccess access = GpuAccess::ReadWrite); void OnGpuUnmap(uint64_t vaddr, uint64_t size, GpuAccess access = GpuAccess::ReadWrite);
[[nodiscard]] bool HandleFault(PageFaultAccess access, uint64_t fault_vaddr) noexcept; [[nodiscard]] bool HandleFault(PageFaultAccess access, uint64_t fault_vaddr) noexcept;
[[nodiscard]] bool HandleWriteRange(uint64_t vaddr, uint64_t size) noexcept;
[[nodiscard]] std::vector<std::unique_ptr<BackingWrite>> [[nodiscard]] std::vector<std::unique_ptr<BackingWrite>>
ReserveBackingWrites(std::span<const RangeSet::Range> ranges); ReserveBackingWrites(std::span<const RangeSet::Range> ranges);
private: private:
void BeginBackingWrite(uint64_t vaddr, uint64_t size) noexcept; void BeginBackingWrite(uint64_t vaddr, uint64_t size) noexcept;
+10
View File
@@ -59,6 +59,16 @@ public:
return result; return result;
} }
[[nodiscard]] bool Contains(uint64_t address, uint64_t size) const {
const auto end = End(address, size);
auto it = m_ranges.upper_bound(address);
if (it == m_ranges.begin()) {
return false;
}
--it;
return it->first <= address && it->second >= end;
}
template <typename Func> template <typename Func>
void ForEachIntersection(uint64_t address, uint64_t size, Func&& func) const { void ForEachIntersection(uint64_t address, uint64_t size, Func&& func) const {
const auto end = End(address, size); const auto end = End(address, size);
+11
View File
@@ -16,6 +16,11 @@
#include <windows.h> #include <windows.h>
#undef min #undef min
#undef max #undef max
#elif defined(__APPLE__)
#include <pthread.h>
#elif defined(__linux__)
#include <sys/syscall.h>
#include <unistd.h>
#endif #endif
namespace Libs::Graphics { namespace Libs::Graphics {
@@ -49,6 +54,12 @@ private:
static uint32_t CurrentThread() noexcept { static uint32_t CurrentThread() noexcept {
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
return GetCurrentThreadId(); return GetCurrentThreadId();
#elif defined(__APPLE__)
// mach thread port is a nonzero per-thread id (0 is the "no owner" sentinel).
return static_cast<uint32_t>(pthread_mach_thread_np(pthread_self()));
#elif defined(__linux__)
static thread_local const uint32_t tid = static_cast<uint32_t>(::syscall(SYS_gettid));
return tid;
#else #else
EXIT("region tracking thread identity is unsupported on this platform\n"); EXIT("region tracking thread identity is unsupported on this platform\n");
#endif #endif
@@ -1,13 +1,13 @@
#include "graphics/host_gpu/renderer/bufferCache.h" #include "graphics/host_gpu/renderer/cache/bufferCache.h"
#include "common/assert.h" #include "common/assert.h"
#include "common/logging/log.h" #include "common/logging/log.h"
#include "common/profiler.h" #include "common/profiler.h"
#include "graphics/host_gpu/graphicContext.h" #include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/renderer/cache/resourceMutex.h"
#include "graphics/host_gpu/renderer/cache/textureCache.h"
#include "graphics/host_gpu/renderer/commandScheduler.h" #include "graphics/host_gpu/renderer/commandScheduler.h"
#include "graphics/host_gpu/renderer/render.h" #include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/resourceMutex.h"
#include "graphics/host_gpu/renderer/textureCache.h"
#include "kernel/memory.h" #include "kernel/memory.h"
#include <algorithm> #include <algorithm>
@@ -183,9 +183,8 @@ BufferCache::RecordDownloads(std::span<const DownloadCopy> copies) {
return {}; return {};
} }
auto& download = m_download_buffer; auto& download = m_download_buffer;
const auto [mapped, base_offset] = const auto [mapped, base_offset] = download.Map(reservation_size, DOWNLOAD_ALIGNMENT);
download.Map(reservation_size, DOWNLOAD_ALIGNMENT);
if (mapped == nullptr) { if (mapped == nullptr) {
EXIT("BufferCache: download batch could not reserve the shared stream\n"); EXIT("BufferCache: download batch could not reserve the shared stream\n");
} }
@@ -215,40 +214,38 @@ void BufferCache::PublishDownloads(std::span<const DownloadRange> downloads) {
} }
} }
void BufferCache::QueueGarbageDownload(std::span<const DownloadCopy> copies, void BufferCache::QueueGarbageDownload(std::span<const DownloadCopy> copies, RetiredBuffer retire) {
RetiredBuffer retire) {
if (copies.empty()) { if (copies.empty()) {
return; return;
} }
auto downloads = RecordDownloads(copies); auto downloads = RecordDownloads(copies);
const auto tick = m_scheduler.CurrentTick(); const auto tick = m_scheduler.CurrentTick();
BeginBackingPublication(retire.address, retire.size, tick); BeginBackingPublication(retire.address, retire.size, tick);
m_scheduler.DeferOperation( m_scheduler.DeferOperation([this, downloads = std::move(downloads), retire = std::move(retire),
[this, downloads = std::move(downloads), retire = std::move(retire), tick]() mutable { tick]() mutable {
PublishDownloads(downloads); PublishDownloads(downloads);
{ {
FaultSafeCacheLock lock(this, m_mutex); FaultSafeCacheLock lock(this, m_mutex);
if (m_memory_tracker.IsRegionGpuModified(retire.address, retire.size)) { if (m_memory_tracker.IsRegionGpuModified(retire.address, retire.size)) {
m_memory_tracker.ForEachDownloadRange<true>( m_memory_tracker.ForEachDownloadRange<true>(
retire.address, retire.size, retire.address, retire.size,
[&](uint64_t address, uint64_t size) noexcept { [&](uint64_t address, uint64_t size) noexcept {
m_memory_tracker.ValidateGpuDirtyPages( m_memory_tracker.ValidateGpuDirtyPages(m_gpu_modified_ranges, address, size,
m_gpu_modified_ranges, address, size, "asynchronous garbage retirement");
"asynchronous garbage retirement"); },
}, [](uint64_t, uint64_t) noexcept {});
[](uint64_t, uint64_t) noexcept {}); }
} for (const auto& range: downloads) {
for (const auto& range: downloads) { m_gpu_modified_ranges.Subtract(range.address, range.size);
m_gpu_modified_ranges.Subtract(range.address, range.size); }
} if (m_memory_tracker.IsRegionGpuModified(retire.address, retire.size) ||
if (m_memory_tracker.IsRegionGpuModified(retire.address, retire.size) || !m_gpu_modified_ranges.Intersections(retire.address, retire.size).empty()) {
!m_gpu_modified_ranges.Intersections(retire.address, retire.size).empty()) { EXIT("BufferCache: asynchronous garbage collection retained GPU ownership\n");
EXIT("BufferCache: asynchronous garbage collection retained GPU ownership\n"); }
} m_memory_tracker.UntrackMemory(retire.address, retire.size);
m_memory_tracker.UntrackMemory(retire.address, retire.size); }
} CompleteBackingPublication(retire.address, retire.size, tick);
CompleteBackingPublication(retire.address, retire.size, tick); });
});
} }
BufferCache::BufferCache(GraphicContext& graphics, CommandScheduler& scheduler, BufferCache::BufferCache(GraphicContext& graphics, CommandScheduler& scheduler,
@@ -301,14 +298,13 @@ BufferCache::~BufferCache() {
bool BufferCache::SynchronizeBacking(uint64_t vaddr, uint64_t size) { bool BufferCache::SynchronizeBacking(uint64_t vaddr, uint64_t size) {
bool waited = false; bool waited = false;
for (;;) { for (;;) {
uint64_t tick = 0; uint64_t tick = 0;
const auto page_begin = vaddr & ~(TRACKER_PAGE_SIZE - 1); const auto page_begin = vaddr & ~(TRACKER_PAGE_SIZE - 1);
const auto page_end = const auto page_end = (vaddr + size + TRACKER_PAGE_SIZE - 1) & ~(TRACKER_PAGE_SIZE - 1);
(vaddr + size + TRACKER_PAGE_SIZE - 1) & ~(TRACKER_PAGE_SIZE - 1);
CacheRange affected {.address = page_begin, .size = page_end - page_begin}; CacheRange affected {.address = page_begin, .size = page_end - page_begin};
{ {
FaultSafeCacheLock lock(this, m_mutex); FaultSafeCacheLock lock(this, m_mutex);
bool changed = true; bool changed = true;
while (changed) { while (changed) {
changed = false; changed = false;
for (const auto& [address, cached]: m_buffers) { for (const auto& [address, cached]: m_buffers) {
@@ -384,6 +380,73 @@ BufferBinding BufferCache::UploadTransient(const void* data, uint64_t size, uint
return {owner, owner->Handle(), 0}; return {owner, owner->Handle(), 0};
} }
void BufferCache::InvalidateMemory(uint64_t vaddr, uint64_t size) {
if (vaddr == 0 || size == 0 || vaddr >= TRACKER_ADDRESS_SIZE ||
size > TRACKER_ADDRESS_SIZE - vaddr) {
EXIT("BufferCache: invalid memory-invalidation range\n");
}
(void)SynchronizeBacking(vaddr, size);
if (!HasPageOverlap(vaddr, size)) {
return;
}
m_memory_tracker.InvalidateRegion(vaddr, size,
[this, vaddr, size] { ReadMemory(vaddr, size); });
}
void BufferCache::ReadMemory(uint64_t vaddr, uint64_t size) {
(void)SynchronizeBacking(vaddr, size);
std::vector<DownloadCopy> copies;
{
FaultSafeCacheLock lock(this, m_mutex);
m_memory_tracker.ForEachDownloadRange<false>(
vaddr, size,
[&](uint64_t address, uint64_t bytes) noexcept {
m_memory_tracker.ValidateGpuDirtyPages(m_gpu_modified_ranges, address, bytes,
"memory invalidation");
},
[&](uint64_t address, uint64_t bytes) noexcept {
for (const auto range: m_gpu_modified_ranges.Intersections(address, bytes)) {
for (uint64_t copied = 0; copied < range.size;) {
const auto copy_address = range.address + copied;
auto owner = m_buffers.upper_bound(copy_address);
if (owner == m_buffers.begin()) {
EXIT("BufferCache: invalidation readback has no buffer owner\n");
}
auto& cached = *std::prev(owner)->second;
if (!cached.buffer->IsInBounds(copy_address, 1)) {
EXIT(
"BufferCache: invalidation readback is outside its buffer owner\n");
}
const auto copy_size = std::min(range.size - copied,
cached.vaddr + cached.size - copy_address);
copies.push_back({cached.buffer, cached.buffer->Offset(copy_address),
copy_address, copy_size});
copied += copy_size;
}
}
});
}
if (copies.empty()) {
return;
}
auto downloads = RecordDownloads(copies);
m_scheduler.FinishCurrent();
PublishDownloads(downloads);
{
FaultSafeCacheLock lock(this, m_mutex);
m_memory_tracker.ForEachDownloadRange<true>(
vaddr, size,
[&](uint64_t address, uint64_t bytes) noexcept {
m_memory_tracker.ValidateGpuDirtyPages(m_gpu_modified_ranges, address, bytes,
"memory invalidation completion");
},
[](uint64_t, uint64_t) noexcept {});
for (const auto& range: downloads) {
m_gpu_modified_ranges.Subtract(range.address, range.size);
}
}
}
bool BufferCache::InvalidateMemory(PageFaultAccess access, uint64_t vaddr, uint64_t size, bool BufferCache::InvalidateMemory(PageFaultAccess access, uint64_t vaddr, uint64_t size,
PageFaultPhase phase) noexcept { PageFaultPhase phase) noexcept {
const auto page = vaddr & ~(TRACKER_PAGE_SIZE - 1); const auto page = vaddr & ~(TRACKER_PAGE_SIZE - 1);
@@ -544,8 +607,8 @@ void BufferCache::UnmapMemory(uint64_t vaddr, uint64_t size) {
m_memory_tracker.ForEachDownloadRange<true>( m_memory_tracker.ForEachDownloadRange<true>(
begin, bytes, begin, bytes,
[&](uint64_t address, uint64_t download_size) noexcept { [&](uint64_t address, uint64_t download_size) noexcept {
m_memory_tracker.ValidateGpuDirtyPages( m_memory_tracker.ValidateGpuDirtyPages(m_gpu_modified_ranges, address,
m_gpu_modified_ranges, address, download_size, "unmap retirement"); download_size, "unmap retirement");
}, },
[](uint64_t, uint64_t) noexcept {}); [](uint64_t, uint64_t) noexcept {});
} }
@@ -722,12 +785,11 @@ ImageBufferSource BufferCache::ObtainBufferForImage(uint64_t vaddr, uint64_t siz
{ {
FaultSafeCacheLock lock(this, m_mutex); FaultSafeCacheLock lock(this, m_mutex);
const bool cpu_modified = m_memory_tracker.IsRegionCpuModified(vaddr, size); const bool cpu_modified = m_memory_tracker.IsRegionCpuModified(vaddr, size);
const bool gpu_modified = m_memory_tracker.IsRegionGpuModified(vaddr, size); const bool gpu_modified = m_memory_tracker.IsRegionGpuModified(vaddr, size);
const auto dirty = m_gpu_modified_ranges.Intersections(vaddr, size); const auto dirty = m_gpu_modified_ranges.Intersections(vaddr, size);
const bool invalidated = const bool invalidated = !m_image_invalidated_ranges.Intersections(vaddr, size).empty();
!m_image_invalidated_ranges.Intersections(vaddr, size).empty(); const bool requested_gpu_owned = !dirty.empty();
const bool requested_gpu_owned = !dirty.empty();
m_memory_tracker.ValidateGpuDirtyOwnership(m_gpu_modified_ranges, vaddr, size, m_memory_tracker.ValidateGpuDirtyOwnership(m_gpu_modified_ranges, vaddr, size,
"image source"); "image source");
@@ -807,8 +869,8 @@ ImageBufferSource BufferCache::ObtainBufferForImage(uint64_t vaddr, uint64_t siz
} }
FaultSafeCacheLock lock(this, m_mutex); FaultSafeCacheLock lock(this, m_mutex);
const auto dirty = m_gpu_modified_ranges.Intersections(vaddr, size); const auto dirty = m_gpu_modified_ranges.Intersections(vaddr, size);
const bool invalidated = !m_image_invalidated_ranges.Intersections(vaddr, size).empty(); const bool invalidated = !m_image_invalidated_ranges.Intersections(vaddr, size).empty();
const bool requested_gpu_owned = !dirty.empty(); const bool requested_gpu_owned = !dirty.empty();
auto owner = find_owner(); auto owner = find_owner();
if (requested_gpu_owned && owner == m_buffers.end()) { if (requested_gpu_owned && owner == m_buffers.end()) {
@@ -831,9 +893,8 @@ ImageBufferSource BufferCache::ObtainBufferForImage(uint64_t vaddr, uint64_t siz
[&]() noexcept { [&]() noexcept {
for (const auto& [address, upload_size]: uploads) { for (const auto& [address, upload_size]: uploads) {
cached.buffer->CopyFrom( cached.buffer->CopyFrom(
m_scheduler.Current(), m_staging_buffer, m_scheduler.Current(), m_staging_buffer, stage_offset + address - stage_address,
stage_offset + address - stage_address, cached.buffer->Offset(address), cached.buffer->Offset(address), upload_size, vk::AccessFlagBits::eHostWrite);
upload_size, vk::AccessFlagBits::eHostWrite);
} }
}); });
DiscardGpuDirtyBytesLocked(vaddr, size, "staged image source transfer"); DiscardGpuDirtyBytesLocked(vaddr, size, "staged image source transfer");
@@ -917,9 +978,8 @@ std::pair<std::shared_ptr<Buffer>, uint64_t> BufferCache::ObtainBufferForImageWr
[&]() noexcept { [&]() noexcept {
for (const auto& [address, upload_size]: uploads) { for (const auto& [address, upload_size]: uploads) {
cached.buffer->CopyFrom( cached.buffer->CopyFrom(
m_scheduler.Current(), m_staging_buffer, m_scheduler.Current(), m_staging_buffer, stage_offset + address - stage_address,
stage_offset + address - stage_address, cached.buffer->Offset(address), cached.buffer->Offset(address), upload_size, vk::AccessFlagBits::eHostWrite);
upload_size, vk::AccessFlagBits::eHostWrite);
} }
}); });
return {cached.buffer, cached.buffer->Offset(vaddr)}; return {cached.buffer, cached.buffer->Offset(vaddr)};
@@ -946,12 +1006,12 @@ void BufferCache::FillBuffer(uint64_t vaddr, uint64_t size, uint32_t value, bool
const auto region = m_texture_cache.QueryRegion(vaddr, size); const auto region = m_texture_cache.QueryRegion(vaddr, size);
if (!HasGpuDirtyBytes(vaddr, size) && !region.gpu_image_bytes) { if (!HasGpuDirtyBytes(vaddr, size) && !region.gpu_image_bytes) {
if (region.image_bytes) { if (region.image_bytes) {
m_texture_cache.PrepareHostWrite(vaddr, size); m_texture_cache.InvalidateMemory(vaddr, size);
} }
std::array<uint32_t, 4096> values; std::array<uint32_t, 4096> values;
values.fill(value); values.fill(value);
const std::span<const uint8_t> bytes { const std::span<const uint8_t> bytes {reinterpret_cast<const uint8_t*>(values.data()),
reinterpret_cast<const uint8_t*>(values.data()), sizeof(values)}; sizeof(values)};
for (uint64_t offset = 0; offset < size;) { for (uint64_t offset = 0; offset < size;) {
const auto chunk = std::min<uint64_t>(size - offset, bytes.size()); const auto chunk = std::min<uint64_t>(size - offset, bytes.size());
WriteHostMemory(vaddr + offset, bytes.first(chunk)); WriteHostMemory(vaddr + offset, bytes.first(chunk));
@@ -992,7 +1052,7 @@ void BufferCache::CopyBuffer(uint64_t dst_vaddr, uint64_t src_vaddr, uint64_t si
if (src_memory) { if (src_memory) {
(void)SynchronizeBacking(src_vaddr, size); (void)SynchronizeBacking(src_vaddr, size);
} }
const auto src_region = const auto src_region =
src_memory ? m_texture_cache.QueryRegion(src_vaddr, size) : TextureCache::RegionInfo {}; src_memory ? m_texture_cache.QueryRegion(src_vaddr, size) : TextureCache::RegionInfo {};
const auto dst_region = const auto dst_region =
dst_memory ? m_texture_cache.QueryRegion(dst_vaddr, size) : TextureCache::RegionInfo {}; dst_memory ? m_texture_cache.QueryRegion(dst_vaddr, size) : TextureCache::RegionInfo {};
@@ -1004,7 +1064,7 @@ void BufferCache::CopyBuffer(uint64_t dst_vaddr, uint64_t src_vaddr, uint64_t si
!HasGpuDirtyBytes(dst_vaddr, size) && !src_region.gpu_image_bytes && !HasGpuDirtyBytes(dst_vaddr, size) && !src_region.gpu_image_bytes &&
!dst_region.gpu_image_bytes) { !dst_region.gpu_image_bytes) {
if (dst_region.image_bytes) { if (dst_region.image_bytes) {
m_texture_cache.PrepareHostWrite(dst_vaddr, size); m_texture_cache.InvalidateMemory(dst_vaddr, size);
} }
std::array<uint8_t, 64 * 1024> bytes; std::array<uint8_t, 64 * 1024> bytes;
for (uint64_t offset = 0; offset < size;) { for (uint64_t offset = 0; offset < size;) {
@@ -1037,10 +1097,10 @@ void BufferCache::CopyBuffer(uint64_t dst_vaddr, uint64_t src_vaddr, uint64_t si
EXIT("BufferCache: resolved Vulkan copy ranges overlap\n"); EXIT("BufferCache: resolved Vulkan copy ranges overlap\n");
} }
auto& source = src.owner != nullptr ? *std::static_pointer_cast<Buffer>(src.owner) auto& source = src.owner != nullptr ? *std::static_pointer_cast<Buffer>(src.owner)
: src_gds ? m_gds_buffer : src_gds ? m_gds_buffer
: m_stream_buffer; : m_stream_buffer;
auto& destination = dst.owner != nullptr ? *std::static_pointer_cast<Buffer>(dst.owner) auto& destination =
: m_gds_buffer; dst.owner != nullptr ? *std::static_pointer_cast<Buffer>(dst.owner) : m_gds_buffer;
if (source.Handle() != src.buffer || destination.Handle() != dst.buffer) { if (source.Handle() != src.buffer || destination.Handle() != dst.buffer) {
EXIT("BufferCache: resolved copy owner does not match its Vulkan handle\n"); EXIT("BufferCache: resolved copy owner does not match its Vulkan handle\n");
} }
@@ -1107,7 +1167,7 @@ void BufferCache::BeginBackingPublication(uint64_t vaddr, uint64_t size, uint64_
void BufferCache::CompleteBackingPublication(uint64_t vaddr, uint64_t size, uint64_t tick) { void BufferCache::CompleteBackingPublication(uint64_t vaddr, uint64_t size, uint64_t tick) {
std::lock_guard lock(m_publication_mutex); std::lock_guard lock(m_publication_mutex);
const auto publication = const auto publication =
std::ranges::find_if(m_pending_backing_publications, [&](const auto& pending) { std::ranges::find_if(m_pending_backing_publications, [&](const auto& pending) {
return pending.address == vaddr && pending.size == size && pending.tick == tick; return pending.address == vaddr && pending.size == size && pending.tick == tick;
}); });
@@ -1170,7 +1230,7 @@ void BufferCache::RunGarbageCollector() {
const uint64_t age = std::min<uint64_t>(aggressive ? 80 : 160, tick); const uint64_t age = std::min<uint64_t>(aggressive ? 80 : 160, tick);
const size_t limit = aggressive ? 64 : 32; const size_t limit = aggressive ? 64 : 32;
std::vector<RetiredBuffer> retires; std::vector<RetiredBuffer> retires;
std::vector<std::pair<RetiredBuffer, std::vector<DownloadCopy>>> dirty_retires; std::vector<std::pair<RetiredBuffer, std::vector<DownloadCopy>>> dirty_retires;
{ {
FaultSafeCacheLock lock(this, m_mutex); FaultSafeCacheLock lock(this, m_mutex);
@@ -1190,8 +1250,8 @@ void BufferCache::RunGarbageCollector() {
} }
for (const auto address: candidates) { for (const auto address: candidates) {
auto& cached = *m_buffers.at(address); auto& cached = *m_buffers.at(address);
m_memory_tracker.ValidateGpuDirtyOwnership( m_memory_tracker.ValidateGpuDirtyOwnership(m_gpu_modified_ranges, cached.vaddr,
m_gpu_modified_ranges, cached.vaddr, cached.size, "garbage collection"); cached.size, "garbage collection");
retires.push_back({address, cached.size, cached.buffer}); retires.push_back({address, cached.size, cached.buffer});
// GC runs immediately before submission. Preserve every source referenced by commands // GC runs immediately before submission. Preserve every source referenced by commands
// already recorded in the active batch. // already recorded in the active batch.
@@ -1205,13 +1265,13 @@ void BufferCache::RunGarbageCollector() {
m_memory_tracker.ForEachDownloadRange<false>( m_memory_tracker.ForEachDownloadRange<false>(
retire.address, retire.size, retire.address, retire.size,
[&](uint64_t address, uint64_t size) noexcept { [&](uint64_t address, uint64_t size) noexcept {
m_memory_tracker.ValidateGpuDirtyPages( m_memory_tracker.ValidateGpuDirtyPages(m_gpu_modified_ranges, address, size,
m_gpu_modified_ranges, address, size, "garbage collection"); "garbage collection");
}, },
[&](uint64_t address, uint64_t size) noexcept { [&](uint64_t address, uint64_t size) noexcept {
for (const auto range: m_gpu_modified_ranges.Intersections(address, size)) { for (const auto range: m_gpu_modified_ranges.Intersections(address, size)) {
copies.push_back({retire.owner, range.address - retire.address, range.address, copies.push_back({retire.owner, range.address - retire.address,
range.size}); range.address, range.size});
} }
}); });
} }
@@ -6,7 +6,7 @@
#include "common/threads.h" #include "common/threads.h"
#include "graphics/host_gpu/memoryTracker.h" #include "graphics/host_gpu/memoryTracker.h"
#include "graphics/host_gpu/rangeSet.h" #include "graphics/host_gpu/rangeSet.h"
#include "graphics/host_gpu/renderer/streamBuffer.h" #include "graphics/host_gpu/renderer/cache/streamBuffer.h"
#include <map> #include <map>
#include <memory> #include <memory>
@@ -49,6 +49,8 @@ public:
[[nodiscard]] bool InvalidateMemory(PageFaultAccess access, uint64_t vaddr, uint64_t size, [[nodiscard]] bool InvalidateMemory(PageFaultAccess access, uint64_t vaddr, uint64_t size,
PageFaultPhase phase) noexcept; PageFaultPhase phase) noexcept;
void InvalidateMemory(uint64_t vaddr, uint64_t size);
void ReadMemory(uint64_t vaddr, uint64_t size);
void UnmapMemory(uint64_t vaddr, uint64_t size); void UnmapMemory(uint64_t vaddr, uint64_t size);
[[nodiscard]] BufferBinding ObtainBuffer(CommandBuffer& command, uint64_t vaddr, uint64_t size, [[nodiscard]] BufferBinding ObtainBuffer(CommandBuffer& command, uint64_t vaddr, uint64_t size,
bool is_written = false, bool is_read = true, bool is_written = false, bool is_read = true,
@@ -71,8 +73,8 @@ public:
[[nodiscard]] bool IsRegionCpuModified(uint64_t vaddr, uint64_t size); [[nodiscard]] bool IsRegionCpuModified(uint64_t vaddr, uint64_t size);
[[nodiscard]] bool IsRegionGpuModified(uint64_t vaddr, uint64_t size); [[nodiscard]] bool IsRegionGpuModified(uint64_t vaddr, uint64_t size);
void InvalidateImageAliases(uint64_t vaddr, uint64_t size); void InvalidateImageAliases(uint64_t vaddr, uint64_t size);
void BeginBackingPublication(uint64_t vaddr, uint64_t size, uint64_t tick); void BeginBackingPublication(uint64_t vaddr, uint64_t size, uint64_t tick);
void CompleteBackingPublication(uint64_t vaddr, uint64_t size, uint64_t tick); void CompleteBackingPublication(uint64_t vaddr, uint64_t size, uint64_t tick);
[[nodiscard]] bool SynchronizeBacking(uint64_t vaddr, uint64_t size); [[nodiscard]] bool SynchronizeBacking(uint64_t vaddr, uint64_t size);
void PublishImageBuffer(uint64_t vaddr, uint64_t size); void PublishImageBuffer(uint64_t vaddr, uint64_t size);
void ValidateGpuAccess(uint64_t vaddr, uint64_t size, bool is_read, bool is_written) const; void ValidateGpuAccess(uint64_t vaddr, uint64_t size, bool is_read, bool is_written) const;
@@ -91,23 +93,21 @@ private:
struct RetiredBuffer; struct RetiredBuffer;
struct FaultReadback; struct FaultReadback;
struct PendingBackingPublication; struct PendingBackingPublication;
static constexpr uint64_t DOWNLOAD_ALIGNMENT = 64; static constexpr uint64_t DOWNLOAD_ALIGNMENT = 64;
[[nodiscard]] static uint64_t AlignDown(uint64_t value) noexcept; [[nodiscard]] static uint64_t AlignDown(uint64_t value) noexcept;
[[nodiscard]] static uint64_t AlignUp(uint64_t value); [[nodiscard]] static uint64_t AlignUp(uint64_t value);
[[nodiscard]] static constexpr uint64_t AlignDownload(uint64_t size) noexcept { [[nodiscard]] static constexpr uint64_t AlignDownload(uint64_t size) noexcept {
return (size + DOWNLOAD_ALIGNMENT - 1) & ~(DOWNLOAD_ALIGNMENT - 1); return (size + DOWNLOAD_ALIGNMENT - 1) & ~(DOWNLOAD_ALIGNMENT - 1);
} }
[[nodiscard]] static bool PageOverlaps(uint64_t left, uint64_t left_size, uint64_t right, [[nodiscard]] static bool PageOverlaps(uint64_t left, uint64_t left_size, uint64_t right,
uint64_t right_size) noexcept; uint64_t right_size) noexcept;
[[nodiscard]] static std::pair<uint64_t, uint64_t> [[nodiscard]] static std::pair<uint64_t, uint64_t> DownloadEnvelope(const DownloadCopy& copy);
DownloadEnvelope(const DownloadCopy& copy); [[nodiscard]] static bool ResolveOverlap(CacheRange& merged, CacheRange candidate) noexcept;
[[nodiscard]] static bool ResolveOverlap(CacheRange& merged, CacheRange candidate) noexcept;
void Upload(CommandBuffer& command, Buffer& destination, uint64_t destination_offset, void Upload(CommandBuffer& command, Buffer& destination, uint64_t destination_offset,
const void* source, uint64_t size); const void* source, uint64_t size);
[[nodiscard]] CachedBuffer& GetOrCreateBuffer(CommandBuffer& command, uint64_t vaddr, [[nodiscard]] CachedBuffer& GetOrCreateBuffer(CommandBuffer& command, uint64_t vaddr,
uint64_t size); uint64_t size);
[[nodiscard]] std::vector<DownloadRange> [[nodiscard]] std::vector<DownloadRange> RecordDownloads(std::span<const DownloadCopy> copies);
RecordDownloads(std::span<const DownloadCopy> copies);
void PublishDownloads(std::span<const DownloadRange> downloads); void PublishDownloads(std::span<const DownloadRange> downloads);
void QueueGarbageDownload(std::span<const DownloadCopy> copies, RetiredBuffer retire); void QueueGarbageDownload(std::span<const DownloadCopy> copies, RetiredBuffer retire);
void RefreshInvalidatedRanges(CommandBuffer& command, CachedBuffer& cached, uint64_t vaddr, void RefreshInvalidatedRanges(CommandBuffer& command, CachedBuffer& cached, uint64_t vaddr,
@@ -1,4 +1,4 @@
#include "graphics/host_gpu/renderer/gpuResourceManager.h" #include "graphics/host_gpu/renderer/cache/gpuResourceManager.h"
#include "common/assert.h" #include "common/assert.h"
#include "graphics/guest_gpu/command_processor/commandProcessor.h" #include "graphics/guest_gpu/command_processor/commandProcessor.h"
@@ -36,7 +36,8 @@ bool GpuResourceManager::InvalidateMemory(PageFaultAccess access, uint64_t vaddr
} }
bool GpuResourceManager::HandleFault(PageFaultAccess access, uint64_t fault_vaddr) noexcept { bool GpuResourceManager::HandleFault(PageFaultAccess access, uint64_t fault_vaddr) noexcept {
if (!m_page_manager.IsMapped(fault_vaddr, 1)) { constexpr uint64_t fault_size = 8;
if (!IsMapped(fault_vaddr, fault_size)) {
return false; return false;
} }
if (CommandScheduler::InDeferredOperation()) { if (CommandScheduler::InDeferredOperation()) {
@@ -47,10 +48,15 @@ bool GpuResourceManager::HandleFault(PageFaultAccess access, uint64_t fault_vadd
bool handled = false; bool handled = false;
const auto resolve = [this, access, fault_vaddr, &handled](CommandProcessor& cp) { const auto resolve = [this, access, fault_vaddr, &handled](CommandProcessor& cp) {
cp.BeginReadbackTransaction(); cp.BeginReadbackTransaction();
(void)m_buffer_cache.SynchronizeBacking(fault_vaddr, 1);
{ {
ResourceMutex::FaultScope fault(m_resource_mutex); ResourceMutex::FaultScope fault(m_resource_mutex);
handled = m_page_manager.HandleFault(access, fault_vaddr); if (access == PageFaultAccess::Write) {
m_buffer_cache.InvalidateMemory(fault_vaddr, fault_size);
m_texture_cache.InvalidateMemory(fault_vaddr, fault_size);
} else {
m_buffer_cache.ReadMemory(fault_vaddr, fault_size);
}
handled = true;
} }
cp.EndReadbackTransaction(); cp.EndReadbackTransaction();
}; };
@@ -68,47 +74,52 @@ bool GpuResourceManager::HandleFault(PageFaultAccess access, uint64_t fault_vadd
return handled; return handled;
} }
void GpuResourceManager::PrepareHostWrite(uint64_t vaddr, uint64_t size) { bool GpuResourceManager::InvalidateMemory(uint64_t vaddr, uint64_t size) {
if (!m_page_manager.HasAnyMapping(vaddr, size)) { if (!IsMapped(vaddr, size)) {
return; return false;
} }
if (CommandScheduler::InDeferredOperation()) { if (CommandScheduler::InDeferredOperation()) {
EXIT("unsupported host write from an asynchronous GPU completion, addr=0x%016" PRIx64 EXIT("unsupported memory invalidation from an asynchronous GPU completion, "
" size=0x%016" PRIx64 "\n", "addr=0x%016" PRIx64 " size=0x%016" PRIx64 "\n",
vaddr, size); vaddr, size);
} }
const auto handle_range = [this, vaddr, size] { const auto resolve = [this, vaddr, size](CommandProcessor& cp) {
if (!m_page_manager.HandleWriteRange(vaddr, size)) {
EXIT("failed to prepare host write, addr=0x%016" PRIx64 " size=0x%016" PRIx64 "\n",
vaddr, size);
}
};
const auto resolve = [this, &handle_range](CommandProcessor& cp) {
cp.BeginReadbackTransaction(); cp.BeginReadbackTransaction();
{ {
ResourceMutex::FaultScope fault(m_resource_mutex); ResourceMutex::FaultScope fault(m_resource_mutex);
handle_range(); m_buffer_cache.InvalidateMemory(vaddr, size);
m_texture_cache.InvalidateMemory(vaddr, size);
} }
cp.EndReadbackTransaction(); cp.EndReadbackTransaction();
}; };
if (auto* cp = Gpu::CurrentCommandProcessor(); cp != nullptr) { if (auto* cp = Gpu::CurrentCommandProcessor(); cp != nullptr) {
resolve(*cp); resolve(*cp);
return; return true;
} }
if (m_resource_mutex.IsOwnedByCurrentThread()) { if (m_resource_mutex.IsOwnedByCurrentThread()) {
EXIT("unsupported host write from a pre-owned resource transaction, addr=0x%016" PRIx64 EXIT("unsupported memory invalidation from a pre-owned resource transaction, "
" size=0x%016" PRIx64 "\n", "addr=0x%016" PRIx64 " size=0x%016" PRIx64 "\n",
vaddr, size); vaddr, size);
} }
EXIT_IF(m_gpu == nullptr); EXIT_IF(m_gpu == nullptr);
m_gpu->SendCommandSyncWithProcessor(resolve); m_gpu->SendCommandSyncWithProcessor(resolve);
return true;
} }
bool GpuResourceManager::IsMapped(uint64_t vaddr, uint64_t size) const noexcept { bool GpuResourceManager::IsMapped(uint64_t vaddr, uint64_t size) const noexcept {
return m_page_manager.IsMapped(vaddr, size); if (vaddr == 0 || size == 0 || vaddr >= TRACKER_ADDRESS_SIZE ||
size > TRACKER_ADDRESS_SIZE - vaddr) {
return false;
}
std::shared_lock lock(m_mapped_ranges_mutex);
return m_mapped_ranges.Contains(vaddr, size);
} }
void GpuResourceManager::MapMemory(uint64_t vaddr, uint64_t size, GpuAccess access) { void GpuResourceManager::MapMemory(uint64_t vaddr, uint64_t size, GpuAccess access) {
{
std::lock_guard lock(m_mapped_ranges_mutex);
m_mapped_ranges.Add(vaddr, size);
}
m_page_manager.OnGpuMap(vaddr, size, access); m_page_manager.OnGpuMap(vaddr, size, access);
} }
@@ -120,6 +131,8 @@ void GpuResourceManager::UnmapMemory(uint64_t vaddr, uint64_t size, GpuAccess ac
m_texture_cache.UnmapMemory(vaddr, size); m_texture_cache.UnmapMemory(vaddr, size);
m_buffer_cache.UnmapMemory(vaddr, size); m_buffer_cache.UnmapMemory(vaddr, size);
m_page_manager.OnGpuUnmap(vaddr, size, access); m_page_manager.OnGpuUnmap(vaddr, size, access);
std::lock_guard lock(m_mapped_ranges_mutex);
m_mapped_ranges.Subtract(vaddr, size);
}; };
if (m_gpu == nullptr) { if (m_gpu == nullptr) {
if (m_resource_mutex.IsOwnedByCurrentThread()) { if (m_resource_mutex.IsOwnedByCurrentThread()) {
@@ -4,11 +4,12 @@
#include "common/abi.h" #include "common/abi.h"
#include "common/common.h" #include "common/common.h"
#include "graphics/host_gpu/pageManager.h" #include "graphics/host_gpu/pageManager.h"
#include "graphics/host_gpu/renderer/bufferCache.h" #include "graphics/host_gpu/renderer/cache/bufferCache.h"
#include "graphics/host_gpu/renderer/resourceMutex.h" #include "graphics/host_gpu/renderer/cache/resourceMutex.h"
#include "graphics/host_gpu/renderer/textureCache.h" #include "graphics/host_gpu/renderer/cache/textureCache.h"
#include <cstdint> #include <cstdint>
#include <shared_mutex>
namespace Libs::Graphics { namespace Libs::Graphics {
@@ -26,7 +27,7 @@ public:
void SetGpu(Gpu* gpu) noexcept { m_gpu = gpu; } void SetGpu(Gpu* gpu) noexcept { m_gpu = gpu; }
[[nodiscard]] bool HandleFault(PageFaultAccess access, uint64_t fault_vaddr) noexcept; [[nodiscard]] bool HandleFault(PageFaultAccess access, uint64_t fault_vaddr) noexcept;
void PrepareHostWrite(uint64_t vaddr, uint64_t size); [[nodiscard]] bool InvalidateMemory(uint64_t vaddr, uint64_t size);
[[nodiscard]] bool IsMapped(uint64_t vaddr, uint64_t size) const noexcept; [[nodiscard]] bool IsMapped(uint64_t vaddr, uint64_t size) const noexcept;
void MapMemory(uint64_t vaddr, uint64_t size, GpuAccess access); void MapMemory(uint64_t vaddr, uint64_t size, GpuAccess access);
void UnmapMemory(uint64_t vaddr, uint64_t size, GpuAccess access); void UnmapMemory(uint64_t vaddr, uint64_t size, GpuAccess access);
@@ -38,11 +39,13 @@ private:
[[nodiscard]] bool InvalidateMemory(PageFaultAccess access, uint64_t vaddr, uint64_t size, [[nodiscard]] bool InvalidateMemory(PageFaultAccess access, uint64_t vaddr, uint64_t size,
PageFaultPhase phase) noexcept; PageFaultPhase phase) noexcept;
PageManager m_page_manager; PageManager m_page_manager;
ResourceMutex m_resource_mutex; ResourceMutex m_resource_mutex;
BufferCache m_buffer_cache; BufferCache m_buffer_cache;
TextureCache m_texture_cache; TextureCache m_texture_cache;
Gpu* m_gpu = nullptr; mutable std::shared_mutex m_mapped_ranges_mutex;
RangeSet m_mapped_ranges;
Gpu* m_gpu = nullptr;
}; };
} // namespace Libs::Graphics } // namespace Libs::Graphics
@@ -1,4 +1,4 @@
#include "graphics/host_gpu/renderer/resourceMutex.h" #include "graphics/host_gpu/renderer/cache/resourceMutex.h"
#include "common/assert.h" #include "common/assert.h"
@@ -1,4 +1,4 @@
#include "graphics/host_gpu/renderer/samplerCache.h" #include "graphics/host_gpu/renderer/cache/samplerCache.h"
#include "common/assert.h" #include "common/assert.h"
#include "common/logging/log.h" #include "common/logging/log.h"
@@ -1,4 +1,4 @@
#include "graphics/host_gpu/renderer/streamBuffer.h" #include "graphics/host_gpu/renderer/cache/streamBuffer.h"
#include "common/assert.h" #include "common/assert.h"
#include "common/profiler.h" #include "common/profiler.h"
@@ -1,4 +1,4 @@
#include "graphics/host_gpu/renderer/textureCache.h" #include "graphics/host_gpu/renderer/cache/textureCache.h"
#include "common/assert.h" #include "common/assert.h"
#include "common/emulatorConfig.h" #include "common/emulatorConfig.h"
@@ -7,13 +7,13 @@
#include "graphics/guest_gpu/gpu_format.h" #include "graphics/guest_gpu/gpu_format.h"
#include "graphics/guest_gpu/tile.h" #include "graphics/guest_gpu/tile.h"
#include "graphics/host_gpu/graphicContext.h" #include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/objects/textureCommon.h" #include "graphics/host_gpu/renderer/cache/bufferCache.h"
#include "graphics/host_gpu/renderer/bufferCache.h" #include "graphics/host_gpu/renderer/cache/resourceMutex.h"
#include "graphics/host_gpu/renderer/commandScheduler.h" #include "graphics/host_gpu/renderer/commandScheduler.h"
#include "graphics/host_gpu/renderer/imageView.h" #include "graphics/host_gpu/renderer/image/imageView.h"
#include "graphics/host_gpu/renderer/image/textureCommon.h"
#include "graphics/host_gpu/renderer/image/tiler.h"
#include "graphics/host_gpu/renderer/render.h" #include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/resourceMutex.h"
#include "graphics/host_gpu/renderer/tiler.h"
#include "kernel/memory.h" #include "kernel/memory.h"
#include <algorithm> #include <algorithm>
@@ -58,8 +58,8 @@ private:
TextureCache::TextureCache(GraphicContext& graphics, CommandScheduler& scheduler, TextureCache::TextureCache(GraphicContext& graphics, CommandScheduler& scheduler,
PageManager& page_manager, BufferCache& buffer_cache, PageManager& page_manager, BufferCache& buffer_cache,
ResourceMutex& resource_mutex) ResourceMutex& resource_mutex)
: m_graphics(graphics), m_scheduler(scheduler), : m_graphics(graphics), m_scheduler(scheduler), m_page_manager(page_manager),
m_memory_tracker(page_manager, PageWatchMode::Write), m_blit_helper(graphics, scheduler), m_blit_helper(graphics, scheduler),
m_tiler(std::make_unique<TileManager>(graphics, scheduler, m_tiler(std::make_unique<TileManager>(graphics, scheduler,
buffer_cache.GetUtilityBuffer(MemoryUsage::Stream))), buffer_cache.GetUtilityBuffer(MemoryUsage::Stream))),
m_buffer_cache(buffer_cache), m_resource_mutex(resource_mutex), m_buffer_cache(buffer_cache), m_resource_mutex(resource_mutex),
@@ -80,7 +80,7 @@ TextureCache::TextureCache(GraphicContext& graphics, CommandScheduler& scheduler
TextureCache::~TextureCache() { TextureCache::~TextureCache() {
for (uint32_t index = 0; index < m_slots.size(); index++) { for (uint32_t index = 0; index < m_slots.size(); index++) {
if (m_slots[index].image != nullptr && m_slots[index].image->registered) { if (m_slots[index].image != nullptr && m_slots[index].image->registered) {
UnregisterImage({index, m_slots[index].generation}, false); UnregisterImage({index, m_slots[index].generation});
} }
m_slots[index].image.reset(); m_slots[index].image.reset();
} }
@@ -117,8 +117,7 @@ bool TextureCache::SafeToDownload(const Image& image) {
return false; return false;
} }
const auto range = image.info.data; const auto range = image.info.data;
return !m_buffer_cache.HasGpuDirtyBytes(range.address, range.size) && return !m_buffer_cache.HasGpuDirtyBytes(range.address, range.size);
!m_memory_tracker.IsRegionCpuModified(range.address, range.size);
} }
Image& TextureCache::ResolveImage(ImageId id) { Image& TextureCache::ResolveImage(ImageId id) {
@@ -187,21 +186,17 @@ void TextureCache::RegisterImage(ImageId id) {
m_total_used_memory += image.AccountedSize(); m_total_used_memory += image.AccountedSize();
} }
void TextureCache::UnregisterImage(ImageId id, bool release_tracking) { void TextureCache::UnregisterImage(ImageId id) {
auto& image = ResolveImage(id); auto& image = ResolveImage(id);
if (!image.registered) { if (!image.registered) {
return; return;
} }
UntrackImage(id);
std::vector<ImageOwnerIndex::ByteRange> releases; std::vector<ImageOwnerIndex::ByteRange> releases;
if (!m_image_owner_index.Unregister(id, releases)) { if (!m_image_owner_index.Unregister(id, releases)) {
EXIT("TextureCache: image missing from owner index\n"); EXIT("TextureCache: image missing from owner index\n");
} }
m_lru_cache.Free(image.lru_id); m_lru_cache.Free(image.lru_id);
if (release_tracking) {
for (const auto& range: releases) {
m_memory_tracker.UntrackMemory(range.address, range.size);
}
}
const auto accounted = image.AccountedSize(); const auto accounted = image.AccountedSize();
if (accounted > m_total_used_memory) { if (accounted > m_total_used_memory) {
EXIT("TextureCache: image accounting underflow\n"); EXIT("TextureCache: image accounting underflow\n");
@@ -210,7 +205,7 @@ void TextureCache::UnregisterImage(ImageId id, bool release_tracking) {
image.registered = false; image.registered = false;
} }
void TextureCache::DeleteImage(ImageId id, bool release_tracking) { void TextureCache::DeleteImage(ImageId id) {
auto owner = ResolveOwner(id); auto owner = ResolveOwner(id);
if (owner == nullptr || !owner->registered) { if (owner == nullptr || !owner->registered) {
return; return;
@@ -224,7 +219,7 @@ void TextureCache::DeleteImage(ImageId id, bool release_tracking) {
} }
} }
for (const auto association: associations) { for (const auto association: associations) {
ReleaseGpuTracking(association); ClearGpuModified(association);
DeleteImage(association); DeleteImage(association);
} }
} }
@@ -235,7 +230,7 @@ void TextureCache::DeleteImage(ImageId id, bool release_tracking) {
if (owner->info.metadata.kind == ImageMetadataKind::Htile) { if (owner->info.metadata.kind == ImageMetadataKind::Htile) {
m_surface_metas.erase(owner->info.metadata.range.address); m_surface_metas.erase(owner->info.metadata.range.address);
} }
UnregisterImage(id, release_tracking); UnregisterImage(id);
const auto erase_slot = [this, id, retained = owner] { const auto erase_slot = [this, id, retained = owner] {
auto& slot = m_slots[id.index]; auto& slot = m_slots[id.index];
if (slot.generation != id.generation || slot.image != retained) { if (slot.generation != id.generation || slot.image != retained) {
@@ -266,10 +261,10 @@ void TextureCache::DeleteImages(std::span<const ImageId> ids,
continue; continue;
} }
if (native_source == id) { if (native_source == id) {
ReleaseGpuTracking(id); ClearGpuModified(id);
} else if (owner->IsGpuModified()) { } else if (owner->IsGpuModified()) {
DownloadImage(id); DownloadImage(id);
ReleaseGpuTracking(id); ClearGpuModified(id);
} }
DeleteImage(id); DeleteImage(id);
} }
@@ -296,6 +291,121 @@ void TextureCache::TouchImage(Image& image) {
} }
} }
void TextureCache::TrackImage(ImageId id) {
auto& image = ResolveImage(id);
if (!image.registered) {
return;
}
const auto image_begin = image.info.data.address;
const auto image_end = image.info.data.End();
if (image_begin == image.track_addr && image_end == image.track_addr_end) {
return;
}
if (!image.IsTracked()) {
image.track_addr = image_begin;
image.track_addr_end = image_end;
m_page_manager.UpdatePageWatchers(true, image_begin, image.info.data.size);
return;
}
if (image_begin < image.track_addr) {
TrackImageHead(id);
}
if (image.track_addr_end < image_end) {
TrackImageTail(id);
}
}
void TextureCache::TrackImageHead(ImageId id) {
auto& image = ResolveImage(id);
if (!image.registered) {
return;
}
const auto image_begin = image.info.data.address;
if (image_begin == image.track_addr) {
return;
}
if (!image.IsTracked() || image_begin > image.track_addr) {
EXIT("TextureCache: invalid image head tracking range\n");
}
const auto size = image.track_addr - image_begin;
image.track_addr = image_begin;
m_page_manager.UpdatePageWatchers(true, image_begin, size);
}
void TextureCache::TrackImageTail(ImageId id) {
auto& image = ResolveImage(id);
if (!image.registered) {
return;
}
const auto image_end = image.info.data.End();
if (image_end == image.track_addr_end) {
return;
}
if (!image.IsTracked() || image.track_addr_end > image_end) {
EXIT("TextureCache: invalid image tail tracking range\n");
}
const auto address = image.track_addr_end;
const auto size = image_end - address;
image.track_addr_end = image_end;
m_page_manager.UpdatePageWatchers(true, address, size);
}
void TextureCache::UntrackImage(ImageId id) {
auto& image = ResolveImage(id);
if (!image.IsTracked()) {
return;
}
const auto address = image.track_addr;
const auto size = image.track_addr_end - image.track_addr;
image.track_addr = 0;
image.track_addr_end = 0;
if (size != 0) {
m_page_manager.UpdatePageWatchers(false, address, size);
}
}
void TextureCache::UntrackImageHead(ImageId id) {
auto& image = ResolveImage(id);
const auto begin = image.info.data.address;
if (!image.IsTracked() || begin < image.track_addr) {
return;
}
const auto address = (begin + TRACKER_PAGE_SIZE) & ~(TRACKER_PAGE_SIZE - 1);
const auto size = address - begin;
image.track_addr = address;
if (image.track_addr == image.track_addr_end) {
image.MarkMaybeCpuDirty();
if (image.NeedsMaybeCpuHash()) {
image.SetMaybeCpuHash(image.HashGuestEdges());
}
UntrackImage(id);
}
if (size != 0) {
m_page_manager.UpdatePageWatchers(false, begin, size);
}
}
void TextureCache::UntrackImageTail(ImageId id) {
auto& image = ResolveImage(id);
const auto end = image.info.data.End();
if (!image.IsTracked() || image.track_addr_end < end) {
return;
}
const auto address = end & ~(TRACKER_PAGE_SIZE - 1);
const auto size = end - address;
image.track_addr_end = address;
if (image.track_addr == image.track_addr_end) {
image.MarkMaybeCpuDirty();
if (image.NeedsMaybeCpuHash()) {
image.SetMaybeCpuHash(image.HashGuestEdges());
}
UntrackImage(id);
}
if (size != 0) {
m_page_manager.UpdatePageWatchers(false, address, size);
}
}
void TextureCache::TrackImageDownload(ImageId id) { void TextureCache::TrackImageDownload(ImageId id) {
std::lock_guard transaction(m_resource_mutex); std::lock_guard transaction(m_resource_mutex);
CacheLock lock(*this, m_lock); CacheLock lock(*this, m_lock);
@@ -376,9 +486,6 @@ void TextureCache::ValidateImageDesc(const ImageDesc& desc) const {
} }
void TextureCache::PrepareImageCopy(Image& image) { void TextureCache::PrepareImageCopy(Image& image) {
const auto range = image.info.data;
m_memory_tracker.ForEachUploadRange(
range.address, range.size, false, [](uint64_t, uint64_t) noexcept {}, []() noexcept {});
if (image.IsCpuDirty()) { if (image.IsCpuDirty()) {
image.RefreshComplete(); image.RefreshComplete();
} }
@@ -471,6 +578,7 @@ void TextureCache::CopyImage(ImageId destination_id, ImageId source_id) {
RefreshCopySource(source_id); RefreshCopySource(source_id);
auto& destination = ResolveImage(destination_id); auto& destination = ResolveImage(destination_id);
auto& source = ResolveImage(source_id); auto& source = ResolveImage(source_id);
TrackImage(destination_id);
if (source.backing.samples != destination.backing.samples) { if (source.backing.samples != destination.backing.samples) {
EXIT("TextureCache: cannot issue an unequal-sample image copy\n"); EXIT("TextureCache: cannot issue an unequal-sample image copy\n");
} }
@@ -479,7 +587,6 @@ void TextureCache::CopyImage(ImageId destination_id, ImageId source_id) {
if (source.info.data == destination.info.data) { if (source.info.data == destination.info.data) {
destination.MarkBufferModified(); destination.MarkBufferModified();
} }
RestoreGpuTracking(destination);
return; return;
} }
const bool source_depth = source.info.IsDepth(); const bool source_depth = source.info.IsDepth();
@@ -503,7 +610,6 @@ void TextureCache::CopyImage(ImageId destination_id, ImageId source_id) {
destination.MarkGpuModified(); destination.MarkGpuModified();
} }
destination.ClearBufferModified(); destination.ClearBufferModified();
RestoreGpuTracking(destination);
} }
void TextureCache::CopyImageMip(ImageId destination_id, ImageId source_id, uint32_t mip, void TextureCache::CopyImageMip(ImageId destination_id, ImageId source_id, uint32_t mip,
@@ -511,6 +617,7 @@ void TextureCache::CopyImageMip(ImageId destination_id, ImageId source_id, uint3
RefreshCopySource(source_id); RefreshCopySource(source_id);
auto& destination = ResolveImage(destination_id); auto& destination = ResolveImage(destination_id);
auto& source = ResolveImage(source_id); auto& source = ResolveImage(source_id);
TrackImage(destination_id);
if (source.IsBufferModified() || source.backing.samples != destination.backing.samples) { if (source.IsBufferModified() || source.backing.samples != destination.backing.samples) {
EXIT("TextureCache: invalid mip-copy ownership or sample count\n"); EXIT("TextureCache: invalid mip-copy ownership or sample count\n");
} }
@@ -520,7 +627,6 @@ void TextureCache::CopyImageMip(ImageId destination_id, ImageId source_id, uint3
if (source.IsGpuModified()) { if (source.IsGpuModified()) {
destination.MarkGpuModified(); destination.MarkGpuModified();
} }
RestoreGpuTracking(destination);
} }
ImageId TextureCache::ResolveDepthOverlap(const ImageInfo& requested, BindingType binding, ImageId TextureCache::ResolveDepthOverlap(const ImageInfo& requested, BindingType binding,
@@ -590,7 +696,7 @@ ImageId TextureCache::ResolveDepthOverlap(const ImageInfo& requested, BindingTyp
if (copied) { if (copied) {
DeleteImages(std::array {cached_id}, cached_id); DeleteImages(std::array {cached_id}, cached_id);
} else { } else {
ReleaseGpuTracking(cached_id); ClearGpuModified(cached_id);
DeleteImage(cached_id); DeleteImage(cached_id);
} }
return replacement_id; return replacement_id;
@@ -903,39 +1009,29 @@ void TextureCache::InitializeImage(ImageId id, const ImageDesc& desc) {
if (image.info.data.Empty()) { if (image.info.data.Empty()) {
return; return;
} }
TrackImage(id);
if (image.info.metadata.compression != VideoOutCompression::Uncompressed) { if (image.info.metadata.compression != VideoOutCompression::Uncompressed) {
m_memory_tracker.ForEachUploadRange(
image.info.data.address, image.info.data.size, false,
[](uint64_t, uint64_t) noexcept {}, []() noexcept {});
if (image.IsCpuDirty()) { if (image.IsCpuDirty()) {
image.RefreshComplete(); image.RefreshComplete();
} }
return; return;
} }
if (image.info.samples > 1) { if (image.info.samples > 1) {
RestoreGpuTracking(image);
return; return;
} }
bool data_gpu_owned = false; bool data_gpu_owned = false;
bool data_imported = false; bool data_imported = false;
bool uploaded = false; const bool upload = image.IsBufferModified() || image.IsCpuDirty();
m_memory_tracker.ForEachUploadRange( if (upload) {
image.info.data.address, image.info.data.size, false, const auto source =
[&](uint64_t, uint64_t) noexcept { uploaded = true; }, m_buffer_cache.ObtainBufferForImage(image.info.data.address, image.info.data.size);
[&]() noexcept { if (source.buffer == nullptr) {
uploaded |= image.IsBufferModified() || image.IsDefinitelyCpuDirty(); EXIT("TextureCache: failed to obtain image upload source\n");
if (!uploaded) { }
return; data_gpu_owned |= source.gpu_owned;
} data_imported = true;
const auto source = UploadImage(image, desc, *source.buffer, source.offset);
m_buffer_cache.ObtainBufferForImage(image.info.data.address, image.info.data.size); }
if (source.buffer == nullptr) {
EXIT("TextureCache: failed to obtain image upload source\n");
}
data_gpu_owned |= source.gpu_owned;
data_imported = true;
UploadImage(image, desc, *source.buffer, source.offset);
});
if (data_imported) { if (data_imported) {
image.ClearBufferModified(); image.ClearBufferModified();
} }
@@ -945,39 +1041,27 @@ void TextureCache::InitializeImage(ImageId id, const ImageDesc& desc) {
if (image.IsCpuDirty()) { if (image.IsCpuDirty()) {
image.RefreshComplete(); image.RefreshComplete();
} }
RestoreGpuTracking(image);
} }
void TextureCache::RefreshImage(ImageId id, const ImageDesc& desc) { void TextureCache::RefreshImage(ImageId id, const ImageDesc& desc) {
auto& image = ResolveImage(id); TrackImage(id);
bool unchanged_maybe = false; auto& image = ResolveImage(id);
if (image.IsMaybeCpuDirty()) { if (image.IsMaybeCpuDirty()) {
const auto hash = image.HashGuestEdges(); const auto hash = image.HashGuestEdges();
if (image.NeedsMaybeCpuHash()) { if (image.NeedsMaybeCpuHash()) {
image.SetMaybeCpuHash(hash); image.SetMaybeCpuHash(hash);
return; return;
} }
unchanged_maybe = !image.ResolveMaybeCpuHash(hash); (void)image.ResolveMaybeCpuHash(hash);
if (unchanged_maybe) {
m_memory_tracker.ForEachUploadRange(
image.info.data.address, image.info.data.size, false,
[](uint64_t, uint64_t) noexcept {}, []() noexcept {});
}
} }
bool cpu_dirty = image.IsBufferModified() || image.IsDefinitelyCpuDirty(); bool cpu_dirty = image.IsBufferModified() || image.IsDefinitelyCpuDirty();
if (!unchanged_maybe) {
cpu_dirty |=
m_memory_tracker.IsRegionCpuModified(image.info.data.address, image.info.data.size);
}
if (image.info.metadata.compression != VideoOutCompression::Uncompressed) { if (image.info.metadata.compression != VideoOutCompression::Uncompressed) {
if (cpu_dirty) { if (cpu_dirty) {
EXIT("TextureCache: compressed guest image refresh is unsupported\n"); EXIT("TextureCache: compressed guest image refresh is unsupported\n");
} }
RestoreGpuTracking(image);
return; return;
} }
if (!cpu_dirty) { if (!cpu_dirty) {
RestoreGpuTracking(image);
return; return;
} }
InitializeImage(id, desc); InitializeImage(id, desc);
@@ -1029,7 +1113,7 @@ ImageId TextureCache::FindImage(ImageDesc& desc, bool exact_format) {
ImageId result {}; ImageId result {};
bool replacement_buffer = false; bool replacement_buffer = false;
bool replacing_image = false; bool inserted_new = false;
{ {
std::lock_guard transaction(m_resource_mutex); std::lock_guard transaction(m_resource_mutex);
CacheLock lock(*this, m_lock); CacheLock lock(*this, m_lock);
@@ -1079,20 +1163,19 @@ ImageId TextureCache::FindImage(ImageDesc& desc, bool exact_format) {
} }
replacement_buffer = resolved.IsBufferModified(); replacement_buffer = resolved.IsBufferModified();
DeleteImage(result); DeleteImage(result);
result = {}; result = {};
replacing_image = true;
} }
} }
if (!result) { if (!result) {
result = InsertImage(desc.info); result = InsertImage(desc.info);
inserted_new = true;
auto& inserted = ResolveImage(result); auto& inserted = ResolveImage(result);
if (replacement_buffer || m_buffer_cache.HasGpuDirtyBytes(inserted.info.data.address, if (replacement_buffer || m_buffer_cache.HasGpuDirtyBytes(inserted.info.data.address,
inserted.info.data.size)) { inserted.info.data.size)) {
inserted.MarkBufferModified(); inserted.MarkBufferModified();
} else if (replacing_image) {
m_memory_tracker.MarkRegionAsCpuModified(inserted.info.data.address,
inserted.info.data.size);
} }
}
if (inserted_new) {
InitializeImage(result, desc); InitializeImage(result, desc);
} else { } else {
RefreshImage(result, desc); RefreshImage(result, desc);
@@ -1101,9 +1184,7 @@ ImageId TextureCache::FindImage(ImageDesc& desc, bool exact_format) {
auto& image = ResolveImage(result); auto& image = ResolveImage(result);
if (desc.type == BindingType::VideoOut && if (desc.type == BindingType::VideoOut &&
desc.info.metadata.compression != VideoOutCompression::Uncompressed) { desc.info.metadata.compression != VideoOutCompression::Uncompressed) {
const bool guest_dirty = const bool guest_dirty = image.IsBufferModified() || image.IsCpuDirty();
image.IsBufferModified() || image.IsCpuDirty() ||
m_memory_tracker.IsRegionCpuModified(image.info.data.address, image.info.data.size);
const bool native_current = const bool native_current =
(image.usage.render_target || image.IsGpuModified()) && !guest_dirty; (image.usage.render_target || image.IsGpuModified()) && !guest_dirty;
if (!native_current) { if (!native_current) {
@@ -1252,6 +1333,7 @@ void TextureCache::MarkGpuWritten(ImageId id) {
if (!image.registered || image.depth_id) { if (!image.registered || image.depth_id) {
EXIT("TextureCache: cannot mark an unavailable image GPU-written\n"); EXIT("TextureCache: cannot mark an unavailable image GPU-written\n");
} }
TrackImage(id);
CommitGpuWrite(image); CommitGpuWrite(image);
} }
@@ -1265,13 +1347,10 @@ void TextureCache::CommitGpuWrite(Image& image) {
} }
m_buffer_cache.InvalidateImageAliases(range.address, range.size); m_buffer_cache.InvalidateImageAliases(range.address, range.size);
image.ClearBufferModified(); image.ClearBufferModified();
m_memory_tracker.ForEachUploadRange(
range.address, range.size, true, [](uint64_t, uint64_t) noexcept {}, []() noexcept {});
if (image.IsCpuDirty()) { if (image.IsCpuDirty()) {
image.RefreshComplete(); image.RefreshComplete();
} }
image.MarkGpuModified(); image.MarkGpuModified();
RestoreGpuTracking(image);
} }
bool TextureCache::ClearImageFromBuffer(CommandBuffer& command, uint64_t address, uint64_t size, bool TextureCache::ClearImageFromBuffer(CommandBuffer& command, uint64_t address, uint64_t size,
@@ -1335,8 +1414,7 @@ bool TextureCache::ClearImageFromBuffer(CommandBuffer& command, uint64_t address
if (m_buffer_cache.HasGpuDirtyBytes(address, size)) { if (m_buffer_cache.HasGpuDirtyBytes(address, size)) {
m_buffer_cache.DiscardGpuDirtyBytes(address, size); m_buffer_cache.DiscardGpuDirtyBytes(address, size);
} }
if (image.IsBufferModified() || image.IsCpuDirty() || if (image.IsBufferModified() || image.IsCpuDirty()) {
m_memory_tracker.IsRegionCpuModified(image.info.data.address, image.info.data.size)) {
ImageDesc refresh {.info = image.info, .view_info = {}, .type = UploadBinding(image)}; ImageDesc refresh {.info = image.info, .view_info = {}, .type = UploadBinding(image)};
InitializeImage(selected, refresh); InitializeImage(selected, refresh);
if (image.info.samples == 1 && (image.IsBufferModified() || image.IsCpuDirty())) { if (image.info.samples == 1 && (image.IsBufferModified() || image.IsCpuDirty())) {
@@ -1361,14 +1439,12 @@ bool TextureCache::ClearImageFromBuffer(CommandBuffer& command, uint64_t address
return true; return true;
} }
void TextureCache::PrepareHostWrite(uint64_t address, uint64_t size) { void TextureCache::InvalidateMemory(uint64_t address, uint64_t size) {
if (!GuestRange {address, size}.Valid()) { if (!GuestRange {address, size}.Valid()) {
EXIT("TextureCache: invalid host-write range\n"); EXIT("TextureCache: invalid memory-invalidation range\n");
} }
CacheLock lock(*this, m_lock); CacheLock lock(*this, m_lock);
InvalidateCpuAliases(address, size); InvalidateCpuAliases(address, size);
m_memory_tracker.ForEachDownloadRange<true>(address, size, [](uint64_t, uint64_t) noexcept {});
m_memory_tracker.MarkRegionAsCpuModified(address, size);
} }
void TextureCache::DownloadDepth(Image& image, Buffer& destination, uint64_t destination_offset) { void TextureCache::DownloadDepth(Image& image, Buffer& destination, uint64_t destination_offset) {
@@ -1567,18 +1643,15 @@ bool TextureCache::SynchronizeImageToBuffer(ImageId id) {
if (!plan.valid) { if (!plan.valid) {
return false; return false;
} }
const auto range = image.info.data; const auto range = image.info.data;
const bool refresh = image.IsDefinitelyCpuDirty() || if (image.IsCpuDirty()) {
m_memory_tracker.IsRegionCpuModified(range.address, range.size);
if (refresh) {
RefreshImage(id, RefreshImage(id,
ImageDesc {.info = image.info, .view_info = {}, .type = UploadBinding(image)}); ImageDesc {.info = image.info, .view_info = {}, .type = UploadBinding(image)});
} }
if (!image.IsGpuModified()) { if (!image.IsGpuModified()) {
return true; return true;
} }
if (image.IsDefinitelyCpuDirty() || image.IsBufferModified() || if (image.IsDefinitelyCpuDirty() || image.IsBufferModified()) {
m_memory_tracker.IsRegionCpuModified(range.address, range.size)) {
EXIT("TextureCache: image mirror source is not native-current\n"); EXIT("TextureCache: image mirror source is not native-current\n");
} }
auto [destination, offset] = auto [destination, offset] =
@@ -1591,7 +1664,7 @@ bool TextureCache::SynchronizeImageToBuffer(ImageId id) {
m_buffer_cache.PublishImageBuffer(range.address, range.size); m_buffer_cache.PublishImageBuffer(range.address, range.size);
image.MarkBufferModified(); image.MarkBufferModified();
RetainImage(m_scheduler.Current(), id); RetainImage(m_scheduler.Current(), id);
ReleaseGpuTracking(id); ClearGpuModified(id);
return true; return true;
} }
@@ -1633,7 +1706,7 @@ bool TextureCache::InvalidateMemoryFromGPU(uint64_t address, uint64_t size,
if (!formatted_buffer_write) { if (!formatted_buffer_write) {
EXIT("TextureCache: buffer write aliases GPU-modified image\n"); EXIT("TextureCache: buffer write aliases GPU-modified image\n");
} }
ReleaseGpuTracking(id); ClearGpuModified(id);
} }
owner->MarkBufferModified(); owner->MarkBufferModified();
found = true; found = true;
@@ -1660,50 +1733,40 @@ TextureCache::RegionInfo TextureCache::QueryRegion(uint64_t address, uint64_t si
} }
void TextureCache::InvalidateCpuAliases(uint64_t address, uint64_t size) { void TextureCache::InvalidateCpuAliases(uint64_t address, uint64_t size) {
const auto page_begin = address & ~(TRACKER_PAGE_SIZE - 1);
const auto page_end = (address + size + TRACKER_PAGE_SIZE - 1) & ~(TRACKER_PAGE_SIZE - 1);
for (const auto id: FindImagesInRegion(address, size, true)) { for (const auto id: FindImagesInRegion(address, size, true)) {
auto owner = ResolveOwner(id); auto owner = ResolveOwner(id);
if (owner == nullptr || owner->depth_id) { if (owner == nullptr || owner->depth_id) {
continue; continue;
} }
owner->InvalidateCpuWrite(address, size); if (owner->Overlaps(address, size)) {
if (owner->NeedsMaybeCpuHash()) { owner->InvalidateCpuWrite(address, size);
owner->SetMaybeCpuHash(owner->HashGuestEdges()); UntrackImage(id);
continue;
}
const auto image_begin = owner->info.data.address;
const auto image_end = owner->info.data.End();
if (page_end < image_end) {
UntrackImageHead(id);
} else if (image_begin < page_begin) {
UntrackImageTail(id);
} else {
owner->MarkMaybeCpuDirty();
if (owner->NeedsMaybeCpuHash()) {
owner->SetMaybeCpuHash(owner->HashGuestEdges());
}
UntrackImage(id);
} }
} }
} }
void TextureCache::RestoreGpuTracking(const Image& image) { void TextureCache::ClearGpuModified(ImageId id) {
if (!image.IsGpuModified()) {
return;
}
constexpr uint64_t page_mask = TRACKER_PAGE_SIZE - 1;
const auto range = image.info.data;
const auto begin = range.address & ~page_mask;
const auto end = (range.End() + page_mask) & ~page_mask;
for (auto page = begin; page < end; page += TRACKER_PAGE_SIZE) {
if (!m_memory_tracker.IsRegionGpuModified(page, TRACKER_PAGE_SIZE) &&
!m_memory_tracker.IsRegionCpuModified(page, TRACKER_PAGE_SIZE)) {
m_memory_tracker.MarkRegionAsGpuModified(page, TRACKER_PAGE_SIZE);
}
}
}
void TextureCache::ReleaseGpuTracking(ImageId id) {
auto owner = ResolveOwner(id); auto owner = ResolveOwner(id);
if (owner == nullptr || !owner->IsGpuModified()) { if (owner == nullptr || !owner->IsGpuModified()) {
return; return;
} }
const auto released = owner->info.data;
owner->ClearGpuModified(); owner->ClearGpuModified();
m_memory_tracker.ForEachDownloadRange<true>(released.address, released.size,
[](uint64_t, uint64_t) noexcept {});
for (const auto candidate: FindImagesInRegion(released.address, released.size, true)) {
const auto survivor = ResolveOwner(candidate);
if (survivor != nullptr && survivor.get() != owner.get() && !survivor->depth_id) {
RestoreGpuTracking(*survivor);
}
}
RestoreGpuTracking(*owner);
} }
bool TextureCache::IsMeta(uint64_t address) { bool TextureCache::IsMeta(uint64_t address) {
@@ -1755,27 +1818,25 @@ bool TextureCache::InvalidateMemory(PageFaultAccess access, uint64_t address, ui
return false; return false;
} }
if (phase == PageFaultPhase::Invalidate) { if (phase == PageFaultPhase::Invalidate) {
const bool gpu_image = CacheLock lock(*this, m_lock);
m_memory_tracker.InvalidateVirtualGpuWrite(access, address, size, phase); const bool tracked =
CpuFaultAction action = gpu_image ? CpuFaultAction::Download std::ranges::any_of(FindImagesInRegion(address, size, true), [&](ImageId id) {
: m_memory_tracker.BeginCpuFault(address, size, access); const auto owner = ResolveOwner(id);
{ return owner != nullptr && !owner->depth_id && owner->IsTracked();
CacheLock lock(*this, m_lock); });
if (tracked) {
InvalidateCpuAliases(address, size); InvalidateCpuAliases(address, size);
} }
return action != CpuFaultAction::Untracked; return tracked;
} }
if (phase != PageFaultPhase::Complete && phase != PageFaultPhase::Release) {
if (phase == PageFaultPhase::Complete) {
const bool gpu_image = m_memory_tracker.IsRegionGpuModified(address, size);
return gpu_image ? m_memory_tracker.InvalidateVirtualGpuWrite(access, address, size, phase)
: m_memory_tracker.CompleteCpuFault(address, size, access, false);
}
if (phase != PageFaultPhase::Release) {
return false; return false;
} }
(void)m_memory_tracker.InvalidateVirtualGpuWrite(access, address, size, phase); CacheLock lock(*this, m_lock);
return true; return std::ranges::any_of(FindImagesInRegion(address, size, true), [&](ImageId id) {
const auto owner = ResolveOwner(id);
return owner != nullptr && !owner->depth_id;
});
} }
void TextureCache::UnmapMemory(uint64_t address, uint64_t size) { void TextureCache::UnmapMemory(uint64_t address, uint64_t size) {
@@ -1794,16 +1855,10 @@ void TextureCache::UnmapMemory(uint64_t address, uint64_t size) {
continue; continue;
} }
if (owner->IsGpuModified()) { if (owner->IsGpuModified()) {
ReleaseGpuTracking(id); ClearGpuModified(id);
} }
DeleteImage(id); DeleteImage(id);
} }
m_memory_tracker.UntrackMemory(address, size);
for (const auto id: FindImagesInRegion(address, size, true)) {
if (const auto survivor = ResolveOwner(id); survivor != nullptr) {
RestoreGpuTracking(*survivor);
}
}
} }
void TextureCache::RunGarbageCollector() { void TextureCache::RunGarbageCollector() {
@@ -1849,7 +1904,7 @@ void TextureCache::RunGarbageCollector() {
if (safe && !TryDownloadImage(id)) { if (safe && !TryDownloadImage(id)) {
continue; continue;
} }
ReleaseGpuTracking(id); ClearGpuModified(id);
} }
DeleteImage(id); DeleteImage(id);
if (m_total_used_memory < m_critical_gc_memory && aggressive) { if (m_total_used_memory < m_critical_gc_memory && aggressive) {
@@ -4,10 +4,11 @@
#include "common/abi.h" #include "common/abi.h"
#include "common/common.h" #include "common/common.h"
#include "common/lruCache.h" #include "common/lruCache.h"
#include "graphics/host_gpu/memoryTracker.h" #include "graphics/host_gpu/pageManager.h"
#include "graphics/host_gpu/renderer/blitHelper.h" #include "graphics/host_gpu/regionManager.h"
#include "graphics/host_gpu/renderer/image.h" #include "graphics/host_gpu/renderer/cache/multiLevelPageTable.h"
#include "graphics/host_gpu/renderer/multiLevelPageTable.h" #include "graphics/host_gpu/renderer/image/blitHelper.h"
#include "graphics/host_gpu/renderer/image/image.h"
#include <compare> #include <compare>
#include <map> #include <map>
@@ -64,7 +65,7 @@ public:
[[nodiscard]] bool ClearImageFromBuffer(CommandBuffer& command, uint64_t address, uint64_t size, [[nodiscard]] bool ClearImageFromBuffer(CommandBuffer& command, uint64_t address, uint64_t size,
uint32_t packed_clear); uint32_t packed_clear);
void PrepareHostWrite(uint64_t address, uint64_t size); void InvalidateMemory(uint64_t address, uint64_t size);
[[nodiscard]] bool SynchronizeImageToBuffer(uint64_t address, uint64_t size); [[nodiscard]] bool SynchronizeImageToBuffer(uint64_t address, uint64_t size);
[[nodiscard]] bool InvalidateMemoryFromGPU(uint64_t address, uint64_t size, [[nodiscard]] bool InvalidateMemoryFromGPU(uint64_t address, uint64_t size,
bool formatted_buffer_write = false); bool formatted_buffer_write = false);
@@ -109,11 +110,17 @@ private:
[[nodiscard]] ImageId InsertImage(const ImageInfo& info); [[nodiscard]] ImageId InsertImage(const ImageInfo& info);
[[nodiscard]] ImageId GetNullImage(const ImageDesc& desc); [[nodiscard]] ImageId GetNullImage(const ImageDesc& desc);
void RegisterImage(ImageId id); void RegisterImage(ImageId id);
void UnregisterImage(ImageId id, bool release_tracking); void UnregisterImage(ImageId id);
void DeleteImage(ImageId id, bool release_tracking = true); void DeleteImage(ImageId id);
void DeleteImages(std::span<const ImageId> ids, std::optional<ImageId> native_source = {}); void DeleteImages(std::span<const ImageId> ids, std::optional<ImageId> native_source = {});
void RetainImage(CommandBuffer& command, ImageId id); void RetainImage(CommandBuffer& command, ImageId id);
void TouchImage(Image& image); void TouchImage(Image& image);
void TrackImage(ImageId id);
void TrackImageHead(ImageId id);
void TrackImageTail(ImageId id);
void UntrackImage(ImageId id);
void UntrackImageHead(ImageId id);
void UntrackImageTail(ImageId id);
void TrackImageDownload(ImageId id); void TrackImageDownload(ImageId id);
void TrackImageDownloadLocked(ImageId id, Image& image); void TrackImageDownloadLocked(ImageId id, Image& image);
[[nodiscard]] static bool SameBacking(const ImageInfo& cached, const ImageInfo& requested, [[nodiscard]] static bool SameBacking(const ImageInfo& cached, const ImageInfo& requested,
@@ -148,8 +155,7 @@ private:
void ValidateImageDesc(const ImageDesc& desc) const; void ValidateImageDesc(const ImageDesc& desc) const;
void InvalidateCpuAliases(uint64_t address, uint64_t size); void InvalidateCpuAliases(uint64_t address, uint64_t size);
void RestoreGpuTracking(const Image& image); void ClearGpuModified(ImageId id);
void ReleaseGpuTracking(ImageId id);
[[nodiscard]] bool SynchronizeImageToBuffer(ImageId id); [[nodiscard]] bool SynchronizeImageToBuffer(ImageId id);
void DownloadImage(ImageId id); void DownloadImage(ImageId id);
@@ -160,7 +166,7 @@ private:
GraphicContext& m_graphics; GraphicContext& m_graphics;
CommandScheduler& m_scheduler; CommandScheduler& m_scheduler;
TrackingSpinLock m_lock; TrackingSpinLock m_lock;
MemoryTracker m_memory_tracker; PageManager& m_page_manager;
BlitHelper m_blit_helper; BlitHelper m_blit_helper;
std::unique_ptr<TileManager> m_tiler; std::unique_ptr<TileManager> m_tiler;
BufferCache& m_buffer_cache; BufferCache& m_buffer_cache;
@@ -7,9 +7,9 @@
#include "graphics/guest_gpu/hardwareContext.h" #include "graphics/guest_gpu/hardwareContext.h"
#include "graphics/guest_gpu/tile.h" #include "graphics/guest_gpu/tile.h"
#include "graphics/host_gpu/graphicContext.h" #include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/objects/textureCommon.h" #include "graphics/host_gpu/renderer/image/textureCommon.h"
#include "graphics/host_gpu/renderer/debug.h" #include "graphics/host_gpu/renderer/debug.h"
#include "graphics/host_gpu/renderer/descriptorCache.h" #include "graphics/host_gpu/renderer/pipeline/descriptorCache.h"
#include "graphics/host_gpu/renderer/render.h" #include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/renderContext.h" #include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/host_gpu/vulkanCommon.h" #include "graphics/host_gpu/vulkanCommon.h"
@@ -3,7 +3,7 @@
#include "graphics/guest_gpu/gpu_defs.h" #include "graphics/guest_gpu/gpu_defs.h"
#include "graphics/host_gpu/renderer/renderTarget.h" #include "graphics/host_gpu/renderer/renderTarget.h"
#include "graphics/host_gpu/renderer/textureCache.h" #include "graphics/host_gpu/renderer/cache/textureCache.h"
#include "graphics/host_gpu/vulkanCommon.h" #include "graphics/host_gpu/vulkanCommon.h"
#include <cstdint> #include <cstdint>
+2 -2
View File
@@ -8,8 +8,8 @@
#include "graphics/host_gpu/renderer/colorRenderTarget.h" #include "graphics/host_gpu/renderer/colorRenderTarget.h"
#include "graphics/host_gpu/renderer/debug.h" #include "graphics/host_gpu/renderer/debug.h"
#include "graphics/host_gpu/renderer/depthRenderTarget.h" #include "graphics/host_gpu/renderer/depthRenderTarget.h"
#include "graphics/host_gpu/renderer/descriptorCache.h" #include "graphics/host_gpu/renderer/pipeline/descriptorCache.h"
#include "graphics/host_gpu/renderer/imageView.h" #include "graphics/host_gpu/renderer/image/imageView.h"
#include "graphics/host_gpu/renderer/render.h" #include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/renderContext.h" #include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/host_gpu/vma.h" #include "graphics/host_gpu/vma.h"
@@ -10,10 +10,10 @@
#include "graphics/guest_gpu/hardwareContext.h" #include "graphics/guest_gpu/hardwareContext.h"
#include "graphics/guest_gpu/tile.h" #include "graphics/guest_gpu/tile.h"
#include "graphics/host_gpu/graphicContext.h" #include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/objects/textureCommon.h" #include "graphics/host_gpu/renderer/image/textureCommon.h"
#include "graphics/host_gpu/renderer/debug.h" #include "graphics/host_gpu/renderer/debug.h"
#include "graphics/host_gpu/renderer/descriptorCache.h" #include "graphics/host_gpu/renderer/pipeline/descriptorCache.h"
#include "graphics/host_gpu/renderer/imageView.h" #include "graphics/host_gpu/renderer/image/imageView.h"
#include "graphics/host_gpu/renderer/render.h" #include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/renderContext.h" #include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/host_gpu/vulkanCommon.h" #include "graphics/host_gpu/vulkanCommon.h"
@@ -2,9 +2,9 @@
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_DEPTHRENDERTARGET_H_ #define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_DEPTHRENDERTARGET_H_
#include "common/assert.h" #include "common/assert.h"
#include "graphics/host_gpu/renderer/imageView.h" #include "graphics/host_gpu/renderer/image/imageView.h"
#include "graphics/host_gpu/renderer/renderTarget.h" #include "graphics/host_gpu/renderer/renderTarget.h"
#include "graphics/host_gpu/renderer/textureCache.h" #include "graphics/host_gpu/renderer/cache/textureCache.h"
#include "graphics/host_gpu/vulkanCommon.h" #include "graphics/host_gpu/vulkanCommon.h"
#include <cstdint> #include <cstdint>
@@ -1,11 +1,11 @@
#include "graphics/host_gpu/renderer/blitHelper.h" #include "graphics/host_gpu/renderer/image/blitHelper.h"
#include "common/assert.h" #include "common/assert.h"
#include "gpu_blit_shaders/gpu_blit_color_to_ms_depth_spv.h" #include "gpu_blit_shaders/gpu_blit_color_to_ms_depth_spv.h"
#include "gpu_blit_shaders/gpu_blit_fs_triangle_spv.h" #include "gpu_blit_shaders/gpu_blit_fs_triangle_spv.h"
#include "graphics/host_gpu/graphicContext.h" #include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/renderer/commandScheduler.h" #include "graphics/host_gpu/renderer/commandScheduler.h"
#include "graphics/host_gpu/renderer/image.h" #include "graphics/host_gpu/renderer/image/image.h"
#include "graphics/host_gpu/renderer/renderTarget.h" #include "graphics/host_gpu/renderer/renderTarget.h"
#include <algorithm> #include <algorithm>
@@ -1,11 +1,11 @@
#include "graphics/host_gpu/renderer/image.h" #include "graphics/host_gpu/renderer/image/image.h"
#include "common/assert.h" #include "common/assert.h"
#include "common/profiler.h" #include "common/profiler.h"
#include "graphics/host_gpu/renderer/cache/streamBuffer.h"
#include "graphics/host_gpu/renderer/commandScheduler.h" #include "graphics/host_gpu/renderer/commandScheduler.h"
#include "graphics/host_gpu/renderer/imageView.h" #include "graphics/host_gpu/renderer/image/imageView.h"
#include "graphics/host_gpu/renderer/renderTarget.h" #include "graphics/host_gpu/renderer/renderTarget.h"
#include "graphics/host_gpu/renderer/streamBuffer.h"
#include "kernel/memory.h" #include "kernel/memory.h"
#include <algorithm> #include <algorithm>
@@ -99,9 +99,9 @@ vk::ImageAspectFlags Image::FullAspectMask(vk::Format format) noexcept {
} }
} }
Image::Barriers Image::GetBarriers(vk::ImageLayout destination_layout, Image::Barriers Image::GetBarriers(vk::ImageLayout destination_layout,
vk::AccessFlags2 destination_access, vk::AccessFlags2 destination_access,
vk::PipelineStageFlags2 destination_stage, vk::PipelineStageFlags2 destination_stage,
std::optional<ImageSubresourceRange> range) { std::optional<ImageSubresourceRange> range) {
auto& state = backing.state; auto& state = backing.state;
auto& subresource_states = backing.subresource_states; auto& subresource_states = backing.subresource_states;
@@ -130,25 +130,25 @@ Image::Barriers Image::GetBarriers(vk::ImageLayout destination_layout,
constexpr auto write_access = vk::AccessFlagBits2::eTransferWrite | constexpr auto write_access = vk::AccessFlagBits2::eTransferWrite |
vk::AccessFlagBits2::eShaderWrite | vk::AccessFlagBits2::eShaderWrite |
vk::AccessFlagBits2::eMemoryWrite; vk::AccessFlagBits2::eMemoryWrite;
const bool repeated_write = const bool repeated_write =
static_cast<bool>(subresource_state.access_mask & write_access); static_cast<bool>(subresource_state.access_mask & write_access);
if (subresource_state.layout != destination_layout || if (subresource_state.layout != destination_layout ||
subresource_state.access_mask != destination_access || repeated_write) { subresource_state.access_mask != destination_access || repeated_write) {
vk::ImageMemoryBarrier2 barrier {}; vk::ImageMemoryBarrier2 barrier {};
barrier.srcStageMask = subresource_state.pl_stage; barrier.srcStageMask = subresource_state.pl_stage;
barrier.srcAccessMask = subresource_state.access_mask; barrier.srcAccessMask = subresource_state.access_mask;
barrier.dstStageMask = destination_stage; barrier.dstStageMask = destination_stage;
barrier.dstAccessMask = destination_access; barrier.dstAccessMask = destination_access;
barrier.oldLayout = subresource_state.layout; barrier.oldLayout = subresource_state.layout;
barrier.newLayout = destination_layout; barrier.newLayout = destination_layout;
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.image = backing.image; barrier.image = backing.image;
barrier.subresourceRange.aspectMask = FullAspectMask(backing.format); barrier.subresourceRange.aspectMask = FullAspectMask(backing.format);
barrier.subresourceRange.baseMipLevel = level; barrier.subresourceRange.baseMipLevel = level;
barrier.subresourceRange.levelCount = 1; barrier.subresourceRange.levelCount = 1;
barrier.subresourceRange.baseArrayLayer = layer; barrier.subresourceRange.baseArrayLayer = layer;
barrier.subresourceRange.layerCount = 1; barrier.subresourceRange.layerCount = 1;
barriers.push_back(barrier); barriers.push_back(barrier);
subresource_state = {destination_stage, destination_access, destination_layout}; subresource_state = {destination_stage, destination_access, destination_layout};
} }
@@ -159,10 +159,10 @@ Image::Barriers Image::GetBarriers(vk::ImageLayout destination_layout,
subresource_states.clear(); subresource_states.clear();
} }
} else { } else {
constexpr auto write_access = vk::AccessFlagBits2::eTransferWrite | constexpr auto write_access = vk::AccessFlagBits2::eTransferWrite |
vk::AccessFlagBits2::eShaderWrite | vk::AccessFlagBits2::eShaderWrite |
vk::AccessFlagBits2::eMemoryWrite; vk::AccessFlagBits2::eMemoryWrite;
const bool repeated_write = static_cast<bool>(state.access_mask & write_access); const bool repeated_write = static_cast<bool>(state.access_mask & write_access);
if (state.layout == destination_layout && state.access_mask == destination_access && if (state.layout == destination_layout && state.access_mask == destination_access &&
!repeated_write) { !repeated_write) {
return {}; return {};
@@ -191,8 +191,7 @@ Image::Barriers Image::GetBarriers(vk::ImageLayout destination_layout,
} }
void Image::Transit(vk::ImageLayout destination_layout, vk::AccessFlags2 destination_access, void Image::Transit(vk::ImageLayout destination_layout, vk::AccessFlags2 destination_access,
std::optional<ImageSubresourceRange> range, std::optional<ImageSubresourceRange> range, vk::CommandBuffer command_buffer) {
vk::CommandBuffer command_buffer) {
const auto transfer_access = const auto transfer_access =
vk::AccessFlagBits2::eTransferRead | vk::AccessFlagBits2::eTransferWrite; vk::AccessFlagBits2::eTransferRead | vk::AccessFlagBits2::eTransferWrite;
vk::PipelineStageFlags2 destination_stage {}; vk::PipelineStageFlags2 destination_stage {};
@@ -201,8 +200,8 @@ void Image::Transit(vk::ImageLayout destination_layout, vk::AccessFlags2 destina
} }
if (!destination_access || if (!destination_access ||
static_cast<bool>(destination_access & ~vk::AccessFlags2 {transfer_access})) { static_cast<bool>(destination_access & ~vk::AccessFlags2 {transfer_access})) {
destination_stage |= vk::PipelineStageFlagBits2::eAllGraphics | destination_stage |=
vk::PipelineStageFlagBits2::eComputeShader; vk::PipelineStageFlagBits2::eAllGraphics | vk::PipelineStageFlagBits2::eComputeShader;
} }
const auto barriers = const auto barriers =
GetBarriers(destination_layout, destination_access, destination_stage, range); GetBarriers(destination_layout, destination_access, destination_stage, range);
@@ -218,10 +217,9 @@ void Image::Transit(vk::ImageLayout destination_layout, vk::AccessFlags2 destina
command_buffer.pipelineBarrier2(dependency); command_buffer.pipelineBarrier2(dependency);
} }
void Image::Upload(std::span<const vk::BufferImageCopy> copies, vk::Buffer buffer, void Image::Upload(std::span<const vk::BufferImageCopy> copies, vk::Buffer buffer, uint64_t offset,
uint64_t offset, uint64_t size) { uint64_t size) {
EXIT_IF(m_scheduler == nullptr || copies.empty() || buffer == nullptr || EXIT_IF(m_scheduler == nullptr || copies.empty() || buffer == nullptr || size == 0);
size == 0);
m_scheduler->EndRendering(); m_scheduler->EndRendering();
vk::BufferMemoryBarrier2 buffer_barrier {}; vk::BufferMemoryBarrier2 buffer_barrier {};
buffer_barrier.srcStageMask = vk::PipelineStageFlagBits2::eAllCommands; buffer_barrier.srcStageMask = vk::PipelineStageFlagBits2::eAllCommands;
@@ -234,16 +232,15 @@ void Image::Upload(std::span<const vk::BufferImageCopy> copies, vk::Buffer buffe
buffer_barrier.offset = offset; buffer_barrier.offset = offset;
buffer_barrier.size = size; buffer_barrier.size = size;
const auto image_barriers = const auto image_barriers =
GetBarriers(vk::ImageLayout::eTransferDstOptimal, GetBarriers(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits2::eTransferWrite,
vk::AccessFlagBits2::eTransferWrite,
vk::PipelineStageFlagBits2::eCopy, {}); vk::PipelineStageFlagBits2::eCopy, {});
vk::DependencyInfo dependency {}; vk::DependencyInfo dependency {};
dependency.dependencyFlags = vk::DependencyFlagBits::eByRegion; dependency.dependencyFlags = vk::DependencyFlagBits::eByRegion;
dependency.bufferMemoryBarrierCount = 1; dependency.bufferMemoryBarrierCount = 1;
dependency.pBufferMemoryBarriers = &buffer_barrier; dependency.pBufferMemoryBarriers = &buffer_barrier;
dependency.imageMemoryBarrierCount = static_cast<uint32_t>(image_barriers.size()); dependency.imageMemoryBarrierCount = static_cast<uint32_t>(image_barriers.size());
dependency.pImageMemoryBarriers = image_barriers.data(); dependency.pImageMemoryBarriers = image_barriers.data();
auto command = m_scheduler->Current().Handle(); auto command = m_scheduler->Current().Handle();
command.pipelineBarrier2(dependency); command.pipelineBarrier2(dependency);
command.copyBufferToImage(buffer, backing.image, vk::ImageLayout::eTransferDstOptimal, command.copyBufferToImage(buffer, backing.image, vk::ImageLayout::eTransferDstOptimal,
static_cast<uint32_t>(copies.size()), copies.data()); static_cast<uint32_t>(copies.size()), copies.data());
@@ -256,8 +253,7 @@ void Image::Upload(std::span<const vk::BufferImageCopy> copies, vk::Buffer buffe
dependency.pImageMemoryBarriers = nullptr; dependency.pImageMemoryBarriers = nullptr;
command.pipelineBarrier2(dependency); command.pipelineBarrier2(dependency);
Transit(vk::ImageLayout::eGeneral, Transit(vk::ImageLayout::eGeneral,
vk::AccessFlagBits2::eShaderRead | vk::AccessFlagBits2::eTransferRead, {}, vk::AccessFlagBits2::eShaderRead | vk::AccessFlagBits2::eTransferRead, {}, command);
command);
} }
void Image::Download(std::span<const vk::BufferImageCopy> copies, vk::Buffer buffer, void Image::Download(std::span<const vk::BufferImageCopy> copies, vk::Buffer buffer,
@@ -265,7 +261,7 @@ void Image::Download(std::span<const vk::BufferImageCopy> copies, vk::Buffer buf
EXIT_IF(m_scheduler == nullptr || copies.empty() || buffer == nullptr || size == 0); EXIT_IF(m_scheduler == nullptr || copies.empty() || buffer == nullptr || size == 0);
m_scheduler->EndRendering(); m_scheduler->EndRendering();
vk::BufferMemoryBarrier2 buffer_barrier {}; vk::BufferMemoryBarrier2 buffer_barrier {};
buffer_barrier.srcStageMask = vk::PipelineStageFlagBits2::eAllCommands; buffer_barrier.srcStageMask = vk::PipelineStageFlagBits2::eAllCommands;
buffer_barrier.srcAccessMask = buffer_barrier.srcAccessMask =
vk::AccessFlagBits2::eMemoryRead | vk::AccessFlagBits2::eMemoryWrite; vk::AccessFlagBits2::eMemoryRead | vk::AccessFlagBits2::eMemoryWrite;
buffer_barrier.dstStageMask = vk::PipelineStageFlagBits2::eCopy; buffer_barrier.dstStageMask = vk::PipelineStageFlagBits2::eCopy;
@@ -276,16 +272,15 @@ void Image::Download(std::span<const vk::BufferImageCopy> copies, vk::Buffer buf
buffer_barrier.offset = offset; buffer_barrier.offset = offset;
buffer_barrier.size = size; buffer_barrier.size = size;
const auto image_barriers = const auto image_barriers =
GetBarriers(vk::ImageLayout::eTransferSrcOptimal, GetBarriers(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits2::eTransferRead,
vk::AccessFlagBits2::eTransferRead,
vk::PipelineStageFlagBits2::eCopy, {}); vk::PipelineStageFlagBits2::eCopy, {});
vk::DependencyInfo dependency {}; vk::DependencyInfo dependency {};
dependency.dependencyFlags = vk::DependencyFlagBits::eByRegion; dependency.dependencyFlags = vk::DependencyFlagBits::eByRegion;
dependency.bufferMemoryBarrierCount = 1; dependency.bufferMemoryBarrierCount = 1;
dependency.pBufferMemoryBarriers = &buffer_barrier; dependency.pBufferMemoryBarriers = &buffer_barrier;
dependency.imageMemoryBarrierCount = static_cast<uint32_t>(image_barriers.size()); dependency.imageMemoryBarrierCount = static_cast<uint32_t>(image_barriers.size());
dependency.pImageMemoryBarriers = image_barriers.data(); dependency.pImageMemoryBarriers = image_barriers.data();
auto command = m_scheduler->Current().Handle(); auto command = m_scheduler->Current().Handle();
command.pipelineBarrier2(dependency); command.pipelineBarrier2(dependency);
command.copyImageToBuffer(backing.image, vk::ImageLayout::eTransferSrcOptimal, buffer, command.copyImageToBuffer(backing.image, vk::ImageLayout::eTransferSrcOptimal, buffer,
static_cast<uint32_t>(copies.size()), copies.data()); static_cast<uint32_t>(copies.size()), copies.data());
@@ -299,11 +294,11 @@ void Image::Download(std::span<const vk::BufferImageCopy> copies, vk::Buffer buf
command.pipelineBarrier2(dependency); command.pipelineBarrier2(dependency);
} }
std::pair<uint32_t, uint32_t> std::pair<uint32_t, uint32_t> Image::SanitizeCopyLayers(const Image& source,
Image::SanitizeCopyLayers(const Image& source, const Image& destination, uint32_t depth) { const Image& destination, uint32_t depth) {
const auto source_type = source.backing.image_type; const auto source_type = source.backing.image_type;
const auto destination_type = destination.backing.image_type; const auto destination_type = destination.backing.image_type;
uint32_t source_layers = source.backing.layers; uint32_t source_layers = source.backing.layers;
uint32_t destination_layers = destination.backing.layers; uint32_t destination_layers = destination.backing.layers;
if (source_type == vk::ImageType::e3D) { if (source_type == vk::ImageType::e3D) {
source_layers = 1; source_layers = 1;
@@ -312,13 +307,10 @@ Image::SanitizeCopyLayers(const Image& source, const Image& destination, uint32_
destination_layers = 1; destination_layers = 1;
} }
if (source_type == destination_type) { if (source_type == destination_type) {
source_layers = destination_layers = source_layers = destination_layers = std::min(source_layers, destination_layers);
std::min(source_layers, destination_layers); } else if (source_type == vk::ImageType::e2D && destination_type == vk::ImageType::e3D) {
} else if (source_type == vk::ImageType::e2D &&
destination_type == vk::ImageType::e3D) {
source_layers = depth; source_layers = depth;
} else if (source_type == vk::ImageType::e3D && } else if (source_type == vk::ImageType::e3D && destination_type == vk::ImageType::e2D) {
destination_type == vk::ImageType::e2D) {
destination_layers = depth; destination_layers = depth;
} }
return {source_layers, destination_layers}; return {source_layers, destination_layers};
@@ -327,12 +319,11 @@ Image::SanitizeCopyLayers(const Image& source, const Image& destination, uint32_
void Image::CopyImage(Image& source) { void Image::CopyImage(Image& source) {
EXIT_IF(m_scheduler == nullptr || source.backing.samples != backing.samples); EXIT_IF(m_scheduler == nullptr || source.backing.samples != backing.samples);
m_scheduler->EndRendering(); m_scheduler->EndRendering();
const uint32_t levels = const uint32_t levels = std::min(source.backing.mip_levels, backing.mip_levels);
std::min(source.backing.mip_levels, backing.mip_levels);
const uint32_t base_depth = backing.image_type == vk::ImageType::e3D const uint32_t base_depth = backing.image_type == vk::ImageType::e3D
? backing.extent.depth ? backing.extent.depth
: source.backing.extent.depth; : source.backing.extent.depth;
const auto source_aspect = const auto source_aspect =
FullAspectMask(source.backing.format) & ~vk::ImageAspectFlagBits::eStencil; FullAspectMask(source.backing.format) & ~vk::ImageAspectFlagBits::eStencil;
const auto destination_aspect = const auto destination_aspect =
FullAspectMask(backing.format) & ~vk::ImageAspectFlagBits::eStencil; FullAspectMask(backing.format) & ~vk::ImageAspectFlagBits::eStencil;
@@ -342,8 +333,7 @@ void Image::CopyImage(Image& source) {
const auto width = std::max(source.backing.extent.width >> level, 1u); const auto width = std::max(source.backing.extent.width >> level, 1u);
const auto height = std::max(source.backing.extent.height >> level, 1u); const auto height = std::max(source.backing.extent.height >> level, 1u);
const auto depth = std::max(base_depth >> level, 1u); const auto depth = std::max(base_depth >> level, 1u);
const auto [source_layers, destination_layers] = const auto [source_layers, destination_layers] = SanitizeCopyLayers(source, *this, depth);
SanitizeCopyLayers(source, *this, depth);
vk::ImageCopy copy {}; vk::ImageCopy copy {};
copy.srcSubresource = {source_aspect, level, 0, 1}; copy.srcSubresource = {source_aspect, level, 0, 1};
copy.dstSubresource = {destination_aspect, level, 0, 1}; copy.dstSubresource = {destination_aspect, level, 0, 1};
@@ -351,8 +341,7 @@ void Image::CopyImage(Image& source) {
if (source.backing.image_type == vk::ImageType::e3D) { if (source.backing.image_type == vk::ImageType::e3D) {
copy.extent = {width, height, depth}; copy.extent = {width, height, depth};
} else { } else {
copy.srcSubresource.layerCount = copy.srcSubresource.layerCount = std::min(source_layers, destination_layers);
std::min(source_layers, destination_layers);
copy.dstSubresource.layerCount = copy.srcSubresource.layerCount; copy.dstSubresource.layerCount = copy.srcSubresource.layerCount;
copy.extent = {width, height, 1}; copy.extent = {width, height, 1};
} }
@@ -369,34 +358,30 @@ void Image::CopyImage(Image& source) {
return; return;
} }
auto command = m_scheduler->Current().Handle(); auto command = m_scheduler->Current().Handle();
source.Transit(vk::ImageLayout::eTransferSrcOptimal, source.Transit(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits2::eTransferRead, {},
vk::AccessFlagBits2::eTransferRead, {}, command); command);
Transit(vk::ImageLayout::eTransferDstOptimal, Transit(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits2::eTransferWrite, {}, command);
vk::AccessFlagBits2::eTransferWrite, {}, command); command.copyImage(source.backing.image, vk::ImageLayout::eTransferSrcOptimal, backing.image,
command.copyImage(source.backing.image, vk::ImageLayout::eTransferSrcOptimal, vk::ImageLayout::eTransferDstOptimal, static_cast<uint32_t>(copies.size()),
backing.image, vk::ImageLayout::eTransferDstOptimal, copies.data());
static_cast<uint32_t>(copies.size()), copies.data());
Transit(vk::ImageLayout::eGeneral, Transit(vk::ImageLayout::eGeneral,
vk::AccessFlagBits2::eShaderRead | vk::AccessFlagBits2::eTransferRead, {}, vk::AccessFlagBits2::eShaderRead | vk::AccessFlagBits2::eTransferRead, {}, command);
command);
} }
void Image::Resolve(Image& source, const ImageSubresourceRange& source_range, void Image::Resolve(Image& source, const ImageSubresourceRange& source_range,
const ImageSubresourceRange& destination_range) { const ImageSubresourceRange& destination_range) {
EXIT_IF(m_scheduler == nullptr || backing.samples != 1 || EXIT_IF(m_scheduler == nullptr || backing.samples != 1 ||
source.backing.image_type != vk::ImageType::e2D || source.backing.image_type != vk::ImageType::e2D ||
backing.image_type != vk::ImageType::e2D || backing.image_type != vk::ImageType::e2D || source_range.level_count != 1 ||
source_range.level_count != 1 || destination_range.level_count != 1 || destination_range.level_count != 1 ||
source_range.base_level >= source.backing.mip_levels || source_range.base_level >= source.backing.mip_levels ||
destination_range.base_level >= backing.mip_levels || destination_range.base_level >= backing.mip_levels ||
source_range.base_layer >= source.backing.layers || source_range.base_layer >= source.backing.layers ||
destination_range.base_layer >= backing.layers); destination_range.base_layer >= backing.layers);
const auto layers = std::min( const auto layers = std::min({source_range.layer_count, destination_range.layer_count,
{source_range.layer_count, destination_range.layer_count, source.backing.layers - source_range.base_layer,
source.backing.layers - source_range.base_layer, backing.layers - destination_range.base_layer});
backing.layers - destination_range.base_layer}); const auto source_width = std::max(source.backing.extent.width >> source_range.base_level, 1u);
const auto source_width =
std::max(source.backing.extent.width >> source_range.base_level, 1u);
const auto source_height = const auto source_height =
std::max(source.backing.extent.height >> source_range.base_level, 1u); std::max(source.backing.extent.height >> source_range.base_level, 1u);
const auto destination_width = const auto destination_width =
@@ -404,43 +389,40 @@ void Image::Resolve(Image& source, const ImageSubresourceRange& source_range,
const auto destination_height = const auto destination_height =
std::max(backing.extent.height >> destination_range.base_level, 1u); std::max(backing.extent.height >> destination_range.base_level, 1u);
const bool copy = source.backing.samples == 1; const bool copy = source.backing.samples == 1;
EXIT_IF(layers == 0 || info.extent.width > source_width || EXIT_IF(layers == 0 || info.extent.width > source_width || info.extent.height > source_height ||
info.extent.height > source_height || info.extent.width > destination_width || info.extent.width > destination_width || info.extent.height > destination_height ||
info.extent.height > destination_height ||
(copy ? !ImageViewOps::FormatsCompatible(source.backing.format, backing.format) (copy ? !ImageViewOps::FormatsCompatible(source.backing.format, backing.format)
: source.backing.format != backing.format)); : source.backing.format != backing.format));
auto resolved_source_range = source_range; auto resolved_source_range = source_range;
auto resolved_destination_range = destination_range; auto resolved_destination_range = destination_range;
resolved_source_range.layer_count = layers; resolved_source_range.layer_count = layers;
resolved_destination_range.layer_count = layers; resolved_destination_range.layer_count = layers;
const vk::Extent3D resolve_extent {info.extent.width, info.extent.height, 1}; const vk::Extent3D resolve_extent {info.extent.width, info.extent.height, 1};
m_scheduler->EndRendering(); m_scheduler->EndRendering();
auto command = m_scheduler->Current().Handle(); auto command = m_scheduler->Current().Handle();
source.Transit(vk::ImageLayout::eTransferSrcOptimal, source.Transit(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits2::eTransferRead,
vk::AccessFlagBits2::eTransferRead, resolved_source_range, command); resolved_source_range, command);
Transit(vk::ImageLayout::eTransferDstOptimal, Transit(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits2::eTransferWrite,
vk::AccessFlagBits2::eTransferWrite, resolved_destination_range, command); resolved_destination_range, command);
if (copy) { if (copy) {
vk::ImageCopy region {}; vk::ImageCopy region {};
region.srcSubresource = {vk::ImageAspectFlagBits::eColor, region.srcSubresource = {vk::ImageAspectFlagBits::eColor, resolved_source_range.base_level,
resolved_source_range.base_level,
resolved_source_range.base_layer, layers}; resolved_source_range.base_layer, layers};
region.dstSubresource = {vk::ImageAspectFlagBits::eColor, region.dstSubresource = {vk::ImageAspectFlagBits::eColor,
resolved_destination_range.base_level, resolved_destination_range.base_level,
resolved_destination_range.base_layer, layers}; resolved_destination_range.base_layer, layers};
region.extent = resolve_extent; region.extent = resolve_extent;
command.copyImage(source.backing.image, vk::ImageLayout::eTransferSrcOptimal, command.copyImage(source.backing.image, vk::ImageLayout::eTransferSrcOptimal, backing.image,
backing.image, vk::ImageLayout::eTransferDstOptimal, region); vk::ImageLayout::eTransferDstOptimal, region);
} else { } else {
vk::ImageResolve region {}; vk::ImageResolve region {};
region.srcSubresource = {vk::ImageAspectFlagBits::eColor, region.srcSubresource = {vk::ImageAspectFlagBits::eColor, resolved_source_range.base_level,
resolved_source_range.base_level,
resolved_source_range.base_layer, layers}; resolved_source_range.base_layer, layers};
region.dstSubresource = {vk::ImageAspectFlagBits::eColor, region.dstSubresource = {vk::ImageAspectFlagBits::eColor,
resolved_destination_range.base_level, resolved_destination_range.base_level,
resolved_destination_range.base_layer, layers}; resolved_destination_range.base_layer, layers};
region.extent = resolve_extent; region.extent = resolve_extent;
command.resolveImage(source.backing.image, vk::ImageLayout::eTransferSrcOptimal, command.resolveImage(source.backing.image, vk::ImageLayout::eTransferSrcOptimal,
backing.image, vk::ImageLayout::eTransferDstOptimal, region); backing.image, vk::ImageLayout::eTransferDstOptimal, region);
} }
@@ -454,22 +436,21 @@ uint32_t Image::CopyRows(uint64_t row_size, uint32_t rows, uint64_t capacity) no
} }
void Image::CopyImageWithBuffer(Image& source, Buffer& buffer) { void Image::CopyImageWithBuffer(Image& source, Buffer& buffer) {
EXIT_IF(m_scheduler == nullptr || buffer.Handle() == nullptr || EXIT_IF(m_scheduler == nullptr || buffer.Handle() == nullptr || source.backing.samples != 1 ||
source.backing.samples != 1 || backing.samples != 1); backing.samples != 1);
m_scheduler->EndRendering(); m_scheduler->EndRendering();
const uint32_t levels = const uint32_t levels = std::min(source.backing.mip_levels, backing.mip_levels);
std::min(source.backing.mip_levels, backing.mip_levels); const auto source_aspect =
const auto source_aspect =
FullAspectMask(source.backing.format) & ~vk::ImageAspectFlagBits::eStencil; FullAspectMask(source.backing.format) & ~vk::ImageAspectFlagBits::eStencil;
const auto destination_aspect = const auto destination_aspect =
FullAspectMask(backing.format) & ~vk::ImageAspectFlagBits::eStencil; FullAspectMask(backing.format) & ~vk::ImageAspectFlagBits::eStencil;
const auto source_bytes = DepthAspectTransferBytes(source.backing.format) != 0 const auto source_bytes = DepthAspectTransferBytes(source.backing.format) != 0
? DepthAspectTransferBytes(source.backing.format) ? DepthAspectTransferBytes(source.backing.format)
: source.info.bytes_per_block; : source.info.bytes_per_block;
const auto destination_bytes = DepthAspectTransferBytes(backing.format) != 0 const auto destination_bytes = DepthAspectTransferBytes(backing.format) != 0
? DepthAspectTransferBytes(backing.format) ? DepthAspectTransferBytes(backing.format)
: info.bytes_per_block; : info.bytes_per_block;
const uint32_t source_block = source.info.IsBlock() ? 4u : 1u; const uint32_t source_block = source.info.IsBlock() ? 4u : 1u;
const uint32_t destination_block = info.IsBlock() ? 4u : 1u; const uint32_t destination_block = info.IsBlock() ? 4u : 1u;
EXIT_IF(levels == 0 || source_bytes == 0 || source_bytes != destination_bytes || EXIT_IF(levels == 0 || source_bytes == 0 || source_bytes != destination_bytes ||
source_block != destination_block); source_block != destination_block);
@@ -484,74 +465,66 @@ void Image::CopyImageWithBuffer(Image& source, Buffer& buffer) {
barrier.buffer = buffer.Handle(); barrier.buffer = buffer.Handle();
barrier.offset = 0; barrier.offset = 0;
vk::DependencyInfo dependency {}; vk::DependencyInfo dependency {};
dependency.dependencyFlags = vk::DependencyFlagBits::eByRegion; dependency.dependencyFlags = vk::DependencyFlagBits::eByRegion;
dependency.bufferMemoryBarrierCount = 1; dependency.bufferMemoryBarrierCount = 1;
dependency.pBufferMemoryBarriers = &barrier; dependency.pBufferMemoryBarriers = &barrier;
auto command = m_scheduler->Current().Handle(); auto command = m_scheduler->Current().Handle();
source.Transit(vk::ImageLayout::eTransferSrcOptimal, source.Transit(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits2::eTransferRead, {},
vk::AccessFlagBits2::eTransferRead, {}, command); command);
Transit(vk::ImageLayout::eTransferDstOptimal, Transit(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits2::eTransferWrite, {}, command);
vk::AccessFlagBits2::eTransferWrite, {}, command);
for (uint32_t level = 0; level < levels; level++) { for (uint32_t level = 0; level < levels; level++) {
const auto width = std::max(source.backing.extent.width >> level, 1u); const auto width = std::max(source.backing.extent.width >> level, 1u);
const auto height = std::max(source.backing.extent.height >> level, 1u); const auto height = std::max(source.backing.extent.height >> level, 1u);
const auto source_depth = source.backing.image_type == vk::ImageType::e3D const auto source_depth = source.backing.image_type == vk::ImageType::e3D
? std::max(source.backing.extent.depth >> level, 1u) ? std::max(source.backing.extent.depth >> level, 1u)
: source.backing.layers; : source.backing.layers;
const auto destination_depth = backing.image_type == vk::ImageType::e3D const auto destination_depth = backing.image_type == vk::ImageType::e3D
? std::max(backing.extent.depth >> level, 1u) ? std::max(backing.extent.depth >> level, 1u)
: backing.layers; : backing.layers;
const auto slices = std::min(source_depth, destination_depth); const auto slices = std::min(source_depth, destination_depth);
const auto block_rows = (height + source_block - 1) / source_block; const auto block_rows = (height + source_block - 1) / source_block;
const auto row_size = const auto row_size =
static_cast<uint64_t>((width + source_block - 1) / source_block) * source_bytes; static_cast<uint64_t>((width + source_block - 1) / source_block) * source_bytes;
const auto rows_per_copy = CopyRows(row_size, block_rows, buffer.Size()); const auto rows_per_copy = CopyRows(row_size, block_rows, buffer.Size());
EXIT_IF(slices == 0 || rows_per_copy == 0); EXIT_IF(slices == 0 || rows_per_copy == 0);
for (uint32_t slice = 0; slice < slices; slice++) { for (uint32_t slice = 0; slice < slices; slice++) {
for (uint32_t block_row = 0; block_row < block_rows; for (uint32_t block_row = 0; block_row < block_rows; block_row += rows_per_copy) {
block_row += rows_per_copy) { const auto copy_rows = std::min(rows_per_copy, block_rows - block_row);
const auto copy_rows = std::min(rows_per_copy, block_rows - block_row); const auto y = block_row * source_block;
const auto y = block_row * source_block; const auto copy_height = std::min(copy_rows * source_block, height - y);
const auto copy_height = const auto copy_size = row_size * copy_rows;
std::min(copy_rows * source_block, height - y);
const auto copy_size = row_size * copy_rows;
vk::BufferImageCopy source_copy {}; vk::BufferImageCopy source_copy {};
source_copy.imageSubresource = { source_copy.imageSubresource = {
source_aspect, level, source_aspect, level,
source.backing.image_type == vk::ImageType::e3D ? 0u : slice, 1}; source.backing.image_type == vk::ImageType::e3D ? 0u : slice, 1};
source_copy.imageOffset = { source_copy.imageOffset = {0, static_cast<int32_t>(y),
0, static_cast<int32_t>(y), source.backing.image_type == vk::ImageType::e3D
source.backing.image_type == vk::ImageType::e3D ? static_cast<int32_t>(slice)
? static_cast<int32_t>(slice) : 0};
: 0}; source_copy.imageExtent = {width, copy_height, 1};
source_copy.imageExtent = {width, copy_height, 1}; auto destination_copy = source_copy;
auto destination_copy = source_copy;
destination_copy.imageSubresource = { destination_copy.imageSubresource = {
destination_aspect, level, destination_aspect, level,
backing.image_type == vk::ImageType::e3D ? 0u : slice, 1}; backing.image_type == vk::ImageType::e3D ? 0u : slice, 1};
destination_copy.imageOffset.z = destination_copy.imageOffset.z =
backing.image_type == vk::ImageType::e3D backing.image_type == vk::ImageType::e3D ? static_cast<int32_t>(slice) : 0;
? static_cast<int32_t>(slice)
: 0;
barrier.size = copy_size; barrier.size = copy_size;
barrier.srcAccessMask = vk::AccessFlagBits2::eTransferRead; barrier.srcAccessMask = vk::AccessFlagBits2::eTransferRead;
barrier.dstAccessMask = vk::AccessFlagBits2::eTransferWrite; barrier.dstAccessMask = vk::AccessFlagBits2::eTransferWrite;
command.pipelineBarrier2(dependency); command.pipelineBarrier2(dependency);
command.copyImageToBuffer(source.backing.image, command.copyImageToBuffer(source.backing.image,
vk::ImageLayout::eTransferSrcOptimal, vk::ImageLayout::eTransferSrcOptimal, buffer.Handle(),
buffer.Handle(), source_copy); source_copy);
barrier.srcAccessMask = vk::AccessFlagBits2::eTransferWrite; barrier.srcAccessMask = vk::AccessFlagBits2::eTransferWrite;
barrier.dstAccessMask = vk::AccessFlagBits2::eTransferRead; barrier.dstAccessMask = vk::AccessFlagBits2::eTransferRead;
command.pipelineBarrier2(dependency); command.pipelineBarrier2(dependency);
command.copyBufferToImage(buffer.Handle(), backing.image, command.copyBufferToImage(buffer.Handle(), backing.image,
vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eTransferDstOptimal, destination_copy);
destination_copy);
} }
} }
} }
Transit(vk::ImageLayout::eGeneral, Transit(vk::ImageLayout::eGeneral,
vk::AccessFlagBits2::eShaderRead | vk::AccessFlagBits2::eTransferRead, {}, vk::AccessFlagBits2::eShaderRead | vk::AccessFlagBits2::eTransferRead, {}, command);
command);
} }
void Image::CopyMip(Image& source, uint32_t mip, uint32_t layer) { void Image::CopyMip(Image& source, uint32_t mip, uint32_t layer) {
@@ -561,11 +534,9 @@ void Image::CopyMip(Image& source, uint32_t mip, uint32_t layer) {
const auto width = std::max(backing.extent.width >> mip, 1u); const auto width = std::max(backing.extent.width >> mip, 1u);
const auto height = std::max(backing.extent.height >> mip, 1u); const auto height = std::max(backing.extent.height >> mip, 1u);
const auto depth = std::max(backing.extent.depth >> mip, 1u); const auto depth = std::max(backing.extent.depth >> mip, 1u);
EXIT_IF(width != source.backing.extent.width || EXIT_IF(width != source.backing.extent.width || height != source.backing.extent.height);
height != source.backing.extent.height); const auto [source_layers, destination_layers] = SanitizeCopyLayers(source, *this, depth);
const auto [source_layers, destination_layers] = const auto aspects = FullAspectMask(source.backing.format);
SanitizeCopyLayers(source, *this, depth);
const auto aspects = FullAspectMask(source.backing.format);
EXIT_IF(aspects != FullAspectMask(backing.format)); EXIT_IF(aspects != FullAspectMask(backing.format));
std::array<vk::ImageCopy, 2> copies {}; std::array<vk::ImageCopy, 2> copies {};
uint32_t copy_count = 0; uint32_t copy_count = 0;
@@ -580,16 +551,13 @@ void Image::CopyMip(Image& source, uint32_t mip, uint32_t layer) {
copy.extent = {width, height, depth}; copy.extent = {width, height, depth};
} }
auto command = m_scheduler->Current().Handle(); auto command = m_scheduler->Current().Handle();
Transit(vk::ImageLayout::eTransferDstOptimal, Transit(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits2::eTransferWrite, {}, command);
vk::AccessFlagBits2::eTransferWrite, {}, command); source.Transit(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits2::eTransferRead, {},
source.Transit(vk::ImageLayout::eTransferSrcOptimal, command);
vk::AccessFlagBits2::eTransferRead, {}, command); command.copyImage(source.backing.image, vk::ImageLayout::eTransferSrcOptimal, backing.image,
command.copyImage(source.backing.image, vk::ImageLayout::eTransferSrcOptimal, vk::ImageLayout::eTransferDstOptimal, copy_count, copies.data());
backing.image, vk::ImageLayout::eTransferDstOptimal, copy_count,
copies.data());
Transit(vk::ImageLayout::eGeneral, Transit(vk::ImageLayout::eGeneral,
vk::AccessFlagBits2::eShaderRead | vk::AccessFlagBits2::eTransferRead, {}, vk::AccessFlagBits2::eShaderRead | vk::AccessFlagBits2::eTransferRead, {}, command);
command);
} }
namespace ImageOps { namespace ImageOps {
@@ -601,8 +569,7 @@ void Validate(const ImageInfo& info) {
if (info.pixel_format == vk::Format::eUndefined) { if (info.pixel_format == vk::Format::eUndefined) {
const bool metadata_empty = const bool metadata_empty =
info.metadata.range.address == 0 && info.metadata.range.size == 0 && info.metadata.range.address == 0 && info.metadata.range.size == 0 &&
info.metadata.kind == ImageMetadataKind::None && info.metadata.kind == ImageMetadataKind::None && info.metadata.control == 0 &&
info.metadata.control == 0 &&
info.metadata.compression == VideoOutCompression::Uncompressed && info.metadata.compression == VideoOutCompression::Uncompressed &&
!info.metadata.stencil_compressed; !info.metadata.stencil_compressed;
if (info.data.Empty() || info.HasStencil() || !metadata_empty || info.extent.width == 0 || if (info.data.Empty() || info.HasStencil() || !metadata_empty || info.extent.width == 0 ||
@@ -615,9 +582,9 @@ void Validate(const ImageInfo& info) {
} }
if (info.extent.width == 0 || info.extent.height == 0 || info.extent.depth == 0 || if (info.extent.width == 0 || info.extent.height == 0 || info.extent.depth == 0 ||
info.resources.levels == 0 || info.resources.levels == 0 || info.resources.levels > info.mip_layout.size() ||
info.resources.levels > info.mip_layout.size() || info.resources.layers == 0 || info.resources.layers == 0 || info.samples == 0 ||
info.samples == 0 || vulkan_sample_count(info.samples) == vk::SampleCountFlagBits {} || vulkan_sample_count(info.samples) == vk::SampleCountFlagBits {} ||
info.bytes_per_block == 0 || (info.data.address != 0 && info.pitch == 0)) { info.bytes_per_block == 0 || (info.data.address != 0 && info.pitch == 0)) {
EXIT("invalid image geometry or format\n"); EXIT("invalid image geometry or format\n");
} }
@@ -688,11 +655,11 @@ uint32_t RenderTargetTransferFormat(uint32_t bytes_per_element) {
} // namespace ImageOps } // namespace ImageOps
Image::Image(GraphicContext& graphics, CommandScheduler& scheduler, Image::Image(GraphicContext& graphics, CommandScheduler& scheduler, const ImageInfo& image_info)
const ImageInfo& image_info)
: info(image_info), m_graphics(&graphics), m_scheduler(&scheduler) { : info(image_info), m_graphics(&graphics), m_scheduler(&scheduler) {
KYTY_PROFILER_FUNCTION(); KYTY_PROFILER_FUNCTION();
ImageOps::Validate(info); ImageOps::Validate(info);
m_cpu_dirty = !info.data.Empty();
if (info.pixel_format == vk::Format::eUndefined) { if (info.pixel_format == vk::Format::eUndefined) {
return; return;
} }
@@ -742,9 +709,9 @@ Image::Image(GraphicContext& graphics, CommandScheduler& scheduler,
} }
uint64_t Image::HashGuestEdges() const { uint64_t Image::HashGuestEdges() const {
constexpr uint64_t page_mask = TRACKER_PAGE_SIZE - 1; constexpr uint64_t page_mask = TRACKER_PAGE_SIZE - 1;
std::array<uint8_t, TRACKER_PAGE_SIZE * 2> bytes {}; std::array<uint8_t, TRACKER_PAGE_SIZE * 2> bytes {};
const auto range = info.data; const auto range = info.data;
const uint64_t head_end = std::min(range.End(), (range.address + page_mask) & ~page_mask); const uint64_t head_end = std::min(range.End(), (range.address + page_mask) & ~page_mask);
const uint64_t tail_begin = std::max(range.address, range.End() & ~page_mask); const uint64_t tail_begin = std::max(range.address, range.End() & ~page_mask);
const uint64_t head_size = head_end - range.address; const uint64_t head_size = head_end - range.address;
@@ -3,7 +3,7 @@
#include "common/assert.h" #include "common/assert.h"
#include "graphics/host_gpu/graphicContext.h" #include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/renderer/imageInfo.h" #include "graphics/host_gpu/renderer/image/imageInfo.h"
#include <compare> #include <compare>
#include <limits> #include <limits>
@@ -66,16 +66,16 @@ public:
[[nodiscard]] vk::ImageView FindView(const ImageViewInfo& view_info); [[nodiscard]] vk::ImageView FindView(const ImageViewInfo& view_info);
void AssociateDepth(ImageId image_id) { depth_id = image_id; } void AssociateDepth(ImageId image_id) { depth_id = image_id; }
using Barriers = std::vector<vk::ImageMemoryBarrier2>; using Barriers = std::vector<vk::ImageMemoryBarrier2>;
[[nodiscard]] Barriers [[nodiscard]] Barriers GetBarriers(vk::ImageLayout destination_layout,
GetBarriers(vk::ImageLayout destination_layout, vk::AccessFlags2 destination_access, vk::AccessFlags2 destination_access,
vk::PipelineStageFlags2 destination_stage, vk::PipelineStageFlags2 destination_stage,
std::optional<ImageSubresourceRange> range); std::optional<ImageSubresourceRange> range);
void Transit(vk::ImageLayout destination_layout, vk::AccessFlags2 destination_access, void Transit(vk::ImageLayout destination_layout, vk::AccessFlags2 destination_access,
std::optional<ImageSubresourceRange> range, vk::CommandBuffer command_buffer); std::optional<ImageSubresourceRange> range, vk::CommandBuffer command_buffer);
void Upload(std::span<const vk::BufferImageCopy> copies, vk::Buffer buffer, void Upload(std::span<const vk::BufferImageCopy> copies, vk::Buffer buffer, uint64_t offset,
uint64_t offset, uint64_t size); uint64_t size);
void Download(std::span<const vk::BufferImageCopy> copies, vk::Buffer buffer, void Download(std::span<const vk::BufferImageCopy> copies, vk::Buffer buffer, uint64_t offset,
uint64_t offset, uint64_t size); uint64_t size);
void CopyImage(Image& source); void CopyImage(Image& source);
void Resolve(Image& source, const ImageSubresourceRange& source_range, void Resolve(Image& source, const ImageSubresourceRange& source_range,
const ImageSubresourceRange& destination_range); const ImageSubresourceRange& destination_range);
@@ -84,8 +84,8 @@ public:
void InvalidateCpuWrite(uint64_t vaddr, uint64_t size) { void InvalidateCpuWrite(uint64_t vaddr, uint64_t size) {
if (ImageRangeOverlaps(info.data.address, info.data.size, vaddr, size)) { if (ImageRangeOverlaps(info.data.address, info.data.size, vaddr, size)) {
m_cpu_dirty = true; m_cpu_dirty = true;
m_maybe_cpu_dirty = false; m_maybe_cpu_dirty = false;
m_maybe_hash_valid = false; m_maybe_hash_valid = false;
} else if (ImagePageRangesOverlap(info.data.address, info.data.size, vaddr, size)) { } else if (ImagePageRangesOverlap(info.data.address, info.data.size, vaddr, size)) {
m_maybe_cpu_dirty = true; m_maybe_cpu_dirty = true;
@@ -95,6 +95,11 @@ public:
[[nodiscard]] bool IsCpuDirty() const { return m_cpu_dirty || m_maybe_cpu_dirty; } [[nodiscard]] bool IsCpuDirty() const { return m_cpu_dirty || m_maybe_cpu_dirty; }
[[nodiscard]] bool IsDefinitelyCpuDirty() const { return m_cpu_dirty; } [[nodiscard]] bool IsDefinitelyCpuDirty() const { return m_cpu_dirty; }
[[nodiscard]] bool IsMaybeCpuDirty() const { return m_maybe_cpu_dirty; } [[nodiscard]] bool IsMaybeCpuDirty() const { return m_maybe_cpu_dirty; }
void MarkMaybeCpuDirty() {
if (!m_cpu_dirty) {
m_maybe_cpu_dirty = true;
}
}
[[nodiscard]] bool NeedsMaybeCpuHash() const { [[nodiscard]] bool NeedsMaybeCpuHash() const {
return m_maybe_cpu_dirty && !m_maybe_hash_valid; return m_maybe_cpu_dirty && !m_maybe_hash_valid;
} }
@@ -102,14 +107,14 @@ public:
if (!NeedsMaybeCpuHash()) { if (!NeedsMaybeCpuHash()) {
EXIT("image cannot initialize maybe-dirty hash\n"); EXIT("image cannot initialize maybe-dirty hash\n");
} }
m_maybe_cpu_hash = hash; m_maybe_cpu_hash = hash;
m_maybe_hash_valid = true; m_maybe_hash_valid = true;
} }
[[nodiscard]] bool ResolveMaybeCpuHash(uint64_t hash) { [[nodiscard]] bool ResolveMaybeCpuHash(uint64_t hash) {
if (!m_maybe_cpu_dirty || !m_maybe_hash_valid || m_cpu_dirty) { if (!m_maybe_cpu_dirty || !m_maybe_hash_valid || m_cpu_dirty) {
EXIT("image cannot resolve maybe-dirty hash\n"); EXIT("image cannot resolve maybe-dirty hash\n");
} }
m_maybe_cpu_dirty = false; m_maybe_cpu_dirty = false;
m_maybe_hash_valid = false; m_maybe_hash_valid = false;
m_cpu_dirty |= hash != m_maybe_cpu_hash; m_cpu_dirty |= hash != m_maybe_cpu_hash;
return m_cpu_dirty; return m_cpu_dirty;
@@ -125,14 +130,15 @@ public:
} }
[[nodiscard]] bool IsGpuModified() const noexcept { return m_gpu_modified; } [[nodiscard]] bool IsGpuModified() const noexcept { return m_gpu_modified; }
void MarkGpuModified() noexcept { m_gpu_modified = true; } void MarkGpuModified() noexcept { m_gpu_modified = true; }
void ClearGpuModified() noexcept { m_gpu_modified = false; } void ClearGpuModified() noexcept { m_gpu_modified = false; }
[[nodiscard]] bool IsBufferModified() const noexcept { return m_buffer_modified; } [[nodiscard]] bool IsBufferModified() const noexcept { return m_buffer_modified; }
void MarkBufferModified() noexcept { m_buffer_modified = true; } void MarkBufferModified() noexcept { m_buffer_modified = true; }
void ClearBufferModified() noexcept { m_buffer_modified = false; } void ClearBufferModified() noexcept { m_buffer_modified = false; }
[[nodiscard]] bool Overlaps(uint64_t address, uint64_t size, bool pages = false) const noexcept { [[nodiscard]] bool Overlaps(uint64_t address, uint64_t size,
bool pages = false) const noexcept {
return pages ? ImagePageRangesOverlap(info.data.address, info.data.size, address, size) return pages ? ImagePageRangesOverlap(info.data.address, info.data.size, address, size)
: ImageRangeOverlaps(info.data.address, info.data.size, address, size); : ImageRangeOverlaps(info.data.address, info.data.size, address, size);
} }
@@ -142,38 +148,41 @@ public:
[[nodiscard]] bool SafeToDownload() const noexcept { [[nodiscard]] bool SafeToDownload() const noexcept {
return IsGpuModified() && !IsBufferModified() && !IsCpuDirty(); return IsGpuModified() && !IsBufferModified() && !IsCpuDirty();
} }
[[nodiscard]] bool IsTracked() const noexcept { return track_addr != 0 && track_addr_end != 0; }
[[nodiscard]] uint64_t AccountedSize() const noexcept { [[nodiscard]] uint64_t AccountedSize() const noexcept {
return backing.image == nullptr ? 0 : (info.data.size + 1023) & ~uint64_t {1023}; return backing.image == nullptr ? 0 : (info.data.size + 1023) & ~uint64_t {1023};
} }
[[nodiscard]] uint64_t HashGuestEdges() const; [[nodiscard]] uint64_t HashGuestEdges() const;
ImageInfo info; ImageInfo info;
VulkanImage backing; VulkanImage backing;
ImageViewCache views; ImageViewCache views;
ImageUsage usage; ImageUsage usage;
ImageBinding binding; ImageBinding binding;
bool registered = false; bool registered = false;
ImageId depth_id {}; uint64_t track_addr = 0;
uint64_t tick_accessed_last = 0; uint64_t track_addr_end = 0;
size_t lru_id = 0; ImageId depth_id {};
uint64_t tick_accessed_last = 0;
size_t lru_id = 0;
private: private:
friend struct ImageTestAccess; friend struct ImageTestAccess;
[[nodiscard]] static vk::ImageAspectFlags FullAspectMask(vk::Format format) noexcept; [[nodiscard]] static vk::ImageAspectFlags FullAspectMask(vk::Format format) noexcept;
[[nodiscard]] static uint32_t CopyRows(uint64_t row_size, uint32_t rows, [[nodiscard]] static uint32_t CopyRows(uint64_t row_size, uint32_t rows,
uint64_t capacity) noexcept; uint64_t capacity) noexcept;
[[nodiscard]] static std::pair<uint32_t, uint32_t> [[nodiscard]] static std::pair<uint32_t, uint32_t>
SanitizeCopyLayers(const Image& source, const Image& destination, uint32_t depth); SanitizeCopyLayers(const Image& source, const Image& destination, uint32_t depth);
GraphicContext* m_graphics = nullptr; GraphicContext* m_graphics = nullptr;
CommandScheduler* m_scheduler = nullptr; CommandScheduler* m_scheduler = nullptr;
uint64_t m_maybe_cpu_hash = 0; uint64_t m_maybe_cpu_hash = 0;
bool m_cpu_dirty = false; bool m_cpu_dirty = false;
bool m_maybe_cpu_dirty = false; bool m_maybe_cpu_dirty = false;
bool m_maybe_hash_valid = false; bool m_maybe_hash_valid = false;
bool m_gpu_modified = false; bool m_gpu_modified = false;
bool m_buffer_modified = false; bool m_buffer_modified = false;
}; };
namespace ImageOps { namespace ImageOps {
@@ -1,8 +1,8 @@
#include "graphics/host_gpu/renderer/imageView.h" #include "graphics/host_gpu/renderer/image/imageView.h"
#include "common/assert.h" #include "common/assert.h"
#include "graphics/host_gpu/graphicContext.h" #include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/renderer/image.h" #include "graphics/host_gpu/renderer/image/image.h"
#include <mutex> #include <mutex>
@@ -2,8 +2,8 @@
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_IMAGEVIEW_H_ #define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_IMAGEVIEW_H_
#include "common/assert.h" #include "common/assert.h"
#include "graphics/host_gpu/renderer/imageInfo.h" #include "graphics/host_gpu/renderer/image/imageInfo.h"
#include "graphics/shader/recompiler/ShaderIR.h" #include "graphics/shader/recompiler/ir/ShaderIR.h"
#include "graphics/shader/shader.h" #include "graphics/shader/shader.h"
namespace Libs::Graphics { namespace Libs::Graphics {
@@ -103,10 +103,7 @@ IsSupportedSampledDepthUintResource(const ShaderRecompiler::IR::ImageResource& r
inline void ValidateStorageColorView(vk::Format image_format, vk::Format view_format, inline void ValidateStorageColorView(vk::Format image_format, vk::Format view_format,
uint32_t swizzle) noexcept { uint32_t swizzle) noexcept {
const auto srgb_view = SrgbStorageViewFormat(image_format); if (!ImageViewOps::FormatsCompatible(image_format, view_format) ||
const bool srgb_storage_view =
srgb_view != vk::Format::eUndefined && view_format == srgb_view;
if ((image_format != view_format && !srgb_storage_view) ||
!IsValidImageSwizzle(swizzle)) { !IsValidImageSwizzle(swizzle)) {
UnsupportedColorView("storage", image_format, view_format, swizzle); UnsupportedColorView("storage", image_format, view_format, swizzle);
} }
@@ -122,7 +119,10 @@ IsSupportedStorageImageResource(const ShaderRecompiler::IR::ImageResource& resou
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim3D || resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim3D ||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2DArray) && resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2DArray) &&
resource.mip_mode == ShaderRecompiler::IR::ImageMipMode::None && resource.written && resource.mip_mode == ShaderRecompiler::IR::ImageMipMode::None && resource.written &&
!resource.atomic && !resource.depth_compare; (!resource.atomic ||
(resource.kind == ShaderRecompiler::IR::ResourceKind::StorageImageUint &&
resource.read)) &&
!resource.depth_compare;
} }
inline void inline void
@@ -1,9 +1,9 @@
#include "graphics/host_gpu/objects/textureCommon.h" #include "graphics/host_gpu/renderer/image/textureCommon.h"
#include "common/assert.h" #include "common/assert.h"
#include "graphics/guest_gpu/gpu_defs.h" #include "graphics/guest_gpu/gpu_defs.h"
#include "graphics/guest_gpu/gpu_format.h" #include "graphics/guest_gpu/gpu_format.h"
#include "graphics/host_gpu/renderer/tiler.h" #include "graphics/host_gpu/renderer/image/tiler.h"
#include "graphics/host_gpu/vulkanCommon.h" #include "graphics/host_gpu/vulkanCommon.h"
#include <algorithm> #include <algorithm>
@@ -102,6 +102,10 @@ constexpr RenderTargetFormatMapping kRenderTargetFormats[] = {
Prospero::ChannelType::kUNorm, Prospero::ChannelType::kUNorm,
Prospero::ChannelOrder::kStandard, Prospero::ChannelOrder::kStandard,
{vk::Format::eR16G16B16A16Unorm, 8}}, {vk::Format::eR16G16B16A16Unorm, 8}},
{Prospero::ChannelLayout::k16_16_16_16,
Prospero::ChannelType::kUInt,
Prospero::ChannelOrder::kStandard,
{vk::Format::eR16G16B16A16Uint, 8}},
{Prospero::ChannelLayout::k16_16_16_16, {Prospero::ChannelLayout::k16_16_16_16,
Prospero::ChannelType::kFloat, Prospero::ChannelType::kFloat,
Prospero::ChannelOrder::kStandard, Prospero::ChannelOrder::kStandard,
@@ -1,5 +1,5 @@
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_OBJECTS_TEXTURECOMMON_H_ #ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_HOST_GPU_RENDERER_IMAGE_TEXTURECOMMON_H_
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_OBJECTS_TEXTURECOMMON_H_ #define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_HOST_GPU_RENDERER_IMAGE_TEXTURECOMMON_H_
#include "common/abi.h" #include "common/abi.h"
#include "common/common.h" #include "common/common.h"
@@ -51,4 +51,4 @@ bool TextureBuildGpuTileInfos(uint64_t size,
} // namespace Libs::Graphics } // namespace Libs::Graphics
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_OBJECTS_TEXTURECOMMON_H_ */ #endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_HOST_GPU_RENDERER_IMAGE_TEXTURECOMMON_H_ */
@@ -1,4 +1,4 @@
#include "graphics/host_gpu/renderer/tiler.h" #include "graphics/host_gpu/renderer/image/tiler.h"
#include "common/assert.h" #include "common/assert.h"
#include "gpu_tiler_shaders/gpu_tiler_demote_d16_spv.h" #include "gpu_tiler_shaders/gpu_tiler_demote_d16_spv.h"
@@ -15,8 +15,8 @@
#include "gpu_tiler_shaders/gpu_tiler_swap_bgra16_spv.h" #include "gpu_tiler_shaders/gpu_tiler_swap_bgra16_spv.h"
#include "graphics/host_gpu/graphicContext.h" #include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/renderer/commandScheduler.h" #include "graphics/host_gpu/renderer/commandScheduler.h"
#include "graphics/host_gpu/renderer/image.h" #include "graphics/host_gpu/renderer/image/image.h"
#include "graphics/host_gpu/renderer/streamBuffer.h" #include "graphics/host_gpu/renderer/cache/streamBuffer.h"
#include <algorithm> #include <algorithm>
#include <array> #include <array>
@@ -1,8 +1,8 @@
#include "graphics/host_gpu/renderer/descriptorCache.h" #include "graphics/host_gpu/renderer/pipeline/descriptorCache.h"
#include "common/assert.h" #include "common/assert.h"
#include "common/profiler.h" #include "common/profiler.h"
#include "graphics/shader/recompiler/ShaderIR.h" #include "graphics/shader/recompiler/ir/ShaderIR.h"
#include <array> #include <array>
@@ -6,7 +6,7 @@
#include "common/common.h" #include "common/common.h"
#include "common/threads.h" #include "common/threads.h"
#include "graphics/host_gpu/graphicContext.h" #include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/renderer/textureCache.h" #include "graphics/host_gpu/renderer/cache/textureCache.h"
#include "graphics/host_gpu/vulkanCommon.h" #include "graphics/host_gpu/vulkanCommon.h"
#include "graphics/shader/shaderBindings.h" #include "graphics/shader/shaderBindings.h"
@@ -1,4 +1,4 @@
#include "graphics/host_gpu/renderer/descriptors.h" #include "graphics/host_gpu/renderer/pipeline/descriptors.h"
#include "common/assert.h" #include "common/assert.h"
#include "common/common.h" #include "common/common.h"
@@ -14,18 +14,18 @@
#include "graphics/guest_gpu/tile.h" #include "graphics/guest_gpu/tile.h"
#include "graphics/host_gpu/graphicContext.h" #include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/hostMemory.h" #include "graphics/host_gpu/hostMemory.h"
#include "graphics/host_gpu/objects/textureCommon.h"
#include "graphics/host_gpu/renderer/debug.h" #include "graphics/host_gpu/renderer/debug.h"
#include "graphics/host_gpu/renderer/descriptorCache.h" #include "graphics/host_gpu/renderer/image/imageView.h"
#include "graphics/host_gpu/renderer/imageView.h" #include "graphics/host_gpu/renderer/image/textureCommon.h"
#include "graphics/host_gpu/renderer/pipeline/descriptorCache.h"
#include "graphics/host_gpu/renderer/pipeline/shaderResourceBarrier.h"
#include "graphics/host_gpu/renderer/render.h" #include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/renderContext.h" #include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/host_gpu/renderer/shaderResourceBarrier.h"
#include "graphics/host_gpu/vma.h" #include "graphics/host_gpu/vma.h"
#include "graphics/host_gpu/vulkanCommon.h" #include "graphics/host_gpu/vulkanCommon.h"
#include "graphics/shader/recompiler/BindingLayout.h" #include "graphics/shader/recompiler/ir/BindingLayout.h"
#include "graphics/shader/recompiler/ResourceMaterialization.h" #include "graphics/shader/recompiler/ir/ResourceMaterialization.h"
#include "graphics/shader/recompiler/ShaderIR.h" #include "graphics/shader/recompiler/ir/ShaderIR.h"
#include "graphics/shader/shader.h" #include "graphics/shader/shader.h"
#include <algorithm> #include <algorithm>
@@ -239,11 +239,11 @@ bool IsSupportedDepthTextureEncoding(const ShaderTextureResource& descriptor, co
const uint32_t field3_expected = const uint32_t field3_expected =
(descriptor.Type() << 28u) | field3_common | descriptor.DstSelXYZW(); (descriptor.Type() << 28u) | field3_common | descriptor.DstSelXYZW();
const uint32_t field4_expected = descriptor.Depth() | (descriptor.BaseArray5() << 16u); const uint32_t field4_expected = descriptor.Depth() | (descriptor.BaseArray5() << 16u);
const bool common = (descriptor.fields[1] & field1_reserved_mask) == 0 && const bool common = (descriptor.fields[1] & field1_reserved_mask) == 0 &&
(descriptor.fields[2] & field2_reserved_mask) == 0 && (descriptor.fields[2] & field2_reserved_mask) == 0 &&
descriptor.fields[3] == field3_expected && descriptor.fields[3] == field3_expected &&
descriptor.fields[4] == field4_expected && descriptor.fields[4] == field4_expected &&
descriptor.fields[5] == field5_expected; descriptor.fields[5] == field5_expected;
if (!common || (descriptor.fields[6] == 0 && descriptor.fields[7] != 0)) { if (!common || (descriptor.fields[6] == 0 && descriptor.fields[7] != 0)) {
return false; return false;
} }
@@ -318,8 +318,8 @@ static bool IsSupportedStorageTextureDescriptor(const ShaderRecompiler::IR::Imag
const bool valid_2d_slice = const bool valid_2d_slice =
(is_color_2d && descriptor.Depth() == 0 && descriptor.BaseArray5() == 0) || (is_color_2d && descriptor.Depth() == 0 && descriptor.BaseArray5() == 0) ||
(is_color_2d_array && descriptor.BaseArray5() <= descriptor.Depth()); (is_color_2d_array && descriptor.BaseArray5() <= descriptor.Depth());
const bool is_2d = resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2D && const bool is_2d =
valid_2d_slice; resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2D && valid_2d_slice;
const bool is_2d_array = const bool is_2d_array =
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2DArray && resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2DArray &&
is_color_2d_array && descriptor.BaseArray5() <= descriptor.Depth(); is_color_2d_array && descriptor.BaseArray5() <= descriptor.Depth();
@@ -333,18 +333,20 @@ static bool IsSupportedStorageTextureDescriptor(const ShaderRecompiler::IR::Imag
!Prospero::IsFmaskTextureFormat(descriptor.Format()) && (is_2d || is_2d_array) && !Prospero::IsFmaskTextureFormat(descriptor.Format()) && (is_2d || is_2d_array) &&
TileGetBlockLayout(TileBlockFamily::Depth64KB, depth_bpe, depth_block); TileGetBlockLayout(TileBlockFamily::Depth64KB, depth_bpe, depth_block);
const bool supported_standard_tile = const bool supported_standard_tile =
tile == Prospero::GpuEnumValue(Prospero::TileMode::kStandard4KB) && (tile == Prospero::GpuEnumValue(Prospero::TileMode::kStandard4KB) &&
TileIsStandard4KBTextureSupported(descriptor.Format()); TileIsStandard4KBTextureSupported(descriptor.Format())) ||
(tile == Prospero::GpuEnumValue(Prospero::TileMode::kStandard64KB) &&
TileIsStandard64KBTextureSupported(descriptor.Format()));
const bool supported_tile = tile == Prospero::GpuEnumValue(Prospero::TileMode::kLinear) || const bool supported_tile = tile == Prospero::GpuEnumValue(Prospero::TileMode::kLinear) ||
tile == Prospero::GpuEnumValue(Prospero::TileMode::kRenderTarget) || tile == Prospero::GpuEnumValue(Prospero::TileMode::kRenderTarget) ||
supported_depth_tile || supported_standard_tile; supported_depth_tile || supported_standard_tile;
const auto swizzle = descriptor.DstSelXYZW();
const bool supported_swizzle = const bool supported_swizzle =
IsValidImageSwizzle(descriptor.DstSelXYZW()) && IsValidImageSwizzle(swizzle) &&
(descriptor.DstSelXYZW() == DstSel(4, 5, 6, 7) || !resource.read); (swizzle == DstSel(4, 5, 6, 7) || !resource.read || resource.atomic);
const bool supported_mip_view = descriptor.BaseLevel() == 0 || is_1d || is_2d; const bool supported_mip_view = descriptor.BaseLevel() == 0 || is_1d || is_2d;
return (is_1d || is_1d_array || is_2d || is_2d_array || is_3d) && supported_tile && return (is_1d || is_1d_array || is_2d || is_2d_array || is_3d) && supported_tile &&
supported_mip_view && supported_mip_view && descriptor.BaseLevel() == descriptor.LastLevel() &&
descriptor.BaseLevel() == descriptor.LastLevel() &&
descriptor.LastLevel() <= descriptor.MaxMip() && descriptor.MinLod() == 0 && descriptor.LastLevel() <= descriptor.MaxMip() && descriptor.MinLod() == 0 &&
supported_swizzle && descriptor.BCSwizzle() == 0 && !descriptor.MsaaDepth(); supported_swizzle && descriptor.BCSwizzle() == 0 && !descriptor.MsaaDepth();
} }
@@ -375,8 +377,10 @@ void ValidateStorageTexture(const ShaderRecompiler::IR::ImageResource& resource,
const bool encoding_ok = IsSupportedStorageTextureEncoding(descriptor); const bool encoding_ok = IsSupportedStorageTextureEncoding(descriptor);
const bool uint_resource = const bool uint_resource =
resource.kind == ShaderRecompiler::IR::ResourceKind::StorageImageUint; resource.kind == ShaderRecompiler::IR::ResourceKind::StorageImageUint;
const bool format_ok = Prospero::IsSupportedTextureFormat(format) && const bool format_ok =
uint_resource == Prospero::IsUintTextureFormat(format); Prospero::IsSupportedTextureFormat(format) &&
uint_resource == Prospero::IsUintTextureFormat(format) &&
(!resource.atomic || format == Prospero::GpuEnumValue(Prospero::BufferFormat::k32UInt));
if (resource_ok && descriptor_ok && encoding_ok && format_ok && size != 0) { if (resource_ok && descriptor_ok && encoding_ok && format_ok && size != 0) {
return; return;
} }
@@ -616,12 +620,12 @@ RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageResource& reso
resource.written); resource.written);
} }
const auto pixel_format = TextureGetFormat(format); const auto pixel_format = TextureGetFormat(format);
const auto storage_view_format = SrgbStorageViewFormat(pixel_format); const auto storage_view_format = SrgbStorageViewFormat(pixel_format);
const auto view_format = const auto view_format = storage && storage_view_format != vk::Format::eUndefined
storage && storage_view_format != vk::Format::eUndefined ? storage_view_format ? storage_view_format
: pixel_format; : pixel_format;
const auto block_bytes = Prospero::BlockCompressedBytesPerBlock(format); const auto block_bytes = Prospero::BlockCompressedBytesPerBlock(format);
TextureCache::ImageDesc desc {}; TextureCache::ImageDesc desc {};
desc.info.data = {address, size.size}; desc.info.data = {address, size.size};
desc.info.pixel_format = pixel_format; desc.info.pixel_format = pixel_format;
@@ -2,9 +2,9 @@
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_DESCRIPTORS_H_ #define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_DESCRIPTORS_H_
#include "common/assert.h" #include "common/assert.h"
#include "graphics/host_gpu/renderer/image.h" #include "graphics/host_gpu/renderer/image/image.h"
#include "graphics/host_gpu/vulkanCommon.h" #include "graphics/host_gpu/vulkanCommon.h"
#include "graphics/shader/recompiler/ShaderIR.h" #include "graphics/shader/recompiler/ir/ShaderIR.h"
#include "graphics/shader/shaderBindings.h" #include "graphics/shader/shaderBindings.h"
#include <cstdint> #include <cstdint>
@@ -1,4 +1,4 @@
#include "graphics/host_gpu/renderer/pipelineCache.h" #include "graphics/host_gpu/renderer/pipeline/pipelineCache.h"
#include "common/assert.h" #include "common/assert.h"
#include "common/logging/log.h" #include "common/logging/log.h"
@@ -7,7 +7,7 @@
#include "graphics/host_gpu/renderer/colorRenderTarget.h" #include "graphics/host_gpu/renderer/colorRenderTarget.h"
#include "graphics/host_gpu/renderer/debug.h" #include "graphics/host_gpu/renderer/debug.h"
#include "graphics/host_gpu/renderer/depthRenderTarget.h" #include "graphics/host_gpu/renderer/depthRenderTarget.h"
#include "graphics/host_gpu/renderer/imageView.h" #include "graphics/host_gpu/renderer/image/imageView.h"
#include "graphics/host_gpu/renderer/render.h" #include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/renderContext.h" #include "graphics/host_gpu/renderer/renderContext.h"
@@ -1,4 +1,4 @@
#include "graphics/host_gpu/renderer/shaderResourceBarrier.h" #include "graphics/host_gpu/renderer/pipeline/shaderResourceBarrier.h"
#include "common/assert.h" #include "common/assert.h"
#include "graphics/shader/shader.h" #include "graphics/shader/shader.h"
@@ -2,7 +2,7 @@
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_SHADERRESOURCEBARRIER_H_ #define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_SHADERRESOURCEBARRIER_H_
#include "graphics/host_gpu/graphicContext.h" #include "graphics/host_gpu/graphicContext.h"
#include "graphics/shader/recompiler/ResourceMaterialization.h" #include "graphics/shader/recompiler/ir/ResourceMaterialization.h"
#include <vector> #include <vector>
@@ -1,6 +1,6 @@
#include "graphics/host_gpu/renderer/shaderSubgroup.h" #include "graphics/host_gpu/renderer/pipeline/shaderSubgroup.h"
#include "graphics/shader/recompiler/SpirvEmitter.h" #include "graphics/shader/recompiler/emitter/SpirvEmitter.h"
namespace Libs::Graphics { namespace Libs::Graphics {
@@ -2,7 +2,7 @@
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_SHADERSUBGROUP_H_ #define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_SHADERSUBGROUP_H_
#include "graphics/host_gpu/graphicContext.h" #include "graphics/host_gpu/graphicContext.h"
#include "graphics/shader/recompiler/ShaderIR.h" #include "graphics/shader/recompiler/ir/ShaderIR.h"
namespace Libs::Graphics { namespace Libs::Graphics {
@@ -6,14 +6,14 @@
#include "graphics/guest_gpu/gpu_defs.h" #include "graphics/guest_gpu/gpu_defs.h"
#include "graphics/host_gpu/graphicContext.h" #include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/renderer/debug.h" #include "graphics/host_gpu/renderer/debug.h"
#include "graphics/host_gpu/renderer/descriptorCache.h" #include "graphics/host_gpu/renderer/pipeline/descriptorCache.h"
#include "graphics/host_gpu/renderer/pipelineCache.h" #include "graphics/host_gpu/renderer/pipeline/pipelineCache.h"
#include "graphics/host_gpu/renderer/render.h" #include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/renderContext.h" #include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/host_gpu/renderer/renderTarget.h" #include "graphics/host_gpu/renderer/renderTarget.h"
#include "graphics/host_gpu/renderer/shaderSubgroup.h" #include "graphics/host_gpu/renderer/pipeline/shaderSubgroup.h"
#include "graphics/host_gpu/vulkanCommon.h" #include "graphics/host_gpu/vulkanCommon.h"
#include "graphics/shader/recompiler/ShaderIR.h" #include "graphics/shader/recompiler/ir/ShaderIR.h"
#include "graphics/shader/shader.h" #include "graphics/shader/shader.h"
#include <algorithm> #include <algorithm>
@@ -729,7 +729,13 @@ void CreatePipelineInternal(GraphicContext& graphics, DescriptorCache& descripto
vk::PipelineRasterizationStateCreateInfo rasterizer {}; vk::PipelineRasterizationStateCreateInfo rasterizer {};
rasterizer.sType = vk::StructureType::ePipelineRasterizationStateCreateInfo; rasterizer.sType = vk::StructureType::ePipelineRasterizationStateCreateInfo;
// MoltenVK lacks VK_EXT_depth_clip_enable; omit the depth-clip struct on macOS and accept
// Vulkan's default depth clipping (enabled) instead of the PS5's clamp behavior.
#if defined(__APPLE__)
rasterizer.pNext = nullptr;
#else
rasterizer.pNext = &clip_ext; rasterizer.pNext = &clip_ext;
#endif
rasterizer.flags = {}; rasterizer.flags = {};
rasterizer.depthClampEnable = VK_FALSE; rasterizer.depthClampEnable = VK_FALSE;
rasterizer.rasterizerDiscardEnable = VK_FALSE; rasterizer.rasterizerDiscardEnable = VK_FALSE;
@@ -807,7 +813,13 @@ void CreatePipelineInternal(GraphicContext& graphics, DescriptorCache& descripto
vk::PipelineColorBlendStateCreateInfo color_blending {}; vk::PipelineColorBlendStateCreateInfo color_blending {};
color_blending.sType = vk::StructureType::ePipelineColorBlendStateCreateInfo; color_blending.sType = vk::StructureType::ePipelineColorBlendStateCreateInfo;
// MoltenVK lacks VK_EXT_color_write_enable; drop the dynamic color-write struct on macOS
// and rely on each attachment's static colorWriteMask (all channels enabled by default).
#if defined(__APPLE__)
color_blending.pNext = nullptr;
#else
color_blending.pNext = &color_write; color_blending.pNext = &color_write;
#endif
color_blending.flags = {}; color_blending.flags = {};
color_blending.logicOpEnable = VK_FALSE; color_blending.logicOpEnable = VK_FALSE;
color_blending.logicOp = vk::LogicOp::eCopy; color_blending.logicOp = vk::LogicOp::eCopy;
@@ -873,7 +885,11 @@ void CreatePipelineInternal(GraphicContext& graphics, DescriptorCache& descripto
depth_stencil_info.depthWriteEnable = (static_params.depth_write_enable ? VK_TRUE : VK_FALSE); depth_stencil_info.depthWriteEnable = (static_params.depth_write_enable ? VK_TRUE : VK_FALSE);
depth_stencil_info.depthCompareOp = static_params.depth_compare_op; depth_stencil_info.depthCompareOp = static_params.depth_compare_op;
depth_stencil_info.depthBoundsTestEnable = depth_stencil_info.depthBoundsTestEnable =
#if defined(__APPLE__)
VK_FALSE; // MoltenVK lacks the depthBounds feature; depth-bounds testing is disabled
#else
(static_params.depth_bounds_test_enable ? VK_TRUE : VK_FALSE); (static_params.depth_bounds_test_enable ? VK_TRUE : VK_FALSE);
#endif
depth_stencil_info.stencilTestEnable = (static_params.stencil_test_enable ? VK_TRUE : VK_FALSE); depth_stencil_info.stencilTestEnable = (static_params.stencil_test_enable ? VK_TRUE : VK_FALSE);
depth_stencil_info.front.failOp = static_params.stencil_front.failOp; depth_stencil_info.front.failOp = static_params.stencil_front.failOp;
depth_stencil_info.front.passOp = static_params.stencil_front.passOp; depth_stencil_info.front.passOp = static_params.stencil_front.passOp;
@@ -893,7 +909,9 @@ void CreatePipelineInternal(GraphicContext& graphics, DescriptorCache& descripto
vk::DynamicState::eStencilCompareMask, vk::DynamicState::eStencilCompareMask,
vk::DynamicState::eStencilReference, vk::DynamicState::eStencilReference,
vk::DynamicState::eStencilWriteMask, vk::DynamicState::eStencilWriteMask,
vk::DynamicState::eColorWriteEnableEXT, #if !defined(__APPLE__)
vk::DynamicState::eColorWriteEnableEXT, // unsupported by MoltenVK; static mask instead
#endif
}; };
const auto dynamic_states_count = const auto dynamic_states_count =
static_cast<uint32_t>(sizeof(dynamic_states) / sizeof(dynamic_states[0])); static_cast<uint32_t>(sizeof(dynamic_states) / sizeof(dynamic_states[0]));
+1 -1
View File
@@ -4,7 +4,7 @@
#include "common/abi.h" #include "common/abi.h"
#include "common/assert.h" #include "common/assert.h"
#include "common/common.h" #include "common/common.h"
#include "graphics/host_gpu/renderer/descriptorCache.h" #include "graphics/host_gpu/renderer/pipeline/descriptorCache.h"
#include "graphics/host_gpu/renderer/renderTarget.h" #include "graphics/host_gpu/renderer/renderTarget.h"
#include "graphics/host_gpu/vulkanCommon.h" #include "graphics/host_gpu/vulkanCommon.h"
@@ -10,17 +10,17 @@
#include "graphics/guest_gpu/graphicsRun.h" #include "graphics/guest_gpu/graphicsRun.h"
#include "graphics/guest_gpu/hardwareContext.h" #include "graphics/guest_gpu/hardwareContext.h"
#include "graphics/host_gpu/graphicContext.h" #include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/renderer/descriptorCache.h" #include "graphics/host_gpu/renderer/pipeline/descriptorCache.h"
#include "graphics/host_gpu/renderer/descriptors.h" #include "graphics/host_gpu/renderer/pipeline/descriptors.h"
#include "graphics/host_gpu/renderer/imageInfo.h" #include "graphics/host_gpu/renderer/image/imageInfo.h"
#include "graphics/host_gpu/renderer/pipelineCache.h" #include "graphics/host_gpu/renderer/pipeline/pipelineCache.h"
#include "graphics/host_gpu/renderer/render.h" #include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/renderContext.h" #include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/host_gpu/renderer/shaderResourceBarrier.h" #include "graphics/host_gpu/renderer/pipeline/shaderResourceBarrier.h"
#include "graphics/host_gpu/renderer/shaderSubgroup.h" #include "graphics/host_gpu/renderer/pipeline/shaderSubgroup.h"
#include "graphics/host_gpu/vulkanCommon.h" #include "graphics/host_gpu/vulkanCommon.h"
#include "graphics/shader/recompiler/ResourceMaterialization.h" #include "graphics/shader/recompiler/ir/ResourceMaterialization.h"
#include "graphics/shader/recompiler/ShaderIR.h" #include "graphics/shader/recompiler/ir/ShaderIR.h"
#include "graphics/shader/shader.h" #include "graphics/shader/shader.h"
#include "kernel/eventQueue.h" #include "kernel/eventQueue.h"
#include "kernel/pthread.h" #include "kernel/pthread.h"
@@ -5,13 +5,13 @@
#include "common/assert.h" #include "common/assert.h"
#include "common/common.h" #include "common/common.h"
#include "common/threads.h" #include "common/threads.h"
#include "graphics/host_gpu/renderer/bufferCache.h" #include "graphics/host_gpu/renderer/cache/bufferCache.h"
#include "graphics/host_gpu/renderer/commandScheduler.h" #include "graphics/host_gpu/renderer/commandScheduler.h"
#include "graphics/host_gpu/renderer/descriptorCache.h" #include "graphics/host_gpu/renderer/pipeline/descriptorCache.h"
#include "graphics/host_gpu/renderer/gpuResourceManager.h" #include "graphics/host_gpu/renderer/cache/gpuResourceManager.h"
#include "graphics/host_gpu/renderer/pipelineCache.h" #include "graphics/host_gpu/renderer/pipeline/pipelineCache.h"
#include "graphics/host_gpu/renderer/samplerCache.h" #include "graphics/host_gpu/renderer/cache/samplerCache.h"
#include "graphics/host_gpu/renderer/textureCache.h" #include "graphics/host_gpu/renderer/cache/textureCache.h"
#include "kernel/eventQueue.h" #include "kernel/eventQueue.h"
#include <memory> #include <memory>
+140 -86
View File
@@ -16,17 +16,18 @@
#include "graphics/host_gpu/renderer/colorRenderTarget.h" #include "graphics/host_gpu/renderer/colorRenderTarget.h"
#include "graphics/host_gpu/renderer/debug.h" #include "graphics/host_gpu/renderer/debug.h"
#include "graphics/host_gpu/renderer/depthRenderTarget.h" #include "graphics/host_gpu/renderer/depthRenderTarget.h"
#include "graphics/host_gpu/renderer/descriptorCache.h" #include "graphics/host_gpu/renderer/pipeline/descriptorCache.h"
#include "graphics/host_gpu/renderer/pipelineCache.h" #include "graphics/host_gpu/renderer/pipeline/pipelineCache.h"
#include "graphics/host_gpu/renderer/pipeline/shaderResourceBarrier.h"
#include "graphics/host_gpu/renderer/pipeline/shaderSubgroup.h"
#include "graphics/host_gpu/renderer/render.h" #include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/renderContext.h" #include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/host_gpu/renderer/shaderResourceBarrier.h"
#include "graphics/host_gpu/renderer/shaderSubgroup.h"
#include "graphics/host_gpu/vulkanCommon.h" #include "graphics/host_gpu/vulkanCommon.h"
#include "graphics/shader/recompiler/ResourceMaterialization.h" #include "graphics/shader/recompiler/ir/ResourceMaterialization.h"
#include "graphics/shader/recompiler/ShaderIR.h" #include "graphics/shader/recompiler/ir/ShaderIR.h"
#include "graphics/shader/shader.h" #include "graphics/shader/shader.h"
#include "kernel/eventQueue.h" #include "kernel/eventQueue.h"
#include "kernel/memory.h"
#include "kernel/pthread.h" #include "kernel/pthread.h"
#include "libs/errno.h" #include "libs/errno.h"
@@ -221,8 +222,7 @@ static void LogDrawTargetState(const char* draw_name, const RenderColorInfo& col
LogMrtState(draw_name, buffer, ps_input_info); LogMrtState(draw_name, buffer, ps_input_info);
} }
static void LogDrawInputState(const RenderCommandBuffer& buffer, static void LogDrawInputState(const RenderCommandBuffer& buffer, const RenderColorInfo& color,
const RenderColorInfo& color,
const ShaderVertexInputInfo& vs_input_info, const ShaderVertexInputInfo& vs_input_info,
uint32_t index_type_and_size, uint32_t index_count, uint32_t index_type_and_size, uint32_t index_count,
const void* index_addr) { const void* index_addr) {
@@ -395,6 +395,10 @@ static void SetDynamicParams(const RenderCommandBuffer& buffer, vk::CommandBuffe
dynamic_params.stencil_back.reference); dynamic_params.stencil_back.reference);
} }
#if defined(__APPLE__)
// MoltenVK has no VK_EXT_color_write_enable; the pipeline is created without the
// eColorWriteEnableEXT dynamic state and relies on the static colorWriteMask instead.
#else
vk::Bool32 enable[RENDER_COLOR_ATTACHMENTS_MAX] = {}; vk::Bool32 enable[RENDER_COLOR_ATTACHMENTS_MAX] = {};
for (uint32_t i = 0; i < dynamic_params.color_write_count; i++) { for (uint32_t i = 0; i < dynamic_params.color_write_count; i++) {
enable[i] = (dynamic_params.color_write_enable[i] ? VK_TRUE : VK_FALSE); enable[i] = (dynamic_params.color_write_enable[i] ? VK_TRUE : VK_FALSE);
@@ -402,6 +406,7 @@ static void SetDynamicParams(const RenderCommandBuffer& buffer, vk::CommandBuffe
if (dynamic_params.color_write_count != 0) { if (dynamic_params.color_write_count != 0) {
vk_buffer.setColorWriteEnableEXT(dynamic_params.color_write_count, enable); vk_buffer.setColorWriteEnableEXT(dynamic_params.color_write_count, enable);
} }
#endif
} }
static bool DrawHasValidVertexShader(const HW::Shader& sh_ctx) { static bool DrawHasValidVertexShader(const HW::Shader& sh_ctx) {
@@ -494,9 +499,9 @@ struct DrawCallInfo {
}; };
RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderColorInfo* colors, RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderColorInfo* colors,
uint32_t color_count, RenderDepthInfo& depth) { uint32_t color_count, RenderDepthInfo& depth) {
EXIT_IF(colors == nullptr || color_count > RENDER_COLOR_ATTACHMENTS_MAX); EXIT_IF(colors == nullptr || color_count > RENDER_COLOR_ATTACHMENTS_MAX);
auto& cache = m_context.GetTextureCache(); auto& cache = m_context.GetTextureCache();
RenderState state {}; RenderState state {};
state.width = std::numeric_limits<uint32_t>::max(); state.width = std::numeric_limits<uint32_t>::max();
state.height = std::numeric_limits<uint32_t>::max(); state.height = std::numeric_limits<uint32_t>::max();
@@ -507,8 +512,7 @@ RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderCo
auto& target = colors[i]; auto& target = colors[i];
EXIT_IF(!target.image_id); EXIT_IF(!target.image_id);
const auto old_image = cache.ResolveOwner(target.image_id); const auto old_image = cache.ResolveOwner(target.image_id);
if (old_image == nullptr || if (old_image == nullptr || (!old_image->registered && !old_image->info.data.Empty()) ||
(!old_image->registered && !old_image->info.data.Empty()) ||
old_image->binding.needs_rebind) { old_image->binding.needs_rebind) {
if (old_image != nullptr) { if (old_image != nullptr) {
old_image->binding = {}; old_image->binding = {};
@@ -517,7 +521,7 @@ RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderCo
BindRenderTarget(target.image_id); BindRenderTarget(target.image_id);
} }
target.image_view = cache.FindRenderTarget(target.image_id, target.desc); target.image_view = cache.FindRenderTarget(target.image_id, target.desc);
auto& image = cache.GetImage(target.image_id); auto& image = cache.GetImage(target.image_id);
EXIT_IF(image.backing.samples != target.samples || target.image_view == nullptr); EXIT_IF(image.backing.samples != target.samples || target.image_view == nullptr);
if (attachment_samples == 0) { if (attachment_samples == 0) {
attachment_samples = target.samples; attachment_samples = target.samples;
@@ -525,20 +529,19 @@ RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderCo
EXIT("mixed color attachment sample counts are unsupported: %u and %u\n", EXIT("mixed color attachment sample counts are unsupported: %u and %u\n",
attachment_samples, target.samples); attachment_samples, target.samples);
} }
const auto& view = target.desc.view_info; const auto& view = target.desc.view_info;
const auto layout = const auto layout = image.binding.is_bound ? vk::ImageLayout::eGeneral
image.binding.is_bound ? vk::ImageLayout::eGeneral : vk::ImageLayout::eColorAttachmentOptimal;
: vk::ImageLayout::eColorAttachmentOptimal;
image.Transit(layout, image.Transit(layout,
vk::AccessFlagBits2::eColorAttachmentRead | vk::AccessFlagBits2::eColorAttachmentRead |
vk::AccessFlagBits2::eColorAttachmentWrite, vk::AccessFlagBits2::eColorAttachmentWrite,
ImageSubresourceRange {view.base_level, view.level_count, view.base_layer, ImageSubresourceRange {view.base_level, view.level_count, view.base_layer,
view.layer_count}, view.layer_count},
buffer.Handle()); buffer.Handle());
state.width = std::min(state.width, target.extent.width); state.width = std::min(state.width, target.extent.width);
state.height = std::min(state.height, target.extent.height); state.height = std::min(state.height, target.extent.height);
state.num_layers = std::min(state.num_layers, view.layer_count); state.num_layers = std::min(state.num_layers, view.layer_count);
auto& attachment = state.color_attachments[i]; auto& attachment = state.color_attachments[i];
attachment.image_view = target.image_view; attachment.image_view = target.image_view;
attachment.image_layout = layout; attachment.image_layout = layout;
attachment.clear_value = target.color_clear_value.uint32; attachment.clear_value = target.color_clear_value.uint32;
@@ -556,8 +559,7 @@ RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderCo
depth.depth_meta_clear_enable = depth.depth_meta_clear_enable =
depth.htile && depth.htile &&
cache.IsMetaCleared(depth.htile_buffer_vaddr, depth.desc.view_info.base_layer); cache.IsMetaCleared(depth.htile_buffer_vaddr, depth.desc.view_info.base_layer);
depth.depth_load_clear_enable = depth.depth_load_clear_enable = depth.depth_clear_enable || depth.depth_meta_clear_enable;
depth.depth_clear_enable || depth.depth_meta_clear_enable;
if (depth.depth_meta_clear_enable && if (depth.depth_meta_clear_enable &&
!cache.TouchMeta(depth.htile_buffer_vaddr, depth.desc.view_info.base_layer, false)) { !cache.TouchMeta(depth.htile_buffer_vaddr, depth.desc.view_info.base_layer, false)) {
EXIT("failed to consume HTile clear state\n"); EXIT("failed to consume HTile clear state\n");
@@ -567,12 +569,12 @@ RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderCo
if (attachment_samples == 0) { if (attachment_samples == 0) {
attachment_samples = depth.samples; attachment_samples = depth.samples;
} else if (attachment_samples != depth.samples) { } else if (attachment_samples != depth.samples) {
EXIT("mixed color/depth sample counts are unsupported: %u and %u\n", EXIT("mixed color/depth sample counts are unsupported: %u and %u\n", attachment_samples,
attachment_samples, depth.samples); depth.samples);
} }
const auto layout = depth_attachment_layout(depth); const auto layout = depth_attachment_layout(depth);
const auto writes = depth.AttachmentWriteAspects(); const auto writes = depth.AttachmentWriteAspects();
auto access = vk::AccessFlags2 {vk::AccessFlagBits2::eDepthStencilAttachmentRead}; auto access = vk::AccessFlags2 {vk::AccessFlagBits2::eDepthStencilAttachmentRead};
if (writes) { if (writes) {
access |= vk::AccessFlagBits2::eDepthStencilAttachmentWrite; access |= vk::AccessFlagBits2::eDepthStencilAttachmentWrite;
} }
@@ -581,21 +583,19 @@ RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderCo
ImageSubresourceRange {view.base_level, view.level_count, view.base_layer, ImageSubresourceRange {view.base_level, view.level_count, view.base_layer,
view.layer_count}, view.layer_count},
buffer.Handle()); buffer.Handle());
state.width = std::min(state.width, depth.width); state.width = std::min(state.width, depth.width);
state.height = std::min(state.height, depth.height); state.height = std::min(state.height, depth.height);
state.num_layers = std::min(state.num_layers, view.layer_count); state.num_layers = std::min(state.num_layers, view.layer_count);
const auto aspects = ImageViewOps::DepthAspectMask(depth.format); const auto aspects = ImageViewOps::DepthAspectMask(depth.format);
auto& attachment = state.depth_stencil_attachment; auto& attachment = state.depth_stencil_attachment;
attachment.image_view = depth.image_view; attachment.image_view = depth.image_view;
attachment.image_layout = layout; attachment.image_layout = layout;
attachment.clear_value[0] = std::bit_cast<uint32_t>(depth.depth_clear_value); attachment.clear_value[0] = std::bit_cast<uint32_t>(depth.depth_clear_value);
attachment.clear_value[1] = depth.stencil_clear_value; attachment.clear_value[1] = depth.stencil_clear_value;
attachment.has_depth = attachment.has_depth = static_cast<bool>(aspects & vk::ImageAspectFlagBits::eDepth);
static_cast<bool>(aspects & vk::ImageAspectFlagBits::eDepth); attachment.depth_clear = depth.depth_load_clear_enable;
attachment.depth_clear = depth.depth_load_clear_enable; attachment.has_stencil = static_cast<bool>(aspects & vk::ImageAspectFlagBits::eStencil);
attachment.has_stencil = attachment.stencil_clear = depth.stencil_clear_enable;
static_cast<bool>(aspects & vk::ImageAspectFlagBits::eStencil);
attachment.stencil_clear = depth.stencil_clear_enable;
} }
if (attachment_samples == 0 || if (attachment_samples == 0 ||
vulkan_sample_count(attachment_samples) == vk::SampleCountFlagBits {}) { vulkan_sample_count(attachment_samples) == vk::SampleCountFlagBits {}) {
@@ -680,6 +680,85 @@ static uint64_t VertexBufferDescriptorSize(const ShaderVertexInputBuffer& buffer
: buffer.num_records); : buffer.num_records);
} }
struct VertexBufferRange {
uint64_t base_address = 0;
uint64_t requested_end = 0;
uint64_t acquired_end = 0;
BufferBinding binding;
[[nodiscard]] uint64_t RequestedSize() const { return requested_end - base_address; }
};
static std::vector<BufferBinding> AcquireVertexBuffers(RenderCommandBuffer& buffer,
const ShaderVertexInputInfo& vs_input_info) {
// Collect the non-empty guest vertex ranges.
std::vector<VertexBufferRange> ranges;
ranges.reserve(vs_input_info.buffers_num);
for (int i = 0; i < vs_input_info.buffers_num; i++) {
const auto& vertex = vs_input_info.buffers[i];
const auto size = VertexBufferDescriptorSize(vertex);
if (size == 0) {
continue;
}
if (vertex.addr == 0 || size > UINT64_MAX - vertex.addr) {
EXIT("invalid vertex buffer range: addr=0x%016" PRIx64 " size=0x%016" PRIx64 "\n",
vertex.addr, size);
}
ranges.push_back({vertex.addr, vertex.addr + size});
}
std::ranges::sort(ranges, [](const VertexBufferRange& left, const VertexBufferRange& right) {
return left.base_address < right.base_address;
});
// Merge overlapping or touching ranges before acquiring host buffers.
std::vector<VertexBufferRange> merged_ranges;
merged_ranges.reserve(ranges.size());
for (const auto& range: ranges) {
if (!merged_ranges.empty() && merged_ranges.back().requested_end >= range.base_address) {
merged_ranges.back().requested_end =
std::max(merged_ranges.back().requested_end, range.requested_end);
continue;
}
merged_ranges.push_back(range);
}
auto& cache = buffer.GetContext().GetBufferCache();
for (auto& range: merged_ranges) {
// PPSA20298
const auto size =
Libs::LibKernel::Memory::ClampRangeSize(range.base_address, range.RequestedSize());
range.acquired_end = range.base_address + size;
range.binding = cache.ObtainBuffer(buffer, range.base_address, size);
}
// Rebuild slot bindings, offsetting non-empty slots into their acquired merged range.
std::vector<BufferBinding> bindings;
bindings.reserve(vs_input_info.buffers_num);
for (int i = 0; i < vs_input_info.buffers_num; i++) {
const auto& vertex = vs_input_info.buffers[i];
const auto size = VertexBufferDescriptorSize(vertex);
if (size == 0) {
auto owner = cache.ObtainNullBuffer();
bindings.push_back({owner, owner->Handle(), 0});
continue;
}
const auto range = std::ranges::find_if(merged_ranges, [&](const VertexBufferRange& value) {
return vertex.addr >= value.base_address && vertex.addr < value.acquired_end;
});
if (range == merged_ranges.end()) {
EXIT("vertex buffer address is outside the acquired range: addr=0x%016" PRIx64 "\n",
vertex.addr);
}
auto binding = range->binding;
binding.offset += vertex.addr - range->base_address;
bindings.push_back(std::move(binding));
}
return bindings;
}
static void SetDrawDebugPhase(RenderCommandBuffer& buffer, uint64_t submit_id, static void SetDrawDebugPhase(RenderCommandBuffer& buffer, uint64_t submit_id,
const DrawCallInfo& draw, uint32_t phase) { const DrawCallInfo& draw, uint32_t phase) {
EXIT_IF(draw.name == nullptr); EXIT_IF(draw.name == nullptr);
@@ -731,9 +810,9 @@ static bool GetDrawTopology(const HW::UserConfig& ucfg, bool auto_draw, bool use
} }
bool RenderExecutor::PrepareDrawRenderState(uint64_t submit_id, RenderCommandBuffer& buffer, bool RenderExecutor::PrepareDrawRenderState(uint64_t submit_id, RenderCommandBuffer& buffer,
const DrawCallInfo& draw, const DrawCallInfo& draw,
uint32_t render_target_slice_offset, uint32_t render_target_slice_offset,
bool log_setup_phases, DrawRenderState& state) { bool log_setup_phases, DrawRenderState& state) {
EXIT_IF(draw.name == nullptr); EXIT_IF(draw.name == nullptr);
auto& ctx = buffer.GetRegisters(); auto& ctx = buffer.GetRegisters();
@@ -818,37 +897,13 @@ static std::vector<BufferBinding> PrepareVertexBuffers(uint64_t
(void)submit_id; (void)submit_id;
LogDrawPhase(draw.name, "PrepareVertexBuffers"); LogDrawPhase(draw.name, "PrepareVertexBuffers");
std::vector<BufferBinding> bindings; return AcquireVertexBuffers(buffer, vs_input_info);
bindings.reserve(vs_input_info.buffers_num);
for (int i = 0; i < vs_input_info.buffers_num; i++) {
const auto& b = vs_input_info.buffers[i];
const auto size = VertexBufferDescriptorSize(b);
if (size == 0) {
auto owner = buffer.GetContext().GetBufferCache().ObtainNullBuffer();
bindings.push_back({owner, owner->Handle(), 0});
} else {
bindings.push_back(
buffer.GetContext().GetBufferCache().ObtainBuffer(buffer, b.addr, size));
}
}
return bindings;
} }
static void RebindVertexBuffers(RenderCommandBuffer& buffer, static void RebindVertexBuffers(RenderCommandBuffer& buffer,
const ShaderVertexInputInfo& vs_input_info, const ShaderVertexInputInfo& vs_input_info,
std::vector<BufferBinding>& bindings) { std::vector<BufferBinding>& bindings) {
EXIT_IF(bindings.size() != static_cast<size_t>(vs_input_info.buffers_num)); bindings = AcquireVertexBuffers(buffer, vs_input_info);
for (int i = 0; i < vs_input_info.buffers_num; i++) {
const auto& vertex = vs_input_info.buffers[i];
const auto size = VertexBufferDescriptorSize(vertex);
if (size == 0) {
auto owner = buffer.GetContext().GetBufferCache().ObtainNullBuffer();
bindings[i] = {owner, owner->Handle(), 0};
} else {
bindings[i] =
buffer.GetContext().GetBufferCache().ObtainBuffer(buffer, vertex.addr, size);
}
}
} }
static PreparedIndexBuffer PrepareIndexBuffer(RenderCommandBuffer& buffer, static PreparedIndexBuffer PrepareIndexBuffer(RenderCommandBuffer& buffer,
@@ -1006,17 +1061,17 @@ static void EmitDrawPrimitives(const HW::UserConfig& ucfg, vk::CommandBuffer vk_
} }
void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, RenderCommandBuffer& buffer, void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, RenderCommandBuffer& buffer,
const DrawCallInfo& draw, DrawRenderState& state, const DrawCallInfo& draw, DrawRenderState& state,
vk::PrimitiveTopology topology, const DrawEmitInfo& emit, vk::PrimitiveTopology topology, const DrawEmitInfo& emit,
const DrawIndexBufferSource& index_source, const DrawIndexBufferSource& index_source,
bool log_pipeline_phase, bool set_bind_debug, bool log_pipeline_phase, bool set_bind_debug,
bool set_auto_debug) { bool set_auto_debug) {
EXIT_IF(draw.name == nullptr); EXIT_IF(draw.name == nullptr);
auto& ucfg = buffer.GetUserConfig(); auto& ucfg = buffer.GetUserConfig();
LogDrawPhase(draw.name, "PrepareBindings"); LogDrawPhase(draw.name, "PrepareBindings");
auto bindings = PrepareGraphicsBindings(buffer, state.vs_input_info.stage, auto bindings = PrepareGraphicsBindings(buffer, state.vs_input_info.stage,
state.ps_input_info.stage, state.ps_active); state.ps_input_info.stage, state.ps_active);
auto vertex_bindings = PrepareVertexBuffers(submit_id, buffer, draw, state.vs_input_info); auto vertex_bindings = PrepareVertexBuffers(submit_id, buffer, draw, state.vs_input_info);
auto index_binding = PrepareIndexBuffer(buffer, index_source); auto index_binding = PrepareIndexBuffer(buffer, index_source);
RebindVertexBuffers(buffer, state.vs_input_info, vertex_bindings); RebindVertexBuffers(buffer, state.vs_input_info, vertex_bindings);
@@ -1089,10 +1144,10 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, RenderCommandBuffer
} }
void RenderExecutor::DrawIndex(uint64_t submit_id, RenderCommandBuffer& buffer, void RenderExecutor::DrawIndex(uint64_t submit_id, RenderCommandBuffer& buffer,
uint32_t index_type_and_size, uint32_t index_count, uint32_t index_type_and_size, uint32_t index_count,
const void* index_addr, uint32_t flags, uint32_t type, const void* index_addr, uint32_t flags, uint32_t type,
uint32_t instance_count, uint32_t render_target_slice_offset, uint32_t instance_count, uint32_t render_target_slice_offset,
int32_t vertex_offset_add, uint32_t first_instance) { int32_t vertex_offset_add, uint32_t first_instance) {
KYTY_PROFILER_FUNCTION(); KYTY_PROFILER_FUNCTION();
EXIT_IF(buffer.IsInvalid()); EXIT_IF(buffer.IsInvalid());
@@ -1223,11 +1278,10 @@ void RenderExecutor::DrawIndex(uint64_t submit_id, RenderCommandBuffer& buffer,
} }
// NOLINTNEXTLINE(readability-function-cognitive-complexity) // NOLINTNEXTLINE(readability-function-cognitive-complexity)
void RenderExecutor::DrawAuto(uint64_t submit_id, RenderCommandBuffer& buffer, void RenderExecutor::DrawAuto(uint64_t submit_id, RenderCommandBuffer& buffer, uint32_t index_count,
uint32_t index_count, uint32_t flags, uint32_t render_target_slice_offset,
uint32_t flags, uint32_t render_target_slice_offset, uint32_t instance_count, uint32_t first_vertex,
uint32_t instance_count, uint32_t first_vertex, uint32_t first_instance) {
uint32_t first_instance) {
KYTY_PROFILER_FUNCTION(); KYTY_PROFILER_FUNCTION();
EXIT_IF(buffer.IsInvalid()); EXIT_IF(buffer.IsInvalid());
@@ -1285,7 +1339,8 @@ void RenderExecutor::DrawAuto(uint64_t submit_id, RenderCommandBuffer& buffer,
instance_count, first_instance}; instance_count, first_instance};
DrawRenderState state {}; DrawRenderState state {};
if (!PrepareDrawRenderState(submit_id, buffer, draw, render_target_slice_offset, false, state)) { if (!PrepareDrawRenderState(submit_id, buffer, draw, render_target_slice_offset, false,
state)) {
ResetBindings(); ResetBindings();
return; return;
} }
@@ -1335,7 +1390,7 @@ void RenderExecutor::DrawAuto(uint64_t submit_id, RenderCommandBuffer& buffer,
} }
bool RenderExecutor::ResolveColorTargets(uint64_t submit_id, RenderCommandBuffer& buffer, bool RenderExecutor::ResolveColorTargets(uint64_t submit_id, RenderCommandBuffer& buffer,
uint32_t render_target_slice_offset) { uint32_t render_target_slice_offset) {
const auto& hw = buffer.GetRegisters(); const auto& hw = buffer.GetRegisters();
if (hw.GetColorControl().mode != 3) { if (hw.GetColorControl().mode != 3) {
return false; return false;
@@ -1364,8 +1419,7 @@ bool RenderExecutor::ResolveColorTargets(uint64_t submit_id, RenderCommandBuffer
cache.MarkGpuWritten(dst.image_id); cache.MarkGpuWritten(dst.image_id);
auto& source = cache.GetImage(src.image_id); auto& source = cache.GetImage(src.image_id);
auto& destination = cache.GetImage(dst.image_id); auto& destination = cache.GetImage(dst.image_id);
destination.Resolve(source, destination.Resolve(source, {src.base_mip_level, 1, src.base_array_layer, 1},
{src.base_mip_level, 1, src.base_array_layer, 1},
{dst.base_mip_level, 1, dst.base_array_layer, 1}); {dst.base_mip_level, 1, dst.base_array_layer, 1});
return true; return true;
} }
+1 -1
View File
@@ -5,7 +5,7 @@
#include "common/logging/log.h" #include "common/logging/log.h"
#include "common/threads.h" #include "common/threads.h"
#include "graphics/host_gpu/graphicContext.h" #include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/renderer/bufferCache.h" #include "graphics/host_gpu/renderer/cache/bufferCache.h"
#include "graphics/host_gpu/renderer/render.h" #include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/renderContext.h" #include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/presentation/videoOut.h" #include "graphics/presentation/videoOut.h"
+109 -11
View File
@@ -19,12 +19,16 @@
#include <windows.h> #include <windows.h>
#undef min #undef min
#undef max #undef max
#else
#include <dlfcn.h>
// RenderDoc uses Windows-style names in its cross-platform API.
#define __cdecl
using HMODULE = void*;
#endif #endif
namespace Libs::Graphics { namespace Libs::Graphics {
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
using RenderDocDevicePointer = void*; using RenderDocDevicePointer = void*;
using RenderDocWindowHandle = void*; using RenderDocWindowHandle = void*;
@@ -107,6 +111,8 @@ static RenderDocDevicePointer GetRenderDocDevicePointer(vk::Instance instance) {
return VulkanHandleToPointer(instance); return VulkanHandleToPointer(instance);
} }
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
static bool BindRenderDocApi(HMODULE module) { static bool BindRenderDocApi(HMODULE module) {
auto* get_api = reinterpret_cast<pRENDERDOC_GetAPI>(GetProcAddress(module, "RENDERDOC_GetAPI")); auto* get_api = reinterpret_cast<pRENDERDOC_GetAPI>(GetProcAddress(module, "RENDERDOC_GetAPI"));
if (get_api == nullptr) { if (get_api == nullptr) {
@@ -147,10 +153,84 @@ static RenderDocWindowHandle GetRenderDocWindowHandle(SDL_Window* window) {
return info.info.win.window; return info.info.win.window;
} }
#else
static bool BindRenderDocApi(HMODULE module) {
auto* get_api = reinterpret_cast<pRENDERDOC_GetAPI>(::dlsym(module, "RENDERDOC_GetAPI"));
if (get_api == nullptr) {
return false;
}
void* api = nullptr;
if (get_api(eRENDERDOC_API_Version_1_4_2, &api) == 0 || api == nullptr) {
return false;
}
g_module = module;
g_api = static_cast<RenderDocApi*>(api);
g_api->SetCaptureFilePathTemplate("_RenderDoc/kyty");
static RenderDocInputButton capture_keys[] = {eRENDERDOC_Key_F1};
g_api->SetCaptureKeys(capture_keys, 1);
g_api->UnloadCrashHandler();
Dl_info info {};
if (::dladdr(reinterpret_cast<void*>(get_api), &info) != 0 && info.dli_fname != nullptr) {
LOGF("RenderDoc: bound API from %s\n", info.dli_fname);
} else {
LOGF("RenderDoc: bound API\n");
}
return true;
}
static RenderDocWindowHandle GetRenderDocWindowHandle(SDL_Window* window) {
if (window == nullptr) {
return nullptr;
}
SDL_SysWMinfo info {};
SDL_VERSION(&info.version);
if (SDL_GetWindowWMInfo(window, &info) != SDL_TRUE) {
return nullptr;
}
#if defined(SDL_VIDEO_DRIVER_X11)
if (info.subsystem == SDL_SYSWM_X11) {
// RenderDoc takes the raw xlib Window id in the pointer slot, not a Display*.
return reinterpret_cast<RenderDocWindowHandle>(
static_cast<uintptr_t>(info.info.x11.window));
}
#endif
// Wayland capture works without an active-window handle.
static std::atomic_bool logged = false;
if (!logged.exchange(true)) {
LOGF("RenderDoc: no native window handle for SDL subsystem %d (Wayland?); the in-app "
"overlay is unavailable, but --rd captures still work\n",
static_cast<int>(info.subsystem));
}
return nullptr;
}
#endif
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
static bool IsAvailable() { static bool IsAvailable() {
return g_api != nullptr && g_device != nullptr && g_window != nullptr; return g_api != nullptr && g_device != nullptr && g_window != nullptr;
} }
#else
static bool IsAvailable() {
return g_api != nullptr && g_device != nullptr;
}
#endif
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
void RenderDocInit() { void RenderDocInit() {
bool expected = false; bool expected = false;
if (!g_init_done.compare_exchange_strong(expected, true)) { if (!g_init_done.compare_exchange_strong(expected, true)) {
@@ -187,6 +267,33 @@ void RenderDocInit() {
} }
} }
#else
void RenderDocInit() {
bool expected = false;
if (!g_init_done.compare_exchange_strong(expected, true)) {
return;
}
// Prefer an injected RenderDoc instance.
auto* module = ::dlopen("librenderdoc.so", RTLD_NOW | RTLD_NOLOAD);
if (module == nullptr) {
module = ::dlopen("librenderdoc.so", RTLD_NOW);
}
if (module == nullptr) {
LOGF("RenderDoc: librenderdoc.so was not found; in-app capture disabled\n");
return;
}
if (!BindRenderDocApi(module)) {
LOGF("RenderDoc: API 1.4.2 is not available; in-app capture disabled\n");
::dlclose(module);
return;
}
}
#endif
void RenderDocSetActiveWindow(vk::Instance instance, SDL_Window* window) { void RenderDocSetActiveWindow(vk::Instance instance, SDL_Window* window) {
if (g_api == nullptr) { if (g_api == nullptr) {
return; return;
@@ -274,13 +381,4 @@ void RenderDocOnPresent() {
} }
} }
#else
void RenderDocInit() {}
void RenderDocSetActiveWindow(vk::Instance /*instance*/, SDL_Window* /*window*/) {}
void RenderDocRequestCapture() {}
void RenderDocOnPresent() {}
#endif
} // namespace Libs::Graphics } // namespace Libs::Graphics
+1 -1
View File
@@ -12,7 +12,7 @@
#include "graphics/guest_gpu/gpu_defs.h" #include "graphics/guest_gpu/gpu_defs.h"
#include "graphics/guest_gpu/graphicsRun.h" #include "graphics/guest_gpu/graphicsRun.h"
#include "graphics/guest_gpu/tile.h" #include "graphics/guest_gpu/tile.h"
#include "graphics/host_gpu/renderer/imageInfo.h" #include "graphics/host_gpu/renderer/image/imageInfo.h"
#include "graphics/host_gpu/renderer/render.h" #include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/renderContext.h" #include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/presentation/presenter.h" #include "graphics/presentation/presenter.h"
@@ -586,7 +586,13 @@ void Swapchain::RefreshSurfaceSize() {
void Swapchain::Recreate(bool surface_lost) { void Swapchain::Recreate(bool surface_lost) {
Destroy(); Destroy();
if (surface_lost) { if (surface_lost) {
#if defined(__APPLE__)
// Surface recreation goes through SDL_Vulkan_CreateSurface, which touches the
// window's view/layer and must run on the main thread on macOS.
m_window.RunOnMainThread([this] { m_window.RecreateSurface(); }, true);
#else
m_window.RecreateSurface(); m_window.RecreateSurface();
#endif
} }
RefreshSurfaceSize(); RefreshSurfaceSize();
Create(); Create();
@@ -797,9 +803,20 @@ void Presenter::Present(Frame& frame, bool reuse) {
auto& window = m_impl->window; auto& window = m_impl->window;
if (window.window_hidden) { if (window.window_hidden) {
#if defined(__APPLE__)
// AppKit traps if a window is shown off the main thread; marshal and wait so the
// swapchain below is recreated against a visible window.
window.RunOnMainThread(
[&window] {
window.UpdateIcon();
SDL_ShowWindow(window.window);
},
true);
#else
window.UpdateIcon(); window.UpdateIcon();
SDL_ShowWindow(window.window); SDL_ShowWindow(window.window);
#endif
window.window_hidden = false; window.window_hidden = false;
m_impl->RecoverSwapchain(Swapchain::Status::Recreate); m_impl->RecoverSwapchain(Swapchain::Status::Recreate);
@@ -217,7 +217,9 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa
if (color_write_ext.colorWriteEnable != VK_TRUE) { if (color_write_ext.colorWriteEnable != VK_TRUE) {
LOGF("colorWriteEnable is not supported\n"); LOGF("colorWriteEnable is not supported\n");
#if !defined(__APPLE__)
skip_device = true; skip_device = true;
#endif
} }
if (depth_clip_control.depthClipControl != VK_TRUE) { if (depth_clip_control.depthClipControl != VK_TRUE) {
@@ -226,7 +228,9 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa
} }
if (depth_clip_enable.depthClipEnable != VK_TRUE) { if (depth_clip_enable.depthClipEnable != VK_TRUE) {
LOGF("depthClipEnable is not supported\n"); LOGF("depthClipEnable is not supported\n");
#if !defined(__APPLE__)
skip_device = true; skip_device = true;
#endif
} }
if (features12.samplerMirrorClampToEdge != VK_TRUE) { if (features12.samplerMirrorClampToEdge != VK_TRUE) {
@@ -271,7 +275,9 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa
} }
if (device_features2.features.depthBounds != VK_TRUE) { if (device_features2.features.depthBounds != VK_TRUE) {
LOGF("depthBounds is not supported\n"); LOGF("depthBounds is not supported\n");
#if !defined(__APPLE__)
skip_device = true; skip_device = true;
#endif
} }
if (device_features2.features.shaderStorageImageWriteWithoutFormat != VK_TRUE) { if (device_features2.features.shaderStorageImageWriteWithoutFormat != VK_TRUE) {
LOGF("shaderStorageImageWriteWithoutFormat is not supported\n"); LOGF("shaderStorageImageWriteWithoutFormat is not supported\n");
@@ -494,7 +500,14 @@ static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const V
vk::PhysicalDeviceDepthClipControlFeaturesEXT depth_clip_control {}; vk::PhysicalDeviceDepthClipControlFeaturesEXT depth_clip_control {};
depth_clip_control.sType = vk::StructureType::ePhysicalDeviceDepthClipControlFeaturesEXT; depth_clip_control.sType = vk::StructureType::ePhysicalDeviceDepthClipControlFeaturesEXT;
// MoltenVK lacks VK_EXT_depth_clip_enable and VK_EXT_color_write_enable, so drop those
// feature structs from the chain on macOS (the renderer falls back to default depth
// clipping and static color-write masks).
#if defined(__APPLE__)
depth_clip_control.pNext = nullptr;
#else
depth_clip_control.pNext = &depth_clip_enable; depth_clip_control.pNext = &depth_clip_enable;
#endif
depth_clip_control.depthClipControl = VK_TRUE; depth_clip_control.depthClipControl = VK_TRUE;
vk::PhysicalDeviceVulkan12Features features12 {}; vk::PhysicalDeviceVulkan12Features features12 {};
@@ -541,7 +554,9 @@ static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const V
device_features.fragmentStoresAndAtomics = VK_TRUE; device_features.fragmentStoresAndAtomics = VK_TRUE;
device_features.samplerAnisotropy = VK_TRUE; device_features.samplerAnisotropy = VK_TRUE;
device_features.robustBufferAccess = VK_TRUE; device_features.robustBufferAccess = VK_TRUE;
device_features.depthBounds = VK_TRUE; #if !defined(__APPLE__)
device_features.depthBounds = VK_TRUE; // unsupported by MoltenVK
#endif
device_features.shaderStorageImageWriteWithoutFormat = VK_TRUE; device_features.shaderStorageImageWriteWithoutFormat = VK_TRUE;
device_features.shaderStorageImageReadWithoutFormat = VK_TRUE; device_features.shaderStorageImageReadWithoutFormat = VK_TRUE;
device_features.shaderImageGatherExtended = VK_TRUE; device_features.shaderImageGatherExtended = VK_TRUE;
@@ -894,10 +909,20 @@ void WindowContext::CreateVulkan() {
} }
surface = native_surface; surface = native_surface;
std::vector<const char*> device_extensions = { std::vector<const char*> device_extensions = {VK_KHR_SWAPCHAIN_EXTENSION_NAME,
VK_KHR_SWAPCHAIN_EXTENSION_NAME, VK_EXT_DEPTH_CLIP_ENABLE_EXTENSION_NAME, VK_EXT_DEPTH_CLIP_CONTROL_EXTENSION_NAME,
VK_EXT_DEPTH_CLIP_CONTROL_EXTENSION_NAME, VK_EXT_COLOR_WRITE_ENABLE_EXTENSION_NAME, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME,
VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME, "VK_KHR_maintenance1"}; "VK_KHR_maintenance1"};
#if defined(__APPLE__)
// MoltenVK lacks VK_EXT_depth_clip_enable and VK_EXT_color_write_enable; the renderer
// falls back to default depth clipping and static color-write masks on macOS. It also
// requires VK_KHR_portability_subset per the Vulkan portability spec.
device_extensions.push_back("VK_KHR_portability_subset");
#else
device_extensions.push_back(VK_EXT_DEPTH_CLIP_ENABLE_EXTENSION_NAME);
device_extensions.push_back(VK_EXT_COLOR_WRITE_ENABLE_EXTENSION_NAME);
#endif
#ifdef KYTY_ENABLE_DEBUG_PRINTF #ifdef KYTY_ENABLE_DEBUG_PRINTF
if (Config::SpirvDebugPrintfEnabled()) { if (Config::SpirvDebugPrintfEnabled()) {
+67 -1
View File
@@ -1,5 +1,7 @@
#include "graphics/presentation/window.h" #include "graphics/presentation/window.h"
#include <cstdlib>
#include "SDL.h" #include "SDL.h"
#include "SDL_error.h" #include "SDL_error.h"
#include "SDL_events.h" #include "SDL_events.h"
@@ -697,6 +699,52 @@ void WindowContext::ProcessEvent(double time_s) {
} }
} }
#if defined(__APPLE__)
void WindowContext::RunOnMainThread(std::function<void()> task, bool wait) {
if (Common::Thread::IsMainThread()) {
task();
return;
}
uint64_t ticket = 0;
{
Common::LockGuard lock(main_task_mutex);
main_tasks.push_back(std::move(task));
ticket = ++main_tasks_queued;
}
// Wake the main loop in case it is blocked in SDL_WaitEvent.
SDL_Event event {};
event.type = SDL_USEREVENT;
SDL_PushEvent(&event);
if (!wait) {
return;
}
Common::LockGuard lock(main_task_mutex);
while (main_tasks_run < ticket) {
main_task_done.Wait(&main_task_mutex);
}
}
void WindowContext::DrainMainThreadTasks() {
std::vector<std::function<void()>> tasks;
{
Common::LockGuard lock(main_task_mutex);
tasks.swap(main_tasks);
}
if (tasks.empty()) {
return;
}
for (auto& task: tasks) {
task();
}
Common::LockGuard lock(main_task_mutex);
main_tasks_run += tasks.size();
main_task_done.SignalAll();
}
#endif
void WindowContext::Run() { void WindowContext::Run() {
Common::Timer timer; Common::Timer timer;
timer.Start(); timer.Start();
@@ -706,6 +754,9 @@ void WindowContext::Run() {
loop.paused.store(false, std::memory_order_release); loop.paused.store(false, std::memory_order_release);
while (!loop.need_exit) { while (!loop.need_exit) {
#if defined(__APPLE__)
DrainMainThreadTasks();
#endif
if (SDL_PollEvent(&loop.event) != 0) { if (SDL_PollEvent(&loop.event) != 0) {
ProcessEvent(timer.GetTimeS()); ProcessEvent(timer.GetTimeS());
continue; continue;
@@ -747,9 +798,18 @@ static void WindowCreate(WindowContext& context) {
LOGF("WindowCreate(): width = %d, height = %d\n", width, height); LOGF("WindowCreate(): width = %d, height = %d\n", width, height);
uint32_t window_flags = KYTY_SDL_WINDOW_FLAGS;
#if defined(__APPLE__)
// macOS 26 window chrome (CoreUI asset decode, SwiftUI titlebar) has been observed
// throwing NSExceptions under Rosetta during the first CATransaction commit. A
// borderless window skips that machinery entirely.
if (std::getenv("KYTY_BORDERLESS") != nullptr) {
window_flags |= static_cast<uint32_t>(SDL_WINDOW_BORDERLESS);
}
#endif
context.window = context.window =
SDL_CreateWindow(KYTY_SDL_WINDOW_CAPTION, KYTY_SDL_WINDOWPOS_CENTERED, SDL_CreateWindow(KYTY_SDL_WINDOW_CAPTION, KYTY_SDL_WINDOWPOS_CENTERED,
KYTY_SDL_WINDOWPOS_CENTERED, width, height, KYTY_SDL_WINDOW_FLAGS); KYTY_SDL_WINDOWPOS_CENTERED, width, height, window_flags);
context.window_hidden = true; context.window_hidden = true;
@@ -896,7 +956,13 @@ void WindowContext::UpdateTitle() {
(has_app_ver ? " " : ""), device_name, processor_name, (has_app_ver ? " " : ""), device_name, processor_name,
frame_num, current_fps); frame_num, current_fps);
#if defined(__APPLE__)
// AppKit traps on title changes off the main thread; fire-and-forget keeps present pacing.
RunOnMainThread([this, fps = std::move(fps)] { SDL_SetWindowTitle(window, fps.c_str()); },
false);
#else
SDL_SetWindowTitle(window, fps.c_str()); SDL_SetWindowTitle(window, fps.c_str());
#endif
} }
} // namespace Libs::Graphics } // namespace Libs::Graphics
@@ -12,6 +12,10 @@
#include <memory> #include <memory>
#include <vector> #include <vector>
#if defined(__APPLE__)
#include <functional>
#endif
namespace Libs::Graphics { namespace Libs::Graphics {
class Presenter; class Presenter;
@@ -47,6 +51,14 @@ struct WindowContext {
void ProcessEvent(double time_seconds); void ProcessEvent(double time_seconds);
void Run(); void Run();
#if defined(__APPLE__)
// AppKit only allows window operations (show, icon, title, view/layer changes) on the
// main thread; the present thread marshals them through the SDL main loop with these.
// wait=true blocks until the task has run on the main thread.
void RunOnMainThread(std::function<void()> task, bool wait);
void DrainMainThreadTasks();
#endif
GraphicContext graphic_ctx; GraphicContext graphic_ctx;
SDL_Window* window = nullptr; SDL_Window* window = nullptr;
bool window_hidden = true; bool window_hidden = true;
@@ -60,6 +72,14 @@ struct WindowContext {
char processor_name[64] = {0}; char processor_name[64] = {0};
Common::Mutex mutex; Common::Mutex mutex;
#if defined(__APPLE__)
Common::Mutex main_task_mutex;
Common::CondVar main_task_done;
std::vector<std::function<void()>> main_tasks; // guarded by main_task_mutex
uint64_t main_tasks_queued = 0; // guarded by main_task_mutex
uint64_t main_tasks_run = 0; // guarded by main_task_mutex
#endif
}; };
} // namespace Libs::Graphics } // namespace Libs::Graphics
@@ -2,17 +2,17 @@
#include "common/assert.h" #include "common/assert.h"
#include "common/logging/log.h" #include "common/logging/log.h"
#include "graphics/shader/recompiler/BindingLayout.h" #include "graphics/shader/recompiler/ir/BindingLayout.h"
#include "graphics/shader/recompiler/ResourceMaterialization.h" #include "graphics/shader/recompiler/ir/ResourceMaterialization.h"
#include "graphics/shader/recompiler/ResourceTracking.h" #include "graphics/shader/recompiler/ir/ResourceTracking.h"
#include "graphics/shader/recompiler/ScalarProvenance.h" #include "graphics/shader/recompiler/ir/ScalarProvenance.h"
#include "graphics/shader/recompiler/ShaderCFG.h" #include "graphics/shader/recompiler/cfg/ShaderCFG.h"
#include "graphics/shader/recompiler/ShaderDecoder.h" #include "graphics/shader/recompiler/decompiler/ShaderDecoder.h"
#include "graphics/shader/recompiler/ShaderIR.h" #include "graphics/shader/recompiler/ir/ShaderIR.h"
#include "graphics/shader/recompiler/ShaderInfoCollection.h" #include "graphics/shader/recompiler/ir/ShaderInfoCollection.h"
#include "graphics/shader/recompiler/SpirvEmitter.h" #include "graphics/shader/recompiler/emitter/SpirvEmitter.h"
#include "graphics/shader/recompiler/SrtPatcher.h" #include "graphics/shader/recompiler/ir/SrtPatcher.h"
#include "graphics/shader/recompiler/SrtWalker.h" #include "graphics/shader/recompiler/ir/SrtWalker.h"
#include <algorithm> #include <algorithm>
#include <array> #include <array>
@@ -3,7 +3,7 @@
#include "common/common.h" #include "common/common.h"
#include "common/stringUtils.h" #include "common/stringUtils.h"
#include "graphics/shader/recompiler/ResourceMaterialization.h" #include "graphics/shader/recompiler/ir/ResourceMaterialization.h"
#include "graphics/shader/shader.h" #include "graphics/shader/shader.h"
#include <optional> #include <optional>
@@ -1,4 +1,4 @@
#include "graphics/shader/recompiler/ShaderCFG.h" #include "graphics/shader/recompiler/cfg/ShaderCFG.h"
#include <algorithm> #include <algorithm>
#include <fmt/format.h> #include <fmt/format.h>
@@ -3,7 +3,7 @@
#include "common/common.h" #include "common/common.h"
#include "common/stringUtils.h" #include "common/stringUtils.h"
#include "graphics/shader/recompiler/ShaderDecoder.h" #include "graphics/shader/recompiler/decompiler/ShaderDecoder.h"
#include <vector> #include <vector>
@@ -1,4 +1,4 @@
#include "graphics/shader/recompiler/ExportOps.h" #include "graphics/shader/recompiler/decompiler/ExportOps.h"
#include <fmt/format.h> #include <fmt/format.h>
@@ -1,7 +1,7 @@
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_EXPORTOPS_H_ #ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_EXPORTOPS_H_
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_EXPORTOPS_H_ #define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_EXPORTOPS_H_
#include "graphics/shader/recompiler/ShaderDecoder.h" #include "graphics/shader/recompiler/decompiler/ShaderDecoder.h"
namespace Libs::Graphics::ShaderRecompiler::Decoder { namespace Libs::Graphics::ShaderRecompiler::Decoder {
@@ -1,4 +1,4 @@
#include "graphics/shader/recompiler/ImageOps.h" #include "graphics/shader/recompiler/decompiler/ImageOps.h"
#include <algorithm> #include <algorithm>
#include <fmt/format.h> #include <fmt/format.h>
@@ -1,7 +1,7 @@
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_IMAGEOPS_H_ #ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_IMAGEOPS_H_
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_IMAGEOPS_H_ #define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_IMAGEOPS_H_
#include "graphics/shader/recompiler/ShaderDecoder.h" #include "graphics/shader/recompiler/decompiler/ShaderDecoder.h"
namespace Libs::Graphics::ShaderRecompiler::Decoder { namespace Libs::Graphics::ShaderRecompiler::Decoder {
@@ -1,4 +1,4 @@
#include "graphics/shader/recompiler/MemoryOps.h" #include "graphics/shader/recompiler/decompiler/MemoryOps.h"
#include <fmt/format.h> #include <fmt/format.h>
#include <iterator> #include <iterator>
@@ -1,7 +1,7 @@
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_MEMORYOPS_H_ #ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_MEMORYOPS_H_
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_MEMORYOPS_H_ #define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_MEMORYOPS_H_
#include "graphics/shader/recompiler/ShaderDecoder.h" #include "graphics/shader/recompiler/decompiler/ShaderDecoder.h"
namespace Libs::Graphics::ShaderRecompiler::Decoder { namespace Libs::Graphics::ShaderRecompiler::Decoder {
@@ -1,4 +1,4 @@
#include "graphics/shader/recompiler/ScalarAluOps.h" #include "graphics/shader/recompiler/decompiler/ScalarAluOps.h"
#include <iterator> #include <iterator>
@@ -1,7 +1,7 @@
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_SCALARALUOPS_H_ #ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_SCALARALUOPS_H_
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_SCALARALUOPS_H_ #define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_SCALARALUOPS_H_
#include "graphics/shader/recompiler/ShaderDecoder.h" #include "graphics/shader/recompiler/decompiler/ShaderDecoder.h"
namespace Libs::Graphics::ShaderRecompiler::Decoder { namespace Libs::Graphics::ShaderRecompiler::Decoder {
@@ -1,10 +1,10 @@
#include "graphics/shader/recompiler/ShaderDecoder.h" #include "graphics/shader/recompiler/decompiler/ShaderDecoder.h"
#include "graphics/shader/recompiler/ExportOps.h" #include "graphics/shader/recompiler/decompiler/ExportOps.h"
#include "graphics/shader/recompiler/ImageOps.h" #include "graphics/shader/recompiler/decompiler/ImageOps.h"
#include "graphics/shader/recompiler/MemoryOps.h" #include "graphics/shader/recompiler/decompiler/MemoryOps.h"
#include "graphics/shader/recompiler/ScalarAluOps.h" #include "graphics/shader/recompiler/decompiler/ScalarAluOps.h"
#include "graphics/shader/recompiler/VectorAluOps.h" #include "graphics/shader/recompiler/decompiler/VectorAluOps.h"
#include <algorithm> #include <algorithm>
#include <bit> #include <bit>
@@ -1,4 +1,4 @@
#include "graphics/shader/recompiler/VectorAluOps.h" #include "graphics/shader/recompiler/decompiler/VectorAluOps.h"
#include <fmt/format.h> #include <fmt/format.h>
#include <iterator> #include <iterator>
@@ -1,7 +1,7 @@
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_VECTORALUOPS_H_ #ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_VECTORALUOPS_H_
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_VECTORALUOPS_H_ #define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_VECTORALUOPS_H_
#include "graphics/shader/recompiler/ShaderDecoder.h" #include "graphics/shader/recompiler/decompiler/ShaderDecoder.h"
namespace Libs::Graphics::ShaderRecompiler::Decoder { namespace Libs::Graphics::ShaderRecompiler::Decoder {
@@ -1,4 +1,4 @@
#include "graphics/shader/recompiler/SpirvBuilder.h" #include "graphics/shader/recompiler/emitter/SpirvBuilder.h"
#include <cstdio> #include <cstdio>
#include <cstring> #include <cstring>
@@ -1,7 +1,7 @@
#include "graphics/shader/recompiler/SpirvEmitter.h" #include "graphics/shader/recompiler/emitter/SpirvEmitter.h"
#include "graphics/shader/recompiler/SrtWalker.h" #include "graphics/shader/recompiler/ir/SrtWalker.h"
#include "graphics/shader/recompiler/spirvEmitter/spirvEmitterInternal.h" #include "graphics/shader/recompiler/emitter/spirvEmitterInternal.h"
#include <algorithm> #include <algorithm>
#include <array> #include <array>
@@ -3,7 +3,7 @@
#include "common/common.h" #include "common/common.h"
#include "common/stringUtils.h" #include "common/stringUtils.h"
#include "graphics/shader/recompiler/ResourceMaterialization.h" #include "graphics/shader/recompiler/ir/ResourceMaterialization.h"
#include <vector> #include <vector>
@@ -1,4 +1,4 @@
#include "graphics/shader/recompiler/spirvEmitter/spirvEmitterInternal.h" #include "graphics/shader/recompiler/emitter/spirvEmitterInternal.h"
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter { namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
@@ -1,7 +1,7 @@
#include "common/assert.h" #include "common/assert.h"
#include "graphics/guest_gpu/gpu_defs.h" #include "graphics/guest_gpu/gpu_defs.h"
#include "graphics/shader/recompiler/SpirvEmitter.h" #include "graphics/shader/recompiler/emitter/SpirvEmitter.h"
#include "graphics/shader/recompiler/spirvEmitter/spirvEmitterInternal.h" #include "graphics/shader/recompiler/emitter/spirvEmitterInternal.h"
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter { namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
@@ -1,4 +1,4 @@
#include "graphics/shader/recompiler/spirvEmitter/spirvEmitterInternal.h" #include "graphics/shader/recompiler/emitter/spirvEmitterInternal.h"
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter { namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
@@ -1,4 +1,4 @@
#include "graphics/shader/recompiler/spirvEmitter/spirvEmitterInternal.h" #include "graphics/shader/recompiler/emitter/spirvEmitterInternal.h"
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter { namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
@@ -1,4 +1,4 @@
#include "graphics/shader/recompiler/spirvEmitter/spirvEmitterInternal.h" #include "graphics/shader/recompiler/emitter/spirvEmitterInternal.h"
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter { namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
@@ -1,5 +1,5 @@
#include "common/assert.h" #include "common/assert.h"
#include "graphics/shader/recompiler/spirvEmitter/spirvEmitterInternal.h" #include "graphics/shader/recompiler/emitter/spirvEmitterInternal.h"
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter { namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
@@ -1,4 +1,4 @@
#include "graphics/shader/recompiler/spirvEmitter/spirvEmitterInternal.h" #include "graphics/shader/recompiler/emitter/spirvEmitterInternal.h"
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter { namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
@@ -3,11 +3,11 @@
#include "common/common.h" #include "common/common.h"
#include "common/stringUtils.h" #include "common/stringUtils.h"
#include "graphics/shader/recompiler/BindingLayout.h" #include "graphics/shader/recompiler/ir/BindingLayout.h"
#include "graphics/shader/recompiler/BufferFormat.h" #include "graphics/shader/recompiler/BufferFormat.h"
#include "graphics/shader/recompiler/ResourceMaterialization.h" #include "graphics/shader/recompiler/ir/ResourceMaterialization.h"
#include "graphics/shader/recompiler/ShaderIR.h" #include "graphics/shader/recompiler/ir/ShaderIR.h"
#include "graphics/shader/recompiler/SpirvBuilder.h" #include "graphics/shader/recompiler/emitter/SpirvBuilder.h"
#include <algorithm> #include <algorithm>
#include <array> #include <array>
@@ -1,4 +1,4 @@
#include "graphics/shader/recompiler/spirvEmitter/spirvEmitterInternal.h" #include "graphics/shader/recompiler/emitter/spirvEmitterInternal.h"
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter { namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
@@ -1,4 +1,4 @@
#include "graphics/shader/recompiler/spirvEmitter/spirvEmitterInternal.h" #include "graphics/shader/recompiler/emitter/spirvEmitterInternal.h"
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter { namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {

Some files were not shown because too many files have changed in this diff Show More