Compare commits

...
Author SHA1 Message Date
Claxtenandnmzik c1a5927036 graphics: pm4: accept trailing PM4 type-2 packets
* A one-dword type-2 NOP is a valid packet tail. Parse it normally instead of aborting command-buffer dumps.

Signed-off-by: Claxten <claxten10@gmail.com>
2026-08-02 02:07:37 +02:00
Claxtenandnmzik bc436548a9 graphics: support packed 10-10-10-2 uint buffers
Signed-off-by: Claxten <claxten10@gmail.com>
2026-08-02 01:44:21 +02:00
nmzik a65d17a5d6 renderer: skip debug checks when stencil&depth is not active 2026-08-01 00:22:24 +02:00
nmzik c690aeea62 add HiS PM4 handler, accept Ngs2CustomMastering 2026-08-01 00:12:42 +02:00
nmzik f830d6b2e4 renderer: remove legacy code left from refactoring 2026-07-31 23:13:48 +02:00
nmzik d68a477276 renderer: broaden compatibility 2026-07-31 22:24:56 +02:00
nmzik 8977d4d2f0 shader: add descriptor log 2026-07-31 21:29:26 +02:00
nmzik 4b4e3bf3cf pm4: implement missing selectors 2026-07-31 19:45:47 +02:00
nmzik a0bb129f02 SaveData: stop escaping root directory 2026-07-31 18:53:54 +02:00
nmzik 846002c5eb shader: fix invalid texture descriptors and add missing format 2026-07-31 17:30:31 +02:00
nmzik d8a4c83cc7 format src and tests with clang-format 2026-07-31 11:36:12 +02:00
nmzik 68be13345a fix(shader): support multisampled depth image loads 2026-07-31 11:36:12 +02:00
nmzik 167da0abe0 shader: fix readlane/writelane for inactive host lanes 2026-07-31 11:36:12 +02:00
nmzik 48c31d61ee Implement VideoDec2 2026-07-31 11:36:12 +02:00
nmzik 212282d693 fix(shader): preserve packed UINT16 MRT exports 2026-07-31 11:36:12 +02:00
nmzik 6bca35d1f5 renderer: broaden compatibility 2026-07-31 11:36:12 +02:00
M. AbdullahandGitHub e4ad5fc988 docs: add macOS build and run instructions (#137)
The README had macOS badges and an experimental-support note but no build,
run, or system-requirement information for the platform. Document the
Rosetta 2 / MoltenVK setup, the x86-64 configure invocation, the Qt
universal-build requirement, MoltenVK installation and signing, and the
SDL_VULKAN_LIBRARY variable needed at run time.
2026-07-31 05:32:29 +02:00
nmzik c0d3d261ea add TextToSpeech2 stubs 2026-07-31 04:05:50 +02:00
3b75a5659a shader: specialize cube image descriptors (#134)
* shader: specialize cube image descriptors

Track whether image descriptors refer to cube maps during resource specialization, and apply the coordinate offset conversion when sampling cube maps as 2D image arrays in SPIR-V emission.

* shader: fix cube array coordinate lowering

---------

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-07-31 03:59:10 +02:00
M. AbdullahandGitHub d475387171 macOS: enable guest signal dispatch on the target thread (#136)
macos: enable guest signal dispatch on the target thread

The POSIX signal-dispatch path (pthread_kill based, added with the Linux
port) was compiled out on macOS, leaving KernelRaiseException to run the
guest handler on the calling thread. IL2CPP's garbage collector raises its
stop-the-world signal at every managed thread and each handler parks its
own thread until resume, so the collector parked itself and every Unity
title froze on the first collection.

Enable the same delivery path on macOS:
- translate between the Darwin mcontext (uc_mcontext->__ss) and the guest
  ucontext in CreateSignalUcontextFromHost/ApplySignalUcontextToHost
- use SIGUSR1 as the host dispatch signal (macOS has no realtime signals)
- block the dispatch signal inside the host fault handler so a suspend
  request cannot preempt fault resolution between the protection fix and
  the retry

Windows and Linux are unchanged.
2026-07-31 03:47:04 +02:00
nmzikandGitHub 2f5396c6a5 Rework guest memory tracking/virtual address space/direct and flexible memory (#135)
* Rework guest memory tracking

* add unknwon flag

* Fix macOS guest address-space reservation
2026-07-31 03:07:17 +02:00
ecb48f90bb Emulate SHA-NI and fix SSE4a EXTRQ/INSERTQ register form (#126)
* Emulate SHA-NI on illegal instruction faults

* Fix SSE4a EXTRQ/INSERTQ register form

* Fix SHA-NI memory operand emulation

* Revert "Fix SSE4a EXTRQ/INSERTQ register form"

This reverts commit ea2b54a4d0.

---------

Co-authored-by: neobugs1 <neobugs1@users.noreply.github.com>
Co-authored-by: nmzik <Nmzik@mail.ru>
2026-07-30 16:21:36 +02:00
nmzik 77aa28b27c update README 2026-07-30 05:16:37 +02:00
nmzikandGitHub d04938c88c Embedded fetch shader: Fix overlapping buffer loads (#133)
Fix overlapping buffer loads. Fixes many games
2026-07-30 05:08:38 +02:00
nmzikandGitHub 85622befb8 Fix fabricated HTTP2 success (#129)
@StefanosCosta Thanks!
2026-07-30 00:48:08 +02:00
nmzik 3965d41d36 texture_cache: fix exact-match reuse across different tile modes 2026-07-30 00:10:40 +02:00
nmzik c508c4a9c0 shader_recompiler: allow GDS append/consume offsets 2026-07-30 00:10:40 +02:00
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
nmzik c71bb9fc9e build: make clang-tidy checks opt-in 2026-07-27 16:33:22 +02:00
nmzikandGitHub f60f80b631 launcher: add local game patches (#111)
launcher: add game patches
2026-07-27 16:19:31 +02:00
Abdullah K.andnmzik 31ea0081a9 perf: skip LOGF formatting when logging is silent
Build KytyPS5 (Linux) / build (push) Waiting to run
Build KytyPS5 / build (push) Waiting to run
Build KytyPS5 / release (push) Blocked by required conditions
LOGF/LOGF_COLOR always ran fmt::sprintf even when the output was discarded; at
emulator log volume the formatting alone costs frames. Add Log::IsSilent() and
short-circuit before formatting. Behavior-preserving on all platforms (it only
skips work whose result is thrown away); before init it reports non-silent so
early logs still print.

(cherry picked from commit 1749e0fc68)
2026-07-27 14:21:03 +02:00
Abdullah K.andnmzik 7921269878 build: add macOS (Apple Silicon) build support
macOS compiles the existing POSIX/Linux code paths (KYTY_PLATFORM_LINUX)
targeting x86_64 under Rosetta 2. Adds an APPLE branch to the build:
selects Apple's ld64 linker, unsets the Ninja response-file forcing (Apple
ar has no @file support), derives the target arch from CMAKE_OSX_ARCHITECTURES
for cpuinfo/ffmpeg, and re-signs the binary post-build with JIT entitlements
(required to execute written trampolines and Rosetta-translated guest code).

All changes are guarded by if(APPLE); Windows and Linux configuration is
unchanged.

(cherry picked from commit 13be13b757)
2026-07-27 14:16:51 +02:00
nmzik e42f6c3892 ci: add Linux build workflow 2026-07-27 14:13:34 +02:00
nmzik 7351eae117 graphics: add 1D image descriptor support 2026-07-27 14:10:51 +02:00
nmzik 125287dc30 graphics: broaden depth compatibility 2026-07-27 10:29:37 +02:00
nmzik 142b3c20fa graphics: unify buffer bindings and depth target handling 2026-07-27 10:12:41 +02:00
nmzik f43567e12c shader: validate MSAA image descriptors during materialization 2026-07-27 10:12:41 +02:00
nmzik c86d4b1f0d Fix linux compilation 2026-07-27 10:12:41 +02:00
nmzik 31fb3ec5af graphics: broaden storage image and view format compatibility 2026-07-27 10:12:41 +02:00
nmzik c3b2ae9733 graphics: support more depth-tiled storage image formats 2026-07-27 10:12:41 +02:00
nmzik 93774ee37c fix teardown 2026-07-27 02:27:37 +02:00
nmzik a20cf5d298 refactor renderer 2026-07-27 02:27:29 +02:00
nmzik a720b28b7f graphics: cleanup 2026-07-23 23:21:26 +02:00
nmzik 7638c25d76 renderer: replace generation retries with staged resource binding 2026-07-23 22:32:30 +02:00
nmzik 85876388cf gpuTiler: fix performance (dispatch a 2D 8×8 workgroup) 2026-07-22 23:36:00 +02:00
nmzik f60007e923 refactor(gpu): use a single ordered PM4 scheduler 2026-07-22 23:15:03 +02:00
nmzik dbcb444c05 unify graphics and compute on one Vulkan queue 2026-07-22 17:44:38 +02:00
nmzikandGitHub cf595beedf Install glslang for Windows CI
Install glslang with validator tools and optimizer support, add it to PATH, and verify it before configuring the Windows build.
2026-07-22 00:32:54 +02:00
nmzik 8a4600c5e9 graphics: fix initialization 2026-07-21 07:37:52 +02:00
nmzik 0a5f78c727 graphics: refactoring #2 2026-07-21 07:37:52 +02:00
nmzik b51d4322cc Add GPU tilers/tests 2026-07-21 07:37:52 +02:00
nmzik 728b5b7d01 fix: render-target aliases were synchronously read back and copied through host memory twice 2026-07-21 07:37:52 +02:00
nmzik 42f634a751 fix fetch shader V_SAD_U32, video-out buffer overlap 2026-07-21 07:37:52 +02:00
nmzik aebf6b4913 graphics: synchronize host file writes and depth clipping
Map paired PS5 depth-clip state through Vulkan pipeline creation and preserve asymmetric hard failures. Prepare GPU-mapped guest ranges before kernel file reads so buffer, texture, and alias ownership remains coherent. Add focused regression coverage and update the current launch target.
2026-07-21 07:37:52 +02:00
nmzik 91690ac29e Implement texture-alias, mip-tail 2026-07-21 07:37:52 +02:00
nmzik 999f87576e graphics: add native PS5 MSAA and safe image allocation reuse 2026-07-21 07:37:52 +02:00
297 changed files with 55142 additions and 50043 deletions
+315 -14
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,6 +23,34 @@ jobs:
- name: Setup Ninja - name: Setup Ninja
uses: seanmiddleditch/gha-setup-ninja@v5 uses: seanmiddleditch/gha-setup-ninja@v5
- name: Locate vcpkg
id: vcpkg
shell: pwsh
run: |
$vcpkgRoot = Split-Path (Get-Command vcpkg).Source
$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" |
Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
- name: Install Qt 6.10.3 - name: Install Qt 6.10.3
uses: jurplel/install-qt-action@v4 uses: jurplel/install-qt-action@v4
with: with:
@@ -38,6 +67,7 @@ jobs:
cmake --version cmake --version
ninja --version ninja --version
clang-cl --version clang-cl --version
glslangValidator --version
echo Qt6_DIR=%Qt6_DIR% echo Qt6_DIR=%Qt6_DIR%
- name: Configure - name: Configure
@@ -53,52 +83,323 @@ jobs:
- name: Build - name: Build
shell: cmd shell: cmd
run: | run: |
cmake --build _Build/windows --target launcher --parallel cmake --build _Build/windows --target launcher virtual_memory_allocation_tests --parallel
- name: Test
shell: cmd
run: |
ctest --test-dir _Build/windows --output-on-failure -R "^virtual_memory_allocation$"
- name: Install - name: Install
shell: cmd shell: cmd
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 virtual_memory_allocation_tests \
--parallel
- name: Test
shell: bash
run: |
ctest --test-dir _Build/macos --output-on-failure \
-R '^virtual_memory_allocation$'
- 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 \
virtual_memory_allocation_tests \
--parallel
- name: Test
shell: bash
run: |
ctest --test-dir _Build/linux --output-on-failure \
-R '^(page_manager|memory_tracker|virtual_memory_allocation)$'
- 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/
+11 -1
View File
@@ -44,7 +44,17 @@ set(SPDLOG_NO_EXCEPTIONS ON CACHE BOOL "" FORCE)
add_subdirectory(spdlog EXCLUDE_FROM_ALL) add_subdirectory(spdlog EXCLUDE_FROM_ALL)
if (NOT TARGET FFmpeg::ffmpeg) if (NOT TARGET FFmpeg::ffmpeg)
if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(aarch64|arm64|ARM64)$") # On macOS the target arch is driven by CMAKE_OSX_ARCHITECTURES, not the host
# CMAKE_SYSTEM_PROCESSOR (which reports arm64 even for an x86_64/Rosetta build).
if(APPLE AND CMAKE_OSX_ARCHITECTURES)
if(CMAKE_OSX_ARCHITECTURES MATCHES "^(x86_64)$")
set(ARCHITECTURE x86_64)
elseif(CMAKE_OSX_ARCHITECTURES MATCHES "^(arm64)$")
set(ARCHITECTURE arm64)
else()
set(ARCHITECTURE x86_64 arm64) # universal
endif()
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^(aarch64|arm64|ARM64)$")
set(ARCHITECTURE arm64) set(ARCHITECTURE arm64)
else() else()
set(ARCHITECTURE x86_64) set(ARCHITECTURE x86_64)
+12 -3
View File
@@ -5,7 +5,14 @@ set(cpuinfo_src
deps/clog/src/clog.c deps/clog/src/clog.c
) )
if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(i[3-6]86|AMD64|x86(_64)?)$") # On Apple the target arch is driven by CMAKE_OSX_ARCHITECTURES, not the host
# CMAKE_SYSTEM_PROCESSOR (which is arm64 even for an x86_64/Rosetta build).
set(CPUINFO_TARGET_PROC "${CMAKE_SYSTEM_PROCESSOR}")
if(APPLE AND CMAKE_OSX_ARCHITECTURES)
list(GET CMAKE_OSX_ARCHITECTURES 0 CPUINFO_TARGET_PROC)
endif()
if(CPUINFO_TARGET_PROC MATCHES "^(i[3-6]86|AMD64|x86(_64)?)$")
list(APPEND cpuinfo_src list(APPEND cpuinfo_src
src/x86/init.c src/x86/init.c
src/x86/info.c src/x86/info.c
@@ -19,11 +26,13 @@ if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(i[3-6]86|AMD64|x86(_64)?)$")
src/x86/cache/deterministic.c) src/x86/cache/deterministic.c)
if(LINUX OR ANDROID) if(LINUX OR ANDROID)
list(APPEND cpuinfo_src src/x86/linux/init.c src/x86/linux/cpuinfo.c) list(APPEND cpuinfo_src src/x86/linux/init.c src/x86/linux/cpuinfo.c)
elseif(APPLE)
list(APPEND cpuinfo_src src/x86/mach/init.c src/mach/topology.c)
else() else()
list(APPEND cpuinfo_src src/x86/windows/init.c) list(APPEND cpuinfo_src src/x86/windows/init.c)
endif() endif()
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^(armv[5-8].*|aarch64|arm64)$") elseif(CPUINFO_TARGET_PROC MATCHES "^(armv[5-8].*|aarch64|arm64)$")
list(APPEND cpuinfo_src src/arm/uarch.c src/arm/cache.c) list(APPEND cpuinfo_src src/arm/uarch.c src/arm/cache.c)
if(LINUX OR ANDROID) if(LINUX OR ANDROID)
+129 -15
View File
@@ -1,13 +1,16 @@
# KytyPS5 # KytyPS5
[![Platform](https://img.shields.io/badge/platform-Windows%20x64-0078D4.svg)](#system-requirements) [![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)
[![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)
[![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
@@ -21,7 +24,12 @@ 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. The emulator is built for x86-64 and runs on Apple Silicon under
Rosetta 2, with Vulkan provided by MoltenVK. A small number of titles have been verified in-game
on Apple Silicon hardware; see [Building on macOS](#building-on-macos).
## Bugs and Issues ## Bugs and Issues
@@ -43,7 +51,7 @@ graphical glitches, low compatibility, and poor performance.
</tr> </tr>
<tr> <tr>
<td align="center"> <td align="center">
<strong>Minecraft Legends</strong><br> <strong>Neptunia ReVerse</strong><br>
<img src="docs/screenshots/ps5-04.png" width="300" alt="Minecraft Legends running in KytyPS5"> <img src="docs/screenshots/ps5-04.png" width="300" alt="Minecraft Legends running in KytyPS5">
</td> </td>
<td align="center"> <td align="center">
@@ -51,15 +59,28 @@ graphical glitches, low compatibility, and poor performance.
<img src="docs/screenshots/ps5-05.png" width="300" alt="SILENT HILL: The Short Message running in KytyPS5"> <img src="docs/screenshots/ps5-05.png" width="300" alt="SILENT HILL: The Short Message running in KytyPS5">
</td> </td>
</tr> </tr>
<tr>
<td align="center">
<strong>Hellboy</strong><br>
<img src="docs/screenshots/ps5-02.png" width="300" alt="Disgaea 6 running in KytyPS5">
</td>
<td align="center">
<strong>Paleo Pines</strong><br>
<img src="docs/screenshots/ps5-06.png" width="300" alt="Dreaming Sarah running in KytyPS5">
</td>
</tr>
</table> </table>
<p align="center"><em>And many more...</em></p>
## Contributing ## Contributing
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
@@ -94,11 +115,12 @@ the Vulkan/SPIR-V validation rules.
### System requirements ### System requirements
- Windows 10 version 1803 - Windows 10 version 1803, a current Linux distribution, or macOS on Apple Silicon
- A 64-bit x86 processor - A 64-bit x86 processor (on macOS, an Apple Silicon processor with Rosetta 2)
- A Vulkan 1.3-capable GPU with current drivers - A Vulkan 1.3-capable GPU with current drivers (on macOS, Vulkan is provided by the bundled
MoltenVK)
### Build requirements ### Build requirements (Windows)
- Git - Git
- CMake 3.12 or newer - CMake 3.12 or newer
@@ -132,11 +154,87 @@ 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.
### Building on macOS
macOS builds target x86-64 and run under Rosetta 2 on Apple Silicon, so the PS5's x86-64 game
code executes through the same translation layer as the emulator itself. Prebuilt archives are
attached to releases; the steps below are for building from source.
Requirements:
- An Apple Silicon Mac with Rosetta 2 installed (`softwareupdate --install-rosetta`)
- Xcode (or the Command Line Tools)
- Homebrew packages: `brew install cmake ninja glslang`
- Qt 6 (Concurrent, Network, Widgets) with x86-64 support. The official Qt installation is
universal and works; Homebrew's Qt is arm64-only and will not link
```bash
git submodule update --init --recursive
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="$Qt6_DIR"
cmake --build _Build/macos --target launcher --parallel
cmake --install _Build/macos --prefix _Build/macos/install
```
The build re-signs `kyty_emulator` with the JIT entitlements it needs to execute translated
guest code; no manual signing step is required.
Vulkan comes from MoltenVK. Download `MoltenVK-macos.tar` from the
[MoltenVK releases](https://github.com/KhronosGroup/MoltenVK/releases), then copy
`MoltenVK/dynamic/dylib/macOS/libMoltenVK.dylib` next to `kyty_emulator` and ad-hoc sign it:
```bash
codesign --force --sign - _Build/macos/install/libMoltenVK.dylib
```
Release archives already include a signed `libMoltenVK.dylib`.
### 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:
@@ -158,6 +256,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.
@@ -168,7 +270,19 @@ 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"
```
On macOS, point SDL at the MoltenVK library explicitly; the hardened runtime prevents it from
being picked up from the executable's directory:
```bash
cd _Build/macos/install
SDL_VULKAN_LIBRARY="$PWD/libMoltenVK.dylib" ./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
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 MiB

After

Width:  |  Height:  |  Size: 2.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

+227 -25
View File
@@ -10,15 +10,19 @@ if(CMAKE_SYSTEM_NAME MATCHES ".*Linux")
set(LINUX TRUE) set(LINUX TRUE)
endif() endif()
if (NOT (WIN32 OR LINUX)) if (NOT (WIN32 OR LINUX OR APPLE))
message(FATAL_ERROR "only Windows and Linux builds are supported") message(FATAL_ERROR "only Windows, Linux, and macOS builds are supported")
endif() endif()
set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_CXX_SCAN_FOR_MODULES OFF)
include(utils.cmake) include(utils.cmake)
include(CTest)
option(KYTY_ENABLE_CLANG_TIDY "Run clang-tidy checks during builds" OFF)
set(KYTY_THIRD_PARTY_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty") set(KYTY_THIRD_PARTY_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty")
@@ -43,8 +47,9 @@ else()
set(KYTY_BUILD KYTY_BUILD_RELEASE) set(KYTY_BUILD KYTY_BUILD_RELEASE)
endif() endif()
if(LINUX) if(LINUX OR APPLE)
set(KYTY_PLATFORM KYTY_PLATFORM_LINUX) # macOS rides the POSIX/Linux code paths until it gets a dedicated platform
set(KYTY_PLATFORM KYTY_PLATFORM_LINUX)
else() else()
set(KYTY_PLATFORM KYTY_PLATFORM_WINDOWS) set(KYTY_PLATFORM KYTY_PLATFORM_WINDOWS)
endif() endif()
@@ -61,6 +66,8 @@ if(CMAKE_CXX_COMPILER_ID MATCHES "^(Apple)?Clang$")
if(WIN32 AND KYTY_CXX_COMPILER_NAME STREQUAL "clang-cl") if(WIN32 AND KYTY_CXX_COMPILER_NAME STREQUAL "clang-cl")
set(KYTY_CLANG_CL TRUE) set(KYTY_CLANG_CL TRUE)
set(KYTY_LINKER LLD_LINK) set(KYTY_LINKER LLD_LINK)
elseif(APPLE)
set(KYTY_LINKER LD64) # Apple's default linker; lld flags don't apply
else() else()
set(KYTY_LINKER LLD) set(KYTY_LINKER LLD)
endif() endif()
@@ -113,6 +120,7 @@ add_custom_target( KytyGitVersion
-D INPUT_FILE=${CMAKE_CURRENT_SOURCE_DIR}/kytyGitVersion.h.in -D INPUT_FILE=${CMAKE_CURRENT_SOURCE_DIR}/kytyGitVersion.h.in
-D OUTPUT_FILE=${CMAKE_CURRENT_BINARY_DIR}/kytyGitVersion.h -D OUTPUT_FILE=${CMAKE_CURRENT_BINARY_DIR}/kytyGitVersion.h
-D GIT_EXECUTABLE=${GIT_EXECUTABLE} -D GIT_EXECUTABLE=${GIT_EXECUTABLE}
-D GIT_WORKING_DIRECTORY=${CMAKE_CURRENT_SOURCE_DIR}
-P ${CMAKE_CURRENT_SOURCE_DIR}/generate_version.cmake -P ${CMAKE_CURRENT_SOURCE_DIR}/generate_version.cmake
COMMENT "Generate kytyGitVersion.h" COMMENT "Generate kytyGitVersion.h"
) )
@@ -123,7 +131,7 @@ if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 12.0.0)
common common
#launcher #launcher
) )
list(APPEND KYTY_CLANG_TYDY list(APPEND KYTY_CLANG_TIDY
kyty_emulator kyty_emulator
#common #common
launcher launcher
@@ -150,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
@@ -170,6 +186,67 @@ file(GLOB kyty_emulator_src CONFIGURE_DEPENDS
loader/*.h loader/*.h
) )
find_program(KYTY_GLSLANG_VALIDATOR glslangValidator REQUIRED)
set(gpu_tiler_shader_dir "${CMAKE_CURRENT_SOURCE_DIR}/graphics/host_gpu/shaders")
set(gpu_tiler_generated_dir "${PROJECT_BINARY_DIR}/gpu_tiler_shaders")
set(gpu_tiler_shader_names
standard256
standard4
standard4_3d
standard64
standard64_3d
prt
prt_3d
render_target
depth
promote_d16
demote_d16
swap_bgra16
)
file(GLOB gpu_tiler_shader_includes CONFIGURE_DEPENDS "${gpu_tiler_shader_dir}/gpu_tiler_*.inc")
foreach(shader_name IN LISTS gpu_tiler_shader_names)
set(shader_source "${gpu_tiler_shader_dir}/gpu_tiler_${shader_name}.comp")
set(shader_spv "${gpu_tiler_generated_dir}/gpu_tiler_${shader_name}.spv")
set(shader_header "${gpu_tiler_generated_dir}/gpu_tiler_${shader_name}_spv.h")
string(TOUPPER "GPU_TILER_${shader_name}_SPV" shader_symbol)
add_custom_command(
OUTPUT "${shader_header}"
COMMAND ${CMAKE_COMMAND} -E make_directory "${gpu_tiler_generated_dir}"
COMMAND "${KYTY_GLSLANG_VALIDATOR}" -V --target-env vulkan1.0 -Os
"-I${gpu_tiler_shader_dir}" -o "${shader_spv}" "${shader_source}"
COMMAND ${CMAKE_COMMAND} -DINPUT=${shader_spv} -DOUTPUT=${shader_header}
-DSYMBOL=${shader_symbol} -P "${CMAKE_CURRENT_SOURCE_DIR}/embed_spirv.cmake"
DEPENDS "${shader_source}" ${gpu_tiler_shader_includes}
VERBATIM
)
list(APPEND gpu_tiler_shader_headers "${shader_header}")
endforeach()
list(APPEND kyty_emulator_src ${gpu_tiler_shader_headers})
set(gpu_blit_generated_dir "${PROJECT_BINARY_DIR}/gpu_blit_shaders")
set(gpu_blit_shader_sources
"${gpu_tiler_shader_dir}/gpu_blit_fs_triangle.vert"
"${gpu_tiler_shader_dir}/gpu_blit_color_to_ms_depth.frag"
)
foreach(shader_source IN LISTS gpu_blit_shader_sources)
get_filename_component(shader_name "${shader_source}" NAME_WE)
set(shader_spv "${gpu_blit_generated_dir}/${shader_name}.spv")
set(shader_header "${gpu_blit_generated_dir}/${shader_name}_spv.h")
string(TOUPPER "${shader_name}_SPV" shader_symbol)
add_custom_command(
OUTPUT "${shader_header}"
COMMAND ${CMAKE_COMMAND} -E make_directory "${gpu_blit_generated_dir}"
COMMAND "${KYTY_GLSLANG_VALIDATOR}" -V --target-env vulkan1.0 -Os
"-I${gpu_tiler_shader_dir}" -o "${shader_spv}" "${shader_source}"
COMMAND ${CMAKE_COMMAND} -DINPUT=${shader_spv} -DOUTPUT=${shader_header}
-DSYMBOL=${shader_symbol} -P "${CMAKE_CURRENT_SOURCE_DIR}/embed_spirv.cmake"
DEPENDS "${shader_source}"
VERBATIM
)
list(APPEND gpu_blit_shader_headers "${shader_header}")
endforeach()
list(APPEND kyty_emulator_src ${gpu_blit_shader_headers})
list(APPEND kyty_emulator_src list(APPEND kyty_emulator_src
emulator.h emulator.h
emulator.cpp emulator.cpp
@@ -182,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
@@ -217,13 +314,24 @@ function(add_kyty_full_emulator_test target source)
endif() endif()
endfunction() endfunction()
function(configure_macos_guest_address_space target)
if(APPLE AND (CMAKE_OSX_ARCHITECTURES STREQUAL "x86_64" OR
(NOT CMAKE_OSX_ARCHITECTURES AND CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|AMD64)$")))
target_sources(${target} PRIVATE kernel/macosGuestAddressSpace.cpp)
target_compile_definitions(${target} PRIVATE KYTY_LINKED_GUEST_ADDRESS_SPACE=1)
target_link_options(${target} PRIVATE
-Wl,-ld_classic,-no_pie,-no_fixup_chains,-no_huge,-pagezero_size,0x40000,-segaddr,SYSTEM_MANAGED,0x40000,-segaddr,SYSTEM_RESERVED,0x7ffffc000,-segaddr,USER_AREA,0x7000000000,-image_base,0x700000000000)
endif()
endfunction()
add_kyty_full_emulator_test(shader_cfg_tests ../tests/shaderCfgTests.cpp) 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/ReadLaneElimination.cpp
graphics/shader/recompiler/SrtWalker.cpp graphics/shader/recompiler/ir/ScalarProvenance.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})
@@ -241,7 +349,6 @@ add_executable(memory_tracker_tests EXCLUDE_FROM_ALL
) )
target_link_libraries(memory_tracker_tests fmt::fmt common) target_link_libraries(memory_tracker_tests fmt::fmt common)
target_include_directories(memory_tracker_tests PRIVATE ${inc_headers}) target_include_directories(memory_tracker_tests PRIVATE ${inc_headers})
target_compile_definitions(memory_tracker_tests PRIVATE KYTY_MEMORY_TRACKER_TESTS=1)
add_executable(shader_vertex_metadata_tests EXCLUDE_FROM_ALL add_executable(shader_vertex_metadata_tests EXCLUDE_FROM_ALL
../tests/ShaderVertexMetadataTests.cpp ../tests/ShaderVertexMetadataTests.cpp
@@ -255,9 +362,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})
@@ -266,37 +373,118 @@ 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})
add_executable(event_queue_lifetime_tests EXCLUDE_FROM_ALL
../tests/EventQueueLifetimeTests.cpp
kernel/eventQueue.cpp
loader/timer.cpp
)
target_link_libraries(event_queue_lifetime_tests common fmt::fmt)
target_include_directories(event_queue_lifetime_tests PRIVATE ${inc_headers})
add_executable(image_page_table_tests EXCLUDE_FROM_ALL add_executable(image_page_table_tests EXCLUDE_FROM_ALL
../tests/ImagePageTableTests.cpp ../tests/ImagePageTableTests.cpp
) )
target_link_libraries(image_page_table_tests common fmt::fmt)
target_include_directories(image_page_table_tests PRIVATE ${inc_headers}) target_include_directories(image_page_table_tests PRIVATE ${inc_headers})
add_kyty_full_emulator_test(shader_recompiler_compute_tests ../tests/ShaderRecompilerComputeTests.cpp) add_kyty_full_emulator_test(shader_recompiler_compute_tests ../tests/ShaderRecompilerComputeTests.cpp)
set(gpu_test_generated_dir "${PROJECT_BINARY_DIR}/gpu_test_shaders")
set(gpu_test_ms_depth_source
"${gpu_tiler_shader_dir}/gpu_test_ms_depth.comp")
set(gpu_test_ms_depth_spv
"${gpu_test_generated_dir}/gpu_test_ms_depth.spv")
set(gpu_test_ms_depth_header
"${gpu_test_generated_dir}/gpu_test_ms_depth_spv.h")
add_custom_command(
OUTPUT "${gpu_test_ms_depth_header}"
COMMAND ${CMAKE_COMMAND} -E make_directory "${gpu_test_generated_dir}"
COMMAND "${KYTY_GLSLANG_VALIDATOR}" -V --target-env vulkan1.0 -Os
-o "${gpu_test_ms_depth_spv}" "${gpu_test_ms_depth_source}"
COMMAND ${CMAKE_COMMAND} -DINPUT=${gpu_test_ms_depth_spv}
-DOUTPUT=${gpu_test_ms_depth_header} -DSYMBOL=GPU_TEST_MS_DEPTH_SPV
-P "${CMAKE_CURRENT_SOURCE_DIR}/embed_spirv.cmake"
DEPENDS "${gpu_test_ms_depth_source}"
VERBATIM
)
target_sources(shader_recompiler_compute_tests PRIVATE
"${gpu_test_ms_depth_header}")
add_kyty_full_emulator_test(virtual_memory_allocation_tests ../tests/VirtualMemoryAllocationTests.cpp) add_kyty_full_emulator_test(virtual_memory_allocation_tests ../tests/VirtualMemoryAllocationTests.cpp)
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)
configure_macos_guest_address_space(virtual_memory_allocation_tests)
# 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)
add_test(NAME scalar_provenance COMMAND $<TARGET_FILE:scalar_provenance_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 page_manager COMMAND $<TARGET_FILE:page_manager_tests>)
add_test(NAME resource_mutex COMMAND $<TARGET_FILE:resource_mutex_tests>)
add_test(NAME event_queue_lifetime COMMAND $<TARGET_FILE:event_queue_lifetime_tests>)
add_test(NAME shader_recompiler_compute COMMAND $<TARGET_FILE:shader_recompiler_compute_tests>)
add_test(NAME virtual_memory_allocation
COMMAND $<TARGET_FILE:virtual_memory_allocation_tests>)
add_test(NAME command_scheduler_timeline
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --scheduler-only)
add_test(NAME stream_buffer_ring
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --stream-buffer-only)
add_test(NAME gpu_command_lane
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --gpu-command-lane-only)
add_test(NAME gpu_tiler
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)
# These tests still depend on the Windows multisample-depth path.
add_test(NAME texture_cache_image_overlap
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --image-overlap-only)
add_test(NAME texture_cache_htile_clear
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --htile-clear-only)
add_test(NAME buffer_cache_ranges
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --buffer-cache-range-only)
endif()
endif()
add_executable(kyty_emulator main.cpp ${kyty_emulator_src}) add_executable(kyty_emulator main.cpp ${kyty_emulator_src})
configure_macos_guest_address_space(kyty_emulator)
target_link_libraries(kyty_emulator ${kyty_emulator_link_libraries}) target_link_libraries(kyty_emulator ${kyty_emulator_link_libraries})
if (WIN32) if (WIN32)
@@ -311,6 +499,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}")
@@ -331,6 +521,18 @@ endif()
add_dependencies(kyty_emulator KytyGitVersion) add_dependencies(kyty_emulator KytyGitVersion)
if(APPLE)
# The emulator writes x86-64 trampolines/PLT stubs into memory and executes them, and
# runs guest code under Rosetta. That requires the JIT / unsigned-executable-memory
# entitlements, so re-sign the binary after every link (an unsigned/relinked binary
# reverts to the hardened defaults and aborts when it first executes written code).
add_custom_command(TARGET kyty_emulator POST_BUILD
COMMAND codesign -s - --force --options runtime
--entitlements "${CMAKE_CURRENT_SOURCE_DIR}/macos_jit.entitlements"
$<TARGET_FILE:kyty_emulator>
COMMENT "Codesign kyty_emulator with JIT entitlements (macOS)")
endif()
install(TARGETS kyty_emulator DESTINATION .) install(TARGETS kyty_emulator DESTINATION .)
if(KYTY_BUILD_LAUNCHER) if(KYTY_BUILD_LAUNCHER)
add_subdirectory(launcher) add_subdirectory(launcher)
+1
View File
@@ -6,6 +6,7 @@
#include <algorithm> #include <algorithm>
#include <cstddef> // IWYU pragma: export #include <cstddef> // IWYU pragma: export
#include <cstring>
#include <utility> #include <utility>
#include <vector> #include <vector>
+1
View File
@@ -5,6 +5,7 @@
#include "common/stringUtils.h" #include "common/stringUtils.h"
#include <array> #include <array>
#include <cstring>
namespace Common { namespace Common {
+4
View File
@@ -93,4 +93,8 @@ bool NggRectlistDrawEnabled() {
return g_config->ngg_rectlist_draw_enabled; return g_config->ngg_rectlist_draw_enabled;
} }
bool ReadbackLinearImagesEnabled() {
return g_config->readback_linear_images;
}
} // namespace Config } // namespace Config
+2
View File
@@ -36,6 +36,7 @@ struct ConfigOptions {
bool spirv_debug_printf_enabled = false; bool spirv_debug_printf_enabled = false;
bool renderdoc_enabled = false; bool renderdoc_enabled = false;
bool ngg_rectlist_draw_enabled = true; bool ngg_rectlist_draw_enabled = true;
bool readback_linear_images = false;
}; };
void Load(const ConfigOptions& cfg); void Load(const ConfigOptions& cfg);
@@ -64,6 +65,7 @@ bool SpirvDebugPrintfEnabled();
bool RenderDocEnabled(); bool RenderDocEnabled();
bool NggRectlistDrawEnabled(); bool NggRectlistDrawEnabled();
bool ReadbackLinearImagesEnabled();
} // namespace Config } // namespace Config
+221 -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,52 @@ 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);
// The guest signal-dispatch path (KernelRaiseException) interrupts threads with
// SIGUSR1; block it while a fault is being resolved so a stop-the-world request
// cannot preempt the handler between the protection fix and the retry.
sigaddset(&sa.sa_mask, SIGUSR1);
// 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
+5
View File
@@ -178,6 +178,11 @@ Direction GetDirection() {
return g_direction; return g_direction;
} }
bool IsSilent() {
// Before init LOGF must keep writing to stdout, so report non-silent.
return g_initialized && g_direction == Direction::Silent;
}
void Write(std::string_view text) { void Write(std::string_view text) {
WriteImpl(text); WriteImpl(text);
} }
+13 -2
View File
@@ -15,6 +15,7 @@ KYTY_SUBSYSTEM_DEFINE(Log);
enum class Direction { Silent, Console, File }; enum class Direction { Silent, Console, File };
Direction GetDirection(); Direction GetDirection();
bool IsSilent();
void Write(std::string_view text); void Write(std::string_view text);
void Write(fmt::text_style style, std::string_view text); void Write(fmt::text_style style, std::string_view text);
void WriteFatal(std::string_view text); void WriteFatal(std::string_view text);
@@ -41,8 +42,18 @@ inline constexpr auto BrightWhite = fmt::fg(fmt::terminal_color::bright_white)
} // namespace Log } // namespace Log
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) // NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define LOGF(...) ::Log::Write(::fmt::sprintf(__VA_ARGS__)) #define LOGF(...) \
do { \
if (!::Log::IsSilent()) { \
::Log::Write(::fmt::sprintf(__VA_ARGS__)); \
} \
} while (false)
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) // NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define LOGF_COLOR(style, ...) ::Log::Write((style), ::fmt::sprintf(__VA_ARGS__)) #define LOGF_COLOR(style, ...) \
do { \
if (!::Log::IsSilent()) { \
::Log::Write((style), ::fmt::sprintf(__VA_ARGS__)); \
} \
} while (false)
#endif /* KYTY_COMMON_LOGGING_LOG_H_ */ #endif /* KYTY_COMMON_LOGGING_LOG_H_ */
+118
View File
@@ -0,0 +1,118 @@
#ifndef EMULATOR_SRC_COMMON_LRUCACHE_H_
#define EMULATOR_SRC_COMMON_LRUCACHE_H_
#include <cstddef>
#include <deque>
#include <type_traits>
#include <utility>
namespace Common {
template <typename Object, typename Tick>
class LeastRecentlyUsedCache {
struct Item {
Object object {};
Tick tick {};
Item* next = nullptr;
Item* prev = nullptr;
};
public:
[[nodiscard]] size_t Insert(Object object, Tick tick) {
const auto id = Build();
auto& item = m_items[id];
item.object = std::move(object);
item.tick = tick;
Attach(item);
return id;
}
void Touch(size_t id, Tick tick) {
auto& item = m_items[id];
if (item.tick >= tick) {
return;
}
item.tick = tick;
if (&item != m_last) {
Detach(item);
Attach(item);
}
}
void Free(size_t id) {
auto& item = m_items[id];
Detach(item);
item.next = nullptr;
item.prev = nullptr;
m_free.push_back(id);
}
template <typename Function>
void ForEachItemBelow(Tick tick, Function&& function) {
constexpr bool ReturnsBool = std::is_same_v<std::invoke_result_t<Function, Object>, bool>;
for (auto* item = m_first; item != nullptr;) {
if (item->tick > tick) {
return;
}
auto* next = item->next;
if constexpr (ReturnsBool) {
if (function(item->object)) {
return;
}
} else {
function(item->object);
}
item = next;
}
}
private:
[[nodiscard]] size_t Build() {
if (m_free.empty()) {
const auto id = m_items.size();
m_items.emplace_back();
return id;
}
const auto id = m_free.front();
m_free.pop_front();
return id;
}
void Attach(Item& item) {
if (m_first == nullptr) {
m_first = &item;
}
if (m_last == nullptr) {
m_last = &item;
return;
}
item.prev = m_last;
m_last->next = &item;
item.next = nullptr;
m_last = &item;
}
void Detach(Item& item) {
if (item.prev != nullptr) {
item.prev->next = item.next;
}
if (item.next != nullptr) {
item.next->prev = item.prev;
}
if (m_first == &item) {
m_first = item.next;
}
if (m_last == &item) {
m_last = item.prev;
}
}
std::deque<Item> m_items;
std::deque<size_t> m_free;
Item* m_first = nullptr;
Item* m_last = nullptr;
};
} // namespace Common
#endif // EMULATOR_SRC_COMMON_LRUCACHE_H_
+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
}; };
+78 -4
View File
@@ -8,12 +8,56 @@
#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 +74,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 +114,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 +187,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 -28
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);
@@ -135,8 +172,7 @@ sys_file_t* SysFileCreate(const std::filesystem::path& file_name) {
return ret; return ret;
} }
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 +186,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;
@@ -179,8 +217,7 @@ sys_file_t* SysFileCreate() {
return ret; return ret;
} }
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 +231,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 +253,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 +280,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 +307,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 +439,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 +458,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 +478,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 +613,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
+215 -39
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,16 +154,15 @@ 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++) {
(*g_protects)[page] = protect; (*g_protects)[page] = protect;
} }
@@ -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_PRIVATE | MAP_ANON | MAP_NORESERVE, -1, 0); // NOLINT map_anonymous(addr, size + alignment, protect, 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,9 +250,9 @@ 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++) {
(*g_protects)[page] = protect; (*g_protects)[page] = protect;
} }
@@ -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,9 +336,9 @@ 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++) {
(*g_protects)[page] = protect; (*g_protects)[page] = protect;
} }
@@ -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
@@ -5,6 +5,7 @@
#include <algorithm> #include <algorithm>
#include <atomic> #include <atomic>
#include <cerrno>
#include <chrono> // IWYU pragma: keep #include <chrono> // IWYU pragma: keep
#include <condition_variable> // IWYU pragma: keep #include <condition_variable> // IWYU pragma: keep
#include <mutex> #include <mutex>
@@ -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
+54
View File
@@ -0,0 +1,54 @@
#ifndef KYTY_COMMON_UNIQUEFUNCTION_H_
#define KYTY_COMMON_UNIQUEFUNCTION_H_
#include <memory>
#include <type_traits>
#include <utility>
namespace Common {
template <typename Result, typename... Args>
class UniqueFunction {
class CallableBase {
public:
virtual ~CallableBase() = default;
virtual Result Invoke(Args&&... args) = 0;
};
template <typename Function>
class Callable final: public CallableBase {
public:
explicit Callable(Function function): m_function(std::move(function)) {}
Result Invoke(Args&&... args) override { return m_function(std::forward<Args>(args)...); }
private:
Function m_function;
};
public:
UniqueFunction() = default;
template <typename Function>
UniqueFunction(Function&& function)
: m_callable(std::make_unique<Callable<std::decay_t<Function>>>(
std::forward<Function>(function))) {}
UniqueFunction(UniqueFunction&&) noexcept = default;
UniqueFunction& operator=(UniqueFunction&&) noexcept = default;
UniqueFunction(const UniqueFunction&) = delete;
UniqueFunction& operator=(const UniqueFunction&) = delete;
Result operator()(Args... args) const {
return m_callable->Invoke(std::forward<Args>(args)...);
}
explicit operator bool() const noexcept { return m_callable != nullptr; }
private:
std::unique_ptr<CallableBase> m_callable;
};
} // namespace Common
#endif // KYTY_COMMON_UNIQUEFUNCTION_H_
-19
View File
@@ -58,25 +58,6 @@ bool FlushInstructionCache(uint64_t address, uint64_t size) {
return SysVirtualFlushInstructionCache(address, size); return SysVirtualFlushInstructionCache(address, size);
} }
bool PatchReplace(uint64_t vaddr, uint64_t value) {
Mode old_mode {};
Protect(vaddr, 8, Mode::ReadWrite, &old_mode);
auto* ptr = reinterpret_cast<uint64_t*>(vaddr);
bool ret = (*ptr != value);
*ptr = value;
Protect(vaddr, 8, old_mode);
if (IsExecute(old_mode)) {
FlushInstructionCache(vaddr, 8);
}
return ret;
}
} // namespace VirtualMemory } // namespace VirtualMemory
} // namespace Common } // namespace Common
-1
View File
@@ -37,7 +37,6 @@ bool Free(uint64_t address);
bool FreeRange(uint64_t address, uint64_t size); bool FreeRange(uint64_t address, uint64_t size);
bool Protect(uint64_t address, uint64_t size, Mode mode, Mode* old_mode = nullptr); bool Protect(uint64_t address, uint64_t size, Mode mode, Mode* old_mode = nullptr);
bool FlushInstructionCache(uint64_t address, uint64_t size); bool FlushInstructionCache(uint64_t address, uint64_t size);
bool PatchReplace(uint64_t vaddr, uint64_t value);
} // namespace VirtualMemory } // namespace VirtualMemory
+30
View File
@@ -0,0 +1,30 @@
if(NOT DEFINED INPUT OR NOT DEFINED OUTPUT OR NOT DEFINED SYMBOL)
message(FATAL_ERROR "embed_spirv.cmake requires INPUT, OUTPUT, and SYMBOL")
endif()
file(READ "${INPUT}" bytes HEX)
string(LENGTH "${bytes}" length)
math(EXPR remainder "${length} % 8")
if(NOT remainder EQUAL 0)
message(FATAL_ERROR "SPIR-V byte count is not a multiple of four: ${INPUT}")
endif()
set(contents "#pragma once\n#include <cstdint>\ninline constexpr uint32_t ${SYMBOL}[] = {\n")
set(column 0)
while(length GREATER 0)
string(SUBSTRING "${bytes}" 0 8 word)
string(SUBSTRING "${word}" 0 2 b0)
string(SUBSTRING "${word}" 2 2 b1)
string(SUBSTRING "${word}" 4 2 b2)
string(SUBSTRING "${word}" 6 2 b3)
string(APPEND contents "0x${b3}${b2}${b1}${b0}u,")
math(EXPR column "${column} + 1")
if(column EQUAL 8)
string(APPEND contents "\n")
set(column 0)
endif()
string(SUBSTRING "${bytes}" 8 -1 bytes)
math(EXPR length "${length} - 8")
endwhile()
string(APPEND contents "\n};\n")
file(WRITE "${OUTPUT}" "${contents}")
+24 -32
View File
@@ -105,7 +105,7 @@ static void ClearDebugTextureFolder() {
} }
} }
static void Init(const Config::ConfigOptions& cfg) { static void Init(const Config::ConfigOptions& cfg, const std::filesystem::path& param_json) {
EXIT_IF(!Common::Thread::IsMainThread()); EXIT_IF(!Common::Thread::IsMainThread());
auto* slist = Common::SubsystemsList::Instance(); auto* slist = Common::SubsystemsList::Instance();
@@ -127,12 +127,21 @@ static void Init(const Config::ConfigOptions& cfg) {
slist->InitAll(true); slist->InitAll(true);
Config::Load(cfg); Config::Load(cfg);
slist->Add(log, {core, config});
slist->InitAll(true);
if (Common::File::IsFileExisting(param_json)) {
Loader::SystemContentLoadParamSfo(param_json);
if (const auto flexible_memory_size = Loader::SystemContentGetFlexibleMemorySize();
flexible_memory_size != 0) {
Libs::LibKernel::Memory::SetFlexibleMemorySize(flexible_memory_size);
}
}
slist->Add(audio, {core, log, pthread, memory}); slist->Add(audio, {core, log, pthread, memory});
slist->Add(controller, {core, log, config}); slist->Add(controller, {core, log, config});
slist->Add(file_system, {core, log, pthread}); slist->Add(file_system, {core, log, pthread});
slist->Add(graphics, {core, log, pthread, memory, config, profiler, controller}); slist->Add(graphics, {core, log, pthread, memory, config, profiler, controller});
slist->Add(log, {core, config});
slist->Add(memory, {core, log}); slist->Add(memory, {core, log});
slist->Add(network, {core, log, pthread}); slist->Add(network, {core, log, pthread});
slist->Add(profiler, {core, config}); slist->Add(profiler, {core, config});
@@ -159,25 +168,16 @@ static void LoadElf(const std::filesystem::path& elf, bool dbg_print_reloc = fal
} }
} }
static void Execute() { static void Execute(const std::filesystem::path& game_patch) {
int thread_model = 1; auto patch_path = game_patch;
Common::Thread guest_thread(
if (thread_model == 0) { [](void* param) {
Common::Thread t([](void* /*unused*/) { Libs::Graphics::WindowRun(); }, nullptr); auto* rt = Common::Singleton<Loader::RuntimeLinker>::Instance();
t.Detach(); rt->Execute(*static_cast<const std::filesystem::path*>(param));
auto* rt = Common::Singleton<Loader::RuntimeLinker>::Instance(); },
rt->Execute(); &patch_path);
} else { Libs::Graphics::WindowRun();
Common::Thread t( std::quick_exit(0);
[](void* /*unused*/) {
auto* rt = Common::Singleton<Loader::RuntimeLinker>::Instance();
rt->Execute();
},
nullptr);
t.Detach();
Libs::Graphics::WindowRun();
t.Join();
}
} }
void Run(const RunOptions& options) { void Run(const RunOptions& options) {
@@ -189,7 +189,8 @@ void Run(const RunOptions& options) {
EXIT("ELF is required\n"); EXIT("ELF is required\n");
} }
Init(options.config); const auto param_json = options.app0_dir / "sce_sys" / "param.json";
Init(options.config, param_json);
ClearDebugTextureFolder(); ClearDebugTextureFolder();
@@ -201,15 +202,6 @@ void Run(const RunOptions& options) {
Libs::LibKernel::FileSystem::Mount(options.app0_dir, "/app0"); Libs::LibKernel::FileSystem::Mount(options.app0_dir, "/app0");
Libs::LibKernel::FileSystem::Mount(options.app0_dir, "/hostapp"); Libs::LibKernel::FileSystem::Mount(options.app0_dir, "/hostapp");
auto param_json = options.app0_dir / "sce_sys" / "param.json";
if (Common::File::IsFileExisting(param_json)) {
Loader::SystemContentLoadParamSfo(param_json);
if (auto flexible_memory_size = Loader::SystemContentGetFlexibleMemorySize();
flexible_memory_size != 0) {
Libs::LibKernel::Memory::SetFlexibleMemorySize(flexible_memory_size);
}
}
MountSandboxDirs(); MountSandboxDirs();
auto* rt = Common::Singleton<Loader::RuntimeLinker>::Instance(); auto* rt = Common::Singleton<Loader::RuntimeLinker>::Instance();
@@ -217,7 +209,7 @@ void Run(const RunOptions& options) {
LoadElf(options.elf); LoadElf(options.elf);
Execute(); Execute(options.game_patch);
} }
} // namespace Emulator } // namespace Emulator
+1
View File
@@ -12,6 +12,7 @@ struct RunOptions {
Config::ConfigOptions config; Config::ConfigOptions config;
std::filesystem::path app0_dir; std::filesystem::path app0_dir;
std::filesystem::path elf; std::filesystem::path elf;
std::filesystem::path game_patch;
}; };
void Run(const RunOptions& options); void Run(const RunOptions& options);
+13 -6
View File
@@ -1,8 +1,15 @@
#message("${GIT_EXECUTABLE}") set(KYTY_GIT_VERSION "unknown")
if(GIT_EXECUTABLE) if(GIT_EXECUTABLE)
execute_process(COMMAND ${GIT_EXECUTABLE} describe --tags --always OUTPUT_VARIABLE KYTY_GIT_VERSION) execute_process(
else() COMMAND "${GIT_EXECUTABLE}" describe --tags --always
set(KYTY_GIT_VERSION "unknown") WORKING_DIRECTORY "${GIT_WORKING_DIRECTORY}"
OUTPUT_VARIABLE KYTY_GIT_VERSION
OUTPUT_STRIP_TRAILING_WHITESPACE
RESULT_VARIABLE GIT_RESULT
ERROR_QUIET
)
if(NOT GIT_RESULT EQUAL 0)
set(KYTY_GIT_VERSION "unknown")
endif()
endif() endif()
string(STRIP ${KYTY_GIT_VERSION} KYTY_GIT_VERSION) configure_file("${INPUT_FILE}" "${OUTPUT_FILE}")
configure_file(${INPUT_FILE} ${OUTPUT_FILE})
-96
View File
@@ -1,96 +0,0 @@
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_ASYNCJOB_H_
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_ASYNCJOB_H_
#include "common/common.h"
#include "common/profiler.h"
#include "common/threads.h"
#include <functional>
#include <string>
#include <utility>
namespace Libs::Graphics {
class AsyncJob final {
public:
using Task = std::function<void()>;
explicit AsyncJob(std::string thread_name = {})
: m_thread_name(std::move(thread_name)), m_worker_thread(WorkerEntry, this) {}
~AsyncJob() {
{
Common::LockGuard lock(m_mutex);
m_stop_requested = true;
m_task_available_condition.Signal();
}
m_worker_thread.Join();
}
KYTY_CLASS_NO_COPY(AsyncJob);
void Execute(Task task) {
Common::LockGuard lock(m_mutex);
while (m_is_busy) {
m_idle_condition.Wait(&m_mutex);
}
m_task = std::move(task);
m_is_busy = true;
m_task_available_condition.Signal();
}
void Wait() {
Common::LockGuard lock(m_mutex);
while (m_is_busy) {
m_idle_condition.Wait(&m_mutex);
}
}
private:
static void WorkerEntry(void* data) { static_cast<AsyncJob*>(data)->WorkerLoop(); }
void WorkerLoop() {
if (!m_thread_name.empty()) {
KYTY_PROFILER_THREAD(m_thread_name.c_str());
}
for (;;) {
Task task;
{
Common::LockGuard lock(m_mutex);
while (!m_is_busy && !m_stop_requested) {
m_task_available_condition.Wait(&m_mutex);
}
if (!m_is_busy) {
return;
}
task = std::move(m_task);
}
task();
{
Common::LockGuard lock(m_mutex);
m_is_busy = false;
m_idle_condition.SignalAll();
}
}
}
std::string m_thread_name;
Common::Mutex m_mutex;
Common::CondVar m_task_available_condition;
Common::CondVar m_idle_condition;
Task m_task;
bool m_is_busy = false;
bool m_stop_requested = false;
Common::Thread m_worker_thread;
};
} // namespace Libs::Graphics
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_ASYNCJOB_H_ */
@@ -2,106 +2,36 @@
#define GRAPHICS_GUEST_GPU_COMMAND_PROCESSOR_COMMAND_PROCESSOR_H #define GRAPHICS_GUEST_GPU_COMMAND_PROCESSOR_COMMAND_PROCESSOR_H
#include "common/assert.h" #include "common/assert.h"
#include "common/threads.h"
#include "graphics/guest_gpu/hardwareContext.h" #include "graphics/guest_gpu/hardwareContext.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 <array>
#include <cstdint> #include <cstdint>
#include <vector>
namespace Libs::Graphics { namespace Libs::Graphics {
inline constexpr uint32_t AcquireGcrGl2Writeback = 1u << 15u;
bool TestWaitRegMemValue(uint64_t value, uint64_t ref, uint64_t mask, uint32_t func); bool TestWaitRegMemValue(uint64_t value, uint64_t ref, uint64_t mask, uint32_t func);
class CommandScheduler { enum class Pm4ProcessResult { Complete, Blocked };
class Pm4Execution {
public: public:
static constexpr int BuffersNum = 8; [[nodiscard]] bool MadeProgress() const noexcept { return m_made_progress; }
void SetQueue(int queue) { m_queue = queue; }
int Queue() const { return m_queue; }
bool Active() const { return m_current >= 0 && m_current < BuffersNum; }
void CheckActive() const { EXIT_IF(!Active()); }
CommandBuffer* Current() const {
CheckActive();
EXIT_IF(m_buffers[m_current] == nullptr);
return m_buffers[m_current];
}
void Init() {
if (m_current >= 0) {
return;
}
for (auto& buf: m_buffers) {
EXIT_IF(buf != nullptr);
buf = new CommandBuffer(m_queue);
}
m_current = 0;
Current()->Begin();
}
void Flush() {
SubmitCurrent();
BeginNext();
}
CommandBuffer* FlushAndGetSubmitted() {
auto* submitted = SubmitCurrent();
BeginNext();
return submitted;
}
void CopyBuffers(std::array<CommandBuffer*, BuffersNum>* out) const {
EXIT_IF(out == nullptr);
for (int i = 0; i < BuffersNum; i++) {
auto* buf = m_buffers[i];
EXIT_IF(buf == nullptr);
(*out)[i] = buf;
}
}
void WaitAll() {
for (auto* buf: m_buffers) {
EXIT_IF(buf == nullptr);
buf->WaitForFenceAndReset();
}
}
void SubmitForReadback() {
if (!Active()) {
return;
}
SubmitCurrent();
}
void ResumeAfterReadback() {
if (!Active()) {
return;
}
Current()->WaitForFenceAndReset();
Current()->Begin();
}
private: private:
CommandBuffer* SubmitCurrent() { friend class CommandProcessor;
auto* submitted = Current();
submitted->End();
submitted->Execute();
return submitted;
}
void BeginNext() { struct BufferCursor {
m_current = (m_current + 1) % BuffersNum; uint32_t* next_packet = nullptr;
Current()->WaitForFenceAndReset(); uint32_t remaining_dw = 0;
Current()->Begin(); uint32_t total_dw = 0;
} uint32_t deferred_advance_dw = 0;
};
CommandBuffer* m_buffers[BuffersNum] = {}; std::vector<BufferCursor> m_buffer_stack;
int m_current = -1; bool m_suspended = false;
int m_queue = -1; bool m_made_progress = false;
}; };
class CommandProcessor { class CommandProcessor {
@@ -113,8 +43,8 @@ public:
int64_t flip_arg = 0; int64_t flip_arg = 0;
}; };
CommandProcessor() = default; explicit CommandProcessor(RenderContext& renderer): m_renderer(renderer) {}
~CommandProcessor() { KYTY_NOT_IMPLEMENTED; } ~CommandProcessor() = default;
KYTY_CLASS_NO_COPY(CommandProcessor); KYTY_CLASS_NO_COPY(CommandProcessor);
@@ -125,29 +55,21 @@ public:
void BufferFlushAndWait(); void BufferFlushAndWait();
void BufferWait(); void BufferWait();
void BeginReadbackTransaction() { void BeginReadbackTransaction() {
m_mutex.Lock();
if (m_readback_active) { if (m_readback_active) {
EXIT("nested command-processor readback transaction\n"); EXIT("nested command-processor readback transaction\n");
} }
m_readback_active = true; m_readback_active = true;
m_readback_finished = false;
} }
void FinishReadbackTransaction();
void EndReadbackTransaction() { void EndReadbackTransaction() {
if (!m_readback_active) { if (!m_readback_active) {
EXIT("command-processor readback transaction is not active\n"); EXIT("command-processor readback transaction is not active\n");
} }
m_readback_active = false; m_readback_active = false;
m_readback_finished = false;
m_mutex.Unlock();
} }
void RunLock() { m_run_mutex.Lock(); } HW::Context& GetCtx() { return m_ctx; }
void RunUnlock() { m_run_mutex.Unlock(); } HW::UserConfig& GetUcfg() { return m_ucfg; }
HW::Shader& GetShCtx() { return m_sh_ctx; }
HW::Context* GetCtx() { return &m_ctx; }
HW::UserConfig* GetUcfg() { return &m_ucfg; }
HW::Shader* GetShCtx() { return &m_sh_ctx; }
void SetIndexType(uint32_t index_type_and_size); void SetIndexType(uint32_t index_type_and_size);
void SetIndexBaseAddress(uint64_t index_base_addr); void SetIndexBaseAddress(uint64_t index_base_addr);
@@ -179,12 +101,10 @@ public:
void Flip(void* dst_gpu_addr, uint32_t value); void Flip(void* dst_gpu_addr, uint32_t value);
void FlipWithInterrupt(uint32_t eop_event_type, uint32_t cache_action, void* dst_gpu_addr, void FlipWithInterrupt(uint32_t eop_event_type, uint32_t cache_action, void* dst_gpu_addr,
uint32_t value); uint32_t value);
void PrepareCpuFlip(); void PrepareCpuFlip(uint64_t request_id);
void SynchronizeGpu(); void SynchronizeGpu();
void MemoryBarrier(); void EmitGlobalBarrier();
void TriggerEopEventAtEndOfPipe(uint32_t interrupt_context_id); void TriggerEopEventAtEndOfPipe(uint32_t interrupt_context_id);
void RenderTextureBarrier(uint64_t vaddr, uint64_t size);
void DepthStencilBarrier(uint64_t vaddr, uint64_t size);
void DispatchDirect(uint32_t thread_group_x, uint32_t thread_group_y, uint32_t thread_group_z, void DispatchDirect(uint32_t thread_group_x, uint32_t thread_group_y, uint32_t thread_group_z,
uint32_t mode); uint32_t mode);
void DispatchIndirect(uint32_t data_offset, uint32_t mode); void DispatchIndirect(uint32_t data_offset, uint32_t mode);
@@ -193,16 +113,13 @@ public:
void SetUserDataMarker(HW::UserSgprType type) { m_user_data_marker = type; } void SetUserDataMarker(HW::UserSgprType type) { m_user_data_marker = type; }
[[nodiscard]] HW::UserSgprType GetUserDataMarker() const { return m_user_data_marker; } [[nodiscard]] HW::UserSgprType GetUserDataMarker() const { return m_user_data_marker; }
void SetEmbeddedDataMarker(const uint32_t* buffer, uint32_t num_dw, uint32_t align) {}
void PushMarker(const char* str) {}
void PopMarker() {}
void PrefetchL2(void* addr, uint32_t size) {}
void ResetDeCe(); void ResetDeCe();
void SetCeComplete(bool complete) { m_ce_complete = complete; }
void WaitCe(); void WaitCe();
void WaitDeDiff(uint32_t diff); void WaitDeDiff(uint32_t diff);
void IncremenetDe(); void IncrementDe();
void IncremenetCe(); void IncrementCe();
void WriteConstRam(uint32_t offset, const uint32_t* src, uint32_t dw_num); void WriteConstRam(uint32_t offset, const uint32_t* src, uint32_t dw_num);
void DumpConstRam(uint32_t* dst, uint32_t offset, uint32_t dw_num); void DumpConstRam(uint32_t* dst, uint32_t offset, uint32_t dw_num);
@@ -219,38 +136,29 @@ public:
const volatile void* address, uint32_t count_in_dwords); const volatile void* address, uint32_t count_in_dwords);
[[nodiscard]] bool ShouldSkipPredicatedPackets() const { return m_predicate_skip; } [[nodiscard]] bool ShouldSkipPredicatedPackets() const { return m_predicate_skip; }
void Run(uint32_t* data, uint32_t num_dw); Pm4ProcessResult Process(Pm4Execution& execution, uint32_t* buffer, uint32_t size_dw);
void ProcessIndirectBuffer(uint32_t* buffer, uint32_t size_dw);
void SetQueue(int queue); void SetFlip(const FlipInfo& flip) { m_flip = flip; }
[[nodiscard]] int GetQueue() const { return m_scheduler.Queue(); }
[[nodiscard]] const FlipInfo& GetFlip() const { return m_flip; }
void SetFlip(const FlipInfo& flip) { m_flip = flip; }
[[nodiscard]] uint64_t GetSubmitId() const { return m_submit_id; } [[nodiscard]] uint64_t GetSubmitId() const { return m_submit_id; }
void SetSubmitId(uint64_t submit_id) { m_submit_id = submit_id; } void SetSubmitId(uint64_t submit_id) { m_submit_id = submit_id; }
private: private:
struct Counter {
Common::Mutex mutex;
Common::CondVar cond_var;
uint32_t value = 0;
};
template <typename T> template <typename T>
void WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_write_dest, uint32_t eop_event_type, void WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_write_dest, uint32_t eop_event_type,
uint32_t cache_action, uint32_t event_index, uint32_t event_write_source, uint32_t cache_action, uint32_t event_index, uint32_t event_write_source,
void* dst_gpu_addr, T value, uint32_t interrupt_selector, void* dst_gpu_addr, T value, uint32_t interrupt_selector,
uint32_t interrupt_context_id); uint32_t interrupt_context_id);
void FinishCommandProcessors(); void ProcessPm4(Pm4Execution& execution, size_t stop_depth);
void SuspendPm4();
CommandBuffer* CurrentBuffer() { return m_scheduler.Current(); } CommandScheduler& GetScheduler() const { return m_renderer.GetCommandScheduler(); }
void CheckBuffer() const { m_scheduler.CheckActive(); } RenderCommandBuffer& CurrentBuffer() { return GetScheduler().Current(); }
GpuResourceManager* GetGpuResources() const { void CheckBuffer() const { GetScheduler().CheckActive(); }
EXIT_IF(g_render_ctx == nullptr); GpuResourceManager& GetGpuResources() const { return m_renderer.GetGpuResources(); }
return g_render_ctx->GetGpuResources();
}
RenderContext& m_renderer;
HW::Context m_ctx; HW::Context m_ctx;
HW::UserConfig m_ucfg; HW::UserConfig m_ucfg;
HW::Shader m_sh_ctx; HW::Shader m_sh_ctx;
@@ -262,16 +170,10 @@ private:
uint64_t m_dispatch_indirect_args_base_addr = 0; uint64_t m_dispatch_indirect_args_base_addr = 0;
uint32_t m_num_instances = 1; uint32_t m_num_instances = 1;
inline static Common::Mutex m_mutex; uint32_t m_de_count = 0;
inline static std::array<CommandProcessor*, GraphicContext::QUEUES_NUM> m_processors {}; uint32_t m_ce_count = 0;
inline static bool m_readback_active = false; bool m_ce_complete = false;
inline static bool m_readback_finished = false; bool m_readback_active = false;
Common::Mutex m_run_mutex;
CommandScheduler m_scheduler;
Counter m_de_counter;
Counter m_ce_counter;
uint32_t m_const_ram[0x3000] = {0}; uint32_t m_const_ram[0x3000] = {0};
@@ -10,17 +10,17 @@ namespace Libs::Graphics {
class CommandProcessor; class CommandProcessor;
using hw_ctx_parser_func_t = uint32_t (*)(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, using hw_ctx_parser_func_t = uint32_t (*)(CommandProcessor&, uint32_t, uint32_t, const uint32_t*,
uint32_t); uint32_t);
using hw_uc_parser_func_t = uint32_t (*)(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, using hw_uc_parser_func_t = uint32_t (*)(CommandProcessor&, uint32_t, uint32_t, const uint32_t*,
uint32_t); uint32_t);
using hw_sh_parser_func_t = uint32_t (*)(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, using hw_sh_parser_func_t = uint32_t (*)(CommandProcessor&, uint32_t, uint32_t, const uint32_t*,
uint32_t); uint32_t);
using cp_op_parser_func_t = uint32_t (*)(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, using cp_op_parser_func_t = uint32_t (*)(CommandProcessor&, uint32_t, const uint32_t*, uint32_t,
uint32_t); uint32_t);
using hw_ctx_indirect_func_t = void (*)(CommandProcessor*, uint32_t, uint32_t); using hw_ctx_indirect_func_t = void (*)(CommandProcessor&, uint32_t, uint32_t);
using hw_uc_indirect_func_t = void (*)(CommandProcessor*, uint32_t, uint32_t); using hw_uc_indirect_func_t = void (*)(CommandProcessor&, uint32_t, uint32_t);
using hw_sh_indirect_func_t = void (*)(CommandProcessor*, uint32_t, uint32_t); using hw_sh_indirect_func_t = void (*)(CommandProcessor&, uint32_t, uint32_t);
extern const std::array<hw_ctx_parser_func_t, Pm4::CX_NUM> g_hw_ctx_func; extern const std::array<hw_ctx_parser_func_t, Pm4::CX_NUM> g_hw_ctx_func;
extern hw_ctx_indirect_func_t g_hw_ctx_indirect_func[Pm4::CX_NUM]; extern hw_ctx_indirect_func_t g_hw_ctx_indirect_func[Pm4::CX_NUM];
@@ -36,136 +36,136 @@ void GraphicsInitJmpTablesCxIndirect();
void GraphicsInitJmpTablesShIndirect(); void GraphicsInitJmpTablesShIndirect();
void GraphicsInitJmpTablesUcIndirect(); void GraphicsInitJmpTablesUcIndirect();
uint32_t HwCtxSetRenderControl(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t); uint32_t HwCtxSetRenderControl(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwCtxSetDepthMetadataRegisters(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t HwCtxSetDepthMetadataRegisters(CommandProcessor&, uint32_t, uint32_t, const uint32_t*,
uint32_t); uint32_t);
uint32_t HwCtxSetBorderColorTableAddr(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t HwCtxSetBorderColorTableAddr(CommandProcessor&, uint32_t, uint32_t, const uint32_t*,
uint32_t); uint32_t);
uint32_t HwCtxSetStencilClear(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t); uint32_t HwCtxSetStencilClear(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwCtxSetDepthClear(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t); uint32_t HwCtxSetDepthClear(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwCtxSetScreenScissor(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t); uint32_t HwCtxSetScreenScissor(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwCtxSetDepthRenderTarget(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t HwCtxSetDepthRenderTarget(CommandProcessor&, uint32_t, uint32_t, const uint32_t*,
uint32_t); uint32_t);
uint32_t HwCtxSetStencilInfo(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t); uint32_t HwCtxSetStencilInfo(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwCtxSetDepthHtileSurface(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t HwCtxSetDepthHtileSurface(CommandProcessor&, uint32_t, uint32_t, const uint32_t*,
uint32_t); uint32_t);
uint32_t HwCtxSetHardwareScreenOffset(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t HwCtxSetHardwareScreenOffset(CommandProcessor&, uint32_t, uint32_t, const uint32_t*,
uint32_t); uint32_t);
uint32_t HwCtxSetWindowOffset(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t); uint32_t HwCtxSetWindowOffset(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwCtxSetWindowScissor(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t); uint32_t HwCtxSetWindowScissor(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwCtxSetClipRect(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t); uint32_t HwCtxSetClipRect(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwCtxSetRenderTargetMask(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t); uint32_t HwCtxSetRenderTargetMask(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwCtxSetGenericScissor(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t); uint32_t HwCtxSetGenericScissor(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwCtxSetBlendColor(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t); uint32_t HwCtxSetBlendColor(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwCtxSetCbDccControl(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t); uint32_t HwCtxSetCbDccControl(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwCtxSetStencilControl(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t); uint32_t HwCtxSetStencilControl(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwCtxSetStencilMask(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t); uint32_t HwCtxSetStencilMask(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwCtxSetPsInput(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t); uint32_t HwCtxSetPsInput(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwCtxSetSpiTmpringSize(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t); uint32_t HwCtxSetSpiTmpringSize(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwCtxSetDepthControl(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t); uint32_t HwCtxSetDepthControl(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwCtxSetEqaaControl(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t); uint32_t HwCtxSetEqaaControl(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwCtxSetColorControl(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t); uint32_t HwCtxSetColorControl(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwCtxSetDepthBounds(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t); uint32_t HwCtxSetDepthBounds(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwCtxSetPointState(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t); uint32_t HwCtxSetPointState(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwCtxSetClipControl(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t); uint32_t HwCtxSetClipControl(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwCtxSetModeControl(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t); uint32_t HwCtxSetModeControl(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwCtxSetPolyOffsetRegisters(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t HwCtxSetPolyOffsetRegisters(CommandProcessor&, uint32_t, uint32_t, const uint32_t*,
uint32_t); uint32_t);
uint32_t HwCtxSetViewportTransformControl(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t HwCtxSetViewportTransformControl(CommandProcessor&, uint32_t, uint32_t, const uint32_t*,
uint32_t); uint32_t);
uint32_t HwCtxSetLineControl(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t); uint32_t HwCtxSetLineControl(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwCtxSetFovWindow(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t); uint32_t HwCtxSetFovWindow(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwCtxSetScanModeControl(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t); uint32_t HwCtxSetScanModeControl(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwCtxSetScanModeControl1(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t); uint32_t HwCtxSetScanModeControl1(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwCtxSetAaConfig(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t); uint32_t HwCtxSetAaConfig(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwCtxSetAaSampleControl(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t); uint32_t HwCtxSetAaSampleControl(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwCtxSetCentroidPriority(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t); uint32_t HwCtxSetCentroidPriority(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwCtxSetAaMask(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t); uint32_t HwCtxSetAaMask(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwCtxSetVtxControl(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t); uint32_t HwCtxSetVtxControl(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwCtxSetPaScExtendedControl(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t HwCtxSetPaScExtendedControl(CommandProcessor&, uint32_t, uint32_t, const uint32_t*,
uint32_t); uint32_t);
uint32_t HwCtxSetAlphaToMask(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t); uint32_t HwCtxSetAlphaToMask(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwCtxSetDrawPayloadControl(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t HwCtxSetDrawPayloadControl(CommandProcessor&, uint32_t, uint32_t, const uint32_t*,
uint32_t); uint32_t);
uint32_t HwCtxSetPrimitiveIdReset(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t); uint32_t HwCtxSetPrimitiveIdReset(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwCtxSetObjprimIdControl(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t); uint32_t HwCtxSetObjprimIdControl(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwCtxSetShaderStages(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t); uint32_t HwCtxSetShaderStages(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwCtxSetGuardBands(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t); uint32_t HwCtxSetGuardBands(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwCtxSetRenderTarget(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t); uint32_t HwCtxSetRenderTarget(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwCtxSetColorInfo(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t); uint32_t HwCtxSetColorInfo(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwCtxSetBlendControl(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t); uint32_t HwCtxSetBlendControl(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwCtxSetViewportScissor(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t); uint32_t HwCtxSetViewportScissor(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwCtxSetViewportZ(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t); uint32_t HwCtxSetViewportZ(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwCtxSetViewportScaleOffset(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t HwCtxSetViewportScaleOffset(CommandProcessor&, uint32_t, uint32_t, const uint32_t*,
uint32_t); uint32_t);
uint32_t HwShSetPsUserSgpr(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t); uint32_t HwShSetPsUserSgpr(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwShSetGsUserSgpr(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t); uint32_t HwShSetGsUserSgpr(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwShSetHsUserSgpr(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t); uint32_t HwShSetHsUserSgpr(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwShSetCsUserSgpr(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t); uint32_t HwShSetCsUserSgpr(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwShSetPsUserAccumSgpr(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t); uint32_t HwShSetPsUserAccumSgpr(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwShSetGsUserAccumSgpr(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t); uint32_t HwShSetGsUserAccumSgpr(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwShSetHsUserAccumSgpr(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t); uint32_t HwShSetHsUserAccumSgpr(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwShSetCsUserAccumSgpr(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t); uint32_t HwShSetCsUserAccumSgpr(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwShSetCsRegisters(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t); uint32_t HwShSetCsRegisters(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwShIgnoreRegisters(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t); uint32_t HwShIgnoreRegisters(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwUcSetPrimitiveType(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t); uint32_t HwUcSetPrimitiveType(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwUcSetIndexType(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t); uint32_t HwUcSetIndexType(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwUcSetObjectId(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t); uint32_t HwUcSetObjectId(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwUcSetTextureGradientFactors(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t HwUcSetTextureGradientFactors(CommandProcessor&, uint32_t, uint32_t, const uint32_t*,
uint32_t); uint32_t);
uint32_t HwUcSetIaMultiVgtParam(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t); uint32_t HwUcSetIaMultiVgtParam(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwUcSetMultiPrimIbReset(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t); uint32_t HwUcSetMultiPrimIbReset(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwUcSetBorderColorTableAddr(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t HwUcSetBorderColorTableAddr(CommandProcessor&, uint32_t, uint32_t, const uint32_t*,
uint32_t); uint32_t);
uint32_t HwUcSetGeIndexOffset(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t); uint32_t HwUcSetGeIndexOffset(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwUcSetGdsOaRegisters(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t); uint32_t HwUcSetGdsOaRegisters(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t CpOpNop(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t); uint32_t CpOpNop(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpSetBase(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t); uint32_t CpOpSetBase(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpClearState(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t); uint32_t CpOpClearState(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpIndexBase(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t); uint32_t CpOpIndexBase(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpIndexBufferSize(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t); uint32_t CpOpIndexBufferSize(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpDispatchDirect(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t); uint32_t CpOpDispatchDirect(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpDispatchIndirect(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t); uint32_t CpOpDispatchIndirect(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpDrawIndirect(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t); uint32_t CpOpDrawIndirect(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpDrawIndirectMulti(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t); uint32_t CpOpDrawIndirectMulti(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpDrawIndex(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t); uint32_t CpOpDrawIndex(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpDrawIndexOffset(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t); uint32_t CpOpDrawIndexOffset(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpPfpSyncMe(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t); uint32_t CpOpPfpSyncMe(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpIndexType(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t); uint32_t CpOpIndexType(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpNumInstances(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t); uint32_t CpOpNumInstances(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpDrawIndexAuto(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t); uint32_t CpOpDrawIndexAuto(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpCondExec(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t); uint32_t CpOpCondExec(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpSetPredication(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t); uint32_t CpOpSetPredication(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpWriteData(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t); uint32_t CpOpWriteData(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpIndirectBuffer(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t); uint32_t CpOpIndirectBuffer(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpCopyData(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t); uint32_t CpOpCopyData(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpEventWrite(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t); uint32_t CpOpEventWrite(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpEventWriteEop(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t); uint32_t CpOpEventWriteEop(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpEventWriteEos(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t); uint32_t CpOpEventWriteEos(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpDmaData(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t); uint32_t CpOpDmaData(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpAcquireMem(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t); uint32_t CpOpAcquireMem(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpSetContextReg(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t); uint32_t CpOpSetContextReg(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpSetShaderReg(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t); uint32_t CpOpSetShaderReg(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpSetUconfigReg(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t); uint32_t CpOpSetUconfigReg(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpIndirectCxRegs(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t); uint32_t CpOpIndirectCxRegs(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpIndirectShRegs(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t); uint32_t CpOpIndirectShRegs(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpIndirectUcRegs(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t); uint32_t CpOpIndirectUcRegs(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpWriteConstRam(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t); uint32_t CpOpWriteConstRam(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpDumpConstRam(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t); uint32_t CpOpDumpConstRam(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpIncrementCeCounter(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t); uint32_t CpOpIncrementCeCounter(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpIncrementDeCounter(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t); uint32_t CpOpIncrementDeCounter(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpWaitOnCeCounter(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t); uint32_t CpOpWaitOnCeCounter(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpWaitOnDeCounterDiff(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t); uint32_t CpOpWaitOnDeCounterDiff(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpGetLodStats(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t); uint32_t CpOpGetLodStats(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpDispatchReset(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t); uint32_t CpOpDispatchReset(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpWaitRegMem32(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t); uint32_t CpOpWaitRegMem32(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpWaitFlipDone(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t); uint32_t CpOpWaitFlipDone(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpPushMarker(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t); uint32_t CpOpPushMarker(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpPopMarker(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t); uint32_t CpOpPopMarker(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpWaitRegMem64(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t); uint32_t CpOpWaitRegMem64(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpFlip(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t); uint32_t CpOpFlip(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpReleaseMem(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t); uint32_t CpOpReleaseMem(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
} // namespace Libs::Graphics } // namespace Libs::Graphics
File diff suppressed because it is too large Load Diff
+21
View File
@@ -72,6 +72,7 @@ enum class ChannelLayout : uint32_t {
k32_32 = 11, k32_32 = 11,
k16_16_16_16 = 12, k16_16_16_16 = 12,
k32_32_32_32 = 14, k32_32_32_32 = 14,
k5_6_5 = 16,
k5_5_5_1 = 17, k5_5_5_1 = 17,
k4_4_4_4 = 19, k4_4_4_4 = 19,
kBc1 = 35, kBc1 = 35,
@@ -142,6 +143,24 @@ enum class StencilFormat : uint32_t {
k8UInt = 1, k8UInt = 1,
}; };
enum class TextureCompatiblePlaneCompression : uint32_t {
kDisable = 0x00000000,
kEnable = 0x02900800,
kBitMask = 0x02900800,
};
enum class TextureCompatibleStencil : uint32_t {
kDisable = 0x00000000,
kEnable = 0x00100800,
kBitMask = 0x00100800,
};
enum class ZCompareBase : uint32_t {
kZMin = 0x00000000,
kZMax = 0x80000000,
kBitMask = 0x80000000,
};
enum class TileMode : uint32_t { enum class TileMode : uint32_t {
kLinear = 0x00, kLinear = 0x00,
kStandard256B = 0x01, kStandard256B = 0x01,
@@ -356,6 +375,8 @@ enum class BufferFormat : uint32_t {
k32_32_32_32UInt = 75, k32_32_32_32UInt = 75,
k32_32_32_32SInt = 76, k32_32_32_32SInt = 76,
k32_32_32_32Float = 77, k32_32_32_32Float = 77,
k8Srgb = 128,
k8_8Srgb = 129,
k8_8_8_8Srgb = 130, k8_8_8_8Srgb = 130,
k9_9_9_5Float = 132, k9_9_9_5Float = 132,
k5_6_5UNorm = 133, k5_6_5UNorm = 133,
+3
View File
@@ -39,6 +39,7 @@ constexpr FormatInfo kFormatInfo[] = {
{GpuEnumValue(BufferFormat::k16_16Float), 4, 0, 4, true, false}, {GpuEnumValue(BufferFormat::k16_16Float), 4, 0, 4, true, false},
{GpuEnumValue(BufferFormat::k11_11_10Float), 4, 0, 4, true, false}, {GpuEnumValue(BufferFormat::k11_11_10Float), 4, 0, 4, true, false},
{GpuEnumValue(BufferFormat::k10_10_10_2UNorm), 4, 0, 4, true, false}, {GpuEnumValue(BufferFormat::k10_10_10_2UNorm), 4, 0, 4, true, false},
{GpuEnumValue(BufferFormat::k10_10_10_2UInt), 4, 0, 4, true, true},
{GpuEnumValue(BufferFormat::k8_8_8_8UNorm), 4, 0, 4, true, false}, {GpuEnumValue(BufferFormat::k8_8_8_8UNorm), 4, 0, 4, true, false},
{GpuEnumValue(BufferFormat::k8_8_8_8SNorm), 4, 0, 4, true, false}, {GpuEnumValue(BufferFormat::k8_8_8_8SNorm), 4, 0, 4, true, false},
{GpuEnumValue(BufferFormat::k8_8_8_8UInt), 4, 0, 4, true, true}, {GpuEnumValue(BufferFormat::k8_8_8_8UInt), 4, 0, 4, true, true},
@@ -57,6 +58,8 @@ constexpr FormatInfo kFormatInfo[] = {
{GpuEnumValue(BufferFormat::k32_32_32_32UInt), 16, 0, 16, true, true}, {GpuEnumValue(BufferFormat::k32_32_32_32UInt), 16, 0, 16, true, true},
{GpuEnumValue(BufferFormat::k32_32_32_32SInt), 16, 0, 16, false, false}, {GpuEnumValue(BufferFormat::k32_32_32_32SInt), 16, 0, 16, false, false},
{GpuEnumValue(BufferFormat::k32_32_32_32Float), 16, 0, 16, true, false}, {GpuEnumValue(BufferFormat::k32_32_32_32Float), 16, 0, 16, true, false},
{GpuEnumValue(BufferFormat::k8Srgb), 1, 0, 0, true, false},
{GpuEnumValue(BufferFormat::k8_8Srgb), 2, 0, 0, true, false},
{GpuEnumValue(BufferFormat::k8_8_8_8Srgb), 4, 0, 4, true, false}, {GpuEnumValue(BufferFormat::k8_8_8_8Srgb), 4, 0, 4, true, false},
{GpuEnumValue(BufferFormat::k9_9_9_5Float), 4, 0, 0, true, false}, {GpuEnumValue(BufferFormat::k9_9_9_5Float), 4, 0, 0, true, false},
{GpuEnumValue(BufferFormat::k5_6_5UNorm), 2, 0, 2, true, false}, {GpuEnumValue(BufferFormat::k5_6_5UNorm), 2, 0, 2, true, false},
File diff suppressed because it is too large Load Diff
+46 -20
View File
@@ -3,33 +3,59 @@
#include "common/abi.h" #include "common/abi.h"
#include "common/common.h" #include "common/common.h"
#include "common/uniqueFunction.h"
#include <memory>
namespace Libs::Graphics { namespace Libs::Graphics {
class CommandProcessor; class CommandProcessor;
class GpuState;
class RenderContext;
class GraphicsRunSubmissionLock final { class Gpu final {
public: public:
GraphicsRunSubmissionLock(); explicit Gpu(RenderContext& renderer);
~GraphicsRunSubmissionLock(); ~Gpu();
KYTY_CLASS_NO_COPY(GraphicsRunSubmissionLock); KYTY_CLASS_NO_COPY(Gpu);
void Shutdown();
[[nodiscard]] bool IsStopping();
void SendCommand(Common::UniqueFunction<void>&& command);
void SendCommandSync(Common::UniqueFunction<void>&& command);
void SendCommandSyncWithProcessor(Common::UniqueFunction<void, CommandProcessor&>&& command);
void Submit(uint32_t* draw_commands, uint32_t draw_size_dw, uint32_t* constant_commands,
uint32_t constant_size_dw, bool trigger_agc_interrupt_on_done = false);
void SubmitCompute(uint32_t queue, uint32_t* commands, uint32_t size_dw,
bool trigger_agc_interrupt_on_done = false);
void SubmitFlipPreparation(uint64_t request_id);
void Done();
[[nodiscard]] int GetFrameNum() const;
[[nodiscard]] static bool IsCommandProcessorThread() noexcept;
[[nodiscard]] static CommandProcessor* CurrentCommandProcessor() noexcept;
[[nodiscard]] static bool SubmissionLockHeld() noexcept;
[[nodiscard]] static bool MutexHeld() noexcept;
class SubmissionLock final {
public:
explicit SubmissionLock(Gpu& gpu);
~SubmissionLock();
KYTY_CLASS_NO_COPY(SubmissionLock);
private:
Gpu& m_gpu;
};
private:
friend class SubmissionLock;
void PauseSubmissions();
void ResumeSubmissions();
std::unique_ptr<GpuState> m_state;
}; };
void GraphicsRunInit();
void GraphicsRunSubmit(uint32_t* cmd_draw_buffer, uint32_t num_draw_dw, uint32_t* cmd_const_buffer,
uint32_t num_const_dw, bool trigger_agc_interrupt_on_done = false);
void GraphicsRunSubmitCompute(uint32_t queue, uint32_t* cmd_buffer, uint32_t num_dw,
bool trigger_agc_interrupt_on_done = false);
void GraphicsRunSubmitFlipPreparation();
void GraphicsRunWait();
void GraphicsRunDone();
int GraphicsRunGetFrameNum();
[[nodiscard]] bool GraphicsRunIsCommandProcessorThread() noexcept;
[[nodiscard]] CommandProcessor* GraphicsRunCurrentCommandProcessor() noexcept;
void GraphicsRunFinishCommandProcessors();
[[nodiscard]] bool GraphicsRunSubmissionLockHeld() noexcept;
[[nodiscard]] bool GraphicsRunGpuLockHeld() noexcept;
} // namespace Libs::Graphics } // namespace Libs::Graphics
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_GRAPHICSRUN_H_ */ #endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_GRAPHICSRUN_H_ */
+63 -17
View File
@@ -3,6 +3,7 @@
#include "common/abi.h" #include "common/abi.h"
#include "common/common.h" #include "common/common.h"
#include "graphics/guest_gpu/gpu_defs.h"
namespace Libs::Graphics::HW { namespace Libs::Graphics::HW {
@@ -131,26 +132,66 @@ struct RenderTarget {
}; };
struct DepthZInfo { struct DepthZInfo {
uint32_t format = 0; uint32_t format = 0;
uint32_t tile_mode_index = 0; uint32_t num_samples = 0;
uint32_t num_samples = 0; Prospero::TextureCompatiblePlaneCompression texture_compatibility =
uint32_t zrange_precision = 0; Prospero::TextureCompatiblePlaneCompression::kDisable;
bool tile_surface_enable = false; Prospero::ZCompareBase z_compare_base = Prospero::ZCompareBase::kZMin;
bool expclear_enabled = false; bool htile_acceleration = false;
bool embedded_sample_locations = false; bool expclear_enabled = false;
bool partially_resident = false; bool partially_resident = false;
uint8_t num_mip_levels = 0; uint8_t max_mip_level = 0;
uint8_t plane_compression = 0;
[[nodiscard]] static DepthZInfo Decode(uint32_t value) {
DepthZInfo info;
info.format = value & 0x3u;
info.num_samples = (value >> 2u) & 0x3u;
info.texture_compatibility = static_cast<Prospero::TextureCompatiblePlaneCompression>(
value & Prospero::GpuEnumValue(Prospero::TextureCompatiblePlaneCompression::kBitMask));
info.partially_resident = (value & 0x00001000u) != 0;
info.max_mip_level = static_cast<uint8_t>((value >> 16u) & 0x0fu);
info.expclear_enabled = (value & 0x08000000u) != 0;
info.htile_acceleration = (value & 0x20000000u) != 0;
info.z_compare_base = static_cast<Prospero::ZCompareBase>(
value & Prospero::GpuEnumValue(Prospero::ZCompareBase::kBitMask));
return info;
}
[[nodiscard]] bool HasValidTextureCompatibility() const {
switch (texture_compatibility) {
case Prospero::TextureCompatiblePlaneCompression::kDisable:
case Prospero::TextureCompatiblePlaneCompression::kEnable: return true;
default: return false;
}
}
}; };
struct DepthStencilInfo { struct DepthStencilInfo {
uint32_t format = 0; uint32_t format = 0;
uint32_t tile_mode_index = 0; Prospero::TextureCompatibleStencil texture_compatibility =
uint32_t tile_split = 0; Prospero::TextureCompatibleStencil::kDisable;
bool expclear_enabled = false; bool expclear_enabled = false;
bool tile_stencil_disable = false; bool htile_stencil_disabled = false;
bool texture_compatible_stencil = false; bool partially_resident = false;
bool partially_resident = false;
[[nodiscard]] static DepthStencilInfo Decode(uint32_t value) {
DepthStencilInfo info;
info.format = value & 0x1u;
info.texture_compatibility = static_cast<Prospero::TextureCompatibleStencil>(
value & Prospero::GpuEnumValue(Prospero::TextureCompatibleStencil::kBitMask));
info.partially_resident = (value & 0x00001000u) != 0;
info.expclear_enabled = (value & 0x08000000u) != 0;
info.htile_stencil_disabled = (value & 0x20000000u) != 0;
return info;
}
[[nodiscard]] bool HasValidTextureCompatibility() const {
switch (texture_compatibility) {
case Prospero::TextureCompatibleStencil::kDisable:
case Prospero::TextureCompatibleStencil::kEnable: return true;
default: return false;
}
}
}; };
struct DepthRenderTargetDepthInfo { struct DepthRenderTargetDepthInfo {
@@ -253,6 +294,11 @@ struct ClipControl {
bool cull_on_clipping_error_disable = false; bool cull_on_clipping_error_disable = false;
bool linear_attribute_clip_enable = false; bool linear_attribute_clip_enable = false;
bool force_viewport_index_from_vs_enable = false; bool force_viewport_index_from_vs_enable = false;
[[nodiscard]] bool IsZClipModeRepresentable() const {
return min_z_clip_disable == max_z_clip_disable;
}
[[nodiscard]] bool IsZClipEnabled() const { return !min_z_clip_disable; }
}; };
struct DepthControl { struct DepthControl {
+3 -1
View File
@@ -110,7 +110,6 @@ void DumpPm4PacketStream(Common::File* file, uint32_t* cmd_buffer, uint32_t star
auto* cmd = cmd_buffer + start_dw; auto* cmd = cmd_buffer + start_dw;
auto dw = num_dw; auto dw = num_dw;
while (dw != 0) { while (dw != 0) {
EXIT_NOT_IMPLEMENTED(dw < 2);
EXIT_NOT_IMPLEMENTED(dw > num_dw); EXIT_NOT_IMPLEMENTED(dw > num_dw);
auto cmd_id = *cmd++; auto cmd_id = *cmd++;
@@ -120,6 +119,9 @@ void DumpPm4PacketStream(Common::File* file, uint32_t* cmd_buffer, uint32_t star
uint32_t len = 0; uint32_t len = 0;
const auto packet_type = static_cast<PacketType>(cmd_id >> 30u); const auto packet_type = static_cast<PacketType>(cmd_id >> 30u);
// Type-2 packets are header-only padding; every other packet type requires a body.
EXIT_NOT_IMPLEMENTED(dw < 2 && packet_type != PacketType::Type2);
switch (packet_type) { switch (packet_type) {
case PacketType::Type3: { case PacketType::Type3: {
const bool sh_gx = (cmd_id & 0x2u) == 0; const bool sh_gx = (cmd_id & 0x2u) == 0;
+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;
File diff suppressed because it is too large Load Diff
+61 -62
View File
@@ -6,15 +6,6 @@
namespace Libs::Graphics { namespace Libs::Graphics {
enum class TileMode {
VideoOutLinear,
VideoOutTiled,
TextureLinear,
TextureTiled,
// RenderTextureLinear,
// RenderTextureTiled,
};
struct TileSizeAlign { struct TileSizeAlign {
uint32_t size = 0; uint32_t size = 0;
uint32_t align = 0; uint32_t align = 0;
@@ -34,74 +25,82 @@ struct TilePaddedSize {
uint32_t height = 0; uint32_t height = 0;
}; };
void TileInit(); enum class TileBlockFamily : uint32_t {
void TileConvertTiledToLinear(void* dst, const void* src, TileMode mode, uint32_t width, Standard256B,
uint32_t height); Standard4KB,
void TileConvertTiledToLinearRenderTarget(void* dst, const void* src, uint32_t width, Standard4KB3D,
uint32_t height, uint32_t pitch, Standard64KB,
uint32_t bytes_per_element, uint64_t size, Standard64KB3D,
uint64_t src_size = 0, uint32_t src_x = 0, Prt64KB,
uint32_t src_y = 0); Prt64KB3D,
void TileConvertLinearToTiledRenderTarget(void* dst, const void* src, uint32_t width, RenderTarget64KB,
uint32_t height, uint32_t pitch, Depth64KB,
uint32_t bytes_per_element, uint64_t size, Count,
uint64_t dst_size = 0, uint32_t dst_x = 0, };
uint32_t dst_y = 0);
void TileConvertTiledToLinearStandard64KB(void* dst, const void* src, uint32_t format, struct TileBlockLayout {
uint32_t width, uint32_t height, uint32_t pitch, TileBlockFamily family = TileBlockFamily::Standard256B;
uint64_t size, uint64_t src_size = 0, uint32_t src_x = 0, uint32_t bytes_per_element = 0;
uint32_t src_y = 0); uint32_t block_size = 0;
void TileConvertTiledToLinearStandard64KB32(void* dst, const void* src, uint32_t width, uint32_t block_width = 0;
uint32_t height, uint32_t pitch, uint64_t size, uint32_t block_height = 0;
uint64_t src_size = 0, uint32_t src_x = 0, uint32_t block_depth = 0;
uint32_t src_y = 0); };
void TileConvertLinearToTiledStandard64KB32(void* dst, const void* src, uint32_t width,
uint32_t height, uint32_t pitch, uint64_t size); struct TileVolumeLayout {
void TileConvertTiledToLinearStandard64KB16(void* dst, const void* src, uint32_t width, TileBlockFamily family = TileBlockFamily::Count;
uint32_t height, uint32_t pitch, uint64_t size, uint32_t bytes_per_element = 0;
uint64_t src_size = 0, uint32_t src_x = 0, uint32_t texel_width = 1;
uint32_t src_y = 0); uint32_t texel_height = 1;
void TileConvertTiledToLinearDepth(void* dst, const void* src, uint32_t format, uint32_t width, uint32_t first_tail_level = 0;
uint32_t height, uint32_t pitch, uint64_t size); uint32_t block_depth = 1;
void TileConvertLinearToTiledDepth(void* dst, const void* src, uint32_t format, uint32_t width, uint64_t block_slice_size = 0;
uint32_t height, uint32_t pitch, uint64_t size); uint64_t total_size = 0;
void TileConvertTiledToLinearStandard4KB(void* dst, const void* src, uint32_t format, uint64_t level_offsets[16] = {};
uint32_t width, uint32_t height, uint32_t pitch, uint64_t level_sizes[16] = {};
uint64_t dst_size, uint64_t src_size, uint32_t src_x = 0, uint32_t tail_x[16] = {};
uint32_t src_y = 0); uint32_t tail_y[16] = {};
void TileConvertTiledToLinearStandard256B(void* dst, const void* src, uint32_t format, uint32_t level_widths[16] = {};
uint32_t width, uint32_t height, uint32_t pitch, uint32_t level_heights[16] = {};
uint64_t dst_size, uint64_t src_size); };
bool TileGetBlockLayout(TileBlockFamily family, uint32_t bytes_per_element,
TileBlockLayout& layout);
bool TileGetBlockOffset(const TileBlockLayout& layout, uint32_t x, uint32_t y, uint32_t z,
uint32_t& byte_offset);
bool TileGetBlockXor(const TileBlockLayout& layout, uint32_t block_x, uint32_t block_y,
uint32_t& byte_offset);
bool TileGetBlockXor(const TileBlockLayout& layout, uint32_t block_x, uint32_t block_y,
uint32_t block_z, uint32_t& byte_offset);
bool TileIsStandard256BTextureSupported(uint32_t format); bool TileIsStandard256BTextureSupported(uint32_t format);
bool TileIsStandard4KBTextureSupported(uint32_t format); bool TileIsStandard4KBTextureSupported(uint32_t format);
bool TileIsStandard64KBTextureSupported(uint32_t format); bool TileIsStandard64KBTextureSupported(uint32_t format);
bool TileGetStandard4KBVolumeLayout(uint32_t format, uint32_t* bytes_per_element, bool TileGetTextureVolumeLayout(uint32_t format, uint32_t width, uint32_t height, uint32_t depth,
uint32_t* texels_per_element_wide, uint32_t levels, uint32_t tile, TileVolumeLayout& layout);
uint32_t* texels_per_element_tall, uint32_t* block_width_log2,
uint32_t* block_height_log2, uint32_t* block_depth_log2);
bool TileGetHtileSize(uint32_t width, uint32_t height, TileSizeAlign& htile_size);
bool TileGetDepthSize(uint32_t width, uint32_t height, uint32_t pitch, uint32_t z_format, bool TileGetDepthSize(uint32_t width, uint32_t height, uint32_t pitch, uint32_t z_format,
uint32_t stencil_format, bool htile, TileSizeAlign* stencil_size, uint32_t stencil_format, bool htile, TileSizeAlign& stencil_size,
TileSizeAlign* htile_size, TileSizeAlign* depth_size); TileSizeAlign& htile_size, TileSizeAlign& depth_size,
uint32_t TileGetRenderTargetPitch(uint32_t width, uint32_t bytes_per_element); uint32_t num_fragments_log2 = 0);
uint32_t TileGetRenderTargetPitch(uint32_t width, uint32_t bytes_per_element,
uint32_t num_fragments_log2 = 0);
uint32_t TileGetDepthPitch(uint32_t width, uint32_t bytes_per_element,
uint32_t num_fragments_log2 = 0);
bool TileGetRenderTargetSize(uint32_t width, uint32_t height, uint32_t pitch, bool TileGetRenderTargetSize(uint32_t width, uint32_t height, uint32_t pitch,
uint32_t bytes_per_element, TileSizeAlign* total_size); uint32_t bytes_per_element, TileSizeAlign& total_size,
uint32_t num_fragments_log2 = 0);
bool TileGetRenderTargetMipLayout(uint32_t width, uint32_t height, uint32_t pitch, bool TileGetRenderTargetMipLayout(uint32_t width, uint32_t height, uint32_t pitch,
uint32_t bytes_per_element, uint32_t levels, uint32_t bytes_per_element, uint32_t levels,
TileSizeAlign* total_size, TileSizeOffset* level_sizes, TileSizeAlign& total_size, TileSizeOffset* level_sizes,
TilePaddedSize* padded_size); TilePaddedSize* padded_size);
void TileGetTextureSize(uint32_t format, uint32_t width, uint32_t height, uint32_t pitch, void TileGetTextureSize(uint32_t format, uint32_t width, uint32_t height, uint32_t pitch,
uint32_t levels, uint32_t tile, TileSizeAlign* total_size, uint32_t levels, uint32_t tile, TileSizeAlign* total_size,
TileSizeOffset* level_sizes, TilePaddedSize* padded_size); TileSizeOffset* level_sizes, TilePaddedSize* padded_size);
void TileGetTextureTotalSize(uint32_t format, uint32_t width, uint32_t height, uint32_t depth, void TileGetTextureTotalSize(uint32_t format, uint32_t width, uint32_t height, uint32_t depth,
uint32_t pitch, uint32_t levels, uint32_t tile, bool volume_texture, uint32_t pitch, uint32_t levels, uint32_t tile, bool volume_texture,
TileSizeAlign* total_size); TileSizeAlign& total_size);
uint32_t TileGetTexturePitch(uint32_t format, uint32_t width, uint32_t levels, uint32_t tile); uint32_t TileGetTexturePitch(uint32_t format, uint32_t width, uint32_t levels, uint32_t tile);
void TileConvertTiledToLinearStandard4KB3D(void* dst, const void* src, uint32_t format,
uint32_t width, uint32_t height, uint32_t depth,
uint32_t pitch, uint64_t dst_slice_stride,
uint64_t dst_size, uint64_t src_size,
bool clear_dst = true);
} // namespace Libs::Graphics } // namespace Libs::Graphics
+49 -119
View File
@@ -3,144 +3,76 @@
#include "common/abi.h" #include "common/abi.h"
#include "common/common.h" #include "common/common.h"
#include "common/threads.h"
#include "graphics/host_gpu/vulkanCommon.h" // IWYU pragma: export #include "graphics/host_gpu/vulkanCommon.h" // IWYU pragma: export
#include "graphics/host_gpu/vulkanInstance.h" #include "graphics/host_gpu/vulkanInstance.h"
#include <array>
#include <memory>
#include <mutex>
#include <vector> #include <vector>
#include <vk_mem_alloc.h> #include <vk_mem_alloc.h>
namespace Libs::Graphics { namespace Libs::Graphics {
class CommandBuffer; class CommandBuffer;
struct VulkanBuffer;
struct VulkanSwapchain { struct VulkanImage;
~VulkanSwapchain(); struct VulkanMemory;
vk::SwapchainKHR swapchain = nullptr;
vk::Format swapchain_format = vk::Format::eUndefined;
vk::Extent2D swapchain_extent = {};
std::unique_ptr<vk::Image[]> swapchain_images;
std::unique_ptr<vk::ImageView[]> swapchain_image_views;
uint32_t swapchain_images_count = 0;
std::unique_ptr<vk::Semaphore[]> image_acquired_semaphores;
std::unique_ptr<vk::Semaphore[]> render_complete_semaphores;
uint32_t current_index = 0;
uint32_t present_frame = 0;
};
struct VulkanCommandPool {
Common::Mutex mutex;
vk::CommandPool pool = nullptr;
std::unique_ptr<vk::CommandBuffer[]> buffers;
std::unique_ptr<vk::Fence[]> fences;
std::unique_ptr<vk::Semaphore[]> semaphores;
std::unique_ptr<bool[]> busy;
uint32_t buffers_count = 0;
};
struct GraphicContext: public VulkanInstance { struct GraphicContext: public VulkanInstance {
uint32_t screen_width = 0; [[nodiscard]] bool CreateAllocator();
uint32_t screen_height = 0; void DestroyAllocator();
std::array<Common::Mutex, QUEUES_NUM> queue_mutexes; void LogMemoryBudget() const;
[[nodiscard]] bool CanReportMemoryUsage() const noexcept { return memory_budget_ext_enabled; }
[[nodiscard]] uint64_t GetDeviceMemoryUsage() const;
[[nodiscard]] uint64_t GetTotalMemoryBudget() const;
void CreateBuffer(uint64_t size, VulkanBuffer& buffer);
void DeleteBuffer(VulkanBuffer& buffer);
[[nodiscard]] bool CreateImage(const vk::ImageCreateInfo& info, VulkanImage& image);
void DeleteImage(VulkanImage& image);
void MapMemory(VulkanMemory& memory, void*& data);
void UnmapMemory(VulkanMemory& memory);
void AppendHardwareRayTracingDeviceExtensions(
const std::vector<vk::ExtensionProperties>& available_extensions,
std::vector<const char*>& device_extensions);
void LoadHardwareRayTracingFunctions() const;
uint32_t screen_width = 0;
uint32_t screen_height = 0;
}; };
struct VulkanMemory { struct VulkanMemory {
vk::MemoryRequirements requirements = {}; vk::MemoryRequirements requirements = {};
vk::MemoryPropertyFlags property = {}; vk::MemoryPropertyFlags property = {};
vk::DeviceMemory memory = nullptr; vk::MemoryPropertyFlags preferred_property = {};
VmaAllocation allocation = nullptr; vk::DeviceMemory memory = nullptr;
VmaAllocationInfo allocation_info = {}; VmaAllocation allocation = nullptr;
vk::DeviceSize offset = 0; VmaAllocationInfo allocation_info = {};
uint32_t type = 0; vk::DeviceSize offset = 0;
uint64_t unique_id = 0; uint32_t type = 0;
uint64_t unique_id = 0;
}; };
enum class VulkanImageType { struct VulkanImageState {
Unknown, vk::PipelineStageFlags2 pl_stage = vk::PipelineStageFlagBits2::eAllCommands;
VideoOut, vk::AccessFlags2 access_mask = vk::AccessFlagBits2::eNone;
DepthStencil, vk::ImageLayout layout = vk::ImageLayout::eUndefined;
Texture,
StorageTexture,
RenderTexture
};
struct ImageViewInfo {
vk::Format format = vk::Format::eUndefined;
vk::ImageViewType type = vk::ImageViewType::e2D;
vk::ImageAspectFlags aspect = {};
uint32_t base_level = 0;
uint32_t level_count = 0;
uint32_t base_layer = 0;
uint32_t layer_count = 1;
uint32_t swizzle = 0;
vk::ImageUsageFlags usage = vk::ImageUsageFlagBits::eSampled;
bool operator==(const ImageViewInfo&) const = default;
};
struct CachedImageView {
ImageViewInfo info;
vk::ImageView view = nullptr;
};
struct ImageViewCache {
std::mutex mutex;
std::vector<CachedImageView> views;
ImageViewCache() = default;
KYTY_CLASS_NO_COPY(ImageViewCache);
}; };
struct VulkanImage { struct VulkanImage {
static constexpr int VIEW_MAX = 4; VulkanImage() = default;
static constexpr int VIEW_DEFAULT = 0;
static constexpr int VIEW_DEFAULT_ARRAY = 1;
static constexpr int VIEW_STORAGE = 2;
static constexpr int VIEW_STORAGE_ARRAY = 3;
explicit VulkanImage(VulkanImageType type): type(type) {}
KYTY_CLASS_NO_COPY(VulkanImage); KYTY_CLASS_NO_COPY(VulkanImage);
VulkanImageType type = VulkanImageType::Unknown; vk::Format format = vk::Format::eUndefined;
vk::Format format = vk::Format::eUndefined; vk::ImageType image_type = vk::ImageType::e2D;
vk::Extent2D extent = {}; vk::Extent3D extent = {1, 1, 1};
uint32_t guest_pitch = 0; uint32_t guest_pitch = 0;
uint32_t layers = 1; uint32_t layers = 1;
uint32_t mip_levels = 1; uint32_t mip_levels = 1;
vk::Image image = nullptr; uint32_t samples = 1;
vk::ImageView image_view[VIEW_MAX] = {}; vk::ImageUsageFlags usage = {};
vk::ImageLayout layout = vk::ImageLayout::eUndefined; vk::ImageCreateFlags flags = {};
Graphics::VulkanMemory memory; vk::Image image = nullptr;
ImageViewCache view_cache; VulkanImageState state;
}; std::vector<VulkanImageState> subresource_states;
Graphics::VulkanMemory memory;
struct VideoOutVulkanImage: public VulkanImage {
VideoOutVulkanImage(): VulkanImage(VulkanImageType::VideoOut) {}
};
struct DepthStencilVulkanImage: public VulkanImage {
DepthStencilVulkanImage(): VulkanImage(VulkanImageType::DepthStencil) {}
bool compressed = false;
};
struct GpuTextureVulkanImage: public VulkanImage {
explicit GpuTextureVulkanImage(VulkanImageType type): VulkanImage(type) {}
};
struct TextureVulkanImage: public GpuTextureVulkanImage {
TextureVulkanImage(): GpuTextureVulkanImage(VulkanImageType::Texture) {}
};
struct StorageTextureVulkanImage: public GpuTextureVulkanImage {
StorageTextureVulkanImage(): GpuTextureVulkanImage(VulkanImageType::StorageTexture) {}
};
struct RenderTextureVulkanImage: public VulkanImage {
RenderTextureVulkanImage(): VulkanImage(VulkanImageType::RenderTexture) {}
}; };
struct VulkanBuffer { struct VulkanBuffer {
@@ -150,8 +82,6 @@ struct VulkanBuffer {
uint64_t buffer_size = 0; uint64_t buffer_size = 0;
}; };
struct StorageVulkanBuffer: public VulkanBuffer {};
} // namespace Libs::Graphics } // namespace Libs::Graphics
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_GRAPHICCONTEXT_H_ */ #endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_GRAPHICCONTEXT_H_ */
+6 -9
View File
@@ -30,11 +30,8 @@ bool IsAccessible(DWORD protect, HostMemoryAccess access) {
} // namespace } // namespace
bool HostMemoryQueryRange(uint64_t addr, uint64_t requested_size, HostMemoryAccess access, bool HostMemoryQueryRange(uint64_t addr, uint64_t requested_size, HostMemoryAccess access,
uint64_t* accessible_size) { uint64_t& accessible_size) {
if (accessible_size == nullptr) { accessible_size = 0;
return false;
}
*accessible_size = 0;
if (addr == 0 || requested_size == 0) { if (addr == 0 || requested_size == 0) {
return false; return false;
} }
@@ -87,11 +84,11 @@ bool HostMemoryQueryRange(uint64_t addr, uint64_t requested_size, HostMemoryAcce
(void)access; (void)access;
#endif #endif
*accessible_size = current - addr; accessible_size = current - addr;
return *accessible_size != 0; return accessible_size != 0;
} }
bool HostMemoryQueryReadable(uint64_t addr, uint64_t requested_size, uint64_t* readable_size) { bool HostMemoryQueryReadable(uint64_t addr, uint64_t requested_size, uint64_t& readable_size) {
return HostMemoryQueryRange(addr, requested_size, HostMemoryAccess::Read, readable_size); return HostMemoryQueryRange(addr, requested_size, HostMemoryAccess::Read, readable_size);
} }
@@ -104,7 +101,7 @@ bool HostMemoryRangeIsReadable(uint64_t addr, uint64_t size) {
return false; return false;
} }
uint64_t readable_size = 0; uint64_t readable_size = 0;
return HostMemoryQueryReadable(addr, size, &readable_size) && readable_size >= size; return HostMemoryQueryReadable(addr, size, readable_size) && readable_size >= size;
} }
} // namespace Libs::Graphics } // namespace Libs::Graphics
+2 -2
View File
@@ -8,8 +8,8 @@ namespace Libs::Graphics {
enum class HostMemoryAccess { Read, Mapped }; enum class HostMemoryAccess { Read, Mapped };
bool HostMemoryQueryRange(uint64_t addr, uint64_t requested_size, HostMemoryAccess access, bool HostMemoryQueryRange(uint64_t addr, uint64_t requested_size, HostMemoryAccess access,
uint64_t* accessible_size); uint64_t& accessible_size);
bool HostMemoryQueryReadable(uint64_t addr, uint64_t requested_size, uint64_t* readable_size); bool HostMemoryQueryReadable(uint64_t addr, uint64_t requested_size, uint64_t& readable_size);
bool HostMemoryIsReadable(uint64_t addr); bool HostMemoryIsReadable(uint64_t addr);
bool HostMemoryRangeIsReadable(uint64_t addr, uint64_t size); bool HostMemoryRangeIsReadable(uint64_t addr, uint64_t size);
+38 -123
View File
@@ -4,16 +4,6 @@
namespace Libs::Graphics { namespace Libs::Graphics {
#if defined(KYTY_MEMORY_TRACKER_TESTS)
namespace {
std::atomic<MemoryTracker::UnmapContentionHook> g_unmap_contention_hook {nullptr};
}
void MemoryTracker::SetUnmapContentionHook(UnmapContentionHook hook) noexcept {
g_unmap_contention_hook.store(hook, std::memory_order_release);
}
#endif
static_assert(std::atomic<void*>::is_always_lock_free); static_assert(std::atomic<void*>::is_always_lock_free);
MemoryTracker::MemoryTracker(PageManager& page_manager, PageWatchMode gpu_watch_mode) MemoryTracker::MemoryTracker(PageManager& page_manager, PageWatchMode gpu_watch_mode)
@@ -31,6 +21,44 @@ MemoryTracker::MemoryTracker(PageManager& page_manager, PageWatchMode gpu_watch_
MemoryTracker::~MemoryTracker() = default; MemoryTracker::~MemoryTracker() = default;
void MemoryTracker::ValidateGpuDirtyPages(const RangeSet& dirty, uint64_t vaddr, uint64_t size,
const char* operation) const noexcept {
if (vaddr == 0 || size == 0 || size > UINT64_MAX - vaddr ||
(vaddr & (TRACKER_PAGE_SIZE - 1)) != 0 || (size & (TRACKER_PAGE_SIZE - 1)) != 0) {
EXIT("MemoryTracker: invalid dirty-page validation range\n");
}
for (auto page = vaddr; page < vaddr + size; page += TRACKER_PAGE_SIZE) {
bool found = false;
dirty.ForEachIntersection(page, TRACKER_PAGE_SIZE,
[&found](RangeSet::Range) { found = true; });
if (!found) {
EXIT("MemoryTracker: GPU-dirty tracker page has no dirty bytes, operation=%s "
"addr=0x%016" PRIx64 "\n",
operation, page);
}
}
}
void MemoryTracker::ValidateGpuDirtyOwnership(const RangeSet& dirty, uint64_t vaddr, uint64_t size,
const char* operation) {
ValidateRange(vaddr, size);
if (vaddr + size > UINT64_MAX - (TRACKER_PAGE_SIZE - 1)) {
EXIT("MemoryTracker: dirty ownership range alignment overflow\n");
}
const auto begin = vaddr & ~(TRACKER_PAGE_SIZE - 1);
const auto end = (vaddr + size + TRACKER_PAGE_SIZE - 1) & ~(TRACKER_PAGE_SIZE - 1);
for (auto page = begin; page < end; page += TRACKER_PAGE_SIZE) {
bool has_dirty_bytes = false;
dirty.ForEachIntersection(page, TRACKER_PAGE_SIZE,
[&has_dirty_bytes](RangeSet::Range) { has_dirty_bytes = true; });
if (IsRegionGpuModified(page, TRACKER_PAGE_SIZE) != has_dirty_bytes) {
EXIT("MemoryTracker: tracker and byte ownership disagree, operation=%s "
"addr=0x%016" PRIx64 "\n",
operation, page);
}
}
}
void MemoryTracker::ValidateRange(uint64_t vaddr, uint64_t size) { void MemoryTracker::ValidateRange(uint64_t vaddr, uint64_t size) {
if (vaddr == 0 || size == 0 || vaddr >= TRACKER_ADDRESS_SIZE || if (vaddr == 0 || size == 0 || vaddr >= TRACKER_ADDRESS_SIZE ||
size > TRACKER_ADDRESS_SIZE - vaddr) { size > TRACKER_ADDRESS_SIZE - vaddr) {
@@ -56,7 +84,6 @@ RegionManager* MemoryTracker::GetOrCreateRegion(uint64_t index) {
bool MemoryTracker::IsRegionCpuModified(uint64_t vaddr, uint64_t size) { bool MemoryTracker::IsRegionCpuModified(uint64_t vaddr, uint64_t size) {
CheckNotInUploadCallback(); CheckNotInUploadCallback();
std::lock_guard access(m_access_mutex); std::lock_guard access(m_access_mutex);
RequireMapped(vaddr, size);
return Iterate<true>(vaddr, size, [](RegionManager* manager, uint64_t offset, uint64_t bytes) { return Iterate<true>(vaddr, size, [](RegionManager* manager, uint64_t offset, uint64_t bytes) {
std::scoped_lock lock(manager->lock); std::scoped_lock lock(manager->lock);
return manager->IsModified<DirtySource::Cpu>(offset, bytes); return manager->IsModified<DirtySource::Cpu>(offset, bytes);
@@ -66,7 +93,6 @@ bool MemoryTracker::IsRegionCpuModified(uint64_t vaddr, uint64_t size) {
bool MemoryTracker::IsRegionGpuModified(uint64_t vaddr, uint64_t size) { bool MemoryTracker::IsRegionGpuModified(uint64_t vaddr, uint64_t size) {
CheckNotInUploadCallback(); CheckNotInUploadCallback();
std::lock_guard access(m_access_mutex); std::lock_guard access(m_access_mutex);
RequireMapped(vaddr, size);
return Iterate<false>(vaddr, size, [](RegionManager* manager, uint64_t offset, uint64_t bytes) { return Iterate<false>(vaddr, size, [](RegionManager* manager, uint64_t offset, uint64_t bytes) {
std::scoped_lock lock(manager->lock); std::scoped_lock lock(manager->lock);
return manager->IsModified<DirtySource::Gpu>(offset, bytes); return manager->IsModified<DirtySource::Gpu>(offset, bytes);
@@ -76,7 +102,6 @@ bool MemoryTracker::IsRegionGpuModified(uint64_t vaddr, uint64_t size) {
void MemoryTracker::MarkRegionAsCpuModified(uint64_t vaddr, uint64_t size) { void MemoryTracker::MarkRegionAsCpuModified(uint64_t vaddr, uint64_t size) {
CheckNotInUploadCallback(); CheckNotInUploadCallback();
std::lock_guard access(m_access_mutex); std::lock_guard access(m_access_mutex);
RequireMapped(vaddr, size);
Iterate<true>(vaddr, size, [](RegionManager* manager, uint64_t offset, uint64_t bytes) { Iterate<true>(vaddr, size, [](RegionManager* manager, uint64_t offset, uint64_t bytes) {
std::scoped_lock lock(manager->lock); std::scoped_lock lock(manager->lock);
const auto changed = const auto changed =
@@ -88,7 +113,6 @@ void MemoryTracker::MarkRegionAsCpuModified(uint64_t vaddr, uint64_t size) {
void MemoryTracker::MarkRegionAsGpuModified(uint64_t vaddr, uint64_t size) { void MemoryTracker::MarkRegionAsGpuModified(uint64_t vaddr, uint64_t size) {
CheckNotInUploadCallback(); CheckNotInUploadCallback();
std::lock_guard access(m_access_mutex); std::lock_guard access(m_access_mutex);
RequireMapped(vaddr, size);
Iterate<true>(vaddr, size, [this](RegionManager* manager, uint64_t offset, uint64_t bytes) { Iterate<true>(vaddr, size, [this](RegionManager* manager, uint64_t offset, uint64_t bytes) {
std::scoped_lock lock(manager->lock); std::scoped_lock lock(manager->lock);
const auto changed = const auto changed =
@@ -100,7 +124,6 @@ void MemoryTracker::MarkRegionAsGpuModified(uint64_t vaddr, uint64_t size) {
void MemoryTracker::UnmarkRegionAsGpuModified(uint64_t vaddr, uint64_t size) { void MemoryTracker::UnmarkRegionAsGpuModified(uint64_t vaddr, uint64_t size) {
CheckNotInUploadCallback(); CheckNotInUploadCallback();
std::lock_guard access(m_access_mutex); std::lock_guard access(m_access_mutex);
RequireMapped(vaddr, size);
Iterate<true>(vaddr, size, [this](RegionManager* manager, uint64_t offset, uint64_t bytes) { Iterate<true>(vaddr, size, [this](RegionManager* manager, uint64_t offset, uint64_t bytes) {
std::scoped_lock lock(manager->lock); std::scoped_lock lock(manager->lock);
if (!manager->IsFullyModified<DirtySource::Gpu>(offset, bytes)) { if (!manager->IsFullyModified<DirtySource::Gpu>(offset, bytes)) {
@@ -113,8 +136,6 @@ void MemoryTracker::UnmarkRegionAsGpuModified(uint64_t vaddr, uint64_t size) {
} }
void MemoryTracker::UntrackMemoryLocked(uint64_t vaddr, uint64_t size) { void MemoryTracker::UntrackMemoryLocked(uint64_t vaddr, uint64_t size) {
RequireMapped(vaddr, size);
std::vector<RegionManager*> managers; std::vector<RegionManager*> managers;
managers.reserve((vaddr % TRACKER_REGION_SIZE + size + TRACKER_REGION_SIZE - 1) / managers.reserve((vaddr % TRACKER_REGION_SIZE + size + TRACKER_REGION_SIZE - 1) /
TRACKER_REGION_SIZE); TRACKER_REGION_SIZE);
@@ -136,7 +157,6 @@ void MemoryTracker::UntrackMemoryLocked(uint64_t vaddr, uint64_t size) {
const auto changed = const auto changed =
manager->ChangeState<DirtySource::Cpu, true>(manager->GetCpuAddr() + offset, bytes); manager->ChangeState<DirtySource::Cpu, true>(manager->GetCpuAddr() + offset, bytes);
manager->ApplyProtection(changed, false); manager->ApplyProtection(changed, false);
manager->Untrack(manager->GetCpuAddr() + offset, bytes);
}); });
locks.clear(); locks.clear();
} }
@@ -147,109 +167,4 @@ void MemoryTracker::UntrackMemory(uint64_t vaddr, uint64_t size) {
UntrackMemoryLocked(vaddr, size); UntrackMemoryLocked(vaddr, size);
} }
void MemoryTracker::UnmapMemory(uint64_t vaddr, uint64_t size) {
CheckNotInUploadCallback();
std::unique_lock access(m_access_mutex, std::try_to_lock);
if (!access.owns_lock()) {
#if defined(KYTY_MEMORY_TRACKER_TESTS)
if (const auto hook = g_unmap_contention_hook.load(std::memory_order_acquire);
hook != nullptr) {
hook();
}
#endif
access.lock();
}
UntrackMemoryLocked(vaddr, size);
m_page_manager.OnGpuUnmap(vaddr, size);
}
bool MemoryTracker::InvalidateRegion(uint64_t vaddr, uint64_t size, PageFaultPhase phase) noexcept {
switch (phase) {
case PageFaultPhase::Release: return true;
case PageFaultPhase::Invalidate: {
const auto action = BeginCpuFault(vaddr, size);
switch (action) {
case CpuFaultAction::Untracked: return false;
case CpuFaultAction::Continue: return true;
case CpuFaultAction::Download:
EXIT("generic region invalidation cannot download GPU-dirty memory\n");
}
}
case PageFaultPhase::Complete:
return CompleteCpuFault(vaddr, size, PageFaultAccess::Write, false);
}
EXIT("unsupported region invalidation phase\n");
}
bool MemoryTracker::InvalidateVirtualGpuWrite(PageFaultAccess access, uint64_t vaddr, uint64_t size,
PageFaultPhase phase) noexcept {
switch (phase) {
case PageFaultPhase::Release: return true;
case PageFaultPhase::Invalidate: {
const bool gpu_modified = Iterate<false>(
vaddr, size, [](RegionManager* manager, uint64_t offset, uint64_t bytes) {
std::scoped_lock lock(manager->lock);
return manager->IsModified<DirtySource::Gpu>(offset, bytes);
});
if (!gpu_modified) {
return false;
}
const auto action = BeginCpuFault(vaddr, size);
if (access != PageFaultAccess::Write || action != CpuFaultAction::Download) {
EXIT("virtual GPU write fault requires write access to GPU-dirty memory\n");
}
return true;
}
case PageFaultPhase::Complete: {
if (access != PageFaultAccess::Write) {
EXIT("virtual GPU write completion requires write access\n");
}
bool completed = false;
Iterate<false>(
vaddr, size, [&completed](RegionManager* manager, uint64_t offset, uint64_t bytes) {
std::scoped_lock lock(manager->lock);
if (completed) {
EXIT("virtual GPU write fault spans multiple tracked regions\n");
}
completed =
manager->CompleteVirtualGpuWrite(manager->GetCpuAddr() + offset, bytes);
});
return completed;
}
}
EXIT("unsupported virtual GPU write invalidation phase\n");
}
CpuFaultAction MemoryTracker::BeginCpuFault(uint64_t vaddr, uint64_t size,
PageFaultAccess access) noexcept {
CheckNotInUploadCallback();
CpuFaultAction action = CpuFaultAction::Untracked;
Iterate<false>(
vaddr, size, [&action, access](RegionManager* manager, uint64_t offset, uint64_t bytes) {
std::scoped_lock lock(manager->lock);
if (action != CpuFaultAction::Untracked) {
EXIT("CPU fault spans multiple tracked regions\n");
}
action = manager->BeginCpuFault(manager->GetCpuAddr() + offset, bytes, access);
});
return action;
}
bool MemoryTracker::CompleteCpuFault(uint64_t vaddr, uint64_t size, PageFaultAccess access,
bool downloaded) noexcept {
CheckNotInUploadCallback();
bool found = false;
Iterate<false>(
vaddr, size,
[&found, access, downloaded](RegionManager* manager, uint64_t offset, uint64_t bytes) {
std::scoped_lock lock(manager->lock);
if (found) {
EXIT("CPU fault completion spans multiple tracked regions\n");
}
found = manager->CompleteCpuFault(manager->GetCpuAddr() + offset, bytes, access,
downloaded);
});
return found;
}
} // namespace Libs::Graphics } // namespace Libs::Graphics
+54 -34
View File
@@ -3,6 +3,7 @@
#include "common/assert.h" #include "common/assert.h"
#include "graphics/host_gpu/pageManager.h" #include "graphics/host_gpu/pageManager.h"
#include "graphics/host_gpu/rangeSet.h"
#include "graphics/host_gpu/regionManager.h" #include "graphics/host_gpu/regionManager.h"
#include <algorithm> #include <algorithm>
@@ -10,6 +11,7 @@
#include <memory> #include <memory>
#include <mutex> #include <mutex>
#include <type_traits> #include <type_traits>
#include <utility>
#include <vector> #include <vector>
namespace Libs::Graphics { namespace Libs::Graphics {
@@ -28,24 +30,58 @@ public:
void MarkRegionAsGpuModified(uint64_t vaddr, uint64_t size); void MarkRegionAsGpuModified(uint64_t vaddr, uint64_t size);
void UnmarkRegionAsGpuModified(uint64_t vaddr, uint64_t size); void UnmarkRegionAsGpuModified(uint64_t vaddr, uint64_t size);
void UntrackMemory(uint64_t vaddr, uint64_t size); void UntrackMemory(uint64_t vaddr, uint64_t size);
void UnmapMemory(uint64_t vaddr, uint64_t size); template <typename Flush>
[[nodiscard]] CpuFaultAction void InvalidateRegion(uint64_t vaddr, uint64_t size, Flush&& on_flush) {
BeginCpuFault(uint64_t vaddr, uint64_t size, static_assert(std::is_invocable_v<Flush&>);
PageFaultAccess access = PageFaultAccess::Write) noexcept; CheckNotInUploadCallback();
[[nodiscard]] bool CompleteCpuFault(uint64_t vaddr, uint64_t size, PageFaultAccess access, ValidateRange(vaddr, size);
bool downloaded) noexcept;
[[nodiscard]] bool InvalidateRegion(uint64_t vaddr, uint64_t size, const auto update_cpu_state = [this, vaddr, size] {
PageFaultPhase phase) noexcept; std::lock_guard access(m_access_mutex);
[[nodiscard]] bool InvalidateVirtualGpuWrite(PageFaultAccess access, uint64_t vaddr, std::vector<RegionManager*> managers;
uint64_t size, PageFaultPhase phase) noexcept; 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");
}
}
void ValidateGpuDirtyPages(const RangeSet& dirty, uint64_t vaddr, uint64_t size,
const char* operation) const noexcept;
void ValidateGpuDirtyOwnership(const RangeSet& dirty, uint64_t vaddr, uint64_t size,
const char* operation);
template <bool clear, typename Preflight, typename Func> template <bool clear, typename Preflight, typename Func>
void ForEachDownloadRange(uint64_t vaddr, uint64_t size, Preflight&& preflight, Func&& func) { void ForEachDownloadRange(uint64_t vaddr, uint64_t size, Preflight&& preflight, Func&& func) {
static_assert(std::is_nothrow_invocable_v<Preflight&, uint64_t, uint64_t>); static_assert(std::is_nothrow_invocable_v<Preflight&, uint64_t, uint64_t>);
static_assert(std::is_nothrow_invocable_v<Func&, uint64_t, uint64_t>); static_assert(std::is_nothrow_invocable_v<Func&, uint64_t, uint64_t>);
CheckNotInUploadCallback(); CheckNotInUploadCallback();
std::lock_guard access(m_access_mutex); std::lock_guard access(m_access_mutex);
RequireMapped(vaddr, size);
std::vector<RegionManager*> managers; std::vector<RegionManager*> managers;
Iterate<false>(vaddr, size, [&](RegionManager* manager, uint64_t, uint64_t) { Iterate<false>(vaddr, size, [&](RegionManager* manager, uint64_t, uint64_t) {
managers.push_back(manager); managers.push_back(manager);
@@ -57,9 +93,6 @@ public:
} }
Iterate<false>(vaddr, size, [&](RegionManager* manager, uint64_t offset, uint64_t bytes) { Iterate<false>(vaddr, size, [&](RegionManager* manager, uint64_t offset, uint64_t bytes) {
const auto address = manager->GetCpuAddr() + offset; const auto address = manager->GetCpuAddr() + offset;
if (manager->HasPendingFault(address, bytes)) {
EXIT("GPU download synchronization raced a pending CPU fault\n");
}
manager->template ForEachModifiedRange<DirtySource::Gpu, false>(address, bytes, manager->template ForEachModifiedRange<DirtySource::Gpu, false>(address, bytes,
preflight); preflight);
}); });
@@ -85,11 +118,6 @@ public:
vaddr, size, [](uint64_t, uint64_t) noexcept {}, std::forward<Func>(func)); vaddr, size, [](uint64_t, uint64_t) noexcept {}, std::forward<Func>(func));
} }
#if defined(KYTY_MEMORY_TRACKER_TESTS)
using UnmapContentionHook = void (*)() noexcept;
static void SetUnmapContentionHook(UnmapContentionHook hook) noexcept;
#endif
template <typename RangeFunc, typename UploadFunc> template <typename RangeFunc, typename UploadFunc>
void ForEachUploadRange(uint64_t vaddr, uint64_t size, bool is_written, RangeFunc&& range_func, void ForEachUploadRange(uint64_t vaddr, uint64_t size, bool is_written, RangeFunc&& range_func,
UploadFunc&& upload_func) { UploadFunc&& upload_func) {
@@ -97,12 +125,10 @@ public:
static_assert(std::is_nothrow_invocable_v<UploadFunc&>); static_assert(std::is_nothrow_invocable_v<UploadFunc&>);
CheckNotInUploadCallback(); CheckNotInUploadCallback();
std::unique_lock access(m_access_mutex); std::unique_lock access(m_access_mutex);
RequireMapped(vaddr, size);
Iterate<true>(vaddr, size, [](RegionManager*, uint64_t, uint64_t) {}); Iterate<true>(vaddr, size, [](RegionManager*, uint64_t, uint64_t) {});
s_upload_owner = this; const auto* previous_upload_owner = std::exchange(s_upload_owner, this);
Iterate<false>(vaddr, size, [&](RegionManager* manager, uint64_t offset, uint64_t bytes) { Iterate<false>(vaddr, size, [&](RegionManager* manager, uint64_t offset, uint64_t bytes) {
manager->lock.lock(); manager->lock.lock();
manager->Track(manager->GetCpuAddr() + offset, bytes);
manager->ForEachModifiedRange<DirtySource::Cpu, true>(manager->GetCpuAddr() + offset, manager->ForEachModifiedRange<DirtySource::Cpu, true>(manager->GetCpuAddr() + offset,
bytes, range_func); bytes, range_func);
if (!is_written) { if (!is_written) {
@@ -119,15 +145,15 @@ public:
manager->lock.unlock(); manager->lock.unlock();
}); });
} }
s_upload_owner = nullptr; s_upload_owner = previous_upload_owner;
} }
private: private:
static constexpr size_t REGION_COUNT = TRACKER_ADDRESS_SIZE / TRACKER_REGION_SIZE; static constexpr size_t REGION_COUNT = TRACKER_ADDRESS_SIZE / TRACKER_REGION_SIZE;
inline static thread_local const MemoryTracker* s_upload_owner = nullptr; inline static thread_local const MemoryTracker* s_upload_owner = nullptr;
static void CheckNotInUploadCallback() noexcept { void CheckNotInUploadCallback() const noexcept {
if (s_upload_owner != nullptr) { if (s_upload_owner == this) {
EXIT("memory tracker re-entered from upload callback\n"); EXIT("memory tracker re-entered from upload callback\n");
} }
} }
@@ -162,14 +188,8 @@ private:
return false; return false;
} }
static void ValidateRange(uint64_t vaddr, uint64_t size); static void ValidateRange(uint64_t vaddr, uint64_t size);
void UntrackMemoryLocked(uint64_t vaddr, uint64_t size); void UntrackMemoryLocked(uint64_t vaddr, uint64_t size);
void RequireMapped(uint64_t vaddr, uint64_t size) const {
ValidateRange(vaddr, size);
if (!m_page_manager.IsMapped(vaddr, size)) {
EXIT("memory tracker range is not mapped\n");
}
}
RegionManager* GetOrCreateRegion(uint64_t index); RegionManager* GetOrCreateRegion(uint64_t index);
std::unique_ptr<std::atomic<RegionManager*>[]> m_regions; std::unique_ptr<std::atomic<RegionManager*>[]> m_regions;
-355
View File
@@ -1,355 +0,0 @@
#include "graphics/host_gpu/objects/label.h"
#include "common/assert.h"
#include "common/threads.h"
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include <algorithm>
#include <memory>
#include <vector>
namespace Libs::Graphics {
enum LabelStatus {
New,
Active,
ActiveDeleted,
NotActive,
};
struct LabelCallbacks {
LabelCallback callback_1 = nullptr;
LabelCallback callback_2 = nullptr;
uint64_t args[LABEL_ARGS_MAX] = {};
};
struct LabelEvent final {
vk::Device device = nullptr;
vk::Event event = nullptr;
~LabelEvent() {
if (event != nullptr) {
device.destroyEvent(event, nullptr);
}
}
};
struct LabelSubmission {
std::shared_ptr<LabelEvent> completion;
LabelCallbacks callbacks;
};
struct Label {
vk::Device device = nullptr;
LabelStatus status = LabelStatus::New;
LabelCallbacks callbacks;
std::vector<LabelSubmission> submissions;
};
class LabelManager {
public:
LabelManager() {
EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread());
Common::Thread t(ThreadRun, this);
t.Detach();
}
~LabelManager() { KYTY_NOT_IMPLEMENTED; }
KYTY_CLASS_NO_COPY(LabelManager);
Label* Create(GraphicContext* ctx, LabelCallback callback_1, LabelCallback callback_2,
const uint64_t* args);
void Delete(Label* label);
void Set(CommandBuffer* buffer, Label* label);
void Drain();
private:
static void ThreadRun(void* data);
bool Remove(Label* label);
static void Destroy(Label* label);
Common::Mutex m_mutex;
Common::CondVar m_cond_var;
std::vector<Label*> m_labels;
uint64_t m_callbacks_in_flight = 0;
};
static LabelManager* g_label_manager = nullptr;
static thread_local bool g_in_label_callback = false;
class LabelCallbackScope final {
public:
LabelCallbackScope() {
if (g_in_label_callback) {
EXIT("recursive GPU label callback\n");
}
g_in_label_callback = true;
}
~LabelCallbackScope() {
if (!g_in_label_callback) {
EXIT("GPU label callback scope is not active\n");
}
g_in_label_callback = false;
}
};
void LabelManager::ThreadRun(void* data) {
auto* manager = static_cast<LabelManager*>(data);
for (;;) {
manager->m_mutex.Lock();
uint64_t active_count = 0;
std::vector<Label*> deleted_labels;
std::vector<LabelCallbacks> fired_labels;
std::vector<LabelSubmission> finished_submissions;
deleted_labels.reserve(manager->m_labels.size());
for (auto& label: manager->m_labels) {
for (auto it = label->submissions.begin(); it != label->submissions.end();) {
active_count++;
if (it->completion == nullptr || it->completion->device == nullptr ||
it->completion->event == nullptr) {
EXIT("GPU label submission has no completion event\n");
}
const auto status = it->completion->device.getEventStatus(it->completion->event);
switch (status) {
case vk::Result::eEventSet:
fired_labels.push_back(it->callbacks);
finished_submissions.push_back(*it);
it = label->submissions.erase(it);
break;
case vk::Result::eEventReset: ++it; break;
default: EXIT("vkGetEventStatus returned an unexpected result\n");
}
}
if (label->submissions.empty()) {
switch (label->status) {
case LabelStatus::ActiveDeleted: deleted_labels.push_back(label); break;
case LabelStatus::Active: label->status = LabelStatus::NotActive; break;
default: break;
}
}
}
if (active_count == 0) {
manager->m_cond_var.Wait(&manager->m_mutex);
}
if (fired_labels.size() > UINT64_MAX - manager->m_callbacks_in_flight) {
EXIT("GPU label callback count overflow\n");
}
manager->m_callbacks_in_flight += fired_labels.size();
for (auto& label: deleted_labels) {
bool removed = manager->Remove(label);
EXIT_NOT_IMPLEMENTED(!removed);
}
manager->m_mutex.Unlock();
// Each completion event is shared with the recording command buffer's fence retainer.
// The event is destroyed only after both the label thread observed it and that command
// buffer completed, even when either side wins the race.
(void)finished_submissions;
for (auto& label: deleted_labels) {
Destroy(label);
}
for (auto& label: fired_labels) {
LabelCallbackScope callback_scope;
if (label.callback_1 != nullptr) {
(void)label.callback_1(label.args);
}
if (label.callback_2 != nullptr) {
label.callback_2(label.args);
}
}
if (!fired_labels.empty()) {
Common::LockGuard lock(manager->m_mutex);
if (manager->m_callbacks_in_flight < fired_labels.size()) {
EXIT("GPU label callback count underflow\n");
}
manager->m_callbacks_in_flight -= fired_labels.size();
manager->m_cond_var.SignalAll();
}
Common::Thread::SleepMicro(100);
}
}
Label* LabelManager::Create(GraphicContext* ctx, LabelCallback callback_1, LabelCallback callback_2,
const uint64_t* args) {
EXIT_IF(ctx == nullptr);
Common::LockGuard lock(m_mutex);
auto* label = new Label;
label->status = LabelStatus::New;
label->device = ctx->device;
label->callbacks.callback_1 = callback_1;
label->callbacks.callback_2 = callback_2;
if (args != nullptr) {
for (int i = 0; i < LABEL_ARGS_MAX; i++) {
label->callbacks.args[i] = args[i];
}
}
m_labels.push_back(label);
return label;
}
bool LabelManager::Remove(Label* label) {
EXIT_IF(label == nullptr);
EXIT_IF(label->device == nullptr);
Common::LockGuard lock(m_mutex);
const auto it = std::find(m_labels.begin(), m_labels.end(), label);
EXIT_NOT_IMPLEMENTED(it == m_labels.end());
EXIT_NOT_IMPLEMENTED(label->status != LabelStatus::NotActive &&
label->status != LabelStatus::Active &&
label->status != LabelStatus::ActiveDeleted);
if (!label->submissions.empty()) {
label->status = LabelStatus::ActiveDeleted;
return false;
}
m_labels.erase(it);
return true;
}
void LabelManager::Destroy(Label* label) {
EXIT_IF(label == nullptr);
EXIT_IF(label->device == nullptr);
EXIT_NOT_IMPLEMENTED(!label->submissions.empty());
delete label;
}
void LabelManager::Delete(Label* label) {
if (Remove(label)) {
Destroy(label);
}
}
void LabelManager::Set(CommandBuffer* buffer, Label* label) {
EXIT_IF(label == nullptr);
EXIT_IF(buffer == nullptr);
EXIT_IF(buffer->IsInvalid());
EXIT_IF(label->device == nullptr);
Common::LockGuard lock(m_mutex);
const auto it = std::find(m_labels.begin(), m_labels.end(), label);
EXIT_NOT_IMPLEMENTED(it == m_labels.end());
EXIT_NOT_IMPLEMENTED(label->status != LabelStatus::New &&
label->status != LabelStatus::NotActive &&
label->status != LabelStatus::Active);
label->status = LabelStatus::Active;
LabelSubmission submission {};
submission.callbacks = label->callbacks;
submission.completion = std::make_shared<LabelEvent>();
submission.completion->device = label->device;
auto vk_buffer = buffer->Handle();
EXIT_NOT_IMPLEMENTED(vk_buffer == nullptr);
vk::EventCreateInfo create_info {};
create_info.sType = vk::StructureType::eEventCreateInfo;
create_info.pNext = nullptr;
create_info.flags = {};
const auto create_result =
label->device.createEvent(&create_info, nullptr, &submission.completion->event);
if (create_result != vk::Result::eSuccess || submission.completion->event == nullptr) {
EXIT("failed to create label event: %s (%d)\n", VulkanToString(create_result).c_str(),
static_cast<int>(create_result));
}
buffer->RetainResourceUntilFence(submission.completion);
// Labels can be reused before an earlier end-of-pipe event has been
// observed by the polling thread. Capture a separate Vulkan event and
// callback snapshot for each set so older writes are not lost.
const auto reset_result = label->device.resetEvent(submission.completion->event);
if (reset_result != vk::Result::eSuccess) {
EXIT("failed to reset label event: %s (%d)\n", VulkanToString(reset_result).c_str(),
static_cast<int>(reset_result));
}
vk_buffer.setEvent(submission.completion->event, vk::PipelineStageFlagBits::eBottomOfPipe);
label->submissions.push_back(submission);
m_cond_var.SignalAll();
}
void LabelManager::Drain() {
m_mutex.Lock();
for (;;) {
const bool pending = std::any_of(m_labels.begin(), m_labels.end(), [](const Label* label) {
return !label->submissions.empty();
});
if (!pending && m_callbacks_in_flight == 0) {
m_mutex.Unlock();
return;
}
m_cond_var.SignalAll();
m_cond_var.Wait(&m_mutex);
}
}
void LabelInit() {
EXIT_IF(g_label_manager != nullptr);
g_label_manager = new LabelManager;
}
Label* LabelCreate(GraphicContext* ctx, LabelCallback callback_1, LabelCallback callback_2,
const uint64_t* args) {
EXIT_IF(g_label_manager == nullptr);
return g_label_manager->Create(ctx, callback_1, callback_2, args);
}
void LabelDelete(Label* label) {
EXIT_IF(g_label_manager == nullptr);
g_label_manager->Delete(label);
}
void LabelSet(CommandBuffer* buffer, Label* label) {
EXIT_IF(g_label_manager == nullptr);
g_label_manager->Set(buffer, label);
}
void LabelDrain() {
EXIT_IF(g_label_manager == nullptr);
g_label_manager->Drain();
}
bool LabelInCallback() noexcept {
return g_in_label_callback;
}
} // namespace Libs::Graphics
-27
View File
@@ -1,27 +0,0 @@
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_OBJECTS_LABEL_H_
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_OBJECTS_LABEL_H_
#include "common/abi.h"
#include "common/common.h"
namespace Libs::Graphics {
struct Label;
class CommandBuffer;
struct GraphicContext;
void LabelInit();
constexpr int LABEL_ARGS_MAX = 5;
using LabelCallback = bool (*)(const uint64_t* args);
Label* LabelCreate(GraphicContext* ctx, LabelCallback callback_1, LabelCallback callback_2,
const uint64_t* args);
void LabelDelete(Label* label);
void LabelSet(CommandBuffer* buffer, Label* label);
void LabelDrain();
[[nodiscard]] bool LabelInCallback() noexcept;
} // namespace Libs::Graphics
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_OBJECTS_LABEL_H_ */
File diff suppressed because it is too large Load Diff
@@ -1,141 +0,0 @@
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_OBJECTS_TEXTURECOMMON_H_
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_OBJECTS_TEXTURECOMMON_H_
#include "common/abi.h"
#include "common/common.h"
#include "graphics/guest_gpu/gpu_defs.h"
#include "graphics/guest_gpu/tile.h"
#include "graphics/host_gpu/transfer.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include <vector>
namespace Libs::Graphics {
struct GraphicContext;
struct VulkanImage;
enum class TextureFormatUsage : uint32_t {
None = 0,
Sampled = 1u << 0u,
Storage = 1u << 1u,
};
constexpr TextureFormatUsage operator|(TextureFormatUsage lhs, TextureFormatUsage rhs) {
return static_cast<TextureFormatUsage>(static_cast<uint32_t>(lhs) | static_cast<uint32_t>(rhs));
}
constexpr TextureFormatUsage operator&(TextureFormatUsage lhs, TextureFormatUsage rhs) {
return static_cast<TextureFormatUsage>(static_cast<uint32_t>(lhs) & static_cast<uint32_t>(rhs));
}
constexpr TextureFormatUsage operator~(TextureFormatUsage usage) {
return static_cast<TextureFormatUsage>(~static_cast<uint32_t>(usage));
}
constexpr TextureFormatUsage& operator|=(TextureFormatUsage& lhs, TextureFormatUsage rhs) {
lhs = lhs | rhs;
return lhs;
}
constexpr bool TextureHasFormatUsage(TextureFormatUsage usage, TextureFormatUsage flag) {
return (static_cast<uint32_t>(usage & flag) == static_cast<uint32_t>(flag));
}
enum class TextureUploadDestination { MipLevels, MipAtlas };
enum class TextureUploadSliceLayout { MipChainPerSlice, MipLevelPerSlice };
struct RenderTargetFormatInfo {
vk::Format format = vk::Format::eUndefined;
uint32_t bytes_per_element = 0;
Prospero::ColorComponentMapping export_mapping;
};
struct TextureUploadLayout {
uint32_t tile = 0;
uint32_t pitch = 0;
uint64_t slice_stride = 0;
uint64_t source_slice_stride = 0;
bool fmt_tiled_render_target = false;
bool fmt_tiled_standard256b = false;
bool fmt_tiled_standard4kb = false;
bool fmt_tiled_standard64kb = false;
bool fmt_tiled_depth = false;
bool volume_texture = false;
TileSizeOffset level_sizes[16] = {};
TilePaddedSize padded_sizes[16] = {};
};
struct TextureImageCreateParams {
uint32_t fmt = 0;
uint64_t width = 0;
uint64_t height = 0;
uint32_t base_level = 0;
uint64_t levels = 1;
uint32_t depth = 1;
uint64_t type = 0;
uint64_t swizzle = 0;
TextureFormatUsage format_usage = TextureFormatUsage::Sampled;
TextureFormatUsage required_format_usage = TextureFormatUsage::Sampled;
TextureFormatUsage view_usage = TextureFormatUsage::Sampled;
TextureUploadDestination image_layout = TextureUploadDestination::MipLevels;
bool allow_cube_view = false;
bool compatible_format_views = false;
bool storage_swizzle_fallback = false;
const char* owner = nullptr;
};
vk::ComponentSwizzle TextureGetComponentSwizzle(uint8_t s);
vk::ComponentMapping TextureGetComponentMapping(uint32_t swizzle);
bool TextureCheckFormat(GraphicContext* ctx, vk::ImageCreateInfo* image_info);
bool TextureCheckStorageSwizzle(vk::ImageCreateInfo* image_info, vk::ComponentMapping* components);
vk::ImageUsageFlags TextureGetUsage(TextureFormatUsage usage);
vk::ImageUsageFlags TextureGetViewUsage(TextureFormatUsage usage);
vk::Format TextureGetFormat(uint32_t fmt);
RenderTargetFormatInfo TextureGetRenderTargetFormat(uint32_t layout, uint32_t type, uint32_t order);
uint32_t TextureGetAtlasSliceYStride(vk::Format format, uint32_t mip_height, uint32_t depth,
uint64_t levels);
uint32_t TextureCalcStackedImageHeight(vk::Format format, uint32_t height, uint32_t depth,
uint64_t levels);
uint32_t TextureCalcMipmapAtlasImageHeight(vk::Format format, uint32_t width, uint32_t height,
uint32_t depth, uint64_t levels);
bool TextureIs3DTexture(uint64_t type);
bool TextureIsCubeTexture(uint64_t type);
bool TextureIsLayeredTexture(uint64_t type);
bool TextureCanCreateCubeView(uint64_t type, uint32_t base_array, uint32_t layer_count);
vk::ComponentMapping TextureCreateImage(GraphicContext* ctx, VulkanImage* image,
const TextureImageCreateParams& params);
void TextureCreateImageViews(GraphicContext* ctx, VulkanImage* vk_obj,
vk::ComponentMapping components, uint64_t type, uint32_t base_array,
uint32_t base_level, uint32_t level_count, uint32_t depth,
bool allow_cube_view, TextureFormatUsage view_usage);
TextureUploadLayout TextureCalcUploadLayout(uint32_t fmt, uint64_t width, uint64_t height,
uint64_t levels, uint32_t depth, uint64_t pitch,
uint64_t tile, uint64_t upload_size,
bool allow_depth_tile,
bool require_single_mip_small_tiles,
bool volume_texture, const char* owner);
uint64_t TextureUploadSliceSourceOffset(const TextureUploadLayout& layout, uint32_t level,
uint32_t slice,
TextureUploadSliceLayout source_slice_layout);
uint64_t TextureCalcUploadSize(const TextureUploadLayout& layout,
const std::vector<BufferImageCopy>& regions, uint64_t levels,
uint32_t depth, TextureUploadSliceLayout source_slice_layout);
std::vector<BufferImageCopy> TextureBuildUploadRegions(
const TextureUploadLayout& layout, vk::Format image_format, uint32_t width, uint32_t height,
uint32_t depth, uint64_t levels, bool array_texture, bool volume_texture,
TextureUploadDestination destination, TextureUploadSliceLayout slice_layout);
void TextureCopyBufferBytes(GraphicContext* ctx, VulkanBuffer* src_buffer,
uint64_t src_buffer_offset, uint64_t copy_size,
Transfer::ScratchBuffer* dst);
void TextureUploadGuestImage(GraphicContext* ctx, VulkanImage* vk_obj, const void* src_data,
uint64_t size, const std::vector<BufferImageCopy>& regions,
const TextureUploadLayout& layout, uint32_t fmt, uint64_t width,
uint64_t height, uint32_t depth, uint64_t levels,
TextureUploadSliceLayout source_slice_layout, const char* owner,
vk::ImageLayout dst_layout);
} // namespace Libs::Graphics
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_OBJECTS_TEXTURECOMMON_H_ */
+261 -384
View File
@@ -1,7 +1,9 @@
#include "graphics/host_gpu/pageManager.h" #include "graphics/host_gpu/pageManager.h"
#include "graphics/host_gpu/regionDefinitions.h" #include "graphics/host_gpu/regionDefinitions.h"
#include "kernel/memory.h"
#include <algorithm>
#include <array> #include <array>
#include <atomic> #include <atomic>
#include <cstdarg> #include <cstdarg>
@@ -19,6 +21,15 @@
#include <windows.h> #include <windows.h>
#undef min #undef min
#undef max #undef max
#elif defined(__APPLE__)
#include <pthread.h>
#include <sys/mman.h>
#include <unistd.h>
#else
#include <execinfo.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <unistd.h>
#endif #endif
namespace Libs::Graphics { namespace Libs::Graphics {
@@ -28,9 +39,22 @@ 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;
thread_local bool g_in_fault_resolution = false; constexpr uint32_t NO_ACCESS_PROTECTION = PAGE_NOACCESS;
constexpr uint32_t READ_ONLY_PROTECTION = PAGE_READONLY;
constexpr uint32_t READ_WRITE_PROTECTION = PAGE_READWRITE;
// Zero is the unknown protection sentinel.
constexpr uint32_t UNKNOWN_PROTECTION = 0;
[[noreturn]] void FailFast(const char* reason = nullptr) noexcept { [[noreturn]] void FailFast(const char* reason = nullptr) noexcept {
std::fputs("PageManager fail-fast: ", stderr); std::fputs("PageManager fail-fast: ", stderr);
@@ -46,6 +70,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
@@ -65,11 +93,31 @@ thread_local bool g_in_fault_resolution = false;
std::_Exit(322); std::_Exit(322);
} }
Common::VirtualMemory::Mode ToMemoryMode(uint32_t protection) {
switch (protection) {
case NO_ACCESS_PROTECTION: return Common::VirtualMemory::Mode::NoAccess;
case READ_ONLY_PROTECTION: return Common::VirtualMemory::Mode::Read;
case READ_WRITE_PROTECTION: return Common::VirtualMemory::Mode::ReadWrite;
default: Fatal("unmappable protection 0x%08" PRIx32, protection);
}
}
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
} }
@@ -106,28 +154,41 @@ 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 write_watchers = 0;
uint32_t gpu_read_mappings = 0; uint32_t access_watchers = 0;
uint32_t gpu_write_mappings = 0; uint32_t original_protection = 0;
uint32_t write_watchers = 0; uint32_t backing_writer = 0;
uint32_t access_watchers = 0; // Shadow the protection applied through Protect().
uint32_t original_protection = 0; uint32_t current_protection = UNKNOWN_PROTECTION;
uint32_t backing_writer = 0; bool resolving = false;
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;
}; };
Impl(PageFaultHandler handler, void* context): fault_handler(handler), fault_context(context) { class PageRangeGuard final {
if (fault_handler == nullptr) { public:
Fatal("null fault handler"); 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() {
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
SYSTEM_INFO info {}; SYSTEM_INFO info {};
GetSystemInfo(&info); GetSystemInfo(&info);
@@ -135,8 +196,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++) {
@@ -148,9 +217,8 @@ struct PageManager::Impl {
for (const auto& region: region_storage) { for (const auto& region: region_storage) {
for (auto& page: region->pages) { for (auto& page: region->pages) {
SpinGuard lock(page.lock); SpinGuard lock(page.lock);
if (page.mappings != 0 || page.gpu_read_mappings != 0 || if (page.write_watchers != 0 || page.access_watchers != 0 ||
page.gpu_write_mappings != 0 || page.write_watchers != 0 || page.backing_writer != 0 || page.resolving) {
page.access_watchers != 0 || page.backing_writer != 0 || page.resolving) {
FailFast("PageManager destroyed with live page state"); FailFast("PageManager destroyed with live page state");
} }
} }
@@ -184,291 +252,189 @@ struct PageManager::Impl {
static uint32_t WatcherProtection(const PageState& page) { static uint32_t WatcherProtection(const PageState& page) {
if (page.access_watchers != 0) { if (page.access_watchers != 0) {
return PAGE_NOACCESS; return NO_ACCESS_PROTECTION;
} }
if (page.write_watchers != 0) { if (page.write_watchers != 0) {
return PAGE_READONLY; return READ_ONLY_PROTECTION;
} }
return page.original_protection; return page.original_protection;
} }
static void PublishDelayedFaults(PageState& page, uint32_t old_protection, static void InitializeProtection(std::span<PageState*> pages) {
uint32_t new_protection) { for (auto* page: pages) {
if (old_protection == PAGE_NOACCESS && new_protection != PAGE_NOACCESS) { page->original_protection = READ_WRITE_PROTECTION;
page.late_read_pending = true; page->current_protection = READ_WRITE_PROTECTION;
}
if ((old_protection == PAGE_NOACCESS || old_protection == PAGE_READONLY) &&
new_protection == PAGE_READWRITE) {
page.late_write_pending = true;
} }
} }
static uint32_t QueryProtection(uint64_t vaddr) { void ProtectRange(std::span<PageState*> pages, uint64_t vaddr, uint32_t protection,
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS std::span<const uint32_t> expected_old) noexcept {
MEMORY_BASIC_INFORMATION info {}; const auto size = pages.size() * PAGE_SIZE;
if (VirtualQuery(reinterpret_cast<const void*>(static_cast<uintptr_t>(vaddr)), &info, if (pages.size() != expected_old.size()) {
sizeof(info)) == 0 || FailFast("protection range state size mismatch");
info.State != MEM_COMMIT || info.Protect != PAGE_READWRITE) {
Fatal("basic path requires PAGE_READWRITE at 0x%016" PRIx64 " (state=0x%08" PRIx32
", protection=0x%08" PRIx32 ")",
vaddr, static_cast<uint32_t>(info.State), static_cast<uint32_t>(info.Protect));
} }
return info.Protect; for (size_t i = 0; i < pages.size(); i++) {
#else const auto actual = pages[i]->current_protection;
(void)vaddr; if (actual != UNKNOWN_PROTECTION && actual != expected_old[i]) {
Fatal("page query is unsupported on this platform"); Fatal("invalid protection transition at 0x%016" PRIx64 ", old=0x%08" PRIx32
#endif ", expected=0x%08" PRIx32 ", new=0x%08" PRIx32,
} vaddr + i * PAGE_SIZE, actual, expected_old[i], protection);
static bool AllowsAccess(uint64_t vaddr, PageFaultAccess access) noexcept {
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
MEMORY_BASIC_INFORMATION info {};
if (VirtualQuery(reinterpret_cast<const void*>(static_cast<uintptr_t>(vaddr)), &info,
sizeof(info)) == 0 ||
info.State != MEM_COMMIT) {
return false;
}
switch (access) {
case PageFaultAccess::Read:
return info.Protect == PAGE_READONLY || info.Protect == PAGE_READWRITE;
case PageFaultAccess::Write: return info.Protect == PAGE_READWRITE;
default: return false;
}
#else
(void)vaddr;
return false;
#endif
}
static void Protect(uint64_t vaddr, uint32_t protection, uint32_t expected_old,
bool fault_path) noexcept {
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
DWORD old_protection = 0;
if (VirtualProtect(reinterpret_cast<void*>(static_cast<uintptr_t>(vaddr)), PAGE_SIZE,
protection, &old_protection) == 0 ||
old_protection != expected_old) {
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,
vaddr, static_cast<uint32_t>(old_protection), expected_old, protection);
} }
#else if (!Libs::LibKernel::Memory::ProtectGuestHostMemory(vaddr, size,
(void)vaddr; ToMemoryMode(protection))) {
(void)protection; Fatal("address-space protection failed at 0x%016" PRIx64 ", new=0x%08" PRIx32, vaddr,
(void)fault_path; protection);
FailFast("page protection is unsupported on this platform"); }
#endif for (auto* page: pages) {
page->current_protection = protection;
}
}
void Protect(PageState& page, uint64_t vaddr, uint32_t protection,
uint32_t expected_old) noexcept {
PageState* pages[] = {&page};
uint32_t expected[] = {expected_old};
ProtectRange(pages, vaddr, protection, expected);
} }
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;
PageFaultHandler fault_handler = nullptr;
void* fault_context = nullptr;
}; };
static_assert(std::atomic<void*>::is_always_lock_free); static_assert(std::atomic<void*>::is_always_lock_free);
PageManager::PageManager(PageFaultHandler fault_handler, void* fault_context) PageManager::PageManager(): m_impl(std::make_unique<Impl>()) {}
: m_impl(std::make_unique<Impl>(fault_handler, fault_context)) {}
PageManager::~PageManager() = default; PageManager::~PageManager() = default;
uint64_t PageManager::GetPageSize() const { uint64_t PageManager::GetPageSize() const {
if (g_in_fault_resolution) {
FailFast("nested page fault while resolving a watched page");
}
return PAGE_SIZE; return PAGE_SIZE;
} }
bool PageManager::IsTracked(uint64_t vaddr) const noexcept {
if (g_in_fault_resolution) {
FailFast("IsTracked called during fault resolution");
}
auto* region = m_impl->FindRegion(vaddr);
if (region == nullptr) {
return false;
}
auto& page = m_impl->GetPage(*region, vaddr);
SpinGuard lock(page.lock);
return page.write_watchers != 0 || page.access_watchers != 0;
}
bool PageManager::IsMapped(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 = PageStart(vaddr + size - 1) + PAGE_SIZE;
for (auto page_vaddr = PageStart(vaddr); page_vaddr < end; page_vaddr += PAGE_SIZE) {
auto* region = m_impl->FindRegion(page_vaddr);
if (region == nullptr) {
return false;
}
auto& page = m_impl->GetPage(*region, page_vaddr);
SpinGuard lock(page.lock);
if (page.mappings == 0) {
return false;
}
}
return true;
}
bool PageManager::HasGpuAccess(uint64_t vaddr, uint64_t size, GpuAccess access) const noexcept {
if (access != GpuAccess::Read && access != GpuAccess::Write && access != GpuAccess::ReadWrite) {
FailFast("HasGpuAccess received an invalid GPU access mode");
}
const bool need_read = access == GpuAccess::Read || access == GpuAccess::ReadWrite;
const bool need_write = access == GpuAccess::Write || access == GpuAccess::ReadWrite;
if (vaddr == 0 || size == 0 || vaddr >= ADDRESS_SIZE || size > ADDRESS_SIZE - vaddr) {
return false;
}
const auto end = PageEnd(vaddr, size);
for (auto addr = PageStart(vaddr); addr < end; addr += PAGE_SIZE) {
auto* region = m_impl->FindRegion(addr);
if (region == nullptr) {
return false;
}
auto& page = m_impl->GetPage(*region, addr);
SpinGuard lock(page.lock);
if ((need_read && page.gpu_read_mappings == 0) ||
(need_write && page.gpu_write_mappings == 0)) {
return false;
}
}
return true;
}
void PageManager::UpdatePageWatchers(bool track, uint64_t vaddr, uint64_t size, void PageManager::UpdatePageWatchers(bool track, uint64_t vaddr, uint64_t size,
PageWatchMode mode) { PageWatchMode mode) {
if (g_in_fault_resolution) {
FailFast("page watchers changed during fault resolution");
}
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");
}
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++;
}
if (first != last) {
Impl::InitializeProtection(std::span {pages}.subspan(first, last - first));
}
first = last;
} }
}
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); const auto old_protection = Impl::WatcherProtection(page);
watchers++; if (track) {
const auto new_protection = Impl::WatcherProtection(page); watchers++;
if (new_protection != old_protection) { } else {
Impl::Protect(page_vaddr, new_protection, old_protection, false); watchers--;
} }
switch (new_protection) { const auto new_protection = Impl::WatcherProtection(page);
case PAGE_NOACCESS: old_protections[i] = old_protection;
page.late_read_pending = false; new_protections[i] = new_protection;
page.late_write_pending = false; 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; break;
case PAGE_READONLY: page.late_write_pending = false; break; }
default: break; if (transitions[current] != 0) {
last = current + 1;
}
} }
} else { m_impl->ProtectRange(std::span {pages}.subspan(first, last - first),
if (watchers == 0) { chunk_begin + first * PAGE_SIZE, protection,
Fatal("watcher underflow at 0x%016" PRIx64, page_vaddr); std::span {old_protections}.subspan(first, last - first));
} first = current;
if (page.backing_writer != 0 && page.backing_writer != CurrentThread()) { }
Fatal("backing write ownership changed at 0x%016" PRIx64, page_vaddr);
} for (auto* page: pages) {
const auto old_protection = Impl::WatcherProtection(page); if (!track && page->backing_writer == 0 && page->write_watchers == 0 &&
watchers--; page->access_watchers == 0) {
const auto new_protection = Impl::WatcherProtection(page); page->original_protection = 0;
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;
} }
} }
chunk_begin = chunk_end;
} }
} }
void PageManager::OnGpuMap(uint64_t vaddr, uint64_t size, GpuAccess access) { void PageManager::OnGpuMap(uint64_t, uint64_t) {}
if (g_in_fault_resolution) {
FailFast("GPU mapping changed during fault resolution");
}
if (access != GpuAccess::Read && access != GpuAccess::Write && access != GpuAccess::ReadWrite) {
FailFast("GPU map received an invalid access mode");
}
const bool gpu_read = access == GpuAccess::Read || access == GpuAccess::ReadWrite;
const bool gpu_write = access == GpuAccess::Write || access == GpuAccess::ReadWrite;
const auto end = PageEnd(vaddr, size);
for (auto addr = PageStart(vaddr); addr < end; addr += PAGE_SIZE) {
auto& page = m_impl->GetPage(*m_impl->GetOrCreateRegion(addr), addr);
SpinGuard lock(page.lock);
if (page.resolving || page.mappings == std::numeric_limits<uint32_t>::max() ||
(gpu_read && page.gpu_read_mappings == std::numeric_limits<uint32_t>::max()) ||
(gpu_write && page.gpu_write_mappings == std::numeric_limits<uint32_t>::max())) {
Fatal("invalid map state at 0x%016" PRIx64, addr);
}
page.mappings++;
page.gpu_read_mappings += gpu_read ? 1u : 0u;
page.gpu_write_mappings += gpu_write ? 1u : 0u;
}
}
void PageManager::OnGpuUnmap(uint64_t vaddr, uint64_t size, GpuAccess access) { void PageManager::OnGpuUnmap(uint64_t, uint64_t) {}
if (g_in_fault_resolution) {
FailFast("GPU unmapping changed during fault resolution");
}
if (access != GpuAccess::Read && access != GpuAccess::Write && access != GpuAccess::ReadWrite) {
FailFast("GPU unmap received an invalid access mode");
}
const bool gpu_read = access == GpuAccess::Read || access == GpuAccess::ReadWrite;
const bool gpu_write = access == GpuAccess::Write || access == GpuAccess::ReadWrite;
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) {
Fatal("unmapping unknown page 0x%016" PRIx64, page_vaddr);
}
auto& page = m_impl->GetPage(*region, page_vaddr);
SpinGuard lock(page.lock);
if (page.resolving || page.mappings == 0 || (gpu_read && page.gpu_read_mappings == 0) ||
(gpu_write && page.gpu_write_mappings == 0) ||
(page.mappings == 1 && (page.write_watchers != 0 || page.access_watchers != 0))) {
Fatal("invalid unmap state at 0x%016" PRIx64, page_vaddr);
}
page.mappings--;
page.gpu_read_mappings -= gpu_read ? 1u : 0u;
page.gpu_write_mappings -= gpu_write ? 1u : 0u;
if (page.mappings == 0) {
if (page.gpu_read_mappings != 0 || page.gpu_write_mappings != 0) {
FailFast("GPU unmap left nonzero GPU mapping counts");
}
page.late_read_pending = false;
page.late_write_pending = false;
}
}
}
PageManager::BackingWrite::BackingWrite(PageManager& manager, uint64_t vaddr, PageManager::BackingWrite::BackingWrite(PageManager& manager, uint64_t vaddr,
uint64_t size) noexcept uint64_t size) noexcept
@@ -480,10 +446,38 @@ PageManager::BackingWrite::~BackingWrite() {
m_manager.EndBackingWrite(m_vaddr, m_size); m_manager.EndBackingWrite(m_vaddr, m_size);
} }
void PageManager::BeginBackingWrite(uint64_t vaddr, uint64_t size) noexcept { std::vector<std::unique_ptr<PageManager::BackingWrite>>
if (g_in_fault_resolution) { PageManager::ReserveBackingWrites(std::span<const RangeSet::Range> ranges) {
FailFast("backing write began during fault resolution"); if (ranges.empty()) {
Fatal("cannot reserve empty backing-write ranges");
} }
std::vector<std::unique_ptr<BackingWrite>> writes;
writes.reserve(ranges.size());
uint64_t begin = 0;
uint64_t end = 0;
for (const auto& range: ranges) {
if (range.address == 0 || range.size == 0 || range.size > UINT64_MAX - range.address ||
range.address + range.size > UINT64_MAX - (PAGE_SIZE - 1)) {
Fatal("invalid backing-write range");
}
const auto page_begin = PageStart(range.address);
const auto page_end = PageStart(range.address + range.size + PAGE_SIZE - 1);
if (begin != 0 && page_begin > end) {
writes.push_back(std::make_unique<BackingWrite>(*this, begin, end - begin));
begin = 0;
}
if (begin == 0) {
begin = page_begin;
end = page_end;
} else {
end = std::max(end, page_end);
}
}
writes.push_back(std::make_unique<BackingWrite>(*this, begin, end - begin));
return writes;
}
void PageManager::BeginBackingWrite(uint64_t vaddr, uint64_t size) noexcept {
const auto end = PageEnd(vaddr, size); const auto end = PageEnd(vaddr, size);
const auto writer = CurrentThread(); const auto writer = CurrentThread();
for (auto address = PageStart(vaddr); address < end; address += PAGE_SIZE) { for (auto address = PageStart(vaddr); address < end; address += PAGE_SIZE) {
@@ -493,20 +487,15 @@ void PageManager::BeginBackingWrite(uint64_t vaddr, uint64_t size) noexcept {
} }
auto& page = m_impl->GetPage(*region, address); auto& page = m_impl->GetPage(*region, address);
SpinGuard lock(page.lock); SpinGuard lock(page.lock);
if (page.mappings == 0 || page.resolving || page.backing_writer != 0 || if (page.resolving || page.backing_writer != 0 || page.access_watchers == 0) {
page.access_watchers == 0) {
Fatal("backing write races page resolution at 0x%016" PRIx64, address); Fatal("backing write races page resolution at 0x%016" PRIx64, address);
} }
page.resolving = true; page.resolving = true;
page.resolving_read_write = true; page.backing_writer = writer;
page.backing_writer = writer;
} }
} }
void PageManager::EndBackingWrite(uint64_t vaddr, uint64_t size) noexcept { void PageManager::EndBackingWrite(uint64_t vaddr, uint64_t size) noexcept {
if (g_in_fault_resolution) {
FailFast("backing write ended during fault resolution");
}
const auto end = PageEnd(vaddr, size); const auto end = PageEnd(vaddr, size);
const auto writer = CurrentThread(); const auto writer = CurrentThread();
for (auto address = PageStart(vaddr); address < end; address += PAGE_SIZE) { for (auto address = PageStart(vaddr); address < end; address += PAGE_SIZE) {
@@ -519,129 +508,17 @@ void PageManager::EndBackingWrite(uint64_t vaddr, uint64_t size) noexcept {
if (!page.resolving || page.backing_writer != writer) { if (!page.resolving || page.backing_writer != writer) {
FailFast("backing write ended without matching owner and resolving state"); FailFast("backing write ended without matching owner and resolving state");
} }
const auto old_protection = PAGE_NOACCESS; 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); m_impl->Protect(page, address, new_protection, old_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) {
page.original_protection = 0; page.original_protection = 0;
} }
page.backing_writer = 0; page.backing_writer = 0;
page.resolving = false; page.resolving = false;
page.resolving_read_write = false;
} }
} }
bool PageManager::HandleFault(PageFaultAccess access, uint64_t fault_vaddr) noexcept {
if (g_in_fault_resolution) {
FailFast("nested HandleFault call");
}
auto* region = m_impl->FindRegion(fault_vaddr);
if (region == nullptr) {
return false;
}
auto& page = m_impl->GetPage(*region, fault_vaddr);
bool waited = false;
while (true) {
SpinGuard lock(page.lock);
if (access == PageFaultAccess::Read && page.late_read_pending &&
Impl::AllowsAccess(fault_vaddr, access)) {
page.late_read_pending = false;
return true;
}
if (access == PageFaultAccess::Write && page.late_write_pending &&
Impl::AllowsAccess(fault_vaddr, access)) {
page.late_write_pending = false;
return true;
}
if (page.resolving) {
if (page.backing_writer == CurrentThread()) {
FailFast("backing writer faulted on its own reserved page");
}
if ((!page.resolving_read_write && access != PageFaultAccess::Write) ||
(page.resolving_read_write && access != PageFaultAccess::Read &&
access != PageFaultAccess::Write)) {
FailFast("fault access is incompatible with the active resolver");
}
waited = true;
continue;
}
if (page.write_watchers == 0 && page.access_watchers == 0) {
if (access != PageFaultAccess::Read && access != PageFaultAccess::Write) {
return false;
}
bool& pending = (access == PageFaultAccess::Read ? page.late_read_pending
: page.late_write_pending);
const bool allowed = Impl::AllowsAccess(fault_vaddr, access);
pending = false;
if (waited && !allowed) {
FailFast("page remained inaccessible after waiting for its resolver");
}
// More than one CPU can fault before a protection transition becomes visible. The first
// delayed fault consumes the hint bit; later faults must also resume once the mapped
// page already permits the requested access. A genuinely read-only/no-access page still
// falls through to the guest exception path.
return allowed;
}
if ((access != PageFaultAccess::Read && access != PageFaultAccess::Write) ||
(access == PageFaultAccess::Read && page.access_watchers == 0)) {
FailFast("fault access is incompatible with active page watchers");
}
page.resolving = true;
page.resolving_read_write = page.access_watchers != 0;
break;
}
g_in_fault_resolution = true;
const bool handled = m_impl->fault_handler(m_impl->fault_context, access, fault_vaddr, 1,
PageFaultPhase::Invalidate);
g_in_fault_resolution = false;
{
SpinGuard lock(page.lock);
if (!handled || !page.resolving) {
FailFast("fault invalidation did not preserve the resolving state");
}
}
g_in_fault_resolution = true;
const bool completed = m_impl->fault_handler(m_impl->fault_context, access, fault_vaddr, 1,
PageFaultPhase::Complete);
g_in_fault_resolution = false;
{
SpinGuard lock(page.lock);
if (!completed || !page.resolving) {
FailFast("fault completion did not preserve the resolving state");
}
if (page.write_watchers != 0 || page.access_watchers != 0) {
const auto old_protection = Impl::WatcherProtection(page);
const bool read_only_fault = access == PageFaultAccess::Read;
if (read_only_fault && page.access_watchers == 0) {
FailFast("read fault completed without a read/write watcher");
}
page.access_watchers = 0;
if (!read_only_fault) {
page.write_watchers = 0;
}
const auto restored_protection = Impl::WatcherProtection(page);
Impl::Protect(PageStart(fault_vaddr), restored_protection, old_protection, true);
if (page.write_watchers == 0) {
page.original_protection = 0;
}
Impl::PublishDelayedFaults(page, old_protection, restored_protection);
} else if (!Impl::AllowsAccess(fault_vaddr, access)) {
FailFast("fault completion left the page inaccessible");
}
page.resolving = false;
page.resolving_read_write = false;
}
g_in_fault_resolution = true;
const bool released = m_impl->fault_handler(m_impl->fault_context, access, fault_vaddr, 1,
PageFaultPhase::Release);
g_in_fault_resolution = false;
if (!released) {
FailFast("fault release callback failed");
}
return true;
}
} // namespace Libs::Graphics } // namespace Libs::Graphics
+8 -12
View File
@@ -2,18 +2,16 @@
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_PAGEMANAGER_H_ #define EMULATOR_SRC_GRAPHICS_HOST_GPU_PAGEMANAGER_H_
#include "common/common.h" #include "common/common.h"
#include "graphics/host_gpu/rangeSet.h"
#include <memory> #include <memory>
#include <span>
#include <vector>
namespace Libs::Graphics { namespace Libs::Graphics {
enum class PageFaultAccess { Read, Write, Execute, Unknown }; enum class PageFaultAccess { Read, Write, Execute, Unknown };
enum class PageFaultPhase { Invalidate, Complete, Release };
enum class PageWatchMode { Write, ReadWrite }; enum class PageWatchMode { Write, ReadWrite };
enum class GpuAccess { Read, Write, ReadWrite };
using PageFaultHandler = bool (*)(void* context, PageFaultAccess access, uint64_t vaddr,
uint64_t size, PageFaultPhase phase) noexcept;
class PageManager final { class PageManager final {
public: public:
@@ -29,23 +27,21 @@ public:
uint64_t m_size = 0; uint64_t m_size = 0;
}; };
PageManager(PageFaultHandler fault_handler, void* fault_context); PageManager();
// The owner must stop all PageManager callers before destruction. // The owner must stop all PageManager callers before destruction.
~PageManager(); ~PageManager();
KYTY_CLASS_NO_COPY(PageManager); KYTY_CLASS_NO_COPY(PageManager);
[[nodiscard]] uint64_t GetPageSize() const; [[nodiscard]] uint64_t GetPageSize() const;
[[nodiscard]] bool IsTracked(uint64_t vaddr) const noexcept;
[[nodiscard]] bool IsMapped(uint64_t vaddr, uint64_t size) 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,
PageWatchMode mode = PageWatchMode::Write); PageWatchMode mode = PageWatchMode::Write);
void OnGpuMap(uint64_t vaddr, uint64_t size, GpuAccess access = GpuAccess::ReadWrite); void OnGpuMap(uint64_t vaddr, uint64_t size);
void OnGpuUnmap(uint64_t vaddr, uint64_t size, GpuAccess access = GpuAccess::ReadWrite); void OnGpuUnmap(uint64_t vaddr, uint64_t size);
[[nodiscard]] bool HandleFault(PageFaultAccess access, uint64_t fault_vaddr) noexcept; [[nodiscard]] std::vector<std::unique_ptr<BackingWrite>>
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);
+15 -120
View File
@@ -16,12 +16,15 @@
#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 {
enum class CpuFaultAction { Untracked, Continue, Download };
class TrackingSpinLock final { class TrackingSpinLock final {
public: public:
void lock() noexcept { void lock() noexcept {
@@ -49,6 +52,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
@@ -74,18 +83,6 @@ public:
KYTY_CLASS_NO_COPY(RegionManager); KYTY_CLASS_NO_COPY(RegionManager);
[[nodiscard]] uint64_t GetCpuAddr() const { return m_cpu_addr; } [[nodiscard]] uint64_t GetCpuAddr() const { return m_cpu_addr; }
void Track(uint64_t vaddr, uint64_t size) {
const auto [start, end] = GetPageRange(vaddr, size);
for (auto page = start; page < end; page++) {
m_tracked.set(page);
}
}
void Untrack(uint64_t vaddr, uint64_t size) {
const auto [start, end] = GetPageRange(vaddr, size);
for (auto page = start; page < end; page++) {
m_tracked.reset(page);
}
}
template <DirtySource source> template <DirtySource source>
[[nodiscard]] bool IsModified(uint64_t offset, uint64_t size) const { [[nodiscard]] bool IsModified(uint64_t offset, uint64_t size) const {
const auto [start, end] = GetPageRange(m_cpu_addr + offset, size); const auto [start, end] = GetPageRange(m_cpu_addr + offset, size);
@@ -115,15 +112,15 @@ public:
const auto [start, end] = GetPageRange(vaddr, size); const auto [start, end] = GetPageRange(vaddr, size);
if constexpr (source == DirtySource::Cpu && enable) { if constexpr (source == DirtySource::Cpu && enable) {
for (auto page = start; page < end; page++) { for (auto page = start; page < end; page++) {
if (m_gpu_dirty.test(page) || m_fault_pending.test(page)) { if (m_gpu_dirty.test(page)) {
EXIT("CPU dirty state conflicts with GPU dirty or pending fault state\n"); EXIT("CPU dirty state conflicts with GPU dirty state\n");
} }
} }
} }
if constexpr (source == DirtySource::Gpu && enable) { if constexpr (source == DirtySource::Gpu && enable) {
for (auto page = start; page < end; page++) { for (auto page = start; page < end; page++) {
if (m_cpu_dirty.test(page) || m_fault_pending.test(page)) { if (m_cpu_dirty.test(page)) {
EXIT("GPU dirty state conflicts with CPU dirty or pending fault state\n"); EXIT("GPU dirty state conflicts with CPU dirty state\n");
} }
} }
} }
@@ -140,110 +137,10 @@ public:
return changed; return changed;
} }
[[nodiscard]] CpuFaultAction BeginCpuFault(uint64_t vaddr, uint64_t size,
PageFaultAccess access = PageFaultAccess::Write) {
if (access != PageFaultAccess::Read && access != PageFaultAccess::Write) {
EXIT("unsupported CPU fault access while beginning ownership transfer\n");
}
const auto [start, end] = GetPageRange(vaddr, size);
const bool tracked = m_tracked.test(start);
for (auto page = start; page < end; page++) {
if (m_tracked.test(page) != tracked) {
EXIT("CPU fault spans mixed tracked and untracked pages\n");
}
if (m_fault_pending.test(page)) {
return CpuFaultAction::Untracked;
}
if (m_cpu_dirty.test(page) != m_writable.test(page) ||
(m_gpu_dirty.test(page) && (m_cpu_dirty.test(page) || m_writable.test(page)))) {
EXIT("inconsistent CPU fault page state\n");
}
}
if (!tracked) {
return CpuFaultAction::Untracked;
}
bool gpu_dirty = m_gpu_dirty.test(start);
bool writable = m_writable.test(start);
for (auto page = start + 1; page < end; page++) {
if (m_gpu_dirty.test(page) != gpu_dirty || m_writable.test(page) != writable) {
EXIT("CPU fault spans pages with incompatible dirty or writable state\n");
}
}
for (auto page = start; page < end; page++) {
if (!gpu_dirty && access == PageFaultAccess::Write) {
m_cpu_dirty.set(page);
m_writable.set(page);
}
m_fault_pending.set(page);
}
return gpu_dirty ? CpuFaultAction::Download : CpuFaultAction::Continue;
}
[[nodiscard]] bool CompleteCpuFault(uint64_t vaddr, uint64_t size, PageFaultAccess access,
bool downloaded) {
const auto [start, end] = GetPageRange(vaddr, size);
for (auto page = start; page < end; page++) {
if (!m_fault_pending.test(page)) {
return false;
}
}
for (auto page = start; page < end; page++) {
const bool gpu_dirty = m_gpu_dirty.test(page);
if (gpu_dirty != downloaded) {
EXIT("CPU fault download result disagrees with GPU dirty state\n");
}
if (gpu_dirty) {
m_gpu_dirty.reset(page);
switch (access) {
case PageFaultAccess::Read: break;
case PageFaultAccess::Write:
m_cpu_dirty.set(page);
m_writable.set(page);
break;
default: EXIT("unsupported CPU fault access after GPU download\n");
}
}
m_fault_pending.reset(page);
}
return true;
}
[[nodiscard]] bool HasPendingFault(uint64_t vaddr, uint64_t size) const {
const auto [start, end] = GetPageRange(vaddr, size);
for (auto page = start; page < end; page++) {
if (m_fault_pending.test(page)) {
return true;
}
}
return false;
}
[[nodiscard]] bool CompleteVirtualGpuWrite(uint64_t vaddr, uint64_t size) {
const auto [start, end] = GetPageRange(vaddr, size);
for (auto page = start; page < end; page++) {
if (!m_fault_pending.test(page)) {
return false;
}
if (!m_gpu_dirty.test(page)) {
EXIT("virtual GPU write completion found a non-GPU-dirty page\n");
}
}
for (auto page = start; page < end; page++) {
m_gpu_dirty.reset(page);
m_cpu_dirty.set(page);
m_writable.set(page);
m_fault_pending.reset(page);
}
return true;
}
template <DirtySource source, bool clear, typename Func> template <DirtySource source, bool clear, typename Func>
RegionBits ForEachModifiedRange(uint64_t vaddr, uint64_t size, Func&& func) { RegionBits ForEachModifiedRange(uint64_t vaddr, uint64_t size, Func&& func) {
const auto [start, end] = GetPageRange(vaddr, size); const auto [start, end] = GetPageRange(vaddr, size);
auto mask = GetBits<source>(); auto mask = GetBits<source>();
if constexpr (source == DirtySource::Cpu) {
mask &= ~m_fault_pending;
}
for (auto page = 0u; page < start; page++) { for (auto page = 0u; page < start; page++) {
mask.reset(page); mask.reset(page);
} }
@@ -340,8 +237,6 @@ private:
RegionBits m_cpu_dirty; RegionBits m_cpu_dirty;
RegionBits m_gpu_dirty; RegionBits m_gpu_dirty;
RegionBits m_writable; RegionBits m_writable;
RegionBits m_fault_pending;
RegionBits m_tracked;
}; };
} // namespace Libs::Graphics } // namespace Libs::Graphics
File diff suppressed because it is too large Load Diff
@@ -1,97 +0,0 @@
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_BUFFERCACHE_H_
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_BUFFERCACHE_H_
#include "common/abi.h"
#include "common/common.h"
#include "common/threads.h"
#include "graphics/host_gpu/memoryTracker.h"
#include "graphics/host_gpu/rangeSet.h"
#include <map>
#include <memory>
#include <mutex>
namespace Libs::Graphics {
struct GraphicContext;
struct VulkanBuffer;
class CommandBuffer;
class TextureCache;
class ResourceMutex;
struct BufferImageCopySource {
VulkanBuffer* buffer = nullptr;
uint64_t offset = 0;
uint64_t address = 0;
uint64_t size = 0;
bool cpu_current = false;
// True when the guest range was CPU-dirty before coherence resolution. ObtainBufferForImage
// may consume that tracker state while publishing the same bytes to a cached buffer.
bool cpu_dirty = false;
};
struct BufferCacheRange {
uint64_t address = 0;
uint64_t size = 0;
};
[[nodiscard]] bool MergeOverlappingBufferCacheRange(BufferCacheRange* merged,
BufferCacheRange candidate) noexcept;
[[nodiscard]] bool CanMergeBufferCacheQueueMask(uint64_t queue_mask, uint32_t queue) noexcept;
class BufferCache {
public:
static constexpr uint64_t CACHING_PAGE_SIZE = 16ull * 1024ull;
static constexpr uint64_t GetBufferOffset(uint64_t vaddr) {
return vaddr & (CACHING_PAGE_SIZE - 1);
}
BufferCache(PageManager& page_manager, ResourceMutex& resource_mutex);
~BufferCache();
KYTY_CLASS_NO_COPY(BufferCache);
[[nodiscard]] bool InvalidateMemory(PageFaultAccess access, uint64_t vaddr, uint64_t size,
PageFaultPhase phase) noexcept;
void UnmapMemory(uint64_t vaddr, uint64_t size);
[[nodiscard]] std::pair<VulkanBuffer*, uint64_t>
ObtainBuffer(CommandBuffer* command, GraphicContext* ctx, uint64_t vaddr, uint64_t size,
bool is_written = false, bool is_read = true, bool is_formatted = false);
// Emulator-owned, CPU-current scratch only. Guest ranges must use ObtainBuffer so page
// ownership is resolved before any CPU access.
[[nodiscard]] bool UploadHostData(CommandBuffer* command, GraphicContext* ctx, const void* src,
uint64_t size, uint64_t alignment, VulkanBuffer** out_buffer,
uint64_t* out_offset, uint64_t* out_range);
[[nodiscard]] VulkanBuffer* ObtainNullBuffer(CommandBuffer* command, GraphicContext* ctx);
[[nodiscard]] BufferImageCopySource ObtainBufferForImage(uint64_t vaddr, uint64_t size);
void FillBuffer(CommandBuffer* command, GraphicContext* ctx, uint64_t vaddr, uint64_t size,
uint32_t value);
void CopyBuffer(CommandBuffer* command, GraphicContext* ctx, uint64_t dst_vaddr,
uint64_t src_vaddr, uint64_t size);
[[nodiscard]] bool HasPageOverlap(uint64_t vaddr, uint64_t size);
[[nodiscard]] bool IsRegionCpuModified(uint64_t vaddr, uint64_t size);
[[nodiscard]] bool IsRegionGpuModified(uint64_t vaddr, uint64_t size);
void PublishImageBacking(uint64_t vaddr, uint64_t size);
void ValidateGpuAccess(uint64_t vaddr, uint64_t size, bool is_read, bool is_written) const;
void SetTextureCache(TextureCache& texture_cache);
void ResetNullBuffer();
private:
struct CachedBuffer;
struct ReadbackWorker;
Common::Mutex m_mutex;
std::shared_ptr<VulkanBuffer> m_null_buffer;
// TODO: add LRU cache
std::map<uint64_t, std::unique_ptr<CachedBuffer>> m_buffers;
std::unique_ptr<ReadbackWorker> m_readback;
RangeSet m_gpu_modified_ranges;
MemoryTracker m_memory_tracker;
PageManager& m_page_manager;
TextureCache* m_texture_cache = nullptr;
ResourceMutex& m_resource_mutex;
};
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_BUFFERCACHE_H_
File diff suppressed because it is too large Load Diff
+140
View File
@@ -0,0 +1,140 @@
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_BUFFERCACHE_H_
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_BUFFERCACHE_H_
#include "common/abi.h"
#include "common/common.h"
#include "common/threads.h"
#include "graphics/host_gpu/memoryTracker.h"
#include "graphics/host_gpu/rangeSet.h"
#include "graphics/host_gpu/renderer/cache/streamBuffer.h"
#include <map>
#include <memory>
#include <mutex>
#include <span>
#include <utility>
#include <vector>
namespace Libs::Graphics {
struct GraphicContext;
class CommandBuffer;
class CommandScheduler;
class TextureCache;
class ResourceMutex;
struct BufferBinding {
std::shared_ptr<void> owner;
vk::Buffer buffer = nullptr;
uint64_t offset = 0;
};
struct ImageBufferSource {
Buffer* buffer = nullptr;
uint64_t offset = 0;
bool gpu_owned = false;
};
class BufferCache {
public:
static constexpr uint64_t CACHING_PAGE_SIZE = 16ull * 1024ull;
static constexpr uint64_t GetBufferOffset(uint64_t vaddr) {
return vaddr & (CACHING_PAGE_SIZE - 1);
}
BufferCache(GraphicContext& graphics, CommandScheduler& scheduler, PageManager& page_manager,
TextureCache& texture_cache, ResourceMutex& resource_mutex);
~BufferCache();
KYTY_CLASS_NO_COPY(BufferCache);
void InvalidateMemory(uint64_t vaddr, uint64_t size);
void ReadMemory(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,
bool is_written = false, bool is_read = true,
bool is_formatted = false);
[[nodiscard]] StreamBuffer& GetUtilityBuffer(MemoryUsage usage) noexcept;
[[nodiscard]] Buffer& GetGdsBuffer() noexcept { return m_gds_buffer; }
[[nodiscard]] const Buffer& GetGdsBuffer() const noexcept { return m_gds_buffer; }
[[nodiscard]] BufferBinding UploadTransient(const void* data, uint64_t size,
uint64_t alignment);
[[nodiscard]] std::shared_ptr<Buffer> ObtainNullBuffer();
[[nodiscard]] ImageBufferSource ObtainBufferForImage(uint64_t vaddr, uint64_t size);
[[nodiscard]] std::pair<std::shared_ptr<Buffer>, uint64_t>
ObtainBufferForImageWrite(uint64_t vaddr, uint64_t size);
void DiscardGpuDirtyBytes(uint64_t vaddr, uint64_t size);
void FillBuffer(uint64_t vaddr, uint64_t size, uint32_t value, bool is_gds = false);
void CopyBuffer(uint64_t dst_vaddr, uint64_t src_vaddr, uint64_t size, bool dst_gds = false,
bool src_gds = false);
[[nodiscard]] bool HasPageOverlap(uint64_t vaddr, uint64_t size);
[[nodiscard]] bool HasGpuDirtyBytes(uint64_t vaddr, uint64_t size);
[[nodiscard]] bool IsRegionCpuModified(uint64_t vaddr, uint64_t size);
[[nodiscard]] bool IsRegionGpuModified(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 CompleteBackingPublication(uint64_t vaddr, uint64_t size, uint64_t tick);
[[nodiscard]] bool SynchronizeBacking(uint64_t vaddr, uint64_t size);
void PublishImageBuffer(uint64_t vaddr, uint64_t size);
void RunGarbageCollector();
private:
friend struct BufferCacheTestAccess;
struct CacheRange {
uint64_t address = 0;
uint64_t size = 0;
};
struct CachedBuffer;
struct DownloadCopy;
struct DownloadRange;
struct RetiredBuffer;
struct PendingBackingPublication;
static constexpr uint64_t DOWNLOAD_ALIGNMENT = 64;
[[nodiscard]] static uint64_t AlignDown(uint64_t value) noexcept;
[[nodiscard]] static uint64_t AlignUp(uint64_t value);
[[nodiscard]] static constexpr uint64_t AlignDownload(uint64_t size) noexcept {
return (size + DOWNLOAD_ALIGNMENT - 1) & ~(DOWNLOAD_ALIGNMENT - 1);
}
[[nodiscard]] static bool PageOverlaps(uint64_t left, uint64_t left_size, uint64_t right,
uint64_t right_size) noexcept;
[[nodiscard]] static std::pair<uint64_t, uint64_t> DownloadEnvelope(const DownloadCopy& copy);
[[nodiscard]] static bool ResolveOverlap(CacheRange& merged, CacheRange candidate) noexcept;
void Upload(CommandBuffer& command, Buffer& destination, uint64_t destination_offset,
const void* source, uint64_t size);
[[nodiscard]] CachedBuffer& GetOrCreateBuffer(CommandBuffer& command, uint64_t vaddr,
uint64_t size);
[[nodiscard]] std::vector<DownloadRange> RecordDownloads(std::span<const DownloadCopy> copies);
void PublishDownloads(std::span<const DownloadRange> downloads);
void QueueGarbageDownload(std::span<const DownloadCopy> copies, RetiredBuffer retire);
void RefreshInvalidatedRanges(CommandBuffer& command, CachedBuffer& cached, uint64_t vaddr,
uint64_t size, bool upload);
void DiscardGpuDirtyBytesLocked(uint64_t vaddr, uint64_t size, const char* operation);
void WriteHostMemory(uint64_t vaddr, std::span<const uint8_t> data);
GraphicContext& m_graphics;
CommandScheduler& m_scheduler;
Buffer m_gds_buffer;
Common::Mutex m_mutex;
std::shared_ptr<Buffer> m_null_buffer;
std::map<uint64_t, std::unique_ptr<CachedBuffer>> m_buffers;
RangeSet m_gpu_modified_ranges;
RangeSet m_image_invalidated_ranges;
std::mutex m_publication_mutex;
std::vector<PendingBackingPublication> m_pending_backing_publications;
MemoryTracker m_memory_tracker;
StreamBuffer m_staging_buffer;
StreamBuffer m_stream_buffer;
StreamBuffer m_download_buffer;
StreamBuffer m_device_buffer;
PageManager& m_page_manager;
TextureCache& m_texture_cache;
ResourceMutex& m_resource_mutex;
uint64_t m_total_used_memory = 0;
uint64_t m_trigger_gc_memory = 1ull * 1024 * 1024 * 1024;
uint64_t m_critical_gc_memory = 2ull * 1024 * 1024 * 1024;
uint64_t m_gc_tick = 0;
};
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_BUFFERCACHE_H_
@@ -0,0 +1,128 @@
#include "graphics/host_gpu/renderer/cache/gpuResourceManager.h"
#include "common/assert.h"
#include "graphics/guest_gpu/command_processor/commandProcessor.h"
#include "graphics/guest_gpu/graphicsRun.h"
#include "graphics/host_gpu/renderer/commandScheduler.h"
namespace Libs::Graphics {
GpuResourceManager::GpuResourceManager(GraphicContext& graphics, CommandScheduler& scheduler)
: m_buffer_cache(graphics, scheduler, m_page_manager, m_texture_cache, m_resource_mutex),
m_texture_cache(graphics, scheduler, m_page_manager, m_buffer_cache, m_resource_mutex) {}
GpuResourceManager::~GpuResourceManager() = default;
bool GpuResourceManager::HandleFault(PageFaultAccess access, uint64_t fault_vaddr) noexcept {
constexpr uint64_t fault_size = 8;
if (!IsMapped(fault_vaddr, fault_size)) {
return false;
}
if (CommandScheduler::InDeferredOperation()) {
EXIT("unsupported guest-memory fault from an asynchronous GPU completion, "
"addr=0x%016" PRIx64 " access=%u\n",
fault_vaddr, static_cast<uint32_t>(access));
}
bool handled = false;
const auto resolve = [this, access, fault_vaddr, &handled](CommandProcessor& cp) {
cp.BeginReadbackTransaction();
{
ResourceMutex::FaultScope fault(m_resource_mutex);
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();
};
if (auto* cp = Gpu::CurrentCommandProcessor(); cp != nullptr) {
resolve(*cp);
return handled;
}
if (m_resource_mutex.IsOwnedByCurrentThread()) {
EXIT("unsupported page fault from a pre-owned resource transaction, addr=0x%016" PRIx64
" access=%u\n",
fault_vaddr, static_cast<uint32_t>(access));
}
EXIT_IF(m_gpu == nullptr);
m_gpu->SendCommandSyncWithProcessor(resolve);
return handled;
}
bool GpuResourceManager::InvalidateMemory(uint64_t vaddr, uint64_t size) {
if (!IsMapped(vaddr, size)) {
return false;
}
if (CommandScheduler::InDeferredOperation()) {
EXIT("unsupported memory invalidation from an asynchronous GPU completion, "
"addr=0x%016" PRIx64 " size=0x%016" PRIx64 "\n",
vaddr, size);
}
const auto resolve = [this, vaddr, size](CommandProcessor& cp) {
cp.BeginReadbackTransaction();
{
ResourceMutex::FaultScope fault(m_resource_mutex);
m_buffer_cache.InvalidateMemory(vaddr, size);
m_texture_cache.InvalidateMemory(vaddr, size);
}
cp.EndReadbackTransaction();
};
if (auto* cp = Gpu::CurrentCommandProcessor(); cp != nullptr) {
resolve(*cp);
return true;
}
if (m_resource_mutex.IsOwnedByCurrentThread()) {
EXIT("unsupported memory invalidation from a pre-owned resource transaction, "
"addr=0x%016" PRIx64 " size=0x%016" PRIx64 "\n",
vaddr, size);
}
EXIT_IF(m_gpu == nullptr);
m_gpu->SendCommandSyncWithProcessor(resolve);
return true;
}
bool GpuResourceManager::IsMapped(uint64_t vaddr, uint64_t size) const noexcept {
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) {
{
std::lock_guard lock(m_mapped_ranges_mutex);
m_mapped_ranges.Add(vaddr, size);
}
m_page_manager.OnGpuMap(vaddr, size);
}
void GpuResourceManager::UnmapMemory(uint64_t vaddr, uint64_t size) {
const auto unmap = [this, vaddr, size] {
m_buffer_cache.UnmapMemory(vaddr, size);
m_texture_cache.UnmapMemory(vaddr, size);
m_page_manager.OnGpuUnmap(vaddr, size);
std::lock_guard lock(m_mapped_ranges_mutex);
m_mapped_ranges.Subtract(vaddr, size);
};
if (m_gpu == nullptr) {
if (m_resource_mutex.IsOwnedByCurrentThread()) {
EXIT("cannot synchronously unmap from a resource transaction\n");
}
unmap();
return;
}
Gpu::SubmissionLock submissions(*m_gpu);
m_gpu->SendCommandSync(unmap);
}
void GpuResourceManager::RunGarbageCollector() {
m_texture_cache.ProcessDownloadImages();
m_texture_cache.RunGarbageCollector();
m_buffer_cache.RunGarbageCollector();
}
} // namespace Libs::Graphics
@@ -0,0 +1,48 @@
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_GPURESOURCEMANAGER_H_
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_GPURESOURCEMANAGER_H_
#include "common/abi.h"
#include "common/common.h"
#include "graphics/host_gpu/pageManager.h"
#include "graphics/host_gpu/renderer/cache/bufferCache.h"
#include "graphics/host_gpu/renderer/cache/resourceMutex.h"
#include "graphics/host_gpu/renderer/cache/textureCache.h"
#include <cstdint>
#include <shared_mutex>
namespace Libs::Graphics {
class CommandScheduler;
class Gpu;
class GpuResourceManager {
public:
GpuResourceManager(GraphicContext& graphics, CommandScheduler& scheduler);
~GpuResourceManager();
KYTY_CLASS_NO_COPY(GpuResourceManager);
[[nodiscard]] BufferCache& GetBufferCache() { return m_buffer_cache; }
[[nodiscard]] TextureCache& GetTextureCache() { return m_texture_cache; }
void SetGpu(Gpu* gpu) noexcept { m_gpu = gpu; }
[[nodiscard]] bool HandleFault(PageFaultAccess access, uint64_t fault_vaddr) noexcept;
[[nodiscard]] bool InvalidateMemory(uint64_t vaddr, uint64_t size);
[[nodiscard]] bool IsMapped(uint64_t vaddr, uint64_t size) const noexcept;
void MapMemory(uint64_t vaddr, uint64_t size);
void UnmapMemory(uint64_t vaddr, uint64_t size);
void RunGarbageCollector();
private:
PageManager m_page_manager;
ResourceMutex m_resource_mutex;
BufferCache m_buffer_cache;
TextureCache m_texture_cache;
mutable std::shared_mutex m_mapped_ranges_mutex;
RangeSet m_mapped_ranges;
Gpu* m_gpu = nullptr;
};
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_GPURESOURCEMANAGER_H_
@@ -1,13 +1,14 @@
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_MULTILEVELPAGETABLE_H_ #ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_MULTILEVELPAGETABLE_H_
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_MULTILEVELPAGETABLE_H_ #define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_MULTILEVELPAGETABLE_H_
#include "common/assert.h"
#include <algorithm> #include <algorithm>
#include <array> #include <array>
#include <cstddef> #include <cstddef>
#include <cstdint> #include <cstdint>
#include <list> #include <list>
#include <memory> #include <memory>
#include <stdexcept>
#include <utility> #include <utility>
#include <vector> #include <vector>
@@ -59,7 +60,7 @@ public:
[[nodiscard]] Entry& GetOrCreate(size_t page) { [[nodiscard]] Entry& GetOrCreate(size_t page) {
if (!IsValidPage(page)) { if (!IsValidPage(page)) {
throw std::out_of_range("MultiLevelPageTable page is outside the guest address space"); EXIT("MultiLevelPageTable page is outside the guest address space");
} }
auto& bucket = m_first_level[FirstLevelIndex(page)]; auto& bucket = m_first_level[FirstLevelIndex(page)];
if (bucket == nullptr) { if (bucket == nullptr) {
@@ -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"
@@ -9,7 +9,7 @@
namespace Libs::Graphics { namespace Libs::Graphics {
// Owner-tracked shared buffer/image transaction. External faults pause GPU submissions first; // Owner-tracked shared buffer/image transaction. External faults pause GPU submissions first;
// command-processor faults drain their queue before entering this transaction. // command-processor faults drain pending guest processors before entering this transaction.
class ResourceMutex final { class ResourceMutex final {
public: public:
class FaultScope final { class FaultScope final {
@@ -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"
@@ -7,6 +7,13 @@
namespace Libs::Graphics { namespace Libs::Graphics {
SamplerCache::~SamplerCache() {
for (const auto& [key, sampler]: m_samplers) {
(void)key;
m_graphics.device.destroySampler(sampler, nullptr);
}
}
vk::Sampler SamplerCache::GetSampler(const ShaderSamplerResource& r) { vk::Sampler SamplerCache::GetSampler(const ShaderSamplerResource& r) {
Common::LockGuard lock(m_mutex); Common::LockGuard lock(m_mutex);
@@ -49,7 +56,10 @@ vk::Sampler SamplerCache::GetSampler(const ShaderSamplerResource& r) {
case Prospero::SamplerAnisoRatio::kFour: aniso_ratio = 4.0f; break; case Prospero::SamplerAnisoRatio::kFour: aniso_ratio = 4.0f; break;
case Prospero::SamplerAnisoRatio::kEight: aniso_ratio = 8.0f; break; case Prospero::SamplerAnisoRatio::kEight: aniso_ratio = 8.0f; break;
case Prospero::SamplerAnisoRatio::kSixteen: aniso_ratio = 16.0f; break; case Prospero::SamplerAnisoRatio::kSixteen: aniso_ratio = 16.0f; break;
default: EXIT("unknown ratio: %d\n", static_cast<int>(r.MaxAnisoRatio())); default:
EXIT("unknown ratio: %d dwords=%08x,%08x,%08x,%08x\n",
static_cast<int>(r.MaxAnisoRatio()), r.fields[0], r.fields[1], r.fields[2],
r.fields[3]);
} }
} }
@@ -143,8 +153,7 @@ vk::Sampler SamplerCache::GetSampler(const ShaderSamplerResource& r) {
} }
vk::Sampler vk_sampler = nullptr; vk::Sampler vk_sampler = nullptr;
const auto result = const auto result = m_graphics.device.createSampler(&sampler_info, nullptr, &vk_sampler);
g_render_ctx->GetGraphicCtx()->device.createSampler(&sampler_info, nullptr, &vk_sampler);
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess || vk_sampler == nullptr); EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess || vk_sampler == nullptr);
m_samplers.emplace(key, vk_sampler); m_samplers.emplace(key, vk_sampler);
@@ -14,10 +14,14 @@
namespace Libs::Graphics { namespace Libs::Graphics {
struct GraphicContext;
class SamplerCache { class SamplerCache {
public: public:
SamplerCache() { EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread()); } explicit SamplerCache(GraphicContext& graphics): m_graphics(graphics) {
~SamplerCache() { KYTY_NOT_IMPLEMENTED; } EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread());
}
~SamplerCache();
KYTY_CLASS_NO_COPY(SamplerCache); KYTY_CLASS_NO_COPY(SamplerCache);
vk::Sampler GetSampler(const ShaderSamplerResource& r); vk::Sampler GetSampler(const ShaderSamplerResource& r);
@@ -37,6 +41,7 @@ private:
} }
}; };
GraphicContext& m_graphics;
Common::Mutex m_mutex; Common::Mutex m_mutex;
std::unordered_map<SamplerKey, vk::Sampler, SamplerKeyHash> m_samplers; std::unordered_map<SamplerKey, vk::Sampler, SamplerKeyHash> m_samplers;
}; };
+358
View File
@@ -0,0 +1,358 @@
#include "graphics/host_gpu/renderer/cache/streamBuffer.h"
#include "common/assert.h"
#include "common/profiler.h"
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/renderer/commandScheduler.h"
#include "graphics/host_gpu/vma.h"
#include <cstring>
#include <limits>
#include <numeric>
#include <vk_mem_alloc.h>
namespace Libs::Graphics {
namespace {
constexpr size_t WATCHES_INITIAL_RESERVE = 0x4000;
constexpr size_t WATCHES_RESERVE_CHUNK = 0x1000;
[[nodiscard]] VmaAllocationCreateFlags AllocationFlags(MemoryUsage usage) {
switch (usage) {
case MemoryUsage::Upload:
case MemoryUsage::Stream:
return VMA_ALLOCATION_CREATE_MAPPED_BIT |
VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;
case MemoryUsage::Download:
return VMA_ALLOCATION_CREATE_MAPPED_BIT | VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT;
case MemoryUsage::DeviceLocal: return {};
}
return {};
}
[[nodiscard]] VmaMemoryUsage AllocationUsage(MemoryUsage usage) {
switch (usage) {
case MemoryUsage::DeviceLocal:
case MemoryUsage::Stream: return VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
case MemoryUsage::Upload:
case MemoryUsage::Download: return VMA_MEMORY_USAGE_AUTO_PREFER_HOST;
}
return VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
}
[[nodiscard]] bool AlignUp(uint64_t value, uint64_t alignment, uint64_t& result) {
if (alignment == 0) {
result = value;
return true;
}
const auto remainder = value % alignment;
if (remainder == 0) {
result = value;
return true;
}
const auto increment = alignment - remainder;
if (value > std::numeric_limits<uint64_t>::max() - increment) {
return false;
}
result = value + increment;
return true;
}
} // namespace
Buffer::Buffer(GraphicContext& graphics, CommandScheduler& scheduler, MemoryUsage usage,
uint64_t cpu_address, vk::BufferUsageFlags flags, uint64_t size)
: m_graphics(&graphics), m_scheduler(&scheduler), m_usage(usage), m_cpu_address(cpu_address),
m_size(size), m_buffer(std::make_unique<VulkanBuffer>()) {
KYTY_PROFILER_FUNCTION();
EXIT_IF(graphics.allocator == nullptr || size == 0);
vk::BufferCreateInfo buffer_info {};
buffer_info.size = size;
buffer_info.usage = flags;
buffer_info.sharingMode = vk::SharingMode::eExclusive;
VmaAllocationCreateInfo allocation_info {};
allocation_info.flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT | AllocationFlags(usage);
allocation_info.usage = AllocationUsage(usage);
allocation_info.preferredFlags = usage == MemoryUsage::DeviceLocal
? VkMemoryPropertyFlags {}
: VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
VmaAllocationInfo allocation_result {};
VkBuffer native_buffer = VK_NULL_HANDLE;
const auto result = static_cast<vk::Result>(vmaCreateBuffer(
graphics.allocator, static_cast<const VkBufferCreateInfo*>(buffer_info), &allocation_info,
&native_buffer, &m_buffer->memory.allocation, &allocation_result));
if (result != vk::Result::eSuccess) {
graphics.LogMemoryBudget();
}
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
m_buffer->buffer = native_buffer;
m_buffer->usage = flags;
m_buffer->buffer_size = size;
m_buffer->memory.allocation_info = allocation_result;
m_buffer->memory.memory = allocation_result.deviceMemory;
m_buffer->memory.offset = allocation_result.offset;
m_buffer->memory.type = allocation_result.memoryType;
m_buffer->memory.unique_id = VulkanNextMemoryUniqueId();
graphics.device.getBufferMemoryRequirements(m_buffer->buffer, &m_buffer->memory.requirements);
VkMemoryPropertyFlags properties = 0;
vmaGetAllocationMemoryProperties(graphics.allocator, m_buffer->memory.allocation, &properties);
m_buffer->memory.property = vk::MemoryPropertyFlags(properties);
m_is_coherent = (properties & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) != 0;
if (allocation_result.pMappedData != nullptr) {
m_mapped = {static_cast<uint8_t*>(allocation_result.pMappedData),
static_cast<size_t>(size)};
}
VulkanTrackAllocation(m_buffer->memory);
}
Buffer::~Buffer() {
if (m_buffer->buffer != nullptr) {
VulkanUntrackAllocation(m_buffer->memory);
vmaDestroyBuffer(m_graphics->allocator, m_buffer->buffer, m_buffer->memory.allocation);
}
}
vk::Buffer Buffer::Handle() const noexcept {
return m_buffer->buffer;
}
bool Buffer::IsInBounds(uint64_t address, uint64_t size) const noexcept {
return address >= m_cpu_address && size <= m_size && address - m_cpu_address <= m_size - size;
}
void Buffer::Write(uint64_t offset, const void* source, uint64_t size) {
EXIT_IF(source == nullptr || m_mapped.empty() || offset > m_size || size > m_size - offset);
std::memcpy(m_mapped.data() + offset, source, static_cast<size_t>(size));
Flush(offset, size);
}
void Buffer::Flush(uint64_t offset, uint64_t size) {
EXIT_IF(m_mapped.empty() || offset > m_size || size > m_size - offset);
if (!m_is_coherent && size != 0) {
const auto result =
vmaFlushAllocation(m_graphics->allocator, m_buffer->memory.allocation, offset, size);
EXIT_NOT_IMPLEMENTED(static_cast<vk::Result>(result) != vk::Result::eSuccess);
}
}
vk::BufferMemoryBarrier Buffer::Barrier(uint64_t offset, uint64_t size, vk::AccessFlags source,
vk::AccessFlags destination) const {
if (Handle() == nullptr || size == 0 || offset > m_size || size > m_size - offset) {
EXIT("Buffer: invalid DMA barrier, handle=%p offset=0x%016" PRIx64 " size=0x%016" PRIx64
" capacity=0x%016" PRIx64 "\n",
static_cast<const void*>(Handle()), offset, size, m_size);
}
vk::BufferMemoryBarrier barrier {};
barrier.sType = vk::StructureType::eBufferMemoryBarrier;
barrier.srcAccessMask = source;
barrier.dstAccessMask = destination;
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.buffer = Handle();
barrier.offset = offset;
barrier.size = size;
return barrier;
}
void Buffer::CopyFrom(CommandBuffer& command, const Buffer& source, uint64_t source_offset,
uint64_t destination_offset, uint64_t size, vk::AccessFlags source_before,
vk::AccessFlags destination_before, vk::AccessFlags source_after,
vk::AccessFlags destination_after) {
if (size == 0 || source_offset > source.m_size || size > source.m_size - source_offset ||
destination_offset > m_size || size > m_size - destination_offset) {
EXIT("Buffer: invalid copy range\n");
}
if (source.Handle() == Handle() && source_offset < destination_offset + size &&
destination_offset < source_offset + size) {
EXIT("Buffer: overlapping self-copy\n");
}
command.EndRendering();
const vk::BufferMemoryBarrier before[] = {
source.Barrier(source_offset, size, source_before, vk::AccessFlagBits::eTransferRead),
Barrier(destination_offset, size, destination_before, vk::AccessFlagBits::eTransferWrite),
};
const auto host_access = vk::AccessFlagBits::eHostRead | vk::AccessFlagBits::eHostWrite;
auto before_stage = vk::PipelineStageFlags {vk::PipelineStageFlagBits::eAllCommands};
if (static_cast<bool>((source_before | destination_before) & host_access)) {
before_stage |= vk::PipelineStageFlagBits::eHost;
}
const auto native = command.Handle();
native.pipelineBarrier(before_stage, vk::PipelineStageFlagBits::eTransfer,
vk::DependencyFlagBits::eByRegion, 0, nullptr, 2, before, 0, nullptr);
const vk::BufferCopy copy {source_offset, destination_offset, size};
native.copyBuffer(source.Handle(), Handle(), 1, &copy);
const vk::BufferMemoryBarrier after[] = {
source.Barrier(source_offset, size, vk::AccessFlagBits::eTransferRead, source_after),
Barrier(destination_offset, size, vk::AccessFlagBits::eTransferWrite, destination_after),
};
auto after_stage = vk::PipelineStageFlags {vk::PipelineStageFlagBits::eAllCommands};
if (static_cast<bool>((source_after | destination_after) & host_access)) {
after_stage |= vk::PipelineStageFlagBits::eHost;
}
native.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, after_stage,
vk::DependencyFlagBits::eByRegion, 0, nullptr, 2, after, 0, nullptr);
}
void Buffer::Fill(uint64_t offset, uint64_t size, uint32_t value) {
if (((offset | size) & 3u) != 0) {
EXIT("Buffer: fill range must be dword aligned\n");
}
auto& command = Scheduler().Current();
command.EndRendering();
const auto before =
Barrier(offset, size, vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite,
vk::AccessFlagBits::eTransferWrite);
const auto native = command.Handle();
native.pipelineBarrier(vk::PipelineStageFlagBits::eAllCommands,
vk::PipelineStageFlagBits::eTransfer, vk::DependencyFlagBits::eByRegion,
0, nullptr, 1, &before, 0, nullptr);
native.fillBuffer(Handle(), offset, size, value);
const auto after = Barrier(offset, size, vk::AccessFlagBits::eTransferWrite,
vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite);
native.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer,
vk::PipelineStageFlagBits::eAllCommands,
vk::DependencyFlagBits::eByRegion, 0, nullptr, 1, &after, 0, nullptr);
}
StreamBuffer::StreamBuffer(GraphicContext& graphics, CommandScheduler& scheduler, MemoryUsage usage,
uint64_t size)
: Buffer(graphics, scheduler, usage, 0, AllFlags, size) {
ReserveWatches(m_current_watches, WATCHES_INITIAL_RESERVE);
ReserveWatches(m_previous_watches, WATCHES_INITIAL_RESERVE);
}
bool StreamBuffer::NormalizeReservation(bool coherent, uint64_t atom, uint64_t& size,
uint64_t& alignment) {
if (coherent) {
return true;
}
if (!AlignUp(size, atom, size)) {
return false;
}
const auto divisor = std::gcd(alignment, atom);
if (alignment != 0 && alignment / divisor > UINT64_MAX / atom) {
return false;
}
alignment = alignment == 0 ? atom : alignment / divisor * atom;
return true;
}
std::pair<uint8_t*, uint64_t> StreamBuffer::Map(uint64_t size, uint64_t alignment,
bool allow_wait) {
if (Mapped().empty()) {
return {nullptr, 0};
}
uint64_t mapped_size = size;
const auto atom = Graphics().physical_device_properties.limits.nonCoherentAtomSize;
if (!NormalizeReservation(IsCoherent(), atom, mapped_size, alignment)) {
return {nullptr, 0};
}
if (mapped_size > Size()) {
return {nullptr, 0};
}
uint64_t aligned_offset = 0;
if (!AlignUp(m_offset, alignment, aligned_offset)) {
return {nullptr, 0};
}
const bool wrap = aligned_offset > Size() - mapped_size;
if (wrap) {
aligned_offset = 0;
}
auto wait_cursor = wrap ? size_t {0} : m_wait_cursor;
auto wait_bound = wrap ? uint64_t {0} : m_wait_bound;
auto invalidation_mark =
wrap ? std::optional<size_t> {m_current_watch_cursor} : m_invalidation_mark;
auto& pending_watches = wrap ? m_current_watches : m_previous_watches;
if (!WaitPendingOperations(pending_watches, invalidation_mark, aligned_offset + mapped_size,
allow_wait, wait_cursor, wait_bound)) {
return {nullptr, 0};
}
if (wrap) {
m_invalidation_mark = invalidation_mark;
m_current_watch_cursor = 0;
std::swap(m_previous_watches, m_current_watches);
}
m_wait_cursor = wait_cursor;
m_wait_bound = wait_bound;
m_offset = aligned_offset;
m_mapped_size = mapped_size;
return {Mapped().data() + m_offset, m_offset};
}
void StreamBuffer::Commit() {
if (!IsCoherent() && Usage() != MemoryUsage::Download && m_mapped_size != 0) {
const auto result = vmaFlushAllocation(
Graphics().allocator, NativeBuffer().memory.allocation, m_offset, m_mapped_size);
EXIT_NOT_IMPLEMENTED(static_cast<vk::Result>(result) != vk::Result::eSuccess);
}
m_offset += m_mapped_size;
const auto tick = Scheduler().CurrentTick();
if (m_current_watch_cursor != 0 && m_current_watches[m_current_watch_cursor - 1].tick == tick) {
m_current_watches[m_current_watch_cursor - 1].upper_bound = m_offset;
return;
}
if (m_current_watch_cursor + 1 >= m_current_watches.size()) {
ReserveWatches(m_current_watches, WATCHES_RESERVE_CHUNK);
}
auto& watch = m_current_watches[m_current_watch_cursor++];
watch.upper_bound = m_offset;
watch.tick = tick;
}
void StreamBuffer::Invalidate(uint64_t offset, uint64_t size) {
EXIT_IF(Usage() != MemoryUsage::Download || offset > Size() || size > Size() - offset);
if (IsCoherent() || size == 0) {
return;
}
const auto result = vmaInvalidateAllocation(Graphics().allocator,
NativeBuffer().memory.allocation, offset, size);
EXIT_NOT_IMPLEMENTED(static_cast<vk::Result>(result) != vk::Result::eSuccess);
}
uint64_t StreamBuffer::Copy(const void* source, uint64_t size, uint64_t alignment) {
EXIT_IF(source == nullptr);
const auto [data, offset] = Map(size, alignment);
EXIT_IF(data == nullptr);
std::memcpy(data, source, static_cast<size_t>(size));
Commit();
return offset;
}
void StreamBuffer::ReserveWatches(std::vector<Watch>& watches, size_t grow_size) {
watches.resize(watches.size() + grow_size);
}
bool StreamBuffer::WaitPendingOperations(const std::vector<Watch>& watches,
std::optional<size_t> invalidation_mark,
uint64_t requested_upper_bound, bool allow_wait,
size_t& wait_cursor, uint64_t& wait_bound) {
if (!invalidation_mark.has_value()) {
return true;
}
while (requested_upper_bound > wait_bound && wait_cursor < *invalidation_mark) {
const auto& watch = watches[wait_cursor];
if (!Scheduler().IsFree(watch.tick) && !allow_wait) {
return false;
}
Scheduler().Wait(watch.tick);
if (Usage() == MemoryUsage::Download) {
Scheduler().WaitPriorityOperations(watch.tick);
}
wait_bound = watch.upper_bound;
++wait_cursor;
}
return true;
}
} // namespace Libs::Graphics
+129
View File
@@ -0,0 +1,129 @@
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_STREAMBUFFER_H_
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_STREAMBUFFER_H_
#include "common/abi.h"
#include "common/common.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include <cstdint>
#include <memory>
#include <optional>
#include <span>
#include <utility>
#include <vector>
namespace Libs::Graphics {
class CommandBuffer;
class CommandScheduler;
struct StreamBufferTestAccess;
struct GraphicContext;
struct VulkanBuffer;
enum class MemoryUsage : uint8_t {
DeviceLocal,
Upload,
Download,
Stream,
};
inline constexpr vk::BufferUsageFlags ReadFlags =
vk::BufferUsageFlagBits::eTransferSrc | vk::BufferUsageFlagBits::eUniformBuffer |
vk::BufferUsageFlagBits::eIndexBuffer | vk::BufferUsageFlagBits::eVertexBuffer |
vk::BufferUsageFlagBits::eIndirectBuffer;
inline constexpr vk::BufferUsageFlags AllFlags =
ReadFlags | vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eStorageBuffer;
class Buffer {
public:
Buffer(GraphicContext& graphics, CommandScheduler& scheduler, MemoryUsage usage,
uint64_t cpu_address, vk::BufferUsageFlags flags, uint64_t size);
~Buffer();
KYTY_CLASS_NO_COPY(Buffer);
[[nodiscard]] vk::Buffer Handle() const noexcept;
[[nodiscard]] uint64_t Size() const noexcept { return m_size; }
[[nodiscard]] std::span<uint8_t> Mapped() const noexcept { return m_mapped; }
[[nodiscard]] bool IsCoherent() const noexcept { return m_is_coherent; }
[[nodiscard]] MemoryUsage Usage() const noexcept { return m_usage; }
[[nodiscard]] uint64_t CpuAddress() const noexcept { return m_cpu_address; }
[[nodiscard]] uint64_t Offset(uint64_t address) const noexcept {
return address - m_cpu_address;
}
[[nodiscard]] bool IsInBounds(uint64_t address, uint64_t size) const noexcept;
void Write(uint64_t offset, const void* source, uint64_t size);
void Flush(uint64_t offset, uint64_t size);
void CopyFrom(CommandBuffer& command, const Buffer& source, uint64_t source_offset,
uint64_t destination_offset, uint64_t size,
vk::AccessFlags source_before = vk::AccessFlagBits::eMemoryWrite,
vk::AccessFlags destination_before = vk::AccessFlagBits::eMemoryRead |
vk::AccessFlagBits::eMemoryWrite,
vk::AccessFlags source_after = vk::AccessFlagBits::eMemoryRead |
vk::AccessFlagBits::eMemoryWrite,
vk::AccessFlags destination_after = vk::AccessFlagBits::eMemoryRead |
vk::AccessFlagBits::eMemoryWrite);
void Fill(uint64_t offset, uint64_t size, uint32_t value);
protected:
[[nodiscard]] GraphicContext& Graphics() const noexcept { return *m_graphics; }
[[nodiscard]] CommandScheduler& Scheduler() const noexcept { return *m_scheduler; }
[[nodiscard]] VulkanBuffer& NativeBuffer() noexcept { return *m_buffer; }
private:
[[nodiscard]] vk::BufferMemoryBarrier Barrier(uint64_t offset, uint64_t size,
vk::AccessFlags source,
vk::AccessFlags destination) const;
GraphicContext* m_graphics = nullptr;
CommandScheduler* m_scheduler = nullptr;
MemoryUsage m_usage = MemoryUsage::DeviceLocal;
uint64_t m_cpu_address = 0;
uint64_t m_size = 0;
std::unique_ptr<VulkanBuffer> m_buffer;
std::span<uint8_t> m_mapped;
bool m_is_coherent = false;
};
class StreamBuffer final: public Buffer {
public:
StreamBuffer(GraphicContext& graphics, CommandScheduler& scheduler, MemoryUsage usage,
uint64_t size);
[[nodiscard]] std::pair<uint8_t*, uint64_t> Map(uint64_t size, uint64_t alignment = 0,
bool allow_wait = true);
void Commit();
// Download mappings become visible to the CPU only after their GPU completion tick is free.
// Call this from the scheduler's deferred completion operation before reading Mapped().
void Invalidate(uint64_t offset, uint64_t size);
[[nodiscard]] uint64_t Copy(const void* source, uint64_t size, uint64_t alignment = 0);
private:
friend struct StreamBufferTestAccess;
struct Watch {
uint64_t tick = 0;
uint64_t upper_bound = 0;
};
void ReserveWatches(std::vector<Watch>& watches, size_t grow_size);
[[nodiscard]] static bool NormalizeReservation(bool coherent, uint64_t atom, uint64_t& size,
uint64_t& alignment);
[[nodiscard]] bool WaitPendingOperations(const std::vector<Watch>& watches,
std::optional<size_t> invalidation_mark,
uint64_t requested_upper_bound, bool allow_wait,
size_t& wait_cursor, uint64_t& wait_bound);
uint64_t m_offset = 0;
uint64_t m_mapped_size = 0;
std::vector<Watch> m_current_watches;
size_t m_current_watch_cursor = 0;
std::optional<size_t> m_invalidation_mark;
std::vector<Watch> m_previous_watches;
size_t m_wait_cursor = 0;
uint64_t m_wait_bound = 0;
};
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_STREAMBUFFER_H_
File diff suppressed because it is too large Load Diff
+192
View File
@@ -0,0 +1,192 @@
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_TEXTURECACHE_H_
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_TEXTURECACHE_H_
#include "common/abi.h"
#include "common/common.h"
#include "common/lruCache.h"
#include "graphics/host_gpu/pageManager.h"
#include "graphics/host_gpu/regionManager.h"
#include "graphics/host_gpu/renderer/cache/multiLevelPageTable.h"
#include "graphics/host_gpu/renderer/image/blitHelper.h"
#include "graphics/host_gpu/renderer/image/image.h"
#include <compare>
#include <map>
#include <memory>
#include <optional>
#include <set>
#include <span>
#include <utility>
#include <vector>
namespace Libs::Graphics {
struct GraphicContext;
class Buffer;
class BufferCache;
class CommandBuffer;
class CommandScheduler;
class ResourceMutex;
class RenderExecutor;
class StreamBuffer;
class TileManager;
struct TextureCacheTestAccess;
class TextureCache {
public:
enum class BindingType : uint8_t { Texture, Storage, RenderTarget, DepthTarget, VideoOut };
struct ImageDesc {
ImageInfo info;
ImageViewInfo view_info;
BindingType type = BindingType::Texture;
};
struct RegionInfo {
bool image_pages = false;
bool image_bytes = false;
bool gpu_image_bytes = false;
};
TextureCache(GraphicContext& graphics, CommandScheduler& scheduler, PageManager& page_manager,
BufferCache& buffer_cache, ResourceMutex& resource_mutex);
~TextureCache();
KYTY_CLASS_NO_COPY(TextureCache);
[[nodiscard]] ImageId FindImage(ImageDesc& desc, bool exact_format = false);
[[nodiscard]] ImageId FindImageFromRange(uint64_t address, uint64_t size,
bool ensure_valid = true);
[[nodiscard]] vk::ImageView FindTexture(ImageId id, const ImageDesc& desc);
[[nodiscard]] vk::ImageView FindRenderTarget(ImageId id, const ImageDesc& desc);
[[nodiscard]] vk::ImageView FindDepthTarget(ImageId id, const ImageDesc& desc);
[[nodiscard]] Image& GetImage(ImageId id);
[[nodiscard]] const Image& GetImage(ImageId id) const;
void MarkGpuWritten(ImageId id);
[[nodiscard]] bool ClearImageFromBuffer(CommandBuffer& command, uint64_t address, uint64_t size,
uint32_t packed_clear);
void InvalidateMemory(uint64_t address, uint64_t size);
[[nodiscard]] bool SynchronizeImageToBuffer(uint64_t address, uint64_t size);
[[nodiscard]] bool InvalidateMemoryFromGPU(uint64_t address, uint64_t size,
bool formatted_buffer_write = false);
[[nodiscard]] RegionInfo QueryRegion(uint64_t address, uint64_t size);
[[nodiscard]] bool IsMeta(uint64_t address);
[[nodiscard]] bool IsMetaCleared(uint64_t address, uint32_t slice);
[[nodiscard]] bool ClearMeta(uint64_t address);
[[nodiscard]] bool TouchMeta(uint64_t address, uint32_t slice, bool is_clear);
void UnmapMemory(uint64_t address, uint64_t size);
void ProcessDownloadImages();
void RunGarbageCollector();
private:
enum class TransferDirection { Upload, Download };
struct ColorTransferPlan;
struct DownloadPlan;
struct Slot {
std::shared_ptr<Image> image;
uint32_t generation = 1;
};
struct MetaDataInfo {
uint32_t clear_mask = 0;
};
struct OverlapResult {
ImageId image;
int32_t mip = -1;
int32_t layer = -1;
};
using ImageOwnerIndex = MultiRangePageOwnerIndex<ImageId>;
[[nodiscard]] Image& ResolveImage(ImageId id);
[[nodiscard]] const Image& ResolveImage(ImageId id) const;
[[nodiscard]] std::shared_ptr<Image> ResolveOwner(ImageId id) const;
[[nodiscard]] ImageId InsertImage(const ImageInfo& info);
[[nodiscard]] ImageId GetNullImage(const ImageDesc& desc);
void RegisterImage(ImageId id);
void UnregisterImage(ImageId id);
void DeleteImage(ImageId id);
void DeleteImages(std::span<const ImageId> ids, std::optional<ImageId> native_source = {});
void RetainImage(CommandBuffer& command, ImageId id);
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 TrackImageDownloadLocked(ImageId id, Image& image);
[[nodiscard]] static bool SameBacking(const ImageInfo& cached, const ImageInfo& requested,
bool exact_format);
[[nodiscard]] static BindingType UploadBinding(const Image& image);
[[nodiscard]] bool SafeToDownload(const Image& image);
[[nodiscard]] std::vector<ImageId> FindImagesInRegion(uint64_t address, uint64_t size,
bool page_overlap) const;
[[nodiscard]] OverlapResult ResolveOverlap(const ImageInfo& requested, BindingType binding,
ImageId cached, ImageId merged);
[[nodiscard]] ImageId ResolveDepthOverlap(const ImageInfo& requested, BindingType binding,
ImageId cached);
[[nodiscard]] ImageId ExpandImage(const ImageInfo& info, ImageId source);
void RefreshImage(ImageId id, const ImageDesc& desc);
void InitializeImage(ImageId id, const ImageDesc& desc);
[[nodiscard]] ColorTransferPlan BuildColorTransfer(const Image& image, BindingType binding,
TransferDirection direction) const;
[[nodiscard]] DownloadPlan BuildDownload(const Image& image) const;
void UploadImage(Image& image, const ImageDesc& desc, Buffer& source, uint64_t source_offset);
void DownloadImageData(Image& image, Buffer& destination, uint64_t destination_offset,
DownloadPlan plan);
void DownloadDepth(Image& image, Buffer& destination, uint64_t destination_offset);
void CommitGpuWrite(Image& image);
void PrepareImageCopy(Image& image);
void RefreshCopySource(ImageId id);
[[nodiscard]] bool CopyD16(Image& destination, Image& source);
void CopyImage(ImageId destination, ImageId source);
void AssociateStencil(ImageId depth, GuestRange stencil);
void AssociateStencilLocked(ImageId depth, GuestRange stencil);
void CopyImageMip(ImageId destination, ImageId source, uint32_t mip, uint32_t layer);
void ValidateImageDesc(const ImageDesc& desc) const;
void InvalidateCpuAliases(uint64_t address, uint64_t size);
void ClearGpuModified(ImageId id);
[[nodiscard]] bool SynchronizeImageToBuffer(ImageId id);
void DownloadImage(ImageId id);
[[nodiscard]] bool TryDownloadImage(ImageId id);
[[nodiscard]] std::pair<uint8_t*, uint64_t> MapDownload(uint64_t size, uint64_t alignment);
void QueueDownload(GuestRange range, StreamBuffer& download, uint8_t* mapped, uint64_t offset);
GraphicContext& m_graphics;
CommandScheduler& m_scheduler;
TrackingSpinLock m_lock;
PageManager& m_page_manager;
BlitHelper m_blit_helper;
std::unique_ptr<TileManager> m_tiler;
BufferCache& m_buffer_cache;
ResourceMutex& m_resource_mutex;
std::vector<Slot> m_slots;
std::vector<uint32_t> m_free_slots;
ImageOwnerIndex m_image_owner_index;
std::map<vk::Format, ImageId> m_null_images;
Common::LeastRecentlyUsedCache<ImageId, uint64_t> m_lru_cache;
std::set<ImageId> m_download_images;
std::map<uint64_t, MetaDataInfo> m_surface_metas;
uint64_t m_total_used_memory = 0;
uint64_t m_trigger_gc_memory = 0;
uint64_t m_pressure_gc_memory = 1536ull * 1024 * 1024;
uint64_t m_critical_gc_memory = 3ull * 1024 * 1024 * 1024;
uint64_t m_gc_tick = 0;
bool m_readback_linear_images = false;
friend struct TextureCacheTestAccess;
friend class RenderExecutor;
};
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_TEXTURECACHE_H_
@@ -7,15 +7,12 @@
#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/debug.h" #include "graphics/host_gpu/renderer/debug.h"
#include "graphics/host_gpu/renderer/descriptorCache.h" #include "graphics/host_gpu/renderer/image/textureCommon.h"
#include "graphics/host_gpu/renderer/framebufferCache.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/transfer.h"
#include "graphics/host_gpu/vulkanCommon.h" #include "graphics/host_gpu/vulkanCommon.h"
#include "graphics/presentation/displayBuffer.h"
#include <algorithm> #include <algorithm>
#include <atomic> #include <atomic>
@@ -25,15 +22,15 @@ namespace Libs::Graphics {
static std::atomic<uint32_t> g_render_color_log_count = 0; static std::atomic<uint32_t> g_render_color_log_count = 0;
// NOLINTNEXTLINE(readability-function-cognitive-complexity) // NOLINTNEXTLINE(readability-function-cognitive-complexity)
void ResolveRenderColorTarget(uint64_t submit_id, CommandBuffer* buffer, const HW::Context& hw, void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandBuffer& buffer,
RenderColorInfo* r, uint32_t render_target_slice_offset, RenderColorInfo& r,
uint32_t render_target_slot, bool ignore_target_mask, uint32_t render_target_slice_offset,
bool reuse_existing_render_texture) { uint32_t render_target_slot, bool ignore_target_mask,
bool exact_format) {
KYTY_PROFILER_FUNCTION(); KYTY_PROFILER_FUNCTION();
const auto& hw = buffer.GetRegisters();
EXIT_IF(r == nullptr); const auto rt_slot = (render_target_slot == UINT32_MAX ? render_target_first_bound_slot(buffer)
const auto rt_slot = (render_target_slot == UINT32_MAX ? render_target_first_bound_slot(hw)
: render_target_slot); : render_target_slot);
const auto& rt = hw.GetRenderTarget(rt_slot); const auto& rt = hw.GetRenderTarget(rt_slot);
auto mask = render_target_mask_slot(hw.GetRenderTargetMask(), rt_slot); auto mask = render_target_mask_slot(hw.GetRenderTargetMask(), rt_slot);
@@ -41,8 +38,8 @@ void ResolveRenderColorTarget(uint64_t submit_id, CommandBuffer* buffer, const H
mask = 0x0f; mask = 0x0f;
} }
r->target_slot = rt_slot; r.target_slot = rt_slot;
r->export_mapping = {}; r.export_mapping = {};
if (rt.base.addr == 0 || mask == 0) { if (rt.base.addr == 0 || mask == 0) {
if (graphics_debug_dump_enabled()) { if (graphics_debug_dump_enabled()) {
@@ -58,51 +55,43 @@ void ResolveRenderColorTarget(uint64_t submit_id, CommandBuffer* buffer, const H
} }
// No color output // No color output
r->type = RenderColorType::NoColorOutput; r.type = RenderColorType::NoColorOutput;
r->base_addr = 0; r.desc = {};
r->vulkan_buffer = nullptr; r.base_addr = 0;
r->vulkan_view = nullptr; r.image_id = {};
r->format = vk::Format::eUndefined; r.image_view = nullptr;
r->extent = {}; r.format = vk::Format::eUndefined;
r->base_mip_level = 0; r.extent = {};
r->buffer_size = 0; r.base_mip_level = 0;
r->color_clear_enable = false; r.base_array_layer = 0;
r->color_clear_value = {}; r.buffer_size = 0;
r.samples = 1;
r.export_mapping = {};
r.color_clear_enable = false;
r.color_clear_value = {};
return; return;
} }
const bool msaa_compat = const auto samples = render_sample_count(rt.attrib.num_fragments);
color_msaa_single_sample_compatible(rt.attrib.num_samples, rt.attrib.num_fragments); if (samples == 0 || rt.attrib.num_samples != rt.attrib.num_fragments) {
if (!msaa_compat && (rt.attrib.num_samples != 0 || rt.attrib.num_fragments != 0)) { EXIT("unsupported render-target sample configuration: samples=%u fragments=%u\n",
EXIT("multisampled render targets are unsupported\n"); rt.attrib.num_samples, rt.attrib.num_fragments);
} }
const auto view = ResolveTargetViewInfo( const auto view = ResolveTargetViewInfo(
rt.view.base_array_slice_index, rt.view.last_array_slice_index, render_target_slice_offset); rt.view.base_array_slice_index, rt.view.last_array_slice_index, render_target_slice_offset);
switch (view.type) { switch (view.type) {
case TargetViewType::Image2D: break; case TargetViewType::Image2D:
case TargetViewType::Image2DArray: case TargetViewType::Image2DArray: break;
EXIT("layered render-target views are unsupported: base=%u count=%u\n", view.base_layer,
view.layer_count);
case TargetViewType::Unsupported: case TargetViewType::Unsupported:
EXIT("invalid render-target view: base=%u last=%u draw_offset=%u\n", EXIT("invalid render-target view: base=%u last=%u draw_offset=%u\n",
rt.view.base_array_slice_index, rt.view.last_array_slice_index, rt.view.base_array_slice_index, rt.view.last_array_slice_index,
render_target_slice_offset); render_target_slice_offset);
} }
r->base_array_layer = view.base_layer; r.base_array_layer = view.base_layer;
const uint32_t levels = rt.attrib2.num_mip_levels + 1u; const uint32_t levels = rt.attrib2.num_mip_levels + 1u;
if (levels == 0 || levels > 16 || rt.view.current_mip_level >= levels) { if (levels == 0 || levels > 16 || rt.view.current_mip_level >= levels) {
EXIT("unsupported render-target mip range: current=%u levels=%u\n", EXIT("unsupported render-target mip range: current=%u levels=%u\n",
rt.view.current_mip_level, levels); rt.view.current_mip_level, levels);
} }
if (msaa_compat) {
static std::atomic<uint32_t> logged_fragments = 0;
const uint32_t bit = 1u << rt.attrib.num_fragments;
if ((logged_fragments.fetch_or(bit, std::memory_order_relaxed) & bit) == 0) {
LOGF("RenderColorTarget: compatibility: rendering PS5 %ux samples/fragments as "
"single-sample\n",
bit);
}
}
if (graphics_debug_dump_enabled()) { if (graphics_debug_dump_enabled()) {
static std::atomic_uint log_count = 0; static std::atomic_uint log_count = 0;
const auto log_id = log_count.fetch_add(1, std::memory_order_relaxed); const auto log_id = log_count.fetch_add(1, std::memory_order_relaxed);
@@ -122,8 +111,8 @@ void ResolveRenderColorTarget(uint64_t submit_id, CommandBuffer* buffer, const H
// SRGB clear words are still encoded as normalized component values. // SRGB clear words are still encoded as normalized component values.
// Fast color clears are metadata driven and must be handled explicitly when // Fast color clears are metadata driven and must be handled explicitly when
// that metadata path is implemented; render-pass load must preserve contents. // that metadata path is implemented; render-pass load must preserve contents.
r->color_clear_enable = false; r.color_clear_enable = false;
r->color_clear_value = {}; r.color_clear_value = {};
uint32_t width = 0; uint32_t width = 0;
uint32_t height = 0; uint32_t height = 0;
@@ -144,6 +133,9 @@ void ResolveRenderColorTarget(uint64_t submit_id, CommandBuffer* buffer, const H
if (!tile && levels > 1) { if (!tile && levels > 1) {
EXIT("linear mipmapped render targets are unsupported\n"); EXIT("linear mipmapped render targets are unsupported\n");
} }
if (samples > 1 && (!tile || levels != 1)) {
EXIT("multisampled render targets require a single-mip tiled surface\n");
}
width = rt.attrib2.width + 1; width = rt.attrib2.width + 1;
height = rt.attrib2.height + 1; height = rt.attrib2.height + 1;
@@ -156,13 +148,13 @@ void ResolveRenderColorTarget(uint64_t submit_id, CommandBuffer* buffer, const H
if (standard64 && if (standard64 &&
(rt.attrib3.dimension != 1 || rt.attrib3.depth != 0 || levels != 1 || (rt.attrib3.dimension != 1 || rt.attrib3.depth != 0 || levels != 1 ||
rt.view.current_mip_level != 0 || view.base_layer != 0 || view.image_layers != 1 || rt.view.current_mip_level != 0 || view.base_layer != 0 || view.image_layers != 1 ||
rt.attrib.num_samples != 0 || rt.attrib.num_fragments != 0 || bytes_per_element != 4 || samples != 1 || bytes_per_element != 4 || rt.pitch.pitch_div8_minus1 != 0 ||
rt.pitch.pitch_div8_minus1 != 0 || (rt.base.addr & 0xffffu) != 0 || (rt.base.addr & 0xffffu) != 0 || rt.info.fmask_compression_enable ||
rt.info.fmask_compression_enable || rt.info.fmask_data_compression_disable || rt.info.fmask_data_compression_disable || rt.info.fmask_one_frag_mode ||
rt.info.fmask_one_frag_mode || rt.info.cmask_fast_clear_enable || rt.info.cmask_fast_clear_enable || rt.info.dcc_compression_enable ||
rt.info.dcc_compression_enable || rt.info.cmask_is_linear != 0 || rt.info.cmask_is_linear != 0 || rt.info.cmask_addr_type != 0 || rt.info.alt_tile_mode ||
rt.info.cmask_addr_type != 0 || rt.info.alt_tile_mode || rt.cmask.addr != 0 || rt.cmask.addr != 0 || rt.fmask.addr != 0 || rt.dcc_addr.addr != 0 ||
rt.fmask.addr != 0 || rt.dcc_addr.addr != 0 || rt.dcc.data_write_on_dcc_clear_to_reg)) { rt.dcc.data_write_on_dcc_clear_to_reg)) {
EXIT("unsupported Standard64KB render target: addr=0x%016" PRIx64 EXIT("unsupported Standard64KB render target: addr=0x%016" PRIx64
" dimension=%u depth=%u levels=%u layer=%u/%u samples=%u fragments=%u bpe=%u" " dimension=%u depth=%u levels=%u layer=%u/%u samples=%u fragments=%u bpe=%u"
" cmask=0x%016" PRIx64 " fmask=0x%016" PRIx64 " dcc=0x%016" PRIx64 "\n", " cmask=0x%016" PRIx64 " fmask=0x%016" PRIx64 " dcc=0x%016" PRIx64 "\n",
@@ -176,7 +168,7 @@ void ResolveRenderColorTarget(uint64_t submit_id, CommandBuffer* buffer, const H
pitch = standard64 pitch = standard64
? TileGetTexturePitch(Prospero::GpuEnumValue(Prospero::BufferFormat::k32Float), ? TileGetTexturePitch(Prospero::GpuEnumValue(Prospero::BufferFormat::k32Float),
width, levels, rt.attrib3.tile_mode) width, levels, rt.attrib3.tile_mode)
: TileGetRenderTargetPitch(width, bytes_per_element); : TileGetRenderTargetPitch(width, bytes_per_element, rt.attrib.num_fragments);
if (pitch == 0) { if (pitch == 0) {
EXIT("unsupported render-target pitch: width=%u bytes=%u\n", width, bytes_per_element); EXIT("unsupported render-target pitch: width=%u bytes=%u\n", width, bytes_per_element);
} }
@@ -184,26 +176,33 @@ void ResolveRenderColorTarget(uint64_t submit_id, CommandBuffer* buffer, const H
pitch = width; pitch = width;
} }
TileSizeOffset mip_sizes[16] {};
TilePaddedSize mip_padded[16] {};
if (tile) { if (tile) {
TileSizeAlign layout {}; TileSizeAlign layout {};
bool valid_layout = false; bool valid_layout = false;
if (standard64) { if (standard64) {
TileGetTextureSize(Prospero::GpuEnumValue(Prospero::BufferFormat::k32Float), width, TileGetTextureSize(Prospero::GpuEnumValue(Prospero::BufferFormat::k32Float), width,
height, pitch, levels, rt.attrib3.tile_mode, &layout, nullptr, height, pitch, levels, rt.attrib3.tile_mode, &layout, mip_sizes,
nullptr); mip_padded);
valid_layout = layout.size != 0 && layout.align == 65536; valid_layout = layout.size != 0 && layout.align == 65536;
} else { } else {
valid_layout = valid_layout =
levels == 1 levels == 1 ? TileGetRenderTargetSize(width, height, pitch, bytes_per_element,
? TileGetRenderTargetSize(width, height, pitch, bytes_per_element, &layout) layout, rt.attrib.num_fragments)
: TileGetRenderTargetMipLayout(width, height, pitch, bytes_per_element, levels, : TileGetRenderTargetMipLayout(width, height, pitch, bytes_per_element,
&layout, nullptr, nullptr); levels, layout, mip_sizes, mip_padded);
} }
if (!valid_layout) { if (!valid_layout) {
EXIT("unsupported render-target layout: %ux%u pitch=%u bytes=%u levels=%u\n", width, EXIT("unsupported render-target layout: %ux%u pitch=%u bytes=%u levels=%u\n", width,
height, pitch, bytes_per_element, levels); height, pitch, bytes_per_element, levels);
} }
size = layout.size; size = layout.size;
EXIT_IF(size > UINT32_MAX);
if (levels == 1) {
mip_sizes[0] = {static_cast<uint32_t>(size), 0, 0, 0, 0, 0};
mip_padded[0] = {pitch, height};
}
if (rt.slice.slice_div64_minus1 != 0 && if (rt.slice.slice_div64_minus1 != 0 &&
(static_cast<uint64_t>(rt.slice.slice_div64_minus1) + 1u) * 64u != size) { (static_cast<uint64_t>(rt.slice.slice_div64_minus1) + 1u) * 64u != size) {
EXIT("render-target slice span mismatch: encoded=0x%016" PRIx64 " derived=0x%016" PRIx64 EXIT("render-target slice span mismatch: encoded=0x%016" PRIx64 " derived=0x%016" PRIx64
@@ -211,7 +210,12 @@ void ResolveRenderColorTarget(uint64_t submit_id, CommandBuffer* buffer, const H
(static_cast<uint64_t>(rt.slice.slice_div64_minus1) + 1u) * 64u, size); (static_cast<uint64_t>(rt.slice.slice_div64_minus1) + 1u) * 64u, size);
} }
} else { } else {
size = static_cast<uint64_t>(pitch) * height * bytes_per_element; size = static_cast<uint64_t>(pitch) * height * bytes_per_element * samples;
if (size > UINT32_MAX) {
EXIT("linear render-target slice exceeds the supported layout size\n");
}
mip_sizes[0] = {static_cast<uint32_t>(size), 0, 0, 0, 0, 0};
mip_padded[0] = {pitch, height};
} }
if (size == 0 || size > UINT64_MAX / view.image_layers) { if (size == 0 || size > UINT64_MAX / view.image_layers) {
EXIT("render-target memory footprint is invalid\n"); EXIT("render-target memory footprint is invalid\n");
@@ -221,112 +225,69 @@ void ResolveRenderColorTarget(uint64_t submit_id, CommandBuffer* buffer, const H
EXIT("render-target backing range is invalid\n"); EXIT("render-target backing range is invalid\n");
} }
auto video_image = Presentation::DisplayBufferFind(rt.base.addr, true);
if (video_image.image != nullptr &&
!IsSupportedDisplayRenderTargetTileMode(rt.attrib3.tile_mode)) {
EXIT("unsupported display render-target tile mode: tile=%u expected=%u addr=0x%010" PRIx64
" backing_size=0x%016" PRIx64 " video_size=0x%016" PRIx64 "\n",
rt.attrib3.tile_mode, Prospero::GpuEnumValue(Prospero::TileMode::kRenderTarget),
rt.base.addr, backing_size, video_image.size);
}
bool render_to_texture = view.base_layer != 0 || video_image.image == nullptr;
if (!render_to_texture && (levels != 1 || rt.view.current_mip_level != 0)) {
EXIT("mipmapped display render targets are unsupported\n");
}
const vk::Extent2D view_extent = {std::max(width >> rt.view.current_mip_level, 1u), const vk::Extent2D view_extent = {std::max(width >> rt.view.current_mip_level, 1u),
std::max(height >> rt.view.current_mip_level, 1u)}; std::max(height >> rt.view.current_mip_level, 1u)};
auto decision_log_id = g_render_color_log_count.fetch_add(1); auto decision_log_id = g_render_color_log_count.fetch_add(1);
if (decision_log_id < 128 || !render_to_texture) { if (decision_log_id < 128) {
LOGF("RenderColorTarget: slot=%" PRIu32 " addr=0x%010" PRIx64 " size=0x%016" PRIx64 LOGF("RenderColorTarget: slot=%" PRIu32 " addr=0x%010" PRIx64 " size=0x%016" PRIx64
" extent=%ux%u view_mip=%u view_extent=%ux%u levels=%u pitch=%u" " extent=%ux%u view_mip=%u view_extent=%ux%u levels=%u pitch=%u"
" fmt=0x%08" PRIx32 " nfmt=0x%08" PRIx32 " order=0x%08" PRIx32 " fmt=0x%08" PRIx32 " nfmt=0x%08" PRIx32 " order=0x%08" PRIx32 " samples=%u tile=%s\n",
" tile=%s target=%s video_size=0x%016" PRIx64 " video_pitch=%" PRIu64 "\n",
rt_slot, rt.base.addr, backing_size, width, height, rt.view.current_mip_level, rt_slot, rt.base.addr, backing_size, width, height, rt.view.current_mip_level,
view_extent.width, view_extent.height, levels, pitch, rt.info.format, view_extent.width, view_extent.height, levels, pitch, rt.info.format,
rt.info.channel_type, rt.info.channel_order, tile ? "tiled" : "linear", rt.info.channel_type, rt.info.channel_order, samples, tile ? "tiled" : "linear");
render_to_texture ? "RenderTexture" : "DisplayBuffer", video_image.size,
video_image.pitch);
} }
if (render_to_texture) { TextureCache::ImageDesc desc {};
(void)reuse_existing_render_texture; desc.type = TextureCache::BindingType::RenderTarget;
RenderTargetInfo target {}; desc.info.data = {rt.base.addr, backing_size};
target.address = rt.base.addr; desc.info.pixel_format = target_format.format;
target.size = backing_size; desc.info.guest_format = ImageOps::RenderTargetTransferFormat(bytes_per_element);
target.format = target_format.format; desc.info.type = Prospero::ImageType::kColor2D;
target.width = width; desc.info.extent = {width, height, 1};
target.height = height; desc.info.resources = {levels, view.image_layers};
target.pitch = pitch; desc.info.pitch = pitch;
target.bytes_per_element = target_format.bytes_per_element; desc.info.bytes_per_block = bytes_per_element;
target.tile_mode = rt.attrib3.tile_mode; desc.info.samples = samples;
target.levels = levels; desc.info.tile_mode = rt.attrib3.tile_mode;
target.layers = view.image_layers; for (uint32_t level = 0; level < levels; level++) {
auto* texture_cache = g_render_ctx->GetTextureCache(); const auto level_offset =
auto* buffer_vulkan = mip_sizes[level].src_size != 0 ? mip_sizes[level].src_offset : mip_sizes[level].offset;
texture_cache->FindRenderTarget(buffer, g_render_ctx->GetGraphicCtx(), target); const auto level_size =
r->type = RenderColorType::RenderTexture; static_cast<uint64_t>(mip_sizes[level].src_size != 0 ? mip_sizes[level].src_size
r->base_addr = rt.base.addr; : mip_sizes[level].size) *
r->vulkan_buffer = buffer_vulkan; view.image_layers;
r->vulkan_view = texture_cache->GetRenderTargetAttachmentView( desc.info.mip_layout[level] = {
g_render_ctx->GetGraphicCtx(), buffer_vulkan, target.format, rt.view.current_mip_level, level_offset,
view.base_layer, view.layer_count); level_size,
r->format = target.format; mip_padded[level].width,
r->extent = view_extent; mip_padded[level].height,
r->base_mip_level = rt.view.current_mip_level; };
r->buffer_size = backing_size;
r->export_mapping = target_format.export_mapping;
} else {
const auto layout = static_cast<Prospero::ChannelLayout>(rt.info.format);
const auto type = static_cast<Prospero::ChannelType>(rt.info.channel_type);
const auto order = static_cast<Prospero::ChannelOrder>(rt.info.channel_order);
bool supported_display_format =
(layout == Prospero::ChannelLayout::k8_8_8_8 &&
(type == Prospero::ChannelType::kSrgb || type == Prospero::ChannelType::kUNorm) &&
(order == Prospero::ChannelOrder::kStandard ||
order == Prospero::ChannelOrder::kAlt)) ||
(layout == Prospero::ChannelLayout::k10_10_10_2 &&
type == Prospero::ChannelType::kUNorm &&
(order == Prospero::ChannelOrder::kStandard ||
order == Prospero::ChannelOrder::kAlt)) ||
(layout == Prospero::ChannelLayout::k16_16_16_16 &&
type == Prospero::ChannelType::kFloat &&
(order == Prospero::ChannelOrder::kStandard || order == Prospero::ChannelOrder::kAlt));
EXIT_NOT_IMPLEMENTED(!supported_display_format);
// Display buffer
if (video_image.size != size) {
LOGF("RenderColorTarget: display buffer size differs from render target span, "
"video_size=0x%016" PRIx64 " render_size=0x%016" PRIx64 "\n",
video_image.size, size);
}
EXIT_NOT_IMPLEMENTED(video_image.size < size);
EXIT_NOT_IMPLEMENTED(video_image.pitch != pitch);
r->type = RenderColorType::DisplayBuffer;
r->base_addr = rt.base.addr;
r->vulkan_buffer = video_image.image;
r->vulkan_view = video_image.image->image_view[VulkanImage::VIEW_DEFAULT];
r->format = video_image.image->format;
r->extent = video_image.image->extent;
r->base_mip_level = 0;
r->buffer_size = video_image.size;
r->export_mapping = target_format.export_mapping;
}
}
void MarkRenderTargetGpuWritten(const RenderColorInfo& target) {
const bool with_color = target.vulkan_buffer != nullptr;
if (with_color) {
if (target.type == RenderColorType::RenderTexture ||
target.type == RenderColorType::DisplayBuffer) {
g_render_ctx->GetTextureCache()->MarkGpuWritten(target.vulkan_buffer);
} else {
EXIT("unknown writable render-color resource type\n");
}
} }
desc.view_info.format = target_format.format;
desc.view_info.type =
view.layer_count == 1 ? vk::ImageViewType::e2D : vk::ImageViewType::e2DArray;
desc.view_info.aspect = vk::ImageAspectFlagBits::eColor;
desc.view_info.base_level = rt.view.current_mip_level;
desc.view_info.level_count = 1;
desc.view_info.base_layer = view.base_layer;
desc.view_info.layer_count = view.layer_count;
desc.view_info.usage = vk::ImageUsageFlagBits::eColorAttachment;
auto& texture_cache = m_context.GetTextureCache();
r.desc = std::move(desc);
r.image_id = texture_cache.FindImage(r.desc, exact_format);
r.type = RenderColorType::RenderTexture;
r.base_addr = rt.base.addr;
r.image_view = nullptr;
r.format = r.desc.view_info.format;
r.extent = view_extent;
r.base_mip_level = rt.view.current_mip_level;
r.buffer_size = backing_size;
r.samples = samples;
r.export_mapping = target_format.export_mapping;
r.color_clear_enable = false;
r.color_clear_value = {};
BindRenderTarget(r.image_id);
} }
} // namespace Libs::Graphics } // namespace Libs::Graphics
@@ -2,6 +2,7 @@
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_COLORRENDERTARGET_H_ #define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_COLORRENDERTARGET_H_
#include "graphics/guest_gpu/gpu_defs.h" #include "graphics/guest_gpu/gpu_defs.h"
#include "graphics/host_gpu/renderer/cache/textureCache.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"
@@ -9,23 +10,18 @@
namespace Libs::Graphics { namespace Libs::Graphics {
class CommandBuffer; class RenderCommandBuffer;
struct VulkanImage;
namespace HW {
class Context;
} // namespace HW
enum class RenderColorType { enum class RenderColorType {
NoColorOutput, NoColorOutput,
DisplayBuffer,
RenderTexture, RenderTexture,
}; };
struct RenderColorInfo { struct RenderColorInfo {
RenderColorType type = RenderColorType::NoColorOutput; RenderColorType type = RenderColorType::NoColorOutput;
VulkanImage* vulkan_buffer = nullptr; TextureCache::ImageDesc desc;
vk::ImageView vulkan_view = nullptr; ImageId image_id;
vk::ImageView image_view = nullptr;
vk::Format format = vk::Format::eUndefined; vk::Format format = vk::Format::eUndefined;
vk::Extent2D extent = {}; vk::Extent2D extent = {};
uint32_t base_mip_level = 0; uint32_t base_mip_level = 0;
@@ -33,18 +29,12 @@ struct RenderColorInfo {
uint64_t base_addr = 0; uint64_t base_addr = 0;
uint64_t buffer_size = 0; uint64_t buffer_size = 0;
uint32_t target_slot = 0; uint32_t target_slot = 0;
uint32_t samples = 1;
Prospero::ColorComponentMapping export_mapping; Prospero::ColorComponentMapping export_mapping;
bool color_clear_enable = false; bool color_clear_enable = false;
vk::ClearColorValue color_clear_value {}; vk::ClearColorValue color_clear_value {};
}; };
void ResolveRenderColorTarget(uint64_t submit_id, CommandBuffer* buffer, const HW::Context& hw,
RenderColorInfo* r, uint32_t render_target_slice_offset = 0,
uint32_t render_target_slot = UINT32_MAX,
bool ignore_target_mask = false,
bool reuse_existing_render_texture = false);
void MarkRenderTargetGpuWritten(const RenderColorInfo& target);
} // namespace Libs::Graphics } // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_COLORRENDERTARGET_H_ #endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_COLORRENDERTARGET_H_
@@ -0,0 +1,401 @@
#include "graphics/host_gpu/renderer/commandScheduler.h"
#include "common/assert.h"
#include "graphics/host_gpu/graphicContext.h"
#include <algorithm>
namespace Libs::Graphics {
static thread_local CommandScheduler* g_deferred_callback_scheduler = nullptr;
void CommandSlot::Reset() {
EXIT_IF(buffer == nullptr);
const auto result = buffer.reset(vk::CommandBufferResetFlagBits::eReleaseResources);
if (result != vk::Result::eSuccess) {
EXIT("failed to reset Vulkan command buffer: %s (%d)\n", VulkanToString(result).c_str(),
static_cast<int>(result));
}
}
CommandScheduler::CommandPool::~CommandPool() {
Destroy();
}
void CommandScheduler::CommandPool::Create(GraphicContext& graphics) {
EXIT_IF(m_pool != nullptr || m_graphics != nullptr ||
graphics.queue_family == static_cast<uint32_t>(-1));
m_graphics = &graphics;
vk::CommandPoolCreateInfo create {};
create.sType = vk::StructureType::eCommandPoolCreateInfo;
create.queueFamilyIndex = graphics.queue_family;
create.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer;
const auto result = graphics.device.createCommandPool(&create, nullptr, &m_pool);
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess || m_pool == nullptr);
}
CommandSlot* CommandScheduler::CommandPool::CreateSlot() {
EXIT_IF(m_graphics == nullptr);
auto& graphics = *m_graphics;
vk::CommandBufferAllocateInfo allocate {};
allocate.sType = vk::StructureType::eCommandBufferAllocateInfo;
allocate.commandPool = m_pool;
allocate.level = vk::CommandBufferLevel::ePrimary;
allocate.commandBufferCount = 1;
vk::CommandBuffer buffer = nullptr;
EXIT_IF(graphics.device.allocateCommandBuffers(&allocate, &buffer) != vk::Result::eSuccess);
vk::FenceCreateInfo fence_create {};
fence_create.sType = vk::StructureType::eFenceCreateInfo;
fence_create.flags = vk::FenceCreateFlagBits::eSignaled;
vk::Fence fence = nullptr;
if (graphics.device.createFence(&fence_create, nullptr, &fence) != vk::Result::eSuccess) {
graphics.device.freeCommandBuffers(m_pool, 1, &buffer);
EXIT("failed to create command-buffer fence\n");
}
auto& slot = m_slots.emplace_back();
slot.pool_mutex = &m_mutex;
slot.id = static_cast<uint32_t>(m_slots.size() - 1);
slot.buffer = buffer;
slot.fence = fence;
return &slot;
}
CommandSlot* CommandScheduler::CommandPool::Allocate(GraphicContext& graphics) {
Common::LockGuard lock(m_mutex);
if (m_pool == nullptr) {
Create(graphics);
}
EXIT_IF(m_graphics != &graphics);
auto found = std::ranges::find_if(m_slots, [](const auto& slot) { return !slot.busy; });
auto* slot = found != m_slots.end() ? &*found : CreateSlot();
slot->busy = true;
slot->Reset();
return slot;
}
void CommandScheduler::CommandPool::Destroy() {
Common::LockGuard lock(m_mutex);
if (m_pool == nullptr) {
return;
}
EXIT_IF(std::ranges::any_of(m_slots, [](const auto& slot) { return slot.busy; }));
EXIT_IF(m_graphics == nullptr);
for (const auto& slot: m_slots) {
m_graphics->device.destroyFence(slot.fence, nullptr);
}
m_graphics->device.destroyCommandPool(m_pool, nullptr);
m_slots.clear();
m_pool = nullptr;
m_graphics = nullptr;
}
bool CommandScheduler::InDeferredOperation() noexcept {
return g_deferred_callback_scheduler != nullptr;
}
CommandScheduler::CommandScheduler(RenderContext& context, GraphicContext& graphics)
: m_master(graphics), m_context(context), m_graphics(graphics),
m_priority_thread([this](std::stop_token stop) { PriorityOperationsThread(stop); }) {}
CommandScheduler::~CommandScheduler() {
Shutdown();
}
void CommandScheduler::Shutdown() {
{
std::unique_lock lock(m_operation_mutex);
if (m_operation_state == OperationState::Closed) {
return;
}
if (g_deferred_callback_scheduler == this) {
EXIT_IF(m_operation_state == OperationState::Open);
// A priority callback cannot join its own runner, while a normal callback can be
// executing inside the shutdown owner's final PopPendingOperations. The owning
// thread will finish shutdown after this callback returns.
return;
}
if (m_operation_state == OperationState::Draining) {
m_operation_available.wait(
lock, [this] { return m_operation_state == OperationState::Closed; });
return;
}
m_operation_state = OperationState::Draining;
}
if (Active() && m_recording) {
Finish();
}
DrainPriorityOperations();
m_priority_thread.request_stop();
m_operation_available.notify_all();
if (m_priority_thread.joinable()) {
m_priority_thread.join();
}
{
std::lock_guard lock(m_operation_mutex);
EXIT_IF(!m_pending_operations.empty() || !m_priority_operations.empty() ||
m_priority_active);
m_operation_state = OperationState::Closed;
}
m_operation_available.notify_all();
}
void CommandScheduler::Begin(HW::Context& registers, HW::UserConfig& user_config,
HW::Shader& shaders) {
{
std::lock_guard lock(m_operation_mutex);
EXIT_IF(m_operation_state != OperationState::Open);
}
m_registers = &registers;
m_user_config = &user_config;
m_shaders = &shaders;
if (!Active()) {
for (auto& buffer: m_buffers) {
buffer = std::make_unique<RenderCommandBuffer>(*this);
}
m_current = 0;
}
BindCurrent();
if (!m_recording) {
Current().Begin();
m_recording = true;
}
}
void CommandScheduler::BeginRendering(const RenderState& state) {
Current().BeginRendering(state);
}
void CommandScheduler::EndRendering() {
if (Active() && m_recording) {
Current().EndRendering();
}
}
void CommandScheduler::Flush() {
SubmitInfo submit;
Flush(submit);
}
void CommandScheduler::Flush(SubmitInfo& submit) {
SubmitCurrent(submit);
BeginNext();
}
CommandBuffer& CommandScheduler::FlushAndGetSubmitted() {
SubmitInfo submit;
auto& submitted = SubmitCurrent(submit);
BeginNext();
return submitted;
}
void CommandScheduler::Finish() {
CheckActive();
const auto tick = CurrentTick();
if (m_recording) {
SubmitInfo submit;
SubmitCurrent(submit);
}
for (auto& buffer: m_buffers) {
buffer->WaitForFenceAndReset();
}
m_master.Wait(tick);
PopPendingOperations();
BindCurrent();
Current().Begin();
m_recording = true;
}
void CommandScheduler::FinishCurrent() {
SubmitInfo submit;
auto& submitted = SubmitCurrent(submit);
submitted.WaitForFenceAndReset();
m_master.Refresh();
PopPendingOperations();
submitted.Begin();
m_recording = true;
}
void CommandScheduler::Wait(uint64_t tick) {
CheckActive();
EXIT_IF(tick > CurrentTick());
if (tick >= CurrentTick()) {
// A stream-buffer wrap can wait while a draw is being prepared through a reference to
// Current(). Recycle the same command object so that reference remains valid.
FinishCurrent();
return;
}
m_master.Wait(tick);
PopPendingOperations();
}
void CommandScheduler::PopPendingOperations() {
m_master.Refresh();
for (;;) {
Common::UniqueFunction<void> callback;
{
std::lock_guard lock(m_operation_mutex);
if (m_pending_operations.empty() ||
!m_master.IsFree(m_pending_operations.front().tick)) {
return;
}
callback = std::move(m_pending_operations.front().callback);
m_pending_operations.pop();
}
RunOperation(std::move(callback));
}
}
void CommandScheduler::DeferOperation(Common::UniqueFunction<void>&& operation) {
CheckActive();
EXIT_IF(!operation);
std::unique_lock lock(m_operation_mutex);
if (m_operation_state == OperationState::Open) {
m_pending_operations.push({std::move(operation), CurrentTick()});
return;
}
if (g_deferred_callback_scheduler == this) {
lock.unlock();
operation();
return;
}
m_operation_available.wait(lock,
[this] { return m_operation_state == OperationState::Closed; });
lock.unlock();
operation();
}
void CommandScheduler::DeferPriorityOperation(Common::UniqueFunction<void>&& operation) {
CheckActive();
EXIT_IF(!operation);
std::unique_lock lock(m_operation_mutex);
if (m_operation_state == OperationState::Open) {
m_priority_operations.push({std::move(operation), CurrentTick()});
lock.unlock();
m_operation_available.notify_one();
return;
}
if (g_deferred_callback_scheduler == this) {
lock.unlock();
operation();
return;
}
m_operation_available.wait(lock,
[this] { return m_operation_state == OperationState::Closed; });
lock.unlock();
operation();
}
void CommandScheduler::PriorityOperationsThread(std::stop_token stop) {
while (!stop.stop_requested()) {
PendingOperation operation;
{
std::unique_lock lock(m_operation_mutex);
m_operation_available.wait(lock, [this, &stop] {
return stop.stop_requested() || !m_priority_operations.empty();
});
if (stop.stop_requested()) {
return;
}
operation = std::move(m_priority_operations.front());
m_priority_operations.pop();
m_priority_active = true;
m_priority_active_tick = operation.tick;
}
m_master.Wait(operation.tick);
if (!stop.stop_requested()) {
RunOperation(std::move(operation.callback));
}
{
std::lock_guard lock(m_operation_mutex);
m_priority_active = false;
m_priority_active_tick = 0;
}
m_operation_available.notify_all();
}
}
void CommandScheduler::DrainPriorityOperations() {
EXIT_IF(g_deferred_callback_scheduler == this);
std::unique_lock lock(m_operation_mutex);
m_operation_available.wait(
lock, [this] { return m_priority_operations.empty() && !m_priority_active; });
}
void CommandScheduler::WaitPriorityOperations(uint64_t tick) {
EXIT_IF(g_deferred_callback_scheduler == this);
std::unique_lock lock(m_operation_mutex);
m_operation_available.wait(lock, [this, tick] {
const bool active_before_or_at = m_priority_active && m_priority_active_tick <= tick;
const bool queued_before_or_at =
!m_priority_operations.empty() && m_priority_operations.front().tick <= tick;
return !active_before_or_at && !queued_before_or_at;
});
}
void CommandScheduler::RunOperation(Common::UniqueFunction<void>&& operation) {
auto* previous = g_deferred_callback_scheduler;
g_deferred_callback_scheduler = this;
operation();
g_deferred_callback_scheduler = previous;
}
bool CommandScheduler::IsFree(uint64_t tick) {
if (m_master.IsFree(tick)) {
return true;
}
m_master.Refresh();
return m_master.IsFree(tick);
}
CommandSlot* CommandScheduler::AllocateCommandBuffer() {
return m_command_pool.Allocate(m_graphics);
}
uint64_t CommandScheduler::NextSubmitSequence() noexcept {
return m_submit_sequence.fetch_add(1, std::memory_order_relaxed) + 1;
}
void CommandScheduler::CheckActive() const {
EXIT_IF(!Active() || m_current >= BufferCount);
}
RenderCommandBuffer& CommandScheduler::Current() const {
CheckActive();
EXIT_IF(m_buffers[m_current] == nullptr);
return *m_buffers[m_current];
}
void CommandScheduler::BindCurrent() const {
EXIT_IF(m_registers == nullptr || m_user_config == nullptr || m_shaders == nullptr);
Current().Bind(*m_registers, *m_user_config, *m_shaders);
}
CommandBuffer& CommandScheduler::SubmitCurrent(SubmitInfo& submit) {
CheckActive();
EXIT_IF(!m_recording);
auto& submitted = Current();
submitted.End();
const auto signal_tick = m_master.NextTick();
submit.AddSignal(m_master.Handle(), signal_tick);
submitted.Execute(submit);
m_recording = false;
return submitted;
}
void CommandScheduler::BeginNext() {
EXIT_IF(m_recording);
m_current = (m_current + 1) % BufferCount;
Current().WaitForFenceAndReset();
PopPendingOperations();
BindCurrent();
Current().Begin();
m_recording = true;
}
} // namespace Libs::Graphics
@@ -0,0 +1,127 @@
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_COMMANDSCHEDULER_H_
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_COMMANDSCHEDULER_H_
#include "common/common.h"
#include "common/uniqueFunction.h"
#include "graphics/host_gpu/renderer/masterSemaphore.h"
#include "graphics/host_gpu/renderer/render.h"
#include <array>
#include <atomic>
#include <condition_variable>
#include <deque>
#include <memory>
#include <mutex>
#include <queue>
#include <thread>
namespace Libs::Graphics {
struct CommandSlot {
Common::Mutex* pool_mutex = nullptr;
uint32_t id = 0;
vk::CommandBuffer buffer = nullptr;
vk::Fence fence = nullptr;
bool busy = false;
void Reset();
};
class CommandScheduler {
public:
static constexpr int BufferCount = 8;
CommandScheduler(RenderContext& context, GraphicContext& graphics);
~CommandScheduler();
KYTY_CLASS_NO_COPY(CommandScheduler);
void Begin(HW::Context& registers, HW::UserConfig& user_config, HW::Shader& shaders);
void BeginRendering(const RenderState& state);
void EndRendering();
void Flush();
void Flush(SubmitInfo& submit);
CommandBuffer& FlushAndGetSubmitted();
void Finish();
void FinishCurrent();
// Deferred callbacks can observe an externally owned drain, but cannot initiate shutdown:
// the priority runner cannot join itself.
void Shutdown();
void Wait(uint64_t tick);
void PopPendingOperations();
void DrainPriorityOperations();
void WaitPriorityOperations(uint64_t tick);
void DeferOperation(Common::UniqueFunction<void>&& operation);
void DeferPriorityOperation(Common::UniqueFunction<void>&& operation);
[[nodiscard]] static bool InDeferredOperation() noexcept;
[[nodiscard]] bool Active() const noexcept { return m_current >= 0; }
void CheckActive() const;
RenderCommandBuffer& Current() const;
[[nodiscard]] uint64_t CurrentTick() const noexcept { return m_master.CurrentTick(); }
[[nodiscard]] bool IsFree(uint64_t tick);
[[nodiscard]] RenderContext& Context() const noexcept { return m_context; }
[[nodiscard]] GraphicContext& Graphics() const noexcept { return m_graphics; }
private:
class CommandPool {
public:
CommandPool() = default;
~CommandPool();
KYTY_CLASS_NO_COPY(CommandPool);
CommandSlot* Allocate(GraphicContext& graphics);
private:
void Create(GraphicContext& graphics);
CommandSlot* CreateSlot();
void Destroy();
GraphicContext* m_graphics = nullptr;
Common::Mutex m_mutex;
vk::CommandPool m_pool = nullptr;
std::deque<CommandSlot> m_slots;
};
enum class OperationState { Open, Draining, Closed };
struct PendingOperation {
Common::UniqueFunction<void> callback;
uint64_t tick = 0;
};
void BindCurrent() const;
CommandBuffer& SubmitCurrent(SubmitInfo& submit);
void BeginNext();
void PriorityOperationsThread(std::stop_token stop);
void RunOperation(Common::UniqueFunction<void>&& operation);
[[nodiscard]] CommandSlot* AllocateCommandBuffer();
[[nodiscard]] uint64_t NextSubmitSequence() noexcept;
MasterSemaphore m_master;
RenderContext& m_context;
GraphicContext& m_graphics;
CommandPool m_command_pool;
std::array<std::unique_ptr<RenderCommandBuffer>, BufferCount> m_buffers;
std::queue<PendingOperation> m_pending_operations;
std::queue<PendingOperation> m_priority_operations;
std::mutex m_operation_mutex;
std::condition_variable m_operation_available;
std::jthread m_priority_thread;
bool m_priority_active = false;
uint64_t m_priority_active_tick = 0;
OperationState m_operation_state = OperationState::Open;
int m_current = -1;
bool m_recording = false;
HW::Context* m_registers = nullptr;
HW::UserConfig* m_user_config = nullptr;
HW::Shader* m_shaders = nullptr;
std::atomic<uint64_t> m_submit_sequence = 0;
friend class CommandBuffer;
};
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_COMMANDSCHEDULER_H_
+121 -393
View File
@@ -8,65 +8,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/image/imageView.h"
#include "graphics/host_gpu/renderer/framebufferCache.h" #include "graphics/host_gpu/renderer/pipeline/descriptorCache.h"
#include "graphics/host_gpu/renderer/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/transfer.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/presentation/window.h"
#include <algorithm> #include <algorithm>
#include <array> #include <bit>
#include <atomic>
#include <cstring> #include <cstring>
#include <memory> #include <memory>
namespace Libs::Graphics { namespace Libs::Graphics {
static std::atomic<uint64_t> g_command_buffer_submit_seq = 0;
static void RequireValidQueueId(int queue_id) {
EXIT_IF(queue_id < 0 || queue_id >= GraphicContext::QUEUES_NUM);
}
static void ResetNativeCommandBuffer(vk::CommandBuffer buffer) {
EXIT_IF(buffer == nullptr);
const auto result = buffer.reset(vk::CommandBufferResetFlagBits::eReleaseResources);
if (result != vk::Result::eSuccess) {
EXIT("failed to reset Vulkan command buffer: %s (%d)\n", VulkanToString(result).c_str(),
static_cast<int>(result));
}
}
class CommandPool {
public:
CommandPool() = default;
~CommandPool() // NOLINT
{
// TODO(): check if destructor is called from std::_Exit()
// DeleteAll();
}
KYTY_CLASS_NO_COPY(CommandPool);
VulkanCommandPool* GetPool(int queue_id) {
RequireValidQueueId(queue_id);
if (m_pools[queue_id] == nullptr) {
Create(queue_id);
}
return m_pools[queue_id];
}
void DeleteAll();
private:
void Create(int queue_id);
std::array<VulkanCommandPool*, GraphicContext::QUEUES_NUM> m_pools {};
};
RenderContext* g_render_ctx = nullptr;
static thread_local CommandPool g_command_pool;
FenceResourceRetainer::~FenceResourceRetainer() { FenceResourceRetainer::~FenceResourceRetainer() {
if (!m_resources.empty()) { if (!m_resources.empty()) {
@@ -89,176 +42,46 @@ void FenceResourceRetainer::ReleaseAfterFence() noexcept {
m_resources.clear(); m_resources.clear();
} }
void GraphicsRenderInit() { CommandBuffer::CommandBuffer(CommandScheduler& scheduler)
EXIT_IF(g_render_ctx != nullptr); : m_context(scheduler.Context()), m_scheduler(scheduler), m_graphics(scheduler.Graphics()),
m_slot(scheduler.AllocateCommandBuffer()) {}
g_render_ctx = new RenderContext; CommandBuffer::~CommandBuffer() {
} Release();
void GraphicsRenderReleaseThreadCommandPools() {
if (g_render_ctx != nullptr) {
g_command_pool.DeleteAll();
}
}
void GraphicsRenderCreateContext() {
EXIT_IF(g_render_ctx == nullptr);
g_render_ctx->SetGraphicCtx(WindowGetGraphicContext());
}
void CommandPool::Create(int queue_id) {
RequireValidQueueId(queue_id);
EXIT_IF(g_render_ctx == nullptr);
auto* ctx = g_render_ctx->GetGraphicCtx();
auto*& pool = m_pools[queue_id];
EXIT_IF(pool != nullptr);
EXIT_IF(ctx == nullptr);
EXIT_IF(ctx->device == nullptr);
EXIT_IF(ctx->queues[queue_id].family == static_cast<uint32_t>(-1));
pool = new VulkanCommandPool;
vk::CommandPoolCreateInfo pool_info {};
pool_info.sType = vk::StructureType::eCommandPoolCreateInfo;
pool_info.pNext = nullptr;
pool_info.queueFamilyIndex = ctx->queues[queue_id].family;
pool_info.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer;
const auto result = ctx->device.createCommandPool(&pool_info, nullptr, &pool->pool);
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess || pool->pool == nullptr);
pool->buffers_count = 8;
pool->buffers = std::make_unique<vk::CommandBuffer[]>(pool->buffers_count);
pool->fences = std::make_unique<vk::Fence[]>(pool->buffers_count);
pool->semaphores = std::make_unique<vk::Semaphore[]>(pool->buffers_count);
pool->busy = std::make_unique<bool[]>(pool->buffers_count);
vk::CommandBufferAllocateInfo alloc_info {};
alloc_info.sType = vk::StructureType::eCommandBufferAllocateInfo;
alloc_info.commandPool = pool->pool;
alloc_info.level = vk::CommandBufferLevel::ePrimary;
alloc_info.commandBufferCount = pool->buffers_count;
if (ctx->device.allocateCommandBuffers(&alloc_info, pool->buffers.get()) !=
vk::Result::eSuccess) {
EXIT("Can't allocate command buffers");
}
for (uint32_t i = 0; i < pool->buffers_count; i++) {
pool->busy[i] = false;
vk::FenceCreateInfo fence_info {};
fence_info.sType = vk::StructureType::eFenceCreateInfo;
fence_info.pNext = nullptr;
fence_info.flags = vk::FenceCreateFlagBits::eSignaled;
if (ctx->device.createFence(&fence_info, nullptr, &pool->fences[i]) !=
vk::Result::eSuccess) {
EXIT("Can't create fence");
}
vk::SemaphoreCreateInfo semaphore_info {};
semaphore_info.sType = vk::StructureType::eSemaphoreCreateInfo;
semaphore_info.pNext = nullptr;
semaphore_info.flags = {};
if (ctx->device.createSemaphore(&semaphore_info, nullptr, &pool->semaphores[i]) !=
vk::Result::eSuccess) {
EXIT("Can't create semaphore");
}
EXIT_IF(pool->buffers[i] == nullptr);
EXIT_IF(pool->fences[i] == nullptr);
EXIT_IF(pool->semaphores[i] == nullptr);
}
}
void CommandPool::DeleteAll() {
auto* ctx = g_render_ctx->GetGraphicCtx();
for (auto& pool: m_pools) {
if (pool != nullptr) {
EXIT_IF(ctx == nullptr);
EXIT_IF(ctx->device == nullptr);
for (uint32_t i = 0; i < pool->buffers_count; i++) {
ctx->device.destroySemaphore(pool->semaphores[i], nullptr);
ctx->device.destroyFence(pool->fences[i], nullptr);
}
ctx->device.freeCommandBuffers(pool->pool, pool->buffers_count, pool->buffers.get());
ctx->device.destroyCommandPool(pool->pool, nullptr);
delete pool;
pool = nullptr;
}
}
} }
bool CommandBuffer::IsInvalid() const { bool CommandBuffer::IsInvalid() const {
EXIT_IF(g_render_ctx == nullptr); return m_slot == nullptr;
if (m_pool != nullptr) {
Common::LockGuard lock(m_pool->mutex);
return (m_index == static_cast<uint32_t>(-1) || m_index >= m_pool->buffers_count);
}
return true;
} }
vk::CommandBuffer CommandBuffer::Handle() const { vk::CommandBuffer CommandBuffer::Handle() const {
EXIT_IF(IsInvalid()); EXIT_IF(IsInvalid());
const auto handle = m_pool->buffers[m_index]; const auto handle = m_slot->buffer;
EXIT_IF(handle == nullptr); EXIT_IF(handle == nullptr);
return handle; return handle;
} }
void CommandBuffer::Allocate() { void CommandBuffer::Release() {
EXIT_IF(!IsInvalid());
m_pool = g_command_pool.GetPool(m_queue);
Common::LockGuard lock(m_pool->mutex);
for (uint32_t i = 0; i < m_pool->buffers_count; i++) {
if (!m_pool->busy[i]) {
m_pool->busy[i] = true;
ResetNativeCommandBuffer(m_pool->buffers[i]);
m_index = i;
break;
}
}
EXIT_NOT_IMPLEMENTED(IsInvalid());
}
void CommandBuffer::Free() {
EXIT_IF(IsInvalid()); EXIT_IF(IsInvalid());
Common::LockGuard lock(m_pool->mutex); Common::LockGuard lock(*m_slot->pool_mutex);
WaitForFence(); WaitForFence();
m_host_stream.Release(); m_slot->busy = false;
m_slot->Reset();
m_pool->busy[m_index] = false;
ResetNativeCommandBuffer(m_pool->buffers[m_index]);
ReleaseResourcesAfterFence(); ReleaseResourcesAfterFence();
m_index = static_cast<uint32_t>(-1); m_slot = nullptr;
EXIT_NOT_IMPLEMENTED(!IsInvalid()); EXIT_NOT_IMPLEMENTED(!IsInvalid());
} }
void CommandBuffer::DeleteAfterFence(VulkanBuffer* buffer) { void CommandBuffer::RetireBufferAfterFence(std::unique_ptr<VulkanBuffer> buffer) {
EXIT_IF(buffer == nullptr); if (IsInvalid() || m_execute || buffer == nullptr || buffer->buffer == nullptr) {
EXIT("cannot retire a buffer on an invalid or submitted command buffer\n");
m_delete_after_fence.push_back(buffer); }
m_retired_buffers.push_back(std::move(buffer));
} }
void CommandBuffer::RetainResourceUntilFence(std::shared_ptr<void> resource) { void CommandBuffer::RetainResourceUntilFence(std::shared_ptr<void> resource) {
@@ -268,20 +91,19 @@ void CommandBuffer::RetainResourceUntilFence(std::shared_ptr<void> resource) {
m_fence_resources.Retain(std::move(resource)); m_fence_resources.Retain(std::move(resource));
} }
void CommandBuffer::RecycleDescriptorAfterFence(VulkanDescriptorSet* set) { void CommandBuffer::RecycleDescriptorAfterFence(VulkanDescriptorSet& set) {
EXIT_IF(set == nullptr); m_descriptor_sets_after_fence.push_back(&set);
m_descriptor_sets_after_fence.push_back(set);
} }
void CommandBuffer::RecycleDescriptorsAfterFence() { void CommandBuffer::RecycleDescriptorsAfterFence() {
for (auto* set: m_descriptor_sets_after_fence) { for (auto* set: m_descriptor_sets_after_fence) {
g_render_ctx->GetDescriptorCache()->Recycle(set); m_context.GetDescriptorCache().Recycle(*set);
} }
m_descriptor_sets_after_fence.clear(); m_descriptor_sets_after_fence.clear();
} }
void CommandBuffer::Begin() const { void CommandBuffer::Begin() const {
EXIT_IF(m_rendering);
auto buffer = Handle(); auto buffer = Handle();
vk::CommandBufferBeginInfo begin_info {}; vk::CommandBufferBeginInfo begin_info {};
@@ -296,6 +118,7 @@ void CommandBuffer::Begin() const {
} }
void CommandBuffer::End() const { void CommandBuffer::End() const {
EndRendering();
auto buffer = Handle(); auto buffer = Handle();
auto result = buffer.end(); auto result = buffer.end();
@@ -314,95 +137,70 @@ void CommandBuffer::SetDebugInfo(uint32_t op, uint64_t submit_id, uint32_t arg0,
m_debug_arg4 = arg4; m_debug_arg4 = arg4;
} }
void CommandBuffer::Execute() { void CommandBuffer::Execute(const SubmitInfo& submit) {
Submit(nullptr, {}, nullptr);
}
void CommandBuffer::ExecuteWithSemaphore(vk::Semaphore signal_semaphore) {
Submit(nullptr, {}, ResolveSignalSemaphore(signal_semaphore));
}
void CommandBuffer::ExecuteWithSemaphore(vk::Semaphore wait_semaphore,
vk::PipelineStageFlags wait_stage,
vk::Semaphore signal_semaphore) {
EXIT_IF(wait_semaphore == nullptr);
Submit(wait_semaphore, wait_stage, ResolveSignalSemaphore(signal_semaphore));
}
void CommandBuffer::Submit(vk::Semaphore wait_semaphore, vk::PipelineStageFlags wait_stage,
vk::Semaphore signal_semaphore) {
RequireValidQueueId(m_queue);
EXIT_IF(IsInvalid()); EXIT_IF(IsInvalid());
EXIT_IF(m_execute); EXIT_IF(m_execute);
EXIT_IF(submit.num_wait_semaphores > SubmitInfo::MaxSemaphores ||
submit.num_signal_semaphores > SubmitInfo::MaxSemaphores);
const bool has_wait = wait_semaphore != nullptr; auto buffer = Handle();
const bool has_signal = signal_semaphore != nullptr; auto fence = m_slot->fence;
auto buffer = Handle();
auto fence = m_pool->fences[m_index]; vk::TimelineSemaphoreSubmitInfo timeline_info {};
timeline_info.sType = vk::StructureType::eTimelineSemaphoreSubmitInfo;
timeline_info.waitSemaphoreValueCount = submit.num_wait_semaphores;
timeline_info.pWaitSemaphoreValues = submit.wait_ticks.data();
timeline_info.signalSemaphoreValueCount = submit.num_signal_semaphores;
timeline_info.pSignalSemaphoreValues = submit.signal_ticks.data();
vk::SubmitInfo submit_info {}; vk::SubmitInfo submit_info {};
submit_info.sType = vk::StructureType::eSubmitInfo; submit_info.sType = vk::StructureType::eSubmitInfo;
submit_info.pNext = nullptr; submit_info.pNext = &timeline_info;
submit_info.waitSemaphoreCount = has_wait ? 1u : 0u; submit_info.waitSemaphoreCount = submit.num_wait_semaphores;
submit_info.pWaitSemaphores = has_wait ? &wait_semaphore : nullptr; submit_info.pWaitSemaphores = submit.wait_semaphores.data();
submit_info.pWaitDstStageMask = has_wait ? &wait_stage : nullptr; submit_info.pWaitDstStageMask = submit.wait_stages.data();
submit_info.commandBufferCount = 1; submit_info.commandBufferCount = 1;
submit_info.pCommandBuffers = &buffer; submit_info.pCommandBuffers = &buffer;
submit_info.signalSemaphoreCount = has_signal ? 1u : 0u; submit_info.signalSemaphoreCount = submit.num_signal_semaphores;
submit_info.pSignalSemaphores = has_signal ? &signal_semaphore : nullptr; submit_info.pSignalSemaphores = submit.signal_semaphores.data();
auto* ctx = g_render_ctx->GetGraphicCtx(); auto& graphics = m_graphics;
EXIT_IF(ctx == nullptr); EXIT_IF(graphics.queue == nullptr);
EXIT_IF(ctx->device == nullptr);
const auto& queue = ctx->queues[m_queue];
auto result = ctx->device.resetFences(1, &fence); auto result = graphics.device.resetFences(1, &fence);
if (result != vk::Result::eSuccess) { if (result != vk::Result::eSuccess) {
LOGF("vkResetFences failed before submit: %s (%d)\n", VulkanToString(result).c_str(), LOGF("vkResetFences failed before submit: %s (%d)\n", VulkanToString(result).c_str(),
static_cast<int>(result)); static_cast<int>(result));
} }
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess); EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
if (queue.mutex != nullptr) {
queue.mutex->Lock();
}
if (Config::GraphicsDebugDumpEnabled()) { if (Config::GraphicsDebugDumpEnabled()) {
LOGF("vkQueueSubmit begin: queue=%d index=%u wait_semaphore=%p signal_semaphore=%p" LOGF("vkQueueSubmit begin: slot=%u waits=%u signals=%u debug_op=%u debug_submit=%" PRIu64
" debug_op=%u debug_submit=%" PRIu64 " args=%u,%u,%u,%u,0x%016" PRIx64 "\n", " args=%u,%u,%u,%u,0x%016" PRIx64 "\n",
m_queue, m_index, static_cast<void*>(wait_semaphore), m_slot->id, submit.num_wait_semaphores, submit.num_signal_semaphores, m_debug_op,
static_cast<void*>(signal_semaphore), m_debug_op, m_debug_submit_id, m_debug_arg0, m_debug_submit_id, m_debug_arg0, m_debug_arg1, m_debug_arg2, m_debug_arg3,
m_debug_arg1, m_debug_arg2, m_debug_arg3, m_debug_arg4); m_debug_arg4);
} }
result = queue.vk_queue.submit(1, &submit_info, fence); {
Common::LockGuard lock(graphics.queue_mutex);
if (queue.mutex != nullptr) { m_submit_seq = m_scheduler.NextSubmitSequence();
queue.mutex->Unlock(); result = graphics.queue.submit(1, &submit_info, fence);
} }
m_execute = true; m_execute = true;
m_fence_waited = false; m_fence_waited = false;
m_submit_seq = g_command_buffer_submit_seq.fetch_add(1, std::memory_order_relaxed) + 1;
if (result != vk::Result::eSuccess) { if (result != vk::Result::eSuccess) {
LOGF("vkQueueSubmit failed: %s (%d), queue=%d index=%u submit_seq=%" PRIu64 LOGF("vkQueueSubmit failed: %s (%d), slot=%u submit_seq=%" PRIu64
" debug_op=%u debug_submit=%" PRIu64 " args=%u,%u,%u,%u,0x%016" PRIx64 "\n", " debug_op=%u debug_submit=%" PRIu64 " args=%u,%u,%u,%u,0x%016" PRIx64 "\n",
VulkanToString(result).c_str(), static_cast<int>(result), m_queue, m_index, VulkanToString(result).c_str(), static_cast<int>(result), m_slot->id, m_submit_seq,
m_submit_seq, m_debug_op, m_debug_submit_id, m_debug_arg0, m_debug_arg1, m_debug_arg2, m_debug_op, m_debug_submit_id, m_debug_arg0, m_debug_arg1, m_debug_arg2, m_debug_arg3,
m_debug_arg3, m_debug_arg4); m_debug_arg4);
} }
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess); EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
} }
vk::Semaphore CommandBuffer::ResolveSignalSemaphore(vk::Semaphore semaphore) const {
if (semaphore != nullptr) {
return semaphore;
}
EXIT_IF(IsInvalid());
return m_pool->semaphores[m_index];
}
void CommandBuffer::WaitForFence() { void CommandBuffer::WaitForFence() {
FinalizeFence(false); FinalizeFence(false);
} }
@@ -412,14 +210,14 @@ void CommandBuffer::WaitForFenceOnly() {
if (!m_execute || m_fence_waited) { if (!m_execute || m_fence_waited) {
return; return;
} }
auto device = g_render_ctx->GetGraphicCtx()->device; auto device = m_graphics.device;
auto result = device.waitForFences(1, &m_pool->fences[m_index], VK_TRUE, UINT64_MAX); auto result = device.waitForFences(1, &m_slot->fence, VK_TRUE, UINT64_MAX);
if (result != vk::Result::eSuccess) { if (result != vk::Result::eSuccess) {
LOGF("vkWaitForFences failed: %s (%d), queue=%d index=%u submit_seq=%" PRIu64 LOGF("vkWaitForFences failed: %s (%d), slot=%u submit_seq=%" PRIu64
" debug_op=%u debug_submit=%" PRIu64 " args=%u,%u,%u,%u,0x%016" PRIx64 "\n", " debug_op=%u debug_submit=%" PRIu64 " args=%u,%u,%u,%u,0x%016" PRIx64 "\n",
VulkanToString(result).c_str(), static_cast<int>(result), m_queue, m_index, VulkanToString(result).c_str(), static_cast<int>(result), m_slot->id, m_submit_seq,
m_submit_seq, m_debug_op, m_debug_submit_id, m_debug_arg0, m_debug_arg1, m_debug_arg2, m_debug_op, m_debug_submit_id, m_debug_arg0, m_debug_arg1, m_debug_arg2, m_debug_arg3,
m_debug_arg3, m_debug_arg4); m_debug_arg4);
} }
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess); EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
m_fence_waited = true; m_fence_waited = true;
@@ -436,13 +234,10 @@ void CommandBuffer::FinalizeFence(bool reset_recording) {
m_execute = false; m_execute = false;
m_fence_waited = false; m_fence_waited = false;
if (reset_recording) { if (reset_recording) {
ResetNativeCommandBuffer(m_pool->buffers[m_index]); Common::LockGuard lock(*m_slot->pool_mutex);
m_recording_generation++; m_slot->Reset();
} }
} }
if (reset_recording) {
m_host_stream.Reset();
}
if (was_executed) { if (was_executed) {
ReleaseResourcesAfterFence(); ReleaseResourcesAfterFence();
} }
@@ -455,138 +250,71 @@ void CommandBuffer::ReleaseResourcesAfterFence() {
} }
void CommandBuffer::DeleteBuffersAfterFence() { void CommandBuffer::DeleteBuffersAfterFence() {
for (auto* buffer: m_delete_after_fence) { for (const auto& buffer: m_retired_buffers) {
VulkanDeleteBuffer(g_render_ctx->GetGraphicCtx(), buffer); m_graphics.DeleteBuffer(*buffer);
delete buffer;
} }
m_delete_after_fence.clear(); m_retired_buffers.clear();
} }
void CommandBuffer::BeginRenderPass(VulkanFramebuffer* framebuffer, RenderColorInfo* colors, void CommandBuffer::BeginRendering(const RenderState& state) const {
uint32_t requested_color_count, RenderDepthInfo* depth) const { EXIT_IF(state.width == 0 || state.height == 0 || state.num_layers == 0 ||
auto buffer = Handle(); state.num_color_attachments > RENDER_COLOR_ATTACHMENTS_MAX);
if (m_rendering && m_render_state == state) {
EXIT_IF(framebuffer == nullptr); return;
EXIT_IF(colors == nullptr);
EXIT_IF(requested_color_count > RENDER_COLOR_ATTACHMENTS_MAX);
bool with_depth = (depth->format != vk::Format::eUndefined && depth->vulkan_buffer != nullptr);
uint32_t color_count = 0;
for (uint32_t i = 0; i < requested_color_count; i++) {
if (colors[i].vulkan_buffer == nullptr) {
break;
}
color_count++;
} }
bool with_color = (color_count != 0); EndRendering();
EXIT_NOT_IMPLEMENTED(!with_depth && !with_color); std::array<vk::RenderingAttachmentInfo, RENDER_COLOR_ATTACHMENTS_MAX> colors {};
for (uint32_t i = 0; i < state.num_color_attachments; i++) {
vk::ClearValue clears[RENDER_COLOR_ATTACHMENTS_MAX + 1] = {}; const auto& attachment = state.color_attachments[i];
for (uint32_t i = 0; i < color_count; i++) { colors[i].sType = vk::StructureType::eRenderingAttachmentInfo;
clears[i].color = colors[i].color_clear_value; colors[i].imageView = attachment.image_view;
} colors[i].imageLayout = attachment.image_layout;
clears[color_count].depthStencil = {depth->depth_clear_value, depth->stencil_clear_value}; colors[i].loadOp =
attachment.is_clear ? vk::AttachmentLoadOp::eClear : vk::AttachmentLoadOp::eLoad;
vk::Extent2D extent = (with_color ? colors[0].extent : depth->vulkan_buffer->extent); colors[i].storeOp = vk::AttachmentStoreOp::eStore;
colors[i].clearValue.color.uint32 = attachment.clear_value;
vk::RenderPassBeginInfo render_pass_info {};
render_pass_info.sType = vk::StructureType::eRenderPassBeginInfo;
render_pass_info.pNext = nullptr;
render_pass_info.renderPass = framebuffer->render_pass;
render_pass_info.framebuffer = framebuffer->framebuffer;
render_pass_info.renderArea.offset = {0, 0};
render_pass_info.renderArea.extent = extent;
render_pass_info.clearValueCount = color_count + (with_depth ? 1u : 0u);
render_pass_info.pClearValues = clears;
for (uint32_t i = 0; i < color_count; i++) {
const auto color_initial_layout = framebuffer->color_layout[i];
if (colors[i].vulkan_buffer->layout != color_initial_layout) {
if (graphics_debug_dump_enabled()) {
LOGF("BeginRenderPass: color%u initial barrier image=%p mem=%" PRIu64 " %s -> %s\n",
i, VulkanHandleToPointer(colors[i].vulkan_buffer->image),
colors[i].vulkan_buffer->memory.unique_id,
VulkanToString(colors[i].vulkan_buffer->layout).c_str(),
VulkanToString(color_initial_layout).c_str());
}
vk::ImageMemoryBarrier image_memory_barrier {};
image_memory_barrier.sType = vk::StructureType::eImageMemoryBarrier;
image_memory_barrier.pNext = nullptr;
image_memory_barrier.srcAccessMask = {};
image_memory_barrier.dstAccessMask = vk::AccessFlagBits::eColorAttachmentRead |
vk::AccessFlagBits::eColorAttachmentWrite;
image_memory_barrier.oldLayout = colors[i].vulkan_buffer->layout;
image_memory_barrier.newLayout = color_initial_layout;
image_memory_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
image_memory_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
image_memory_barrier.image = colors[i].vulkan_buffer->image;
image_memory_barrier.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor;
image_memory_barrier.subresourceRange.baseMipLevel = 0;
image_memory_barrier.subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS;
image_memory_barrier.subresourceRange.baseArrayLayer = 0;
image_memory_barrier.subresourceRange.layerCount = colors[i].vulkan_buffer->layers;
buffer.pipelineBarrier(vk::PipelineStageFlagBits::eTopOfPipe,
vk::PipelineStageFlagBits::eColorAttachmentOutput,
vk::DependencyFlags {}, 0, nullptr, 0, nullptr, 1,
&image_memory_barrier);
colors[i].vulkan_buffer->layout = image_memory_barrier.newLayout;
} else if (graphics_debug_dump_enabled()) {
LOGF("BeginRenderPass: color%u initial image=%p mem=%" PRIu64 " layout=%s\n", i,
VulkanHandleToPointer(colors[i].vulkan_buffer->image),
colors[i].vulkan_buffer->memory.unique_id,
VulkanToString(colors[i].vulkan_buffer->layout).c_str());
}
} }
const auto depth_layout = const auto& depth_stencil = state.depth_stencil_attachment;
(with_depth && framebuffer != nullptr ? framebuffer->depth_layout vk::RenderingAttachmentInfo depth {};
: vk::ImageLayout::eDepthStencilAttachmentOptimal); depth.sType = vk::StructureType::eRenderingAttachmentInfo;
depth.imageView = depth_stencil.image_view;
depth.imageLayout = depth_stencil.image_layout;
depth.loadOp =
depth_stencil.depth_clear ? vk::AttachmentLoadOp::eClear : vk::AttachmentLoadOp::eLoad;
depth.storeOp = vk::AttachmentStoreOp::eStore;
depth.clearValue.depthStencil.depth = std::bit_cast<float>(depth_stencil.clear_value[0]);
if (with_depth && depth->vulkan_buffer->layout != depth_layout) { vk::RenderingAttachmentInfo stencil {};
vk::ImageMemoryBarrier image_memory_barrier {}; stencil.sType = vk::StructureType::eRenderingAttachmentInfo;
image_memory_barrier.sType = vk::StructureType::eImageMemoryBarrier; stencil.imageView = depth_stencil.image_view;
image_memory_barrier.pNext = nullptr; stencil.imageLayout = depth_stencil.image_layout;
image_memory_barrier.srcAccessMask = stencil.loadOp =
vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite; depth_stencil.stencil_clear ? vk::AttachmentLoadOp::eClear : vk::AttachmentLoadOp::eLoad;
image_memory_barrier.dstAccessMask = stencil.storeOp = vk::AttachmentStoreOp::eStore;
(depth_layout == vk::ImageLayout::eDepthStencilReadOnlyOptimal stencil.clearValue.depthStencil.stencil = depth_stencil.clear_value[1];
? vk::AccessFlagBits::eMemoryRead
: vk::AccessFlagBits::eMemoryWrite);
image_memory_barrier.oldLayout = depth->vulkan_buffer->layout;
image_memory_barrier.newLayout = depth_layout;
image_memory_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
image_memory_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
image_memory_barrier.image = depth->vulkan_buffer->image;
image_memory_barrier.subresourceRange.aspectMask =
ImageViewOps::DepthAspectMask(depth->vulkan_buffer->format);
image_memory_barrier.subresourceRange.baseMipLevel = 0;
image_memory_barrier.subresourceRange.levelCount = 1;
image_memory_barrier.subresourceRange.baseArrayLayer = 0;
image_memory_barrier.subresourceRange.layerCount = depth->vulkan_buffer->layers;
buffer.pipelineBarrier( vk::RenderingInfo rendering {};
vk::PipelineStageFlagBits::eAllGraphics | vk::PipelineStageFlagBits::eComputeShader, rendering.sType = vk::StructureType::eRenderingInfo;
vk::PipelineStageFlagBits::eAllGraphics | vk::PipelineStageFlagBits::eComputeShader, rendering.renderArea.extent = {state.width, state.height};
vk::DependencyFlags {}, 0, nullptr, 0, nullptr, 1, &image_memory_barrier); rendering.layerCount = state.num_layers;
rendering.colorAttachmentCount = state.num_color_attachments;
depth->vulkan_buffer->layout = image_memory_barrier.newLayout; rendering.pColorAttachments = colors.data();
} rendering.pDepthAttachment = depth_stencil.has_depth ? &depth : nullptr;
rendering.pStencilAttachment = depth_stencil.has_stencil ? &stencil : nullptr;
buffer.beginRenderPass(&render_pass_info, vk::SubpassContents::eInline); Handle().beginRendering(rendering);
m_render_state = state;
for (uint32_t i = 0; i < color_count; i++) { m_rendering = true;
colors[i].vulkan_buffer->layout = RENDER_COLOR_IMAGE_LAYOUT;
}
} }
void CommandBuffer::EndRenderPass() const { void CommandBuffer::EndRendering() const {
auto buffer = Handle(); if (!m_rendering) {
return;
buffer.endRenderPass(); }
Handle().endRendering();
m_rendering = false;
m_render_state = {};
} }
} // namespace Libs::Graphics } // namespace Libs::Graphics
+56 -116
View File
@@ -9,6 +9,7 @@
#include "graphics/guest_gpu/hardwareContext.h" #include "graphics/guest_gpu/hardwareContext.h"
#include "graphics/host_gpu/renderer/render.h" #include "graphics/host_gpu/renderer/render.h"
#include <algorithm>
#include <atomic> #include <atomic>
#include <cmath> #include <cmath>
#include <fmt/format.h> #include <fmt/format.h>
@@ -25,8 +26,9 @@ static bool RenderTargetMaskHasMrt(uint32_t mask) {
return (mask & ~0x0fu) != 0; return (mask & ~0x0fu) != 0;
} }
static bool RenderTargetMaskHasBoundMrt(const HW::Context& hw) { static bool RenderTargetMaskHasBoundMrt(const RenderCommandBuffer& buffer) {
const auto mask = hw.GetRenderTargetMask(); const auto& hw = buffer.GetRegisters();
const auto mask = hw.GetRenderTargetMask();
if (!RenderTargetMaskHasMrt(mask)) { if (!RenderTargetMaskHasMrt(mask)) {
return false; return false;
@@ -42,8 +44,9 @@ static bool RenderTargetMaskHasBoundMrt(const HW::Context& hw) {
return bound_targets > 1; return bound_targets > 1;
} }
uint32_t render_target_first_bound_slot(const HW::Context& hw) { uint32_t render_target_first_bound_slot(const RenderCommandBuffer& buffer) {
const auto mask = hw.GetRenderTargetMask(); const auto& hw = buffer.GetRegisters();
const auto mask = hw.GetRenderTargetMask();
for (uint32_t i = 0; i < 8; i++) { for (uint32_t i = 0; i < 8; i++) {
if (render_target_mask_slot(mask, i) != 0 && hw.GetRenderTarget(i).base.addr != 0) { if (render_target_mask_slot(mask, i) != 0 && hw.GetRenderTarget(i).base.addr != 0) {
return i; return i;
@@ -98,8 +101,6 @@ void sh_print(const char* func, const HW::Shader& /*uc*/) {
LOGF("%s\n", func); LOGF("%s\n", func);
} }
void sh_check(const HW::Shader& /*uc*/) {}
std::vector<std::string> rt_print(const char* func, const HW::RenderTarget& rt) { std::vector<std::string> rt_print(const char* func, const HW::RenderTarget& rt) {
std::vector<std::string> dst; std::vector<std::string> dst;
dst.reserve(53); dst.reserve(53);
@@ -334,7 +335,7 @@ static void RtCheck(const HW::RenderTarget& rt) {
if (rt.attrib.num_samples != 0x00000000 || rt.attrib.num_fragments != 0x00000000) { if (rt.attrib.num_samples != 0x00000000 || rt.attrib.num_fragments != 0x00000000) {
static bool logged = false; static bool logged = false;
if (!logged) { if (!logged) {
LOGF("RenderTarget: temporary: rendering PS5 MSAA color target as single-sample, " LOGF("RenderTarget: using native PS5 MSAA color target, "
"samples=0x%08" PRIx32 " fragments=0x%08" PRIx32 "\n", "samples=0x%08" PRIx32 " fragments=0x%08" PRIx32 "\n",
rt.attrib.num_samples, rt.attrib.num_fragments); rt.attrib.num_samples, rt.attrib.num_fragments);
logged = true; logged = true;
@@ -429,21 +430,17 @@ static void ZPrint(const char* func, const HW::DepthRenderTarget& z) {
LOGF("%s\n", func); LOGF("%s\n", func);
LOGF("\t z_info.format = 0x%08" PRIx32 "\n" LOGF("\t z_info.format = 0x%08" PRIx32 "\n"
"\t z_info.tile_mode_index = 0x%08" PRIx32 "\n"
"\t z_info.num_samples = 0x%08" PRIx32 "\n" "\t z_info.num_samples = 0x%08" PRIx32 "\n"
"\t z_info.tile_surface_enable = %s\n" "\t z_info.texture_compatibility = 0x%08" PRIx32 "\n"
"\t z_info.htile_acceleration = %s\n"
"\t z_info.expclear_enabled = %s\n" "\t z_info.expclear_enabled = %s\n"
"\t z_info.zrange_precision = 0x%08" PRIx32 "\n" "\t z_info.z_compare_base = 0x%08" PRIx32 "\n"
"\t z_info.embedded_sample_locations = %s\n"
"\t z_info.partially_resident = %s\n" "\t z_info.partially_resident = %s\n"
"\t z_info.num_mip_levels = 0x%02" PRIx8 "\n" "\t z_info.max_mip_level = 0x%02" PRIx8 "\n"
"\t z_info.plane_compression = 0x%02" PRIx8 "\n"
"\t stencil_info.format = 0x%08" PRIx32 "\n" "\t stencil_info.format = 0x%08" PRIx32 "\n"
"\t stencil_info.tile_stencil_disable = %s\n" "\t stencil_info.texture_compatibility = 0x%08" PRIx32 "\n"
"\t stencil_info.htile_stencil_disabled = %s\n"
"\t stencil_info.expclear_enabled = %s\n" "\t stencil_info.expclear_enabled = %s\n"
"\t stencil_info.tile_mode_index = 0x%08" PRIx32 "\n"
"\t stencil_info.tile_split = 0x%08" PRIx32 "\n"
"\t stencil_info.texture_compatible_stencil = %s\n"
"\t stencil_info.partially_resident = %s\n" "\t stencil_info.partially_resident = %s\n"
"\t depth_info.addr5_swizzle_mask = 0x%08" PRIx32 "\n" "\t depth_info.addr5_swizzle_mask = 0x%08" PRIx32 "\n"
"\t depth_info.array_mode = 0x%08" PRIx32 "\n" "\t depth_info.array_mode = 0x%08" PRIx32 "\n"
@@ -476,15 +473,15 @@ static void ZPrint(const char* func, const HW::DepthRenderTarget& z) {
"\t height = 0x%08" PRIx32 "\n" "\t height = 0x%08" PRIx32 "\n"
"\t size.x_max = 0x%04" PRIx16 "\n" "\t size.x_max = 0x%04" PRIx16 "\n"
"\t size.y_max = 0x%04" PRIx16 "\n", "\t size.y_max = 0x%04" PRIx16 "\n",
z.z_info.format, z.z_info.tile_mode_index, z.z_info.num_samples, z.z_info.format, z.z_info.num_samples,
z.z_info.tile_surface_enable ? "true" : "false", Prospero::GpuEnumValue(z.z_info.texture_compatibility),
z.z_info.expclear_enabled ? "true" : "false", z.z_info.zrange_precision, z.z_info.htile_acceleration ? "true" : "false",
z.z_info.embedded_sample_locations ? "true" : "false", z.z_info.expclear_enabled ? "true" : "false",
z.z_info.partially_resident ? "true" : "false", z.z_info.num_mip_levels, Prospero::GpuEnumValue(z.z_info.z_compare_base),
z.z_info.plane_compression, z.stencil_info.format, z.z_info.partially_resident ? "true" : "false", z.z_info.max_mip_level,
z.stencil_info.tile_stencil_disable ? "true" : "false", z.stencil_info.format, Prospero::GpuEnumValue(z.stencil_info.texture_compatibility),
z.stencil_info.expclear_enabled ? "true" : "false", z.stencil_info.tile_mode_index, z.stencil_info.htile_stencil_disabled ? "true" : "false",
z.stencil_info.tile_split, z.stencil_info.texture_compatible_stencil ? "true" : "false", z.stencil_info.expclear_enabled ? "true" : "false",
z.stencil_info.partially_resident ? "true" : "false", z.depth_info.addr5_swizzle_mask, z.stencil_info.partially_resident ? "true" : "false", z.depth_info.addr5_swizzle_mask,
z.depth_info.array_mode, z.depth_info.pipe_config, z.depth_info.bank_width, z.depth_info.array_mode, z.depth_info.pipe_config, z.depth_info.bank_width,
z.depth_info.bank_height, z.depth_info.macro_tile_aspect, z.depth_info.num_banks, z.depth_info.bank_height, z.depth_info.macro_tile_aspect, z.depth_info.num_banks,
@@ -500,95 +497,45 @@ static void ZPrint(const char* func, const HW::DepthRenderTarget& z) {
} }
// NOLINTNEXTLINE(readability-function-cognitive-complexity) // NOLINTNEXTLINE(readability-function-cognitive-complexity)
static void ZCheck(const HW::DepthRenderTarget& z) { static void ZCheck(const HW::DepthRenderTarget& z, const HW::DepthControl& dc,
const HW::RenderControl& rc) {
const bool depth_active =
dc.z_enable || dc.z_write_enable || dc.depth_bounds_enable || rc.depth_clear_enable;
const bool stencil_active = dc.stencil_enable || rc.stencil_clear_enable;
if (!depth_active && !stencil_active) {
return;
}
EXIT_NOT_IMPLEMENTED(!z.z_info.HasValidTextureCompatibility());
EXIT_NOT_IMPLEMENTED(!z.stencil_info.HasValidTextureCompatibility());
if (z.z_info.format == 0) { if (z.z_info.format == 0) {
EXIT_NOT_IMPLEMENTED(z.z_info.format != 0); EXIT_NOT_IMPLEMENTED(z.z_info.format != 0);
EXIT_NOT_IMPLEMENTED(z.z_info.tile_mode_index != 0);
EXIT_NOT_IMPLEMENTED(z.z_info.num_samples != 0); EXIT_NOT_IMPLEMENTED(z.z_info.num_samples != 0);
EXIT_NOT_IMPLEMENTED(z.z_info.tile_surface_enable != false); EXIT_NOT_IMPLEMENTED(z.z_info.htile_acceleration != false);
EXIT_NOT_IMPLEMENTED(z.z_info.expclear_enabled != false); EXIT_NOT_IMPLEMENTED(z.z_info.expclear_enabled != false);
if (z.z_info.zrange_precision != 0) {
LOGF("Warning: zrange_precision != 0\n");
// z.z_info.zrange_precision = 0;
}
if (z.z_info.embedded_sample_locations) {
static bool logged = false;
if (!logged) {
LOGF("DepthTarget: temporary: ignoring embedded sample locations\n");
logged = true;
}
}
EXIT_NOT_IMPLEMENTED(z.z_info.partially_resident != false); EXIT_NOT_IMPLEMENTED(z.z_info.partially_resident != false);
EXIT_NOT_IMPLEMENTED(z.z_info.num_mip_levels != 0); EXIT_NOT_IMPLEMENTED(z.z_info.max_mip_level != 0);
if (z.z_info.plane_compression != 0) {
static bool logged = false;
if (!logged) {
LOGF("DepthTarget: temporary: ignoring PS5 plane_compression=0x%02" PRIx8 "\n",
z.z_info.plane_compression);
logged = true;
}
}
} else { } else {
EXIT_NOT_IMPLEMENTED(z.z_info.format != 0x00000001 && z.z_info.format != 0x00000003); EXIT_NOT_IMPLEMENTED(z.z_info.format != 0x00000001 && z.z_info.format != 0x00000003);
// EXIT_NOT_IMPLEMENTED(z.z_info.tile_mode_index != 0x00000002);
if (z.z_info.num_samples != 0x00000000) { if (z.z_info.num_samples != 0x00000000) {
static bool logged = false; static bool logged = false;
if (!logged) { if (!logged) {
LOGF("DepthTarget: temporary: ignoring num_samples=0x%08" PRIx32 "\n", LOGF("DepthTarget: using native num_samples=0x%08" PRIx32 "\n",
z.z_info.num_samples); z.z_info.num_samples);
logged = true; logged = true;
} }
} }
// EXIT_NOT_IMPLEMENTED(z.z_info.tile_surface_enable != true);
EXIT_NOT_IMPLEMENTED(z.z_info.expclear_enabled != false); EXIT_NOT_IMPLEMENTED(z.z_info.expclear_enabled != false);
if (z.z_info.zrange_precision != 0x00000001) {
static bool logged = false;
if (!logged) {
LOGF("DepthTarget: temporary: ignoring zrange_precision=0x%08" PRIx32 "\n",
z.z_info.zrange_precision);
logged = true;
}
}
if (z.z_info.embedded_sample_locations) {
static bool logged = false;
if (!logged) {
LOGF("DepthTarget: temporary: ignoring embedded sample locations\n");
logged = true;
}
}
EXIT_NOT_IMPLEMENTED(z.z_info.partially_resident != false); EXIT_NOT_IMPLEMENTED(z.z_info.partially_resident != false);
EXIT_NOT_IMPLEMENTED(z.z_info.num_mip_levels != 0); EXIT_NOT_IMPLEMENTED(z.z_info.max_mip_level != 0);
if (z.z_info.plane_compression != 0) {
static bool logged = false;
if (!logged) {
LOGF("DepthTarget: temporary: ignoring PS5 plane_compression=0x%02" PRIx8 "\n",
z.z_info.plane_compression);
logged = true;
}
}
} }
if (z.stencil_info.format == 0) { if (z.stencil_info.format == 0) {
// EXIT_NOT_IMPLEMENTED(z.stencil_info.format != 0);
// EXIT_NOT_IMPLEMENTED(z.stencil_info.tile_stencil_disable != false);
EXIT_NOT_IMPLEMENTED(z.stencil_info.expclear_enabled != false); EXIT_NOT_IMPLEMENTED(z.stencil_info.expclear_enabled != false);
// EXIT_NOT_IMPLEMENTED(z.stencil_info.tile_mode_index != 0);
// EXIT_NOT_IMPLEMENTED(z.stencil_info.tile_split != 0);
// EXIT_NOT_IMPLEMENTED(z.stencil_info.texture_compatible_stencil != true);
EXIT_NOT_IMPLEMENTED(z.stencil_info.partially_resident != false); EXIT_NOT_IMPLEMENTED(z.stencil_info.partially_resident != false);
} else { } else {
// EXIT_NOT_IMPLEMENTED(z.stencil_info.format != 0x00000001); EXIT_NOT_IMPLEMENTED(z.stencil_info.format != 0x00000001);
if (z.stencil_info.tile_stencil_disable != true) {
static std::atomic<uint32_t> log_count {0};
if (log_count.fetch_add(1) < 16) {
LOGF("DepthTarget: temporary: ignoring PS5 HTILE stencil acceleration\n");
}
}
EXIT_NOT_IMPLEMENTED(z.stencil_info.expclear_enabled != false); EXIT_NOT_IMPLEMENTED(z.stencil_info.expclear_enabled != false);
// EXIT_NOT_IMPLEMENTED(z.stencil_info.tile_mode_index != 0x00000002);
// EXIT_NOT_IMPLEMENTED(z.stencil_info.tile_split != 0x00000002);
// EXIT_NOT_IMPLEMENTED(z.stencil_info.texture_compatible_stencil != true);
EXIT_NOT_IMPLEMENTED(z.stencil_info.partially_resident != false); EXIT_NOT_IMPLEMENTED(z.stencil_info.partially_resident != false);
} }
@@ -609,14 +556,6 @@ static void ZCheck(const HW::DepthRenderTarget& z) {
EXIT_NOT_IMPLEMENTED(z.htile_surface.prefetch_height != 0x00000000); EXIT_NOT_IMPLEMENTED(z.htile_surface.prefetch_height != 0x00000000);
EXIT_NOT_IMPLEMENTED(z.htile_surface.dst_outside_zero_to_one != 0x00000000); EXIT_NOT_IMPLEMENTED(z.htile_surface.dst_outside_zero_to_one != 0x00000000);
if (z.depth_view.slice_start != 0x00000000 || z.depth_view.slice_max != 0x00000000) {
static std::atomic<uint32_t> log_count {0};
if (log_count.fetch_add(1, std::memory_order_relaxed) < 16) {
LOGF("DepthTarget: temporary: ignoring PS5 array slice view start=0x%08" PRIx32
", max=0x%08" PRIx32 "\n",
z.depth_view.slice_start, z.depth_view.slice_max);
}
}
if (z.depth_view.current_mip_level != 0x00000000) { if (z.depth_view.current_mip_level != 0x00000000) {
static std::atomic<uint32_t> log_count {0}; static std::atomic<uint32_t> log_count {0};
if (log_count.fetch_add(1, std::memory_order_relaxed) < 16) { if (log_count.fetch_add(1, std::memory_order_relaxed) < 16) {
@@ -687,7 +626,7 @@ static void ClipCheck(const HW::ClipControl& c) {
// dx_linear_attr_clip_enable preserves linear (noperspective) attributes at clip-generated // dx_linear_attr_clip_enable preserves linear (noperspective) attributes at clip-generated
// vertices, which Vulkan provides as part of clipping and interpolation. // vertices, which Vulkan provides as part of clipping and interpolation.
EXIT_NOT_IMPLEMENTED(c.user_clip_planes != 0 || c.user_clip_plane_mode != 0 || EXIT_NOT_IMPLEMENTED(c.user_clip_planes != 0 || c.user_clip_plane_mode != 0 ||
c.vertex_kill_any || c.min_z_clip_disable || c.max_z_clip_disable || c.vertex_kill_any || !c.IsZClipModeRepresentable() ||
c.user_clip_plane_negate_y || c.clip_disable || c.user_clip_plane_negate_y || c.clip_disable ||
c.user_clip_plane_cull_only || c.cull_on_clipping_error_disable || c.user_clip_plane_cull_only || c.cull_on_clipping_error_disable ||
c.force_viewport_index_from_vs_enable); c.force_viewport_index_from_vs_enable);
@@ -932,7 +871,7 @@ static void EqaaCheck(const HW::EqaaControl& c) {
c.incoherent_eqaa_reads || c.interpolate_comp_z || c.static_anchor_associations) { c.incoherent_eqaa_reads || c.interpolate_comp_z || c.static_anchor_associations) {
static std::atomic<uint32_t> log_count {0}; static std::atomic<uint32_t> log_count {0};
if (log_count.fetch_add(1) < 16) { if (log_count.fetch_add(1) < 16) {
LOGF("\t warning: unsupported PS5 EQAA state, rendering with single-sample fallback\n"); LOGF("\t warning: unsupported PS5 EQAA controls use native Vulkan MSAA defaults\n");
} }
} }
} }
@@ -962,8 +901,8 @@ static void AaCheck(const HW::AaSampleControl& c, const HW::AaConfig& cf) {
cf.max_sample_dist != 0 || cf.msaa_exposed_samples != 0) { cf.max_sample_dist != 0 || cf.msaa_exposed_samples != 0) {
static std::atomic<uint32_t> log_count {0}; static std::atomic<uint32_t> log_count {0};
if (log_count.fetch_add(1) < 16) { if (log_count.fetch_add(1) < 16) {
LOGF("\t warning: unsupported PS5 AA/MSAA state, rendering with single-sample " LOGF("\t warning: unsupported PS5 sample locations use native Vulkan locations: "
"fallback: samples=%" PRIu8 ", exposed=%" PRIu8 ", max_dist=%" PRIu8 "\n", "samples=%" PRIu8 ", exposed=%" PRIu8 ", max_dist=%" PRIu8 "\n",
cf.msaa_num_samples, cf.msaa_exposed_samples, cf.max_sample_dist); cf.msaa_num_samples, cf.msaa_exposed_samples, cf.max_sample_dist);
} }
} }
@@ -1058,8 +997,7 @@ static void VpCheck(const HW::ScreenViewport& vp, const HW::ScanModeControl& smc
static std::atomic<uint32_t> log_count {0}; static std::atomic<uint32_t> log_count {0};
if (log_count.fetch_add(1) < 16) { if (log_count.fetch_add(1) < 16) {
LOGF("\t warning: unsupported PS5 MSAA raster state, rendering with single-sample " LOGF("\t warning: unsupported PS5 MSAA raster controls use native Vulkan defaults\n");
"fallback\n");
} }
} }
// EXIT_NOT_IMPLEMENTED(smc.vport_scissor_enable); // EXIT_NOT_IMPLEMENTED(smc.vport_scissor_enable);
@@ -1134,10 +1072,10 @@ static ScissorRect ScissorRectClamp(ScissorRect r, uint32_t width, uint32_t heig
int max_right = static_cast<int>(width); int max_right = static_cast<int>(width);
int max_bottom = static_cast<int>(height); int max_bottom = static_cast<int>(height);
r.left = (r.left < 0 ? 0 : (r.left > max_right ? max_right : r.left)); r.left = std::clamp(r.left, 0, max_right);
r.right = (r.right < 0 ? 0 : (r.right > max_right ? max_right : r.right)); r.right = std::clamp(r.right, 0, max_right);
r.top = (r.top < 0 ? 0 : (r.top > max_bottom ? max_bottom : r.top)); r.top = std::clamp(r.top, 0, max_bottom);
r.bottom = (r.bottom < 0 ? 0 : (r.bottom > max_bottom ? max_bottom : r.bottom)); r.bottom = std::clamp(r.bottom, 0, max_bottom);
if (!ScissorRectValid(r)) { if (!ScissorRectValid(r)) {
r.right = r.left; r.right = r.left;
@@ -1242,8 +1180,9 @@ ScissorRect calc_final_scissor(const HW::ScreenViewport& vp, const HW::ScanModeC
return ScissorRectClamp(final, extent.width, extent.height); return ScissorRectClamp(final, extent.width, extent.height);
} }
void hw_check(const HW::Context& hw) { void hw_check(const RenderCommandBuffer& buffer) {
const auto rt_slot = render_target_first_bound_slot(hw); const auto& hw = buffer.GetRegisters();
const auto rt_slot = render_target_first_bound_slot(buffer);
const auto& rt = hw.GetRenderTarget(rt_slot); const auto& rt = hw.GetRenderTarget(rt_slot);
const auto& bc = hw.GetBlendControl(rt_slot); const auto& bc = hw.GetBlendControl(rt_slot);
const auto& bclr = hw.GetBlendColor(); const auto& bclr = hw.GetBlendColor();
@@ -1275,7 +1214,7 @@ void hw_check(const HW::Context& hw) {
log_phase("vp"); log_phase("vp");
VpCheck(vp, smc); VpCheck(vp, smc);
log_phase("z"); log_phase("z");
ZCheck(z); ZCheck(z, d, rc);
log_phase("clip"); log_phase("clip");
ClipCheck(c); ClipCheck(c);
log_phase("rc"); log_phase("rc");
@@ -1292,7 +1231,7 @@ void hw_check(const HW::Context& hw) {
AaCheck(aa, ac); AaCheck(aa, ac);
log_phase("done"); log_phase("done");
if (RenderTargetMaskHasBoundMrt(hw)) { if (RenderTargetMaskHasBoundMrt(buffer)) {
LOGF("MRT render target mask: 0x%08" PRIx32 "\n", hw.GetRenderTargetMask()); LOGF("MRT render target mask: 0x%08" PRIx32 "\n", hw.GetRenderTargetMask());
for (uint32_t i = 0; i < 8; i++) { for (uint32_t i = 0; i < 8; i++) {
const auto& mrt = hw.GetRenderTarget(i); const auto& mrt = hw.GetRenderTarget(i);
@@ -1313,8 +1252,9 @@ void hw_check(const HW::Context& hw) {
// EXIT_NOT_IMPLEMENTED(hw.GetStencilClearValue() != 0); // EXIT_NOT_IMPLEMENTED(hw.GetStencilClearValue() != 0);
} }
void hw_print(const HW::Context& hw) { void hw_print(const RenderCommandBuffer& buffer) {
const auto rt_slot = render_target_first_bound_slot(hw); const auto& hw = buffer.GetRegisters();
const auto rt_slot = render_target_first_bound_slot(buffer);
const auto& rt = hw.GetRenderTarget(rt_slot); const auto& rt = hw.GetRenderTarget(rt_slot);
const auto& bc = hw.GetBlendControl(rt_slot); const auto& bc = hw.GetBlendControl(rt_slot);
const auto& bclr = hw.GetBlendColor(); const auto& bclr = hw.GetBlendColor();
+5 -4
View File
@@ -9,6 +9,8 @@
namespace Libs::Graphics { namespace Libs::Graphics {
class RenderCommandBuffer;
namespace HW { namespace HW {
class Context; class Context;
class Shader; class Shader;
@@ -26,16 +28,15 @@ struct ScissorRect {
}; };
uint32_t render_target_mask_slot(uint32_t mask, uint32_t slot); uint32_t render_target_mask_slot(uint32_t mask, uint32_t slot);
uint32_t render_target_first_bound_slot(const HW::Context& hw); uint32_t render_target_first_bound_slot(const RenderCommandBuffer& buffer);
bool graphics_debug_dump_enabled(); bool graphics_debug_dump_enabled();
void uc_print(const char* func, const HW::UserConfig& uc); void uc_print(const char* func, const HW::UserConfig& uc);
void uc_check(const HW::UserConfig& uc); void uc_check(const HW::UserConfig& uc);
void sh_print(const char* func, const HW::Shader& uc); void sh_print(const char* func, const HW::Shader& uc);
void sh_check(const HW::Shader& uc);
std::vector<std::string> rt_print(const char* func, const HW::RenderTarget& rt); std::vector<std::string> rt_print(const char* func, const HW::RenderTarget& rt);
bool RenderIsColorTileModeLinear(uint32_t tile_mode); bool RenderIsColorTileModeLinear(uint32_t tile_mode);
void hw_print(const HW::Context& hw); void hw_print(const RenderCommandBuffer& buffer);
void hw_check(const HW::Context& hw); void hw_check(const RenderCommandBuffer& buffer);
void LogDrawPhase(const char* draw_name, const char* phase); void LogDrawPhase(const char* draw_name, const char* phase);
ScissorRect calc_final_scissor(const HW::ScreenViewport& vp, const HW::ScanModeControl& smc, ScissorRect calc_final_scissor(const HW::ScreenViewport& vp, const HW::ScanModeControl& smc,
vk::Extent2D extent); vk::Extent2D extent);
@@ -10,15 +10,13 @@
#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/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/framebufferCache.h" #include "graphics/host_gpu/renderer/image/textureCommon.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/transfer.h"
#include "graphics/host_gpu/vulkanCommon.h" #include "graphics/host_gpu/vulkanCommon.h"
#include "graphics/presentation/displayBuffer.h"
#include <algorithm> #include <algorithm>
#include <atomic> #include <atomic>
@@ -61,40 +59,38 @@ static bool UsesStencilOpValue(uint8_t fail, uint8_t pass, uint8_t depth_fail) {
depth_fail == Prospero::GpuEnumValue(Prospero::StencilOp::kReplaceOp); depth_fail == Prospero::GpuEnumValue(Prospero::StencilOp::kReplaceOp);
} }
[[nodiscard]] static vk::Format ResolveHostDepthAttachmentFormat(GraphicContext* ctx, [[nodiscard]] static vk::Format ResolveHostDepthAttachmentFormat(const RenderCommandBuffer& buffer,
const DepthFormatPolicy& policy, const DepthFormatPolicy& policy,
bool has_stencil) { bool has_stencil,
uint32_t samples) {
auto& graphics = buffer.GetGraphics();
const auto required_samples = vulkan_sample_count(samples);
const auto supports = [&](vk::Format format) {
vk::ImageFormatProperties properties {};
return format != vk::Format::eUndefined &&
graphics.GetImageFormatProperties(
format, vk::ImageType::e2D, vk::ImageTiling::eOptimal, DepthTargetImageUsage(),
vk::ImageCreateFlags {}, &properties) == vk::Result::eSuccess &&
static_cast<bool>(properties.sampleCounts & required_samples);
};
if (!has_stencil) { if (!has_stencil) {
return policy.depth_attachment_format; return supports(policy.depth_attachment_format) ? policy.depth_attachment_format
: vk::Format::eUndefined;
} }
switch (policy.depth_format) { for (const auto format: policy.stencil_attachment_formats) {
case Prospero::DepthFormat::kZ32F: return policy.stencil_attachment_formats.front(); if (supports(format)) {
case Prospero::DepthFormat::kZ16: { return format;
if (ctx == nullptr) {
return vk::Format::eUndefined;
}
for (const auto format: policy.stencil_attachment_formats) {
vk::ImageFormatProperties properties {};
if (ctx->GetImageFormatProperties(format, vk::ImageType::e2D,
vk::ImageTiling::eOptimal,
DepthTargetImageUsage(), vk::ImageCreateFlags {},
&properties) == vk::Result::eSuccess) {
return format;
}
}
return vk::Format::eUndefined;
} }
default: return vk::Format::eUndefined;
} }
return vk::Format::eUndefined;
} }
// NOLINTNEXTLINE(readability-function-cognitive-complexity) // NOLINTNEXTLINE(readability-function-cognitive-complexity)
void ResolveRenderDepthTarget(uint64_t submit_id, CommandBuffer* buffer, const HW::Context& hw, void RenderExecutor::ResolveRenderDepthTarget(uint64_t submit_id, RenderCommandBuffer& buffer,
RenderDepthInfo* r) { RenderDepthInfo& r) {
KYTY_PROFILER_FUNCTION(); KYTY_PROFILER_FUNCTION();
EXIT_IF(r == nullptr);
(void)submit_id; (void)submit_id;
(void)buffer; const auto& hw = buffer.GetRegisters();
const auto& z = hw.GetDepthRenderTarget(); const auto& z = hw.GetDepthRenderTarget();
const auto& rc = hw.GetRenderControl(); const auto& rc = hw.GetRenderControl();
const auto& dc = hw.GetDepthControl(); const auto& dc = hw.GetDepthControl();
@@ -106,31 +102,34 @@ void ResolveRenderDepthTarget(uint64_t submit_id, CommandBuffer* buffer, const H
if (!depth_active && !stencil_active) { if (!depth_active && !stencil_active) {
return; return;
} }
if (!z.z_info.HasValidTextureCompatibility() ||
!z.stencil_info.HasValidTextureCompatibility()) {
DepthFatal("invalid PS5 depth texture-compatibility encoding");
}
const bool attachment_unbound = const bool attachment_unbound =
z.z_info.format == Prospero::GpuEnumValue(Prospero::DepthFormat::kInvalid) && z.z_info.format == Prospero::GpuEnumValue(Prospero::DepthFormat::kInvalid) &&
z.stencil_info.format == Prospero::GpuEnumValue(Prospero::StencilFormat::kInvalid) && z.stencil_info.format == Prospero::GpuEnumValue(Prospero::StencilFormat::kInvalid) &&
z.z_info.tile_mode_index == 0 && z.z_info.num_samples == 0 && z.z_info.num_samples == 0 &&
z.z_info.zrange_precision <= 1 && !z.z_info.expclear_enabled && z.z_info.texture_compatibility == Prospero::TextureCompatiblePlaneCompression::kDisable &&
!z.z_info.embedded_sample_locations && !z.z_info.partially_resident && !z.z_info.expclear_enabled && !z.z_info.partially_resident && z.z_info.max_mip_level == 0 &&
z.z_info.num_mip_levels == 0 && z.z_info.plane_compression == 0 && z.stencil_info.texture_compatibility == Prospero::TextureCompatibleStencil::kDisable &&
z.stencil_info.tile_mode_index == 0 && z.stencil_info.tile_split == 0 && !z.stencil_info.expclear_enabled && !z.stencil_info.partially_resident &&
!z.stencil_info.expclear_enabled && !z.stencil_info.texture_compatible_stencil && z.depth_view.slice_start == 0 && z.depth_view.slice_max == 0 &&
!z.stencil_info.partially_resident && z.depth_view.slice_start == 0 && z.depth_view.current_mip_level == 0 && !z.depth_view.depth_write_disable &&
z.depth_view.slice_max == 0 && z.depth_view.current_mip_level == 0 && !z.depth_view.stencil_write_disable && z.depth_info.addr5_swizzle_mask == 0 &&
!z.depth_view.depth_write_disable && !z.depth_view.stencil_write_disable && z.depth_info.array_mode == 0 && z.depth_info.pipe_config == 0 &&
z.depth_info.addr5_swizzle_mask == 0 && z.depth_info.array_mode == 0 && z.depth_info.bank_width == 0 && z.depth_info.bank_height == 0 &&
z.depth_info.pipe_config == 0 && z.depth_info.bank_width == 0 && z.depth_info.macro_tile_aspect == 0 && z.depth_info.num_banks == 0 &&
z.depth_info.bank_height == 0 && z.depth_info.macro_tile_aspect == 0 && z.htile_surface.linear == 0 && z.htile_surface.full_cache == 0 &&
z.depth_info.num_banks == 0 && z.htile_surface.linear == 0 && z.htile_surface.htile_uses_preload_win == 0 && z.htile_surface.preload == 0 &&
z.htile_surface.full_cache == 0 && z.htile_surface.htile_uses_preload_win == 0 && z.htile_surface.prefetch_width == 0 && z.htile_surface.prefetch_height == 0 &&
z.htile_surface.preload == 0 && z.htile_surface.prefetch_width == 0 && z.htile_surface.dst_outside_zero_to_one == 0 && z.z_read_base_addr == 0 &&
z.htile_surface.prefetch_height == 0 && z.htile_surface.dst_outside_zero_to_one == 0 && z.z_write_base_addr == 0 && z.stencil_read_base_addr == 0 &&
z.z_read_base_addr == 0 && z.z_write_base_addr == 0 && z.stencil_read_base_addr == 0 &&
z.stencil_write_base_addr == 0 && z.htile_data_base_addr == 0 && z.stencil_write_base_addr == 0 && z.htile_data_base_addr == 0 &&
// DB_DEPTH_SIZE_XY is independent state and may remain programmed after the attachment // DB_DEPTH_SIZE_XY is independent state and may remain programmed after the attachment
// formats and addresses are unbound. A zero encoding is the valid 1x1 value, so its // formats and addresses are unbound. A zero encoding is the valid 1x1 value, so its
// presence alone must not manufacture a depth attachment. // presence alone must not manufacture a depth attachment.
!z.z_info.tile_surface_enable && !z.width_height_valid && !z.pitch_height_valid && !z.z_info.htile_acceleration && !z.width_height_valid && !z.pitch_height_valid &&
z.size.x_max == 0 && z.size.y_max == 0 && z.pitch_div8_minus1 == 0 && z.size.x_max == 0 && z.size.y_max == 0 && z.pitch_div8_minus1 == 0 &&
z.height_div8_minus1 == 0 && z.slice_div64_minus1 == 0 && z.width == 0 && z.height == 0; z.height_div8_minus1 == 0 && z.slice_div64_minus1 == 0 && z.width == 0 && z.height == 0;
if (attachment_unbound) { if (attachment_unbound) {
@@ -142,70 +141,48 @@ void ResolveRenderDepthTarget(uint64_t submit_id, CommandBuffer* buffer, const H
} }
const bool has_stencil = const bool has_stencil =
z.stencil_info.format != Prospero::GpuEnumValue(Prospero::StencilFormat::kInvalid); z.stencil_info.format != Prospero::GpuEnumValue(Prospero::StencilFormat::kInvalid);
const bool has_htile = z.z_info.tile_surface_enable; const bool has_htile = z.z_info.htile_acceleration;
const bool msaa_compat = depth_msaa_single_sample_compatible(z.z_info.num_samples); const auto samples = render_sample_count(z.z_info.num_samples);
if (samples == 0) {
DepthFatal("unsupported depth fragment count: %u", z.z_info.num_samples);
}
const bool htile_stencil_compat = depth_htile_stencil_acceleration_compatible( const bool htile_stencil_compat = depth_htile_stencil_acceleration_compatible(
has_stencil, has_htile, z.stencil_info.tile_stencil_disable); has_stencil, has_htile, z.stencil_info.htile_stencil_disabled);
const auto view = ResolveTargetViewInfo(z.depth_view.slice_start, z.depth_view.slice_max); const auto view = ResolveTargetViewInfo(z.depth_view.slice_start, z.depth_view.slice_max);
switch (view.type) { switch (view.type) {
case TargetViewType::Image2D: break; case TargetViewType::Image2D:
case TargetViewType::Image2DArray: case TargetViewType::Image2DArray: break;
DepthFatal("layered depth views are unsupported: base=%u count=%u", view.base_layer,
view.layer_count);
case TargetViewType::Unsupported: case TargetViewType::Unsupported:
DepthFatal("invalid depth view: base=%u last=%u", z.depth_view.slice_start, DepthFatal("invalid depth view: base=%u last=%u", z.depth_view.slice_start,
z.depth_view.slice_max); z.depth_view.slice_max);
} }
// Prospero defines the compression-disable bits as tile writeback policy. Vulkan attachments
// expose the same logical depth/stencil values regardless of the driver's backing compression.
if ((stencil_active && !has_stencil) || rc.resummarize_enable || rc.copy_centroid || if ((stencil_active && !has_stencil) || rc.resummarize_enable || rc.copy_centroid ||
rc.copy_sample != 0 || z.z_info.expclear_enabled || z.stencil_info.expclear_enabled || rc.copy_sample != 0 || z.z_info.expclear_enabled || z.stencil_info.expclear_enabled ||
z.z_info.embedded_sample_locations || z.z_info.partially_resident || z.z_info.partially_resident || z.stencil_info.partially_resident ||
z.stencil_info.partially_resident || z.z_info.plane_compression != 0 || z.z_info.max_mip_level != 0 || z.depth_view.current_mip_level != 0 ||
(z.z_info.num_samples != 0 && !msaa_compat) || z.z_info.num_mip_levels != 0 || z.depth_info.addr5_swizzle_mask != 0 || z.depth_info.array_mode != 0 ||
z.z_info.tile_mode_index != 0 || z.z_info.zrange_precision > 1 || z.depth_info.pipe_config != 0 || z.depth_info.bank_width != 0 ||
z.depth_view.current_mip_level != 0 || z.depth_info.addr5_swizzle_mask != 0 || z.depth_info.bank_height != 0 || z.depth_info.macro_tile_aspect != 0 ||
z.depth_info.array_mode != 0 || z.depth_info.pipe_config != 0 || z.depth_info.num_banks != 0 || z.htile_surface.linear != 0 ||
z.depth_info.bank_width != 0 || z.depth_info.bank_height != 0 || z.htile_surface.full_cache != 0 || z.htile_surface.htile_uses_preload_win != 0 ||
z.depth_info.macro_tile_aspect != 0 || z.depth_info.num_banks != 0 || z.htile_surface.preload != 0 || z.htile_surface.prefetch_width != 0 ||
z.htile_surface.linear != 0 || z.htile_surface.full_cache != 0 || z.htile_surface.prefetch_height != 0 || z.htile_surface.dst_outside_zero_to_one != 0 ||
z.htile_surface.htile_uses_preload_win != 0 || z.htile_surface.preload != 0 || z.z_read_base_addr == 0 || z.z_write_base_addr != z.z_read_base_addr ||
z.htile_surface.prefetch_width != 0 || z.htile_surface.prefetch_height != 0 || (z.z_read_base_addr & 0xffffu) != 0 ||
z.htile_surface.dst_outside_zero_to_one != 0 || z.z_read_base_addr == 0 ||
z.z_write_base_addr != z.z_read_base_addr || (z.z_read_base_addr & 0xffffu) != 0 ||
dc.zfunc > static_cast<uint8_t>(vk::CompareOp::eAlways)) { dc.zfunc > static_cast<uint8_t>(vk::CompareOp::eAlways)) {
DepthFatal("unsupported depth register state"); DepthFatal("unsupported depth register state");
} }
if (msaa_compat) {
static std::atomic<uint32_t> logged_fragments = 0;
const uint32_t bit = 1u << z.z_info.num_samples;
if ((logged_fragments.fetch_or(bit, std::memory_order_relaxed) & bit) == 0) {
LOGF("DepthTarget: compatibility: rendering PS5 %ux depth fragments as single-sample\n",
bit);
}
}
if (has_stencil) { if (has_stencil) {
// Prospero defines Hi-Stencil as HTile-backed acceleration of the logical stencil plane.
// Keep the plane native in Vulkan while tracking HTile separately.
if (z.stencil_info.format != Prospero::GpuEnumValue(Prospero::StencilFormat::k8UInt) || if (z.stencil_info.format != Prospero::GpuEnumValue(Prospero::StencilFormat::k8UInt) ||
z.stencil_info.tile_mode_index != 0 || z.stencil_info.tile_split != 0 || !htile_stencil_compat || z.stencil_read_base_addr == 0 ||
!htile_stencil_compat || z.stencil_info.texture_compatible_stencil ||
z.stencil_read_base_addr == 0 ||
z.stencil_write_base_addr != z.stencil_read_base_addr || z.stencil_write_base_addr != z.stencil_read_base_addr ||
(z.stencil_read_base_addr & 0xffffu) != 0 || z.depth_view.stencil_write_disable) { (z.stencil_read_base_addr & 0xffffu) != 0 || z.depth_view.stencil_write_disable) {
DepthFatal("unsupported stencil attachment state"); DepthFatal("unsupported stencil attachment state");
} }
if (!z.stencil_info.tile_stencil_disable) {
static std::atomic_bool logged = false;
if (!logged.load(std::memory_order_relaxed) &&
!logged.exchange(true, std::memory_order_relaxed)) {
LOGF("DepthTarget: compatibility: using native stencil with PS5 HTILE "
"acceleration\n");
}
}
} else if (z.stencil_read_base_addr != 0 || z.stencil_write_base_addr != 0 || } else if (z.stencil_read_base_addr != 0 || z.stencil_write_base_addr != 0 ||
z.stencil_info.tile_mode_index != 0 || z.stencil_info.tile_split != 0 || !htile_stencil_compat ||
!htile_stencil_compat || z.stencil_info.texture_compatible_stencil) { z.stencil_info.texture_compatibility !=
Prospero::TextureCompatibleStencil::kDisable) {
DepthFatal("stencil state without an active stencil attachment"); DepthFatal("stencil state without an active stencil attachment");
} }
if (has_htile) { if (has_htile) {
@@ -236,55 +213,34 @@ void ResolveRenderDepthTarget(uint64_t submit_id, CommandBuffer* buffer, const H
DepthFatal("unsupported depth/stencil format pair"); DepthFatal("unsupported depth/stencil format pair");
} }
const auto ideal_format = DepthAttachmentFormat(*policy, has_stencil); const auto ideal_format = DepthAttachmentFormat(*policy, has_stencil);
r->format = r.format = ResolveHostDepthAttachmentFormat(buffer, *policy, has_stencil, samples);
ResolveHostDepthAttachmentFormat(g_render_ctx->GetGraphicCtx(), *policy, has_stencil); if (r.format == vk::Format::eUndefined) {
if (r->format == vk::Format::eUndefined) {
DepthFatal("no host depth/stencil format supports required usage for %s", DepthFatal("no host depth/stencil format supports required usage for %s",
VulkanToString(ideal_format).c_str()); VulkanToString(ideal_format).c_str());
} }
const uint32_t guest_format = Prospero::GpuEnumValue(policy->guest_format); const uint32_t guest_format = Prospero::GpuEnumValue(policy->guest_format);
const uint32_t bytes = policy->bytes_per_element; const uint32_t bytes = policy->bytes_per_element;
const auto pitch = TileGetTexturePitch(guest_format, width, 1, const auto pitch = TileGetDepthPitch(width, bytes, z.z_info.num_samples);
Prospero::GpuEnumValue(Prospero::TileMode::kDepth));
if (z.pitch_height_valid && ((static_cast<uint64_t>(z.pitch_div8_minus1) + 1u) * 8u != pitch || if (z.pitch_height_valid && ((static_cast<uint64_t>(z.pitch_div8_minus1) + 1u) * 8u != pitch ||
(static_cast<uint64_t>(z.height_div8_minus1) + 1u) * 8u != (static_cast<uint64_t>(z.height_div8_minus1) + 1u) * 8u !=
((static_cast<uint64_t>(height) + 7u) & ~7ull))) { ((static_cast<uint64_t>(height) + 7u) & ~7ull))) {
DepthFatal("encoded depth pitch or height mismatch"); DepthFatal("encoded depth pitch or height mismatch");
} }
const uint32_t block_width = bytes == 2 ? 256u : 128u; TileSizeAlign depth_size {};
const uint64_t padded_width = TileSizeAlign stencil_size {};
(static_cast<uint64_t>(pitch) + block_width - 1u) & ~(block_width - 1u); TileSizeAlign htile_size {};
const uint64_t padded_height = if (!TileGetDepthSize(width, height, 0, z.z_info.format, z.stencil_info.format, has_htile,
(static_cast<uint64_t>(height) + 127u) & ~static_cast<uint64_t>(127u); stencil_size, htile_size, depth_size, z.z_info.num_samples) ||
if (padded_width > UINT64_MAX / padded_height || depth_size.align != 65536 || depth_size.size == 0 ||
padded_width * padded_height > UINT64_MAX / bytes) { (has_stencil != (stencil_size.align == 65536 && stencil_size.size != 0)) ||
DepthFatal("depth footprint overflow"); (has_htile != (htile_size.align == 32768 && htile_size.size != 0))) {
DepthFatal("unsupported depth/stencil/HTile footprint");
} }
const uint64_t expected_size = padded_width * padded_height * bytes; if (z.pitch_height_valid &&
TileSizeAlign depth_size {}; (static_cast<uint64_t>(z.slice_div64_minus1) + 1u) * 64u != depth_size.size) {
TileSizeAlign stencil_size {};
TileSizeAlign htile_size {};
if (has_stencil || has_htile) {
if (!TileGetDepthSize(width, height, 0, z.z_info.format, z.stencil_info.format, has_htile,
&stencil_size, &htile_size, &depth_size) ||
depth_size.align != 65536 || depth_size.size != expected_size ||
(has_stencil != (stencil_size.align == 65536 && stencil_size.size != 0)) ||
(has_htile != (htile_size.align == 32768 && htile_size.size != 0))) {
DepthFatal("unsupported depth/stencil/HTile footprint");
}
} else {
TileGetTextureTotalSize(guest_format, width, height, 1, pitch, 1,
Prospero::GpuEnumValue(Prospero::TileMode::kDepth), false,
&depth_size);
}
if (expected_size == 0 || expected_size > UINT32_MAX || depth_size.align != 65536 ||
depth_size.size != expected_size ||
(z.pitch_height_valid &&
(static_cast<uint64_t>(z.slice_div64_minus1) + 1u) * 64u != expected_size)) {
DepthFatal("depth footprint mismatch: extent=%ux%u pitch=%u expected=0x%016" PRIx64 DepthFatal("depth footprint mismatch: extent=%ux%u pitch=%u expected=0x%016" PRIx64
" calculated=0x%016" PRIx64 "/0x%016" PRIx64 " align=0x%016" PRIx64 " encoded_valid=%u encoded=0x%016" PRIx64,
" encoded_valid=%u encoded=0x%016" PRIx64, width, height, pitch, depth_size.size, depth_size.align,
width, height, pitch, expected_size, depth_size.size, depth_size.align,
z.pitch_height_valid ? 1u : 0u, z.pitch_height_valid ? 1u : 0u,
(static_cast<uint64_t>(z.slice_div64_minus1) + 1u) * 64u); (static_cast<uint64_t>(z.slice_div64_minus1) + 1u) * 64u);
} }
@@ -301,39 +257,31 @@ void ResolveRenderDepthTarget(uint64_t submit_id, CommandBuffer* buffer, const H
(has_htile && htile_backing_size > TRACKER_ADDRESS_SIZE - z.htile_data_base_addr)) { (has_htile && htile_backing_size > TRACKER_ADDRESS_SIZE - z.htile_data_base_addr)) {
DepthFatal("layered depth backing range is invalid"); DepthFatal("layered depth backing range is invalid");
} }
r->htile = has_htile; r.htile = has_htile;
r->width = width; r.width = width;
r->height = height; r.height = height;
r->depth_buffer_size = depth_backing_size; r.samples = samples;
r->depth_buffer_vaddr = z.z_read_base_addr; r.depth_buffer_size = depth_backing_size;
r->stencil_buffer_size = has_stencil ? stencil_backing_size : 0; r.depth_buffer_vaddr = z.z_read_base_addr;
r->stencil_buffer_vaddr = has_stencil ? z.stencil_read_base_addr : 0; r.stencil_buffer_size = has_stencil ? stencil_backing_size : 0;
r->htile_buffer_size = has_htile ? htile_backing_size : 0; r.stencil_buffer_vaddr = has_stencil ? z.stencil_read_base_addr : 0;
r->htile_buffer_vaddr = has_htile ? z.htile_data_base_addr : 0; r.htile_buffer_size = has_htile ? htile_backing_size : 0;
auto* cache = g_render_ctx->GetTextureCache(); r.htile_buffer_vaddr = has_htile ? z.htile_data_base_addr : 0;
if (has_htile) { r.depth_clear_enable = rc.depth_clear_enable;
cache->RegisterMeta(r->htile_buffer_vaddr, r->htile_buffer_size, view.image_layers); r.depth_meta_clear_enable = false;
} r.depth_load_clear_enable = r.depth_clear_enable;
if (has_htile && rc.depth_clear_enable && !cache->ClearMeta(z.htile_data_base_addr)) { r.depth_clear_value = hw.GetDepthClearValue();
DepthFatal("failed to acquire HTile metadata for a depth clear"); r.depth_test_enable = dc.z_enable;
} r.depth_write_enable = dc.z_write_enable && !z.depth_view.depth_write_disable;
const bool meta_clear = r.depth_compare_op = static_cast<vk::CompareOp>(dc.zfunc);
has_htile && cache->IsMetaCleared(z.htile_data_base_addr, z.depth_view.slice_start);
r->depth_clear_enable = rc.depth_clear_enable;
r->depth_meta_clear_enable = meta_clear;
r->depth_load_clear_enable = r->depth_clear_enable || r->depth_meta_clear_enable;
r->depth_clear_value = hw.GetDepthClearValue();
r->depth_test_enable = dc.z_enable;
r->depth_write_enable = dc.z_write_enable && !z.depth_view.depth_write_disable;
r->depth_compare_op = static_cast<vk::CompareOp>(dc.zfunc);
r->depth_bounds_test_enable = dc.depth_bounds_enable; r.depth_bounds_test_enable = dc.depth_bounds_enable;
r->depth_min_bounds = hw.GetDepthBoundsMin(); r.depth_min_bounds = hw.GetDepthBoundsMin();
r->depth_max_bounds = hw.GetDepthBoundsMax(); r.depth_max_bounds = hw.GetDepthBoundsMax();
r->stencil_clear_enable = rc.stencil_clear_enable; r.stencil_clear_enable = rc.stencil_clear_enable;
r->stencil_clear_value = hw.GetStencilClearValue(); r.stencil_clear_value = hw.GetStencilClearValue();
r->stencil_test_enable = dc.stencil_enable; r.stencil_test_enable = dc.stencil_enable;
if (dc.stencil_enable) { if (dc.stencil_enable) {
if (dc.stencilfunc > static_cast<uint8_t>(vk::CompareOp::eAlways) || if (dc.stencilfunc > static_cast<uint8_t>(vk::CompareOp::eAlways) ||
(dc.backface_enable && (dc.backface_enable &&
@@ -345,71 +293,119 @@ void ResolveRenderDepthTarget(uint64_t submit_id, CommandBuffer* buffer, const H
sm.stencil_opval_bf != sm.stencil_testval_bf)) { sm.stencil_opval_bf != sm.stencil_testval_bf)) {
DepthFatal("unsupported stencil compare or replacement state"); DepthFatal("unsupported stencil compare or replacement state");
} }
r->stencil_static_front = { r.stencil_static_front = {
ConvertStencilOp(sc.stencil_fail), ConvertStencilOp(sc.stencil_zpass), ConvertStencilOp(sc.stencil_fail), ConvertStencilOp(sc.stencil_zpass),
ConvertStencilOp(sc.stencil_zfail), static_cast<vk::CompareOp>(dc.stencilfunc)}; ConvertStencilOp(sc.stencil_zfail), static_cast<vk::CompareOp>(dc.stencilfunc)};
r->stencil_dynamic_front = {sm.stencil_mask, r.stencil_dynamic_front = {sm.stencil_mask,
rc.stencil_clear_enable ? 0u : sm.stencil_writemask, rc.stencil_clear_enable ? 0u : sm.stencil_writemask,
sm.stencil_testval}; sm.stencil_testval};
if (dc.backface_enable) { if (dc.backface_enable) {
r->stencil_static_back = {ConvertStencilOp(sc.stencil_fail_bf), r.stencil_static_back = {ConvertStencilOp(sc.stencil_fail_bf),
ConvertStencilOp(sc.stencil_zpass_bf), ConvertStencilOp(sc.stencil_zpass_bf),
ConvertStencilOp(sc.stencil_zfail_bf), ConvertStencilOp(sc.stencil_zfail_bf),
static_cast<vk::CompareOp>(dc.stencilfunc_bf)}; static_cast<vk::CompareOp>(dc.stencilfunc_bf)};
r->stencil_dynamic_back = {sm.stencil_mask_bf, r.stencil_dynamic_back = {sm.stencil_mask_bf,
rc.stencil_clear_enable ? 0u : sm.stencil_writemask_bf, rc.stencil_clear_enable ? 0u : sm.stencil_writemask_bf,
sm.stencil_testval_bf}; sm.stencil_testval_bf};
} else { } else {
r->stencil_static_back = r->stencil_static_front; r.stencil_static_back = r.stencil_static_front;
r->stencil_dynamic_back = r->stencil_dynamic_front; r.stencil_dynamic_back = r.stencil_dynamic_front;
} }
} }
r->vaddr_num = has_stencil ? 2 : 1; r.vaddr_num = has_stencil ? 2 : 1;
r->vaddr[0] = r->depth_buffer_vaddr; r.vaddr[0] = r.depth_buffer_vaddr;
r->size[0] = r->depth_buffer_size; r.size[0] = r.depth_buffer_size;
if (has_stencil) { if (has_stencil) {
r->vaddr[1] = r->stencil_buffer_vaddr; r.vaddr[1] = r.stencil_buffer_vaddr;
r->size[1] = r->stencil_buffer_size; r.size[1] = r.stencil_buffer_size;
}
DepthTargetInfo info {};
info.address = r->depth_buffer_vaddr;
info.size = r->depth_buffer_size;
info.stencil_address = r->stencil_buffer_vaddr;
info.stencil_size = r->stencil_buffer_size;
info.htile_address = r->htile_buffer_vaddr;
info.htile_size = r->htile_buffer_size;
info.format = r->format;
info.guest_format = guest_format;
info.width = width;
info.height = height;
info.pitch = pitch;
info.bytes_per_element = bytes;
info.tile_mode = Prospero::GpuEnumValue(Prospero::TileMode::kDepth);
info.layers = view.image_layers;
info.depth_load_clear = r->depth_load_clear_enable;
info.stencil_load_clear = rc.stencil_clear_enable;
info.stencil_access =
r->stencil_clear_enable ||
(r->stencil_test_enable &&
(stencil_face_accesses_attachment(r->stencil_static_front, r->stencil_dynamic_front) ||
stencil_face_accesses_attachment(r->stencil_static_back, r->stencil_dynamic_back)));
info.stencil_htile_compressed =
has_stencil && has_htile && !z.stencil_info.tile_stencil_disable;
r->vulkan_buffer = cache->FindDepthTarget(buffer, g_render_ctx->GetGraphicCtx(), info);
r->vulkan_view = cache->GetDepthTargetAttachmentView(
g_render_ctx->GetGraphicCtx(), r->vulkan_buffer, view.base_layer, view.layer_count);
if (meta_clear && !cache->TouchMeta(z.htile_data_base_addr, z.depth_view.slice_start, false)) {
DepthFatal("failed to consume HTile clear state");
} }
TextureCache::ImageDesc desc {};
desc.type = TextureCache::BindingType::DepthTarget;
desc.info.data = {r.depth_buffer_vaddr, r.depth_buffer_size};
desc.info.stencil = {r.stencil_buffer_vaddr, r.stencil_buffer_size};
desc.info.pixel_format = r.format;
desc.info.guest_format = guest_format;
desc.info.type = Prospero::ImageType::kColor2D;
desc.info.extent = {width, height, 1};
desc.info.resources = {1, view.image_layers};
desc.info.pitch = pitch;
desc.info.bytes_per_block = bytes;
desc.info.samples = samples;
desc.info.tile_mode = Prospero::GpuEnumValue(Prospero::TileMode::kDepth);
desc.info.mip_layout[0] = {0, r.depth_buffer_size, pitch, height};
desc.info.metadata.range = {r.htile_buffer_vaddr, r.htile_buffer_size};
desc.info.metadata.kind = has_htile ? ImageMetadataKind::Htile : ImageMetadataKind::None;
desc.info.metadata.stencil_compressed =
has_stencil && has_htile && !z.stencil_info.htile_stencil_disabled;
desc.view_info.format = r.format;
desc.view_info.type =
view.layer_count == 1 ? vk::ImageViewType::e2D : vk::ImageViewType::e2DArray;
desc.view_info.aspect = ImageViewOps::DepthAspectMask(r.format);
desc.view_info.base_level = 0;
desc.view_info.level_count = 1;
desc.view_info.base_layer = view.base_layer;
desc.view_info.layer_count = view.layer_count;
desc.view_info.usage = vk::ImageUsageFlagBits::eDepthStencilAttachment;
r.desc = std::move(desc);
auto& cache = m_context.GetTextureCache();
r.image_id = cache.FindImage(r.desc);
r.image_view = nullptr;
BindRenderTarget(r.image_id);
} }
void MarkRenderTargetGpuWritten(const RenderDepthInfo& target) { vk::ImageAspectFlags RenderDepthInfo::AttachmentWriteAspects() const {
const bool with_depth = if (format == vk::Format::eUndefined) {
target.format != vk::Format::eUndefined && target.vulkan_buffer != nullptr; return {};
if (with_depth && !depth_attachment_read_only(&target)) {
g_render_ctx->GetTextureCache()->MarkGpuWritten(target.vulkan_buffer);
} }
const auto available = ImageViewOps::DepthAspectMask(format);
vk::ImageAspectFlags writes {};
if ((available & vk::ImageAspectFlagBits::eDepth) &&
(depth_load_clear_enable || (depth_test_enable && depth_write_enable))) {
writes |= vk::ImageAspectFlagBits::eDepth;
}
if (!(available & vk::ImageAspectFlagBits::eStencil)) {
return writes;
}
const auto face_writes = [&](const PipelineStencilStaticState& state,
const PipelineStencilDynamicState& dynamic) {
if (dynamic.writeMask == 0) {
return false;
}
bool can_pass = state.compareOp != vk::CompareOp::eNever;
bool can_fail = state.compareOp != vk::CompareOp::eAlways;
if (dynamic.compareMask == 0) {
switch (state.compareOp) {
case vk::CompareOp::eEqual:
case vk::CompareOp::eLessOrEqual:
case vk::CompareOp::eGreaterOrEqual:
case vk::CompareOp::eAlways:
can_pass = true;
can_fail = false;
break;
case vk::CompareOp::eNever:
case vk::CompareOp::eLess:
case vk::CompareOp::eGreater:
case vk::CompareOp::eNotEqual:
can_pass = false;
can_fail = true;
break;
default: break;
}
}
const bool depth_pass = !depth_test_enable || depth_compare_op != vk::CompareOp::eNever;
const bool depth_fail = depth_test_enable && depth_compare_op != vk::CompareOp::eAlways;
return (can_fail && state.failOp != vk::StencilOp::eKeep) ||
(can_pass && depth_pass && state.passOp != vk::StencilOp::eKeep) ||
(can_pass && depth_fail && state.depthFailOp != vk::StencilOp::eKeep);
};
if (stencil_clear_enable ||
(stencil_test_enable && (face_writes(stencil_static_front, stencil_dynamic_front) ||
face_writes(stencil_static_back, stencil_dynamic_back)))) {
writes |= vk::ImageAspectFlagBits::eStencil;
}
return writes;
} }
} // namespace Libs::Graphics } // namespace Libs::Graphics
@@ -2,6 +2,8 @@
#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/cache/textureCache.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/vulkanCommon.h" #include "graphics/host_gpu/vulkanCommon.h"
@@ -9,22 +11,19 @@
namespace Libs::Graphics { namespace Libs::Graphics {
class CommandBuffer; class RenderCommandBuffer;
struct DepthStencilVulkanImage;
namespace HW {
class Context;
} // namespace HW
inline constexpr bool depth_htile_stencil_acceleration_compatible(bool has_stencil, bool has_htile, inline constexpr bool depth_htile_stencil_acceleration_compatible(bool has_stencil, bool has_htile,
bool acceleration_disabled) { bool htile_stencil_disabled) {
return acceleration_disabled || (has_stencil && has_htile); return htile_stencil_disabled || (has_stencil && has_htile);
} }
struct RenderDepthInfo { struct RenderDepthInfo {
TextureCache::ImageDesc desc;
vk::Format format = vk::Format::eUndefined; vk::Format format = vk::Format::eUndefined;
uint32_t width = 0; uint32_t width = 0;
uint32_t height = 0; uint32_t height = 0;
uint32_t samples = 1;
bool htile = false; bool htile = false;
uint64_t depth_buffer_size = 0; uint64_t depth_buffer_size = 0;
uint64_t depth_buffer_vaddr = 0; uint64_t depth_buffer_vaddr = 0;
@@ -52,31 +51,46 @@ struct RenderDepthInfo {
PipelineStencilStaticState stencil_static_back; PipelineStencilStaticState stencil_static_back;
PipelineStencilDynamicState stencil_dynamic_front; PipelineStencilDynamicState stencil_dynamic_front;
PipelineStencilDynamicState stencil_dynamic_back; PipelineStencilDynamicState stencil_dynamic_back;
DepthStencilVulkanImage* vulkan_buffer = nullptr; ImageId image_id;
vk::ImageView vulkan_view = nullptr; vk::ImageView image_view = nullptr;
uint64_t vaddr[3] = {}; uint64_t vaddr[3] = {};
uint64_t size[3] = {}; uint64_t size[3] = {};
int vaddr_num = 0; int vaddr_num = 0;
[[nodiscard]] vk::ImageAspectFlags AttachmentWriteAspects() const;
}; };
inline bool depth_attachment_read_only(const RenderDepthInfo* depth) { inline bool depth_attachment_read_only(const RenderDepthInfo& depth) {
EXIT_IF(depth == nullptr); return !depth.AttachmentWriteAspects();
const bool stencil_write =
depth->stencil_test_enable &&
(depth->stencil_dynamic_front.writeMask != 0 || depth->stencil_dynamic_back.writeMask != 0);
return !depth->depth_load_clear_enable && !depth->stencil_clear_enable &&
!depth->depth_write_enable && !stencil_write;
} }
inline vk::ImageLayout depth_attachment_layout(const RenderDepthInfo* depth) { inline vk::ImageLayout depth_attachment_layout(const RenderDepthInfo& depth) {
return depth_attachment_read_only(depth) ? vk::ImageLayout::eDepthStencilReadOnlyOptimal const auto available = ImageViewOps::DepthAspectMask(depth.format);
: vk::ImageLayout::eDepthStencilAttachmentOptimal; const auto writes = depth.AttachmentWriteAspects();
const bool has_depth = static_cast<bool>(available & vk::ImageAspectFlagBits::eDepth);
const bool has_stencil = static_cast<bool>(available & vk::ImageAspectFlagBits::eStencil);
const bool depth_write = static_cast<bool>(writes & vk::ImageAspectFlagBits::eDepth);
const bool stencil_write = static_cast<bool>(writes & vk::ImageAspectFlagBits::eStencil);
if (!has_stencil) {
return depth_write ? vk::ImageLayout::eDepthAttachmentOptimal
: vk::ImageLayout::eDepthReadOnlyOptimal;
}
if (!has_depth) {
return stencil_write ? vk::ImageLayout::eStencilAttachmentOptimal
: vk::ImageLayout::eStencilReadOnlyOptimal;
}
if (depth_write && stencil_write) {
return vk::ImageLayout::eDepthStencilAttachmentOptimal;
}
if (depth_write) {
return vk::ImageLayout::eDepthAttachmentStencilReadOnlyOptimal;
}
if (stencil_write) {
return vk::ImageLayout::eDepthReadOnlyStencilAttachmentOptimal;
}
return vk::ImageLayout::eDepthStencilReadOnlyOptimal;
} }
void ResolveRenderDepthTarget(uint64_t submit_id, CommandBuffer* buffer, const HW::Context& hw,
RenderDepthInfo* r);
void MarkRenderTargetGpuWritten(const RenderDepthInfo& target);
} // namespace Libs::Graphics } // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_DEPTHRENDERTARGET_H_ #endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_DEPTHRENDERTARGET_H_
@@ -1,867 +0,0 @@
#include "graphics/host_gpu/renderer/descriptors.h"
#include "common/assert.h"
#include "common/common.h"
#include "common/file.h"
#include "common/logging/log.h"
#include "common/profiler.h"
#include "common/stringUtils.h"
#include "common/threads.h"
#include "graphics/guest_gpu/gpu_defs.h"
#include "graphics/guest_gpu/gpu_format.h"
#include "graphics/guest_gpu/graphicsRun.h"
#include "graphics/guest_gpu/hardwareContext.h"
#include "graphics/guest_gpu/tile.h"
#include "graphics/host_gpu/graphicContext.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/descriptorCache.h"
#include "graphics/host_gpu/renderer/framebufferCache.h"
#include "graphics/host_gpu/renderer/imageView.h"
#include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/host_gpu/renderer/renderTargetBarriers.h"
#include "graphics/host_gpu/renderer/shaderResourceBarrier.h"
#include "graphics/host_gpu/transfer.h"
#include "graphics/host_gpu/vma.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include "graphics/presentation/displayBuffer.h"
#include "graphics/shader/recompiler/BindingLayout.h"
#include "graphics/shader/recompiler/ResourceMaterialization.h"
#include "graphics/shader/recompiler/ShaderIR.h"
#include "graphics/shader/shader.h"
#include <algorithm>
#include <atomic>
#include <fmt/format.h>
#include <limits>
#include <span>
#ifdef min
#undef min
#endif
#ifdef max
#undef max
#endif
namespace Libs::Graphics {
using TextureVariant = DescriptorCache::TextureVariant;
static void BindNullStorageBuffer(CommandBuffer* cmd_buffer, BufferView* dst) {
EXIT_IF(cmd_buffer == nullptr || dst == nullptr);
dst->buffer =
g_render_ctx->GetBufferCache()->ObtainNullBuffer(cmd_buffer, g_render_ctx->GetGraphicCtx());
dst->offset = 0;
dst->range = 16;
}
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
static const char* VulkanImageTypeName(VulkanImageType type) {
switch (type) {
case VulkanImageType::VideoOut: return "VideoOut";
case VulkanImageType::DepthStencil: return "DepthStencil";
case VulkanImageType::Texture: return "Texture";
case VulkanImageType::StorageTexture: return "StorageTexture";
case VulkanImageType::RenderTexture: return "RenderTexture";
case VulkanImageType::Unknown:
default: return "Unknown";
}
}
static int SampledArrayViewIndex(const VulkanImage* image, int view_index) {
EXIT_IF(image == nullptr);
switch (view_index) {
case VulkanImage::VIEW_DEFAULT: return VulkanImage::VIEW_DEFAULT_ARRAY;
default: return view_index;
}
}
static int SelectSampledTextureArrayView(const VulkanImage* image, int base_view) {
const int array_view = SampledArrayViewIndex(image, base_view);
if (image->image_view[array_view] == nullptr) {
EXIT("missing sampled array image view: image_type=%s base_view=%d array_view=%d "
"layers=%u\n",
VulkanImageTypeName(image->type), base_view, array_view, image->layers);
}
return array_view;
}
static bool TextureVariantIsUint(TextureVariant variant) {
return variant == TextureVariant::Uint2D || variant == TextureVariant::UintArray ||
variant == TextureVariant::Uint3D;
}
static bool TextureVariantIsArray(TextureVariant variant) {
return variant == TextureVariant::FloatArray || variant == TextureVariant::UintArray;
}
static bool TextureVariantIs3D(TextureVariant variant) {
return variant == TextureVariant::Float3D || variant == TextureVariant::Uint3D;
}
static int TextureVariantDefaultView(TextureVariant variant) {
return TextureVariantIsArray(variant) ? VulkanImage::VIEW_DEFAULT_ARRAY
: VulkanImage::VIEW_DEFAULT;
}
static VulkanImage* GetDummySampledTexture(TextureVariant variant) {
return g_render_ctx->GetTextureCache()->GetDummySampledTexture(TextureVariantIsUint(variant),
TextureVariantIs3D(variant));
}
static VulkanImage* GetDummyStorageTexture(TextureVariant variant) {
return g_render_ctx->GetTextureCache()->GetDummyStorageTexture(TextureVariantIsUint(variant),
TextureVariantIs3D(variant));
}
static void CopyNativeDescriptor(const ShaderRecompiler::IR::DescriptorValue& source,
std::span<uint32_t> destination) {
EXIT_IF(source.dword_count != destination.size());
std::copy_n(source.dwords.begin(), destination.size(), destination.begin());
}
static BufferView NativeStorageBuffer(uint64_t submit_id, CommandBuffer* command_buffer,
const ShaderBufferResource& descriptor,
const ShaderRecompiler::IR::BufferResource& resource) {
BufferView result;
// Regression
// Bind a null buffer when these four dwords are
// the tracked prefix of an active image sharp. Image validity is encoded by dword 3 bit 31.
/*if (resource.image_alias != ShaderRecompiler::IR::BufferResource::NoImageAlias &&
(descriptor.Type() & 2u) != 0) {
BindNullStorageBuffer(command_buffer, &result);
return result;
}
// Buffer TYPE is zero. A nonzero value means a buffer instruction received the first four
// dwords of an image sharp without a tracked image alias; support the known write-only path.
if (descriptor.Type() != 0) {
ShaderTextureResource texture {};
std::copy_n(descriptor.fields, 4, texture.fields);
const auto width = static_cast<uint32_t>(texture.Width5()) + 1u;
const auto height = static_cast<uint32_t>(texture.Height5()) + 1u;
const auto format = texture.Format();
const auto tile = texture.TileMode();
const auto type = texture.Type();
const auto address = texture.Base40();
const auto pitch = TileGetTexturePitch(format, width, 1, tile);
TileSizeAlign footprint {};
TileGetTextureTotalSize(format, width, height, 1, pitch, 1, tile, false, &footprint);
const bool supported =
resource.formatted && resource.written && !resource.read && !resource.atomic &&
format == Prospero::GpuEnumValue(Prospero::BufferFormat::k8_8_8_8UInt) &&
tile == Prospero::GpuEnumValue(Prospero::TileMode::kRenderTarget) &&
type == Prospero::GpuEnumValue(Prospero::ImageType::kColor2D) &&
texture.DstSelXYZW() == DstSel(4, 5, 6, 7) && address != 0 && footprint.size != 0 &&
footprint.align == 65536;
if (!supported) {
EXIT("unsupported texture descriptor used as storage buffer: type=%u format=%u"
" tile=%u swizzle=0x%03x extent=%ux%u read=%d written=%d formatted=%d atomic=%d\n",
type, format, tile, texture.DstSelXYZW(), width, height, resource.read,
resource.written, resource.formatted, resource.atomic);
}
auto* ctx = g_render_ctx->GetGraphicCtx();
g_render_ctx->GetBufferCache()->ValidateGpuAccess(address, footprint.size, false, true);
auto binding = g_render_ctx->GetBufferCache()->ObtainBuffer(
command_buffer, ctx, address, footprint.size, true, false, true);
const auto alignment = ctx->StorageMinAlignment();
if (alignment == 0 || binding.second % alignment != 0 ||
footprint.size > ctx->GetPhysicalDeviceProperties().limits.maxStorageBufferRange) {
EXIT("texture-backed storage buffer binding is unsupported: addr=0x%016" PRIx64
" size=0x%016" PRIx64 " offset=0x%016" PRIx64 " alignment=0x%016" PRIx64 "\n",
address, footprint.size, static_cast<uint64_t>(binding.second),
static_cast<uint64_t>(alignment));
}
result.buffer = binding.first;
result.offset = binding.second;
result.range = footprint.size;
return result;
}*/
const auto address = descriptor.Base48();
const auto stride = descriptor.Stride();
const auto records = descriptor.NumRecords();
if (stride != 0 && records > UINT64_MAX / stride) {
EXIT("storage buffer descriptor footprint overflow\n");
}
const auto size = stride != 0 ? static_cast<uint64_t>(stride) * records : records;
if (address == 0 || size == 0) {
BindNullStorageBuffer(command_buffer, &result);
return result;
}
auto* const ctx = g_render_ctx->GetGraphicCtx();
const auto alignment = ctx->StorageMinAlignment();
if (alignment == 0 || size > ctx->GetPhysicalDeviceProperties().limits.maxStorageBufferRange ||
BufferCache::CACHING_PAGE_SIZE % alignment != 0) {
EXIT("storage buffer range or device alignment is unsupported\n");
}
(void)submit_id;
auto binding = g_render_ctx->GetBufferCache()->ObtainBuffer(
command_buffer, ctx, address, size, resource.written, resource.read, resource.formatted);
if (binding.second % alignment != 0) {
EXIT("storage buffer binding is not device-aligned\n");
}
result.buffer = binding.first;
result.offset = binding.second;
result.range = static_cast<vk::DeviceSize>(size);
return result;
}
static BufferView
NativeAddressBuffer(uint64_t submit_id, CommandBuffer* command_buffer,
const ShaderRecompiler::IR::AddressResource& resource,
const ShaderRecompiler::IR::ResourceSnapshot::Address& address) {
BufferView result;
if (address.binding_base == 0) {
BindNullStorageBuffer(command_buffer, &result);
return result;
}
if (resource.written) {
EXIT("writable address resources are unsupported\n");
}
const auto limit = resource.kind == ShaderRecompiler::IR::ResourceKind::Flat
? ShaderRecompiler::IR::FlatAddressWindowSize
: static_cast<uint64_t>(g_render_ctx->GetGraphicCtx()
->GetPhysicalDeviceProperties()
.limits.maxStorageBufferRange);
uint64_t size = 0;
const auto access = HostMemoryAccess::Mapped;
if (!HostMemoryQueryRange(address.binding_base, limit, access, &size)) {
EXIT("address resource is not host-accessible: base=0x%016" PRIx64 "\n",
address.binding_base);
}
auto* const ctx = g_render_ctx->GetGraphicCtx();
const auto alignment = ctx->StorageMinAlignment();
if (alignment == 0 || size > ctx->GetPhysicalDeviceProperties().limits.maxStorageBufferRange ||
BufferCache::GetBufferOffset(address.binding_base) % alignment != 0) {
EXIT("address resource range or alignment is unsupported\n");
}
(void)submit_id;
auto binding = g_render_ctx->GetBufferCache()->ObtainBuffer(command_buffer, ctx,
address.binding_base, size);
result.buffer = binding.first;
result.offset = binding.second;
result.range = static_cast<vk::DeviceSize>(size);
return result;
}
static TextureVariant NativeTextureVariant(const ShaderRecompiler::IR::ImageResource& resource) {
const bool uint_image = resource.kind == ShaderRecompiler::IR::ResourceKind::ImageUint ||
resource.kind == ShaderRecompiler::IR::ResourceKind::StorageImageUint;
switch (resource.dimension) {
case ShaderRecompiler::Decoder::ImageDimension::Dim3D:
return uint_image ? TextureVariant::Uint3D : TextureVariant::Float3D;
case ShaderRecompiler::Decoder::ImageDimension::Dim2DArray:
return uint_image ? TextureVariant::UintArray : TextureVariant::FloatArray;
default: return uint_image ? TextureVariant::Uint2D : TextureVariant::Float2D;
}
}
static bool IsSupportedSampledColorResource(const ShaderRecompiler::IR::ImageResource& resource) {
bool supported_dimension = false;
switch (resource.dimension) {
case ShaderRecompiler::Decoder::ImageDimension::Dim2D:
case ShaderRecompiler::Decoder::ImageDimension::Dim2DArray:
supported_dimension = true;
break;
default: break;
}
const bool sampled_kind = resource.kind == ShaderRecompiler::IR::ResourceKind::Image ||
resource.kind == ShaderRecompiler::IR::ResourceKind::ImageUint;
return sampled_kind && supported_dimension &&
resource.mip_mode == ShaderRecompiler::IR::ImageMipMode::None && resource.read &&
!resource.written && !resource.atomic && !resource.depth_compare;
}
TargetTextureViewInfo ResolveTargetTextureView(const ShaderRecompiler::IR::ImageResource& resource,
Prospero::ImageType type, uint32_t base_layer,
uint32_t image_layers) {
switch (type) {
case Prospero::ImageType::kColor2D:
return resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2D &&
base_layer == 0 && image_layers == 1
? TargetTextureViewInfo {vk::ImageViewType::e2D, 0, 1}
: TargetTextureViewInfo {};
case Prospero::ImageType::kColor2DArray:
if (resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2D &&
base_layer == 0 && image_layers == 1) {
return {vk::ImageViewType::e2D, 0, 1};
}
return resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2DArray &&
base_layer < image_layers
? TargetTextureViewInfo {vk::ImageViewType::e2DArray, base_layer,
image_layers - base_layer}
: TargetTextureViewInfo {};
default: return {};
}
}
bool IsSupportedSampledVideoOutView(const ShaderRecompiler::IR::ImageResource& resource,
const ShaderTextureResource& descriptor,
const VulkanImage& image) {
return image.type == VulkanImageType::VideoOut && image.layers == 1 &&
IsSupportedSampledColorResource(resource) &&
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2D &&
descriptor.Type() == Prospero::GpuEnumValue(Prospero::ImageType::kColor2D) &&
descriptor.Depth() == 0 && descriptor.BaseArray5() == 0;
}
bool IsSupportedDepthTargetDescriptor(const ShaderTextureResource& descriptor,
const VulkanImage& image) {
const auto width = static_cast<uint32_t>(descriptor.Width5()) + 1u;
const auto height = static_cast<uint32_t>(descriptor.Height5()) + 1u;
const auto pitch = TileGetTexturePitch(descriptor.Format(), width, 1, descriptor.TileMode());
const auto type = static_cast<Prospero::ImageType>(descriptor.Type());
const bool supported_type =
type == Prospero::ImageType::kColor2D || type == Prospero::ImageType::kColor2DArray;
return image.type == VulkanImageType::DepthStencil && image.layers == 1 &&
width == image.extent.width && height == image.extent.height &&
descriptor.Depth() == 0 && descriptor.BaseLevel() == 0 && descriptor.LastLevel() == 0 &&
descriptor.MaxMip() == 0 && descriptor.MinLod() == 0 && descriptor.BaseArray5() == 0 &&
descriptor.TileMode() == Prospero::GpuEnumValue(Prospero::TileMode::kDepth) &&
supported_type && descriptor.BCSwizzle() == 0 && !descriptor.MsaaDepth() &&
pitch >= width && pitch == image.guest_pitch;
}
static bool IsSupportedDepthTextureEncoding(const ShaderTextureResource& descriptor) {
constexpr uint32_t field1_reserved_mask = 0x200fff00u;
constexpr uint32_t field2_reserved_mask = 0xf0003000u;
constexpr uint32_t field3_common = 0x01800000u;
constexpr uint32_t field5_expected = 0x00700000u;
const uint32_t field3_expected =
(descriptor.Type() << 28u) | field3_common | descriptor.DstSelXYZW();
return (descriptor.fields[1] & field1_reserved_mask) == 0 &&
(descriptor.fields[2] & field2_reserved_mask) == 0 &&
descriptor.fields[3] == field3_expected && descriptor.fields[4] == 0 &&
descriptor.fields[5] == field5_expected && descriptor.fields[6] == 0 &&
descriptor.fields[7] == 0;
}
static void ValidateDepthTargetBinding(const ShaderRecompiler::IR::ImageResource& resource,
const ShaderTextureResource& descriptor,
const VulkanImage* image, vk::Format view_format,
uint64_t size) {
const bool resource_ok = IsSupportedSampledDepthResource(resource);
const bool descriptor_ok =
image != nullptr && IsSupportedDepthTargetDescriptor(descriptor, *image);
const bool encoding_ok = IsSupportedDepthTextureEncoding(descriptor);
const bool format_ok = image != nullptr && IsSupportedSampledDepthFormat(
image->format, descriptor.Format(), view_format);
if (resource_ok && descriptor_ok && encoding_ok && format_ok && size != 0) {
return;
}
const auto descriptor_pitch =
TileGetTexturePitch(descriptor.Format(), static_cast<uint32_t>(descriptor.Width5()) + 1u, 1,
descriptor.TileMode());
EXIT("unsupported sampled depth target: resource=%d descriptor=%d encoding=%d format=%d "
"kind=%u dimension=%u mip_mode=%u read=%d written=%d atomic=%d compare=%d "
"guest_format=%u swizzle=0x%03x image_format=%d view_format=%d image_layers=%u "
"descriptor_type=%u base_array=%u depth=%u descriptor_pitch=%u target_pitch=%u "
"addr=0x%016" PRIx64 " size=0x%016" PRIx64
" dwords=%08x,%08x,%08x,%08x,%08x,%08x,%08x,%08x\n",
resource_ok, descriptor_ok, encoding_ok, format_ok, static_cast<uint32_t>(resource.kind),
static_cast<uint32_t>(resource.dimension), static_cast<uint32_t>(resource.mip_mode),
resource.read, resource.written, resource.atomic, resource.depth_compare,
descriptor.Format(), descriptor.DstSelXYZW(),
image == nullptr ? static_cast<int>(vk::Format::eUndefined)
: static_cast<int>(image->format),
static_cast<int>(view_format), image == nullptr ? 0u : image->layers, descriptor.Type(),
descriptor.BaseArray5(), descriptor.Depth(), descriptor_pitch,
image == nullptr ? 0u : image->guest_pitch, descriptor.Base40(), size,
descriptor.fields[0], descriptor.fields[1], descriptor.fields[2], descriptor.fields[3],
descriptor.fields[4], descriptor.fields[5], descriptor.fields[6], descriptor.fields[7]);
}
static bool IsSupportedStorageTextureDescriptor(const ShaderRecompiler::IR::ImageResource& resource,
const ShaderTextureResource& descriptor) {
const auto tile = descriptor.TileMode();
const auto width = static_cast<uint32_t>(descriptor.Width5()) + 1u;
const auto height = static_cast<uint32_t>(descriptor.Height5()) + 1u;
const auto depth = static_cast<uint32_t>(descriptor.Depth()) + 1u;
const bool is_2d = resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2D &&
descriptor.Type() == Prospero::GpuEnumValue(Prospero::ImageType::kColor2D) &&
descriptor.Depth() == 0;
const bool is_2d_array =
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2DArray &&
descriptor.Type() == Prospero::GpuEnumValue(Prospero::ImageType::kColor2DArray) &&
descriptor.BaseArray5() <= descriptor.Depth();
const bool is_3d = resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim3D &&
descriptor.Type() == Prospero::GpuEnumValue(Prospero::ImageType::kColor3D);
const bool supported_depth_tile =
tile == Prospero::GpuEnumValue(Prospero::TileMode::kDepth) && !resource.read &&
resource.kind == ShaderRecompiler::IR::ResourceKind::StorageImageUint &&
IsSupportedStorageDepthTile(descriptor.Format(), descriptor.Type(), width, height, depth);
const bool supported_tile = tile == Prospero::GpuEnumValue(Prospero::TileMode::kLinear) ||
tile == Prospero::GpuEnumValue(Prospero::TileMode::kRenderTarget) ||
supported_depth_tile;
const bool supported_swizzle =
IsSupportedStorageSwizzle(descriptor.Format(), descriptor.DstSelXYZW()) &&
(descriptor.DstSelXYZW() == DstSel(4, 5, 6, 7) || !resource.read);
const bool supported_mip_view = descriptor.BaseLevel() == 0 || is_2d;
return (is_2d || is_2d_array || is_3d) && supported_tile && supported_mip_view &&
descriptor.BaseLevel() == descriptor.LastLevel() &&
descriptor.LastLevel() <= descriptor.MaxMip() && descriptor.MinLod() == 0 &&
descriptor.BaseArray5() == 0 && supported_swizzle && descriptor.BCSwizzle() == 0 &&
!descriptor.MsaaDepth();
}
static bool IsSupportedStorageTextureEncoding(const ShaderTextureResource& descriptor) {
constexpr uint32_t field1_reserved_mask = 0x200fff00u;
constexpr uint32_t field2_reserved_mask = 0xf0003000u;
constexpr uint32_t field5_expected = 0x00700000u;
constexpr uint32_t field5_max_mip_mask = 0x000000f0u;
const uint32_t expected_field3 = descriptor.DstSelXYZW() |
(static_cast<uint32_t>(descriptor.BaseLevel()) << 12u) |
(static_cast<uint32_t>(descriptor.LastLevel()) << 16u) |
(static_cast<uint32_t>(descriptor.TileMode()) << 20u) |
(static_cast<uint32_t>(descriptor.Type()) << 28u);
return (descriptor.fields[1] & field1_reserved_mask) == 0 &&
(descriptor.fields[2] & field2_reserved_mask) == 0 &&
descriptor.fields[3] == expected_field3 && descriptor.fields[4] == descriptor.Depth() &&
(descriptor.fields[5] & ~field5_max_mip_mask) == field5_expected &&
descriptor.fields[6] == 0 && descriptor.fields[7] == 0;
}
void ValidateStorageTexture(const ShaderRecompiler::IR::ImageResource& resource,
const ShaderTextureResource& descriptor, uint64_t size) {
const auto format = descriptor.Format();
const bool resource_ok = IsSupportedStorageImageResource(resource);
const bool descriptor_ok = IsSupportedStorageTextureDescriptor(resource, descriptor);
const bool encoding_ok = IsSupportedStorageTextureEncoding(descriptor);
const bool uint_resource =
resource.kind == ShaderRecompiler::IR::ResourceKind::StorageImageUint;
const bool format_ok = Prospero::IsSupportedTextureFormat(format) &&
uint_resource == Prospero::IsUintTextureFormat(format);
if (resource_ok && descriptor_ok && encoding_ok && format_ok && size != 0) {
return;
}
EXIT("unsupported storage texture: resource=%d descriptor=%d encoding=%d format=%d "
"kind=%u dimension=%u mip_mode=%u atomic=%d compare=%d "
"base_level=%u last_level=%u max_mip=%u min_lod=%u base_array=%u bc=%u msaa=%d "
"depth_tile_shape=%d swizzle_ok=%d "
"addr=0x%016" PRIx64 " size=0x%016" PRIx64
" extent=%ux%ux%u type=%u format=%u tile=%u swizzle=0x%03x read=%d written=%d "
"dwords=%08x,%08x,%08x,%08x,%08x,%08x,%08x,%08x\n",
resource_ok, descriptor_ok, encoding_ok, format_ok, static_cast<uint32_t>(resource.kind),
static_cast<uint32_t>(resource.dimension), static_cast<uint32_t>(resource.mip_mode),
resource.atomic, resource.depth_compare, descriptor.BaseLevel(), descriptor.LastLevel(),
descriptor.MaxMip(), descriptor.MinLod(), descriptor.BaseArray5(), descriptor.BCSwizzle(),
descriptor.MsaaDepth(),
IsSupportedStorageDepthTile(descriptor.Format(), descriptor.Type(),
static_cast<uint32_t>(descriptor.Width5()) + 1u,
static_cast<uint32_t>(descriptor.Height5()) + 1u,
static_cast<uint32_t>(descriptor.Depth()) + 1u),
IsSupportedStorageSwizzle(descriptor.Format(), descriptor.DstSelXYZW()),
descriptor.Base40(), size, static_cast<uint32_t>(descriptor.Width5()) + 1u,
static_cast<uint32_t>(descriptor.Height5()) + 1u,
static_cast<uint32_t>(descriptor.Depth()) + 1u, descriptor.Type(), format,
descriptor.TileMode(), descriptor.DstSelXYZW(), resource.read, resource.written,
descriptor.fields[0], descriptor.fields[1], descriptor.fields[2], descriptor.fields[3],
descriptor.fields[4], descriptor.fields[5], descriptor.fields[6], descriptor.fields[7]);
}
void ValidateMetadataReuseTexture(const ShaderRecompiler::IR::ImageResource& resource,
const ShaderTextureResource& descriptor, uint64_t size) {
constexpr uint32_t field1_reserved = 0x200fff00u;
constexpr uint32_t field2_reserved = 0xf0003000u;
const auto format = descriptor.Format();
if (!IsSupportedSampledColorResource(resource) || size == 0 ||
(descriptor.fields[1] & field1_reserved) != 0 ||
(descriptor.fields[2] & field2_reserved) != 0 || descriptor.fields[3] != 0x90500facu ||
descriptor.fields[4] != 0 || descriptor.fields[5] != 0x00700000u ||
descriptor.fields[6] != 0 || descriptor.fields[7] != 0 ||
!Prospero::IsSupportedTextureFormat(format) || Prospero::IsUintTextureFormat(format)) {
EXIT("unsupported storage texture descriptor encoding\n");
}
}
static DescriptorCache::TextureBinding
NativeTexture(uint64_t submit_id, CommandBuffer* command_buffer,
const ShaderRecompiler::IR::ImageResource& resource,
const ShaderRecompiler::IR::DescriptorValue& value) {
ShaderTextureResource descriptor;
CopyNativeDescriptor(value, descriptor.fields);
const bool storage = resource.kind == ShaderRecompiler::IR::ResourceKind::StorageImage ||
resource.kind == ShaderRecompiler::IR::ResourceKind::StorageImageUint;
const auto variant = NativeTextureVariant(resource);
if (storage) {
ValidateStorageImageResource(resource);
}
if (descriptor.IsNull()) {
return storage ? DescriptorCache::TextureBinding {GetDummyStorageTexture(variant),
TextureVariantDefaultView(variant)}
: DescriptorCache::TextureBinding {GetDummySampledTexture(variant),
TextureVariantDefaultView(variant)};
}
const auto address = descriptor.Base40();
const auto width = static_cast<uint32_t>(descriptor.Width5()) + 1u;
const auto height = static_cast<uint32_t>(descriptor.Height5()) + 1u;
const auto base_level = descriptor.BaseLevel();
const auto last_level = descriptor.LastLevel();
const auto levels = static_cast<uint32_t>(descriptor.MaxMip()) + 1u;
if (base_level > last_level || last_level >= levels) {
EXIT("unsupported texture mip view: base=%u last=%u levels=%u\n", base_level, last_level,
levels);
}
const auto view_levels = last_level - base_level + 1u;
const auto depth = static_cast<uint32_t>(descriptor.Depth()) + 1u;
const auto tile = descriptor.TileMode();
const auto format = descriptor.Format();
const bool sampled_numeric_class =
storage || ((resource.kind == ShaderRecompiler::IR::ResourceKind::ImageUint) ==
Prospero::IsUintTextureFormat(format));
if (!storage &&
(resource.kind == ShaderRecompiler::IR::ResourceKind::Image ||
resource.kind == ShaderRecompiler::IR::ResourceKind::ImageUint) &&
!sampled_numeric_class) {
EXIT("sampled image numeric class mismatch: kind=%u format=%u addr=0x%016" PRIx64 "\n",
static_cast<uint32_t>(resource.kind), format, address);
}
const auto view_format = TextureGetFormat(format);
const auto type = static_cast<Prospero::ImageType>(descriptor.Type());
const auto target_view =
ResolveTargetTextureView(resource, type, descriptor.BaseArray5(), depth);
const auto pitch = TileGetTexturePitch(format, width, levels, tile);
const auto swizzle = descriptor.DstSelXYZW();
TileSizeAlign size;
TileGetTextureTotalSize(format, width, height, depth, pitch, levels, tile,
type == Prospero::ImageType::kColor3D, &size);
EXIT_NOT_IMPLEMENTED(size.size == 0 ||
(address & (static_cast<uint64_t>(size.align) - 1u)) != 0);
if (storage) {
ValidateStorageTexture(resource, descriptor, size.size);
g_render_ctx->GetBufferCache()->ValidateGpuAccess(address, size.size, resource.read,
resource.written);
}
VulkanImage* image = nullptr;
int view = VulkanImage::VIEW_DEFAULT;
vk::ImageView image_view = nullptr;
const bool check_depth = static_cast<Prospero::TileMode>(tile) == Prospero::TileMode::kDepth ||
descriptor.MsaaDepth();
if (image == nullptr) {
if (check_depth) {
image = g_render_ctx->GetTextureCache()->FindDepthTargetByRange(command_buffer, address,
size.size, true);
} else {
image = g_render_ctx->GetTextureCache()->FindRenderTargetByRange(command_buffer,
address, size.size);
}
if (image != nullptr) {
if (check_depth) {
const bool uint_reinterpret =
IsSupportedSampledDepthUintResource(resource) &&
IsSupportedDepthTargetDescriptor(descriptor, *image) &&
IsSupportedDepthTextureEncoding(descriptor) &&
IsDepthUintTextureReinterpretation(image->format, descriptor.Format(),
view_format);
const bool uint_storage_reinterpret =
storage && IsSupportedStorageImageResource(resource) &&
IsSupportedStorageTextureDescriptor(resource, descriptor) &&
IsSupportedStorageTextureEncoding(descriptor) &&
IsDepthUintTextureReinterpretation(image->format, descriptor.Format(),
view_format);
if (uint_reinterpret || uint_storage_reinterpret) {
image = nullptr;
} else {
const auto depth_view = ResolveTargetTextureView(
resource, type, descriptor.BaseArray5(), image->layers);
ValidateDepthTargetBinding(resource, descriptor, image, view_format, size.size);
if (depth_view.type ==
static_cast<vk::ImageViewType>(VK_IMAGE_VIEW_TYPE_MAX_ENUM)) {
EXIT("unsupported sampled depth target view: dimension=%u "
"descriptor_type=%u "
"base_array=%u image_layers=%u\n",
static_cast<uint32_t>(resource.dimension), descriptor.Type(),
descriptor.BaseArray5(), image->layers);
}
image_view = g_render_ctx->GetTextureCache()->GetDepthTargetSampledView(
g_render_ctx->GetGraphicCtx(), static_cast<DepthStencilVulkanImage*>(image),
view_format, swizzle, 0, 1, depth_view.type, depth_view.base_layer,
depth_view.layer_count);
}
} else {
if (!(storage ? IsSupportedStorageImageResource(resource)
: IsSupportedSampledColorResource(resource)) ||
image->type != VulkanImageType::RenderTexture || width != image->extent.width ||
height != image->extent.height ||
(storage ? levels != image->mip_levels || base_level != 0
: levels != image->mip_levels || base_level >= levels) ||
target_view.type ==
static_cast<vk::ImageViewType>(VK_IMAGE_VIEW_TYPE_MAX_ENUM) ||
target_view.base_layer >= image->layers ||
target_view.layer_count > image->layers - target_view.base_layer) {
EXIT("unsupported cached render-target image view: storage=%d resource=%u "
"dimension=%u"
" image_type=%u layers=%u extent=%ux%u/%ux%u depth=%u"
" levels=%u/%u base_level=%u base_array=%u descriptor_type=%u\n",
storage, static_cast<uint32_t>(resource.kind),
static_cast<uint32_t>(resource.dimension),
static_cast<uint32_t>(image->type), image->layers, width, height,
image->extent.width, image->extent.height, depth, levels,
image->mip_levels, base_level, descriptor.BaseArray5(),
static_cast<uint32_t>(type));
}
if (storage) {
view = SelectStorageColorView(image->format, view_format, swizzle);
image_view = g_render_ctx->GetTextureCache()->GetRenderTargetStorageView(
g_render_ctx->GetGraphicCtx(),
static_cast<RenderTextureVulkanImage*>(image), view_format, base_level,
view_levels, target_view.type, target_view.base_layer,
target_view.layer_count);
} else {
image_view = g_render_ctx->GetTextureCache()->GetSampledColorView(
g_render_ctx->GetGraphicCtx(), image, view_format, swizzle, base_level,
view_levels, target_view.type, target_view.base_layer,
target_view.layer_count);
}
}
if (image != nullptr && image_view == nullptr && image->image_view[view] == nullptr) {
EXIT("required cached texture image view is missing\n");
}
if (storage && image != nullptr) {
g_render_ctx->GetTextureCache()->MarkGpuWritten(image);
}
}
}
if (image == nullptr) {
const auto video = Presentation::DisplayBufferFind(address);
if (video.image != nullptr) {
if (storage) {
const bool exact =
resource.kind == ShaderRecompiler::IR::ResourceKind::StorageImageUint &&
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2D &&
!resource.read && resource.written && !resource.atomic &&
format == Prospero::GpuEnumValue(Prospero::BufferFormat::k8_8_8_8UInt) &&
view_format == vk::Format::eR8G8B8A8Uint && swizzle == DstSel(6, 5, 4, 7) &&
width == video.image->extent.width && height == video.image->extent.height &&
depth == 1 && levels == 1 && base_level == 0 && view_levels == 1 &&
type == Prospero::ImageType::kColor2D && video.size == size.size &&
video.pitch == pitch &&
(video.image->format == vk::Format::eR8G8B8A8Srgb ||
video.image->format == vk::Format::eB8G8R8A8Srgb) &&
video.image->image_view[VulkanImage::VIEW_STORAGE] != nullptr;
if (!exact) {
EXIT("unsupported storage access to video-out surface: format=%u view=%d"
" extent=%ux%u size=0x%016" PRIx64 " pitch=%u\n",
format, static_cast<int>(view_format), width, height, size.size, pitch);
}
image = video.image;
view = VulkanImage::VIEW_STORAGE;
g_render_ctx->GetTextureCache()->MarkGpuWritten(image);
} else {
const bool exact =
IsSupportedSampledVideoOutView(resource, descriptor, *video.image) &&
width == video.image->extent.width && height == video.image->extent.height &&
levels == 1 && base_level == 0 && view_levels == 1 && video.size == size.size &&
video.pitch == pitch;
if (!exact) {
EXIT("unsupported sampled access to video-out surface: resource=%u dimension=%u"
" image_format=%d view_format=%d swizzle=0x%03x extent=%ux%u/%ux%u"
" depth=%u levels=%u base=%u count=%u type=%u size=0x%016" PRIx64
"/0x%016" PRIx64 " pitch=%u/%u\n",
static_cast<uint32_t>(resource.kind),
static_cast<uint32_t>(resource.dimension),
static_cast<int>(video.image->format), static_cast<int>(view_format),
swizzle, width, height, video.image->extent.width,
video.image->extent.height, depth, levels, base_level, view_levels,
static_cast<uint32_t>(type), size.size, video.size, pitch, video.pitch);
}
image = video.image;
image_view = g_render_ctx->GetTextureCache()->GetSampledColorView(
g_render_ctx->GetGraphicCtx(), video.image, view_format, swizzle, 0, 1,
vk::ImageViewType::e2D, 0, 1);
}
}
}
if (image == nullptr) {
auto* texture_cache = g_render_ctx->GetTextureCache();
const bool metadata_read = texture_cache->QueryRegion(address, size.size).metadata_pages;
if (storage && metadata_read) {
EXIT("storage texture overlaps surface metadata\n");
}
if (!storage && metadata_read) {
ValidateMetadataReuseTexture(resource, descriptor, size.size);
}
(void)submit_id;
(void)command_buffer;
ImageInfo info {};
info.address = address;
info.size = size.size;
info.format = format;
info.width = width;
info.height = height;
info.pitch = pitch;
info.base_level = base_level;
info.levels = levels;
info.view_levels = view_levels;
info.tile = tile;
info.swizzle = swizzle;
info.depth = depth;
info.type = descriptor.Type();
info.base_array = descriptor.BaseArray5();
if (storage) {
image = texture_cache->FindStorageTexture(command_buffer, g_render_ctx->GetGraphicCtx(),
info);
view = VulkanImage::VIEW_DEFAULT;
image_view = texture_cache->GetStorageTextureStorageView(
g_render_ctx->GetGraphicCtx(), static_cast<StorageTextureVulkanImage*>(image),
base_level);
} else {
image = texture_cache->FindTexture(command_buffer, g_render_ctx->GetGraphicCtx(), info,
metadata_read);
if (image->type == VulkanImageType::StorageTexture) {
image_view = texture_cache->GetStorageTextureSampledView(
g_render_ctx->GetGraphicCtx(), static_cast<StorageTextureVulkanImage*>(image),
info);
}
}
}
EXIT_NOT_IMPLEMENTED(image == nullptr);
if (NeedsStaticSampledArrayView(resource.dimension ==
ShaderRecompiler::Decoder::ImageDimension::Dim2DArray,
image_view != nullptr)) {
view = SelectSampledTextureArrayView(image, view);
}
return {image, view, image_view};
}
static vk::Sampler NativeSampler(const ShaderRecompiler::IR::Program& program, uint32_t index,
const ShaderRecompiler::IR::DescriptorValue& value) {
ShaderSamplerResource descriptor;
CopyNativeDescriptor(value, descriptor.fields);
const bool depth_compare = std::any_of(program.info.sampled_pairs.begin(),
program.info.sampled_pairs.end(), [&](const auto& pair) {
return pair.sampler == index &&
pair.image < program.info.images.size() &&
program.info.images[pair.image].depth_compare;
});
if (!depth_compare) {
descriptor.fields[0] &= ~(0x7u << 12u);
}
return g_render_ctx->GetSamplerCache()->GetSampler(descriptor);
}
static BufferView NativeUpload(CommandBuffer* command_buffer, std::span<const uint32_t> data) {
EXIT_IF(data.empty());
BufferView result;
EXIT_IF(!g_render_ctx->GetBufferCache()->UploadHostData(
command_buffer, g_render_ctx->GetGraphicCtx(), data.data(), data.size_bytes(), 256,
&result.buffer, &result.offset, &result.range));
return result;
}
void BindDescriptors(uint64_t submit_id, CommandBuffer* buffer,
vk::PipelineBindPoint pipeline_bind_point, vk::PipelineLayout layout,
const ShaderStageRuntime& runtime, vk::ShaderStageFlags vk_stage,
DescriptorCache::Stage stage) {
KYTY_PROFILER_FUNCTION();
EXIT_IF(buffer == nullptr || !runtime);
const auto& program = *runtime.program;
const auto& snapshot = *runtime.resources;
std::string error;
if (!ShaderRecompiler::IR::ValidateResourceSpecialization(program, snapshot, &error)) {
EXIT("invalid native shader runtime snapshot: %s\n", error.c_str());
}
auto vk_buffer = buffer->Handle();
const auto shader_stages = ShaderPipelineStages(vk_stage);
DescriptorCache::NativeDescriptors descriptors;
descriptors.buffers.reserve(program.info.buffers.size());
for (uint32_t i = 0; i < program.info.buffers.size(); i++) {
ShaderBufferResource descriptor;
CopyNativeDescriptor(snapshot.buffers[i], descriptor.fields);
descriptors.buffers.push_back(
NativeStorageBuffer(submit_id, buffer, descriptor, program.info.buffers[i]));
}
descriptors.images.reserve(program.info.images.size());
for (uint32_t i = 0; i < program.info.images.size(); i++) {
const auto kind = program.info.images[i].kind;
if ((kind == ShaderRecompiler::IR::ResourceKind::StorageImage ||
kind == ShaderRecompiler::IR::ResourceKind::StorageImageUint) &&
vk_stage != vk::ShaderStageFlagBits::eCompute) {
EXIT("storage images are unsupported outside compute shaders\n");
}
descriptors.images.push_back(
NativeTexture(submit_id, buffer, program.info.images[i], snapshot.images[i]));
}
descriptors.samplers.reserve(program.info.samplers.size());
for (uint32_t i = 0; i < program.info.samplers.size(); i++) {
descriptors.samplers.push_back(NativeSampler(program, i, snapshot.samplers[i]));
}
descriptors.addresses.reserve(program.info.addresses.size());
for (uint32_t i = 0; i < program.info.addresses.size(); i++) {
descriptors.addresses.push_back(NativeAddressBuffer(
submit_id, buffer, program.info.addresses[i], snapshot.addresses[i]));
}
if (ShaderRecompiler::IR::FindBinding(
program.bindings, ShaderRecompiler::IR::DescriptorBindingKind::FlattenedSrt) !=
nullptr) {
descriptors.flattened_srt = NativeUpload(buffer, snapshot.flattened_srt);
}
std::vector<uint32_t> user_data;
user_data.reserve(program.bindings.user_data_registers.size());
for (const auto reg: program.bindings.user_data_registers) {
user_data.push_back(snapshot.user_data[reg - program.user_data_base]);
}
if (ShaderRecompiler::IR::FindBinding(
program.bindings, ShaderRecompiler::IR::DescriptorBindingKind::UserData) != nullptr) {
descriptors.user_data = NativeUpload(buffer, user_data);
}
if (ShaderRecompiler::IR::FindBinding(
program.bindings, ShaderRecompiler::IR::DescriptorBindingKind::Gds) != nullptr) {
descriptors.gds.buffer =
g_render_ctx->GetGdsBuffer()->GetBuffer(g_render_ctx->GetGraphicCtx());
const auto barrier = MakeGdsDependency(*descriptors.gds.buffer);
vk_buffer.pipelineBarrier(
vk::PipelineStageFlagBits::eHost | vk::PipelineStageFlagBits::eTransfer |
vk::PipelineStageFlagBits::eAllGraphics | vk::PipelineStageFlagBits::eComputeShader,
shader_stages, vk::DependencyFlags {}, 0, nullptr, 1, &barrier, 0, nullptr);
}
for (uint32_t i = 0; i < program.info.images.size(); i++) {
auto* image = descriptors.images[i].image;
const auto& resource = program.info.images[i];
if (resource.kind == ShaderRecompiler::IR::ResourceKind::Image ||
resource.kind == ShaderRecompiler::IR::ResourceKind::ImageUint) {
switch (image->type) {
case VulkanImageType::DepthStencil:
GraphicsRenderDepthStencilBarrier(vk_buffer, image);
break;
case VulkanImageType::RenderTexture:
case VulkanImageType::StorageTexture:
GraphicsRenderTextureBarrier(vk_buffer, image);
break;
case VulkanImageType::VideoOut:
GraphicsRenderColorImageBarrier(vk_buffer, image, RENDER_COLOR_IMAGE_LAYOUT);
break;
default: break;
}
} else {
const auto barrier =
MakeStorageImageDependency(*image, resource.read, resource.written);
vk_buffer.pipelineBarrier(vk::PipelineStageFlagBits::eAllCommands, shader_stages,
vk::DependencyFlags {}, 0, nullptr, 0, nullptr, 1, &barrier);
image->layout = vk::ImageLayout::eGeneral;
}
}
if (!program.bindings.descriptors.empty()) {
auto* set = g_render_ctx->GetDescriptorCache()->GetDescriptor(stage, program, descriptors);
EXIT_IF(set == nullptr);
vk_buffer.bindDescriptorSets(pipeline_bind_point, layout, program.bindings.descriptor_set,
1, &set->set, 0, nullptr);
buffer->RecycleDescriptorAfterFence(set);
}
if (program.bindings.push_constant_size != 0) {
EXIT_IF(program.bindings.push_constant_size != user_data.size() * sizeof(uint32_t));
vk_buffer.pushConstants(layout, vk_stage, program.bindings.push_constant_offset,
program.bindings.push_constant_size, user_data.data());
}
}
} // namespace Libs::Graphics
@@ -1,58 +0,0 @@
#include "graphics/host_gpu/renderer/dummyTextureCache.h"
#include "common/assert.h"
#include "graphics/host_gpu/renderer/image.h"
#include "graphics/host_gpu/transfer.h"
namespace Libs::Graphics {
namespace {
[[nodiscard]] constexpr size_t DummyTextureIndex(bool uint_format, bool image_3d) noexcept {
return (image_3d ? 2u : 0u) + (uint_format ? 1u : 0u);
}
} // namespace
DummyTextureCache::~DummyTextureCache() {
Common::LockGuard lock(m_mutex);
if (m_ctx == nullptr) {
return;
}
Transfer::WaitForGraphicsIdle(m_ctx);
const auto destroy = [this](auto& slots) {
for (auto& slot: slots) {
if (slot.image != nullptr) {
ImageOps::Destroy(m_ctx, slot.image);
slot.image = nullptr;
}
}
};
destroy(m_sampled);
destroy(m_storage);
}
VulkanImage* DummyTextureCache::Get(GraphicContext* ctx, Usage usage, bool uint_format,
bool image_3d) {
Common::LockGuard lock(m_mutex);
if (ctx == nullptr) {
EXIT("TextureCache: dummy texture requires a graphics context\n");
}
if (m_ctx != nullptr && m_ctx != ctx) {
EXIT("TextureCache: dummy texture context changed, previous=%p current=%p usage=%u\n",
static_cast<const void*>(m_ctx), static_cast<const void*>(ctx),
static_cast<uint32_t>(usage));
}
m_ctx = ctx;
auto& slots = usage == Usage::Storage ? m_storage : m_sampled;
auto& slot = slots[DummyTextureIndex(uint_format, image_3d)];
if (slot.image == nullptr) {
slot.image =
ImageOps::CreateDummyTexture(ctx, uint_format, image_3d, usage == Usage::Storage);
}
return slot.image;
}
} // namespace Libs::Graphics
@@ -1,37 +0,0 @@
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_DUMMYTEXTURECACHE_H_
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_DUMMYTEXTURECACHE_H_
#include "common/abi.h"
#include "common/threads.h"
#include "graphics/host_gpu/graphicContext.h"
#include <array>
#include <cstdint>
namespace Libs::Graphics {
class DummyTextureCache final {
public:
enum class Usage : uint8_t { Sampled, Storage };
DummyTextureCache() = default;
~DummyTextureCache();
KYTY_CLASS_NO_COPY(DummyTextureCache);
[[nodiscard]] VulkanImage* Get(GraphicContext* ctx, Usage usage, bool uint_format,
bool image_3d);
private:
struct Slot {
GpuTextureVulkanImage* image = nullptr;
};
Common::Mutex m_mutex;
std::array<Slot, 4> m_sampled {};
std::array<Slot, 4> m_storage {};
GraphicContext* m_ctx = nullptr;
};
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_DUMMYTEXTURECACHE_H_
@@ -1,353 +0,0 @@
#include "graphics/host_gpu/renderer/framebufferCache.h"
#include "common/assert.h"
#include "common/logging/log.h"
#include "common/profiler.h"
#include "graphics/host_gpu/renderer/colorRenderTarget.h"
#include "graphics/host_gpu/renderer/depthRenderTarget.h"
#include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include <algorithm>
#include <atomic>
namespace Libs::Graphics {
VulkanFramebuffer* FramebufferCache::CreateFramebuffer(RenderColorInfo* colors,
uint32_t requested_color_count,
RenderDepthInfo* depth) {
KYTY_PROFILER_FUNCTION();
Common::LockGuard lock(m_mutex);
EXIT_IF(colors == nullptr);
EXIT_IF(depth == nullptr);
EXIT_IF(g_render_ctx == nullptr);
EXIT_IF(requested_color_count > RENDER_COLOR_ATTACHMENTS_MAX);
bool with_depth = (depth->format != vk::Format::eUndefined && depth->vulkan_buffer != nullptr);
bool with_color[RENDER_COLOR_ATTACHMENTS_MAX] = {};
uint32_t color_count = 0;
VulkanImage* first_color = nullptr;
vk::Extent2D first_color_extent = {};
for (uint32_t i = 0; i < requested_color_count; i++) {
with_color[i] = (colors[i].vulkan_buffer != nullptr);
if (!with_color[i]) {
break;
}
if (first_color == nullptr) {
first_color = colors[i].vulkan_buffer;
first_color_extent = colors[i].extent;
} else if (colors[i].extent.width != first_color_extent.width ||
colors[i].extent.height != first_color_extent.height) {
LOGF("Framebuffer: temporary: dropping mismatched MRT%u attachment color0=%ux%u "
"color%u=%ux%u\n",
i, first_color_extent.width, first_color_extent.height, i, colors[i].extent.width,
colors[i].extent.height);
with_color[i] = false;
break;
}
color_count++;
}
vk::ImageLayout color_layout[RENDER_COLOR_ATTACHMENTS_MAX] = {};
for (auto& layout: color_layout) {
layout = RENDER_COLOR_IMAGE_LAYOUT;
}
auto depth_layout = (with_depth ? depth_attachment_layout(depth)
: vk::ImageLayout::eDepthStencilAttachmentOptimal);
auto depth_read_only =
(with_depth && depth_layout == vk::ImageLayout::eDepthStencilReadOnlyOptimal);
if (with_depth && first_color != nullptr &&
(first_color_extent.width != depth->vulkan_buffer->extent.width ||
first_color_extent.height != depth->vulkan_buffer->extent.height)) {
static std::atomic<uint32_t> log_count {0};
if (log_count.fetch_add(1, std::memory_order_relaxed) < 16) {
LOGF("Framebuffer: temporary: dropping mismatched PS5 depth attachment color=%ux%u "
"depth=%ux%u format=%s\n",
first_color_extent.width, first_color_extent.height,
depth->vulkan_buffer->extent.width, depth->vulkan_buffer->extent.height,
VulkanToString(depth->format).c_str());
}
depth->format = vk::Format::eUndefined;
depth->vulkan_buffer = nullptr;
depth->vulkan_view = nullptr;
depth->depth_test_enable = false;
depth->depth_write_enable = false;
depth->depth_bounds_test_enable = false;
depth->stencil_test_enable = false;
depth->depth_clear_enable = false;
depth->depth_load_clear_enable = false;
depth->stencil_clear_enable = false;
with_depth = false;
depth_layout = vk::ImageLayout::eDepthStencilAttachmentOptimal;
depth_read_only = false;
}
for (auto& f: m_framebuffers) {
bool color_match = (f.framebuffer != nullptr);
for (uint32_t i = 0; color_match && i < RENDER_COLOR_ATTACHMENTS_MAX; i++) {
const uint64_t image_id =
(i < color_count && with_color[i] ? colors[i].vulkan_buffer->memory.unique_id : 0);
color_match =
color_match && f.image_id[i] == image_id &&
f.color_view[i] ==
(i < color_count && with_color[i] ? colors[i].vulkan_view : nullptr) &&
f.color_clear_enable[i] ==
(i < color_count && with_color[i] && colors[i].color_clear_enable) &&
f.color_layout[i] == color_layout[i];
}
if (color_match &&
f.depth_id == (with_depth ? depth->vulkan_buffer->memory.unique_id : 0) &&
f.depth_view == (with_depth ? depth->vulkan_view : nullptr) &&
f.depth_clear_enable == depth->depth_load_clear_enable &&
f.stencil_clear_enable == depth->stencil_clear_enable &&
f.depth_read_only == depth_read_only) {
return f.framebuffer;
}
}
if (!with_depth && color_count == 0) {
LOGF("Framebuffer: warning: no color or depth attachment\n");
return nullptr;
}
EXIT_NOT_IMPLEMENTED(with_depth && first_color != nullptr &&
(first_color_extent.width != depth->vulkan_buffer->extent.width ||
first_color_extent.height != depth->vulkan_buffer->extent.height));
if (first_color == nullptr) {
first_color_extent = depth->vulkan_buffer->extent;
}
auto* framebuffer = new VulkanFramebuffer;
framebuffer->render_pass = nullptr;
framebuffer->framebuffer = nullptr;
for (uint32_t i = 0; i < RENDER_COLOR_ATTACHMENTS_MAX; i++) {
framebuffer->color_layout[i] = color_layout[i];
}
framebuffer->depth_layout = depth_layout;
auto* gctx = g_render_ctx->GetGraphicCtx();
EXIT_IF(gctx == nullptr);
vk::AttachmentDescription attachments[RENDER_COLOR_ATTACHMENTS_MAX + 1] = {};
for (uint32_t i = 0; i < color_count; i++) {
attachments[i].flags = {};
attachments[i].format = colors[i].format;
attachments[i].samples = vk::SampleCountFlagBits::e1;
attachments[i].loadOp = (colors[i].color_clear_enable ? vk::AttachmentLoadOp::eClear
: vk::AttachmentLoadOp::eLoad);
attachments[i].storeOp = vk::AttachmentStoreOp::eStore;
attachments[i].stencilLoadOp = vk::AttachmentLoadOp::eDontCare;
attachments[i].stencilStoreOp = vk::AttachmentStoreOp::eDontCare;
attachments[i].initialLayout = color_layout[i];
attachments[i].finalLayout = RENDER_COLOR_IMAGE_LAYOUT;
}
const uint32_t depth_attachment = color_count;
attachments[depth_attachment].flags = {};
attachments[depth_attachment].format = depth->format;
attachments[depth_attachment].samples = vk::SampleCountFlagBits::e1;
attachments[depth_attachment].loadOp =
(depth->depth_load_clear_enable ? vk::AttachmentLoadOp::eClear
: vk::AttachmentLoadOp::eLoad);
attachments[depth_attachment].storeOp = vk::AttachmentStoreOp::eStore;
attachments[depth_attachment].stencilLoadOp =
(depth->stencil_clear_enable ? vk::AttachmentLoadOp::eClear : vk::AttachmentLoadOp::eLoad);
attachments[depth_attachment].stencilStoreOp = vk::AttachmentStoreOp::eStore;
attachments[depth_attachment].initialLayout = depth_layout;
attachments[depth_attachment].finalLayout = depth_layout;
vk::AttachmentReference color_attachment_ref[RENDER_COLOR_ATTACHMENTS_MAX] = {};
for (uint32_t i = 0; i < RENDER_COLOR_ATTACHMENTS_MAX; i++) {
color_attachment_ref[i].attachment = (i < color_count ? i : VK_ATTACHMENT_UNUSED);
color_attachment_ref[i].layout = RENDER_COLOR_IMAGE_LAYOUT;
}
vk::AttachmentReference depth_attachment_ref {};
depth_attachment_ref.attachment = depth_attachment;
depth_attachment_ref.layout = depth_layout;
vk::SubpassDescription subpass {};
subpass.flags = {};
subpass.pipelineBindPoint = vk::PipelineBindPoint::eGraphics;
subpass.inputAttachmentCount = 0;
subpass.pInputAttachments = nullptr;
subpass.colorAttachmentCount = color_count;
subpass.pColorAttachments = (color_count > 0 ? color_attachment_ref : nullptr);
subpass.pResolveAttachments = nullptr;
subpass.pDepthStencilAttachment = (with_depth ? &depth_attachment_ref : nullptr);
subpass.preserveAttachmentCount = 0;
subpass.pPreserveAttachments = nullptr;
const auto attachment_stage_mask =
static_cast<vk::PipelineStageFlags>(vk::PipelineStageFlagBits::eColorAttachmentOutput |
vk::PipelineStageFlagBits::eEarlyFragmentTests |
vk::PipelineStageFlagBits::eLateFragmentTests);
const auto attachment_access_mask = static_cast<vk::AccessFlags>(
vk::AccessFlagBits::eColorAttachmentRead | vk::AccessFlagBits::eColorAttachmentWrite |
vk::AccessFlagBits::eDepthStencilAttachmentRead |
vk::AccessFlagBits::eDepthStencilAttachmentWrite);
vk::SubpassDependency dependencies[2] = {};
dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
dependencies[0].dstSubpass = 0;
dependencies[0].srcStageMask = vk::PipelineStageFlagBits::eAllCommands;
dependencies[0].dstStageMask = attachment_stage_mask;
dependencies[0].srcAccessMask =
vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite;
dependencies[0].dstAccessMask = attachment_access_mask;
dependencies[0].dependencyFlags = vk::DependencyFlagBits::eByRegion;
dependencies[1].srcSubpass = 0;
dependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL;
dependencies[1].srcStageMask = attachment_stage_mask;
dependencies[1].dstStageMask = vk::PipelineStageFlagBits::eAllCommands;
dependencies[1].srcAccessMask = attachment_access_mask;
dependencies[1].dstAccessMask =
vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite;
dependencies[1].dependencyFlags = vk::DependencyFlagBits::eByRegion;
vk::RenderPassCreateInfo render_pass_info {};
render_pass_info.sType = vk::StructureType::eRenderPassCreateInfo;
render_pass_info.pNext = nullptr;
render_pass_info.flags = {};
render_pass_info.attachmentCount = color_count + (with_depth ? 1u : 0u);
render_pass_info.pAttachments = attachments;
render_pass_info.subpassCount = 1;
render_pass_info.pSubpasses = &subpass;
render_pass_info.dependencyCount = 2;
render_pass_info.pDependencies = dependencies;
auto result =
gctx->device.createRenderPass(&render_pass_info, nullptr, &framebuffer->render_pass);
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
vk::Format color_formats[RENDER_COLOR_ATTACHMENTS_MAX] = {};
for (uint32_t i = 0; i < color_count; i++) {
color_formats[i] = colors[i].format;
}
framebuffer->render_pass_id =
render_pass_compat_id(color_count, color_formats, with_depth, depth->format, depth_layout);
EXIT_NOT_IMPLEMENTED(framebuffer->render_pass == nullptr);
vk::ImageView views[RENDER_COLOR_ATTACHMENTS_MAX + 1] = {};
for (uint32_t i = 0; i < color_count; i++) {
if (colors[i].vulkan_view == nullptr) {
EXIT("Framebuffer: color attachment view is missing at slot %u\n", i);
}
views[i] = colors[i].vulkan_view;
}
if (with_depth) {
if (depth->vulkan_view == nullptr) {
EXIT("Framebuffer: depth attachment view is missing\n");
}
views[color_count] = depth->vulkan_view;
}
vk::FramebufferCreateInfo framebuffer_info {};
framebuffer_info.sType = vk::StructureType::eFramebufferCreateInfo;
framebuffer_info.pNext = nullptr;
framebuffer_info.flags = {};
framebuffer_info.renderPass = framebuffer->render_pass;
framebuffer_info.attachmentCount = color_count + (with_depth ? 1u : 0u);
framebuffer_info.pAttachments = views;
framebuffer_info.width = first_color_extent.width;
framebuffer_info.height = first_color_extent.height;
framebuffer_info.layers = 1;
result = gctx->device.createFramebuffer(&framebuffer_info, nullptr, &framebuffer->framebuffer);
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
EXIT_NOT_IMPLEMENTED(framebuffer->framebuffer == nullptr);
Framebuffer fnew;
fnew.framebuffer = framebuffer;
for (uint32_t i = 0; i < RENDER_COLOR_ATTACHMENTS_MAX; i++) {
fnew.image_id[i] =
(i < color_count && with_color[i] ? colors[i].vulkan_buffer->memory.unique_id : 0);
fnew.color_view[i] = (i < color_count && with_color[i] ? colors[i].vulkan_view : nullptr);
fnew.color_clear_enable[i] =
(i < color_count && with_color[i] && colors[i].color_clear_enable);
fnew.color_layout[i] = color_layout[i];
}
fnew.depth_id = (with_depth ? depth->vulkan_buffer->memory.unique_id : 0);
fnew.depth_view = (with_depth ? depth->vulkan_view : nullptr);
fnew.depth_clear_enable = depth->depth_load_clear_enable;
fnew.stencil_clear_enable = depth->stencil_clear_enable;
fnew.depth_read_only = depth_read_only;
bool updated = false;
for (auto& f: m_framebuffers) {
if (f.framebuffer == nullptr) {
f = fnew;
updated = true;
break;
}
}
if (!updated) {
m_framebuffers.push_back(fnew);
}
return framebuffer;
}
void FramebufferCache::FreeFramebufferByColor(VulkanImage* image) {
EXIT_IF(g_render_ctx == nullptr);
EXIT_IF(image == nullptr);
Common::LockGuard lock(m_mutex);
for (auto& f: m_framebuffers) {
bool uses_image = false;
for (auto image_id: f.image_id) {
if (image_id == image->memory.unique_id) {
uses_image = true;
break;
}
}
if (f.framebuffer != nullptr && uses_image) {
auto* gctx = g_render_ctx->GetGraphicCtx();
EXIT_IF(gctx == nullptr);
gctx->device.destroyFramebuffer(f.framebuffer->framebuffer, nullptr);
gctx->device.destroyRenderPass(f.framebuffer->render_pass, nullptr);
delete f.framebuffer;
f.framebuffer = nullptr;
}
}
}
void FramebufferCache::FreeFramebufferByDepth(DepthStencilVulkanImage* image) {
EXIT_IF(g_render_ctx == nullptr);
EXIT_IF(image == nullptr);
Common::LockGuard lock(m_mutex);
for (auto& f: m_framebuffers) {
if (f.framebuffer != nullptr && f.depth_id == image->memory.unique_id) {
auto* gctx = g_render_ctx->GetGraphicCtx();
EXIT_IF(gctx == nullptr);
gctx->device.destroyFramebuffer(f.framebuffer->framebuffer, nullptr);
gctx->device.destroyRenderPass(f.framebuffer->render_pass, nullptr);
delete f.framebuffer;
f.framebuffer = nullptr;
}
}
}
} // namespace Libs::Graphics
@@ -1,81 +0,0 @@
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_FRAMEBUFFERCACHE_H_
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_FRAMEBUFFERCACHE_H_
#include "common/abi.h"
#include "common/assert.h"
#include "common/common.h"
#include "common/threads.h"
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/renderer/renderTarget.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include <vector>
namespace Libs::Graphics {
struct RenderColorInfo;
struct RenderDepthInfo;
static constexpr vk::ImageLayout RENDER_COLOR_IMAGE_LAYOUT = vk::ImageLayout::eGeneral;
struct VulkanFramebuffer {
vk::RenderPass render_pass = nullptr;
uint64_t render_pass_id = 0;
vk::Framebuffer framebuffer = nullptr;
vk::ImageLayout color_layout[RENDER_COLOR_ATTACHMENTS_MAX] = {};
vk::ImageLayout depth_layout = vk::ImageLayout::eDepthStencilAttachmentOptimal;
};
inline uint64_t render_pass_compat_id(uint32_t color_count, const vk::Format* color_formats,
bool with_depth, vk::Format depth_format,
vk::ImageLayout depth_layout) {
uint64_t id = 0xcbf29ce484222325ull;
auto mix = [&id](uint64_t v) {
id ^= v;
id *= 0x100000001b3ull;
};
mix(color_count);
for (uint32_t i = 0; i < color_count; i++) {
mix(static_cast<uint32_t>(color_formats[i]));
}
mix(with_depth ? 1u : 0u);
mix(static_cast<uint32_t>(depth_format));
mix(static_cast<uint32_t>(depth_layout));
mix(static_cast<uint32_t>(vk::SampleCountFlagBits::e1));
return id;
}
class FramebufferCache {
public:
FramebufferCache() { EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread()); }
~FramebufferCache() { KYTY_NOT_IMPLEMENTED; }
KYTY_CLASS_NO_COPY(FramebufferCache);
VulkanFramebuffer* CreateFramebuffer(RenderColorInfo* colors, uint32_t color_count,
RenderDepthInfo* depth);
void FreeFramebufferByColor(VulkanImage* image);
void FreeFramebufferByDepth(DepthStencilVulkanImage* image);
private:
struct Framebuffer {
VulkanFramebuffer* framebuffer = nullptr;
uint64_t image_id[RENDER_COLOR_ATTACHMENTS_MAX] = {};
vk::ImageView color_view[RENDER_COLOR_ATTACHMENTS_MAX] = {};
uint64_t depth_id = 0;
vk::ImageView depth_view = nullptr;
bool color_clear_enable[RENDER_COLOR_ATTACHMENTS_MAX] = {};
vk::ImageLayout color_layout[RENDER_COLOR_ATTACHMENTS_MAX] = {};
bool depth_clear_enable = false;
bool stencil_clear_enable = false;
bool depth_read_only = false;
};
Common::Mutex m_mutex;
std::vector<Framebuffer> m_framebuffers;
};
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_FRAMEBUFFERCACHE_H_
@@ -1,70 +0,0 @@
#include "graphics/host_gpu/renderer/gdsBuffer.h"
#include "common/assert.h"
#include "graphics/host_gpu/transfer.h"
#include "graphics/host_gpu/vma.h"
namespace Libs::Graphics {
void GdsBuffer::Init(GraphicContext* ctx) {
if (m_buffer == nullptr) {
m_buffer = std::make_unique<VulkanBuffer>();
m_buffer->usage = vk::BufferUsageFlagBits::eStorageBuffer;
m_buffer->memory.property = vk::MemoryPropertyFlagBits::eHostVisible |
vk::MemoryPropertyFlagBits::eHostCoherent |
vk::MemoryPropertyFlagBits::eHostCached;
VulkanCreateBuffer(ctx, DW_SIZE * 4, m_buffer.get());
}
}
void GdsBuffer::Clear(GraphicContext* ctx, uint64_t dw_offset, uint32_t dw_num,
uint32_t clear_value) {
EXIT_IF(ctx == nullptr);
Common::LockGuard lock(m_mutex);
Init(ctx);
EXIT_NOT_IMPLEMENTED(dw_offset >= DW_SIZE);
EXIT_NOT_IMPLEMENTED(dw_offset + dw_num > DW_SIZE);
void* data = nullptr;
VulkanMapMemory(ctx, &m_buffer->memory, &data);
for (uint32_t i = 0; i < dw_num; i++) {
static_cast<uint32_t*>(data)[dw_offset + i] = clear_value;
}
VulkanUnmapMemory(ctx, &m_buffer->memory);
}
void GdsBuffer::Read(GraphicContext* ctx, uint32_t* dst, uint32_t dw_offset, uint32_t dw_size) {
EXIT_IF(dst == nullptr);
Common::LockGuard lock(m_mutex);
Init(ctx);
EXIT_NOT_IMPLEMENTED(dw_offset >= DW_SIZE);
EXIT_NOT_IMPLEMENTED(dw_offset + dw_size > DW_SIZE);
void* data = nullptr;
VulkanMapMemory(ctx, &m_buffer->memory, &data);
for (uint32_t i = 0; i < dw_size; i++) {
dst[i] = static_cast<uint32_t*>(data)[dw_offset + i];
}
VulkanUnmapMemory(ctx, &m_buffer->memory);
}
VulkanBuffer* GdsBuffer::GetBuffer(GraphicContext* ctx) {
Common::LockGuard lock(m_mutex);
Init(ctx);
return m_buffer.get();
}
} // namespace Libs::Graphics
@@ -1,36 +0,0 @@
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_GDSBUFFER_H_
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_GDSBUFFER_H_
#include "common/abi.h"
#include "common/assert.h"
#include "common/common.h"
#include "common/threads.h"
#include "graphics/host_gpu/graphicContext.h"
#include <memory>
namespace Libs::Graphics {
class GdsBuffer {
public:
GdsBuffer() { EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread()); }
~GdsBuffer() { KYTY_NOT_IMPLEMENTED; }
KYTY_CLASS_NO_COPY(GdsBuffer);
void Clear(GraphicContext* ctx, uint64_t dw_offset, uint32_t dw_num, uint32_t clear_value);
void Read(GraphicContext* ctx, uint32_t* dst, uint32_t dw_offset, uint32_t dw_size);
VulkanBuffer* GetBuffer(GraphicContext* ctx);
private:
static constexpr uint64_t DW_SIZE = 0x3000;
void Init(GraphicContext* ctx);
Common::Mutex m_mutex;
std::unique_ptr<VulkanBuffer> m_buffer;
};
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_GDSBUFFER_H_
@@ -1,99 +0,0 @@
#include "graphics/host_gpu/renderer/gpuResourceManager.h"
#include "common/assert.h"
#include "graphics/guest_gpu/command_processor/commandProcessor.h"
#include "graphics/guest_gpu/graphicsRun.h"
#include "graphics/host_gpu/objects/label.h"
#include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/renderContext.h"
namespace Libs::Graphics {
GpuResourceManager::GpuResourceManager()
: m_page_manager(FaultThunk, this), m_buffer_cache(m_page_manager, m_resource_mutex),
m_texture_cache(m_page_manager, m_buffer_cache, m_resource_mutex) {
m_buffer_cache.SetTextureCache(m_texture_cache);
}
GpuResourceManager::~GpuResourceManager() = default;
bool GpuResourceManager::FaultThunk(void* context, PageFaultAccess access, uint64_t vaddr,
uint64_t size, PageFaultPhase phase) noexcept {
return static_cast<GpuResourceManager*>(context)->InvalidateMemory(access, vaddr, size, phase);
}
bool GpuResourceManager::InvalidateMemory(PageFaultAccess access, uint64_t vaddr, uint64_t size,
PageFaultPhase phase) noexcept {
const bool buffer_handled = m_buffer_cache.InvalidateMemory(access, vaddr, size, phase);
const bool image_handled = m_texture_cache.InvalidateMemory(access, vaddr, size, phase);
return buffer_handled || image_handled;
}
bool GpuResourceManager::HandleFault(PageFaultAccess access, uint64_t fault_vaddr) noexcept {
if (!m_page_manager.IsMapped(fault_vaddr, 1)) {
return false;
}
if (LabelInCallback()) {
EXIT("unsupported guest-memory fault from an asynchronous GPU label callback, "
"addr=0x%016" PRIx64 " access=%u\n",
fault_vaddr, static_cast<uint32_t>(access));
}
if (auto* cp = GraphicsRunCurrentCommandProcessor(); cp != nullptr) {
cp->BeginReadbackTransaction();
bool handled = false;
{
ResourceMutex::FaultScope fault(m_resource_mutex);
handled = m_page_manager.HandleFault(access, fault_vaddr);
}
cp->EndReadbackTransaction();
return handled;
}
if (m_resource_mutex.IsOwnedByCurrentThread()) {
EXIT("unsupported page fault from a pre-owned resource transaction, addr=0x%016" PRIx64
" access=%u\n",
fault_vaddr, static_cast<uint32_t>(access));
}
// Stop command-processor jobs before taking the shared cache transaction. External readback
// workers inherit this paused state and therefore never form resource -> submission lock
// inversion.
GraphicsRunSubmissionLock submissions;
ResourceMutex::FaultScope fault(m_resource_mutex);
return m_page_manager.HandleFault(access, fault_vaddr);
}
bool GpuResourceManager::IsMapped(uint64_t vaddr, uint64_t size) const noexcept {
return m_page_manager.IsMapped(vaddr, size);
}
void GpuResourceManager::MapMemory(uint64_t vaddr, uint64_t size, GpuAccess access) {
m_page_manager.OnGpuMap(vaddr, size, access);
}
void GpuResourceManager::UnmapMemory(uint64_t vaddr, uint64_t size, GpuAccess access) {
if (!IsMapped(vaddr, size)) {
EXIT("cannot unmap an unmapped GPU resource range\n");
}
m_texture_cache.UnmapMemory(vaddr, size);
m_buffer_cache.UnmapMemory(vaddr, size);
m_page_manager.OnGpuUnmap(vaddr, size, access);
}
void GpuResourceManager::FillBuffer(CommandBuffer* command, uint64_t vaddr, uint64_t size,
uint32_t value) {
if (g_render_ctx == nullptr || command == nullptr || command->IsInvalid()) {
EXIT("cannot fill a buffer without a valid render command context\n");
}
Common::LockGuard lock(g_render_ctx->GetMutex());
m_buffer_cache.FillBuffer(command, g_render_ctx->GetGraphicCtx(), vaddr, size, value);
}
void GpuResourceManager::CopyBuffer(CommandBuffer* command, uint64_t dst_vaddr, uint64_t src_vaddr,
uint64_t size) {
if (g_render_ctx == nullptr || command == nullptr || command->IsInvalid()) {
EXIT("cannot copy a buffer without a valid render command context\n");
}
Common::LockGuard lock(g_render_ctx->GetMutex());
m_buffer_cache.CopyBuffer(command, g_render_ctx->GetGraphicCtx(), dst_vaddr, src_vaddr, size);
}
} // namespace Libs::Graphics
@@ -1,47 +0,0 @@
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_GPURESOURCEMANAGER_H_
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_GPURESOURCEMANAGER_H_
#include "common/abi.h"
#include "common/common.h"
#include "graphics/host_gpu/pageManager.h"
#include "graphics/host_gpu/renderer/bufferCache.h"
#include "graphics/host_gpu/renderer/resourceMutex.h"
#include "graphics/host_gpu/renderer/textureCache.h"
#include <cstdint>
namespace Libs::Graphics {
class CommandBuffer;
class GpuResourceManager {
public:
GpuResourceManager();
~GpuResourceManager();
KYTY_CLASS_NO_COPY(GpuResourceManager);
[[nodiscard]] BufferCache* GetBufferCache() { return &m_buffer_cache; }
[[nodiscard]] TextureCache* GetTextureCache() { return &m_texture_cache; }
[[nodiscard]] bool HandleFault(PageFaultAccess access, uint64_t fault_vaddr) noexcept;
[[nodiscard]] bool IsMapped(uint64_t vaddr, uint64_t size) const noexcept;
void MapMemory(uint64_t vaddr, uint64_t size, GpuAccess access);
void UnmapMemory(uint64_t vaddr, uint64_t size, GpuAccess access);
void FillBuffer(CommandBuffer* command, uint64_t vaddr, uint64_t size, uint32_t value);
void CopyBuffer(CommandBuffer* command, uint64_t dst_vaddr, uint64_t src_vaddr, uint64_t size);
private:
static bool FaultThunk(void* context, PageFaultAccess access, uint64_t vaddr, uint64_t size,
PageFaultPhase phase) noexcept;
[[nodiscard]] bool InvalidateMemory(PageFaultAccess access, uint64_t vaddr, uint64_t size,
PageFaultPhase phase) noexcept;
PageManager m_page_manager;
ResourceMutex m_resource_mutex;
BufferCache m_buffer_cache;
TextureCache m_texture_cache;
};
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_GPURESOURCEMANAGER_H_
-443
View File
@@ -1,443 +0,0 @@
#include "graphics/host_gpu/renderer/image.h"
#include "common/assert.h"
#include "common/profiler.h"
#include "graphics/guest_gpu/gpu_defs.h"
#include "graphics/guest_gpu/tile.h"
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/objects/textureCommon.h"
#include "graphics/host_gpu/regionDefinitions.h"
#include "graphics/host_gpu/renderer/framebufferCache.h"
#include "graphics/host_gpu/renderer/imageView.h"
#include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/host_gpu/transfer.h"
#include "graphics/host_gpu/vma.h"
#include "graphics/shader/shader.h"
#include <algorithm>
namespace Libs::Graphics {
namespace {
TextureImageCreateParams MakeImageParams(const ImageInfo& info, bool storage) {
TextureImageCreateParams params {};
params.fmt = info.format;
params.width = info.width;
params.height = info.height;
params.base_level = SelectImageBackingBaseLevel(storage, info.base_level);
params.levels = info.levels;
params.depth = info.depth;
params.type = info.type;
// Storage image views use identity component mapping. The guest storage write mapping is
// validated before this point and intentionally does not become a Vulkan view swizzle.
params.swizzle = storage ? DstSel(4, 5, 6, 7) : info.swizzle;
params.format_usage = TextureFormatUsage::Sampled | TextureFormatUsage::Storage;
params.required_format_usage = storage
? TextureFormatUsage::Sampled | TextureFormatUsage::Storage
: TextureFormatUsage::Sampled;
params.view_usage = storage ? TextureFormatUsage::Sampled | TextureFormatUsage::Storage
: TextureFormatUsage::Sampled;
params.image_layout = TextureUploadDestination::MipLevels;
params.allow_cube_view = !storage;
params.compatible_format_views =
storage && (IsRgba8SrgbViewFormat(TextureGetFormat(info.format)) ||
info.format == Prospero::GpuEnumValue(Prospero::BufferFormat::k32UInt) ||
info.format == Prospero::GpuEnumValue(Prospero::BufferFormat::k32Float));
params.owner = storage ? "StorageTextureCache" : "TextureCache";
return params;
}
bool RenderTargetSupportsStorage(GraphicContext* ctx, vk::Format format,
vk::ImageCreateFlags flags) {
const auto compatible = SrgbStorageViewFormat(format);
const auto required_flags =
vk::ImageCreateFlagBits::eMutableFormat | vk::ImageCreateFlagBits::eExtendedUsage;
const bool compatible_views = (flags & required_flags) == required_flags;
return ImageViewOps::FormatSupportsStorage(ctx, format) ||
(compatible_views && compatible != vk::Format::eUndefined &&
ImageViewOps::FormatSupportsStorage(ctx, compatible));
}
vk::ImageCreateFlags RenderTargetCreateFlags(vk::Format format) {
const bool compatible_format_view =
IsRgba8SrgbViewFormat(format) ||
BgraToRgbaSampledViewFormat(format) != vk::Format::eUndefined ||
format == vk::Format::eR8G8B8A8Uint || format == vk::Format::eR16G16B16A16Sfloat ||
format == vk::Format::eR16G16B16A16Uint;
return compatible_format_view
? vk::ImageCreateFlagBits::eMutableFormat | vk::ImageCreateFlagBits::eExtendedUsage
: vk::ImageCreateFlags {0};
}
vk::ImageUsageFlags RenderTargetUsage(GraphicContext* ctx, vk::Format format,
vk::ImageCreateFlags flags) {
auto usage = static_cast<vk::ImageUsageFlags>(vk::ImageUsageFlagBits::eColorAttachment) |
static_cast<vk::ImageUsageFlags>(vk::ImageUsageFlagBits::eTransferSrc) |
static_cast<vk::ImageUsageFlags>(vk::ImageUsageFlagBits::eTransferDst) |
static_cast<vk::ImageUsageFlags>(vk::ImageUsageFlagBits::eSampled);
if (RenderTargetSupportsStorage(ctx, format, flags)) {
usage |= vk::ImageUsageFlagBits::eStorage;
}
vk::ImageFormatProperties properties {};
if (ctx->GetImageFormatProperties(format, vk::ImageType::e2D, vk::ImageTiling::eOptimal, usage,
flags, &properties) != vk::Result::eSuccess) {
EXIT("TextureCache: render-target format does not support required usage, format=%d "
"usage=0x%x\n",
static_cast<int>(format), static_cast<vk::ImageUsageFlags::MaskType>(usage));
}
return usage;
}
[[nodiscard]] uint32_t RenderTargetTransferFormatImpl(uint32_t bytes_per_element) {
switch (bytes_per_element) {
case 1: return Prospero::GpuEnumValue(Prospero::BufferFormat::k8UNorm);
case 2: return Prospero::GpuEnumValue(Prospero::BufferFormat::k16UNorm);
case 4: return Prospero::GpuEnumValue(Prospero::BufferFormat::k32Float);
case 8: return Prospero::GpuEnumValue(Prospero::BufferFormat::k16_16_16_16Float);
case 16: return Prospero::GpuEnumValue(Prospero::BufferFormat::k32_32_32_32Float);
default:
EXIT("TextureCache: unsupported render-target element size: %u\n", bytes_per_element);
}
}
static constexpr uint32_t DummyTextureSwizzle() {
return Prospero::GpuEnumValue(Prospero::CompSwizzle::kRed) |
(Prospero::GpuEnumValue(Prospero::CompSwizzle::kGreen) << 3u) |
(Prospero::GpuEnumValue(Prospero::CompSwizzle::kBlue) << 6u) |
(Prospero::GpuEnumValue(Prospero::CompSwizzle::kAlpha) << 9u);
}
TextureImageCreateParams MakeDummyTextureParams(bool uint_format, bool image_3d,
TextureFormatUsage usage, const char* owner) {
TextureImageCreateParams params {};
params.fmt = static_cast<uint32_t>(
Prospero::GpuEnumValue(uint_format ? Prospero::BufferFormat::k8_8_8_8UInt
: Prospero::BufferFormat::k8_8_8_8UNorm));
params.width = 1;
params.height = 1;
params.base_level = 0;
params.levels = 1;
params.depth = 1;
params.type = Prospero::GpuEnumValue(image_3d ? Prospero::ImageType::kColor3D
: Prospero::ImageType::kColor2D);
params.swizzle = DummyTextureSwizzle();
params.format_usage = usage;
params.required_format_usage = usage;
params.view_usage = usage;
params.image_layout = TextureUploadDestination::MipLevels;
params.allow_cube_view = true;
params.storage_swizzle_fallback = TextureHasFormatUsage(usage, TextureFormatUsage::Storage);
params.owner = owner;
return params;
}
} // namespace
namespace ImageOps {
uint32_t RenderTargetTransferFormat(uint32_t bytes_per_element) {
return RenderTargetTransferFormatImpl(bytes_per_element);
}
GpuTextureVulkanImage* CreateTexture(GraphicContext* ctx, const ImageInfo& info, bool storage,
vk::ComponentMapping* components) {
if (components == nullptr) {
EXIT("TextureCache: invalid texture component output\n");
}
auto* image = storage ? static_cast<GpuTextureVulkanImage*>(new StorageTextureVulkanImage)
: new TextureVulkanImage;
*components = TextureCreateImage(ctx, image, MakeImageParams(info, storage));
return image;
}
void CreateTextureViews(GraphicContext* ctx, GpuTextureVulkanImage* image, const ImageInfo& info,
bool storage, vk::ComponentMapping components) {
if (storage) {
TextureCreateImageViews(ctx, image, components, info.type, 0, 0, 1, info.depth, false,
TextureFormatUsage::Sampled | TextureFormatUsage::Storage);
} else {
TextureCreateImageViews(ctx, image, components, info.type, info.base_array, info.base_level,
info.view_levels, info.depth, true, TextureFormatUsage::Sampled);
}
}
void UploadRenderTargetLayers(GraphicContext* ctx, RenderTextureVulkanImage* image,
const RenderTargetInfo& info, uint32_t base_layer,
uint32_t layer_count, bool refresh) {
if (info.layers == 0 || info.size % info.layers != 0 || layer_count == 0 ||
base_layer >= info.layers || layer_count > info.layers - base_layer || image == nullptr ||
base_layer >= image->layers || layer_count > image->layers - base_layer) {
EXIT("TextureCache: invalid render-target layer upload, base=%u count=%u "
"info_layers=%u image_layers=%u size=0x%016" PRIx64 "\n",
base_layer, layer_count, info.layers, image != nullptr ? image->layers : 0, info.size);
}
if (refresh) {
Transfer::WaitForGraphicsIdle(ctx);
}
const auto slice_size = info.size / info.layers;
const auto upload_size = slice_size * layer_count;
const bool standard64 = IsSupportedStandard64RenderTarget(info);
if (standard64 || info.levels > 1 || info.layers > 1) {
const auto format = RenderTargetTransferFormat(info.bytes_per_element);
auto layout = TextureCalcUploadLayout(format, info.width, info.height, info.levels,
layer_count, info.pitch, info.tile_mode, upload_size,
false, false, false, "TextureCache render target");
const bool render_target_tiled =
info.tile_mode == Prospero::GpuEnumValue(Prospero::TileMode::kRenderTarget);
if (!standard64 && ((render_target_tiled && !layout.fmt_tiled_render_target) ||
layout.pitch != info.pitch)) {
EXIT("TextureCache: unsupported render-target mip upload layout, pitch=%u/%u tile=%u\n",
info.pitch, layout.pitch, info.tile_mode);
}
auto regions = TextureBuildUploadRegions(
layout, info.format, info.width, info.height, layer_count, info.levels, true, false,
TextureUploadDestination::MipLevels, TextureUploadSliceLayout::MipChainPerSlice);
for (auto& region: regions) {
region.dst_layer += base_layer;
}
const auto source_address = info.address + slice_size * base_layer;
TextureUploadGuestImage(ctx, image, reinterpret_cast<const void*>(source_address),
upload_size, regions, layout, format, info.width, info.height,
layer_count, info.levels,
TextureUploadSliceLayout::MipChainPerSlice,
"TextureCache render target", vk::ImageLayout::eGeneral);
return;
}
if (info.tile_mode == Prospero::GpuEnumValue(Prospero::TileMode::kRenderTarget) &&
Transfer::GuestBufferIsTiled(info.address, slice_size)) {
Transfer::ScratchBuffer scratch(slice_size);
TileConvertTiledToLinearRenderTarget(
scratch.Data(), reinterpret_cast<const void*>(info.address), info.width, info.height,
info.pitch, info.bytes_per_element, slice_size);
Transfer::UploadImage(ctx, image, scratch.Data(), slice_size, info.pitch,
vk::ImageLayout::eGeneral);
} else {
Transfer::UploadImage(ctx, image, reinterpret_cast<const void*>(info.address), slice_size,
info.pitch, vk::ImageLayout::eGeneral);
}
}
void UploadRenderTarget(GraphicContext* ctx, RenderTextureVulkanImage* image,
const RenderTargetInfo& info, bool refresh) {
UploadRenderTargetLayers(ctx, image, info, 0, info.layers, refresh);
}
RenderTextureVulkanImage* CreateRenderTarget(GraphicContext* ctx, const RenderTargetInfo& info) {
auto* image = new RenderTextureVulkanImage;
image->extent.width = info.width;
image->extent.height = info.height;
image->format = info.format;
image->mip_levels = info.levels;
image->layers = info.layers;
image->layout = vk::ImageLayout::eUndefined;
vk::ImageCreateInfo create {};
create.sType = vk::StructureType::eImageCreateInfo;
create.flags = RenderTargetCreateFlags(info.format);
create.imageType = vk::ImageType::e2D;
create.extent = {info.width, info.height, 1};
create.mipLevels = info.levels;
create.arrayLayers = info.layers;
create.format = info.format;
create.tiling = vk::ImageTiling::eOptimal;
create.initialLayout = vk::ImageLayout::eUndefined;
create.usage = RenderTargetUsage(ctx, info.format, create.flags);
create.sharingMode = vk::SharingMode::eExclusive;
create.samples = vk::SampleCountFlagBits::e1;
image->memory.property = vk::MemoryPropertyFlagBits::eDeviceLocal;
if (!VulkanCreateImage(ctx, create, image)) {
EXIT("TextureCache: failed to create render target, addr=0x%016" PRIx64
" extent=%ux%u format=%d\n",
info.address, info.width, info.height, static_cast<int>(info.format));
}
ImageViewOps::CreateRenderTargetViews(ctx, image);
return image;
}
DepthStencilVulkanImage* CreateDepthTarget(GraphicContext* ctx, const DepthTargetInfo& info) {
vk::ImageCreateInfo create {};
create.sType = vk::StructureType::eImageCreateInfo;
create.imageType = vk::ImageType::e2D;
create.extent = {info.width, info.height, 1};
create.mipLevels = 1;
create.arrayLayers = info.layers;
create.format = info.format;
create.tiling = vk::ImageTiling::eOptimal;
create.initialLayout = vk::ImageLayout::eUndefined;
create.usage = DepthTargetImageUsage();
create.sharingMode = vk::SharingMode::eExclusive;
create.samples = vk::SampleCountFlagBits::e1;
vk::ImageFormatProperties properties {};
if (ctx->GetImageFormatProperties(info.format, vk::ImageType::e2D, vk::ImageTiling::eOptimal,
create.usage, vk::ImageCreateFlags {},
&properties) != vk::Result::eSuccess) {
EXIT("TextureCache: depth format does not support required usage, format=%d usage=0x%x\n",
static_cast<int>(info.format),
static_cast<vk::ImageUsageFlags::MaskType>(create.usage));
}
auto* image = new DepthStencilVulkanImage;
image->extent.width = info.width;
image->extent.height = info.height;
image->guest_pitch = info.pitch;
image->layers = info.layers;
image->format = info.format;
image->layout = vk::ImageLayout::eUndefined;
image->compressed = false;
image->memory.property = vk::MemoryPropertyFlagBits::eDeviceLocal;
if (!VulkanCreateImage(ctx, create, image)) {
EXIT("TextureCache: failed to create depth target, addr=0x%016" PRIx64
" extent=%ux%u format=%d\n",
info.address, info.width, info.height, static_cast<int>(info.format));
}
ImageViewOps::CreateDepthViews(ctx, image);
return image;
}
void ValidateVideoOut(GraphicContext* ctx, const VideoOutInfo& info) {
const auto compression =
ClassifyVideoOutCompression(info.compression != VideoOutCompression::Uncompressed,
info.metadata_address, info.dcc_control, 0);
const bool metadata_invalid = compression != VideoOutCompression::Uncompressed &&
compression != VideoOutCompression::Unsupported &&
(info.metadata_address >= TRACKER_ADDRESS_SIZE ||
(info.metadata_address >= info.address &&
info.metadata_address < info.address + info.size));
if (ctx == nullptr || info.address == 0 || info.size == 0 ||
info.address >= TRACKER_ADDRESS_SIZE || info.size > TRACKER_ADDRESS_SIZE - info.address ||
(info.address & 0xffffu) != 0 || info.width == 0 || info.height == 0 ||
info.width > 16384 || info.height > 16384 || info.pitch < info.width ||
info.tile_mode != Prospero::GpuEnumValue(Prospero::TileMode::kRenderTarget) ||
compression == VideoOutCompression::Unsupported || compression != info.compression ||
metadata_invalid || !IsSupportedVideoOutFormat(info)) {
EXIT("TextureCache: unsupported video-out surface, ctx=%p addr=0x%016" PRIx64
" size=0x%016" PRIx64 " metadata=0x%016" PRIx64 " dcc=0x%08" PRIx32
" extent=%ux%u pitch=%u tile=%u guest_format=%u bpe=%u vk_format=%d\n",
static_cast<const void*>(ctx), info.address, info.size, info.metadata_address,
info.dcc_control, info.width, info.height, info.pitch, info.tile_mode,
info.guest_format, info.bytes_per_element, static_cast<int>(info.format));
}
TileSizeAlign exact {};
TileGetTextureTotalSize(info.guest_format, info.width, info.height, 1, info.pitch, 1,
info.tile_mode, false, &exact);
if (exact.align != 65536 || exact.size != info.size ||
TileGetTexturePitch(info.guest_format, info.width, 1, info.tile_mode) != info.pitch) {
EXIT("TextureCache: video-out tile layout mismatch, addr=0x%016" PRIx64
" size=0x%016" PRIx64 " expected_size=0x%016" PRIx64 " align=0x%016" PRIx64
" pitch=%u\n",
info.address, info.size, exact.size, exact.align, info.pitch);
}
(void)RenderTargetUsage(ctx, info.format, vk::ImageCreateFlags {});
}
VideoOutVulkanImage* CreateVideoOut(GraphicContext* ctx, const VideoOutInfo& info) {
auto* image = new VideoOutVulkanImage;
image->extent.width = info.width;
image->extent.height = info.height;
image->format = info.format;
image->layout = vk::ImageLayout::eUndefined;
vk::ImageCreateInfo create {};
create.sType = vk::StructureType::eImageCreateInfo;
create.imageType = vk::ImageType::e2D;
create.extent = {info.width, info.height, 1};
create.mipLevels = 1;
create.arrayLayers = 1;
create.format = info.format;
create.tiling = vk::ImageTiling::eOptimal;
create.initialLayout = vk::ImageLayout::eUndefined;
create.flags = RenderTargetCreateFlags(info.format);
create.usage = RenderTargetUsage(ctx, info.format, create.flags);
create.sharingMode = vk::SharingMode::eExclusive;
create.samples = vk::SampleCountFlagBits::e1;
image->memory.property = vk::MemoryPropertyFlagBits::eDeviceLocal;
if (!VulkanCreateImage(ctx, create, image)) {
EXIT("TextureCache: failed to create video-out image, addr=0x%016" PRIx64
" extent=%ux%u format=%d\n",
info.address, info.width, info.height, static_cast<int>(info.format));
}
ImageViewOps::CreateVideoOutViews(ctx, image);
return image;
}
void UploadVideoOut(GraphicContext* ctx, VideoOutVulkanImage* image, const VideoOutInfo& info,
bool refresh) {
if (info.compression != VideoOutCompression::Uncompressed) {
EXIT("TextureCache: compressed video-out guest upload is unsupported, "
"addr=0x%016" PRIx64 " metadata=0x%016" PRIx64 " dcc=0x%08" PRIx32 "\n",
info.address, info.metadata_address, info.dcc_control);
}
if (refresh) {
Transfer::WaitForGraphicsIdle(ctx);
}
image->layout = vk::ImageLayout::eUndefined;
Transfer::ScratchBuffer scratch(info.size);
TileConvertTiledToLinearRenderTarget(
scratch.Data(), reinterpret_cast<const void*>(info.address), info.width, info.height,
info.pitch, info.bytes_per_element, info.size);
if (info.bgra16) {
auto* pixels = static_cast<uint16_t*>(scratch.Data());
for (uint64_t i = 0; i < info.size / sizeof(uint16_t); i += 4) {
std::swap(pixels[i], pixels[i + 2]);
}
}
Transfer::UploadImage(ctx, image, scratch.Data(), info.size, info.pitch,
vk::ImageLayout::eGeneral);
}
GpuTextureVulkanImage* CreateDummyTexture(GraphicContext* ctx, bool uint_format, bool image_3d,
bool storage) {
auto* image = storage ? static_cast<GpuTextureVulkanImage*>(new StorageTextureVulkanImage)
: new TextureVulkanImage;
auto usage = storage ? TextureFormatUsage::Storage : TextureFormatUsage::Sampled;
auto layout = storage ? vk::ImageLayout::eGeneral : vk::ImageLayout::eShaderReadOnlyOptimal;
auto owner = storage ? "DummyStorageTexture" : "DummySampledTexture";
auto params = MakeDummyTextureParams(uint_format, image_3d, usage, owner);
auto components = TextureCreateImage(ctx, image, params);
static constexpr uint32_t zero = 0;
Transfer::UploadImage(ctx, image, &zero, sizeof(zero), 1, layout);
TextureCreateImageViews(ctx, image, components, params.type, 0, params.base_level,
params.levels, params.depth, params.allow_cube_view, params.view_usage);
return image;
}
void Destroy(GraphicContext* ctx, VulkanImage* image) {
KYTY_PROFILER_BLOCK("TextureCache::DeleteImage");
EXIT_IF(ctx == nullptr || image == nullptr || g_render_ctx == nullptr);
switch (image->type) {
case VulkanImageType::RenderTexture:
case VulkanImageType::VideoOut:
g_render_ctx->GetFramebufferCache()->FreeFramebufferByColor(image);
break;
case VulkanImageType::DepthStencil:
g_render_ctx->GetFramebufferCache()->FreeFramebufferByDepth(
static_cast<DepthStencilVulkanImage*>(image));
break;
case VulkanImageType::Texture:
case VulkanImageType::StorageTexture: break;
case VulkanImageType::Unknown: EXIT("cannot destroy an untyped Vulkan image\n");
}
ImageViewOps::DestroyViews(ctx, image);
VulkanDeleteImage(ctx, image);
switch (image->type) {
case VulkanImageType::Texture: delete static_cast<TextureVulkanImage*>(image); break;
case VulkanImageType::StorageTexture:
delete static_cast<StorageTextureVulkanImage*>(image);
break;
case VulkanImageType::RenderTexture:
delete static_cast<RenderTextureVulkanImage*>(image);
break;
case VulkanImageType::DepthStencil:
delete static_cast<DepthStencilVulkanImage*>(image);
break;
case VulkanImageType::VideoOut: delete static_cast<VideoOutVulkanImage*>(image); break;
case VulkanImageType::Unknown: EXIT("cannot delete an untyped Vulkan image\n");
}
}
} // namespace ImageOps
} // namespace Libs::Graphics
-166
View File
@@ -1,166 +0,0 @@
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_IMAGE_H_
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_IMAGE_H_
#include "common/assert.h"
#include "graphics/host_gpu/renderer/imageInfo.h"
#include <algorithm>
#include <cstddef>
#include <span>
namespace Libs::Graphics {
struct DepthStencilVulkanImage;
struct GpuTextureVulkanImage;
struct GraphicContext;
struct RenderTextureVulkanImage;
struct VideoOutVulkanImage;
struct VulkanImage;
struct Image final: ImageInfo {
Image& operator=(const ImageInfo& value) {
if (IsCpuDirty()) {
EXIT("dirty sampled image cannot be reassigned\n");
}
static_cast<ImageInfo&>(*this) = value;
m_track_begin = address;
m_track_end = address + size;
m_maybe_cpu_hash_valid = false;
return *this;
}
void InvalidateCpuWrite(uint64_t vaddr, uint64_t size) {
if (ImageRangeOverlaps(address, this->size, vaddr, size)) {
m_cpu_dirty = true;
m_maybe_cpu_dirty = false;
m_maybe_cpu_hash_valid = false;
m_track_begin = m_track_end;
} else if (ImagePageRangesOverlap(address, this->size, vaddr, size)) {
constexpr uint64_t page_mask = 4096 - 1;
if (vaddr + size <= address) {
const auto next_page = (address + page_mask) & ~page_mask;
m_track_begin = std::min(m_track_end, std::max(m_track_begin, next_page));
} else if (vaddr >= address + this->size) {
const auto page = (address + this->size) & ~page_mask;
m_track_end = std::max(m_track_begin, std::min(m_track_end, page));
}
m_maybe_cpu_dirty = m_track_begin == m_track_end;
}
}
[[nodiscard]] bool IsCpuDirty() const { return m_cpu_dirty || m_maybe_cpu_dirty; }
[[nodiscard]] bool IsDefinitelyCpuDirty() const { return m_cpu_dirty; }
[[nodiscard]] bool IsMaybeCpuDirty() const { return m_maybe_cpu_dirty; }
[[nodiscard]] bool NeedsMaybeCpuHash() const {
return m_maybe_cpu_dirty && !m_maybe_cpu_hash_valid;
}
[[nodiscard]] bool IsCpuTrackingComplete() const {
return m_track_begin == address && m_track_end == address + size;
}
void SetMaybeCpuHash(uint64_t hash) {
if (!NeedsMaybeCpuHash()) {
EXIT("sampled image cannot initialize maybe-dirty hash\n");
}
m_maybe_cpu_hash = hash;
m_maybe_cpu_hash_valid = true;
}
[[nodiscard]] bool ResolveMaybeCpuHash(uint64_t hash) {
if (!m_maybe_cpu_dirty || !m_maybe_cpu_hash_valid || m_cpu_dirty) {
EXIT("sampled image cannot resolve maybe-dirty hash\n");
}
m_maybe_cpu_dirty = false;
m_maybe_cpu_hash_valid = false;
m_cpu_dirty = hash != m_maybe_cpu_hash;
if (!m_cpu_dirty) {
m_track_begin = address;
m_track_end = address + size;
}
return m_cpu_dirty;
}
void RefreshComplete() {
if (!IsCpuDirty()) {
EXIT("clean sampled image cannot complete a refresh\n");
}
m_cpu_dirty = false;
m_maybe_cpu_dirty = false;
m_maybe_cpu_hash_valid = false;
m_track_begin = address;
m_track_end = address + size;
}
private:
bool m_cpu_dirty = false;
bool m_maybe_cpu_dirty = false;
bool m_maybe_cpu_hash_valid = false;
uint64_t m_track_begin = 0;
uint64_t m_track_end = 0;
uint64_t m_maybe_cpu_hash = 0;
};
namespace ImageOps {
[[nodiscard]] GpuTextureVulkanImage* CreateTexture(GraphicContext* ctx, const ImageInfo& info,
bool storage, vk::ComponentMapping* components);
void CreateTextureViews(GraphicContext* ctx, GpuTextureVulkanImage* image, const ImageInfo& info,
bool storage, vk::ComponentMapping components);
[[nodiscard]] RenderTextureVulkanImage* CreateRenderTarget(GraphicContext* ctx,
const RenderTargetInfo& info);
[[nodiscard]] uint32_t RenderTargetTransferFormat(uint32_t bytes_per_element);
void UploadRenderTargetLayers(GraphicContext* ctx, RenderTextureVulkanImage* image,
const RenderTargetInfo& info, uint32_t base_layer,
uint32_t layer_count, bool refresh);
void UploadRenderTarget(GraphicContext* ctx, RenderTextureVulkanImage* image,
const RenderTargetInfo& info, bool refresh);
[[nodiscard]] DepthStencilVulkanImage* CreateDepthTarget(GraphicContext* ctx,
const DepthTargetInfo& info);
void ValidateVideoOut(GraphicContext* ctx, const VideoOutInfo& info);
[[nodiscard]] VideoOutVulkanImage* CreateVideoOut(GraphicContext* ctx, const VideoOutInfo& info);
void UploadVideoOut(GraphicContext* ctx, VideoOutVulkanImage* image, const VideoOutInfo& info,
bool refresh);
[[nodiscard]] GpuTextureVulkanImage* CreateDummyTexture(GraphicContext* ctx, bool uint_format,
bool image_3d, bool storage);
void Destroy(GraphicContext* ctx, VulkanImage* image);
} // namespace ImageOps
struct ImageRetirementRange {
uint64_t address = 0;
uint64_t size = 0;
bool retire = false;
};
struct ImageRetirementConflict {
size_t retired = SIZE_MAX;
size_t retained = SIZE_MAX;
[[nodiscard]] bool Exists() const { return retired != SIZE_MAX; }
};
[[nodiscard]] inline ImageRetirementConflict
FindImageRetirementConflict(std::span<const ImageRetirementRange> ranges) {
for (size_t retired = 0; retired < ranges.size(); retired++) {
if (!ranges[retired].retire) {
continue;
}
for (size_t retained = 0; retained < ranges.size(); retained++) {
if (ranges[retained].retire) {
continue;
}
if (ImageRangeOverlaps(ranges[retired].address, ranges[retired].size,
ranges[retained].address, ranges[retained].size)) {
return {retired, retained};
}
}
}
return {};
}
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_IMAGE_H_
@@ -0,0 +1,231 @@
#include "graphics/host_gpu/renderer/image/blitHelper.h"
#include "common/assert.h"
#include "gpu_blit_shaders/gpu_blit_color_to_ms_depth_spv.h"
#include "gpu_blit_shaders/gpu_blit_fs_triangle_spv.h"
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/renderer/commandScheduler.h"
#include "graphics/host_gpu/renderer/image/image.h"
#include "graphics/host_gpu/renderer/renderTarget.h"
#include <algorithm>
#include <array>
#include <iterator>
namespace Libs::Graphics {
BlitHelper::BlitHelper(GraphicContext& graphics, CommandScheduler& scheduler)
: m_graphics(graphics), m_scheduler(scheduler) {
vk::DescriptorSetLayoutBinding texture_binding {};
texture_binding.binding = 0;
texture_binding.descriptorType = vk::DescriptorType::eSampledImage;
texture_binding.descriptorCount = 1;
texture_binding.stageFlags = vk::ShaderStageFlagBits::eFragment;
vk::DescriptorSetLayoutCreateInfo descriptor_info {};
descriptor_info.sType = vk::StructureType::eDescriptorSetLayoutCreateInfo;
descriptor_info.flags = vk::DescriptorSetLayoutCreateFlagBits::ePushDescriptorKHR;
descriptor_info.bindingCount = 1;
descriptor_info.pBindings = &texture_binding;
RequireVulkanSuccess(m_graphics.device.createDescriptorSetLayout(&descriptor_info, nullptr,
&m_descriptor_layout),
"create BlitHelper descriptor layout");
vk::PipelineLayoutCreateInfo layout_info {};
layout_info.sType = vk::StructureType::ePipelineLayoutCreateInfo;
layout_info.setLayoutCount = 1;
layout_info.pSetLayouts = &m_descriptor_layout;
RequireVulkanSuccess(
m_graphics.device.createPipelineLayout(&layout_info, nullptr, &m_pipeline_layout),
"create BlitHelper pipeline layout");
m_vertex_shader = CreateShader(GPU_BLIT_FS_TRIANGLE_SPV, std::size(GPU_BLIT_FS_TRIANGLE_SPV));
m_fragment_shader =
CreateShader(GPU_BLIT_COLOR_TO_MS_DEPTH_SPV, std::size(GPU_BLIT_COLOR_TO_MS_DEPTH_SPV));
}
BlitHelper::~BlitHelper() {
for (const auto& pipeline: m_pipelines) {
m_graphics.device.destroyPipeline(pipeline.handle, nullptr);
}
if (m_fragment_shader != nullptr) {
m_graphics.device.destroyShaderModule(m_fragment_shader, nullptr);
}
if (m_vertex_shader != nullptr) {
m_graphics.device.destroyShaderModule(m_vertex_shader, nullptr);
}
if (m_pipeline_layout != nullptr) {
m_graphics.device.destroyPipelineLayout(m_pipeline_layout, nullptr);
}
if (m_descriptor_layout != nullptr) {
m_graphics.device.destroyDescriptorSetLayout(m_descriptor_layout, nullptr);
}
}
vk::ShaderModule BlitHelper::CreateShader(const uint32_t* code, size_t words) const {
EXIT_IF(code == nullptr || words == 0);
vk::ShaderModuleCreateInfo create {};
create.sType = vk::StructureType::eShaderModuleCreateInfo;
create.codeSize = words * sizeof(uint32_t);
create.pCode = code;
vk::ShaderModule module = nullptr;
RequireVulkanSuccess(m_graphics.device.createShaderModule(&create, nullptr, &module),
"create BlitHelper shader module");
return module;
}
vk::Pipeline BlitHelper::GetPipeline(PipelineKey key) {
const auto cached = std::ranges::find(m_pipelines, key, &Pipeline::key);
if (cached != m_pipelines.end()) {
return cached->handle;
}
const auto samples = vulkan_sample_count(key.samples);
EXIT_IF(samples == vk::SampleCountFlagBits {} || key.format == vk::Format::eUndefined);
std::array<vk::PipelineShaderStageCreateInfo, 2> stages {};
stages[0].sType = vk::StructureType::ePipelineShaderStageCreateInfo;
stages[0].stage = vk::ShaderStageFlagBits::eVertex;
stages[0].module = m_vertex_shader;
stages[0].pName = "main";
stages[1].sType = vk::StructureType::ePipelineShaderStageCreateInfo;
stages[1].stage = vk::ShaderStageFlagBits::eFragment;
stages[1].module = m_fragment_shader;
stages[1].pName = "main";
vk::PipelineVertexInputStateCreateInfo vertex_input {};
vertex_input.sType = vk::StructureType::ePipelineVertexInputStateCreateInfo;
vk::PipelineInputAssemblyStateCreateInfo input_assembly {};
input_assembly.sType = vk::StructureType::ePipelineInputAssemblyStateCreateInfo;
input_assembly.topology = vk::PrimitiveTopology::eTriangleList;
vk::PipelineViewportStateCreateInfo viewport {};
viewport.sType = vk::StructureType::ePipelineViewportStateCreateInfo;
viewport.viewportCount = 1;
viewport.scissorCount = 1;
vk::PipelineRasterizationStateCreateInfo rasterization {};
rasterization.sType = vk::StructureType::ePipelineRasterizationStateCreateInfo;
rasterization.polygonMode = vk::PolygonMode::eFill;
rasterization.cullMode = vk::CullModeFlagBits::eNone;
rasterization.lineWidth = 1.0f;
vk::PipelineMultisampleStateCreateInfo multisample {};
multisample.sType = vk::StructureType::ePipelineMultisampleStateCreateInfo;
multisample.rasterizationSamples = samples;
vk::PipelineDepthStencilStateCreateInfo depth {};
depth.sType = vk::StructureType::ePipelineDepthStencilStateCreateInfo;
depth.depthTestEnable = VK_TRUE;
depth.depthWriteEnable = VK_TRUE;
depth.depthCompareOp = vk::CompareOp::eAlways;
vk::PipelineColorBlendStateCreateInfo color_blend {};
color_blend.sType = vk::StructureType::ePipelineColorBlendStateCreateInfo;
const std::array dynamic_states {vk::DynamicState::eViewport, vk::DynamicState::eScissor};
vk::PipelineDynamicStateCreateInfo dynamic {};
dynamic.sType = vk::StructureType::ePipelineDynamicStateCreateInfo;
dynamic.dynamicStateCount = static_cast<uint32_t>(dynamic_states.size());
dynamic.pDynamicStates = dynamic_states.data();
vk::PipelineRenderingCreateInfo rendering {};
rendering.sType = vk::StructureType::ePipelineRenderingCreateInfo;
rendering.depthAttachmentFormat = key.format;
vk::GraphicsPipelineCreateInfo create {};
create.sType = vk::StructureType::eGraphicsPipelineCreateInfo;
create.pNext = &rendering;
create.stageCount = static_cast<uint32_t>(stages.size());
create.pStages = stages.data();
create.pVertexInputState = &vertex_input;
create.pInputAssemblyState = &input_assembly;
create.pViewportState = &viewport;
create.pRasterizationState = &rasterization;
create.pMultisampleState = &multisample;
create.pDepthStencilState = &depth;
create.pColorBlendState = &color_blend;
create.pDynamicState = &dynamic;
create.layout = m_pipeline_layout;
vk::Pipeline pipeline = nullptr;
RequireVulkanSuccess(
m_graphics.device.createGraphicsPipelines(nullptr, 1, &create, nullptr, &pipeline),
"create color-to-MS-depth pipeline");
m_pipelines.push_back({key, pipeline});
return pipeline;
}
void BlitHelper::ReinterpretColorAsMsDepth(Image& source, Image& destination) {
const auto& source_info = source.info;
const auto& destination_info = destination.info;
EXIT_IF(DepthAspectTransferFormat(source_info.pixel_format) != vk::Format::eUndefined ||
DepthAspectTransferFormat(destination_info.pixel_format) == vk::Format::eUndefined ||
source_info.samples != 1 || destination_info.samples <= 1 ||
destination_info.samples > 4 || source.backing.image_type != vk::ImageType::e2D ||
destination.backing.image_type != vk::ImageType::e2D ||
source_info.extent.width != destination_info.extent.width ||
source_info.extent.height != destination_info.extent.height ||
source_info.extent.depth != 1 || destination_info.extent.depth != 1 ||
source.backing.image == nullptr || destination.backing.image == nullptr);
m_scheduler.EndRendering();
ImageViewInfo source_view_info {};
source_view_info.format = source_info.pixel_format;
source_view_info.type = vk::ImageViewType::e2D;
source_view_info.aspect = vk::ImageAspectFlagBits::eColor;
source_view_info.usage = vk::ImageUsageFlagBits::eSampled;
const auto source_view = source.FindView(source_view_info);
ImageViewInfo destination_view_info {};
destination_view_info.format = destination_info.pixel_format;
destination_view_info.type = vk::ImageViewType::e2D;
destination_view_info.aspect = vk::ImageAspectFlagBits::eDepth;
destination_view_info.usage = vk::ImageUsageFlagBits::eDepthStencilAttachment;
const auto destination_view = destination.FindView(destination_view_info);
auto& command_buffer = m_scheduler.Current();
auto command = command_buffer.Handle();
source.Transit(vk::ImageLayout::eShaderReadOnlyOptimal, vk::AccessFlagBits2::eShaderRead, {},
command);
destination.Transit(ColorToMsDepthLayout, vk::AccessFlagBits2::eDepthStencilAttachmentWrite, {},
command);
vk::RenderingAttachmentInfo depth_attachment {};
depth_attachment.sType = vk::StructureType::eRenderingAttachmentInfo;
depth_attachment.imageView = destination_view;
depth_attachment.imageLayout = ColorToMsDepthLayout;
depth_attachment.loadOp = vk::AttachmentLoadOp::eClear;
depth_attachment.storeOp = vk::AttachmentStoreOp::eStore;
depth_attachment.clearValue.depthStencil = {0.0f, 0};
vk::RenderingInfo rendering {};
rendering.sType = vk::StructureType::eRenderingInfo;
rendering.renderArea.extent = {destination_info.extent.width, destination_info.extent.height};
rendering.layerCount = 1;
rendering.pDepthAttachment = &depth_attachment;
command.beginRendering(&rendering);
vk::DescriptorImageInfo descriptor_image {};
descriptor_image.imageView = source_view;
descriptor_image.imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
vk::WriteDescriptorSet descriptor_write {};
descriptor_write.sType = vk::StructureType::eWriteDescriptorSet;
descriptor_write.dstBinding = 0;
descriptor_write.descriptorCount = 1;
descriptor_write.descriptorType = vk::DescriptorType::eSampledImage;
descriptor_write.pImageInfo = &descriptor_image;
command.pushDescriptorSetKHR(vk::PipelineBindPoint::eGraphics, m_pipeline_layout, 0, 1,
&descriptor_write);
command.bindPipeline(vk::PipelineBindPoint::eGraphics,
GetPipeline({destination_info.samples, destination_info.pixel_format}));
const vk::Viewport viewport {0.0f,
0.0f,
static_cast<float>(destination_info.extent.width),
static_cast<float>(destination_info.extent.height),
0.0f,
1.0f};
const vk::Rect2D scissor {{0, 0},
{destination_info.extent.width, destination_info.extent.height}};
command.setViewport(0, 1, &viewport);
command.setScissor(0, 1, &scissor);
command.draw(3, 1, 0, 0);
command.endRendering();
}
} // namespace Libs::Graphics
@@ -0,0 +1,54 @@
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_BLITHELPER_H_
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_BLITHELPER_H_
#include "common/common.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include <compare>
#include <vector>
namespace Libs::Graphics {
class CommandScheduler;
class Image;
struct GraphicContext;
class BlitHelper final {
public:
inline static constexpr auto ColorToMsDepthLayout =
vk::ImageLayout::eDepthStencilAttachmentOptimal;
BlitHelper(GraphicContext& graphics, CommandScheduler& scheduler);
~BlitHelper();
KYTY_CLASS_NO_COPY(BlitHelper);
void ReinterpretColorAsMsDepth(Image& source, Image& destination);
private:
struct PipelineKey {
uint32_t samples = 1;
vk::Format format = vk::Format::eUndefined;
auto operator<=>(const PipelineKey&) const = default;
};
struct Pipeline {
PipelineKey key;
vk::Pipeline handle = nullptr;
};
[[nodiscard]] vk::ShaderModule CreateShader(const uint32_t* code, size_t words) const;
[[nodiscard]] vk::Pipeline GetPipeline(PipelineKey key);
GraphicContext& m_graphics;
CommandScheduler& m_scheduler;
vk::DescriptorSetLayout m_descriptor_layout = nullptr;
vk::PipelineLayout m_pipeline_layout = nullptr;
vk::ShaderModule m_vertex_shader = nullptr;
vk::ShaderModule m_fragment_shader = nullptr;
std::vector<Pipeline> m_pipelines;
};
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_BLITHELPER_H_
@@ -0,0 +1,749 @@
#include "graphics/host_gpu/renderer/image/image.h"
#include "common/assert.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/image/imageView.h"
#include "graphics/host_gpu/renderer/renderTarget.h"
#include "kernel/memory.h"
#include <algorithm>
#include <array>
#include <cstdint>
#include <xxhash.h>
namespace Libs::Graphics {
namespace {
[[nodiscard]] vk::ImageType HostImageType(Prospero::ImageType type) {
switch (type) {
case Prospero::ImageType::kColor1D: return vk::ImageType::e1D;
case Prospero::ImageType::kColor3D: return vk::ImageType::e3D;
case Prospero::ImageType::kColor2D: return vk::ImageType::e2D;
default: EXIT("non-base image type: %u\n", static_cast<uint32_t>(type));
}
}
[[nodiscard]] vk::ImageCreateFlags ImageCreateFlags(const ImageInfo& info) {
vk::ImageCreateFlags flags {};
if (DepthAspectTransferFormat(info.pixel_format) == vk::Format::eUndefined) {
flags |= vk::ImageCreateFlagBits::eMutableFormat;
flags |= vk::ImageCreateFlagBits::eExtendedUsage;
if (Prospero::BlockCompressedBytesPerBlock(info.guest_format) != 0) {
flags |= vk::ImageCreateFlagBits::eBlockTexelViewCompatible;
}
}
if (info.IsVolume()) {
flags |= vk::ImageCreateFlagBits::e2DArrayCompatible;
}
return flags;
}
[[nodiscard]] bool HasFormatFeature(vk::FormatProperties properties,
vk::FormatFeatureFlagBits feature) {
return static_cast<bool>(properties.optimalTilingFeatures & feature);
}
[[nodiscard]] vk::ImageUsageFlags ImageUsageFlags(GraphicContext& graphics, const ImageInfo& info) {
const auto properties = graphics.GetFormatProperties(info.pixel_format);
auto usage = vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst;
if (HasFormatFeature(properties, vk::FormatFeatureFlagBits::eSampledImage)) {
usage |= vk::ImageUsageFlagBits::eSampled;
}
if (DepthAspectTransferFormat(info.pixel_format) != vk::Format::eUndefined) {
usage |= vk::ImageUsageFlagBits::eDepthStencilAttachment;
return usage;
}
if (HasFormatFeature(properties, vk::FormatFeatureFlagBits::eColorAttachment)) {
usage |= vk::ImageUsageFlagBits::eColorAttachment;
}
if (info.samples == 1 &&
HasFormatFeature(properties, vk::FormatFeatureFlagBits::eStorageImage)) {
usage |= vk::ImageUsageFlagBits::eStorage;
} else if (info.samples == 1) {
const auto compatible = SrgbStorageViewFormat(info.pixel_format);
if (compatible != vk::Format::eUndefined &&
HasFormatFeature(graphics.GetFormatProperties(compatible),
vk::FormatFeatureFlagBits::eStorageImage)) {
usage |= vk::ImageUsageFlagBits::eStorage;
}
}
return usage;
}
void ValidateRange(GuestRange range, const char* name) {
if ((range.address == 0) != (range.size == 0) ||
(range.address != 0 && (range.address >= TRACKER_ADDRESS_SIZE ||
range.size > TRACKER_ADDRESS_SIZE - range.address))) {
EXIT("invalid %s image range: address=0x%016llx size=0x%016llx\n", name,
static_cast<unsigned long long>(range.address),
static_cast<unsigned long long>(range.size));
}
}
} // namespace
vk::ImageAspectFlags Image::FullAspectMask(vk::Format format) noexcept {
switch (format) {
case vk::Format::eD16Unorm:
case vk::Format::eX8D24UnormPack32:
case vk::Format::eD32Sfloat: return vk::ImageAspectFlagBits::eDepth;
case vk::Format::eS8Uint: return vk::ImageAspectFlagBits::eStencil;
case vk::Format::eD16UnormS8Uint:
case vk::Format::eD24UnormS8Uint:
case vk::Format::eD32SfloatS8Uint:
return vk::ImageAspectFlagBits::eDepth | vk::ImageAspectFlagBits::eStencil;
default: return vk::ImageAspectFlagBits::eColor;
}
}
Image::Barriers Image::GetBarriers(vk::ImageLayout destination_layout,
vk::AccessFlags2 destination_access,
vk::PipelineStageFlags2 destination_stage,
std::optional<ImageSubresourceRange> range) {
auto& state = backing.state;
auto& subresource_states = backing.subresource_states;
const bool partial =
range && (range->base_level != 0 || range->level_count != info.resources.levels ||
range->base_layer != 0 || range->layer_count != info.resources.layers);
const bool has_subresource_states = !subresource_states.empty();
Barriers barriers;
if (partial || has_subresource_states) {
if (!has_subresource_states) {
subresource_states.resize(info.resources.levels * info.resources.layers, state);
}
const uint32_t base_level = partial ? range->base_level : 0;
const uint32_t level_count = partial ? range->level_count : info.resources.levels;
const uint32_t base_layer = partial ? range->base_layer : 0;
const uint32_t layer_count = partial ? range->layer_count : info.resources.layers;
for (uint32_t level = base_level; level < base_level + level_count; level++) {
for (uint32_t layer = base_layer; layer < base_layer + layer_count; layer++) {
const auto index = level * info.resources.layers + layer;
EXIT_IF(index >= subresource_states.size());
auto& subresource_state = subresource_states[index];
constexpr auto write_access = vk::AccessFlagBits2::eTransferWrite |
vk::AccessFlagBits2::eShaderWrite |
vk::AccessFlagBits2::eMemoryWrite;
const bool repeated_write =
static_cast<bool>(subresource_state.access_mask & write_access);
if (subresource_state.layout != destination_layout ||
subresource_state.access_mask != destination_access || repeated_write) {
vk::ImageMemoryBarrier2 barrier {};
barrier.srcStageMask = subresource_state.pl_stage;
barrier.srcAccessMask = subresource_state.access_mask;
barrier.dstStageMask = destination_stage;
barrier.dstAccessMask = destination_access;
barrier.oldLayout = subresource_state.layout;
barrier.newLayout = destination_layout;
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.image = backing.image;
barrier.subresourceRange.aspectMask = FullAspectMask(backing.format);
barrier.subresourceRange.baseMipLevel = level;
barrier.subresourceRange.levelCount = 1;
barrier.subresourceRange.baseArrayLayer = layer;
barrier.subresourceRange.layerCount = 1;
barriers.push_back(barrier);
subresource_state = {destination_stage, destination_access, destination_layout};
}
}
}
if (!partial) {
subresource_states.clear();
}
} else {
constexpr auto write_access = vk::AccessFlagBits2::eTransferWrite |
vk::AccessFlagBits2::eShaderWrite |
vk::AccessFlagBits2::eMemoryWrite;
const bool repeated_write = static_cast<bool>(state.access_mask & write_access);
if (state.layout == destination_layout && state.access_mask == destination_access &&
!repeated_write) {
return {};
}
vk::ImageMemoryBarrier2 barrier {};
barrier.srcStageMask = state.pl_stage;
barrier.srcAccessMask = state.access_mask;
barrier.dstStageMask = destination_stage;
barrier.dstAccessMask = destination_access;
barrier.oldLayout = state.layout;
barrier.newLayout = destination_layout;
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.image = backing.image;
barrier.subresourceRange.aspectMask = FullAspectMask(backing.format);
barrier.subresourceRange.baseMipLevel = 0;
barrier.subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS;
barrier.subresourceRange.baseArrayLayer = 0;
barrier.subresourceRange.layerCount = VK_REMAINING_ARRAY_LAYERS;
barriers.push_back(barrier);
}
state = {destination_stage, destination_access, destination_layout};
return barriers;
}
void Image::Transit(vk::ImageLayout destination_layout, vk::AccessFlags2 destination_access,
std::optional<ImageSubresourceRange> range, vk::CommandBuffer command_buffer) {
const auto transfer_access =
vk::AccessFlagBits2::eTransferRead | vk::AccessFlagBits2::eTransferWrite;
vk::PipelineStageFlags2 destination_stage {};
if (static_cast<bool>(destination_access & transfer_access)) {
destination_stage |= vk::PipelineStageFlagBits2::eTransfer;
}
if (!destination_access ||
static_cast<bool>(destination_access & ~vk::AccessFlags2 {transfer_access})) {
destination_stage |=
vk::PipelineStageFlagBits2::eAllGraphics | vk::PipelineStageFlagBits2::eComputeShader;
}
const auto barriers =
GetBarriers(destination_layout, destination_access, destination_stage, range);
if (barriers.empty()) {
return;
}
if (m_scheduler != nullptr) {
m_scheduler->EndRendering();
}
vk::DependencyInfo dependency {};
dependency.imageMemoryBarrierCount = static_cast<uint32_t>(barriers.size());
dependency.pImageMemoryBarriers = barriers.data();
command_buffer.pipelineBarrier2(dependency);
}
void Image::Upload(std::span<const vk::BufferImageCopy> copies, vk::Buffer buffer, uint64_t offset,
uint64_t size) {
EXIT_IF(m_scheduler == nullptr || copies.empty() || buffer == nullptr || size == 0);
m_scheduler->EndRendering();
vk::BufferMemoryBarrier2 buffer_barrier {};
buffer_barrier.srcStageMask = vk::PipelineStageFlagBits2::eAllCommands;
buffer_barrier.srcAccessMask = vk::AccessFlagBits2::eMemoryWrite;
buffer_barrier.dstStageMask = vk::PipelineStageFlagBits2::eTransfer;
buffer_barrier.dstAccessMask = vk::AccessFlagBits2::eTransferRead;
buffer_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
buffer_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
buffer_barrier.buffer = buffer;
buffer_barrier.offset = offset;
buffer_barrier.size = size;
const auto image_barriers =
GetBarriers(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits2::eTransferWrite,
vk::PipelineStageFlagBits2::eCopy, {});
vk::DependencyInfo dependency {};
dependency.dependencyFlags = vk::DependencyFlagBits::eByRegion;
dependency.bufferMemoryBarrierCount = 1;
dependency.pBufferMemoryBarriers = &buffer_barrier;
dependency.imageMemoryBarrierCount = static_cast<uint32_t>(image_barriers.size());
dependency.pImageMemoryBarriers = image_barriers.data();
auto command = m_scheduler->Current().Handle();
command.pipelineBarrier2(dependency);
command.copyBufferToImage(buffer, backing.image, vk::ImageLayout::eTransferDstOptimal,
static_cast<uint32_t>(copies.size()), copies.data());
buffer_barrier.srcStageMask = vk::PipelineStageFlagBits2::eTransfer;
buffer_barrier.srcAccessMask = vk::AccessFlagBits2::eTransferRead;
buffer_barrier.dstStageMask = vk::PipelineStageFlagBits2::eAllCommands;
buffer_barrier.dstAccessMask =
vk::AccessFlagBits2::eMemoryRead | vk::AccessFlagBits2::eMemoryWrite;
dependency.imageMemoryBarrierCount = 0;
dependency.pImageMemoryBarriers = nullptr;
command.pipelineBarrier2(dependency);
Transit(vk::ImageLayout::eGeneral,
vk::AccessFlagBits2::eShaderRead | vk::AccessFlagBits2::eTransferRead, {}, command);
}
void Image::Download(std::span<const vk::BufferImageCopy> copies, vk::Buffer buffer,
uint64_t offset, uint64_t size) {
EXIT_IF(m_scheduler == nullptr || copies.empty() || buffer == nullptr || size == 0);
m_scheduler->EndRendering();
vk::BufferMemoryBarrier2 buffer_barrier {};
buffer_barrier.srcStageMask = vk::PipelineStageFlagBits2::eAllCommands;
buffer_barrier.srcAccessMask =
vk::AccessFlagBits2::eMemoryRead | vk::AccessFlagBits2::eMemoryWrite;
buffer_barrier.dstStageMask = vk::PipelineStageFlagBits2::eCopy;
buffer_barrier.dstAccessMask = vk::AccessFlagBits2::eTransferWrite;
buffer_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
buffer_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
buffer_barrier.buffer = buffer;
buffer_barrier.offset = offset;
buffer_barrier.size = size;
const auto image_barriers =
GetBarriers(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits2::eTransferRead,
vk::PipelineStageFlagBits2::eCopy, {});
vk::DependencyInfo dependency {};
dependency.dependencyFlags = vk::DependencyFlagBits::eByRegion;
dependency.bufferMemoryBarrierCount = 1;
dependency.pBufferMemoryBarriers = &buffer_barrier;
dependency.imageMemoryBarrierCount = static_cast<uint32_t>(image_barriers.size());
dependency.pImageMemoryBarriers = image_barriers.data();
auto command = m_scheduler->Current().Handle();
command.pipelineBarrier2(dependency);
command.copyImageToBuffer(backing.image, vk::ImageLayout::eTransferSrcOptimal, buffer,
static_cast<uint32_t>(copies.size()), copies.data());
buffer_barrier.srcStageMask = vk::PipelineStageFlagBits2::eCopy;
buffer_barrier.srcAccessMask = vk::AccessFlagBits2::eTransferWrite;
buffer_barrier.dstStageMask = vk::PipelineStageFlagBits2::eAllCommands;
buffer_barrier.dstAccessMask =
vk::AccessFlagBits2::eMemoryRead | vk::AccessFlagBits2::eMemoryWrite;
dependency.imageMemoryBarrierCount = 0;
dependency.pImageMemoryBarriers = nullptr;
command.pipelineBarrier2(dependency);
}
std::pair<uint32_t, uint32_t> Image::SanitizeCopyLayers(const Image& source,
const Image& destination, uint32_t depth) {
const auto source_type = source.backing.image_type;
const auto destination_type = destination.backing.image_type;
uint32_t source_layers = source.backing.layers;
uint32_t destination_layers = destination.backing.layers;
if (source_type == vk::ImageType::e3D) {
source_layers = 1;
}
if (destination_type == vk::ImageType::e3D) {
destination_layers = 1;
}
if (source_type == destination_type) {
source_layers = destination_layers = std::min(source_layers, destination_layers);
} else if (source_type == vk::ImageType::e2D && destination_type == vk::ImageType::e3D) {
source_layers = depth;
} else if (source_type == vk::ImageType::e3D && destination_type == vk::ImageType::e2D) {
destination_layers = depth;
}
return {source_layers, destination_layers};
}
void Image::CopyImage(Image& source) {
EXIT_IF(m_scheduler == nullptr || source.backing.samples != backing.samples);
m_scheduler->EndRendering();
const uint32_t levels = std::min(source.backing.mip_levels, backing.mip_levels);
const uint32_t base_depth = backing.image_type == vk::ImageType::e3D
? backing.extent.depth
: source.backing.extent.depth;
const auto source_aspect =
FullAspectMask(source.backing.format) & ~vk::ImageAspectFlagBits::eStencil;
const auto destination_aspect =
FullAspectMask(backing.format) & ~vk::ImageAspectFlagBits::eStencil;
std::vector<vk::ImageCopy> copies;
copies.reserve(levels);
for (uint32_t level = 0; level < levels; level++) {
const auto width = std::max(source.backing.extent.width >> level, 1u);
const auto height = std::max(source.backing.extent.height >> level, 1u);
const auto depth = std::max(base_depth >> level, 1u);
const auto [source_layers, destination_layers] = SanitizeCopyLayers(source, *this, depth);
vk::ImageCopy copy {};
copy.srcSubresource = {source_aspect, level, 0, 1};
copy.dstSubresource = {destination_aspect, level, 0, 1};
if (source.backing.image_type == backing.image_type) {
if (source.backing.image_type == vk::ImageType::e3D) {
copy.extent = {width, height, depth};
} else {
copy.srcSubresource.layerCount = std::min(source_layers, destination_layers);
copy.dstSubresource.layerCount = copy.srcSubresource.layerCount;
copy.extent = {width, height, 1};
}
} else if (source.backing.image_type == vk::ImageType::e2D) {
copy.srcSubresource.layerCount = source_layers;
copy.extent = {width, height, source_layers};
} else {
copy.dstSubresource.layerCount = destination_layers;
copy.extent = {width, height, destination_layers};
}
copies.push_back(copy);
}
if (copies.empty()) {
return;
}
auto command = m_scheduler->Current().Handle();
source.Transit(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits2::eTransferRead, {},
command);
Transit(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits2::eTransferWrite, {}, command);
command.copyImage(source.backing.image, vk::ImageLayout::eTransferSrcOptimal, backing.image,
vk::ImageLayout::eTransferDstOptimal, static_cast<uint32_t>(copies.size()),
copies.data());
Transit(vk::ImageLayout::eGeneral,
vk::AccessFlagBits2::eShaderRead | vk::AccessFlagBits2::eTransferRead, {}, command);
}
void Image::Resolve(Image& source, const ImageSubresourceRange& source_range,
const ImageSubresourceRange& destination_range) {
EXIT_IF(m_scheduler == nullptr || backing.samples != 1 ||
source.backing.image_type != vk::ImageType::e2D ||
backing.image_type != vk::ImageType::e2D || source_range.level_count != 1 ||
destination_range.level_count != 1 ||
source_range.base_level >= source.backing.mip_levels ||
destination_range.base_level >= backing.mip_levels ||
source_range.base_layer >= source.backing.layers ||
destination_range.base_layer >= backing.layers);
const auto layers = std::min({source_range.layer_count, destination_range.layer_count,
source.backing.layers - source_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_height =
std::max(source.backing.extent.height >> source_range.base_level, 1u);
const auto destination_width =
std::max(backing.extent.width >> destination_range.base_level, 1u);
const auto destination_height =
std::max(backing.extent.height >> destination_range.base_level, 1u);
const bool copy = source.backing.samples == 1;
EXIT_IF(layers == 0 || info.extent.width > source_width || info.extent.height > source_height ||
info.extent.width > destination_width || info.extent.height > destination_height ||
(copy ? !ImageViewOps::FormatsCompatible(source.backing.format, backing.format)
: source.backing.format != backing.format));
auto resolved_source_range = source_range;
auto resolved_destination_range = destination_range;
resolved_source_range.layer_count = layers;
resolved_destination_range.layer_count = layers;
const vk::Extent3D resolve_extent {info.extent.width, info.extent.height, 1};
m_scheduler->EndRendering();
auto command = m_scheduler->Current().Handle();
source.Transit(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits2::eTransferRead,
resolved_source_range, command);
Transit(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits2::eTransferWrite,
resolved_destination_range, command);
if (copy) {
vk::ImageCopy region {};
region.srcSubresource = {vk::ImageAspectFlagBits::eColor, resolved_source_range.base_level,
resolved_source_range.base_layer, layers};
region.dstSubresource = {vk::ImageAspectFlagBits::eColor,
resolved_destination_range.base_level,
resolved_destination_range.base_layer, layers};
region.extent = resolve_extent;
command.copyImage(source.backing.image, vk::ImageLayout::eTransferSrcOptimal, backing.image,
vk::ImageLayout::eTransferDstOptimal, region);
} else {
vk::ImageResolve region {};
region.srcSubresource = {vk::ImageAspectFlagBits::eColor, resolved_source_range.base_level,
resolved_source_range.base_layer, layers};
region.dstSubresource = {vk::ImageAspectFlagBits::eColor,
resolved_destination_range.base_level,
resolved_destination_range.base_layer, layers};
region.extent = resolve_extent;
command.resolveImage(source.backing.image, vk::ImageLayout::eTransferSrcOptimal,
backing.image, vk::ImageLayout::eTransferDstOptimal, region);
}
}
uint32_t Image::CopyRows(uint64_t row_size, uint32_t rows, uint64_t capacity) noexcept {
if (row_size == 0 || rows == 0 || row_size > capacity) {
return 0;
}
return static_cast<uint32_t>(std::min<uint64_t>(rows, capacity / row_size));
}
void Image::CopyImageWithBuffer(Image& source, Buffer& buffer) {
EXIT_IF(m_scheduler == nullptr || buffer.Handle() == nullptr || source.backing.samples != 1 ||
backing.samples != 1);
m_scheduler->EndRendering();
const uint32_t levels = std::min(source.backing.mip_levels, backing.mip_levels);
const auto source_aspect =
FullAspectMask(source.backing.format) & ~vk::ImageAspectFlagBits::eStencil;
const auto destination_aspect =
FullAspectMask(backing.format) & ~vk::ImageAspectFlagBits::eStencil;
const auto source_bytes = DepthAspectTransferBytes(source.backing.format) != 0
? DepthAspectTransferBytes(source.backing.format)
: source.info.bytes_per_block;
const auto destination_bytes = DepthAspectTransferBytes(backing.format) != 0
? DepthAspectTransferBytes(backing.format)
: info.bytes_per_block;
const uint32_t source_block = source.info.IsBlock() ? 4u : 1u;
const uint32_t destination_block = info.IsBlock() ? 4u : 1u;
EXIT_IF(levels == 0 || source_bytes == 0 || source_bytes != destination_bytes ||
source_block != destination_block);
vk::BufferMemoryBarrier2 barrier {};
barrier.srcStageMask = vk::PipelineStageFlagBits2::eTransfer;
barrier.srcAccessMask = vk::AccessFlagBits2::eTransferRead;
barrier.dstStageMask = vk::PipelineStageFlagBits2::eTransfer;
barrier.dstAccessMask = vk::AccessFlagBits2::eTransferWrite;
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.buffer = buffer.Handle();
barrier.offset = 0;
vk::DependencyInfo dependency {};
dependency.dependencyFlags = vk::DependencyFlagBits::eByRegion;
dependency.bufferMemoryBarrierCount = 1;
dependency.pBufferMemoryBarriers = &barrier;
auto command = m_scheduler->Current().Handle();
source.Transit(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits2::eTransferRead, {},
command);
Transit(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits2::eTransferWrite, {}, command);
for (uint32_t level = 0; level < levels; level++) {
const auto width = std::max(source.backing.extent.width >> level, 1u);
const auto height = std::max(source.backing.extent.height >> level, 1u);
const auto source_depth = source.backing.image_type == vk::ImageType::e3D
? std::max(source.backing.extent.depth >> level, 1u)
: source.backing.layers;
const auto destination_depth = backing.image_type == vk::ImageType::e3D
? std::max(backing.extent.depth >> level, 1u)
: backing.layers;
const auto slices = std::min(source_depth, destination_depth);
const auto block_rows = (height + source_block - 1) / source_block;
const auto row_size =
static_cast<uint64_t>((width + source_block - 1) / source_block) * source_bytes;
const auto rows_per_copy = CopyRows(row_size, block_rows, buffer.Size());
EXIT_IF(slices == 0 || rows_per_copy == 0);
for (uint32_t slice = 0; slice < slices; slice++) {
for (uint32_t block_row = 0; block_row < block_rows; block_row += rows_per_copy) {
const auto copy_rows = std::min(rows_per_copy, block_rows - block_row);
const auto y = block_row * source_block;
const auto copy_height = std::min(copy_rows * source_block, height - y);
const auto copy_size = row_size * copy_rows;
vk::BufferImageCopy source_copy {};
source_copy.imageSubresource = {
source_aspect, level,
source.backing.image_type == vk::ImageType::e3D ? 0u : slice, 1};
source_copy.imageOffset = {0, static_cast<int32_t>(y),
source.backing.image_type == vk::ImageType::e3D
? static_cast<int32_t>(slice)
: 0};
source_copy.imageExtent = {width, copy_height, 1};
auto destination_copy = source_copy;
destination_copy.imageSubresource = {
destination_aspect, level,
backing.image_type == vk::ImageType::e3D ? 0u : slice, 1};
destination_copy.imageOffset.z =
backing.image_type == vk::ImageType::e3D ? static_cast<int32_t>(slice) : 0;
barrier.size = copy_size;
barrier.srcAccessMask = vk::AccessFlagBits2::eTransferRead;
barrier.dstAccessMask = vk::AccessFlagBits2::eTransferWrite;
command.pipelineBarrier2(dependency);
command.copyImageToBuffer(source.backing.image,
vk::ImageLayout::eTransferSrcOptimal, buffer.Handle(),
source_copy);
barrier.srcAccessMask = vk::AccessFlagBits2::eTransferWrite;
barrier.dstAccessMask = vk::AccessFlagBits2::eTransferRead;
command.pipelineBarrier2(dependency);
command.copyBufferToImage(buffer.Handle(), backing.image,
vk::ImageLayout::eTransferDstOptimal, destination_copy);
}
}
}
Transit(vk::ImageLayout::eGeneral,
vk::AccessFlagBits2::eShaderRead | vk::AccessFlagBits2::eTransferRead, {}, command);
}
void Image::CopyMip(Image& source, uint32_t mip, uint32_t layer) {
EXIT_IF(m_scheduler == nullptr || source.backing.samples != backing.samples ||
mip >= backing.mip_levels || layer >= backing.layers);
m_scheduler->EndRendering();
const auto width = std::max(backing.extent.width >> mip, 1u);
const auto height = std::max(backing.extent.height >> mip, 1u);
const auto depth = std::max(backing.extent.depth >> mip, 1u);
EXIT_IF(width != source.backing.extent.width || height != source.backing.extent.height);
const auto [source_layers, destination_layers] = SanitizeCopyLayers(source, *this, depth);
const auto aspects = FullAspectMask(source.backing.format);
EXIT_IF(aspects != FullAspectMask(backing.format));
std::array<vk::ImageCopy, 2> copies {};
uint32_t copy_count = 0;
for (const auto aspect: {vk::ImageAspectFlagBits::eColor, vk::ImageAspectFlagBits::eDepth,
vk::ImageAspectFlagBits::eStencil}) {
if (!static_cast<bool>(aspects & aspect)) {
continue;
}
auto& copy = copies[copy_count++];
copy.srcSubresource = {aspect, 0, 0, source_layers};
copy.dstSubresource = {aspect, mip, layer, destination_layers};
copy.extent = {width, height, depth};
}
auto command = m_scheduler->Current().Handle();
Transit(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits2::eTransferWrite, {}, command);
source.Transit(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits2::eTransferRead, {},
command);
command.copyImage(source.backing.image, vk::ImageLayout::eTransferSrcOptimal, backing.image,
vk::ImageLayout::eTransferDstOptimal, copy_count, copies.data());
Transit(vk::ImageLayout::eGeneral,
vk::AccessFlagBits2::eShaderRead | vk::AccessFlagBits2::eTransferRead, {}, command);
}
namespace ImageOps {
void Validate(const ImageInfo& info) {
ValidateRange(info.data, "data");
ValidateRange(info.stencil, "stencil");
if (info.pixel_format == vk::Format::eUndefined) {
const bool metadata_empty =
info.metadata.range.address == 0 && info.metadata.range.size == 0 &&
info.metadata.kind == ImageMetadataKind::None && info.metadata.control == 0 &&
info.metadata.compression == VideoOutCompression::Uncompressed &&
!info.metadata.stencil_compressed;
if (info.data.Empty() || info.HasStencil() || !metadata_empty || info.extent.width == 0 ||
info.extent.height == 0 || info.extent.depth == 0 || info.resources.levels != 1 ||
info.resources.layers != 1 || info.samples != 1 || info.pitch != 0 ||
info.bytes_per_block != 0) {
EXIT("invalid stencil association image\n");
}
return;
}
if (info.extent.width == 0 || info.extent.height == 0 || info.extent.depth == 0 ||
info.resources.levels == 0 || info.resources.levels > info.mip_layout.size() ||
info.resources.layers == 0 || info.samples == 0 ||
vulkan_sample_count(info.samples) == vk::SampleCountFlagBits {} ||
info.bytes_per_block == 0 || (info.data.address != 0 && info.pitch == 0)) {
EXIT("invalid image geometry or format\n");
}
switch (info.type) {
case Prospero::ImageType::kColor1D:
if (info.extent.height != 1 || info.extent.depth != 1) {
EXIT("invalid 1D image shape\n");
}
break;
case Prospero::ImageType::kColor3D:
if (info.resources.layers != 1) {
EXIT("3D images cannot have array layers\n");
}
break;
case Prospero::ImageType::kColor2D:
if (info.extent.depth != 1) {
EXIT("invalid 2D image shape\n");
}
break;
default: EXIT("non-base image type: %u\n", static_cast<uint32_t>(info.type));
}
if (info.samples > 1 && info.resources.levels != 1) {
EXIT("multisampled images cannot have mip levels\n");
}
if (info.metadata.stencil_compressed && !info.HasStencil()) {
EXIT("compressed stencil metadata requires a stencil plane\n");
}
switch (info.metadata.kind) {
case ImageMetadataKind::None:
if (info.metadata.range.address != 0 || info.metadata.range.size != 0 ||
info.metadata.control != 0 ||
info.metadata.compression != VideoOutCompression::Uncompressed ||
info.metadata.stencil_compressed) {
EXIT("metadata-free image has metadata state\n");
}
break;
case ImageMetadataKind::Htile:
ValidateRange(info.metadata.range, "HTILE");
if (info.metadata.range.Empty() ||
info.metadata.compression != VideoOutCompression::Uncompressed) {
EXIT("invalid HTILE metadata\n");
}
break;
case ImageMetadataKind::Dcc:
if (info.metadata.range.address == 0 ||
info.metadata.range.address >= TRACKER_ADDRESS_SIZE ||
(info.metadata.range.size != 0 &&
info.metadata.range.size > TRACKER_ADDRESS_SIZE - info.metadata.range.address) ||
info.metadata.compression == VideoOutCompression::Unsupported) {
EXIT("invalid DCC metadata\n");
}
break;
}
}
uint32_t RenderTargetTransferFormat(uint32_t bytes_per_element) {
switch (bytes_per_element) {
case 1: return Prospero::GpuEnumValue(Prospero::BufferFormat::k8UNorm);
case 2: return Prospero::GpuEnumValue(Prospero::BufferFormat::k16UNorm);
case 4: return Prospero::GpuEnumValue(Prospero::BufferFormat::k32Float);
case 8: return Prospero::GpuEnumValue(Prospero::BufferFormat::k16_16_16_16Float);
case 16: return Prospero::GpuEnumValue(Prospero::BufferFormat::k32_32_32_32Float);
default: EXIT("unsupported render-target element size: %u\n", bytes_per_element);
}
}
} // namespace ImageOps
Image::Image(GraphicContext& graphics, CommandScheduler& scheduler, const ImageInfo& image_info)
: info(image_info), m_graphics(&graphics), m_scheduler(&scheduler) {
KYTY_PROFILER_FUNCTION();
ImageOps::Validate(info);
m_cpu_dirty = !info.data.Empty();
if (info.pixel_format == vk::Format::eUndefined) {
return;
}
backing.format = info.pixel_format;
backing.image_type = HostImageType(info.type);
backing.extent = info.extent;
backing.guest_pitch = info.pitch;
backing.layers = info.IsVolume() ? 1u : info.resources.layers;
backing.mip_levels = info.resources.levels;
backing.samples = info.samples;
backing.flags = ImageCreateFlags(info);
backing.usage = ImageUsageFlags(graphics, info);
vk::ImageCreateInfo create {};
create.sType = vk::StructureType::eImageCreateInfo;
create.flags = backing.flags;
create.imageType = backing.image_type;
create.extent = backing.extent;
create.mipLevels = backing.mip_levels;
create.arrayLayers = backing.layers;
create.format = backing.format;
create.tiling = vk::ImageTiling::eOptimal;
create.initialLayout = backing.state.layout;
create.usage = backing.usage;
create.sharingMode = vk::SharingMode::eExclusive;
create.samples = vulkan_sample_count(backing.samples);
vk::ImageFormatProperties properties {};
if (graphics.GetImageFormatProperties(create.format, create.imageType, create.tiling,
create.usage, create.flags,
&properties) != vk::Result::eSuccess ||
!static_cast<bool>(properties.sampleCounts & create.samples)) {
EXIT("image format does not support required usage: format=%d type=%d usage=0x%x "
"flags=0x%x samples=%u\n",
static_cast<int>(create.format), static_cast<int>(create.imageType),
static_cast<vk::ImageUsageFlags::MaskType>(create.usage),
static_cast<vk::ImageCreateFlags::MaskType>(create.flags), backing.samples);
}
backing.memory.property = vk::MemoryPropertyFlagBits::eDeviceLocal;
if (!graphics.CreateImage(create, backing)) {
EXIT("failed to create image: extent=%ux%ux%u format=%d layers=%u levels=%u\n",
create.extent.width, create.extent.height, create.extent.depth,
static_cast<int>(create.format), create.arrayLayers, create.mipLevels);
}
}
uint64_t Image::HashGuestEdges() const {
constexpr uint64_t page_mask = TRACKER_PAGE_SIZE - 1;
std::array<uint8_t, TRACKER_PAGE_SIZE * 2> bytes {};
const auto range = info.data;
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 head_size = head_end - range.address;
const uint64_t tail_address = tail_begin < head_end ? head_end : tail_begin;
const uint64_t tail_size = range.End() - tail_address;
if ((head_size != 0 &&
!LibKernel::Memory::TryReadBacking(range.address, bytes.data(), head_size)) ||
(tail_size != 0 &&
!LibKernel::Memory::TryReadBacking(tail_address, bytes.data() + head_size, tail_size))) {
EXIT("Image: failed to hash guest backing\n");
}
return XXH3_64bits(bytes.data(), static_cast<size_t>(head_size + tail_size));
}
Image::~Image() {
KYTY_PROFILER_FUNCTION();
if (m_graphics == nullptr) {
return;
}
{
std::lock_guard lock(views.mutex);
for (auto& cached: views.views) {
if (cached.view != nullptr) {
m_graphics->device.destroyImageView(cached.view, nullptr);
cached.view = nullptr;
}
}
views.views.clear();
}
if (backing.image != nullptr) {
m_graphics->DeleteImage(backing);
}
}
} // namespace Libs::Graphics
@@ -0,0 +1,197 @@
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_IMAGE_H_
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_IMAGE_H_
#include "common/assert.h"
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/renderer/image/imageInfo.h"
#include <compare>
#include <limits>
#include <mutex>
#include <optional>
#include <span>
#include <utility>
#include <vector>
namespace Libs::Graphics {
class Buffer;
class CommandScheduler;
struct ImageTestAccess;
struct ImageId {
uint32_t index = std::numeric_limits<uint32_t>::max();
uint32_t generation = 0;
[[nodiscard]] explicit operator bool() const noexcept {
return index != std::numeric_limits<uint32_t>::max();
}
auto operator<=>(const ImageId&) const = default;
};
struct CachedImageView {
ImageViewInfo info;
vk::ImageView view = nullptr;
};
struct ImageViewCache {
ImageViewCache() = default;
KYTY_CLASS_NO_COPY(ImageViewCache);
std::mutex mutex;
std::vector<CachedImageView> views;
};
struct ImageUsage {
bool texture = false;
bool storage = false;
bool render_target = false;
bool depth_target = false;
bool video_out = false;
};
struct ImageBinding {
bool is_bound = false;
bool is_target = false;
bool needs_rebind = false;
bool force_general = false;
};
class Image final {
public:
Image(GraphicContext& graphics, CommandScheduler& scheduler, const ImageInfo& info);
~Image();
KYTY_CLASS_NO_COPY(Image);
[[nodiscard]] vk::ImageView FindView(const ImageViewInfo& view_info);
void AssociateDepth(ImageId image_id) { depth_id = image_id; }
using Barriers = std::vector<vk::ImageMemoryBarrier2>;
[[nodiscard]] Barriers GetBarriers(vk::ImageLayout destination_layout,
vk::AccessFlags2 destination_access,
vk::PipelineStageFlags2 destination_stage,
std::optional<ImageSubresourceRange> range);
void Transit(vk::ImageLayout destination_layout, vk::AccessFlags2 destination_access,
std::optional<ImageSubresourceRange> range, vk::CommandBuffer command_buffer);
void Upload(std::span<const vk::BufferImageCopy> copies, vk::Buffer buffer, uint64_t offset,
uint64_t size);
void Download(std::span<const vk::BufferImageCopy> copies, vk::Buffer buffer, uint64_t offset,
uint64_t size);
void CopyImage(Image& source);
void Resolve(Image& source, const ImageSubresourceRange& source_range,
const ImageSubresourceRange& destination_range);
void CopyImageWithBuffer(Image& source, Buffer& buffer);
void CopyMip(Image& source, uint32_t mip, uint32_t layer);
void InvalidateCpuWrite(uint64_t vaddr, uint64_t size) {
if (ImageRangeOverlaps(info.data.address, info.data.size, vaddr, size)) {
m_cpu_dirty = true;
m_maybe_cpu_dirty = false;
m_maybe_hash_valid = false;
} else if (ImagePageRangesOverlap(info.data.address, info.data.size, vaddr, size)) {
m_maybe_cpu_dirty = true;
}
}
[[nodiscard]] bool IsCpuDirty() const { return m_cpu_dirty || m_maybe_cpu_dirty; }
[[nodiscard]] bool IsDefinitelyCpuDirty() const { return m_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 {
return m_maybe_cpu_dirty && !m_maybe_hash_valid;
}
void SetMaybeCpuHash(uint64_t hash) {
if (!NeedsMaybeCpuHash()) {
EXIT("image cannot initialize maybe-dirty hash\n");
}
m_maybe_cpu_hash = hash;
m_maybe_hash_valid = true;
}
[[nodiscard]] bool ResolveMaybeCpuHash(uint64_t hash) {
if (!m_maybe_cpu_dirty || !m_maybe_hash_valid || m_cpu_dirty) {
EXIT("image cannot resolve maybe-dirty hash\n");
}
m_maybe_cpu_dirty = false;
m_maybe_hash_valid = false;
m_cpu_dirty |= hash != m_maybe_cpu_hash;
return m_cpu_dirty;
}
void RefreshComplete() {
if (!IsCpuDirty()) {
EXIT("clean image cannot complete a refresh\n");
}
m_cpu_dirty = false;
m_maybe_cpu_dirty = false;
m_maybe_hash_valid = false;
}
[[nodiscard]] bool IsGpuModified() const noexcept { return m_gpu_modified; }
void MarkGpuModified() noexcept { m_gpu_modified = true; }
void ClearGpuModified() noexcept { m_gpu_modified = false; }
[[nodiscard]] bool IsBufferModified() const noexcept { return m_buffer_modified; }
void MarkBufferModified() noexcept { m_buffer_modified = true; }
void ClearBufferModified() noexcept { m_buffer_modified = false; }
[[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)
: ImageRangeOverlaps(info.data.address, info.data.size, address, size);
}
[[nodiscard]] bool GpuOverlaps(uint64_t address, uint64_t size) const noexcept {
return IsGpuModified() && Overlaps(address, size);
}
[[nodiscard]] bool SafeToDownload() const noexcept {
return IsGpuModified() && !IsBufferModified() && !IsCpuDirty();
}
[[nodiscard]] bool IsTracked() const noexcept { return track_addr != 0 && track_addr_end != 0; }
[[nodiscard]] uint64_t AccountedSize() const noexcept {
return backing.image == nullptr ? 0 : (info.data.size + 1023) & ~uint64_t {1023};
}
[[nodiscard]] uint64_t HashGuestEdges() const;
ImageInfo info;
VulkanImage backing;
ImageViewCache views;
ImageUsage usage;
ImageBinding binding;
bool registered = false;
uint64_t track_addr = 0;
uint64_t track_addr_end = 0;
ImageId depth_id {};
uint64_t tick_accessed_last = 0;
size_t lru_id = 0;
private:
friend struct ImageTestAccess;
[[nodiscard]] static vk::ImageAspectFlags FullAspectMask(vk::Format format) noexcept;
[[nodiscard]] static uint32_t CopyRows(uint64_t row_size, uint32_t rows,
uint64_t capacity) noexcept;
[[nodiscard]] static std::pair<uint32_t, uint32_t>
SanitizeCopyLayers(const Image& source, const Image& destination, uint32_t depth);
GraphicContext* m_graphics = nullptr;
CommandScheduler* m_scheduler = nullptr;
uint64_t m_maybe_cpu_hash = 0;
bool m_cpu_dirty = false;
bool m_maybe_cpu_dirty = false;
bool m_maybe_hash_valid = false;
bool m_gpu_modified = false;
bool m_buffer_modified = false;
};
namespace ImageOps {
void Validate(const ImageInfo& info);
[[nodiscard]] uint32_t RenderTargetTransferFormat(uint32_t bytes_per_element);
} // namespace ImageOps
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_IMAGE_H_
@@ -0,0 +1,573 @@
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_IMAGEINFO_H_
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_IMAGEINFO_H_
#include "common/assert.h"
#include "graphics/guest_gpu/gpu_defs.h"
#include "graphics/guest_gpu/gpu_format.h"
#include "graphics/host_gpu/regionDefinitions.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include <algorithm>
#include <array>
#include <bit>
#include <cmath>
#include <cstdint>
namespace Libs::Graphics {
struct GuestRange {
uint64_t address = 0;
uint64_t size = 0;
[[nodiscard]] constexpr bool Empty() const noexcept { return address == 0 || size == 0; }
[[nodiscard]] constexpr bool Valid() const noexcept {
return !Empty() && address < TRACKER_ADDRESS_SIZE && size <= TRACKER_ADDRESS_SIZE - address;
}
[[nodiscard]] constexpr uint64_t End() const noexcept { return address + size; }
auto operator<=>(const GuestRange&) const = default;
};
enum class VideoOutCompression : uint8_t { Uncompressed, Dcc256_256_0, Dcc256_64_64, Unsupported };
enum class ImageMetadataKind : uint8_t { None, Htile, Dcc };
struct ImageMetadataInfo {
GuestRange range;
ImageMetadataKind kind = ImageMetadataKind::None;
uint32_t control = 0;
VideoOutCompression compression = VideoOutCompression::Uncompressed;
bool stencil_compressed = false;
};
struct ImageSubresources {
uint32_t levels = 1;
uint32_t layers = 1;
auto operator<=>(const ImageSubresources&) const = default;
};
struct ImageSubresourceRange {
uint32_t base_level = 0;
uint32_t level_count = 1;
uint32_t base_layer = 0;
uint32_t layer_count = 1;
auto operator<=>(const ImageSubresourceRange&) const = default;
};
struct ImageMipInfo {
uint64_t offset = 0;
uint64_t size = 0;
uint32_t pitch = 0;
uint32_t height = 0;
auto operator<=>(const ImageMipInfo&) const = default;
};
struct ImageInfo {
GuestRange data;
GuestRange stencil;
ImageMetadataInfo metadata;
uint32_t htile_clear_mask = UINT32_MAX;
vk::Format pixel_format = vk::Format::eUndefined;
uint32_t guest_format = 0;
Prospero::ImageType type = Prospero::ImageType::kColor2D;
vk::Extent3D extent = {1, 1, 1};
ImageSubresources resources;
uint32_t pitch = 0;
uint32_t bytes_per_block = 0;
uint32_t samples = 1;
uint32_t tile_mode = 0;
bool bgra16 = false;
std::array<ImageMipInfo, 16> mip_layout {};
[[nodiscard]] constexpr bool HasStencil() const noexcept { return !stencil.Empty(); }
[[nodiscard]] constexpr bool HasMetadata() const noexcept {
return metadata.kind != ImageMetadataKind::None;
}
[[nodiscard]] bool IsDepth() const noexcept;
[[nodiscard]] bool IsBlock() const noexcept {
return Prospero::BlockCompressedBytesPerBlock(guest_format) != 0;
}
[[nodiscard]] bool IsTiled() const noexcept {
return tile_mode != Prospero::GpuEnumValue(Prospero::TileMode::kLinear);
}
[[nodiscard]] constexpr bool IsVolume() const noexcept {
return type == Prospero::ImageType::kColor3D;
}
[[nodiscard]] constexpr bool IsLayered() const noexcept {
return !IsVolume() && resources.layers > 1;
}
[[nodiscard]] constexpr uint32_t TransferLayers() const noexcept {
return IsVolume() ? extent.depth : resources.layers;
}
[[nodiscard]] vk::Extent2D BlockExtent() const noexcept {
const auto shift = Prospero::BlockCompressedBytesPerBlock(guest_format) != 0 ? 2u : 0u;
return {pitch >> shift, extent.height >> shift};
}
[[nodiscard]] bool IsCompatible(const ImageInfo& other) const noexcept {
return pixel_format == other.pixel_format && samples == other.samples &&
bytes_per_block == other.bytes_per_block;
}
[[nodiscard]] int32_t MipOf(const ImageInfo& container) const noexcept {
if (!IsCompatible(container) || tile_mode != container.tile_mode || resources.levels != 1 ||
container.resources.layers == 0 ||
container.resources.levels > container.mip_layout.size()) {
return -1;
}
if (HasStencil() != container.HasStencil() ||
(HasStencil() && (stencil.address < container.stencil.address ||
stencil.End() > container.stencil.End()))) {
return -1;
}
int32_t mip = -1;
for (uint32_t level = 0; level < container.resources.levels; level++) {
const auto& layout = container.mip_layout[level];
if (layout.size == 0 || layout.size % container.resources.layers != 0 ||
container.data.address > UINT64_MAX - layout.offset) {
continue;
}
const auto mip_base = container.data.address + layout.offset;
const auto slice_size = layout.size / container.resources.layers;
if (slice_size == 0 || mip_base > UINT64_MAX - layout.size) {
continue;
}
const auto mip_end = mip_base + layout.size;
if (data.address >= mip_base && data.address < mip_end &&
(data.address - mip_base) % slice_size == 0) {
mip = static_cast<int32_t>(level);
break;
}
}
if (mip < 0) {
return -1;
}
const auto level = static_cast<uint32_t>(mip);
if (extent.width != std::max(container.extent.width >> level, 1u) ||
extent.height != std::max(container.extent.height >> level, 1u)) {
return -1;
}
const auto mip_depth = std::max(container.extent.depth >> level, 1u);
if (container.type == Prospero::ImageType::kColor3D &&
type == Prospero::ImageType::kColor2D) {
if (resources.layers != mip_depth) {
return -1;
}
} else if (type != container.type) {
return -1;
}
return mip;
}
[[nodiscard]] int32_t SliceOf(const ImageInfo& container, int32_t mip) const noexcept {
if (!IsCompatible(container) || type != container.type || mip < 0 ||
static_cast<uint32_t>(mip) >= container.resources.levels ||
container.resources.levels > container.mip_layout.size() ||
container.resources.layers == 0 || data.size == 0) {
return -1;
}
const auto level = static_cast<uint32_t>(mip);
if (extent.width != std::max(container.extent.width >> level, 1u) ||
extent.height != std::max(container.extent.height >> level, 1u)) {
return -1;
}
const auto& layout = container.mip_layout[level];
if (layout.size == 0 || layout.size % container.resources.layers != 0 ||
container.data.address > UINT64_MAX - layout.offset) {
return -1;
}
const auto slice_size = layout.size / container.resources.layers;
if (slice_size == 0 || data.size % slice_size != 0) {
return -1;
}
const auto mip_base = container.data.address + layout.offset;
if (data.address < mip_base) {
return -1;
}
const auto address_delta = data.address - mip_base;
if (address_delta % data.size != 0 || address_delta / data.size > INT32_MAX) {
return -1;
}
return static_cast<int32_t>(address_delta / data.size);
}
};
struct ImageViewInfo {
vk::Format format = vk::Format::eUndefined;
vk::ImageViewType type = vk::ImageViewType::e2D;
vk::ImageAspectFlags aspect = vk::ImageAspectFlagBits::eColor;
uint32_t base_level = 0;
uint32_t level_count = 1;
uint32_t base_layer = 0;
uint32_t layer_count = 1;
vk::ComponentMapping mapping = {};
vk::ImageUsageFlags usage = vk::ImageUsageFlagBits::eSampled;
[[nodiscard]] bool operator==(const ImageViewInfo& rhs) const noexcept {
return format == rhs.format && type == rhs.type && aspect == rhs.aspect &&
base_level == rhs.base_level && level_count == rhs.level_count &&
base_layer == rhs.base_layer && layer_count == rhs.layer_count &&
mapping.r == rhs.mapping.r && mapping.g == rhs.mapping.g &&
mapping.b == rhs.mapping.b && mapping.a == rhs.mapping.a && usage == rhs.usage;
}
};
struct DepthFormatPolicy {
Prospero::DepthFormat depth_format;
Prospero::BufferFormat guest_format;
uint32_t bytes_per_element;
vk::Format sampled_view_format;
vk::Format depth_attachment_format;
std::array<vk::Format, 3> stencil_attachment_formats;
};
inline constexpr std::array<DepthFormatPolicy, 2> DEPTH_FORMAT_POLICIES {{
{Prospero::DepthFormat::kZ16,
Prospero::BufferFormat::k16UNorm,
2,
vk::Format::eR16Unorm,
vk::Format::eD16Unorm,
{vk::Format::eD16UnormS8Uint, vk::Format::eD24UnormS8Uint, vk::Format::eD32SfloatS8Uint}},
{Prospero::DepthFormat::kZ32F,
Prospero::BufferFormat::k32Float,
4,
vk::Format::eR32Sfloat,
vk::Format::eD32Sfloat,
{vk::Format::eD32SfloatS8Uint, vk::Format::eUndefined, vk::Format::eUndefined}},
}};
[[nodiscard]] inline constexpr const DepthFormatPolicy*
FindDepthFormatPolicy(uint32_t depth_format) noexcept {
for (const auto& policy: DEPTH_FORMAT_POLICIES) {
if (Prospero::GpuEnumValue(policy.depth_format) == depth_format) {
return &policy;
}
}
return nullptr;
}
[[nodiscard]] inline constexpr const DepthFormatPolicy*
FindGuestDepthFormatPolicy(uint32_t guest_format) noexcept {
for (const auto& policy: DEPTH_FORMAT_POLICIES) {
if (Prospero::GpuEnumValue(policy.guest_format) == guest_format) {
return &policy;
}
}
return nullptr;
}
[[nodiscard]] inline constexpr bool IsStencilAttachmentFormat(const DepthFormatPolicy& policy,
vk::Format format) noexcept {
for (const auto candidate: policy.stencil_attachment_formats) {
if (candidate != vk::Format::eUndefined && candidate == format) {
return true;
}
}
return false;
}
[[nodiscard]] inline constexpr vk::Format DepthAttachmentFormat(const DepthFormatPolicy& policy,
bool has_stencil) noexcept {
return has_stencil ? policy.stencil_attachment_formats.front() : policy.depth_attachment_format;
}
[[nodiscard]] inline constexpr vk::Format DepthAttachmentFormat(uint32_t depth_format,
uint32_t stencil_format) noexcept {
bool has_stencil = false;
switch (static_cast<Prospero::StencilFormat>(stencil_format)) {
case Prospero::StencilFormat::kInvalid: break;
case Prospero::StencilFormat::k8UInt: has_stencil = true; break;
default: return vk::Format::eUndefined;
}
const auto* policy = FindDepthFormatPolicy(depth_format);
return policy == nullptr ? vk::Format::eUndefined : DepthAttachmentFormat(*policy, has_stencil);
}
[[nodiscard]] inline constexpr vk::ImageUsageFlags DepthTargetImageUsage() noexcept {
return vk::ImageUsageFlagBits::eDepthStencilAttachment | vk::ImageUsageFlagBits::eSampled |
vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst;
}
[[nodiscard]] inline constexpr vk::Format DepthAspectTransferFormat(vk::Format format) noexcept {
switch (format) {
case vk::Format::eD16Unorm:
case vk::Format::eD16UnormS8Uint: return vk::Format::eD16Unorm;
case vk::Format::eD24UnormS8Uint: return vk::Format::eX8D24UnormPack32;
case vk::Format::eD32Sfloat:
case vk::Format::eD32SfloatS8Uint: return vk::Format::eD32Sfloat;
default: return vk::Format::eUndefined;
}
}
inline bool ImageInfo::IsDepth() const noexcept {
return DepthAspectTransferFormat(pixel_format) != vk::Format::eUndefined;
}
[[nodiscard]] inline constexpr uint32_t DepthAspectTransferBytes(vk::Format format) noexcept {
switch (DepthAspectTransferFormat(format)) {
case vk::Format::eD16Unorm: return 2;
case vk::Format::eX8D24UnormPack32:
case vk::Format::eD32Sfloat: return 4;
default: return 0;
}
}
[[nodiscard]] inline constexpr uint32_t EncodeD16AsD24(uint16_t value) noexcept {
return static_cast<uint32_t>((static_cast<uint64_t>(value) * 0x00ffffffu + 0x7fffu) / 0xffffu);
}
[[nodiscard]] inline uint32_t EncodeD16AsD32(uint16_t value) noexcept {
return std::bit_cast<uint32_t>(static_cast<float>(value) / 65535.0f);
}
[[nodiscard]] inline constexpr bool IsSupportedSampledDepthFormat(vk::Format image_format,
uint32_t guest_format,
vk::Format view_format) noexcept {
const auto* policy = FindGuestDepthFormatPolicy(guest_format);
return policy != nullptr && view_format == policy->sampled_view_format &&
(image_format == policy->depth_attachment_format ||
IsStencilAttachmentFormat(*policy, image_format));
}
[[nodiscard]] inline constexpr bool IsSupportedSampledDepthFormat(vk::Format image_format,
vk::Format view_format) noexcept {
for (const auto& policy: DEPTH_FORMAT_POLICIES) {
if (IsSupportedSampledDepthFormat(image_format, Prospero::GpuEnumValue(policy.guest_format),
view_format)) {
return true;
}
}
return false;
}
[[nodiscard]] inline constexpr bool IsSupportedDepthTargetFormat(const ImageInfo& info) {
const auto* policy = FindGuestDepthFormatPolicy(info.guest_format);
return policy != nullptr && info.bytes_per_block == policy->bytes_per_element &&
(info.HasStencil() ? IsStencilAttachmentFormat(*policy, info.pixel_format)
: info.pixel_format == policy->depth_attachment_format);
}
[[nodiscard]] inline constexpr bool IsSupportedDepthPlaneReadback(const ImageInfo& info) {
if (!IsSupportedDepthTargetFormat(info)) {
return false;
}
const auto transfer_bytes = DepthAspectTransferBytes(info.pixel_format);
return transfer_bytes == info.bytes_per_block ||
(info.bytes_per_block == sizeof(uint16_t) && transfer_bytes == sizeof(uint32_t));
}
[[nodiscard]] inline VideoOutCompression
ClassifyVideoOutCompression(bool compressed, uint64_t metadata_address, uint32_t dcc_control,
uint64_t dcc_clear_color) noexcept {
constexpr uint32_t DCC_256_256_0 = 0x00000048u;
constexpr uint32_t DCC_256_64_64 = 0x00000208u;
if (!compressed) {
return metadata_address == 0 && dcc_control == 0 && dcc_clear_color == 0
? VideoOutCompression::Uncompressed
: VideoOutCompression::Unsupported;
}
if (metadata_address == 0 || (metadata_address & 0xffu) != 0 || dcc_clear_color != 0) {
return VideoOutCompression::Unsupported;
}
switch (dcc_control) {
case DCC_256_256_0: return VideoOutCompression::Dcc256_256_0;
case DCC_256_64_64: return VideoOutCompression::Dcc256_64_64;
default: return VideoOutCompression::Unsupported;
}
}
[[nodiscard]] inline constexpr bool
CanUseVideoOutNativeWithoutUpload(VideoOutCompression compression, bool render_target,
bool gpu_modified, bool guest_modified) noexcept {
return compression != VideoOutCompression::Uncompressed &&
compression != VideoOutCompression::Unsupported && !guest_modified &&
(render_target || gpu_modified);
}
struct VideoOutPixelFormatInfo {
vk::Format format = vk::Format::eUndefined;
uint32_t guest_format = 0;
uint32_t bytes_per_element = 0;
bool bgra16 = false;
};
struct VideoOutFormatPolicy {
uint64_t pixel_format;
VideoOutPixelFormatInfo info;
};
inline constexpr std::array<VideoOutFormatPolicy, 6> VIDEO_OUT_FORMAT_POLICIES {{
{0x8000000022000000ull,
{vk::Format::eR8G8B8A8Srgb, Prospero::GpuEnumValue(Prospero::BufferFormat::k8_8_8_8Srgb), 4,
false}},
{0x8000000000000000ull,
{vk::Format::eB8G8R8A8Srgb, Prospero::GpuEnumValue(Prospero::BufferFormat::k8_8_8_8Srgb), 4,
false}},
{0x8100000022000000ull,
{vk::Format::eA2B10G10R10UnormPack32,
Prospero::GpuEnumValue(Prospero::BufferFormat::k10_10_10_2UNorm), 4, false}},
{0x8100000000000000ull,
{vk::Format::eA2R10G10B10UnormPack32,
Prospero::GpuEnumValue(Prospero::BufferFormat::k10_10_10_2UNorm), 4, false}},
{0xc001000622000000ull,
{vk::Format::eR16G16B16A16Sfloat,
Prospero::GpuEnumValue(Prospero::BufferFormat::k16_16_16_16Float), 8, false}},
{0xc001000600000000ull,
{vk::Format::eR16G16B16A16Sfloat,
Prospero::GpuEnumValue(Prospero::BufferFormat::k16_16_16_16Float), 8, true}},
}};
[[nodiscard]] inline bool DecodeVideoOutPixelFormat(uint64_t pixel_format,
VideoOutPixelFormatInfo& info) {
for (const auto& policy: VIDEO_OUT_FORMAT_POLICIES) {
if (policy.pixel_format == pixel_format) {
info = policy.info;
return true;
}
}
return false;
}
[[nodiscard]] inline bool IsSupportedVideoOutFormat(const ImageInfo& info) {
for (const auto& policy: VIDEO_OUT_FORMAT_POLICIES) {
if (info.pixel_format == policy.info.format &&
info.guest_format == policy.info.guest_format &&
info.bytes_per_block == policy.info.bytes_per_element &&
info.bgra16 == policy.info.bgra16) {
return true;
}
}
return false;
}
[[nodiscard]] inline constexpr bool
IsSupportedDisplayRenderTargetTileMode(uint32_t tile_mode) noexcept {
return tile_mode == Prospero::GpuEnumValue(Prospero::TileMode::kRenderTarget);
}
[[nodiscard]] inline constexpr bool IsSupportedStandard64RenderTarget(const ImageInfo& info) {
if (info.tile_mode != Prospero::GpuEnumValue(Prospero::TileMode::kStandard64KB) ||
info.data.address == 0 || (info.data.address & 0xffffu) != 0 || info.extent.width == 0 ||
info.extent.height == 0 || info.bytes_per_block != 4 || info.resources.levels != 1 ||
info.resources.layers != 1 || info.samples != 1) {
return false;
}
const auto expected_pitch =
(static_cast<uint64_t>(info.extent.width) + 127u) & ~uint64_t {127u};
const auto padded_height =
(static_cast<uint64_t>(info.extent.height) + 127u) & ~uint64_t {127u};
return expected_pitch <= UINT32_MAX && info.pitch == expected_pitch &&
expected_pitch <= UINT64_MAX / padded_height / info.bytes_per_block &&
info.data.size == expected_pitch * padded_height * info.bytes_per_block;
}
[[nodiscard]] inline constexpr bool IsTiledRenderTarget(const ImageInfo& info) noexcept {
return info.tile_mode == Prospero::GpuEnumValue(Prospero::TileMode::kRenderTarget) ||
IsSupportedStandard64RenderTarget(info);
}
[[nodiscard]] inline bool DecodePackedColorClear(vk::Format format, uint32_t packed,
vk::ClearColorValue& clear) {
vk::ClearColorValue next {};
const auto unorm8 = [](uint32_t value) { return static_cast<float>(value & 0xffu) / 255.0f; };
const auto srgb8 = [](uint32_t value) {
const auto encoded = static_cast<float>(value & 0xffu) / 255.0f;
return encoded <= 0.04045f ? encoded / 12.92f : std::pow((encoded + 0.055f) / 1.055f, 2.4f);
};
switch (format) {
case vk::Format::eR32Uint: next.uint32[0] = packed; break;
case vk::Format::eR32Sint: next.int32[0] = static_cast<int32_t>(packed); break;
case vk::Format::eR8G8B8A8Srgb:
next.float32[0] = srgb8(packed);
next.float32[1] = srgb8(packed >> 8u);
next.float32[2] = srgb8(packed >> 16u);
next.float32[3] = unorm8(packed >> 24u);
break;
case vk::Format::eB8G8R8A8Srgb:
next.float32[0] = srgb8(packed >> 16u);
next.float32[1] = srgb8(packed >> 8u);
next.float32[2] = srgb8(packed);
next.float32[3] = unorm8(packed >> 24u);
break;
case vk::Format::eR8G8B8A8Unorm:
next.float32[0] = unorm8(packed);
next.float32[1] = unorm8(packed >> 8u);
next.float32[2] = unorm8(packed >> 16u);
next.float32[3] = unorm8(packed >> 24u);
break;
case vk::Format::eB8G8R8A8Unorm:
next.float32[0] = unorm8(packed >> 16u);
next.float32[1] = unorm8(packed >> 8u);
next.float32[2] = unorm8(packed);
next.float32[3] = unorm8(packed >> 24u);
break;
case vk::Format::eA2B10G10R10UnormPack32:
next.float32[0] = static_cast<float>(packed & 0x3ffu) / 1023.0f;
next.float32[1] = static_cast<float>((packed >> 10u) & 0x3ffu) / 1023.0f;
next.float32[2] = static_cast<float>((packed >> 20u) & 0x3ffu) / 1023.0f;
next.float32[3] = static_cast<float>((packed >> 30u) & 0x3u) / 3.0f;
break;
case vk::Format::eA2R10G10B10UnormPack32:
next.float32[0] = static_cast<float>((packed >> 20u) & 0x3ffu) / 1023.0f;
next.float32[1] = static_cast<float>((packed >> 10u) & 0x3ffu) / 1023.0f;
next.float32[2] = static_cast<float>(packed & 0x3ffu) / 1023.0f;
next.float32[3] = static_cast<float>((packed >> 30u) & 0x3u) / 3.0f;
break;
default: return false;
}
clear = next;
return true;
}
[[nodiscard]] inline bool DecodePackedStencilClear(uint32_t packed, uint8_t& clear) {
const auto value = static_cast<uint8_t>(packed);
if (packed != static_cast<uint32_t>(value) * 0x01010101u) {
return false;
}
clear = value;
return true;
}
[[nodiscard]] inline bool DecodePackedDepthClear(vk::Format format, uint32_t packed, float& clear) {
if (format != vk::Format::eD32Sfloat && format != vk::Format::eD32SfloatS8Uint) {
return false;
}
const auto value = std::bit_cast<float>(packed);
if (!std::isfinite(value) || value < 0.0f || value > 1.0f) {
return false;
}
clear = value;
return true;
}
[[nodiscard]] inline bool ImageRangeOverlaps(uint64_t left, uint64_t left_size, uint64_t right,
uint64_t right_size) {
if (left_size == 0 || right_size == 0 || left > UINT64_MAX - left_size ||
right > UINT64_MAX - right_size) {
EXIT("invalid image overlap range\n");
}
return left < right + right_size && right < left + left_size;
}
[[nodiscard]] inline bool ImageRangeOverlaps(GuestRange left, GuestRange right) {
return ImageRangeOverlaps(left.address, left.size, right.address, right.size);
}
[[nodiscard]] inline bool ImagePageRangesOverlap(uint64_t left, uint64_t left_size, uint64_t right,
uint64_t right_size) {
if (left_size == 0 || right_size == 0 || left > UINT64_MAX - left_size ||
right > UINT64_MAX - right_size) {
EXIT("invalid image page-overlap range\n");
}
const auto left_first = left / TRACKER_PAGE_SIZE;
const auto left_last = (left + left_size - 1) / TRACKER_PAGE_SIZE;
const auto right_first = right / TRACKER_PAGE_SIZE;
const auto right_last = (right + right_size - 1) / TRACKER_PAGE_SIZE;
return left_first <= right_last && right_first <= left_last;
}
[[nodiscard]] inline bool ImagePageRangesOverlap(GuestRange left, GuestRange right) {
return ImagePageRangesOverlap(left.address, left.size, right.address, right.size);
}
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_IMAGEINFO_H_
@@ -0,0 +1,412 @@
#include "graphics/host_gpu/renderer/image/imageView.h"
#include "common/assert.h"
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/renderer/image/image.h"
#include <mutex>
namespace Libs::Graphics {
namespace {
[[nodiscard]] bool IsComponentSwizzle(vk::ComponentSwizzle swizzle) {
switch (swizzle) {
case vk::ComponentSwizzle::eIdentity:
case vk::ComponentSwizzle::eZero:
case vk::ComponentSwizzle::eOne:
case vk::ComponentSwizzle::eR:
case vk::ComponentSwizzle::eG:
case vk::ComponentSwizzle::eB:
case vk::ComponentSwizzle::eA: return true;
default: return false;
}
}
[[nodiscard]] bool IsCompatibleViewFormat(vk::Format image_format, vk::Format view_format) {
return ImageViewOps::FormatsCompatible(image_format, view_format);
}
[[nodiscard]] bool IsStencilViewFormat(vk::Format format) {
switch (format) {
case vk::Format::eS8Uint:
case vk::Format::eR8Uint:
case vk::Format::eR8Unorm: return true;
default: return false;
}
}
[[nodiscard]] bool IsDepthViewFormat(vk::Format format) {
switch (format) {
case vk::Format::eD16Unorm:
case vk::Format::eR16Unorm:
case vk::Format::eD32Sfloat:
case vk::Format::eR32Sfloat:
case vk::Format::eR32Uint: return true;
default: return false;
}
}
[[nodiscard]] bool IsValidViewType(const VulkanImage& image, const ImageViewInfo& info) {
switch (image.image_type) {
case vk::ImageType::e1D:
if (info.type != vk::ImageViewType::e1D && info.type != vk::ImageViewType::e1DArray) {
return false;
}
return info.type != vk::ImageViewType::e1D || info.layer_count == 1;
case vk::ImageType::e2D:
switch (info.type) {
case vk::ImageViewType::e2D: return info.layer_count == 1;
case vk::ImageViewType::e2DArray: return true;
case vk::ImageViewType::eCube:
return static_cast<bool>(image.flags &
vk::ImageCreateFlagBits::eCubeCompatible) &&
info.base_layer % 6 == 0 && info.layer_count == 6;
case vk::ImageViewType::eCubeArray:
return static_cast<bool>(image.flags &
vk::ImageCreateFlagBits::eCubeCompatible) &&
info.base_layer % 6 == 0 && info.layer_count % 6 == 0;
default: return false;
}
case vk::ImageType::e3D:
switch (info.type) {
case vk::ImageViewType::e3D: return info.base_layer == 0 && info.layer_count == 1;
case vk::ImageViewType::e2D:
return static_cast<bool>(image.flags &
vk::ImageCreateFlagBits::e2DArrayCompatible) &&
info.level_count == 1 && info.layer_count == 1;
case vk::ImageViewType::e2DArray:
return static_cast<bool>(image.flags &
vk::ImageCreateFlagBits::e2DArrayCompatible) &&
info.level_count == 1;
default: return false;
}
default: return false;
}
}
[[nodiscard]] bool IsValidAspect(const VulkanImage& image, vk::ImageAspectFlags aspect) {
const auto depth_format = DepthAspectTransferFormat(image.format);
if (depth_format == vk::Format::eUndefined) {
return aspect == vk::ImageAspectFlagBits::eColor;
}
const auto supported = ImageViewOps::DepthAspectMask(image.format);
return static_cast<bool>(aspect) && !(aspect & ~supported);
}
} // namespace
namespace ImageViewOps {
namespace {
enum CompatibilityClass : uint32_t {
None = 0,
Bit8 = 1u << 0,
Bit16 = 1u << 1,
Bit24 = 1u << 2,
Bit32 = 1u << 3,
Bit48 = 1u << 4,
Bit64 = 1u << 5,
Bit96 = 1u << 6,
Bit128 = 1u << 7,
Bit192 = 1u << 8,
Bit256 = 1u << 9,
Bc1Rgb = 1u << 10,
Bc1Rgba = 1u << 11,
Bc2 = 1u << 12,
Bc3 = 1u << 13,
Bc4 = 1u << 14,
Bc5 = 1u << 15,
Bc6h = 1u << 16,
Bc7 = 1u << 17,
D16 = 1u << 18,
D16S8 = 1u << 19,
D24 = 1u << 20,
D24S8 = 1u << 21,
D32 = 1u << 22,
D32S8 = 1u << 23,
S8 = 1u << 24,
};
[[nodiscard]] uint32_t FormatClass(vk::Format format) noexcept {
switch (format) {
case vk::Format::eR4G4UnormPack8:
case vk::Format::eR8Sint:
case vk::Format::eR8Snorm:
case vk::Format::eR8Srgb:
case vk::Format::eR8Sscaled:
case vk::Format::eR8Uint:
case vk::Format::eR8Unorm:
case vk::Format::eR8Uscaled: return Bit8;
case vk::Format::eA1R5G5B5UnormPack16:
case vk::Format::eA4B4G4R4UnormPack16:
case vk::Format::eA4R4G4B4UnormPack16:
case vk::Format::eB4G4R4A4UnormPack16:
case vk::Format::eB5G5R5A1UnormPack16:
case vk::Format::eB5G6R5UnormPack16:
case vk::Format::eR10X6UnormPack16:
case vk::Format::eR12X4UnormPack16:
case vk::Format::eR16Sfloat:
case vk::Format::eR16Sint:
case vk::Format::eR16Snorm:
case vk::Format::eR16Sscaled:
case vk::Format::eR16Uint:
case vk::Format::eR16Unorm:
case vk::Format::eR16Uscaled:
case vk::Format::eR4G4B4A4UnormPack16:
case vk::Format::eR5G5B5A1UnormPack16:
case vk::Format::eR5G6B5UnormPack16:
case vk::Format::eR8G8Sint:
case vk::Format::eR8G8Snorm:
case vk::Format::eR8G8Srgb:
case vk::Format::eR8G8Sscaled:
case vk::Format::eR8G8Uint:
case vk::Format::eR8G8Unorm:
case vk::Format::eR8G8Uscaled: return Bit16;
case vk::Format::eB8G8R8Sint:
case vk::Format::eB8G8R8Snorm:
case vk::Format::eB8G8R8Srgb:
case vk::Format::eB8G8R8Sscaled:
case vk::Format::eB8G8R8Uint:
case vk::Format::eB8G8R8Unorm:
case vk::Format::eB8G8R8Uscaled:
case vk::Format::eR8G8B8Sint:
case vk::Format::eR8G8B8Snorm:
case vk::Format::eR8G8B8Srgb:
case vk::Format::eR8G8B8Sscaled:
case vk::Format::eR8G8B8Uint:
case vk::Format::eR8G8B8Unorm:
case vk::Format::eR8G8B8Uscaled: return Bit24;
case vk::Format::eA2B10G10R10SintPack32:
case vk::Format::eA2B10G10R10SnormPack32:
case vk::Format::eA2B10G10R10SscaledPack32:
case vk::Format::eA2B10G10R10UintPack32:
case vk::Format::eA2B10G10R10UnormPack32:
case vk::Format::eA2B10G10R10UscaledPack32:
case vk::Format::eA2R10G10B10SintPack32:
case vk::Format::eA2R10G10B10SnormPack32:
case vk::Format::eA2R10G10B10SscaledPack32:
case vk::Format::eA2R10G10B10UintPack32:
case vk::Format::eA2R10G10B10UnormPack32:
case vk::Format::eA2R10G10B10UscaledPack32:
case vk::Format::eA8B8G8R8SintPack32:
case vk::Format::eA8B8G8R8SnormPack32:
case vk::Format::eA8B8G8R8SrgbPack32:
case vk::Format::eA8B8G8R8SscaledPack32:
case vk::Format::eA8B8G8R8UintPack32:
case vk::Format::eA8B8G8R8UnormPack32:
case vk::Format::eA8B8G8R8UscaledPack32:
case vk::Format::eB10G11R11UfloatPack32:
case vk::Format::eB8G8R8A8Sint:
case vk::Format::eB8G8R8A8Snorm:
case vk::Format::eB8G8R8A8Srgb:
case vk::Format::eB8G8R8A8Sscaled:
case vk::Format::eB8G8R8A8Uint:
case vk::Format::eB8G8R8A8Unorm:
case vk::Format::eB8G8R8A8Uscaled:
case vk::Format::eE5B9G9R9UfloatPack32:
case vk::Format::eR10X6G10X6Unorm2Pack16:
case vk::Format::eR12X4G12X4Unorm2Pack16:
case vk::Format::eR16G16Sfloat:
case vk::Format::eR16G16Sint:
case vk::Format::eR16G16Snorm:
case vk::Format::eR16G16Sscaled:
case vk::Format::eR16G16Uint:
case vk::Format::eR16G16Unorm:
case vk::Format::eR16G16Uscaled:
case vk::Format::eR32Sfloat:
case vk::Format::eR32Sint:
case vk::Format::eR32Uint:
case vk::Format::eR8G8B8A8Sint:
case vk::Format::eR8G8B8A8Snorm:
case vk::Format::eR8G8B8A8Srgb:
case vk::Format::eR8G8B8A8Sscaled:
case vk::Format::eR8G8B8A8Uint:
case vk::Format::eR8G8B8A8Unorm:
case vk::Format::eR8G8B8A8Uscaled: return Bit32;
case vk::Format::eR16G16B16Sfloat:
case vk::Format::eR16G16B16Sint:
case vk::Format::eR16G16B16Snorm:
case vk::Format::eR16G16B16Sscaled:
case vk::Format::eR16G16B16Uint:
case vk::Format::eR16G16B16Unorm:
case vk::Format::eR16G16B16Uscaled: return Bit48;
case vk::Format::eR16G16B16A16Sfloat:
case vk::Format::eR16G16B16A16Sint:
case vk::Format::eR16G16B16A16Snorm:
case vk::Format::eR16G16B16A16Sscaled:
case vk::Format::eR16G16B16A16Uint:
case vk::Format::eR16G16B16A16Unorm:
case vk::Format::eR16G16B16A16Uscaled:
case vk::Format::eR32G32Sfloat:
case vk::Format::eR32G32Sint:
case vk::Format::eR32G32Uint:
case vk::Format::eR64Sfloat:
case vk::Format::eR64Sint:
case vk::Format::eR64Uint: return Bit64;
case vk::Format::eR32G32B32Sfloat:
case vk::Format::eR32G32B32Sint:
case vk::Format::eR32G32B32Uint: return Bit96;
case vk::Format::eR32G32B32A32Sfloat:
case vk::Format::eR32G32B32A32Sint:
case vk::Format::eR32G32B32A32Uint:
case vk::Format::eR64G64Sfloat:
case vk::Format::eR64G64Sint:
case vk::Format::eR64G64Uint: return Bit128;
case vk::Format::eR64G64B64Sfloat:
case vk::Format::eR64G64B64Sint:
case vk::Format::eR64G64B64Uint: return Bit192;
case vk::Format::eR64G64B64A64Sfloat:
case vk::Format::eR64G64B64A64Sint:
case vk::Format::eR64G64B64A64Uint: return Bit256;
case vk::Format::eBc1RgbSrgbBlock:
case vk::Format::eBc1RgbUnormBlock: return Bc1Rgb | Bit64;
case vk::Format::eBc1RgbaSrgbBlock:
case vk::Format::eBc1RgbaUnormBlock: return Bc1Rgba | Bit64;
case vk::Format::eBc2SrgbBlock:
case vk::Format::eBc2UnormBlock: return Bc2 | Bit128;
case vk::Format::eBc3SrgbBlock:
case vk::Format::eBc3UnormBlock: return Bc3 | Bit128;
case vk::Format::eBc4SnormBlock:
case vk::Format::eBc4UnormBlock: return Bc4 | Bit64;
case vk::Format::eBc5SnormBlock:
case vk::Format::eBc5UnormBlock: return Bc5 | Bit128;
case vk::Format::eBc6HSfloatBlock:
case vk::Format::eBc6HUfloatBlock: return Bc6h | Bit128;
case vk::Format::eBc7SrgbBlock:
case vk::Format::eBc7UnormBlock: return Bc7 | Bit128;
case vk::Format::eD16Unorm: return D16;
case vk::Format::eD16UnormS8Uint: return D16S8;
case vk::Format::eX8D24UnormPack32: return D24;
case vk::Format::eD24UnormS8Uint: return D24S8;
case vk::Format::eD32Sfloat: return D32;
case vk::Format::eD32SfloatS8Uint: return D32S8;
case vk::Format::eS8Uint: return S8;
default: return None;
}
}
} // namespace
vk::ImageAspectFlags DepthAspectMask(vk::Format format) {
switch (format) {
case vk::Format::eD16Unorm:
case vk::Format::eD32Sfloat: return vk::ImageAspectFlagBits::eDepth;
case vk::Format::eD16UnormS8Uint:
case vk::Format::eD24UnormS8Uint:
case vk::Format::eD32SfloatS8Uint:
return vk::ImageAspectFlagBits::eDepth | vk::ImageAspectFlagBits::eStencil;
default: EXIT("unsupported depth/stencil image format: %d\n", static_cast<int>(format));
}
}
bool FormatsCompatible(vk::Format base, vk::Format view) noexcept {
if (base == view) {
return true;
}
const auto base_class = FormatClass(base);
const auto view_class = FormatClass(view);
return view_class != None && (base_class & view_class) == view_class;
}
} // namespace ImageViewOps
vk::ImageView Image::FindView(const ImageViewInfo& view_info) {
const auto& image = backing;
auto normalized = view_info;
const bool is_storage = static_cast<bool>(normalized.usage & vk::ImageUsageFlagBits::eStorage);
normalized.aspect = FullAspectMask(image.format);
if (normalized.aspect & vk::ImageAspectFlagBits::eDepth &&
IsDepthViewFormat(normalized.format)) {
normalized.format = image.format;
normalized.aspect = vk::ImageAspectFlagBits::eDepth;
}
if (normalized.aspect & vk::ImageAspectFlagBits::eStencil &&
IsStencilViewFormat(normalized.format)) {
normalized.format = image.format;
normalized.aspect = vk::ImageAspectFlagBits::eStencil;
}
normalized.usage = is_storage ? vk::ImageUsageFlagBits::eStorage : vk::ImageUsageFlags {};
const bool format_compatible = normalized.format != vk::Format::eUndefined &&
IsCompatibleViewFormat(image.format, normalized.format);
const bool slice_view =
image.image_type == vk::ImageType::e3D && (normalized.type == vk::ImageViewType::e2D ||
normalized.type == vk::ImageViewType::e2DArray);
const bool levels_valid = normalized.level_count != 0 &&
normalized.base_level < image.mip_levels &&
normalized.level_count <= image.mip_levels - normalized.base_level;
const auto view_layers = slice_view && levels_valid
? std::max(image.extent.depth >> normalized.base_level, 1u)
: image.layers;
const bool ranges_valid = levels_valid && normalized.layer_count != 0 &&
normalized.base_layer < view_layers &&
normalized.layer_count <= view_layers - normalized.base_layer;
const bool mapping_valid =
IsComponentSwizzle(normalized.mapping.r) && IsComponentSwizzle(normalized.mapping.g) &&
IsComponentSwizzle(normalized.mapping.b) && IsComponentSwizzle(normalized.mapping.a);
if (image.image == nullptr || !format_compatible || !ranges_valid || !mapping_valid ||
!IsValidViewType(image, normalized) || !IsValidAspect(image, normalized.aspect)) {
EXIT("invalid image view: image_format=%d view_format=%d type=%d aspect=0x%x "
"mip=%u+%u layer=%u+%u usage=0x%x image_levels=%u image_layers=%u\n",
static_cast<int>(image.format), static_cast<int>(normalized.format),
static_cast<int>(normalized.type),
static_cast<vk::ImageAspectFlags::MaskType>(normalized.aspect), normalized.base_level,
normalized.level_count, normalized.base_layer, normalized.layer_count,
static_cast<vk::ImageUsageFlags::MaskType>(normalized.usage), image.mip_levels,
image.layers);
}
std::lock_guard lock(views.mutex);
for (const auto& cached: views.views) {
if (cached.info == normalized) {
return cached.view;
}
}
vk::ImageViewUsageCreateInfo usage {};
usage.sType = vk::StructureType::eImageViewUsageCreateInfo;
usage.usage = image.usage;
if (!is_storage) {
usage.usage &= ~vk::ImageUsageFlagBits::eStorage;
}
vk::ImageViewCreateInfo create {};
create.sType = vk::StructureType::eImageViewCreateInfo;
create.pNext = &usage;
create.image = image.image;
create.viewType = normalized.type;
create.format = normalized.format;
create.components = normalized.mapping;
create.subresourceRange.aspectMask = normalized.aspect;
create.subresourceRange.baseMipLevel = normalized.base_level;
create.subresourceRange.levelCount = normalized.level_count;
create.subresourceRange.baseArrayLayer = normalized.base_layer;
create.subresourceRange.layerCount = normalized.layer_count;
vk::ImageView view = nullptr;
const auto result = m_graphics->device.createImageView(&create, nullptr, &view);
if (result != vk::Result::eSuccess || view == nullptr) {
EXIT("failed to create image view: result=%d image_format=%d view_format=%d type=%d "
"aspect=0x%x mip=%u+%u layer=%u+%u usage=0x%x\n",
static_cast<int>(result), static_cast<int>(image.format),
static_cast<int>(view_info.format), static_cast<int>(view_info.type),
static_cast<vk::ImageAspectFlags::MaskType>(view_info.aspect), view_info.base_level,
view_info.level_count, view_info.base_layer, view_info.layer_count,
static_cast<vk::ImageUsageFlags::MaskType>(view_info.usage));
}
views.views.push_back({normalized, view});
return view;
}
} // namespace Libs::Graphics
@@ -0,0 +1,143 @@
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_IMAGEVIEW_H_
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_IMAGEVIEW_H_
#include "common/assert.h"
#include "graphics/host_gpu/renderer/image/imageInfo.h"
#include "graphics/shader/recompiler/ir/ShaderIR.h"
#include "graphics/shader/shader.h"
namespace Libs::Graphics {
namespace ImageViewOps {
[[nodiscard]] vk::ImageAspectFlags DepthAspectMask(vk::Format format);
[[nodiscard]] bool FormatsCompatible(vk::Format base, vk::Format view) noexcept;
} // namespace ImageViewOps
[[nodiscard]] inline bool IsValidImageSwizzle(uint32_t swizzle) noexcept {
if ((swizzle & ~0xfffu) != 0) {
return false;
}
for (uint32_t channel = 0; channel < 4; channel++) {
switch (GetDstSel(swizzle, channel)) {
case 0:
case 1:
case 4:
case 5:
case 6:
case 7: break;
default: return false;
}
}
return true;
}
[[noreturn]] inline void UnsupportedColorView(const char* usage, vk::Format image_format,
vk::Format view_format, uint32_t swizzle) noexcept {
EXIT("unsupported %s color image view: image_format=%d view_format=%d swizzle=0x%03x\n", usage,
static_cast<int>(image_format), static_cast<int>(view_format), swizzle);
}
[[nodiscard]] inline vk::Format SrgbStorageViewFormat(vk::Format image_format) noexcept {
switch (image_format) {
case vk::Format::eR8G8B8A8Srgb:
case vk::Format::eB8G8R8A8Srgb: return vk::Format::eR8G8B8A8Unorm;
default: return vk::Format::eUndefined;
}
}
[[nodiscard]] inline bool IsSupportedSampledColorView(vk::Format image_format,
vk::Format view_format,
uint32_t swizzle) noexcept {
return IsValidImageSwizzle(swizzle) &&
ImageViewOps::FormatsCompatible(image_format, view_format);
}
[[nodiscard]] inline uint32_t
SelectSampledColorView(vk::Format image_format, vk::Format view_format, uint32_t swizzle) noexcept {
if (IsSupportedSampledColorView(image_format, view_format, swizzle)) {
return swizzle;
}
UnsupportedColorView("sampled", image_format, view_format, swizzle);
}
[[nodiscard]] inline bool IsSupportedSampledDepthView(vk::Format image_format,
vk::Format view_format,
uint32_t swizzle) noexcept {
if (!IsSupportedSampledDepthFormat(image_format, view_format)) {
return false;
}
switch (swizzle) {
case DstSel(4, 4, 4, 4):
case DstSel(4, 0, 0, 0):
case DstSel(4, 0, 0, 1): return true;
default: return false;
}
}
[[nodiscard]] inline uint32_t
SelectSampledDepthView(vk::Format image_format, vk::Format view_format, uint32_t swizzle) noexcept {
if (IsSupportedSampledDepthView(image_format, view_format, swizzle)) {
return swizzle;
}
EXIT("unsupported sampled depth image view: image_format=%d view_format=%d swizzle=0x%03x\n",
static_cast<int>(image_format), static_cast<int>(view_format), swizzle);
}
[[nodiscard]] inline bool
IsSupportedSampledDepthResource(const ShaderRecompiler::IR::ImageResource& resource) noexcept {
return resource.kind == ShaderRecompiler::IR::ResourceKind::Image &&
(resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2D ||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2DArray ||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2DMsaa ||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2DMsaaArray) &&
resource.mip_mode == ShaderRecompiler::IR::ImageMipMode::None && resource.read &&
!resource.written && !resource.atomic;
}
[[nodiscard]] inline bool
IsSupportedSampledDepthUintResource(const ShaderRecompiler::IR::ImageResource& resource) noexcept {
return resource.kind == ShaderRecompiler::IR::ResourceKind::ImageUint &&
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2D &&
resource.mip_mode == ShaderRecompiler::IR::ImageMipMode::None && resource.read &&
!resource.written && !resource.atomic && !resource.depth_compare;
}
inline void ValidateStorageColorView(vk::Format image_format, vk::Format view_format,
uint32_t swizzle) noexcept {
if (!ImageViewOps::FormatsCompatible(image_format, view_format) ||
!IsValidImageSwizzle(swizzle)) {
UnsupportedColorView("storage", image_format, view_format, swizzle);
}
}
[[nodiscard]] inline bool
IsSupportedStorageImageResource(const ShaderRecompiler::IR::ImageResource& resource) noexcept {
return (resource.kind == ShaderRecompiler::IR::ResourceKind::StorageImage ||
resource.kind == ShaderRecompiler::IR::ResourceKind::StorageImageUint) &&
(resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim1D ||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim1DArray ||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2D ||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim3D ||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2DArray) &&
resource.mip_mode == ShaderRecompiler::IR::ImageMipMode::None && resource.written &&
(!resource.atomic ||
(resource.kind == ShaderRecompiler::IR::ResourceKind::StorageImageUint &&
resource.read)) &&
!resource.depth_compare;
}
inline void
ValidateStorageImageResource(const ShaderRecompiler::IR::ImageResource& resource) noexcept {
if (!IsSupportedStorageImageResource(resource)) {
EXIT("unsupported storage color image resource: kind=%u dimension=%u mip=%u "
"read=%d written=%d atomic=%d depth_compare=%d\n",
static_cast<uint32_t>(resource.kind), static_cast<uint32_t>(resource.dimension),
static_cast<uint32_t>(resource.mip_mode), resource.read, resource.written,
resource.atomic, resource.depth_compare);
}
}
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_IMAGEVIEW_H_
@@ -0,0 +1,631 @@
#include "graphics/host_gpu/renderer/image/textureCommon.h"
#include "common/assert.h"
#include "graphics/guest_gpu/gpu_defs.h"
#include "graphics/guest_gpu/gpu_format.h"
#include "graphics/host_gpu/renderer/image/tiler.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include <algorithm>
#include <bit>
#include <cinttypes>
#include <cstring>
namespace Libs::Graphics {
namespace {
struct RenderTargetFormatMapping {
Prospero::ChannelLayout layout;
Prospero::ChannelType type;
Prospero::ChannelOrder order;
RenderTargetFormatInfo info;
};
constexpr RenderTargetFormatMapping kRenderTargetFormats[] = {
{Prospero::ChannelLayout::k8_8,
Prospero::ChannelType::kUNorm,
Prospero::ChannelOrder::kStandard,
{vk::Format::eR8G8Unorm, 2}},
{Prospero::ChannelLayout::k8_8_8_8,
Prospero::ChannelType::kUNorm,
Prospero::ChannelOrder::kStandard,
{vk::Format::eR8G8B8A8Unorm, 4}},
{Prospero::ChannelLayout::k8_8_8_8,
Prospero::ChannelType::kSNorm,
Prospero::ChannelOrder::kStandard,
{vk::Format::eR8G8B8A8Snorm, 4}},
{Prospero::ChannelLayout::k8_8_8_8,
Prospero::ChannelType::kSrgb,
Prospero::ChannelOrder::kStandard,
{vk::Format::eR8G8B8A8Srgb, 4}},
{Prospero::ChannelLayout::k8_8_8_8,
Prospero::ChannelType::kUNorm,
Prospero::ChannelOrder::kAlt,
{vk::Format::eB8G8R8A8Unorm, 4}},
{Prospero::ChannelLayout::k8_8_8_8,
Prospero::ChannelType::kSNorm,
Prospero::ChannelOrder::kAlt,
{vk::Format::eB8G8R8A8Snorm, 4}},
{Prospero::ChannelLayout::k8_8_8_8,
Prospero::ChannelType::kSrgb,
Prospero::ChannelOrder::kAlt,
{vk::Format::eB8G8R8A8Srgb, 4}},
{Prospero::ChannelLayout::k5_5_5_1,
Prospero::ChannelType::kUNorm,
Prospero::ChannelOrder::kStandard,
{vk::Format::eR5G5B5A1UnormPack16, 2}},
{Prospero::ChannelLayout::k4_4_4_4,
Prospero::ChannelType::kUNorm,
Prospero::ChannelOrder::kReversed,
{vk::Format::eB4G4R4A4UnormPack16, 2}},
{Prospero::ChannelLayout::k10_10_10_2,
Prospero::ChannelType::kUNorm,
Prospero::ChannelOrder::kStandard,
{vk::Format::eA2B10G10R10UnormPack32, 4}},
{Prospero::ChannelLayout::k10_10_10_2,
Prospero::ChannelType::kUNorm,
Prospero::ChannelOrder::kAlt,
{vk::Format::eA2R10G10B10UnormPack32, 4}},
{Prospero::ChannelLayout::k11_11_10,
Prospero::ChannelType::kFloat,
Prospero::ChannelOrder::kStandard,
{vk::Format::eB10G11R11UfloatPack32, 4}},
{Prospero::ChannelLayout::k5_6_5,
Prospero::ChannelType::kUNorm,
Prospero::ChannelOrder::kStandard,
{vk::Format::eB5G6R5UnormPack16, 2}},
{Prospero::ChannelLayout::k16,
Prospero::ChannelType::kUNorm,
Prospero::ChannelOrder::kStandard,
{vk::Format::eR16Unorm, 2}},
{Prospero::ChannelLayout::k16,
Prospero::ChannelType::kUInt,
Prospero::ChannelOrder::kStandard,
{vk::Format::eR16Uint, 2}},
{Prospero::ChannelLayout::k16,
Prospero::ChannelType::kFloat,
Prospero::ChannelOrder::kStandard,
{vk::Format::eR16Sfloat, 2}},
{Prospero::ChannelLayout::k16_16,
Prospero::ChannelType::kUNorm,
Prospero::ChannelOrder::kStandard,
{vk::Format::eR16G16Unorm, 4}},
{Prospero::ChannelLayout::k16_16,
Prospero::ChannelType::kSNorm,
Prospero::ChannelOrder::kStandard,
{vk::Format::eR16G16Snorm, 4}},
{Prospero::ChannelLayout::k16_16,
Prospero::ChannelType::kUInt,
Prospero::ChannelOrder::kStandard,
{vk::Format::eR16G16Uint, 4}},
{Prospero::ChannelLayout::k16_16,
Prospero::ChannelType::kFloat,
Prospero::ChannelOrder::kStandard,
{vk::Format::eR16G16Sfloat, 4}},
{Prospero::ChannelLayout::k16_16_16_16,
Prospero::ChannelType::kUNorm,
Prospero::ChannelOrder::kStandard,
{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::ChannelType::kFloat,
Prospero::ChannelOrder::kStandard,
{vk::Format::eR16G16B16A16Sfloat, 8}},
{Prospero::ChannelLayout::k16_16_16_16,
Prospero::ChannelType::kFloat,
Prospero::ChannelOrder::kAlt,
{vk::Format::eR16G16B16A16Sfloat, 8, Prospero::ColorMappingBgra}},
{Prospero::ChannelLayout::k16_16_16_16,
Prospero::ChannelType::kFloat,
Prospero::ChannelOrder::kReversed,
{vk::Format::eR16G16B16A16Sfloat, 8, Prospero::ColorMappingAbgr}},
{Prospero::ChannelLayout::k32,
Prospero::ChannelType::kFloat,
Prospero::ChannelOrder::kStandard,
{vk::Format::eR32Sfloat, 4}},
{Prospero::ChannelLayout::k32_32,
Prospero::ChannelType::kUInt,
Prospero::ChannelOrder::kStandard,
{vk::Format::eR32G32Uint, 8}},
{Prospero::ChannelLayout::k32_32,
Prospero::ChannelType::kFloat,
Prospero::ChannelOrder::kStandard,
{vk::Format::eR32G32Sfloat, 8}},
{Prospero::ChannelLayout::k32_32_32_32,
Prospero::ChannelType::kFloat,
Prospero::ChannelOrder::kStandard,
{vk::Format::eR32G32B32A32Sfloat, 16}},
};
} // namespace
// TODO: cleanup!
RenderTargetFormatInfo TextureGetRenderTargetFormat(uint32_t raw_layout, uint32_t raw_type,
uint32_t raw_order) {
const auto layout = static_cast<Prospero::ChannelLayout>(raw_layout);
const auto type = static_cast<Prospero::ChannelType>(raw_type);
const auto order = static_cast<Prospero::ChannelOrder>(raw_order);
if (layout == Prospero::ChannelLayout::k8 && type == Prospero::ChannelType::kUNorm &&
raw_order <= Prospero::GpuEnumValue(Prospero::ChannelOrder::kAltReversed)) {
return {vk::Format::eR8Unorm, 1};
}
for (const auto& mapping: kRenderTargetFormats) {
if (mapping.layout == layout && mapping.type == type && mapping.order == order) {
return mapping.info;
}
}
EXIT("unsupported render-target format combination: layout=%u type=%u order=%u\n", raw_layout,
raw_type, raw_order);
}
namespace {
static uint64_t GetLevelSrcOffset(const TileSizeOffset& level_size) {
return (level_size.src_size != 0 ? level_size.src_offset : level_size.offset);
}
static uint64_t GetLevelSrcSize(const TileSizeOffset& level_size) {
return (level_size.src_size != 0 ? level_size.src_size : level_size.size);
}
static uint32_t GetTextureLevelDepth(uint32_t depth, uint32_t level, bool volume_texture) {
return volume_texture ? std::max(depth >> level, 1u) : depth;
}
static size_t GetTextureRegionCount(uint32_t depth, uint64_t levels, bool volume_texture) {
size_t count = 0;
for (uint32_t level = 0; level < levels; level++) {
count += GetTextureLevelDepth(depth, level, volume_texture);
}
return count;
}
uint64_t TextureUploadSliceSourceOffset(const TextureUploadLayout& layout, uint32_t level,
uint32_t slice) {
if (level >= 16 || layout.level_sizes[level].size == 0) {
EXIT("invalid texture upload slice source, level=%u slice=%u\n", level, slice);
}
const auto level_offset = GetLevelSrcOffset(layout.level_sizes[level]);
const auto slice_stride =
layout.source_slice_stride != 0 ? layout.source_slice_stride : layout.slice_stride;
if (slice_stride != 0 && slice > (UINT64_MAX - level_offset) / slice_stride) {
EXIT("texture upload slice source offset overflow, level=%u slice=%u\n", level, slice);
}
return level_offset + static_cast<uint64_t>(slice) * slice_stride;
}
vk::ComponentSwizzle TextureGetComponentSwizzle(uint8_t s) {
switch (static_cast<Prospero::CompSwizzle>(s)) {
case Prospero::CompSwizzle::kZero: return vk::ComponentSwizzle::eZero;
case Prospero::CompSwizzle::kOne: return vk::ComponentSwizzle::eOne;
case Prospero::CompSwizzle::kRed: return vk::ComponentSwizzle::eR;
case Prospero::CompSwizzle::kGreen: return vk::ComponentSwizzle::eG;
case Prospero::CompSwizzle::kBlue: return vk::ComponentSwizzle::eB;
case Prospero::CompSwizzle::kAlpha: return vk::ComponentSwizzle::eA;
default: EXIT("unknown swizzle: %d\n", static_cast<int>(s));
}
return vk::ComponentSwizzle::eIdentity;
}
static uint32_t TextureGetDstSel(uint32_t swizzle, uint32_t channel) {
return (swizzle >> (channel * 3u)) & 0x7u;
}
} // namespace
vk::ComponentMapping TextureGetComponentMapping(uint32_t swizzle) {
vk::ComponentMapping components {};
components.r = TextureGetComponentSwizzle(static_cast<uint8_t>(TextureGetDstSel(swizzle, 0)));
components.g = TextureGetComponentSwizzle(static_cast<uint8_t>(TextureGetDstSel(swizzle, 1)));
components.b = TextureGetComponentSwizzle(static_cast<uint8_t>(TextureGetDstSel(swizzle, 2)));
components.a = TextureGetComponentSwizzle(static_cast<uint8_t>(TextureGetDstSel(swizzle, 3)));
return components;
}
vk::Format TextureGetFormat(uint32_t fmt) {
const auto vk_format = VulkanFormat(fmt);
if (vk_format != vk::Format::eUndefined) {
return vk_format;
}
EXIT("unknown format: fmt = %u\n", fmt);
return vk::Format::eUndefined;
}
namespace {
uint64_t CalcTextureSliceStride(const TileSizeOffset* level_sizes, uint64_t levels,
uint64_t total_size, uint32_t depth) {
uint64_t stride = 0;
for (uint32_t i = 0; i < levels; i++) {
stride =
std::max(stride, static_cast<uint64_t>(level_sizes[i].offset) + level_sizes[i].size);
}
if (depth > 1 && total_size != 0 && total_size % depth == 0) {
const auto guest_stride = total_size / depth;
if (guest_stride >= stride) {
stride = guest_stride;
}
}
return stride;
}
uint64_t CalcLinearUploadLevelSize(uint32_t fmt, uint32_t pitch, uint32_t height) {
if (const uint32_t bytes_per_element = Prospero::NumBytesPerElement(fmt);
bytes_per_element != 0) {
return static_cast<uint64_t>(pitch) * height * bytes_per_element;
}
if (const uint32_t bytes_per_block = Prospero::BlockCompressedBytesPerBlock(fmt);
bytes_per_block != 0) {
const uint32_t blocks_w = std::max((pitch + 3u) / 4u, 1u);
const uint32_t blocks_h = std::max((height + 3u) / 4u, 1u);
return static_cast<uint64_t>(blocks_w) * blocks_h * bytes_per_block;
}
return 0;
}
uint64_t SetLinearUploadLevels(TileSizeOffset* level_sizes, uint32_t fmt, uint64_t height,
uint64_t levels, uint32_t base_pitch) {
uint64_t offset = 0;
auto pitch = base_pitch;
auto h = static_cast<uint32_t>(height);
for (uint32_t i = 0; i < levels; i++) {
const auto size = CalcLinearUploadLevelSize(fmt, pitch, h);
EXIT_NOT_IMPLEMENTED(size > 0xffffffffull);
EXIT_NOT_IMPLEMENTED(offset > 0xffffffffull);
level_sizes[i].size = static_cast<uint32_t>(size);
level_sizes[i].offset = static_cast<uint32_t>(offset);
level_sizes[i].src_size = 0;
level_sizes[i].src_offset = 0;
level_sizes[i].x = 0;
level_sizes[i].y = 0;
offset += size;
if (pitch > 1) {
pitch /= 2;
}
if (h > 1) {
h /= 2;
}
}
return offset;
}
} // namespace
TextureUploadLayout TextureCalcUploadLayout(uint32_t fmt, uint64_t width, uint64_t height,
uint64_t levels, uint32_t depth, uint64_t pitch,
uint64_t tile, uint64_t upload_size,
bool allow_depth_tile, bool volume_texture,
const char* owner) {
TextureUploadLayout layout {};
layout.tile = static_cast<uint32_t>(tile);
layout.pitch = static_cast<uint32_t>(pitch);
layout.texel_block = Prospero::BlockCompressedBytesPerBlock(fmt) != 0 ? 4u : 1u;
layout.volume_texture = volume_texture;
if (fmt != 0) {
if (layout.tile != 0) {
const auto tile_mode = static_cast<Prospero::TileMode>(layout.tile);
switch (tile_mode) {
case Prospero::TileMode::kStandard256B:
if (TileIsStandard256BTextureSupported(fmt)) {
layout.tile_family = TileBlockFamily::Standard256B;
}
break;
case Prospero::TileMode::kStandard4KB:
if (TileIsStandard4KBTextureSupported(fmt)) {
layout.tile_family = TileBlockFamily::Standard4KB;
}
break;
case Prospero::TileMode::kStandard64KB:
if (TileIsStandard64KBTextureSupported(fmt)) {
layout.tile_family = TileBlockFamily::Standard64KB;
}
break;
case Prospero::TileMode::kPrt:
if (TileIsStandard64KBTextureSupported(fmt)) {
layout.tile_family = TileBlockFamily::Prt64KB;
}
break;
case Prospero::TileMode::kRenderTarget:
if (Prospero::RenderTargetBytesPerElement(fmt) != 0) {
layout.tile_family = TileBlockFamily::RenderTarget64KB;
}
break;
case Prospero::TileMode::kDepth:
if (allow_depth_tile && Prospero::RenderTargetBytesPerElement(fmt) != 0) {
layout.tile_family = TileBlockFamily::Depth64KB;
}
break;
default: break;
}
if (layout.tile_family == TileBlockFamily::Count) {
EXIT("%s: unsupported typed tiled upload: fmt=%u tile=%u "
"size=%" PRIu64 " extent=%" PRIu64 "x%" PRIu64 " pitch=%" PRIu64
" levels=%" PRIu64 "\n",
owner, static_cast<uint32_t>(fmt), layout.tile, upload_size, width, height,
pitch, levels);
}
}
layout.pitch = TileGetTexturePitch(fmt, width, levels, layout.tile);
TileGetTextureSize(fmt, width, height, layout.pitch, levels, layout.tile, nullptr,
layout.level_sizes, layout.padded_sizes);
if (static_cast<Prospero::TileMode>(layout.tile) != Prospero::TileMode::kLinear) {
if (layout.volume_texture) {
layout.slice_stride = SetLinearUploadLevels(layout.level_sizes, fmt, height, levels,
static_cast<uint32_t>(width));
} else {
TileSizeOffset tiled_levels[16] {};
std::copy_n(layout.level_sizes, levels, tiled_levels);
layout.source_slice_stride =
CalcTextureSliceStride(tiled_levels, levels, upload_size, depth);
SetLinearUploadLevels(layout.level_sizes, fmt, height, levels, layout.pitch);
for (uint32_t i = 0; i < levels; ++i) {
if (tiled_levels[i].src_size > tiled_levels[i].size) {
layout.first_tail_level = std::min(layout.first_tail_level, i);
}
layout.level_sizes[i].src_offset = GetLevelSrcOffset(tiled_levels[i]);
layout.level_sizes[i].src_size = GetLevelSrcSize(tiled_levels[i]);
layout.level_sizes[i].x = tiled_levels[i].x;
layout.level_sizes[i].y = tiled_levels[i].y;
}
}
} else if (layout.volume_texture) {
layout.slice_stride =
CalcTextureSliceStride(layout.level_sizes, levels, upload_size, depth);
}
} else {
EXIT("%s: legacy texture upload format unsupported: fmt=0 tile=%u size=%" PRIu64
" extent=%" PRIu64 "x%" PRIu64 " pitch=%" PRIu64 " levels=%" PRIu64 "\n",
owner, layout.tile, upload_size, width, height, pitch, levels);
}
if (!layout.volume_texture) {
layout.slice_stride =
CalcTextureSliceStride(layout.level_sizes, levels, upload_size, depth);
}
return layout;
}
std::vector<vk::BufferImageCopy> TextureBuildImageCopies(const TextureUploadLayout& layout,
uint32_t width, uint32_t height,
uint32_t depth, uint64_t levels,
bool array_texture, bool volume_texture) {
uint32_t mip_width = width;
uint32_t mip_height = height;
uint32_t mip_pitch = volume_texture && static_cast<Prospero::TileMode>(layout.tile) !=
Prospero::TileMode::kLinear
? width
: layout.pitch;
std::vector<vk::BufferImageCopy> regions;
regions.reserve(GetTextureRegionCount(depth, levels, volume_texture));
for (uint32_t i = 0; i < levels; i++) {
EXIT_NOT_IMPLEMENTED(layout.level_sizes[i].size == 0);
const auto mip_depth = GetTextureLevelDepth(depth, i, volume_texture);
for (uint32_t z = 0; z < mip_depth; z++) {
const auto slice_offset = z * layout.slice_stride;
vk::BufferImageCopy region {};
region.bufferOffset = layout.level_sizes[i].offset + slice_offset;
region.imageSubresource = {vk::ImageAspectFlagBits::eColor, i, array_texture ? z : 0,
1};
region.imageOffset.z = volume_texture ? static_cast<int>(z) : 0;
region.imageExtent = {mip_width, mip_height, 1};
const bool linear =
static_cast<Prospero::TileMode>(layout.tile) == Prospero::TileMode::kLinear;
if (linear) {
region.bufferRowLength = layout.padded_sizes[i].width;
region.bufferImageHeight = layout.padded_sizes[i].height;
} else {
const auto align = [](uint32_t value, uint32_t block) {
return ((value + block - 1u) / block) * block;
};
const auto pitch = align(mip_pitch, layout.texel_block);
region.bufferRowLength = pitch > align(mip_width, layout.texel_block) ? pitch : 0;
}
regions.push_back(region);
}
if (mip_width > 1) {
mip_width /= 2;
}
if (mip_height > 1) {
mip_height /= 2;
}
if (mip_pitch > 1) {
mip_pitch /= 2;
}
}
return regions;
}
struct GpuTileElementLayout {
uint32_t bytes = 0;
uint32_t wide = 1;
uint32_t tall = 1;
};
static bool GetGpuTileElementLayout(uint32_t fmt, GpuTileElementLayout& out) {
if (const auto bytes = Prospero::NumBytesPerElement(fmt); bytes != 0) {
out = {bytes, 1, 1};
return true;
}
if (const auto bytes = Prospero::BlockCompressedBytesPerBlock(fmt); bytes != 0) {
out = {bytes, 4, 4};
return true;
}
return false;
}
static bool SetGpuTileSize(uint64_t offset, uint64_t length, uint64_t capacity, uint64_t& size) {
if (offset > capacity || length > capacity - offset) {
return false;
}
size = length;
return true;
}
bool TextureBuildGpuTileInfos(uint64_t size, const std::vector<vk::BufferImageCopy>& regions,
const TextureUploadLayout& layout, uint32_t fmt, uint32_t depth,
uint64_t levels, std::vector<GpuTileInfo>& out_infos) {
if (size == 0 || levels == 0 || levels > 16 || depth == 0 ||
regions.size() != GetTextureRegionCount(depth, levels, layout.volume_texture) ||
Prospero::IsFmaskTextureFormat(fmt)) {
return false;
}
GpuTileElementLayout element {};
if (layout.tile_family == TileBlockFamily::RenderTarget64KB ||
layout.tile_family == TileBlockFamily::Depth64KB) {
element.bytes = Prospero::RenderTargetBytesPerElement(fmt);
} else if (!GetGpuTileElementLayout(fmt, element)) {
return false;
}
if (element.bytes == 0) {
return false;
}
std::vector<GpuTileInfo> infos;
infos.reserve(regions.size());
if (layout.volume_texture) {
TileVolumeLayout volume {};
if (!TileGetTextureVolumeLayout(fmt, regions[0].imageExtent.width,
regions[0].imageExtent.height, depth,
static_cast<uint32_t>(levels), layout.tile, volume)) {
return false;
}
element = {volume.bytes_per_element, volume.texel_width, volume.texel_height};
TileBlockLayout block {};
if (!TileGetBlockLayout(volume.family, element.bytes, block)) return false;
size_t region_base = 0;
for (uint32_t level = 0; level < levels; ++level) {
const uint32_t mip_depth = GetTextureLevelDepth(depth, level, true);
const bool tail = level >= volume.first_tail_level;
const uint64_t linear_stride = layout.slice_stride;
for (uint32_t z = 0; z < mip_depth; z += block.block_depth) {
const uint32_t copy_depth = std::min(block.block_depth, mip_depth - z);
const auto& region = regions[region_base + z];
const auto pitch =
region.bufferRowLength != 0 ? region.bufferRowLength : region.imageExtent.width;
const auto logical_height = region.bufferImageHeight != 0
? region.bufferImageHeight
: region.imageExtent.height;
GpuTileInfo info {};
info.family = block.family;
info.bytes_per_element = block.bytes_per_element;
info.linear_offset = region.bufferOffset;
info.tiled_offset =
static_cast<uint64_t>(z / block.block_depth) * volume.block_slice_size +
volume.level_offsets[level];
const uint64_t linear_span =
static_cast<uint64_t>(copy_depth - 1u) * linear_stride +
layout.level_sizes[level].size;
if (!SetGpuTileSize(info.linear_offset, linear_span, size, info.linear_size) ||
!SetGpuTileSize(info.tiled_offset, volume.level_sizes[level], size,
info.tiled_size)) {
return false;
}
info.linear_slice_stride = linear_stride;
info.width =
std::max((region.imageExtent.width + element.wide - 1u) / element.wide, 1u);
info.height = std::max((logical_height + element.tall - 1u) / element.tall, 1u);
info.depth = copy_depth;
info.surface_z =
block.block_depth == 1 ? static_cast<uint32_t>(region.imageOffset.z) : 0;
info.pitch = std::max((pitch + element.wide - 1u) / element.wide, 1u);
info.tail_x = tail ? volume.tail_x[level] : 0;
info.tail_y = tail ? volume.tail_y[level] : 0;
info.tail = tail;
info.tiled_width = volume.level_widths[level];
info.tiled_height = volume.level_heights[level];
infos.push_back(info);
}
region_base += mip_depth;
}
} else {
const auto base_family = layout.tile_family;
if (base_family == TileBlockFamily::Count) {
return false;
}
size_t region_index = 0;
for (uint32_t level = 0; level < levels; level++) {
const auto& level_size = layout.level_sizes[level];
const bool tail = level >= layout.first_tail_level;
const auto family = base_family;
TileBlockLayout block {};
if (!TileGetBlockLayout(family, element.bytes, block)) {
return false;
}
const auto level_depth = GetTextureLevelDepth(depth, level, layout.volume_texture);
for (uint32_t z = 0; z < level_depth; z++) {
const auto& region = regions[region_index++];
const auto pitch =
region.bufferRowLength != 0 ? region.bufferRowLength : region.imageExtent.width;
const auto logical_height = region.bufferImageHeight != 0
? region.bufferImageHeight
: region.imageExtent.height;
GpuTileInfo info {};
info.family = block.family;
info.bytes_per_element = block.bytes_per_element;
info.linear_offset = region.bufferOffset;
info.tiled_offset = TextureUploadSliceSourceOffset(layout, level, z);
if (!SetGpuTileSize(info.linear_offset, level_size.size, size, info.linear_size) ||
!SetGpuTileSize(info.tiled_offset, GetLevelSrcSize(level_size), size,
info.tiled_size)) {
return false;
}
info.width =
std::max((region.imageExtent.width + element.wide - 1u) / element.wide, 1u);
info.height = std::max((logical_height + element.tall - 1u) / element.tall, 1u);
info.surface_z = base_family == TileBlockFamily::RenderTarget64KB ||
base_family == TileBlockFamily::Depth64KB
? region.imageSubresource.baseArrayLayer
: 0;
info.pitch = std::max((pitch + element.wide - 1u) / element.wide, 1u);
info.tail = tail;
info.tail_x = tail ? level_size.x : 0;
info.tail_y = tail ? level_size.y : 0;
info.tiled_width =
layout.padded_sizes[level].width != 0
? std::max((layout.padded_sizes[level].width + element.wide - 1u) /
element.wide,
1u)
: info.pitch;
info.tiled_height =
layout.padded_sizes[level].height != 0
? std::max((layout.padded_sizes[level].height + element.tall - 1u) /
element.tall,
1u)
: info.height;
infos.push_back(info);
}
}
}
if (infos.empty()) {
return false;
}
out_infos = std::move(infos);
return true;
}
} // namespace Libs::Graphics
@@ -0,0 +1,53 @@
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_HOST_GPU_RENDERER_IMAGE_TEXTURECOMMON_H_
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_HOST_GPU_RENDERER_IMAGE_TEXTURECOMMON_H_
#include "common/abi.h"
#include "common/common.h"
#include "graphics/guest_gpu/gpu_defs.h"
#include "graphics/guest_gpu/tile.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include <vector>
namespace Libs::Graphics {
struct GpuTileInfo;
struct RenderTargetFormatInfo {
vk::Format format = vk::Format::eUndefined;
uint32_t bytes_per_element = 0;
Prospero::ColorComponentMapping export_mapping;
};
struct TextureUploadLayout {
uint32_t tile = 0;
uint32_t pitch = 0;
uint32_t texel_block = 1;
uint64_t slice_stride = 0;
uint64_t source_slice_stride = 0;
uint32_t first_tail_level = 16;
TileBlockFamily tile_family = TileBlockFamily::Count;
bool volume_texture = false;
TileSizeOffset level_sizes[16] = {};
TilePaddedSize padded_sizes[16] = {};
};
vk::ComponentMapping TextureGetComponentMapping(uint32_t swizzle);
vk::Format TextureGetFormat(uint32_t fmt);
RenderTargetFormatInfo TextureGetRenderTargetFormat(uint32_t layout, uint32_t type, uint32_t order);
TextureUploadLayout TextureCalcUploadLayout(uint32_t fmt, uint64_t width, uint64_t height,
uint64_t levels, uint32_t depth, uint64_t pitch,
uint64_t tile, uint64_t upload_size,
bool allow_depth_tile, bool volume_texture,
const char* owner);
std::vector<vk::BufferImageCopy> TextureBuildImageCopies(const TextureUploadLayout& layout,
uint32_t width, uint32_t height,
uint32_t depth, uint64_t levels,
bool array_texture, bool volume_texture);
bool TextureBuildGpuTileInfos(uint64_t size, const std::vector<vk::BufferImageCopy>& regions,
const TextureUploadLayout& layout, uint32_t fmt, uint32_t depth,
uint64_t levels, std::vector<GpuTileInfo>& infos);
} // namespace Libs::Graphics
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_HOST_GPU_RENDERER_IMAGE_TEXTURECOMMON_H_ */

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