mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-08-03 11:23:49 +00:00
Compare commits
98
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
59b8fad341 | ||
|
|
b877b4be9c | ||
|
|
9da7fc5dd6 | ||
|
|
da0d33224d | ||
|
|
f831e60412 | ||
|
|
84236d1f87 | ||
|
|
302b579779 | ||
|
|
0b6bf01b36 | ||
|
|
66f640527d | ||
|
|
4631b96178 | ||
|
|
43f64e4ab4 | ||
|
|
e63f5b7d5c | ||
|
|
fa7c3c01bf | ||
|
|
89651f6f59 | ||
|
|
44d7f2a3e8 | ||
|
|
2dcb90066c | ||
|
|
51a33cc363 | ||
|
|
ed84370786 | ||
|
|
43f30d3ab2 | ||
|
|
0838142abd | ||
|
|
0f550d1fd0 | ||
|
|
c1a5927036 | ||
|
|
bc436548a9 | ||
|
|
a65d17a5d6 | ||
|
|
c690aeea62 | ||
|
|
f830d6b2e4 | ||
|
|
d68a477276 | ||
|
|
8977d4d2f0 | ||
|
|
4b4e3bf3cf | ||
|
|
a0bb129f02 | ||
|
|
846002c5eb | ||
|
|
d8a4c83cc7 | ||
|
|
68be13345a | ||
|
|
167da0abe0 | ||
|
|
48c31d61ee | ||
|
|
212282d693 | ||
|
|
6bca35d1f5 | ||
|
|
e4ad5fc988 | ||
|
|
c0d3d261ea | ||
|
|
3b75a5659a | ||
|
|
d475387171 | ||
|
|
2f5396c6a5 | ||
|
|
ecb48f90bb | ||
|
|
77aa28b27c | ||
|
|
d04938c88c | ||
|
|
85622befb8 | ||
|
|
3965d41d36 | ||
|
|
c508c4a9c0 | ||
|
|
e91dd39cb0 | ||
|
|
cc76827e63 | ||
|
|
832bc84100 | ||
|
|
65a0f0baa7 | ||
|
|
b9ae2537ef | ||
|
|
0b9edaa721 | ||
|
|
8a244677d7 | ||
|
|
f6e01e5403 | ||
|
|
861729fc6c | ||
|
|
687ce025c6 | ||
|
|
a21d1aaa47 | ||
|
|
ec11f31aa6 | ||
|
|
aeceaff028 | ||
|
|
e76f2d1af8 | ||
|
|
64845e2294 | ||
|
|
4b9030bd3d | ||
|
|
a6b61d7aa0 | ||
|
|
2b9cba457e | ||
|
|
3dc793d859 | ||
|
|
01d78b71fe | ||
|
|
4d79f19089 | ||
|
|
8fe4765f9e | ||
|
|
c71bb9fc9e | ||
|
|
f60f80b631 | ||
|
|
31ea0081a9 | ||
|
|
7921269878 | ||
|
|
e42f6c3892 | ||
|
|
7351eae117 | ||
|
|
125287dc30 | ||
|
|
142b3c20fa | ||
|
|
f43567e12c | ||
|
|
c86d4b1f0d | ||
|
|
31fb3ec5af | ||
|
|
c3b2ae9733 | ||
|
|
93774ee37c | ||
|
|
a20cf5d298 | ||
|
|
a720b28b7f | ||
|
|
7638c25d76 | ||
|
|
85876388cf | ||
|
|
f60007e923 | ||
|
|
dbcb444c05 | ||
|
|
cf595beedf | ||
|
|
8a4600c5e9 | ||
|
|
0a5f78c727 | ||
|
|
b51d4322cc | ||
|
|
728b5b7d01 | ||
|
|
42f634a751 | ||
|
|
aebf6b4913 | ||
|
|
91690ac29e | ||
|
|
999f87576e |
+315
-14
@@ -1,4 +1,4 @@
|
||||
name: Build KytyPS5
|
||||
name: Build and Release KytyPS5
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
@@ -7,7 +7,8 @@ on:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
windows:
|
||||
name: Build KytyPS5 (Windows)
|
||||
runs-on: windows-2022
|
||||
|
||||
steps:
|
||||
@@ -22,6 +23,34 @@ jobs:
|
||||
- name: Setup Ninja
|
||||
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
|
||||
uses: jurplel/install-qt-action@v4
|
||||
with:
|
||||
@@ -38,6 +67,7 @@ jobs:
|
||||
cmake --version
|
||||
ninja --version
|
||||
clang-cl --version
|
||||
glslangValidator --version
|
||||
echo Qt6_DIR=%Qt6_DIR%
|
||||
|
||||
- name: Configure
|
||||
@@ -53,52 +83,323 @@ jobs:
|
||||
- name: Build
|
||||
shell: cmd
|
||||
run: |
|
||||
cmake --build _Build/windows --target launcher --parallel
|
||||
cmake --build _Build/windows --target launcher audio_out2_port_tests virtual_memory_allocation_tests --parallel
|
||||
|
||||
- name: Test
|
||||
shell: cmd
|
||||
run: |
|
||||
ctest --test-dir _Build/windows --output-on-failure -R "^(audio_out2_port|virtual_memory_allocation)$"
|
||||
|
||||
- name: Install
|
||||
shell: cmd
|
||||
run: |
|
||||
cmake --install _Build/windows --prefix _Build/windows/install
|
||||
|
||||
- name: Upload Artifacts
|
||||
- name: Upload Windows artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: KytyPS5
|
||||
name: KytyPS5-Windows-x64
|
||||
path: _Build/windows/install/**
|
||||
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 audio_out2_port_tests virtual_memory_allocation_tests \
|
||||
--parallel
|
||||
|
||||
- name: Test
|
||||
shell: bash
|
||||
run: |
|
||||
ctest --test-dir _Build/macos --output-on-failure \
|
||||
-R '^(audio_out2_port|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 \
|
||||
audio_out2_port_tests virtual_memory_allocation_tests \
|
||||
--parallel
|
||||
|
||||
- name: Test
|
||||
shell: bash
|
||||
run: |
|
||||
ctest --test-dir _Build/linux --output-on-failure \
|
||||
-R '^(audio_out2_port|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:
|
||||
if: github.event_name == 'push'
|
||||
needs: build
|
||||
name: Release KytyPS5
|
||||
if: github.event_name == 'push' && github.repository == 'KytyPS5/KytyPS5'
|
||||
needs: [windows, macos, linux]
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- name: Download build
|
||||
- name: Download builds
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: KytyPS5
|
||||
path: KytyPS5
|
||||
path: artifacts
|
||||
|
||||
- name: Set release name
|
||||
shell: bash
|
||||
run: |
|
||||
echo "RELEASE_NAME=KytyPS5-$(date -u +'%Y-%m-%d')-${GITHUB_SHA::7}" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Package build
|
||||
- name: Package builds
|
||||
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
|
||||
shell: bash
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
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
|
||||
gh release upload "$RELEASE_NAME" "$RELEASE_NAME.zip" --clobber --repo "$GITHUB_REPOSITORY"
|
||||
gh release upload "$RELEASE_NAME" "${assets[@]}" \
|
||||
--clobber \
|
||||
--repo "$GITHUB_REPOSITORY"
|
||||
else
|
||||
gh release create "$RELEASE_NAME" "$RELEASE_NAME.zip" \
|
||||
gh release create "$RELEASE_NAME" "${assets[@]}" \
|
||||
--generate-notes \
|
||||
--repo "$GITHUB_REPOSITORY" \
|
||||
--target "$GITHUB_SHA" \
|
||||
|
||||
+2
-1
@@ -2,4 +2,5 @@
|
||||
.vs/
|
||||
.idea/
|
||||
build/
|
||||
_Build/vscode-clang/
|
||||
_Build/vscode-clang/
|
||||
_Build/
|
||||
Vendored
+11
-1
@@ -44,7 +44,17 @@ set(SPDLOG_NO_EXCEPTIONS ON CACHE BOOL "" FORCE)
|
||||
add_subdirectory(spdlog EXCLUDE_FROM_ALL)
|
||||
|
||||
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)
|
||||
else()
|
||||
set(ARCHITECTURE x86_64)
|
||||
|
||||
Vendored
+12
-3
@@ -5,7 +5,14 @@ set(cpuinfo_src
|
||||
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
|
||||
src/x86/init.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)
|
||||
if(LINUX OR ANDROID)
|
||||
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()
|
||||
list(APPEND cpuinfo_src src/x86/windows/init.c)
|
||||
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)
|
||||
|
||||
if(LINUX OR ANDROID)
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
# KytyPS5
|
||||
|
||||
[](#system-requirements)
|
||||
[](https://github.com/KytyPS5/KytyPS5/actions/workflows/build.yml)
|
||||
[](https://github.com/KytyPS5/KytyPS5/actions/workflows/build.yml)
|
||||
[](https://github.com/KytyPS5/KytyPS5/actions/workflows/build.yml)
|
||||
[](#system-requirements)
|
||||
[](#current-status)
|
||||
[](LICENSE)
|
||||
|
||||
KytyPS5 is a free and open-source PlayStation 5 emulator written in C++ for Windows. It is based on
|
||||
a heavily modified version of [Kyty](https://github.com/InoriRus/Kyty). The project is in an early
|
||||
stage of development, so compatibility is limited and behavior may change significantly between
|
||||
builds.
|
||||
KytyPS5 is a free and open-source PlayStation 5 emulator written in C++ for Windows and Linux,
|
||||
with experimental macOS support. It is based on a heavily modified version of
|
||||
[Kyty](https://github.com/InoriRus/Kyty). The project is in an early stage of development, so
|
||||
compatibility is limited and behavior may change significantly between builds.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> 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.
|
||||
|
||||
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
|
||||
|
||||
@@ -43,7 +51,7 @@ graphical glitches, low compatibility, and poor performance.
|
||||
</tr>
|
||||
<tr>
|
||||
<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">
|
||||
</td>
|
||||
<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">
|
||||
</td>
|
||||
</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>
|
||||
|
||||
<p align="center"><em>And many more...</em></p>
|
||||
|
||||
## Contributing
|
||||
|
||||
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.
|
||||
|
||||
Code contributions should be focused, build successfully on Windows, and include relevant tests
|
||||
where practical. Because KytyPS5 is still evolving quickly, consider opening an issue before
|
||||
Code contributions should be focused, build successfully on the platforms they touch, and include
|
||||
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.
|
||||
|
||||
### Formatting
|
||||
@@ -94,11 +115,12 @@ the Vulkan/SPIR-V validation rules.
|
||||
|
||||
### System requirements
|
||||
|
||||
- Windows 10 version 1803
|
||||
- A 64-bit x86 processor
|
||||
- A Vulkan 1.3-capable GPU with current drivers
|
||||
- Windows 10 version 1803, a current Linux distribution, or macOS on Apple Silicon
|
||||
- A 64-bit x86 processor (on macOS, an Apple Silicon processor with Rosetta 2)
|
||||
- A Vulkan 1.3-capable GPU with current drivers (on macOS, Vulkan is provided by the bundled
|
||||
MoltenVK)
|
||||
|
||||
### Build requirements
|
||||
### Build requirements (Windows)
|
||||
|
||||
- Git
|
||||
- CMake 3.12 or newer
|
||||
@@ -132,11 +154,98 @@ cmake --install _Build/windows --prefix _Build/windows/install
|
||||
The finished application and its runtime dependencies will be placed in
|
||||
`_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`.
|
||||
|
||||
### Regression tests
|
||||
|
||||
Build every regression executable and run the registered tests with:
|
||||
|
||||
```powershell
|
||||
cmake --build _Build/windows --target kyty_tests
|
||||
ctest --test-dir _Build/windows --output-on-failure
|
||||
```
|
||||
|
||||
Use `_Build/linux` instead of `_Build/windows` for a Linux build.
|
||||
|
||||
### Visual Studio Code
|
||||
|
||||
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
|
||||
`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:
|
||||
|
||||
@@ -158,6 +267,10 @@ To use the graphical launcher:
|
||||
.\_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
|
||||
folders recursively for game directories containing `eboot.bin`. Select a detected game and run it
|
||||
from the game list.
|
||||
@@ -168,7 +281,19 @@ The emulator can also be started directly with a legally obtained game directory
|
||||
.\_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.
|
||||
|
||||
### 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 |
+270
-26
@@ -10,15 +10,19 @@ if(CMAKE_SYSTEM_NAME MATCHES ".*Linux")
|
||||
set(LINUX TRUE)
|
||||
endif()
|
||||
|
||||
if (NOT (WIN32 OR LINUX))
|
||||
message(FATAL_ERROR "only Windows and Linux builds are supported")
|
||||
if (NOT (WIN32 OR LINUX OR APPLE))
|
||||
message(FATAL_ERROR "only Windows, Linux, and macOS builds are supported")
|
||||
endif()
|
||||
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
set(CMAKE_CXX_SCAN_FOR_MODULES OFF)
|
||||
|
||||
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")
|
||||
|
||||
@@ -43,8 +47,9 @@ else()
|
||||
set(KYTY_BUILD KYTY_BUILD_RELEASE)
|
||||
endif()
|
||||
|
||||
if(LINUX)
|
||||
set(KYTY_PLATFORM KYTY_PLATFORM_LINUX)
|
||||
if(LINUX OR APPLE)
|
||||
# macOS rides the POSIX/Linux code paths until it gets a dedicated platform
|
||||
set(KYTY_PLATFORM KYTY_PLATFORM_LINUX)
|
||||
else()
|
||||
set(KYTY_PLATFORM KYTY_PLATFORM_WINDOWS)
|
||||
endif()
|
||||
@@ -61,6 +66,8 @@ if(CMAKE_CXX_COMPILER_ID MATCHES "^(Apple)?Clang$")
|
||||
if(WIN32 AND KYTY_CXX_COMPILER_NAME STREQUAL "clang-cl")
|
||||
set(KYTY_CLANG_CL TRUE)
|
||||
set(KYTY_LINKER LLD_LINK)
|
||||
elseif(APPLE)
|
||||
set(KYTY_LINKER LD64) # Apple's default linker; lld flags don't apply
|
||||
else()
|
||||
set(KYTY_LINKER LLD)
|
||||
endif()
|
||||
@@ -113,6 +120,7 @@ add_custom_target( KytyGitVersion
|
||||
-D INPUT_FILE=${CMAKE_CURRENT_SOURCE_DIR}/kytyGitVersion.h.in
|
||||
-D OUTPUT_FILE=${CMAKE_CURRENT_BINARY_DIR}/kytyGitVersion.h
|
||||
-D GIT_EXECUTABLE=${GIT_EXECUTABLE}
|
||||
-D GIT_WORKING_DIRECTORY=${CMAKE_CURRENT_SOURCE_DIR}
|
||||
-P ${CMAKE_CURRENT_SOURCE_DIR}/generate_version.cmake
|
||||
COMMENT "Generate kytyGitVersion.h"
|
||||
)
|
||||
@@ -123,7 +131,7 @@ if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 12.0.0)
|
||||
common
|
||||
#launcher
|
||||
)
|
||||
list(APPEND KYTY_CLANG_TYDY
|
||||
list(APPEND KYTY_CLANG_TIDY
|
||||
kyty_emulator
|
||||
#common
|
||||
launcher
|
||||
@@ -150,16 +158,24 @@ file(GLOB kyty_emulator_src CONFIGURE_DEPENDS
|
||||
graphics/host_gpu/*.h
|
||||
graphics/host_gpu/renderer/*.cpp
|
||||
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/*.h
|
||||
graphics/shader/recompiler/*.cpp
|
||||
graphics/shader/recompiler/*.h
|
||||
graphics/shader/recompiler/shaderIR/*.cpp
|
||||
graphics/shader/recompiler/shaderIR/*.h
|
||||
graphics/shader/recompiler/spirvEmitter/*.cpp
|
||||
graphics/shader/recompiler/spirvEmitter/*.h
|
||||
graphics/host_gpu/objects/*.cpp
|
||||
graphics/host_gpu/objects/*.h
|
||||
graphics/shader/recompiler/cfg/*.cpp
|
||||
graphics/shader/recompiler/cfg/*.h
|
||||
graphics/shader/recompiler/decompiler/*.cpp
|
||||
graphics/shader/recompiler/decompiler/*.h
|
||||
graphics/shader/recompiler/emitter/*.cpp
|
||||
graphics/shader/recompiler/emitter/*.h
|
||||
graphics/shader/recompiler/ir/*.cpp
|
||||
graphics/shader/recompiler/ir/*.h
|
||||
graphics/presentation/*.cpp
|
||||
graphics/presentation/*.h
|
||||
graphics/presentation/window/*.cpp
|
||||
@@ -170,6 +186,67 @@ file(GLOB kyty_emulator_src CONFIGURE_DEPENDS
|
||||
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
|
||||
emulator.h
|
||||
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)
|
||||
|
||||
# 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
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
${KYTY_THIRD_PARTY_DIR}/SDL2/include
|
||||
@@ -215,6 +312,19 @@ function(add_kyty_full_emulator_test target source)
|
||||
target_link_libraries(${target} onecore)
|
||||
add_custom_command(TARGET ${target} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different "${KYTY_THIRD_PARTY_DIR}/winpthread/bin/libwinpthread-1.dll" $<TARGET_FILE_DIR:${target}>/libwinpthread-1.dll)
|
||||
endif()
|
||||
# The macOS x86_64 guest address space needs its .zerofill segments anchored
|
||||
# by linker flags, or the kernel kills the binary on load (posix_spawn EIO).
|
||||
configure_macos_guest_address_space(${target})
|
||||
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)
|
||||
@@ -222,8 +332,9 @@ add_kyty_full_emulator_test(shader_cfg_tests ../tests/shaderCfgTests.cpp)
|
||||
add_executable(scalar_provenance_tests EXCLUDE_FROM_ALL
|
||||
../tests/ScalarProvenanceTests.cpp
|
||||
graphics/host_gpu/hostMemory.cpp
|
||||
graphics/shader/recompiler/ScalarProvenance.cpp
|
||||
graphics/shader/recompiler/SrtWalker.cpp
|
||||
graphics/shader/recompiler/ir/ReadLaneElimination.cpp
|
||||
graphics/shader/recompiler/ir/ScalarProvenance.cpp
|
||||
graphics/shader/recompiler/ir/SrtWalker.cpp
|
||||
)
|
||||
target_link_libraries(scalar_provenance_tests fmt::fmt)
|
||||
target_include_directories(scalar_provenance_tests PRIVATE ${inc_headers})
|
||||
@@ -234,6 +345,11 @@ add_executable(page_manager_tests EXCLUDE_FROM_ALL
|
||||
)
|
||||
target_include_directories(page_manager_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(bit_array_tests EXCLUDE_FROM_ALL
|
||||
../tests/BitArrayTests.cpp
|
||||
)
|
||||
target_include_directories(bit_array_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(memory_tracker_tests EXCLUDE_FROM_ALL
|
||||
../tests/MemoryTrackerTests.cpp
|
||||
graphics/host_gpu/pageManager.cpp
|
||||
@@ -241,7 +357,6 @@ add_executable(memory_tracker_tests EXCLUDE_FROM_ALL
|
||||
)
|
||||
target_link_libraries(memory_tracker_tests fmt::fmt common)
|
||||
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
|
||||
../tests/ShaderVertexMetadataTests.cpp
|
||||
@@ -255,9 +370,9 @@ add_executable(shader_stage_runtime_tests EXCLUDE_FROM_ALL
|
||||
graphics/guest_gpu/gpu_format.cpp
|
||||
graphics/host_gpu/hostMemory.cpp
|
||||
graphics/shader/shaderStageRuntime.cpp
|
||||
graphics/shader/recompiler/ResourceMaterialization.cpp
|
||||
graphics/shader/recompiler/ScalarProvenance.cpp
|
||||
graphics/shader/recompiler/SrtWalker.cpp
|
||||
graphics/shader/recompiler/ir/ResourceMaterialization.cpp
|
||||
graphics/shader/recompiler/ir/ScalarProvenance.cpp
|
||||
graphics/shader/recompiler/ir/SrtWalker.cpp
|
||||
)
|
||||
target_link_libraries(shader_stage_runtime_tests fmt::fmt)
|
||||
target_include_directories(shader_stage_runtime_tests PRIVATE ${inc_headers})
|
||||
@@ -266,37 +381,147 @@ add_executable(resource_tracking_tests EXCLUDE_FROM_ALL
|
||||
../tests/ResourceTrackingTests.cpp
|
||||
graphics/guest_gpu/gpu_format.cpp
|
||||
graphics/host_gpu/hostMemory.cpp
|
||||
graphics/shader/recompiler/ScalarProvenance.cpp
|
||||
graphics/shader/recompiler/SrtWalker.cpp
|
||||
graphics/shader/recompiler/SrtPatcher.cpp
|
||||
graphics/shader/recompiler/ResourceTracking.cpp
|
||||
graphics/shader/recompiler/ResourceMaterialization.cpp
|
||||
graphics/shader/recompiler/ShaderInfoCollection.cpp
|
||||
graphics/shader/recompiler/BindingLayout.cpp
|
||||
graphics/shader/recompiler/ir/ScalarProvenance.cpp
|
||||
graphics/shader/recompiler/ir/SrtWalker.cpp
|
||||
graphics/shader/recompiler/ir/SrtPatcher.cpp
|
||||
graphics/shader/recompiler/ir/ResourceTracking.cpp
|
||||
graphics/shader/recompiler/ir/ResourceMaterialization.cpp
|
||||
graphics/shader/recompiler/ir/ShaderInfoCollection.cpp
|
||||
graphics/shader/recompiler/ir/BindingLayout.cpp
|
||||
)
|
||||
target_link_libraries(resource_tracking_tests fmt::fmt)
|
||||
target_include_directories(resource_tracking_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(resource_mutex_tests EXCLUDE_FROM_ALL
|
||||
../tests/ResourceMutexTests.cpp
|
||||
graphics/host_gpu/renderer/resourceMutex.cpp
|
||||
graphics/host_gpu/renderer/cache/resourceMutex.cpp
|
||||
)
|
||||
target_link_libraries(resource_mutex_tests common)
|
||||
target_include_directories(resource_mutex_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(audio_out2_port_tests EXCLUDE_FROM_ALL
|
||||
../tests/AudioOut2PortTests.cpp
|
||||
libs/libAudio2.cpp
|
||||
loader/timer.cpp
|
||||
)
|
||||
target_link_libraries(audio_out2_port_tests common fmt::fmt)
|
||||
target_include_directories(audio_out2_port_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
|
||||
../tests/ImagePageTableTests.cpp
|
||||
)
|
||||
target_link_libraries(image_page_table_tests common fmt::fmt)
|
||||
target_include_directories(image_page_table_tests PRIVATE ${inc_headers})
|
||||
|
||||
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)
|
||||
target_compile_definitions(virtual_memory_allocation_tests PRIVATE
|
||||
KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS=1)
|
||||
|
||||
# These tests use exceptions.
|
||||
if(NOT KYTY_CLANG_CL)
|
||||
foreach(kyty_exception_test scalar_provenance_tests resource_tracking_tests
|
||||
virtual_memory_allocation_tests)
|
||||
target_compile_options(${kyty_exception_test} PRIVATE -fexceptions)
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
if(BUILD_TESTING)
|
||||
add_test(NAME shader_cfg COMMAND $<TARGET_FILE:shader_cfg_tests>)
|
||||
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 bit_array COMMAND $<TARGET_FILE:bit_array_tests>)
|
||||
add_test(NAME shader_vertex_metadata COMMAND $<TARGET_FILE:shader_vertex_metadata_tests>)
|
||||
add_test(NAME shader_stage_runtime COMMAND $<TARGET_FILE:shader_stage_runtime_tests>)
|
||||
add_test(NAME resource_tracking COMMAND $<TARGET_FILE:resource_tracking_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 audio_out2_port COMMAND $<TARGET_FILE:audio_out2_port_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()
|
||||
|
||||
add_custom_target(kyty_tests DEPENDS
|
||||
shader_cfg_tests
|
||||
scalar_provenance_tests
|
||||
image_page_table_tests
|
||||
memory_tracker_tests
|
||||
page_manager_tests
|
||||
bit_array_tests
|
||||
shader_vertex_metadata_tests
|
||||
shader_stage_runtime_tests
|
||||
resource_tracking_tests
|
||||
resource_mutex_tests
|
||||
event_queue_lifetime_tests
|
||||
shader_recompiler_compute_tests
|
||||
virtual_memory_allocation_tests
|
||||
)
|
||||
endif()
|
||||
|
||||
|
||||
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})
|
||||
if (WIN32)
|
||||
@@ -311,6 +536,8 @@ endif()
|
||||
if (CLANG AND NOT KYTY_CLANG_CL)
|
||||
target_link_libraries(kyty_emulator pthread)
|
||||
endif()
|
||||
# dlopen/dlsym/dladdr for RenderDoc.
|
||||
target_link_libraries(kyty_emulator ${CMAKE_DL_LIBS})
|
||||
target_include_directories(kyty_emulator PRIVATE ${inc_headers})
|
||||
|
||||
clang_tidy_check(kyty_emulator "" "${check_headers}" "${inc_headers}")
|
||||
@@ -323,7 +550,12 @@ set(KYTY_EMULATOR_MAP_LINK_PATH "${CMAKE_CURRENT_BINARY_DIR}/${KYTY_EMULATOR_MAP
|
||||
set(KYTY_EMULATOR_PDB_LINK_PATH "${CMAKE_CURRENT_BINARY_DIR}/kyty_emulator.pdb")
|
||||
|
||||
if(KYTY_CLANG_CL)
|
||||
set_target_properties(kyty_emulator PROPERTIES LINK_FLAGS "/DYNAMICBASE:NO /DEBUG:FULL /PDB:${KYTY_EMULATOR_PDB_LINK_PATH} /lldmap:${KYTY_EMULATOR_MAP_LINK_PATH}")
|
||||
target_link_options(kyty_emulator PRIVATE
|
||||
"/DYNAMICBASE:NO"
|
||||
"/DEBUG:FULL"
|
||||
"/PDB:${KYTY_EMULATOR_PDB_LINK_PATH}"
|
||||
"/lldmap:${KYTY_EMULATOR_MAP_LINK_PATH}"
|
||||
)
|
||||
add_custom_command(TARGET kyty_emulator POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different "${KYTY_THIRD_PARTY_DIR}/winpthread/bin/libwinpthread-1.dll" $<TARGET_FILE_DIR:kyty_emulator>/libwinpthread-1.dll)
|
||||
elseif(WIN32 OR LINUX)
|
||||
set_target_properties(kyty_emulator PROPERTIES LINK_FLAGS "${KYTY_LD_OPTIONS} -Wl,-Map=${KYTY_EMULATOR_MAP_LINK_PATH}")
|
||||
@@ -331,6 +563,18 @@ endif()
|
||||
|
||||
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 .)
|
||||
if(KYTY_BUILD_LAUNCHER)
|
||||
add_subdirectory(launcher)
|
||||
|
||||
@@ -0,0 +1,260 @@
|
||||
#ifndef EMULATOR_SRC_COMMON_BITARRAY_H_
|
||||
#define EMULATOR_SRC_COMMON_BITARRAY_H_
|
||||
|
||||
#include <array>
|
||||
#include <bit>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <iterator>
|
||||
#include <utility>
|
||||
|
||||
namespace Common {
|
||||
|
||||
template <size_t N>
|
||||
class BitArray final {
|
||||
static_assert(N != 0, "BitArray size must be nonzero");
|
||||
static_assert(N % 64 == 0, "BitArray size must be a multiple of 64 bits");
|
||||
|
||||
static constexpr size_t BITS_PER_WORD = 64;
|
||||
static constexpr size_t WORD_COUNT = N / BITS_PER_WORD;
|
||||
|
||||
public:
|
||||
using Range = std::pair<size_t, size_t>;
|
||||
|
||||
class Iterator final {
|
||||
public:
|
||||
using iterator_category = std::forward_iterator_tag;
|
||||
using value_type = Range;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using pointer = const Range*;
|
||||
using reference = const Range&;
|
||||
|
||||
Iterator(const BitArray& bits, size_t start)
|
||||
: m_bits(bits), m_range(bits.FirstRangeFrom(start)) {}
|
||||
|
||||
Iterator& operator++() {
|
||||
m_range = m_bits.FirstRangeFrom(m_range.second);
|
||||
return *this;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool operator==(const Iterator& other) const {
|
||||
return &m_bits == &other.m_bits && m_range == other.m_range;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool operator!=(const Iterator& other) const { return !(*this == other); }
|
||||
|
||||
[[nodiscard]] reference operator*() const { return m_range; }
|
||||
[[nodiscard]] pointer operator->() const { return &m_range; }
|
||||
|
||||
private:
|
||||
const BitArray& m_bits;
|
||||
Range m_range;
|
||||
};
|
||||
|
||||
using const_iterator = Iterator;
|
||||
|
||||
constexpr BitArray() = default;
|
||||
|
||||
constexpr BitArray(const BitArray& other, size_t start, size_t end) {
|
||||
if (start >= end || end > N) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto first_word = start / BITS_PER_WORD;
|
||||
const auto last_word = (end - 1) / BITS_PER_WORD;
|
||||
const auto start_bit = start % BITS_PER_WORD;
|
||||
const auto end_bit = (end - 1) % BITS_PER_WORD;
|
||||
const auto start_mask = ~uint64_t {0} << start_bit;
|
||||
const auto end_mask =
|
||||
end_bit == BITS_PER_WORD - 1 ? ~uint64_t {0} : (uint64_t {1} << (end_bit + 1)) - 1;
|
||||
|
||||
if (first_word == last_word) {
|
||||
m_data[first_word] = other.m_data[first_word] & start_mask & end_mask;
|
||||
return;
|
||||
}
|
||||
|
||||
m_data[first_word] = other.m_data[first_word] & start_mask;
|
||||
for (auto word = first_word + 1; word < last_word; word++) {
|
||||
m_data[word] = other.m_data[word];
|
||||
}
|
||||
m_data[last_word] = other.m_data[last_word] & end_mask;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr bool Get(size_t index) const {
|
||||
return (m_data[index / BITS_PER_WORD] & (uint64_t {1} << (index % BITS_PER_WORD))) != 0;
|
||||
}
|
||||
|
||||
constexpr void Set(size_t index) {
|
||||
m_data[index / BITS_PER_WORD] |= uint64_t {1} << (index % BITS_PER_WORD);
|
||||
}
|
||||
|
||||
constexpr void Unset(size_t index) {
|
||||
m_data[index / BITS_PER_WORD] &= ~(uint64_t {1} << (index % BITS_PER_WORD));
|
||||
}
|
||||
|
||||
constexpr void SetRange(size_t start, size_t end) {
|
||||
if (start >= end || end > N) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto first_word = start / BITS_PER_WORD;
|
||||
const auto last_word = (end - 1) / BITS_PER_WORD;
|
||||
const auto start_bit = start % BITS_PER_WORD;
|
||||
const auto end_bit = (end - 1) % BITS_PER_WORD;
|
||||
const auto start_mask = ~uint64_t {0} << start_bit;
|
||||
const auto end_mask =
|
||||
end_bit == BITS_PER_WORD - 1 ? ~uint64_t {0} : (uint64_t {1} << (end_bit + 1)) - 1;
|
||||
|
||||
if (first_word == last_word) {
|
||||
m_data[first_word] |= start_mask & end_mask;
|
||||
return;
|
||||
}
|
||||
|
||||
m_data[first_word] |= start_mask;
|
||||
for (auto word = first_word + 1; word < last_word; word++) {
|
||||
m_data[word] = ~uint64_t {0};
|
||||
}
|
||||
m_data[last_word] |= end_mask;
|
||||
}
|
||||
|
||||
constexpr void UnsetRange(size_t start, size_t end) {
|
||||
if (start >= end || end > N) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto first_word = start / BITS_PER_WORD;
|
||||
const auto last_word = (end - 1) / BITS_PER_WORD;
|
||||
const auto start_bit = start % BITS_PER_WORD;
|
||||
const auto end_bit = (end - 1) % BITS_PER_WORD;
|
||||
const auto start_mask = (uint64_t {1} << start_bit) - 1;
|
||||
const auto end_mask =
|
||||
end_bit == BITS_PER_WORD - 1 ? uint64_t {0} : ~((uint64_t {1} << (end_bit + 1)) - 1);
|
||||
|
||||
if (first_word == last_word) {
|
||||
m_data[first_word] &= start_mask | end_mask;
|
||||
return;
|
||||
}
|
||||
|
||||
m_data[first_word] &= start_mask;
|
||||
for (auto word = first_word + 1; word < last_word; word++) {
|
||||
m_data[word] = 0;
|
||||
}
|
||||
m_data[last_word] &= end_mask;
|
||||
}
|
||||
|
||||
constexpr void Clear() { m_data.fill(0); }
|
||||
constexpr void Fill() { m_data.fill(~uint64_t {0}); }
|
||||
|
||||
[[nodiscard]] constexpr bool None() const {
|
||||
uint64_t combined = 0;
|
||||
for (const auto word: m_data) {
|
||||
combined |= word;
|
||||
}
|
||||
return combined == 0;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr bool Any() const { return !None(); }
|
||||
|
||||
[[nodiscard]] constexpr Range FirstRangeFrom(size_t start) const {
|
||||
if (start >= N) {
|
||||
return {N, N};
|
||||
}
|
||||
|
||||
auto word_index = start / BITS_PER_WORD;
|
||||
auto word = m_data[word_index] & (~uint64_t {0} << (start % BITS_PER_WORD));
|
||||
while (word == 0) {
|
||||
word_index++;
|
||||
if (word_index == WORD_COUNT) {
|
||||
return {N, N};
|
||||
}
|
||||
word = m_data[word_index];
|
||||
}
|
||||
|
||||
const auto first = word_index * BITS_PER_WORD + std::countr_zero(word);
|
||||
const auto first_bit = first % BITS_PER_WORD;
|
||||
const auto first_ones =
|
||||
static_cast<size_t>(std::countr_one(m_data[word_index] >> first_bit));
|
||||
if (first_bit + first_ones < BITS_PER_WORD) {
|
||||
return {first, first + first_ones};
|
||||
}
|
||||
|
||||
for (word_index++; word_index < WORD_COUNT; word_index++) {
|
||||
word = m_data[word_index];
|
||||
if (word != ~uint64_t {0}) {
|
||||
return {first, word_index * BITS_PER_WORD + std::countr_one(word)};
|
||||
}
|
||||
}
|
||||
return {first, N};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr Range FirstRange() const { return FirstRangeFrom(0); }
|
||||
|
||||
[[nodiscard]] constexpr Range LastRangeFrom(size_t end) const {
|
||||
if (end == 0) {
|
||||
return {0, 0};
|
||||
}
|
||||
if (end > N) {
|
||||
end = N;
|
||||
}
|
||||
|
||||
auto word_index = (end - 1) / BITS_PER_WORD;
|
||||
const auto end_bit = (end - 1) % BITS_PER_WORD;
|
||||
const auto end_mask =
|
||||
end_bit == BITS_PER_WORD - 1 ? ~uint64_t {0} : (uint64_t {1} << (end_bit + 1)) - 1;
|
||||
auto word = m_data[word_index] & end_mask;
|
||||
while (word == 0) {
|
||||
if (word_index == 0) {
|
||||
return {0, 0};
|
||||
}
|
||||
word = m_data[--word_index];
|
||||
}
|
||||
|
||||
const auto empty_bits = static_cast<size_t>(std::countl_zero(word));
|
||||
const auto ones = static_cast<size_t>(std::countl_one(word << empty_bits));
|
||||
const auto last = (word_index + 1) * BITS_PER_WORD - empty_bits;
|
||||
if (empty_bits + ones < BITS_PER_WORD) {
|
||||
return {last - ones, last};
|
||||
}
|
||||
|
||||
while (word_index != 0) {
|
||||
word = m_data[--word_index];
|
||||
if (word != ~uint64_t {0}) {
|
||||
return {(word_index + 1) * BITS_PER_WORD - std::countl_one(word), last};
|
||||
}
|
||||
}
|
||||
return {0, last};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr Range LastRange() const { return LastRangeFrom(N); }
|
||||
|
||||
[[nodiscard]] const_iterator begin() const { return Iterator(*this, 0); }
|
||||
[[nodiscard]] const_iterator end() const { return Iterator(*this, N); }
|
||||
|
||||
constexpr BitArray& operator^=(const BitArray& other) {
|
||||
for (size_t word = 0; word < WORD_COUNT; word++) {
|
||||
m_data[word] ^= other.m_data[word];
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr BitArray operator^(const BitArray& other) const {
|
||||
auto result = *this;
|
||||
result ^= other;
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr BitArray operator~() const {
|
||||
auto result = *this;
|
||||
for (auto& word: result.m_data) {
|
||||
word = ~word;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
std::array<uint64_t, WORD_COUNT> m_data {};
|
||||
};
|
||||
|
||||
} // namespace Common
|
||||
|
||||
#endif // EMULATOR_SRC_COMMON_BITARRAY_H_
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef> // IWYU pragma: export
|
||||
#include <cstring>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "common/stringUtils.h"
|
||||
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
|
||||
namespace Common {
|
||||
|
||||
|
||||
@@ -93,4 +93,8 @@ bool NggRectlistDrawEnabled() {
|
||||
return g_config->ngg_rectlist_draw_enabled;
|
||||
}
|
||||
|
||||
bool ReadbackLinearImagesEnabled() {
|
||||
return g_config->readback_linear_images;
|
||||
}
|
||||
|
||||
} // namespace Config
|
||||
|
||||
@@ -36,6 +36,7 @@ struct ConfigOptions {
|
||||
bool spirv_debug_printf_enabled = false;
|
||||
bool renderdoc_enabled = false;
|
||||
bool ngg_rectlist_draw_enabled = true;
|
||||
bool readback_linear_images = false;
|
||||
};
|
||||
|
||||
void Load(const ConfigOptions& cfg);
|
||||
@@ -64,6 +65,7 @@ bool SpirvDebugPrintfEnabled();
|
||||
|
||||
bool RenderDocEnabled();
|
||||
bool NggRectlistDrawEnabled();
|
||||
bool ReadbackLinearImagesEnabled();
|
||||
|
||||
} // namespace Config
|
||||
|
||||
|
||||
@@ -6,6 +6,14 @@
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
#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
|
||||
|
||||
// IWYU pragma: no_include <errhandlingapi.h>
|
||||
@@ -16,7 +24,7 @@
|
||||
|
||||
namespace Common::HostException {
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
#if !defined(__APPLE__)
|
||||
|
||||
static std::atomic<Handler> g_handler {nullptr};
|
||||
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::fputc('\n', stderr);
|
||||
std::fflush(stderr);
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
TerminateProcess(GetCurrentProcess(), static_cast<UINT>(EXCEPTION_NONCONTINUABLE_EXCEPTION));
|
||||
#endif
|
||||
std::_Exit(321);
|
||||
}
|
||||
|
||||
@@ -48,6 +58,21 @@ public:
|
||||
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) {
|
||||
FilterScope filter_scope;
|
||||
|
||||
@@ -106,22 +131,176 @@ static LONG WINAPI ExceptionFilter(PEXCEPTION_POINTERS exception) {
|
||||
info.r14 = exception->ContextRecord->R14;
|
||||
info.r15 = exception->ContextRecord->R15;
|
||||
|
||||
if (g_install_state.load(std::memory_order_acquire) == 0) {
|
||||
FailFast("host exception handler is not installed");
|
||||
const auto handler = LoadInstalledHandler();
|
||||
|
||||
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);
|
||||
if (handler == nullptr) {
|
||||
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
|
||||
|
||||
bool InstallHandler(Handler handler) {
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
if (handler == nullptr) {
|
||||
return false;
|
||||
}
|
||||
@@ -133,19 +312,52 @@ bool InstallHandler(Handler handler) {
|
||||
|
||||
g_handler.store(handler, std::memory_order_release);
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
if (AddVectoredExceptionHandler(1, ExceptionFilter) == nullptr) {
|
||||
g_handler.store(nullptr, std::memory_order_release);
|
||||
g_install_state.store(0, std::memory_order_release);
|
||||
printf("AddVectoredExceptionHandler() failed\n");
|
||||
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);
|
||||
return true;
|
||||
#else
|
||||
(void)handler;
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace Common::HostException
|
||||
|
||||
@@ -178,6 +178,11 @@ Direction GetDirection() {
|
||||
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) {
|
||||
WriteImpl(text);
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ KYTY_SUBSYSTEM_DEFINE(Log);
|
||||
enum class Direction { Silent, Console, File };
|
||||
|
||||
Direction GetDirection();
|
||||
bool IsSilent();
|
||||
void Write(std::string_view text);
|
||||
void Write(fmt::text_style style, 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
|
||||
|
||||
// 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)
|
||||
#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_ */
|
||||
|
||||
@@ -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_
|
||||
@@ -23,6 +23,10 @@ struct sys_dbg_stack_info_t {
|
||||
size_t commited_size;
|
||||
size_t total_size;
|
||||
size_t code_size;
|
||||
|
||||
// Full stack reservation reported by pthread.
|
||||
uintptr_t reserved_addr;
|
||||
size_t reserved_size;
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
@@ -8,12 +8,56 @@
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <execinfo.h>
|
||||
#include <pthread.h>
|
||||
#include <sys/param.h>
|
||||
#include <sys/types.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) {
|
||||
*depth = 0;
|
||||
// Avoid unwinding a guest-owned stack.
|
||||
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) {
|
||||
@@ -30,6 +74,33 @@ void SysStackUsage(sys_dbg_stack_info_t& s) {
|
||||
|
||||
[[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 str2[1024];
|
||||
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));
|
||||
|
||||
memset(&s, 0, sizeof(sys_dbg_stack_info_t));
|
||||
|
||||
FILE* f = fopen(str, "r");
|
||||
|
||||
if (f == nullptr) {
|
||||
@@ -118,6 +187,11 @@ void SysStackUsage(sys_dbg_stack_info_t& s) {
|
||||
}
|
||||
|
||||
result = fclose(f);
|
||||
|
||||
if (s.reserved_addr == 0) {
|
||||
s.reserved_addr = s.addr;
|
||||
s.reserved_size = s.total_size;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -11,7 +11,11 @@
|
||||
|
||||
#include <cerrno>
|
||||
#include <cstdlib>
|
||||
#include <dirent.h>
|
||||
#include <fcntl.h>
|
||||
#include <filesystem>
|
||||
#include <sys/stat.h>
|
||||
#include <system_error>
|
||||
#include <unistd.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) {
|
||||
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) {
|
||||
if (f.type == SYS_FILE_FILE) {
|
||||
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;
|
||||
}
|
||||
|
||||
sys_file_t* SysFileOpenR(const std::filesystem::path& file_name,
|
||||
sys_file_cache_type_t /*cache_type*/) {
|
||||
sys_file_t* SysFileOpenR(const std::filesystem::path& file_name, sys_file_cache_type_t cache_type) {
|
||||
auto* ret = new sys_file_t;
|
||||
|
||||
ret->type = SYS_FILE_FILE;
|
||||
@@ -150,6 +186,8 @@ sys_file_t* SysFileOpenR(const std::filesystem::path& file_name,
|
||||
ret->type = SYS_FILE_ERROR;
|
||||
}
|
||||
|
||||
apply_cache_hint(f, cache_type);
|
||||
|
||||
ret->f = f;
|
||||
|
||||
return ret;
|
||||
@@ -179,8 +217,7 @@ sys_file_t* SysFileCreate() {
|
||||
return ret;
|
||||
}
|
||||
|
||||
sys_file_t* SysFileOpenW(const std::filesystem::path& file_name,
|
||||
sys_file_cache_type_t /*cache_type*/) {
|
||||
sys_file_t* SysFileOpenW(const std::filesystem::path& file_name, sys_file_cache_type_t cache_type) {
|
||||
auto* ret = new sys_file_t;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
apply_cache_hint(f, cache_type);
|
||||
|
||||
ret->f = f;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
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 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;
|
||||
}
|
||||
|
||||
apply_cache_hint(f, cache_type);
|
||||
|
||||
ret->f = f;
|
||||
|
||||
return ret;
|
||||
@@ -239,11 +280,17 @@ uint64_t SysFileSize(sys_file_t& f) {
|
||||
[[maybe_unused]] int result = 0;
|
||||
|
||||
if (f.type == SYS_FILE_FILE) {
|
||||
uint32_t pos = ftell(f.f);
|
||||
result = fseek(f.f, 0, SEEK_END);
|
||||
uint32_t size = ftell(f.f);
|
||||
result = fseek(f.f, pos, SEEK_SET);
|
||||
return size;
|
||||
// Preserve sizes above 4 GiB.
|
||||
const off_t pos = ftello(f.f);
|
||||
if (pos < 0) {
|
||||
return 0;
|
||||
}
|
||||
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) {
|
||||
@@ -260,8 +307,18 @@ uint64_t SysFileSize(const std::filesystem::path& file_name) {
|
||||
return size;
|
||||
}
|
||||
|
||||
bool SysFileTruncate(sys_file_t& /*f*/, uint64_t /*size*/) {
|
||||
return false;
|
||||
bool SysFileTruncate(sys_file_t& f, uint64_t size) {
|
||||
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) {
|
||||
@@ -382,6 +439,7 @@ SysFileTimeStruct SysFileGetLastAccessTimeUtc(const std::filesystem::path& name)
|
||||
} else {
|
||||
r.is_invalid = false;
|
||||
r.time = s.st_atime;
|
||||
r.nanos = KYTY_STAT_ATIME_NS(s);
|
||||
}
|
||||
|
||||
return r;
|
||||
@@ -400,6 +458,7 @@ SysFileTimeStruct SysFileGetLastWriteTimeUtc(const std::filesystem::path& name)
|
||||
} else {
|
||||
r.is_invalid = false;
|
||||
r.time = s.st_mtime;
|
||||
r.nanos = KYTY_STAT_MTIME_NS(s);
|
||||
}
|
||||
|
||||
return r;
|
||||
@@ -419,13 +478,36 @@ void SysFileGetLastAccessAndWriteTimeUtc(const std::filesystem::path& name, SysF
|
||||
a.is_invalid = false;
|
||||
w.is_invalid = false;
|
||||
a.time = s.st_atime;
|
||||
a.nanos = KYTY_STAT_ATIME_NS(s);
|
||||
w.time = s.st_mtime;
|
||||
w.nanos = KYTY_STAT_MTIME_NS(s);
|
||||
}
|
||||
}
|
||||
|
||||
void SysFileGetLastAccessAndWriteTimeUtc(sys_file_t& /*f*/, SysFileTimeStruct& /*a*/,
|
||||
SysFileTimeStruct& /*w*/) {
|
||||
EXIT("not implemented\n");
|
||||
void SysFileGetLastAccessAndWriteTimeUtc(sys_file_t& f, SysFileTimeStruct& a,
|
||||
SysFileTimeStruct& w) {
|
||||
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) {
|
||||
@@ -531,27 +613,110 @@ bool SysFileSetLastAccessAndWriteTimeUtc(const std::filesystem::path& name,
|
||||
// }
|
||||
}
|
||||
|
||||
void SysFileFindFiles(const std::filesystem::path& /*path*/,
|
||||
std::vector<sys_file_find_t>& /*out*/) {
|
||||
EXIT("not implemented\n");
|
||||
// Recursively collect regular files.
|
||||
void SysFileFindFiles(const std::filesystem::path& path, std::vector<sys_file_find_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)) {
|
||||
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*/) {
|
||||
EXIT("not implemented\n");
|
||||
// Keep "." and ".." to match FindFirstFileW.
|
||||
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*/) {
|
||||
EXIT("not implemented\n");
|
||||
return false;
|
||||
bool SysFileCopyFile(const std::filesystem::path& src, const std::filesystem::path& dst) {
|
||||
std::error_code error;
|
||||
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*/) {
|
||||
EXIT("not implemented\n");
|
||||
return false;
|
||||
bool SysFileMoveFile(const std::filesystem::path& src, const std::filesystem::path& dst) {
|
||||
auto real_src = get_internal_name(src);
|
||||
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*/) {
|
||||
EXIT("not implemented\n");
|
||||
void SysFileRemoveReadonly(const std::filesystem::path& name) {
|
||||
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
|
||||
|
||||
@@ -8,9 +8,16 @@
|
||||
#include "common/platform/sysVirtual.h"
|
||||
#include "common/virtualMemory.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <map>
|
||||
#include <pthread.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.h>
|
||||
@@ -31,8 +38,8 @@ void SysVirtualInit() {
|
||||
pthread_mutexattr_t attr {};
|
||||
|
||||
pthread_mutexattr_init(&attr);
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_LINUX
|
||||
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_FAST_NP);
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_LINUX && !defined(__APPLE__)
|
||||
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_FAST_NP); // glibc-only fast mutex
|
||||
#else
|
||||
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL);
|
||||
#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) {
|
||||
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);
|
||||
|
||||
void* ptr =
|
||||
mmap(reinterpret_cast<void*>(addr), size, protect, MAP_PRIVATE | MAP_ANON, -1, 0); // NOLINT
|
||||
void* ptr = map_anonymous(addr, size, protect, MAP_PRIVATE | MAP_ANON);
|
||||
|
||||
auto ret_addr = reinterpret_cast<uintptr_t>(ptr);
|
||||
|
||||
if (ptr != MAP_FAILED) {
|
||||
pthread_mutex_lock(&g_virtual_mutex);
|
||||
(*g_allocs)[ret_addr] = size;
|
||||
uintptr_t page_start = ret_addr >> 12u;
|
||||
uintptr_t page_end = (ret_addr + size - 1) >> 12u;
|
||||
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] = protect;
|
||||
}
|
||||
@@ -117,18 +187,43 @@ uint64_t SysVirtualAllocAligned(uint64_t address, uint64_t size, VirtualMemory::
|
||||
auto addr = static_cast<uintptr_t>(address);
|
||||
int protect = get_protection_flag(mode);
|
||||
|
||||
void* ptr =
|
||||
mmap(reinterpret_cast<void*>(addr), size, protect, MAP_PRIVATE | MAP_ANON, -1, 0); // NOLINT
|
||||
void* ptr = map_anonymous(addr, size, protect, MAP_PRIVATE | MAP_ANON);
|
||||
|
||||
auto ret_addr = reinterpret_cast<uintptr_t>(ptr);
|
||||
|
||||
if (ptr != MAP_FAILED && ((ret_addr & (alignment - 1)) != 0)) {
|
||||
munmap(ptr, size);
|
||||
|
||||
ptr = mmap(reinterpret_cast<void*>(addr), size + alignment, protect,
|
||||
MAP_PRIVATE | MAP_ANON | MAP_NORESERVE, -1, 0); // NOLINT
|
||||
ptr =
|
||||
map_anonymous(addr, size + alignment, protect, MAP_PRIVATE | MAP_ANON | MAP_NORESERVE);
|
||||
ret_addr = reinterpret_cast<uintptr_t>(ptr);
|
||||
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);
|
||||
auto aligned_addr = align_up(ret_addr, alignment);
|
||||
#ifdef KYTY_FIXED_NOREPLACE
|
||||
@@ -146,6 +241,7 @@ uint64_t SysVirtualAllocAligned(uint64_t address, uint64_t size, VirtualMemory::
|
||||
ret_addr = 0;
|
||||
ptr = MAP_FAILED;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,9 +250,9 @@ uint64_t SysVirtualAllocAligned(uint64_t address, uint64_t size, VirtualMemory::
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&g_virtual_mutex);
|
||||
(*g_allocs)[ret_addr] = size;
|
||||
uintptr_t page_start = ret_addr >> 12u;
|
||||
uintptr_t page_end = (ret_addr + size - 1) >> 12u;
|
||||
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] = protect;
|
||||
}
|
||||
@@ -165,6 +261,27 @@ uint64_t SysVirtualAllocAligned(uint64_t address, uint64_t size, VirtualMemory::
|
||||
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(), ®ion_addr, ®ion_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) {
|
||||
FILE* file = fopen("/proc/self/maps", "r");
|
||||
char line[1024];
|
||||
@@ -189,6 +306,7 @@ static bool is_mapped(void* ptr, size_t length) {
|
||||
fclose(file);
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool SysVirtualAllocFixed(uint64_t address, uint64_t size, VirtualMemory::Mode mode) {
|
||||
EXIT_IF(g_allocs == nullptr);
|
||||
@@ -218,9 +336,9 @@ bool SysVirtualAllocFixed(uint64_t address, uint64_t size, VirtualMemory::Mode m
|
||||
|
||||
if (ptr != MAP_FAILED) {
|
||||
pthread_mutex_lock(&g_virtual_mutex);
|
||||
(*g_allocs)[ret_addr] = size;
|
||||
uintptr_t page_start = ret_addr >> 12u;
|
||||
uintptr_t page_end = (ret_addr + size - 1) >> 12u;
|
||||
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] = protect;
|
||||
}
|
||||
@@ -249,18 +367,44 @@ uint64_t SysVirtualReserveAligned(uint64_t address, uint64_t size, uint64_t alig
|
||||
|
||||
auto addr = static_cast<uintptr_t>(address);
|
||||
|
||||
void* ptr = mmap(reinterpret_cast<void*>(addr), size, PROT_NONE,
|
||||
MAP_PRIVATE | MAP_ANON | MAP_NORESERVE, -1, 0); // NOLINT
|
||||
void* ptr = map_anonymous(addr, size, PROT_NONE, MAP_PRIVATE | MAP_ANON | MAP_NORESERVE);
|
||||
|
||||
auto ret_addr = reinterpret_cast<uintptr_t>(ptr);
|
||||
|
||||
if (ptr != MAP_FAILED && ((ret_addr & (alignment - 1)) != 0)) {
|
||||
munmap(ptr, size);
|
||||
|
||||
ptr = mmap(reinterpret_cast<void*>(addr), size + alignment, PROT_NONE,
|
||||
MAP_PRIVATE | MAP_ANON | MAP_NORESERVE, -1, 0); // NOLINT
|
||||
ptr = map_anonymous(addr, size + alignment, PROT_NONE,
|
||||
MAP_PRIVATE | MAP_ANON | MAP_NORESERVE);
|
||||
ret_addr = reinterpret_cast<uintptr_t>(ptr);
|
||||
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);
|
||||
auto aligned_addr = align_up(ret_addr, alignment);
|
||||
#ifdef KYTY_FIXED_NOREPLACE
|
||||
@@ -278,6 +422,7 @@ uint64_t SysVirtualReserveAligned(uint64_t address, uint64_t size, uint64_t alig
|
||||
ret_addr = 0;
|
||||
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);
|
||||
(*g_allocs)[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;
|
||||
}
|
||||
record_alloc(ret_addr, size);
|
||||
pthread_mutex_unlock(&g_virtual_mutex);
|
||||
|
||||
return ret_addr;
|
||||
@@ -324,12 +464,7 @@ bool SysVirtualReserveFixed(uint64_t address, uint64_t size) {
|
||||
|
||||
if (ptr != MAP_FAILED) {
|
||||
pthread_mutex_lock(&g_virtual_mutex);
|
||||
(*g_allocs)[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;
|
||||
}
|
||||
record_alloc(ret_addr, size);
|
||||
pthread_mutex_unlock(&g_virtual_mutex);
|
||||
|
||||
return true;
|
||||
@@ -339,7 +474,29 @@ bool SysVirtualReserveFixed(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) {
|
||||
@@ -391,15 +548,34 @@ bool SysVirtualFreeRange(uint64_t address, uint64_t size) {
|
||||
pthread_mutex_unlock(&g_virtual_mutex);
|
||||
return false;
|
||||
}
|
||||
auto it = std::prev(next);
|
||||
const auto alloc_addr = it->first;
|
||||
const auto alloc_end = alloc_addr + it->second;
|
||||
if (addr < alloc_addr || end > alloc_end || munmap(reinterpret_cast<void*>(addr), size) != 0) {
|
||||
|
||||
// A reservation may have been split into several adjacent records.
|
||||
auto first = std::prev(next);
|
||||
const auto alloc_addr = first->first;
|
||||
if (addr < alloc_addr || alloc_addr + first->second <= addr) {
|
||||
pthread_mutex_unlock(&g_virtual_mutex);
|
||||
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) {
|
||||
(*g_allocs)[alloc_addr] = addr - alloc_addr;
|
||||
}
|
||||
|
||||
@@ -27,7 +27,9 @@ struct SysFileTimeStruct {
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
FILETIME time;
|
||||
#elif KYTY_PLATFORM == KYTY_PLATFORM_LINUX
|
||||
// Nanoseconds preserve sub-second file timestamps.
|
||||
time_t time;
|
||||
long nanos;
|
||||
#endif
|
||||
bool is_invalid;
|
||||
};
|
||||
@@ -139,7 +141,7 @@ inline void SysFileToSystemTimeUtc(const SysFileTimeStruct& f, SysTimeStruct& t)
|
||||
t.Hour = i.tm_hour;
|
||||
t.Minute = i.tm_min;
|
||||
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) {
|
||||
@@ -168,10 +170,11 @@ inline void SysSystemToFileTimeUtc(const SysTimeStruct& f, SysFileTimeStruct& t)
|
||||
|
||||
// Retrieves the current local date and time.
|
||||
inline void SysGetSystemTime(SysTimeStruct& t) {
|
||||
time_t st {};
|
||||
// Preserve millisecond precision.
|
||||
timespec now {};
|
||||
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;
|
||||
return;
|
||||
}
|
||||
@@ -183,15 +186,16 @@ inline void SysGetSystemTime(SysTimeStruct& t) {
|
||||
t.Hour = i.tm_hour;
|
||||
t.Minute = i.tm_min;
|
||||
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).
|
||||
inline void SysGetSystemTimeUtc(SysTimeStruct& t) {
|
||||
time_t st {};
|
||||
// Preserve millisecond precision.
|
||||
timespec now {};
|
||||
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;
|
||||
return;
|
||||
}
|
||||
@@ -203,7 +207,7 @@ inline void SysGetSystemTimeUtc(SysTimeStruct& t) {
|
||||
t.Hour = i.tm_hour;
|
||||
t.Minute = i.tm_min;
|
||||
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) {
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cerrno>
|
||||
#include <chrono> // IWYU pragma: keep
|
||||
#include <condition_variable> // IWYU pragma: keep
|
||||
#include <mutex>
|
||||
@@ -14,6 +15,12 @@
|
||||
#define KYTY_WIN_CS
|
||||
#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 <string>
|
||||
#include <thread>
|
||||
@@ -121,6 +128,42 @@ static SleepConditionVariableCS_func_t ResolveSleepConditionVariableCS() {
|
||||
|
||||
#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 {
|
||||
|
||||
using thread_id_t = std::thread::id;
|
||||
@@ -249,6 +292,8 @@ void Thread::Sleep(uint32_t millis) {
|
||||
void Thread::SleepMicro(uint32_t micros) {
|
||||
#ifdef KYTY_WIN_CS
|
||||
SleepHighResolution100ns(static_cast<uint64_t>(micros) * 10);
|
||||
#elif defined(KYTY_POSIX_HIGH_RES_SLEEP)
|
||||
SleepHighResolutionNanos(static_cast<uint64_t>(micros) * 1000);
|
||||
#else
|
||||
std::this_thread::sleep_for(std::chrono::microseconds(micros));
|
||||
#endif
|
||||
@@ -257,6 +302,8 @@ void Thread::SleepMicro(uint32_t micros) {
|
||||
void Thread::SleepNano(uint64_t nanos) {
|
||||
#ifdef KYTY_WIN_CS
|
||||
SleepHighResolution100ns((nanos + 99) / 100);
|
||||
#elif defined(KYTY_POSIX_HIGH_RES_SLEEP)
|
||||
SleepHighResolutionNanos(nanos);
|
||||
#else
|
||||
std::this_thread::sleep_for(std::chrono::nanoseconds(nanos));
|
||||
#endif
|
||||
|
||||
@@ -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_
|
||||
@@ -58,25 +58,6 @@ bool FlushInstructionCache(uint64_t address, uint64_t 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 Common
|
||||
|
||||
@@ -37,7 +37,6 @@ bool Free(uint64_t address);
|
||||
bool FreeRange(uint64_t address, uint64_t size);
|
||||
bool Protect(uint64_t address, uint64_t size, Mode mode, Mode* old_mode = nullptr);
|
||||
bool FlushInstructionCache(uint64_t address, uint64_t size);
|
||||
bool PatchReplace(uint64_t vaddr, uint64_t value);
|
||||
|
||||
} // namespace VirtualMemory
|
||||
|
||||
|
||||
@@ -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
@@ -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());
|
||||
|
||||
auto* slist = Common::SubsystemsList::Instance();
|
||||
@@ -127,12 +127,21 @@ static void Init(const Config::ConfigOptions& cfg) {
|
||||
slist->InitAll(true);
|
||||
|
||||
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(controller, {core, log, config});
|
||||
slist->Add(file_system, {core, log, pthread});
|
||||
slist->Add(graphics, {core, log, pthread, memory, config, profiler, controller});
|
||||
slist->Add(log, {core, config});
|
||||
slist->Add(memory, {core, log});
|
||||
slist->Add(network, {core, log, pthread});
|
||||
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() {
|
||||
int thread_model = 1;
|
||||
|
||||
if (thread_model == 0) {
|
||||
Common::Thread t([](void* /*unused*/) { Libs::Graphics::WindowRun(); }, nullptr);
|
||||
t.Detach();
|
||||
auto* rt = Common::Singleton<Loader::RuntimeLinker>::Instance();
|
||||
rt->Execute();
|
||||
} else {
|
||||
Common::Thread t(
|
||||
[](void* /*unused*/) {
|
||||
auto* rt = Common::Singleton<Loader::RuntimeLinker>::Instance();
|
||||
rt->Execute();
|
||||
},
|
||||
nullptr);
|
||||
t.Detach();
|
||||
Libs::Graphics::WindowRun();
|
||||
t.Join();
|
||||
}
|
||||
static void Execute(const std::filesystem::path& game_patch) {
|
||||
auto patch_path = game_patch;
|
||||
Common::Thread guest_thread(
|
||||
[](void* param) {
|
||||
auto* rt = Common::Singleton<Loader::RuntimeLinker>::Instance();
|
||||
rt->Execute(*static_cast<const std::filesystem::path*>(param));
|
||||
},
|
||||
&patch_path);
|
||||
Libs::Graphics::WindowRun();
|
||||
std::quick_exit(0);
|
||||
}
|
||||
|
||||
void Run(const RunOptions& options) {
|
||||
@@ -189,7 +189,8 @@ void Run(const RunOptions& options) {
|
||||
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();
|
||||
|
||||
@@ -201,15 +202,6 @@ void Run(const RunOptions& options) {
|
||||
Libs::LibKernel::FileSystem::Mount(options.app0_dir, "/app0");
|
||||
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();
|
||||
|
||||
auto* rt = Common::Singleton<Loader::RuntimeLinker>::Instance();
|
||||
@@ -217,7 +209,7 @@ void Run(const RunOptions& options) {
|
||||
|
||||
LoadElf(options.elf);
|
||||
|
||||
Execute();
|
||||
Execute(options.game_patch);
|
||||
}
|
||||
|
||||
} // namespace Emulator
|
||||
|
||||
@@ -12,6 +12,7 @@ struct RunOptions {
|
||||
Config::ConfigOptions config;
|
||||
std::filesystem::path app0_dir;
|
||||
std::filesystem::path elf;
|
||||
std::filesystem::path game_patch;
|
||||
};
|
||||
|
||||
void Run(const RunOptions& options);
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
#message("${GIT_EXECUTABLE}")
|
||||
set(KYTY_GIT_VERSION "unknown")
|
||||
if(GIT_EXECUTABLE)
|
||||
execute_process(COMMAND ${GIT_EXECUTABLE} describe --tags --always OUTPUT_VARIABLE KYTY_GIT_VERSION)
|
||||
else()
|
||||
set(KYTY_GIT_VERSION "unknown")
|
||||
execute_process(
|
||||
COMMAND "${GIT_EXECUTABLE}" describe --tags --always
|
||||
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()
|
||||
string(STRIP ${KYTY_GIT_VERSION} KYTY_GIT_VERSION)
|
||||
configure_file(${INPUT_FILE} ${OUTPUT_FILE})
|
||||
configure_file("${INPUT_FILE}" "${OUTPUT_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
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/threads.h"
|
||||
#include "graphics/guest_gpu/hardwareContext.h"
|
||||
#include "graphics/host_gpu/renderer/render.h"
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
inline constexpr uint32_t AcquireGcrGl2Writeback = 1u << 15u;
|
||||
|
||||
bool TestWaitRegMemValue(uint64_t value, uint64_t ref, uint64_t mask, uint32_t func);
|
||||
|
||||
class CommandScheduler {
|
||||
enum class Pm4ProcessResult { Complete, Blocked };
|
||||
|
||||
class Pm4Execution {
|
||||
public:
|
||||
static constexpr int BuffersNum = 8;
|
||||
|
||||
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();
|
||||
}
|
||||
[[nodiscard]] bool MadeProgress() const noexcept { return m_made_progress; }
|
||||
|
||||
private:
|
||||
CommandBuffer* SubmitCurrent() {
|
||||
auto* submitted = Current();
|
||||
submitted->End();
|
||||
submitted->Execute();
|
||||
return submitted;
|
||||
}
|
||||
friend class CommandProcessor;
|
||||
|
||||
void BeginNext() {
|
||||
m_current = (m_current + 1) % BuffersNum;
|
||||
Current()->WaitForFenceAndReset();
|
||||
Current()->Begin();
|
||||
}
|
||||
struct BufferCursor {
|
||||
uint32_t* next_packet = nullptr;
|
||||
uint32_t remaining_dw = 0;
|
||||
uint32_t total_dw = 0;
|
||||
uint32_t deferred_advance_dw = 0;
|
||||
};
|
||||
|
||||
CommandBuffer* m_buffers[BuffersNum] = {};
|
||||
int m_current = -1;
|
||||
int m_queue = -1;
|
||||
std::vector<BufferCursor> m_buffer_stack;
|
||||
bool m_suspended = false;
|
||||
bool m_made_progress = false;
|
||||
};
|
||||
|
||||
class CommandProcessor {
|
||||
@@ -113,8 +43,8 @@ public:
|
||||
int64_t flip_arg = 0;
|
||||
};
|
||||
|
||||
CommandProcessor() = default;
|
||||
~CommandProcessor() { KYTY_NOT_IMPLEMENTED; }
|
||||
explicit CommandProcessor(RenderContext& renderer): m_renderer(renderer) {}
|
||||
~CommandProcessor() = default;
|
||||
|
||||
KYTY_CLASS_NO_COPY(CommandProcessor);
|
||||
|
||||
@@ -125,29 +55,21 @@ public:
|
||||
void BufferFlushAndWait();
|
||||
void BufferWait();
|
||||
void BeginReadbackTransaction() {
|
||||
m_mutex.Lock();
|
||||
if (m_readback_active) {
|
||||
EXIT("nested command-processor readback transaction\n");
|
||||
}
|
||||
m_readback_active = true;
|
||||
m_readback_finished = false;
|
||||
m_readback_active = true;
|
||||
}
|
||||
void FinishReadbackTransaction();
|
||||
void EndReadbackTransaction() {
|
||||
if (!m_readback_active) {
|
||||
EXIT("command-processor readback transaction is not active\n");
|
||||
}
|
||||
m_readback_active = false;
|
||||
m_readback_finished = false;
|
||||
m_mutex.Unlock();
|
||||
m_readback_active = false;
|
||||
}
|
||||
|
||||
void RunLock() { m_run_mutex.Lock(); }
|
||||
void RunUnlock() { m_run_mutex.Unlock(); }
|
||||
|
||||
HW::Context* GetCtx() { return &m_ctx; }
|
||||
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 SetIndexBaseAddress(uint64_t index_base_addr);
|
||||
@@ -179,12 +101,10 @@ public:
|
||||
void Flip(void* dst_gpu_addr, uint32_t value);
|
||||
void FlipWithInterrupt(uint32_t eop_event_type, uint32_t cache_action, void* dst_gpu_addr,
|
||||
uint32_t value);
|
||||
void PrepareCpuFlip();
|
||||
void PrepareCpuFlip(uint64_t request_id);
|
||||
void SynchronizeGpu();
|
||||
void MemoryBarrier();
|
||||
void EmitGlobalBarrier();
|
||||
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,
|
||||
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; }
|
||||
[[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 SetCeComplete(bool complete) { m_ce_complete = complete; }
|
||||
void WaitCe();
|
||||
void WaitDeDiff(uint32_t diff);
|
||||
void IncremenetDe();
|
||||
void IncremenetCe();
|
||||
void IncrementDe();
|
||||
void IncrementCe();
|
||||
|
||||
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);
|
||||
@@ -219,38 +136,29 @@ public:
|
||||
const volatile void* address, uint32_t count_in_dwords);
|
||||
[[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);
|
||||
[[nodiscard]] int GetQueue() const { return m_scheduler.Queue(); }
|
||||
|
||||
[[nodiscard]] const FlipInfo& GetFlip() const { return m_flip; }
|
||||
void SetFlip(const FlipInfo& flip) { m_flip = flip; }
|
||||
void SetFlip(const FlipInfo& flip) { m_flip = flip; }
|
||||
|
||||
[[nodiscard]] uint64_t GetSubmitId() const { return m_submit_id; }
|
||||
void SetSubmitId(uint64_t submit_id) { m_submit_id = submit_id; }
|
||||
|
||||
private:
|
||||
struct Counter {
|
||||
Common::Mutex mutex;
|
||||
Common::CondVar cond_var;
|
||||
uint32_t value = 0;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
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,
|
||||
void* dst_gpu_addr, T value, uint32_t interrupt_selector,
|
||||
uint32_t interrupt_context_id);
|
||||
void FinishCommandProcessors();
|
||||
void ProcessPm4(Pm4Execution& execution, size_t stop_depth);
|
||||
void SuspendPm4();
|
||||
|
||||
CommandBuffer* CurrentBuffer() { return m_scheduler.Current(); }
|
||||
void CheckBuffer() const { m_scheduler.CheckActive(); }
|
||||
GpuResourceManager* GetGpuResources() const {
|
||||
EXIT_IF(g_render_ctx == nullptr);
|
||||
return g_render_ctx->GetGpuResources();
|
||||
}
|
||||
CommandScheduler& GetScheduler() const { return m_renderer.GetCommandScheduler(); }
|
||||
RenderCommandBuffer& CurrentBuffer() { return GetScheduler().Current(); }
|
||||
void CheckBuffer() const { GetScheduler().CheckActive(); }
|
||||
GpuResourceManager& GetGpuResources() const { return m_renderer.GetGpuResources(); }
|
||||
|
||||
RenderContext& m_renderer;
|
||||
HW::Context m_ctx;
|
||||
HW::UserConfig m_ucfg;
|
||||
HW::Shader m_sh_ctx;
|
||||
@@ -262,16 +170,10 @@ private:
|
||||
uint64_t m_dispatch_indirect_args_base_addr = 0;
|
||||
uint32_t m_num_instances = 1;
|
||||
|
||||
inline static Common::Mutex m_mutex;
|
||||
inline static std::array<CommandProcessor*, GraphicContext::QUEUES_NUM> m_processors {};
|
||||
inline static bool m_readback_active = false;
|
||||
inline static bool m_readback_finished = false;
|
||||
Common::Mutex m_run_mutex;
|
||||
|
||||
CommandScheduler m_scheduler;
|
||||
|
||||
Counter m_de_counter;
|
||||
Counter m_ce_counter;
|
||||
uint32_t m_de_count = 0;
|
||||
uint32_t m_ce_count = 0;
|
||||
bool m_ce_complete = false;
|
||||
bool m_readback_active = false;
|
||||
|
||||
uint32_t m_const_ram[0x3000] = {0};
|
||||
|
||||
|
||||
@@ -10,17 +10,17 @@ namespace Libs::Graphics {
|
||||
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
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_sh_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_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 hw_ctx_indirect_func_t g_hw_ctx_indirect_func[Pm4::CX_NUM];
|
||||
@@ -36,136 +36,136 @@ void GraphicsInitJmpTablesCxIndirect();
|
||||
void GraphicsInitJmpTablesShIndirect();
|
||||
void GraphicsInitJmpTablesUcIndirect();
|
||||
|
||||
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 HwCtxSetRenderControl(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwCtxSetDepthMetadataRegisters(CommandProcessor&, uint32_t, uint32_t, const 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 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 HwCtxSetScreenScissor(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwCtxSetDepthRenderTarget(CommandProcessor*, uint32_t, uint32_t, const 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 HwCtxSetScreenScissor(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwCtxSetDepthRenderTarget(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 HwCtxSetStencilInfo(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwCtxSetDepthHtileSurface(CommandProcessor&, uint32_t, uint32_t, const 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 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 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 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 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 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 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 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 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 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 HwCtxSetPolyOffsetRegisters(CommandProcessor*, uint32_t, uint32_t, const 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 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 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 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 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 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 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 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 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 HwCtxSetPolyOffsetRegisters(CommandProcessor&, uint32_t, uint32_t, const 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 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 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 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 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 HwCtxSetVtxControl(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwCtxSetPaScExtendedControl(CommandProcessor*, uint32_t, uint32_t, const 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 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 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 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 HwCtxSetVtxControl(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwCtxSetPaScExtendedControl(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 HwCtxSetAlphaToMask(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwCtxSetDrawPayloadControl(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 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 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 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 HwCtxSetViewportZ(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwCtxSetViewportScaleOffset(CommandProcessor*, uint32_t, uint32_t, const 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 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 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 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 HwCtxSetViewportZ(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwCtxSetViewportScaleOffset(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 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 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 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 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 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 HwUcSetObjectId(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwUcSetTextureGradientFactors(CommandProcessor*, uint32_t, uint32_t, const 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 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 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 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 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 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 HwUcSetObjectId(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwUcSetTextureGradientFactors(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 HwUcSetBorderColorTableAddr(CommandProcessor*, uint32_t, uint32_t, const 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 HwUcSetBorderColorTableAddr(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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 CpOpFlip(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
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -72,6 +72,7 @@ enum class ChannelLayout : uint32_t {
|
||||
k32_32 = 11,
|
||||
k16_16_16_16 = 12,
|
||||
k32_32_32_32 = 14,
|
||||
k5_6_5 = 16,
|
||||
k5_5_5_1 = 17,
|
||||
k4_4_4_4 = 19,
|
||||
kBc1 = 35,
|
||||
@@ -142,6 +143,24 @@ enum class StencilFormat : uint32_t {
|
||||
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 {
|
||||
kLinear = 0x00,
|
||||
kStandard256B = 0x01,
|
||||
@@ -356,6 +375,8 @@ enum class BufferFormat : uint32_t {
|
||||
k32_32_32_32UInt = 75,
|
||||
k32_32_32_32SInt = 76,
|
||||
k32_32_32_32Float = 77,
|
||||
k8Srgb = 128,
|
||||
k8_8Srgb = 129,
|
||||
k8_8_8_8Srgb = 130,
|
||||
k9_9_9_5Float = 132,
|
||||
k5_6_5UNorm = 133,
|
||||
|
||||
@@ -39,6 +39,7 @@ constexpr FormatInfo kFormatInfo[] = {
|
||||
{GpuEnumValue(BufferFormat::k16_16Float), 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_2UInt), 4, 0, 4, true, true},
|
||||
{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_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_32SInt), 16, 0, 16, false, 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::k9_9_9_5Float), 4, 0, 0, true, false},
|
||||
{GpuEnumValue(BufferFormat::k5_6_5UNorm), 2, 0, 2, true, false},
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,33 +3,59 @@
|
||||
|
||||
#include "common/abi.h"
|
||||
#include "common/common.h"
|
||||
#include "common/uniqueFunction.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
class CommandProcessor;
|
||||
class GpuState;
|
||||
class RenderContext;
|
||||
|
||||
class GraphicsRunSubmissionLock final {
|
||||
class Gpu final {
|
||||
public:
|
||||
GraphicsRunSubmissionLock();
|
||||
~GraphicsRunSubmissionLock();
|
||||
KYTY_CLASS_NO_COPY(GraphicsRunSubmissionLock);
|
||||
explicit Gpu(RenderContext& renderer);
|
||||
~Gpu();
|
||||
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
|
||||
|
||||
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_GRAPHICSRUN_H_ */
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include "common/abi.h"
|
||||
#include "common/common.h"
|
||||
#include "graphics/guest_gpu/gpu_defs.h"
|
||||
|
||||
namespace Libs::Graphics::HW {
|
||||
|
||||
@@ -131,26 +132,66 @@ struct RenderTarget {
|
||||
};
|
||||
|
||||
struct DepthZInfo {
|
||||
uint32_t format = 0;
|
||||
uint32_t tile_mode_index = 0;
|
||||
uint32_t num_samples = 0;
|
||||
uint32_t zrange_precision = 0;
|
||||
bool tile_surface_enable = false;
|
||||
bool expclear_enabled = false;
|
||||
bool embedded_sample_locations = false;
|
||||
bool partially_resident = false;
|
||||
uint8_t num_mip_levels = 0;
|
||||
uint8_t plane_compression = 0;
|
||||
uint32_t format = 0;
|
||||
uint32_t num_samples = 0;
|
||||
Prospero::TextureCompatiblePlaneCompression texture_compatibility =
|
||||
Prospero::TextureCompatiblePlaneCompression::kDisable;
|
||||
Prospero::ZCompareBase z_compare_base = Prospero::ZCompareBase::kZMin;
|
||||
bool htile_acceleration = false;
|
||||
bool expclear_enabled = false;
|
||||
bool partially_resident = false;
|
||||
uint8_t max_mip_level = 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 {
|
||||
uint32_t format = 0;
|
||||
uint32_t tile_mode_index = 0;
|
||||
uint32_t tile_split = 0;
|
||||
bool expclear_enabled = false;
|
||||
bool tile_stencil_disable = false;
|
||||
bool texture_compatible_stencil = false;
|
||||
bool partially_resident = false;
|
||||
uint32_t format = 0;
|
||||
Prospero::TextureCompatibleStencil texture_compatibility =
|
||||
Prospero::TextureCompatibleStencil::kDisable;
|
||||
bool expclear_enabled = false;
|
||||
bool htile_stencil_disabled = 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 {
|
||||
@@ -253,6 +294,11 @@ struct ClipControl {
|
||||
bool cull_on_clipping_error_disable = false;
|
||||
bool linear_attribute_clip_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 {
|
||||
|
||||
@@ -110,7 +110,6 @@ void DumpPm4PacketStream(Common::File* file, uint32_t* cmd_buffer, uint32_t star
|
||||
auto* cmd = cmd_buffer + start_dw;
|
||||
auto dw = num_dw;
|
||||
while (dw != 0) {
|
||||
EXIT_NOT_IMPLEMENTED(dw < 2);
|
||||
EXIT_NOT_IMPLEMENTED(dw > num_dw);
|
||||
|
||||
auto cmd_id = *cmd++;
|
||||
@@ -120,6 +119,9 @@ void DumpPm4PacketStream(Common::File* file, uint32_t* cmd_buffer, uint32_t star
|
||||
uint32_t len = 0;
|
||||
|
||||
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) {
|
||||
case PacketType::Type3: {
|
||||
const bool sh_gx = (cmd_id & 0x2u) == 0;
|
||||
|
||||
@@ -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_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_COLOR_SRCBLEND_SHIFT = 0;
|
||||
constexpr uint32_t CB_BLEND0_CONTROL_COLOR_SRCBLEND_MASK = 0x1F;
|
||||
|
||||
+614
-1967
File diff suppressed because it is too large
Load Diff
@@ -6,15 +6,6 @@
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
enum class TileMode {
|
||||
VideoOutLinear,
|
||||
VideoOutTiled,
|
||||
TextureLinear,
|
||||
TextureTiled,
|
||||
// RenderTextureLinear,
|
||||
// RenderTextureTiled,
|
||||
};
|
||||
|
||||
struct TileSizeAlign {
|
||||
uint32_t size = 0;
|
||||
uint32_t align = 0;
|
||||
@@ -34,74 +25,82 @@ struct TilePaddedSize {
|
||||
uint32_t height = 0;
|
||||
};
|
||||
|
||||
void TileInit();
|
||||
void TileConvertTiledToLinear(void* dst, const void* src, TileMode mode, uint32_t width,
|
||||
uint32_t height);
|
||||
void TileConvertTiledToLinearRenderTarget(void* dst, const void* src, uint32_t width,
|
||||
uint32_t height, uint32_t pitch,
|
||||
uint32_t bytes_per_element, uint64_t size,
|
||||
uint64_t src_size = 0, uint32_t src_x = 0,
|
||||
uint32_t src_y = 0);
|
||||
void TileConvertLinearToTiledRenderTarget(void* dst, const void* src, uint32_t width,
|
||||
uint32_t height, uint32_t pitch,
|
||||
uint32_t bytes_per_element, uint64_t size,
|
||||
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,
|
||||
uint32_t width, uint32_t height, uint32_t pitch,
|
||||
uint64_t size, uint64_t src_size = 0, uint32_t src_x = 0,
|
||||
uint32_t src_y = 0);
|
||||
void TileConvertTiledToLinearStandard64KB32(void* dst, const void* src, uint32_t width,
|
||||
uint32_t height, uint32_t pitch, uint64_t size,
|
||||
uint64_t src_size = 0, uint32_t src_x = 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);
|
||||
void TileConvertTiledToLinearStandard64KB16(void* dst, const void* src, uint32_t width,
|
||||
uint32_t height, uint32_t pitch, uint64_t size,
|
||||
uint64_t src_size = 0, uint32_t src_x = 0,
|
||||
uint32_t src_y = 0);
|
||||
void TileConvertTiledToLinearDepth(void* dst, const void* src, uint32_t format, uint32_t width,
|
||||
uint32_t height, uint32_t pitch, uint64_t size);
|
||||
void TileConvertLinearToTiledDepth(void* dst, const void* src, uint32_t format, uint32_t width,
|
||||
uint32_t height, uint32_t pitch, uint64_t size);
|
||||
void TileConvertTiledToLinearStandard4KB(void* dst, const void* src, uint32_t format,
|
||||
uint32_t width, uint32_t height, uint32_t pitch,
|
||||
uint64_t dst_size, uint64_t src_size, uint32_t src_x = 0,
|
||||
uint32_t src_y = 0);
|
||||
void TileConvertTiledToLinearStandard256B(void* dst, const void* src, uint32_t format,
|
||||
uint32_t width, uint32_t height, uint32_t pitch,
|
||||
uint64_t dst_size, uint64_t src_size);
|
||||
enum class TileBlockFamily : uint32_t {
|
||||
Standard256B,
|
||||
Standard4KB,
|
||||
Standard4KB3D,
|
||||
Standard64KB,
|
||||
Standard64KB3D,
|
||||
Prt64KB,
|
||||
Prt64KB3D,
|
||||
RenderTarget64KB,
|
||||
Depth64KB,
|
||||
Count,
|
||||
};
|
||||
|
||||
struct TileBlockLayout {
|
||||
TileBlockFamily family = TileBlockFamily::Standard256B;
|
||||
uint32_t bytes_per_element = 0;
|
||||
uint32_t block_size = 0;
|
||||
uint32_t block_width = 0;
|
||||
uint32_t block_height = 0;
|
||||
uint32_t block_depth = 0;
|
||||
};
|
||||
|
||||
struct TileVolumeLayout {
|
||||
TileBlockFamily family = TileBlockFamily::Count;
|
||||
uint32_t bytes_per_element = 0;
|
||||
uint32_t texel_width = 1;
|
||||
uint32_t texel_height = 1;
|
||||
uint32_t first_tail_level = 0;
|
||||
uint32_t block_depth = 1;
|
||||
uint64_t block_slice_size = 0;
|
||||
uint64_t total_size = 0;
|
||||
uint64_t level_offsets[16] = {};
|
||||
uint64_t level_sizes[16] = {};
|
||||
uint32_t tail_x[16] = {};
|
||||
uint32_t tail_y[16] = {};
|
||||
uint32_t level_widths[16] = {};
|
||||
uint32_t level_heights[16] = {};
|
||||
};
|
||||
|
||||
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 TileIsStandard4KBTextureSupported(uint32_t format);
|
||||
bool TileIsStandard64KBTextureSupported(uint32_t format);
|
||||
bool TileGetStandard4KBVolumeLayout(uint32_t format, uint32_t* bytes_per_element,
|
||||
uint32_t* texels_per_element_wide,
|
||||
uint32_t* texels_per_element_tall, uint32_t* block_width_log2,
|
||||
uint32_t* block_height_log2, uint32_t* block_depth_log2);
|
||||
bool TileGetTextureVolumeLayout(uint32_t format, uint32_t width, uint32_t height, uint32_t depth,
|
||||
uint32_t levels, uint32_t tile, TileVolumeLayout& layout);
|
||||
|
||||
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,
|
||||
uint32_t stencil_format, bool htile, TileSizeAlign* stencil_size,
|
||||
TileSizeAlign* htile_size, TileSizeAlign* depth_size);
|
||||
uint32_t TileGetRenderTargetPitch(uint32_t width, uint32_t bytes_per_element);
|
||||
uint32_t stencil_format, bool htile, TileSizeAlign& stencil_size,
|
||||
TileSizeAlign& htile_size, TileSizeAlign& depth_size,
|
||||
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,
|
||||
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,
|
||||
uint32_t bytes_per_element, uint32_t levels,
|
||||
TileSizeAlign* total_size, TileSizeOffset* level_sizes,
|
||||
TileSizeAlign& total_size, TileSizeOffset* level_sizes,
|
||||
TilePaddedSize* padded_size);
|
||||
void TileGetTextureSize(uint32_t format, uint32_t width, uint32_t height, uint32_t pitch,
|
||||
uint32_t levels, uint32_t tile, TileSizeAlign* total_size,
|
||||
TileSizeOffset* level_sizes, TilePaddedSize* padded_size);
|
||||
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,
|
||||
TileSizeAlign* total_size);
|
||||
TileSizeAlign& total_size);
|
||||
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
|
||||
|
||||
|
||||
@@ -3,144 +3,76 @@
|
||||
|
||||
#include "common/abi.h"
|
||||
#include "common/common.h"
|
||||
#include "common/threads.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h" // IWYU pragma: export
|
||||
#include "graphics/host_gpu/vulkanInstance.h"
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
#include <vk_mem_alloc.h>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
class CommandBuffer;
|
||||
|
||||
struct VulkanSwapchain {
|
||||
~VulkanSwapchain();
|
||||
|
||||
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 VulkanBuffer;
|
||||
struct VulkanImage;
|
||||
struct VulkanMemory;
|
||||
|
||||
struct GraphicContext: public VulkanInstance {
|
||||
uint32_t screen_width = 0;
|
||||
uint32_t screen_height = 0;
|
||||
std::array<Common::Mutex, QUEUES_NUM> queue_mutexes;
|
||||
[[nodiscard]] bool CreateAllocator();
|
||||
void DestroyAllocator();
|
||||
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 {
|
||||
vk::MemoryRequirements requirements = {};
|
||||
vk::MemoryPropertyFlags property = {};
|
||||
vk::DeviceMemory memory = nullptr;
|
||||
VmaAllocation allocation = nullptr;
|
||||
VmaAllocationInfo allocation_info = {};
|
||||
vk::DeviceSize offset = 0;
|
||||
uint32_t type = 0;
|
||||
uint64_t unique_id = 0;
|
||||
vk::MemoryRequirements requirements = {};
|
||||
vk::MemoryPropertyFlags property = {};
|
||||
vk::MemoryPropertyFlags preferred_property = {};
|
||||
vk::DeviceMemory memory = nullptr;
|
||||
VmaAllocation allocation = nullptr;
|
||||
VmaAllocationInfo allocation_info = {};
|
||||
vk::DeviceSize offset = 0;
|
||||
uint32_t type = 0;
|
||||
uint64_t unique_id = 0;
|
||||
};
|
||||
|
||||
enum class VulkanImageType {
|
||||
Unknown,
|
||||
VideoOut,
|
||||
DepthStencil,
|
||||
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 VulkanImageState {
|
||||
vk::PipelineStageFlags2 pl_stage = vk::PipelineStageFlagBits2::eAllCommands;
|
||||
vk::AccessFlags2 access_mask = vk::AccessFlagBits2::eNone;
|
||||
vk::ImageLayout layout = vk::ImageLayout::eUndefined;
|
||||
};
|
||||
|
||||
struct VulkanImage {
|
||||
static constexpr int VIEW_MAX = 4;
|
||||
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) {}
|
||||
VulkanImage() = default;
|
||||
KYTY_CLASS_NO_COPY(VulkanImage);
|
||||
|
||||
VulkanImageType type = VulkanImageType::Unknown;
|
||||
vk::Format format = vk::Format::eUndefined;
|
||||
vk::Extent2D extent = {};
|
||||
uint32_t guest_pitch = 0;
|
||||
uint32_t layers = 1;
|
||||
uint32_t mip_levels = 1;
|
||||
vk::Image image = nullptr;
|
||||
vk::ImageView image_view[VIEW_MAX] = {};
|
||||
vk::ImageLayout layout = vk::ImageLayout::eUndefined;
|
||||
Graphics::VulkanMemory memory;
|
||||
ImageViewCache view_cache;
|
||||
};
|
||||
|
||||
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) {}
|
||||
vk::Format format = vk::Format::eUndefined;
|
||||
vk::ImageType image_type = vk::ImageType::e2D;
|
||||
vk::Extent3D extent = {1, 1, 1};
|
||||
uint32_t guest_pitch = 0;
|
||||
uint32_t layers = 1;
|
||||
uint32_t mip_levels = 1;
|
||||
uint32_t samples = 1;
|
||||
vk::ImageUsageFlags usage = {};
|
||||
vk::ImageCreateFlags flags = {};
|
||||
vk::Image image = nullptr;
|
||||
VulkanImageState state;
|
||||
std::vector<VulkanImageState> subresource_states;
|
||||
Graphics::VulkanMemory memory;
|
||||
};
|
||||
|
||||
struct VulkanBuffer {
|
||||
@@ -150,8 +82,6 @@ struct VulkanBuffer {
|
||||
uint64_t buffer_size = 0;
|
||||
};
|
||||
|
||||
struct StorageVulkanBuffer: public VulkanBuffer {};
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_GRAPHICCONTEXT_H_ */
|
||||
|
||||
@@ -30,11 +30,8 @@ bool IsAccessible(DWORD protect, HostMemoryAccess access) {
|
||||
} // namespace
|
||||
|
||||
bool HostMemoryQueryRange(uint64_t addr, uint64_t requested_size, HostMemoryAccess access,
|
||||
uint64_t* accessible_size) {
|
||||
if (accessible_size == nullptr) {
|
||||
return false;
|
||||
}
|
||||
*accessible_size = 0;
|
||||
uint64_t& accessible_size) {
|
||||
accessible_size = 0;
|
||||
if (addr == 0 || requested_size == 0) {
|
||||
return false;
|
||||
}
|
||||
@@ -87,11 +84,11 @@ bool HostMemoryQueryRange(uint64_t addr, uint64_t requested_size, HostMemoryAcce
|
||||
(void)access;
|
||||
#endif
|
||||
|
||||
*accessible_size = current - addr;
|
||||
return *accessible_size != 0;
|
||||
accessible_size = current - addr;
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -104,7 +101,7 @@ bool HostMemoryRangeIsReadable(uint64_t addr, uint64_t size) {
|
||||
return false;
|
||||
}
|
||||
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
|
||||
|
||||
@@ -8,8 +8,8 @@ namespace Libs::Graphics {
|
||||
enum class HostMemoryAccess { Read, Mapped };
|
||||
|
||||
bool HostMemoryQueryRange(uint64_t addr, uint64_t requested_size, HostMemoryAccess access,
|
||||
uint64_t* accessible_size);
|
||||
bool HostMemoryQueryReadable(uint64_t addr, uint64_t requested_size, uint64_t* readable_size);
|
||||
uint64_t& accessible_size);
|
||||
bool HostMemoryQueryReadable(uint64_t addr, uint64_t requested_size, uint64_t& readable_size);
|
||||
bool HostMemoryIsReadable(uint64_t addr);
|
||||
bool HostMemoryRangeIsReadable(uint64_t addr, uint64_t size);
|
||||
|
||||
|
||||
@@ -4,25 +4,9 @@
|
||||
|
||||
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);
|
||||
|
||||
MemoryTracker::MemoryTracker(PageManager& page_manager, PageWatchMode gpu_watch_mode)
|
||||
: m_page_manager(page_manager), m_gpu_watch_mode(gpu_watch_mode) {
|
||||
switch (m_gpu_watch_mode) {
|
||||
case PageWatchMode::Write:
|
||||
case PageWatchMode::ReadWrite: break;
|
||||
default: EXIT("unsupported memory tracker GPU page-watch mode\n");
|
||||
}
|
||||
MemoryTracker::MemoryTracker(PageManager& page_manager): m_page_manager(page_manager) {
|
||||
m_regions = std::make_unique<std::atomic<RegionManager*>[]>(REGION_COUNT);
|
||||
for (size_t i = 0; i < REGION_COUNT; i++) {
|
||||
m_regions[i].store(nullptr, std::memory_order_relaxed);
|
||||
@@ -31,6 +15,46 @@ MemoryTracker::MemoryTracker(PageManager& page_manager, PageWatchMode gpu_watch_
|
||||
|
||||
MemoryTracker::~MemoryTracker() = default;
|
||||
|
||||
#if KYTY_BUILD == KYTY_BUILD_DEBUG
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void MemoryTracker::ValidateRange(uint64_t vaddr, uint64_t size) {
|
||||
if (vaddr == 0 || size == 0 || vaddr >= TRACKER_ADDRESS_SIZE ||
|
||||
size > TRACKER_ADDRESS_SIZE - vaddr) {
|
||||
@@ -56,7 +80,6 @@ RegionManager* MemoryTracker::GetOrCreateRegion(uint64_t index) {
|
||||
bool MemoryTracker::IsRegionCpuModified(uint64_t vaddr, uint64_t size) {
|
||||
CheckNotInUploadCallback();
|
||||
std::lock_guard access(m_access_mutex);
|
||||
RequireMapped(vaddr, size);
|
||||
return Iterate<true>(vaddr, size, [](RegionManager* manager, uint64_t offset, uint64_t bytes) {
|
||||
std::scoped_lock lock(manager->lock);
|
||||
return manager->IsModified<DirtySource::Cpu>(offset, bytes);
|
||||
@@ -66,7 +89,6 @@ bool MemoryTracker::IsRegionCpuModified(uint64_t vaddr, uint64_t size) {
|
||||
bool MemoryTracker::IsRegionGpuModified(uint64_t vaddr, uint64_t size) {
|
||||
CheckNotInUploadCallback();
|
||||
std::lock_guard access(m_access_mutex);
|
||||
RequireMapped(vaddr, size);
|
||||
return 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);
|
||||
@@ -76,45 +98,31 @@ bool MemoryTracker::IsRegionGpuModified(uint64_t vaddr, uint64_t size) {
|
||||
void MemoryTracker::MarkRegionAsCpuModified(uint64_t vaddr, uint64_t size) {
|
||||
CheckNotInUploadCallback();
|
||||
std::lock_guard access(m_access_mutex);
|
||||
RequireMapped(vaddr, size);
|
||||
Iterate<true>(vaddr, size, [](RegionManager* manager, uint64_t offset, uint64_t bytes) {
|
||||
std::scoped_lock lock(manager->lock);
|
||||
const auto changed =
|
||||
manager->ChangeState<DirtySource::Cpu, true>(manager->GetCpuAddr() + offset, bytes);
|
||||
manager->ApplyProtection(changed, false);
|
||||
manager->ChangeState<DirtySource::Cpu, true>(manager->GetCpuAddr() + offset, bytes);
|
||||
});
|
||||
}
|
||||
|
||||
void MemoryTracker::MarkRegionAsGpuModified(uint64_t vaddr, uint64_t size) {
|
||||
CheckNotInUploadCallback();
|
||||
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, [](RegionManager* manager, uint64_t offset, uint64_t bytes) {
|
||||
std::scoped_lock lock(manager->lock);
|
||||
const auto changed =
|
||||
manager->ChangeState<DirtySource::Gpu, true>(manager->GetCpuAddr() + offset, bytes);
|
||||
manager->ApplyGpuProtection(changed, true, m_gpu_watch_mode);
|
||||
manager->ChangeState<DirtySource::Gpu, true>(manager->GetCpuAddr() + offset, bytes);
|
||||
});
|
||||
}
|
||||
|
||||
void MemoryTracker::UnmarkRegionAsGpuModified(uint64_t vaddr, uint64_t size) {
|
||||
CheckNotInUploadCallback();
|
||||
std::lock_guard access(m_access_mutex);
|
||||
RequireMapped(vaddr, size);
|
||||
Iterate<true>(vaddr, size, [this](RegionManager* manager, uint64_t offset, uint64_t bytes) {
|
||||
Iterate<false>(vaddr, size, [](RegionManager* manager, uint64_t offset, uint64_t bytes) {
|
||||
std::scoped_lock lock(manager->lock);
|
||||
if (!manager->IsFullyModified<DirtySource::Gpu>(offset, bytes)) {
|
||||
EXIT("cannot clear partially GPU-dirty tracking range\n");
|
||||
}
|
||||
const auto changed =
|
||||
manager->ChangeState<DirtySource::Gpu, false>(manager->GetCpuAddr() + offset, bytes);
|
||||
manager->ApplyGpuProtection(changed, false, m_gpu_watch_mode);
|
||||
manager->ChangeState<DirtySource::Gpu, false>(manager->GetCpuAddr() + offset, bytes);
|
||||
});
|
||||
}
|
||||
|
||||
void MemoryTracker::UntrackMemoryLocked(uint64_t vaddr, uint64_t size) {
|
||||
RequireMapped(vaddr, size);
|
||||
|
||||
std::vector<RegionManager*> managers;
|
||||
managers.reserve((vaddr % TRACKER_REGION_SIZE + size + TRACKER_REGION_SIZE - 1) /
|
||||
TRACKER_REGION_SIZE);
|
||||
@@ -133,10 +141,7 @@ void MemoryTracker::UntrackMemoryLocked(uint64_t vaddr, uint64_t size) {
|
||||
EXIT("cannot untrack GPU-dirty memory\n");
|
||||
}
|
||||
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);
|
||||
manager->Untrack(manager->GetCpuAddr() + offset, bytes);
|
||||
manager->ChangeState<DirtySource::Cpu, true>(manager->GetCpuAddr() + offset, bytes);
|
||||
});
|
||||
locks.clear();
|
||||
}
|
||||
@@ -147,109 +152,4 @@ void MemoryTracker::UntrackMemory(uint64_t vaddr, uint64_t 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
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "graphics/host_gpu/pageManager.h"
|
||||
#include "graphics/host_gpu/rangeSet.h"
|
||||
#include "graphics/host_gpu/regionManager.h"
|
||||
|
||||
#include <algorithm>
|
||||
@@ -10,14 +11,14 @@
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
class MemoryTracker final {
|
||||
public:
|
||||
explicit MemoryTracker(PageManager& page_manager,
|
||||
PageWatchMode gpu_watch_mode = PageWatchMode::ReadWrite);
|
||||
explicit MemoryTracker(PageManager& page_manager);
|
||||
~MemoryTracker();
|
||||
|
||||
KYTY_CLASS_NO_COPY(MemoryTracker);
|
||||
@@ -28,24 +29,62 @@ public:
|
||||
void MarkRegionAsGpuModified(uint64_t vaddr, uint64_t size);
|
||||
void UnmarkRegionAsGpuModified(uint64_t vaddr, uint64_t size);
|
||||
void UntrackMemory(uint64_t vaddr, uint64_t size);
|
||||
void UnmapMemory(uint64_t vaddr, uint64_t size);
|
||||
[[nodiscard]] CpuFaultAction
|
||||
BeginCpuFault(uint64_t vaddr, uint64_t size,
|
||||
PageFaultAccess access = PageFaultAccess::Write) noexcept;
|
||||
[[nodiscard]] bool CompleteCpuFault(uint64_t vaddr, uint64_t size, PageFaultAccess access,
|
||||
bool downloaded) noexcept;
|
||||
[[nodiscard]] bool InvalidateRegion(uint64_t vaddr, uint64_t size,
|
||||
PageFaultPhase phase) noexcept;
|
||||
[[nodiscard]] bool InvalidateVirtualGpuWrite(PageFaultAccess access, uint64_t vaddr,
|
||||
uint64_t size, PageFaultPhase phase) noexcept;
|
||||
template <typename Flush>
|
||||
void InvalidateRegion(uint64_t vaddr, uint64_t size, Flush&& on_flush) {
|
||||
static_assert(std::is_invocable_v<Flush&>);
|
||||
CheckNotInUploadCallback();
|
||||
ValidateRange(vaddr, size);
|
||||
|
||||
const auto update_cpu_state = [this, vaddr, size] {
|
||||
std::lock_guard access(m_access_mutex);
|
||||
std::vector<RegionManager*> managers;
|
||||
Iterate<false>(vaddr, size, [&](RegionManager* manager, uint64_t, uint64_t) {
|
||||
managers.push_back(manager);
|
||||
});
|
||||
std::vector<std::unique_lock<TrackingSpinLock>> locks;
|
||||
locks.reserve(managers.size());
|
||||
for (auto* manager: managers) {
|
||||
locks.emplace_back(manager->lock);
|
||||
}
|
||||
const bool gpu_modified = Iterate<false>(
|
||||
vaddr, size, [](RegionManager* manager, uint64_t offset, uint64_t bytes) {
|
||||
return manager->IsModified<DirtySource::Gpu>(offset, bytes);
|
||||
});
|
||||
if (gpu_modified) {
|
||||
return true;
|
||||
}
|
||||
Iterate<false>(vaddr, size,
|
||||
[](RegionManager* manager, uint64_t offset, uint64_t bytes) {
|
||||
manager->ChangeState<DirtySource::Cpu, true>(
|
||||
manager->GetCpuAddr() + offset, bytes);
|
||||
});
|
||||
return false;
|
||||
};
|
||||
|
||||
if (!update_cpu_state()) {
|
||||
return;
|
||||
}
|
||||
std::forward<Flush>(on_flush)();
|
||||
if (update_cpu_state()) {
|
||||
EXIT("memory invalidation retained GPU-owned pages\n");
|
||||
}
|
||||
}
|
||||
#if KYTY_BUILD == KYTY_BUILD_DEBUG
|
||||
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);
|
||||
#else
|
||||
void ValidateGpuDirtyPages(const RangeSet&, uint64_t, uint64_t, const char*) const noexcept {}
|
||||
void ValidateGpuDirtyOwnership(const RangeSet&, uint64_t, uint64_t, const char*) {}
|
||||
#endif
|
||||
|
||||
template <bool clear, typename Preflight, typename 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<Func&, uint64_t, uint64_t>);
|
||||
CheckNotInUploadCallback();
|
||||
std::lock_guard access(m_access_mutex);
|
||||
RequireMapped(vaddr, size);
|
||||
std::lock_guard access(m_access_mutex);
|
||||
std::vector<RegionManager*> managers;
|
||||
Iterate<false>(vaddr, size, [&](RegionManager* manager, uint64_t, uint64_t) {
|
||||
managers.push_back(manager);
|
||||
@@ -57,9 +96,6 @@ public:
|
||||
}
|
||||
Iterate<false>(vaddr, size, [&](RegionManager* manager, uint64_t offset, uint64_t bytes) {
|
||||
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,
|
||||
preflight);
|
||||
});
|
||||
@@ -71,10 +107,8 @@ public:
|
||||
Iterate<false>(vaddr, size,
|
||||
[&](RegionManager* manager, uint64_t offset, uint64_t bytes) {
|
||||
const auto address = manager->GetCpuAddr() + offset;
|
||||
const auto changed =
|
||||
manager->template ForEachModifiedRange<DirtySource::Gpu, true>(
|
||||
address, bytes, [](uint64_t, uint64_t) noexcept {});
|
||||
manager->ApplyGpuProtection(changed, false, m_gpu_watch_mode);
|
||||
manager->template ForEachModifiedRange<DirtySource::Gpu, true>(
|
||||
address, bytes, [](uint64_t, uint64_t) noexcept {});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -85,11 +119,6 @@ public:
|
||||
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>
|
||||
void ForEachUploadRange(uint64_t vaddr, uint64_t size, bool is_written, RangeFunc&& range_func,
|
||||
UploadFunc&& upload_func) {
|
||||
@@ -97,12 +126,10 @@ public:
|
||||
static_assert(std::is_nothrow_invocable_v<UploadFunc&>);
|
||||
CheckNotInUploadCallback();
|
||||
std::unique_lock access(m_access_mutex);
|
||||
RequireMapped(vaddr, size);
|
||||
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) {
|
||||
manager->lock.lock();
|
||||
manager->Track(manager->GetCpuAddr() + offset, bytes);
|
||||
manager->ForEachModifiedRange<DirtySource::Cpu, true>(manager->GetCpuAddr() + offset,
|
||||
bytes, range_func);
|
||||
if (!is_written) {
|
||||
@@ -111,23 +138,22 @@ public:
|
||||
});
|
||||
upload_func();
|
||||
if (is_written) {
|
||||
Iterate<false>(
|
||||
vaddr, size, [this](RegionManager* manager, uint64_t offset, uint64_t bytes) {
|
||||
const auto changed = manager->template ChangeState<DirtySource::Gpu, true>(
|
||||
manager->GetCpuAddr() + offset, bytes);
|
||||
manager->ApplyGpuProtection(changed, true, m_gpu_watch_mode);
|
||||
manager->lock.unlock();
|
||||
});
|
||||
Iterate<false>(vaddr, size,
|
||||
[](RegionManager* manager, uint64_t offset, uint64_t bytes) {
|
||||
manager->template ChangeState<DirtySource::Gpu, true>(
|
||||
manager->GetCpuAddr() + offset, bytes);
|
||||
manager->lock.unlock();
|
||||
});
|
||||
}
|
||||
s_upload_owner = nullptr;
|
||||
s_upload_owner = previous_upload_owner;
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr size_t REGION_COUNT = TRACKER_ADDRESS_SIZE / TRACKER_REGION_SIZE;
|
||||
inline static thread_local const MemoryTracker* s_upload_owner = nullptr;
|
||||
|
||||
static void CheckNotInUploadCallback() noexcept {
|
||||
if (s_upload_owner != nullptr) {
|
||||
void CheckNotInUploadCallback() const noexcept {
|
||||
if (s_upload_owner == this) {
|
||||
EXIT("memory tracker re-entered from upload callback\n");
|
||||
}
|
||||
}
|
||||
@@ -162,14 +188,8 @@ private:
|
||||
return false;
|
||||
}
|
||||
|
||||
static void ValidateRange(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");
|
||||
}
|
||||
}
|
||||
static void ValidateRange(uint64_t vaddr, uint64_t size);
|
||||
void UntrackMemoryLocked(uint64_t vaddr, uint64_t size);
|
||||
RegionManager* GetOrCreateRegion(uint64_t index);
|
||||
|
||||
std::unique_ptr<std::atomic<RegionManager*>[]> m_regions;
|
||||
@@ -177,7 +197,6 @@ private:
|
||||
std::mutex m_region_mutex;
|
||||
std::mutex m_access_mutex;
|
||||
PageManager& m_page_manager;
|
||||
PageWatchMode m_gpu_watch_mode = PageWatchMode::ReadWrite;
|
||||
};
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
@@ -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
|
||||
@@ -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_ */
|
||||
@@ -1,13 +1,14 @@
|
||||
#include "graphics/host_gpu/pageManager.h"
|
||||
|
||||
#include "graphics/host_gpu/regionDefinitions.h"
|
||||
#include "kernel/memory.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <cstdarg>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
@@ -19,6 +20,11 @@
|
||||
#include <windows.h>
|
||||
#undef min
|
||||
#undef max
|
||||
#elif defined(__APPLE__)
|
||||
#include <unistd.h>
|
||||
#else
|
||||
#include <execinfo.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
namespace Libs::Graphics {
|
||||
@@ -28,9 +34,19 @@ constexpr uint64_t PAGE_SIZE = TRACKER_PAGE_SIZE;
|
||||
constexpr uint64_t REGION_SIZE = TRACKER_REGION_SIZE;
|
||||
constexpr uint64_t ADDRESS_SIZE = TRACKER_ADDRESS_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.
|
||||
// Mirror their canonical 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;
|
||||
|
||||
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;
|
||||
|
||||
[[noreturn]] void FailFast(const char* reason = nullptr) noexcept {
|
||||
std::fputs("PageManager fail-fast: ", stderr);
|
||||
@@ -46,6 +62,10 @@ thread_local bool g_in_fault_resolution = false;
|
||||
std::fprintf(stderr, " frame[%u]=0x%016" PRIxPTR " image_rva=0x%016" PRIxPTR "\n", i,
|
||||
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
|
||||
std::fflush(stderr);
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
@@ -65,12 +85,13 @@ thread_local bool g_in_fault_resolution = false;
|
||||
std::_Exit(322);
|
||||
}
|
||||
|
||||
uint32_t CurrentThread() noexcept {
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
return GetCurrentThreadId();
|
||||
#else
|
||||
FailFast();
|
||||
#endif
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
class SpinGuard final {
|
||||
@@ -106,28 +127,61 @@ uint64_t PageEnd(uint64_t vaddr, uint64_t size) {
|
||||
|
||||
struct PageManager::Impl {
|
||||
struct PageState {
|
||||
std::atomic_flag lock = ATOMIC_FLAG_INIT;
|
||||
uint32_t mappings = 0;
|
||||
uint32_t gpu_read_mappings = 0;
|
||||
uint32_t gpu_write_mappings = 0;
|
||||
uint32_t write_watchers = 0;
|
||||
uint32_t access_watchers = 0;
|
||||
uint32_t original_protection = 0;
|
||||
uint32_t backing_writer = 0;
|
||||
bool resolving = false;
|
||||
bool resolving_read_write = false;
|
||||
bool late_read_pending = false;
|
||||
bool late_write_pending = false;
|
||||
uint8_t write_watchers : 7 = 0;
|
||||
uint8_t access_watchers : 1 = 0;
|
||||
|
||||
[[nodiscard]] uint32_t Perms() const noexcept {
|
||||
if (access_watchers != 0) {
|
||||
return NO_ACCESS_PROTECTION;
|
||||
}
|
||||
if (write_watchers != 0) {
|
||||
return READ_ONLY_PROTECTION;
|
||||
}
|
||||
return READ_WRITE_PROTECTION;
|
||||
}
|
||||
|
||||
template <int delta, bool is_read>
|
||||
uint32_t AddDelta(uint64_t address) {
|
||||
static_assert(delta >= -1 && delta <= 1);
|
||||
if constexpr (is_read) {
|
||||
if constexpr (delta == 1) {
|
||||
if (access_watchers != 0) {
|
||||
Fatal("read-watcher overflow at 0x%016" PRIx64, address);
|
||||
}
|
||||
return ++access_watchers;
|
||||
} else if constexpr (delta == -1) {
|
||||
if (access_watchers == 0) {
|
||||
Fatal("read-watcher underflow at 0x%016" PRIx64, address);
|
||||
}
|
||||
return --access_watchers;
|
||||
} else {
|
||||
return access_watchers;
|
||||
}
|
||||
} else {
|
||||
if constexpr (delta == 1) {
|
||||
if (write_watchers == 0x7f) {
|
||||
Fatal("write-watcher overflow at 0x%016" PRIx64, address);
|
||||
}
|
||||
return ++write_watchers;
|
||||
} else if constexpr (delta == -1) {
|
||||
if (write_watchers == 0) {
|
||||
Fatal("write-watcher underflow at 0x%016" PRIx64, address);
|
||||
}
|
||||
return --write_watchers;
|
||||
} else {
|
||||
return write_watchers;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
static_assert(sizeof(PageState) == 1);
|
||||
|
||||
struct Region {
|
||||
std::atomic_flag lock = ATOMIC_FLAG_INIT;
|
||||
std::array<PageState, REGION_PAGES> pages;
|
||||
};
|
||||
|
||||
Impl(PageFaultHandler handler, void* context): fault_handler(handler), fault_context(context) {
|
||||
if (fault_handler == nullptr) {
|
||||
Fatal("null fault handler");
|
||||
}
|
||||
Impl() {
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
SYSTEM_INFO info {};
|
||||
GetSystemInfo(&info);
|
||||
@@ -135,8 +189,16 @@ struct PageManager::Impl {
|
||||
Fatal("unsupported host page size 0x%08" PRIx32,
|
||||
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
|
||||
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
|
||||
regions = std::make_unique<std::atomic<Region*>[]>(REGION_COUNT);
|
||||
for (uint64_t i = 0; i < REGION_COUNT; i++) {
|
||||
@@ -146,11 +208,9 @@ struct PageManager::Impl {
|
||||
|
||||
~Impl() {
|
||||
for (const auto& region: region_storage) {
|
||||
SpinGuard lock(region->lock);
|
||||
for (auto& page: region->pages) {
|
||||
SpinGuard lock(page.lock);
|
||||
if (page.mappings != 0 || page.gpu_read_mappings != 0 ||
|
||||
page.gpu_write_mappings != 0 || page.write_watchers != 0 ||
|
||||
page.access_watchers != 0 || page.backing_writer != 0 || page.resolving) {
|
||||
if (page.write_watchers != 0 || page.access_watchers != 0) {
|
||||
FailFast("PageManager destroyed with live page state");
|
||||
}
|
||||
}
|
||||
@@ -178,470 +238,140 @@ struct PageManager::Impl {
|
||||
return ptr;
|
||||
}
|
||||
|
||||
PageState& GetPage(Region& region, uint64_t vaddr) const {
|
||||
return region.pages[(vaddr % REGION_SIZE) / PAGE_SIZE];
|
||||
}
|
||||
|
||||
static uint32_t WatcherProtection(const PageState& page) {
|
||||
if (page.access_watchers != 0) {
|
||||
return PAGE_NOACCESS;
|
||||
}
|
||||
if (page.write_watchers != 0) {
|
||||
return PAGE_READONLY;
|
||||
}
|
||||
return page.original_protection;
|
||||
}
|
||||
|
||||
static void PublishDelayedFaults(PageState& page, uint32_t old_protection,
|
||||
uint32_t new_protection) {
|
||||
if (old_protection == PAGE_NOACCESS && new_protection != PAGE_NOACCESS) {
|
||||
page.late_read_pending = true;
|
||||
}
|
||||
if ((old_protection == PAGE_NOACCESS || old_protection == PAGE_READONLY) &&
|
||||
new_protection == PAGE_READWRITE) {
|
||||
page.late_write_pending = true;
|
||||
void Protect(uint64_t vaddr, uint64_t size, uint32_t protection) noexcept {
|
||||
if (!Libs::LibKernel::Memory::ProtectGuestHostMemory(vaddr, size,
|
||||
ToMemoryMode(protection))) {
|
||||
Fatal("address-space protection failed at 0x%016" PRIx64 ", new=0x%08" PRIx32, vaddr,
|
||||
protection);
|
||||
}
|
||||
}
|
||||
|
||||
static uint32_t QueryProtection(uint64_t vaddr) {
|
||||
#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 || 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;
|
||||
#else
|
||||
(void)vaddr;
|
||||
Fatal("page query is unsupported on this platform");
|
||||
#endif
|
||||
}
|
||||
template <bool track, bool is_read, bool masked>
|
||||
void UpdateRegionWatchers(Region& region, uint64_t base_addr, size_t first, size_t last,
|
||||
const RegionBits* mask = nullptr) {
|
||||
SpinGuard lock(region.lock);
|
||||
auto perms = region.pages[first].Perms();
|
||||
uint64_t range_begin = 0;
|
||||
uint64_t range_bytes = 0;
|
||||
uint64_t potential_range_bytes = 0;
|
||||
|
||||
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
|
||||
}
|
||||
const auto release_pending = [&] {
|
||||
if (range_bytes != 0) {
|
||||
Protect(base_addr + range_begin * PAGE_SIZE, range_bytes, perms);
|
||||
range_bytes = 0;
|
||||
potential_range_bytes = 0;
|
||||
}
|
||||
};
|
||||
|
||||
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");
|
||||
for (size_t page_index = first; page_index < last; page_index++) {
|
||||
auto& page = region.pages[page_index];
|
||||
const auto address = base_addr + page_index * PAGE_SIZE;
|
||||
const bool update = !masked || mask->Get(page_index);
|
||||
|
||||
const auto old_perms = page.Perms();
|
||||
const auto new_count = update ? page.AddDelta<track ? 1 : -1, is_read>(address)
|
||||
: page.AddDelta<0, is_read>(address);
|
||||
const auto new_perms = page.Perms();
|
||||
|
||||
if (new_perms != perms) [[unlikely]] {
|
||||
release_pending();
|
||||
perms = new_perms;
|
||||
} else if (range_bytes != 0) {
|
||||
potential_range_bytes += PAGE_SIZE;
|
||||
}
|
||||
|
||||
if (!update) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const bool watcher_edge = (track && new_count == 1) || (!track && new_count == 0);
|
||||
if (watcher_edge && old_perms != new_perms) {
|
||||
if (range_bytes == 0) {
|
||||
range_begin = page_index;
|
||||
potential_range_bytes = PAGE_SIZE;
|
||||
}
|
||||
range_bytes = potential_range_bytes;
|
||||
}
|
||||
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
|
||||
(void)vaddr;
|
||||
(void)protection;
|
||||
(void)fault_path;
|
||||
FailFast("page protection is unsupported on this platform");
|
||||
#endif
|
||||
|
||||
release_pending();
|
||||
}
|
||||
|
||||
template <bool track, bool is_read>
|
||||
void UpdatePageWatchers(uint64_t vaddr, uint64_t size) {
|
||||
const auto begin = PageStart(vaddr);
|
||||
const auto end = PageEnd(vaddr, size);
|
||||
for (auto chunk_begin = begin; chunk_begin < end;) {
|
||||
const auto chunk_end = std::min(end, (chunk_begin / REGION_SIZE + 1) * REGION_SIZE);
|
||||
const auto region_base = chunk_begin / REGION_SIZE * REGION_SIZE;
|
||||
auto* region = track ? GetOrCreateRegion(chunk_begin) : FindRegion(chunk_begin);
|
||||
if (region == nullptr) {
|
||||
Fatal("untracking unknown page 0x%016" PRIx64, chunk_begin);
|
||||
}
|
||||
const auto first = static_cast<size_t>((chunk_begin - region_base) / PAGE_SIZE);
|
||||
const auto last = static_cast<size_t>((chunk_end - region_base) / PAGE_SIZE);
|
||||
UpdateRegionWatchers<track, is_read, false>(*region, region_base, first, last);
|
||||
chunk_begin = chunk_end;
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<std::atomic<Region*>[]> regions;
|
||||
std::vector<std::unique_ptr<Region>> region_storage;
|
||||
std::mutex region_mutex;
|
||||
PageFaultHandler fault_handler = nullptr;
|
||||
void* fault_context = nullptr;
|
||||
};
|
||||
|
||||
static_assert(std::atomic<void*>::is_always_lock_free);
|
||||
|
||||
PageManager::PageManager(PageFaultHandler fault_handler, void* fault_context)
|
||||
: m_impl(std::make_unique<Impl>(fault_handler, fault_context)) {}
|
||||
PageManager::PageManager(): m_impl(std::make_unique<Impl>()) {}
|
||||
|
||||
PageManager::~PageManager() = default;
|
||||
|
||||
uint64_t PageManager::GetPageSize() const {
|
||||
if (g_in_fault_resolution) {
|
||||
FailFast("nested page fault while resolving a watched page");
|
||||
}
|
||||
return PAGE_SIZE;
|
||||
}
|
||||
|
||||
bool PageManager::IsTracked(uint64_t vaddr) const noexcept {
|
||||
if (g_in_fault_resolution) {
|
||||
FailFast("IsTracked called during fault resolution");
|
||||
template <bool track>
|
||||
void PageManager::UpdatePageWatchers(uint64_t vaddr, uint64_t size) {
|
||||
m_impl->UpdatePageWatchers<track, false>(vaddr, size);
|
||||
}
|
||||
|
||||
template void PageManager::UpdatePageWatchers<true>(uint64_t, uint64_t);
|
||||
template void PageManager::UpdatePageWatchers<false>(uint64_t, uint64_t);
|
||||
|
||||
template <bool track, bool is_read>
|
||||
void PageManager::UpdatePageWatchersForRegion(uint64_t base_addr, RegionBits& mask) {
|
||||
if (base_addr % REGION_SIZE != 0 || base_addr >= ADDRESS_SIZE ||
|
||||
REGION_SIZE > ADDRESS_SIZE - base_addr) {
|
||||
Fatal("invalid tracking region base 0x%016" PRIx64, base_addr);
|
||||
}
|
||||
auto* region = m_impl->FindRegion(vaddr);
|
||||
|
||||
const auto start_range = mask.FirstRange();
|
||||
const auto end_range = mask.LastRange();
|
||||
if (start_range.first == REGION_PAGES) {
|
||||
FailFast("empty region watcher mask");
|
||||
}
|
||||
const auto first = start_range.first;
|
||||
const auto last = end_range.second;
|
||||
if (start_range.second == end_range.second) {
|
||||
m_impl->UpdatePageWatchers<track, is_read>(base_addr + first * PAGE_SIZE,
|
||||
(last - first) * PAGE_SIZE);
|
||||
return;
|
||||
}
|
||||
|
||||
auto* region = track ? m_impl->GetOrCreateRegion(base_addr) : m_impl->FindRegion(base_addr);
|
||||
if (region == nullptr) {
|
||||
return false;
|
||||
Fatal("untracking unknown region 0x%016" PRIx64, base_addr);
|
||||
}
|
||||
auto& page = m_impl->GetPage(*region, vaddr);
|
||||
SpinGuard lock(page.lock);
|
||||
return page.write_watchers != 0 || page.access_watchers != 0;
|
||||
m_impl->UpdateRegionWatchers<track, is_read, true>(*region, base_addr, first, last, &mask);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
template void PageManager::UpdatePageWatchersForRegion<true, true>(uint64_t, RegionBits&);
|
||||
template void PageManager::UpdatePageWatchersForRegion<true, false>(uint64_t, RegionBits&);
|
||||
template void PageManager::UpdatePageWatchersForRegion<false, true>(uint64_t, RegionBits&);
|
||||
template void PageManager::UpdatePageWatchersForRegion<false, false>(uint64_t, RegionBits&);
|
||||
|
||||
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::OnGpuMap(uint64_t, uint64_t) {}
|
||||
|
||||
void PageManager::UpdatePageWatchers(bool track, uint64_t vaddr, uint64_t size,
|
||||
PageWatchMode mode) {
|
||||
if (g_in_fault_resolution) {
|
||||
FailFast("page watchers changed during fault resolution");
|
||||
}
|
||||
if (mode != PageWatchMode::Write && mode != PageWatchMode::ReadWrite) {
|
||||
Fatal("invalid watcher mode");
|
||||
}
|
||||
const auto end = PageEnd(vaddr, size);
|
||||
for (auto page_vaddr = PageStart(vaddr); page_vaddr < end; page_vaddr += PAGE_SIZE) {
|
||||
auto* region =
|
||||
track ? m_impl->GetOrCreateRegion(page_vaddr) : m_impl->FindRegion(page_vaddr);
|
||||
if (region == nullptr) {
|
||||
Fatal("untracking unknown page 0x%016" PRIx64, page_vaddr);
|
||||
}
|
||||
auto& page = m_impl->GetPage(*region, page_vaddr);
|
||||
SpinGuard lock(page.lock);
|
||||
if (page.resolving && track) {
|
||||
FailFast("new page watcher raced active fault resolution");
|
||||
}
|
||||
if (page.mappings == 0) {
|
||||
Fatal("watching unmapped page 0x%016" PRIx64, page_vaddr);
|
||||
}
|
||||
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, page_vaddr);
|
||||
}
|
||||
const bool first_watcher = page.write_watchers == 0 && page.access_watchers == 0;
|
||||
if (first_watcher) {
|
||||
page.original_protection = Impl::QueryProtection(page_vaddr);
|
||||
}
|
||||
const auto old_protection = Impl::WatcherProtection(page);
|
||||
watchers++;
|
||||
const auto new_protection = Impl::WatcherProtection(page);
|
||||
if (new_protection != old_protection) {
|
||||
Impl::Protect(page_vaddr, new_protection, old_protection, false);
|
||||
}
|
||||
switch (new_protection) {
|
||||
case PAGE_NOACCESS:
|
||||
page.late_read_pending = false;
|
||||
page.late_write_pending = false;
|
||||
break;
|
||||
case PAGE_READONLY: page.late_write_pending = false; break;
|
||||
default: break;
|
||||
}
|
||||
} else {
|
||||
if (watchers == 0) {
|
||||
Fatal("watcher underflow at 0x%016" PRIx64, page_vaddr);
|
||||
}
|
||||
if (page.backing_writer != 0 && page.backing_writer != CurrentThread()) {
|
||||
Fatal("backing write ownership changed at 0x%016" PRIx64, page_vaddr);
|
||||
}
|
||||
const auto old_protection = Impl::WatcherProtection(page);
|
||||
watchers--;
|
||||
const auto new_protection = Impl::WatcherProtection(page);
|
||||
if (page.backing_writer == 0 && new_protection != old_protection) {
|
||||
Impl::Protect(page_vaddr, new_protection, old_protection, false);
|
||||
}
|
||||
if (page.backing_writer == 0) {
|
||||
Impl::PublishDelayedFaults(page, old_protection, new_protection);
|
||||
}
|
||||
if (page.backing_writer == 0 && page.write_watchers == 0 && page.access_watchers == 0) {
|
||||
page.original_protection = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PageManager::OnGpuMap(uint64_t vaddr, uint64_t size, GpuAccess access) {
|
||||
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) {
|
||||
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,
|
||||
uint64_t size) noexcept
|
||||
: m_manager(manager), m_vaddr(vaddr), m_size(size) {
|
||||
m_manager.BeginBackingWrite(vaddr, size);
|
||||
}
|
||||
|
||||
PageManager::BackingWrite::~BackingWrite() {
|
||||
m_manager.EndBackingWrite(m_vaddr, m_size);
|
||||
}
|
||||
|
||||
void PageManager::BeginBackingWrite(uint64_t vaddr, uint64_t size) noexcept {
|
||||
if (g_in_fault_resolution) {
|
||||
FailFast("backing write began during fault resolution");
|
||||
}
|
||||
const auto end = PageEnd(vaddr, size);
|
||||
const auto writer = CurrentThread();
|
||||
for (auto address = PageStart(vaddr); address < end; address += PAGE_SIZE) {
|
||||
auto* region = m_impl->FindRegion(address);
|
||||
if (region == nullptr) {
|
||||
Fatal("backing write reserves an unknown page at 0x%016" PRIx64, address);
|
||||
}
|
||||
auto& page = m_impl->GetPage(*region, address);
|
||||
SpinGuard lock(page.lock);
|
||||
if (page.mappings == 0 || page.resolving || page.backing_writer != 0 ||
|
||||
page.access_watchers == 0) {
|
||||
Fatal("backing write races page resolution at 0x%016" PRIx64, address);
|
||||
}
|
||||
page.resolving = true;
|
||||
page.resolving_read_write = true;
|
||||
page.backing_writer = writer;
|
||||
}
|
||||
}
|
||||
|
||||
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 writer = CurrentThread();
|
||||
for (auto address = PageStart(vaddr); address < end; address += PAGE_SIZE) {
|
||||
auto* region = m_impl->FindRegion(address);
|
||||
if (region == nullptr) {
|
||||
FailFast("backing write ended for an unknown page");
|
||||
}
|
||||
auto& page = m_impl->GetPage(*region, address);
|
||||
SpinGuard lock(page.lock);
|
||||
if (!page.resolving || page.backing_writer != writer) {
|
||||
FailFast("backing write ended without matching owner and resolving state");
|
||||
}
|
||||
const auto old_protection = PAGE_NOACCESS;
|
||||
const auto new_protection = Impl::WatcherProtection(page);
|
||||
if (new_protection != old_protection) {
|
||||
Impl::Protect(address, new_protection, old_protection, false);
|
||||
}
|
||||
Impl::PublishDelayedFaults(page, old_protection, new_protection);
|
||||
if (page.write_watchers == 0 && page.access_watchers == 0) {
|
||||
page.original_protection = 0;
|
||||
}
|
||||
page.backing_writer = 0;
|
||||
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;
|
||||
}
|
||||
void PageManager::OnGpuUnmap(uint64_t, uint64_t) {}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
@@ -2,55 +2,32 @@
|
||||
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_PAGEMANAGER_H_
|
||||
|
||||
#include "common/common.h"
|
||||
#include "graphics/host_gpu/regionDefinitions.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
enum class PageFaultAccess { Read, Write, Execute, Unknown };
|
||||
enum class PageFaultPhase { Invalidate, Complete, Release };
|
||||
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 {
|
||||
public:
|
||||
class BackingWrite final {
|
||||
public:
|
||||
BackingWrite(PageManager& manager, uint64_t vaddr, uint64_t size) noexcept;
|
||||
~BackingWrite();
|
||||
KYTY_CLASS_NO_COPY(BackingWrite);
|
||||
|
||||
private:
|
||||
PageManager& m_manager;
|
||||
uint64_t m_vaddr = 0;
|
||||
uint64_t m_size = 0;
|
||||
};
|
||||
|
||||
PageManager(PageFaultHandler fault_handler, void* fault_context);
|
||||
PageManager();
|
||||
// The owner must stop all PageManager callers before destruction.
|
||||
~PageManager();
|
||||
|
||||
KYTY_CLASS_NO_COPY(PageManager);
|
||||
|
||||
[[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,
|
||||
PageWatchMode mode = PageWatchMode::Write);
|
||||
void OnGpuMap(uint64_t vaddr, uint64_t size, GpuAccess access = GpuAccess::ReadWrite);
|
||||
void OnGpuUnmap(uint64_t vaddr, uint64_t size, GpuAccess access = GpuAccess::ReadWrite);
|
||||
|
||||
[[nodiscard]] bool HandleFault(PageFaultAccess access, uint64_t fault_vaddr) noexcept;
|
||||
template <bool track>
|
||||
void UpdatePageWatchers(uint64_t vaddr, uint64_t size);
|
||||
template <bool track, bool is_read = false>
|
||||
void UpdatePageWatchersForRegion(uint64_t base_addr, RegionBits& mask);
|
||||
void OnGpuMap(uint64_t vaddr, uint64_t size);
|
||||
void OnGpuUnmap(uint64_t vaddr, uint64_t size);
|
||||
|
||||
private:
|
||||
void BeginBackingWrite(uint64_t vaddr, uint64_t size) noexcept;
|
||||
void EndBackingWrite(uint64_t vaddr, uint64_t size) noexcept;
|
||||
|
||||
struct Impl;
|
||||
std::unique_ptr<Impl> m_impl;
|
||||
};
|
||||
|
||||
@@ -59,6 +59,16 @@ public:
|
||||
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>
|
||||
void ForEachIntersection(uint64_t address, uint64_t size, Func&& func) const {
|
||||
const auto end = End(address, size);
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_REGIONDEFINITIONS_H_
|
||||
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_REGIONDEFINITIONS_H_
|
||||
|
||||
#include "common/bitArray.h"
|
||||
#include "common/common.h"
|
||||
|
||||
#include <bitset>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
constexpr uint64_t TRACKER_PAGE_SIZE = 4ull * 1024ull;
|
||||
@@ -13,7 +12,8 @@ constexpr uint64_t TRACKER_ADDRESS_SIZE = 1ull << 40u;
|
||||
constexpr size_t TRACKER_REGION_PAGES = TRACKER_REGION_SIZE / TRACKER_PAGE_SIZE;
|
||||
|
||||
enum class DirtySource { Cpu, Gpu };
|
||||
using RegionBits = std::bitset<TRACKER_REGION_PAGES>;
|
||||
using RegionBits = Common::BitArray<TRACKER_REGION_PAGES>;
|
||||
static_assert(sizeof(RegionBits) == TRACKER_REGION_PAGES / 8);
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
|
||||
@@ -16,12 +16,15 @@
|
||||
#include <windows.h>
|
||||
#undef min
|
||||
#undef max
|
||||
#elif defined(__APPLE__)
|
||||
#include <pthread.h>
|
||||
#elif defined(__linux__)
|
||||
#include <sys/syscall.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
enum class CpuFaultAction { Untracked, Continue, Download };
|
||||
|
||||
class TrackingSpinLock final {
|
||||
public:
|
||||
void lock() noexcept {
|
||||
@@ -49,6 +52,12 @@ private:
|
||||
static uint32_t CurrentThread() noexcept {
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
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
|
||||
EXIT("region tracking thread identity is unsupported on this platform\n");
|
||||
#endif
|
||||
@@ -67,229 +76,93 @@ public:
|
||||
if (m_cpu_addr % TRACKER_REGION_SIZE != 0) {
|
||||
EXIT("invalid region tracking manager construction\n");
|
||||
}
|
||||
m_cpu_dirty.set();
|
||||
m_writable.set();
|
||||
m_cpu_dirty.Fill();
|
||||
m_writable.Fill();
|
||||
m_readable.Fill();
|
||||
}
|
||||
|
||||
KYTY_CLASS_NO_COPY(RegionManager);
|
||||
|
||||
[[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>
|
||||
[[nodiscard]] bool IsModified(uint64_t offset, uint64_t size) const {
|
||||
const auto [start, end] = GetPageRange(m_cpu_addr + offset, size);
|
||||
const auto& bits = GetBits<source>();
|
||||
for (auto page = start; page < end; page++) {
|
||||
if (bits.test(page)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
template <DirtySource source>
|
||||
[[nodiscard]] bool IsFullyModified(uint64_t offset, uint64_t size) const {
|
||||
const auto [start, end] = GetPageRange(m_cpu_addr + offset, size);
|
||||
const auto& bits = GetBits<source>();
|
||||
for (auto page = start; page < end; page++) {
|
||||
if (!bits.test(page)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return RegionBits(bits, start, end).Any();
|
||||
}
|
||||
|
||||
template <DirtySource source, bool enable>
|
||||
RegionBits ChangeState(uint64_t vaddr, uint64_t size) {
|
||||
void ChangeState(uint64_t vaddr, uint64_t size) {
|
||||
const auto [start, end] = GetPageRange(vaddr, size);
|
||||
if constexpr (source == DirtySource::Cpu && enable) {
|
||||
for (auto page = start; page < end; page++) {
|
||||
if (m_gpu_dirty.test(page) || m_fault_pending.test(page)) {
|
||||
EXIT("CPU dirty state conflicts with GPU dirty or pending fault state\n");
|
||||
}
|
||||
if (RegionBits(m_gpu_dirty, start, end).Any()) {
|
||||
EXIT("CPU dirty state conflicts with GPU dirty state\n");
|
||||
}
|
||||
}
|
||||
if constexpr (source == DirtySource::Gpu && enable) {
|
||||
for (auto page = start; page < end; page++) {
|
||||
if (m_cpu_dirty.test(page) || m_fault_pending.test(page)) {
|
||||
EXIT("GPU dirty state conflicts with CPU dirty or pending fault state\n");
|
||||
}
|
||||
if (RegionBits(m_cpu_dirty, start, end).Any()) {
|
||||
EXIT("GPU dirty state conflicts with CPU dirty state\n");
|
||||
}
|
||||
}
|
||||
auto& bits = GetBits<source>();
|
||||
auto changed = bits;
|
||||
for (auto page = start; page < end; page++) {
|
||||
bits.set(page, enable);
|
||||
auto& bits = GetBits<source>();
|
||||
if constexpr (enable) {
|
||||
bits.SetRange(start, end);
|
||||
} else {
|
||||
bits.UnsetRange(start, end);
|
||||
}
|
||||
changed ^= bits;
|
||||
if constexpr (source == DirtySource::Cpu) {
|
||||
changed = m_cpu_dirty ^ m_writable;
|
||||
m_writable = m_cpu_dirty;
|
||||
UpdateCpuProtection<!enable>();
|
||||
} else {
|
||||
UpdateGpuProtection<enable>();
|
||||
}
|
||||
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>
|
||||
RegionBits ForEachModifiedRange(uint64_t vaddr, uint64_t size, Func&& func) {
|
||||
void ForEachModifiedRange(uint64_t vaddr, uint64_t size, Func&& func) {
|
||||
const auto [start, end] = GetPageRange(vaddr, size);
|
||||
auto mask = GetBits<source>();
|
||||
if constexpr (source == DirtySource::Cpu) {
|
||||
mask &= ~m_fault_pending;
|
||||
}
|
||||
for (auto page = 0u; page < start; page++) {
|
||||
mask.reset(page);
|
||||
}
|
||||
for (auto page = end; page < TRACKER_REGION_PAGES; page++) {
|
||||
mask.reset(page);
|
||||
}
|
||||
RegionBits mask(GetBits<source>(), start, end);
|
||||
if constexpr (clear) {
|
||||
auto& bits = GetBits<source>();
|
||||
for (auto page = start; page < end; page++) {
|
||||
if (mask.test(page)) {
|
||||
bits.reset(page);
|
||||
}
|
||||
}
|
||||
GetBits<source>().UnsetRange(start, end);
|
||||
}
|
||||
if constexpr (source == DirtySource::Cpu && clear) {
|
||||
auto changed = m_cpu_dirty ^ m_writable;
|
||||
m_writable = m_cpu_dirty;
|
||||
ApplyProtection(changed, true);
|
||||
UpdateCpuProtection<true>();
|
||||
ForEachRange(mask, std::forward<Func>(func));
|
||||
return changed;
|
||||
return;
|
||||
}
|
||||
if constexpr (source == DirtySource::Gpu && clear) {
|
||||
UpdateGpuProtection<false>();
|
||||
}
|
||||
ForEachRange(mask, std::forward<Func>(func));
|
||||
if constexpr (clear) {
|
||||
return mask;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
void ApplyProtection(const RegionBits& changed, bool track) {
|
||||
ForEachRange(changed, [this, track](uint64_t vaddr, uint64_t size) {
|
||||
m_page_manager.UpdatePageWatchers(track, vaddr, size);
|
||||
});
|
||||
}
|
||||
|
||||
void ApplyGpuProtection(const RegionBits& changed, bool track, PageWatchMode mode) {
|
||||
if (mode != PageWatchMode::Write && mode != PageWatchMode::ReadWrite) {
|
||||
EXIT("unsupported GPU page-watch mode\n");
|
||||
}
|
||||
ForEachRange(changed, [this, track, mode](uint64_t vaddr, uint64_t size) {
|
||||
m_page_manager.UpdatePageWatchers(track, vaddr, size, mode);
|
||||
});
|
||||
}
|
||||
|
||||
TrackingSpinLock lock;
|
||||
|
||||
private:
|
||||
template <bool track>
|
||||
void UpdateCpuProtection() {
|
||||
auto mask = m_cpu_dirty ^ m_writable;
|
||||
m_writable = m_cpu_dirty;
|
||||
if (mask.None()) {
|
||||
return;
|
||||
}
|
||||
m_page_manager.UpdatePageWatchersForRegion<track>(m_cpu_addr, mask);
|
||||
}
|
||||
|
||||
template <bool track>
|
||||
void UpdateGpuProtection() {
|
||||
auto readable = ~m_gpu_dirty;
|
||||
auto mask = readable ^ m_readable;
|
||||
m_readable = readable;
|
||||
if (mask.None()) {
|
||||
return;
|
||||
}
|
||||
if constexpr (track) {
|
||||
m_page_manager.UpdatePageWatchersForRegion<true, true>(m_cpu_addr, mask);
|
||||
} else {
|
||||
m_page_manager.UpdatePageWatchersForRegion<false, true>(m_cpu_addr, mask);
|
||||
}
|
||||
}
|
||||
|
||||
template <DirtySource source>
|
||||
RegionBits& GetBits() {
|
||||
if constexpr (source == DirtySource::Cpu) {
|
||||
@@ -320,18 +193,8 @@ private:
|
||||
|
||||
template <typename Func>
|
||||
void ForEachRange(const RegionBits& bits, Func&& func) const {
|
||||
size_t page = 0;
|
||||
while (page < TRACKER_REGION_PAGES) {
|
||||
while (page < TRACKER_REGION_PAGES && !bits.test(page)) {
|
||||
page++;
|
||||
}
|
||||
const auto start = page;
|
||||
while (page < TRACKER_REGION_PAGES && bits.test(page)) {
|
||||
page++;
|
||||
}
|
||||
if (start != page) {
|
||||
func(m_cpu_addr + start * TRACKER_PAGE_SIZE, (page - start) * TRACKER_PAGE_SIZE);
|
||||
}
|
||||
for (const auto [start, end]: bits) {
|
||||
func(m_cpu_addr + start * TRACKER_PAGE_SIZE, (end - start) * TRACKER_PAGE_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -340,8 +203,7 @@ private:
|
||||
RegionBits m_cpu_dirty;
|
||||
RegionBits m_gpu_dirty;
|
||||
RegionBits m_writable;
|
||||
RegionBits m_fault_pending;
|
||||
RegionBits m_tracked;
|
||||
RegionBits m_readable;
|
||||
};
|
||||
|
||||
} // 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_
|
||||
@@ -0,0 +1,923 @@
|
||||
#include "graphics/host_gpu/renderer/cache/bufferCache.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/profiler.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "graphics/host_gpu/renderer/cache/resourceMutex.h"
|
||||
#include "graphics/host_gpu/renderer/cache/textureCache.h"
|
||||
#include "graphics/host_gpu/renderer/commandScheduler.h"
|
||||
#include "graphics/host_gpu/renderer/render.h"
|
||||
#include "kernel/memory.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
namespace {
|
||||
|
||||
thread_local const void* g_cache_lock_owner = nullptr;
|
||||
|
||||
constexpr uint64_t MiB = 1024 * 1024;
|
||||
constexpr uint64_t GdsBufferSize = 64 * 1024;
|
||||
|
||||
class FaultSafeCacheLock final {
|
||||
public:
|
||||
FaultSafeCacheLock(const void* owner, Common::Mutex& mutex): m_mutex(mutex) {
|
||||
if (g_cache_lock_owner != nullptr) {
|
||||
EXIT("BufferCache: recursive cache lock acquisition\n");
|
||||
}
|
||||
g_cache_lock_owner = owner;
|
||||
m_mutex.Lock();
|
||||
}
|
||||
|
||||
~FaultSafeCacheLock() {
|
||||
m_mutex.Unlock();
|
||||
g_cache_lock_owner = nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
Common::Mutex& m_mutex;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
uint64_t BufferCache::AlignDown(uint64_t value) noexcept {
|
||||
return value & ~(CACHING_PAGE_SIZE - 1);
|
||||
}
|
||||
|
||||
uint64_t BufferCache::AlignUp(uint64_t value) {
|
||||
if (value > UINT64_MAX - (CACHING_PAGE_SIZE - 1)) {
|
||||
EXIT("BufferCache: address alignment overflow\n");
|
||||
}
|
||||
return (value + CACHING_PAGE_SIZE - 1) & ~(CACHING_PAGE_SIZE - 1);
|
||||
}
|
||||
|
||||
bool BufferCache::PageOverlaps(uint64_t left, uint64_t left_size, uint64_t right,
|
||||
uint64_t right_size) noexcept {
|
||||
const auto left_begin = left & ~(TRACKER_PAGE_SIZE - 1);
|
||||
const auto left_end = (left + left_size + TRACKER_PAGE_SIZE - 1) & ~(TRACKER_PAGE_SIZE - 1);
|
||||
const auto right_begin = right & ~(TRACKER_PAGE_SIZE - 1);
|
||||
const auto right_end = (right + right_size + TRACKER_PAGE_SIZE - 1) & ~(TRACKER_PAGE_SIZE - 1);
|
||||
return left_begin < right_end && right_begin < left_end;
|
||||
}
|
||||
|
||||
void BufferCache::Upload(CommandBuffer& command, Buffer& destination, uint64_t destination_offset,
|
||||
const void* source, uint64_t size) {
|
||||
auto* bytes = static_cast<const uint8_t*>(source);
|
||||
while (size != 0) {
|
||||
const auto chunk = std::min(size, m_staging_buffer.Size());
|
||||
const auto stage_offset = m_staging_buffer.Copy(bytes, chunk, 4);
|
||||
destination.CopyFrom(command, m_staging_buffer, stage_offset, destination_offset, chunk,
|
||||
vk::AccessFlagBits::eHostWrite);
|
||||
bytes += chunk;
|
||||
destination_offset += chunk;
|
||||
size -= chunk;
|
||||
}
|
||||
}
|
||||
|
||||
bool BufferCache::ResolveOverlap(CacheRange& merged, CacheRange candidate) noexcept {
|
||||
if (merged.address == 0 || merged.size == 0 || candidate.address == 0 || candidate.size == 0 ||
|
||||
merged.size > UINT64_MAX - merged.address ||
|
||||
candidate.size > UINT64_MAX - candidate.address) {
|
||||
EXIT("BufferCache: invalid overlap-merge range\n");
|
||||
}
|
||||
const auto merged_end = merged.address + merged.size;
|
||||
const auto candidate_end = candidate.address + candidate.size;
|
||||
if (merged.address >= candidate_end || candidate.address >= merged_end) {
|
||||
return false;
|
||||
}
|
||||
const auto address = std::min(merged.address, candidate.address);
|
||||
const auto end = std::max(merged_end, candidate_end);
|
||||
merged = {.address = address, .size = end - address};
|
||||
return true;
|
||||
}
|
||||
|
||||
struct BufferCache::CachedBuffer {
|
||||
uint64_t vaddr = 0;
|
||||
uint64_t size = 0;
|
||||
std::shared_ptr<Buffer> buffer;
|
||||
uint64_t tick_accessed_last = 0;
|
||||
};
|
||||
|
||||
struct BufferCache::DownloadCopy {
|
||||
std::shared_ptr<Buffer> owner;
|
||||
uint64_t source_offset = 0;
|
||||
uint64_t address = 0;
|
||||
uint64_t size = 0;
|
||||
};
|
||||
|
||||
struct BufferCache::DownloadRange {
|
||||
uint64_t address = 0;
|
||||
uint64_t size = 0;
|
||||
uint64_t offset = 0;
|
||||
};
|
||||
|
||||
struct BufferCache::RetiredBuffer {
|
||||
uint64_t address = 0;
|
||||
uint64_t size = 0;
|
||||
std::shared_ptr<Buffer> owner;
|
||||
};
|
||||
|
||||
std::pair<uint64_t, uint64_t> BufferCache::DownloadEnvelope(const DownloadCopy& copy) {
|
||||
if (copy.owner == nullptr || copy.size == 0 || copy.source_offset > copy.owner->Size() ||
|
||||
copy.size > copy.owner->Size() - copy.source_offset) {
|
||||
EXIT("BufferCache: invalid download copy\n");
|
||||
}
|
||||
const auto begin = copy.source_offset & ~uint64_t {3};
|
||||
if (copy.source_offset > UINT64_MAX - copy.size ||
|
||||
copy.source_offset + copy.size > UINT64_MAX - 3) {
|
||||
EXIT("BufferCache: download copy alignment overflow\n");
|
||||
}
|
||||
const auto end = (copy.source_offset + copy.size + 3) & ~uint64_t {3};
|
||||
if (end > copy.owner->Size()) {
|
||||
EXIT("BufferCache: aligned download copy exceeds its owner\n");
|
||||
}
|
||||
return {begin, end - begin};
|
||||
}
|
||||
|
||||
std::vector<BufferCache::DownloadRange>
|
||||
BufferCache::RecordDownloads(std::span<const DownloadCopy> copies) {
|
||||
uint64_t reservation_size = 0;
|
||||
for (const auto& copy: copies) {
|
||||
const auto [source_begin, envelope_size] = DownloadEnvelope(copy);
|
||||
(void)source_begin;
|
||||
if (envelope_size > UINT64_MAX - (DOWNLOAD_ALIGNMENT - 1)) {
|
||||
EXIT("BufferCache: download batch alignment overflow\n");
|
||||
}
|
||||
const auto aligned_size = AlignDownload(envelope_size);
|
||||
if (aligned_size > UINT64_MAX - reservation_size) {
|
||||
EXIT("BufferCache: download batch overflow\n");
|
||||
}
|
||||
reservation_size += aligned_size;
|
||||
}
|
||||
if (reservation_size == 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto& download = m_download_buffer;
|
||||
const auto [mapped, base_offset] = download.Map(reservation_size, DOWNLOAD_ALIGNMENT);
|
||||
if (mapped == nullptr) {
|
||||
EXIT("BufferCache: download batch could not reserve the shared stream\n");
|
||||
}
|
||||
|
||||
std::vector<DownloadRange> downloads;
|
||||
downloads.reserve(copies.size());
|
||||
uint64_t cursor = 0;
|
||||
for (const auto& copy: copies) {
|
||||
const auto [source_begin, envelope_size] = DownloadEnvelope(copy);
|
||||
const auto prefix = copy.source_offset - source_begin;
|
||||
download.CopyFrom(m_scheduler.Current(), *copy.owner, source_begin, base_offset + cursor,
|
||||
envelope_size, vk::AccessFlagBits::eMemoryWrite, vk::AccessFlags {},
|
||||
vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite,
|
||||
vk::AccessFlagBits::eHostRead);
|
||||
downloads.push_back({copy.address, copy.size, base_offset + cursor + prefix});
|
||||
cursor += AlignDownload(envelope_size);
|
||||
}
|
||||
download.Commit();
|
||||
return downloads;
|
||||
}
|
||||
|
||||
void BufferCache::PublishDownloads(std::span<const DownloadRange> downloads) {
|
||||
for (const auto& range: downloads) {
|
||||
m_download_buffer.Invalidate(range.offset, range.size);
|
||||
Libs::LibKernel::Memory::WriteBacking(
|
||||
range.address, m_download_buffer.Mapped().data() + range.offset, range.size);
|
||||
}
|
||||
}
|
||||
|
||||
void BufferCache::QueueGarbageDownload(std::span<const DownloadCopy> copies, RetiredBuffer retire) {
|
||||
if (copies.empty()) {
|
||||
return;
|
||||
}
|
||||
auto downloads = RecordDownloads(copies);
|
||||
m_scheduler.DeferOperation(
|
||||
[this, downloads = std::move(downloads), retire = std::move(retire)]() mutable {
|
||||
PublishDownloads(downloads);
|
||||
{
|
||||
FaultSafeCacheLock lock(this, m_mutex);
|
||||
for (const auto& range: downloads) {
|
||||
m_gpu_modified_ranges.Subtract(range.address, range.size);
|
||||
}
|
||||
// ForEachDownloadRange reports full tracker pages, and every exact GPU-owned
|
||||
// interval on those pages was downloaded and removed. Clearing the original
|
||||
// query therefore cannot orphan a dirty sibling on an edge page.
|
||||
m_memory_tracker.UnmarkRegionAsGpuModified(retire.address, retire.size);
|
||||
if (m_memory_tracker.IsRegionGpuModified(retire.address, retire.size) ||
|
||||
!m_gpu_modified_ranges.Intersections(retire.address, retire.size).empty()) {
|
||||
EXIT("BufferCache: asynchronous garbage collection retained GPU ownership\n");
|
||||
}
|
||||
m_memory_tracker.UntrackMemory(retire.address, retire.size);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
BufferCache::BufferCache(GraphicContext& graphics, CommandScheduler& scheduler,
|
||||
PageManager& page_manager, TextureCache& texture_cache,
|
||||
ResourceMutex& resource_mutex)
|
||||
: m_graphics(graphics), m_scheduler(scheduler),
|
||||
m_gds_buffer(graphics, scheduler, MemoryUsage::Stream, 0, AllFlags, GdsBufferSize),
|
||||
m_memory_tracker(page_manager),
|
||||
m_staging_buffer(graphics, scheduler, MemoryUsage::Upload, 512 * MiB),
|
||||
m_stream_buffer(graphics, scheduler, MemoryUsage::Stream, 64 * MiB),
|
||||
m_download_buffer(graphics, scheduler, MemoryUsage::Download, 32 * MiB),
|
||||
m_device_buffer(graphics, scheduler, MemoryUsage::DeviceLocal, 128 * MiB),
|
||||
m_texture_cache(texture_cache), m_resource_mutex(resource_mutex) {
|
||||
std::memset(m_gds_buffer.Mapped().data(), 0, static_cast<size_t>(m_gds_buffer.Size()));
|
||||
m_gds_buffer.Flush(0, m_gds_buffer.Size());
|
||||
if (!m_graphics.CanReportMemoryUsage()) {
|
||||
return;
|
||||
}
|
||||
constexpr int64_t GiB = 1024ll * 1024 * 1024;
|
||||
constexpr int64_t target_threshold = 8 * GiB;
|
||||
const auto budget =
|
||||
static_cast<int64_t>(std::min<uint64_t>(m_graphics.GetTotalMemoryBudget(), INT64_MAX));
|
||||
const auto threshold = std::min(budget, target_threshold);
|
||||
const auto expected = std::min(budget - 6 * threshold / 10, budget - GiB);
|
||||
const auto critical = std::min(budget - 2 * threshold / 10, budget - GiB / 2);
|
||||
m_trigger_gc_memory = static_cast<uint64_t>(std::max<int64_t>(expected, GiB));
|
||||
m_critical_gc_memory = static_cast<uint64_t>(std::max<int64_t>(critical, 2 * GiB));
|
||||
}
|
||||
|
||||
BufferCache::~BufferCache() {
|
||||
if (!m_gpu_modified_ranges.Empty()) {
|
||||
EXIT("BufferCache: destroyed with pending GPU-modified ranges\n");
|
||||
}
|
||||
for (const auto& [vaddr, cached]: m_buffers) {
|
||||
(void)vaddr;
|
||||
if (m_memory_tracker.IsRegionGpuModified(cached->vaddr, cached->size)) {
|
||||
EXIT("BufferCache: destroyed with GPU-modified buffer\n");
|
||||
}
|
||||
}
|
||||
m_buffers.clear();
|
||||
}
|
||||
|
||||
StreamBuffer& BufferCache::GetUtilityBuffer(MemoryUsage usage) noexcept {
|
||||
switch (usage) {
|
||||
case MemoryUsage::Upload: return m_staging_buffer;
|
||||
case MemoryUsage::Stream: return m_stream_buffer;
|
||||
case MemoryUsage::Download: return m_download_buffer;
|
||||
case MemoryUsage::DeviceLocal: return m_device_buffer;
|
||||
}
|
||||
EXIT("BufferCache: invalid utility-buffer usage\n");
|
||||
}
|
||||
|
||||
BufferBinding BufferCache::UploadTransient(const void* data, uint64_t size, uint64_t alignment) {
|
||||
EXIT_IF(data == nullptr || size == 0);
|
||||
if (auto [mapped, offset] = m_stream_buffer.Map(size, alignment, false); mapped != nullptr) {
|
||||
std::memcpy(mapped, data, static_cast<size_t>(size));
|
||||
m_stream_buffer.Commit();
|
||||
return {{}, m_stream_buffer.Handle(), offset};
|
||||
}
|
||||
auto owner =
|
||||
std::make_shared<Buffer>(m_graphics, m_scheduler, MemoryUsage::Upload, 0, AllFlags, size);
|
||||
owner->Write(0, data, size);
|
||||
return {owner, owner->Handle(), 0};
|
||||
}
|
||||
|
||||
void BufferCache::InvalidateMemory(uint64_t vaddr, uint64_t size) {
|
||||
if (vaddr == 0 || size == 0 || vaddr >= TRACKER_ADDRESS_SIZE ||
|
||||
size > TRACKER_ADDRESS_SIZE - vaddr) {
|
||||
EXIT("BufferCache: invalid memory-invalidation range\n");
|
||||
}
|
||||
if (!HasPageOverlap(vaddr, size)) {
|
||||
return;
|
||||
}
|
||||
m_memory_tracker.InvalidateRegion(vaddr, size,
|
||||
[this, vaddr, size] { ReadMemory(vaddr, size); });
|
||||
}
|
||||
|
||||
void BufferCache::ReadMemory(uint64_t vaddr, uint64_t size) {
|
||||
std::vector<DownloadCopy> copies;
|
||||
{
|
||||
FaultSafeCacheLock lock(this, m_mutex);
|
||||
m_memory_tracker.ForEachDownloadRange<false>(
|
||||
vaddr, size,
|
||||
[&](uint64_t address, uint64_t bytes) noexcept {
|
||||
m_memory_tracker.ValidateGpuDirtyPages(m_gpu_modified_ranges, address, bytes,
|
||||
"memory invalidation");
|
||||
},
|
||||
[&](uint64_t address, uint64_t bytes) noexcept {
|
||||
for (const auto range: m_gpu_modified_ranges.Intersections(address, bytes)) {
|
||||
for (uint64_t copied = 0; copied < range.size;) {
|
||||
const auto copy_address = range.address + copied;
|
||||
auto owner = m_buffers.upper_bound(copy_address);
|
||||
if (owner == m_buffers.begin()) {
|
||||
EXIT("BufferCache: invalidation readback has no buffer owner\n");
|
||||
}
|
||||
auto& cached = *std::prev(owner)->second;
|
||||
if (!cached.buffer->IsInBounds(copy_address, 1)) {
|
||||
EXIT(
|
||||
"BufferCache: invalidation readback is outside its buffer owner\n");
|
||||
}
|
||||
const auto copy_size = std::min(range.size - copied,
|
||||
cached.vaddr + cached.size - copy_address);
|
||||
copies.push_back({cached.buffer, cached.buffer->Offset(copy_address),
|
||||
copy_address, copy_size});
|
||||
copied += copy_size;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
if (copies.empty()) {
|
||||
return;
|
||||
}
|
||||
auto downloads = RecordDownloads(copies);
|
||||
m_scheduler.FinishCurrent();
|
||||
PublishDownloads(downloads);
|
||||
{
|
||||
FaultSafeCacheLock lock(this, m_mutex);
|
||||
for (const auto& range: downloads) {
|
||||
m_gpu_modified_ranges.Subtract(range.address, range.size);
|
||||
}
|
||||
// The enumeration above covered whole dirty pages and every exact interval on them.
|
||||
m_memory_tracker.UnmarkRegionAsGpuModified(vaddr, size);
|
||||
}
|
||||
}
|
||||
|
||||
void BufferCache::UnmapMemory(uint64_t vaddr, uint64_t size) {
|
||||
if (vaddr == 0 || size == 0 || size > UINT64_MAX - vaddr) {
|
||||
EXIT("BufferCache: invalid unmap range\n");
|
||||
}
|
||||
std::vector<DownloadCopy> copies;
|
||||
std::vector<std::pair<uint64_t, uint64_t>> modified_buffers;
|
||||
std::vector<std::pair<uint64_t, uint64_t>> retired_buffers;
|
||||
{
|
||||
FaultSafeCacheLock lock(this, m_mutex);
|
||||
for (const auto& [begin, cached]: m_buffers) {
|
||||
if (vaddr < begin + cached->size && begin < vaddr + size) {
|
||||
retired_buffers.emplace_back(begin, cached->size);
|
||||
}
|
||||
}
|
||||
for (const auto& [begin, cached]: m_buffers) {
|
||||
if (vaddr >= begin + cached->size || begin >= vaddr + size ||
|
||||
!m_memory_tracker.IsRegionGpuModified(begin, cached->size)) {
|
||||
continue;
|
||||
}
|
||||
const auto dirty = m_gpu_modified_ranges.Intersections(begin, cached->size);
|
||||
if (dirty.empty()) {
|
||||
EXIT("BufferCache: GPU-modified buffer has no dirty ranges\n");
|
||||
}
|
||||
modified_buffers.emplace_back(begin, cached->size);
|
||||
}
|
||||
for (const auto& [begin, bytes]: modified_buffers) {
|
||||
auto owner = m_buffers.find(begin);
|
||||
if (owner == m_buffers.end() || owner->second->size != bytes) {
|
||||
EXIT("BufferCache: unmap owner changed during collection\n");
|
||||
}
|
||||
auto& cached = *owner->second;
|
||||
m_memory_tracker.ForEachDownloadRange<false>(
|
||||
begin, cached.size,
|
||||
[&](uint64_t address, uint64_t bytes) noexcept {
|
||||
m_memory_tracker.ValidateGpuDirtyPages(m_gpu_modified_ranges, address, bytes,
|
||||
"unmap");
|
||||
},
|
||||
[&](uint64_t address, uint64_t bytes) noexcept {
|
||||
for (const auto& range: m_gpu_modified_ranges.Intersections(address, bytes)) {
|
||||
copies.push_back(
|
||||
{cached.buffer, range.address - begin, range.address, range.size});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
if (!copies.empty()) {
|
||||
auto downloads = RecordDownloads(copies);
|
||||
m_scheduler.FinishCurrent();
|
||||
PublishDownloads(downloads);
|
||||
} else if (!retired_buffers.empty()) {
|
||||
// Image uploads can reference a clean cached buffer without owning it. Submit the active
|
||||
// command stream before removing such backing.
|
||||
m_scheduler.FinishCurrent();
|
||||
}
|
||||
{
|
||||
FaultSafeCacheLock lock(this, m_mutex);
|
||||
for (const auto& [begin, bytes]: modified_buffers) {
|
||||
m_gpu_modified_ranges.Subtract(begin, bytes);
|
||||
m_memory_tracker.UnmarkRegionAsGpuModified(begin, bytes);
|
||||
}
|
||||
for (const auto& [begin, bytes]: retired_buffers) {
|
||||
m_memory_tracker.MarkRegionAsCpuModified(begin, bytes);
|
||||
}
|
||||
if (!m_gpu_modified_ranges.Intersections(vaddr, size).empty()) {
|
||||
EXIT("BufferCache: unmap retained dirty byte ranges\n");
|
||||
}
|
||||
m_memory_tracker.UntrackMemory(vaddr, size);
|
||||
for (auto it = m_buffers.begin(); it != m_buffers.end();) {
|
||||
if (vaddr < it->first + it->second->size && it->first < vaddr + size) {
|
||||
if (it->second->size > m_total_used_memory) {
|
||||
EXIT("BufferCache: allocation accounting underflow\n");
|
||||
}
|
||||
m_total_used_memory -= it->second->size;
|
||||
it = m_buffers.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BufferCache::CachedBuffer& BufferCache::GetOrCreateBuffer(CommandBuffer& command, uint64_t vaddr,
|
||||
uint64_t size) {
|
||||
const auto begin = AlignDown(vaddr);
|
||||
const auto end = AlignUp(vaddr + size);
|
||||
auto it = m_buffers.upper_bound(vaddr);
|
||||
if (it != m_buffers.begin()) {
|
||||
auto previous = std::prev(it);
|
||||
if (previous->second->buffer->IsInBounds(vaddr, size)) {
|
||||
it = previous;
|
||||
}
|
||||
}
|
||||
if (it != m_buffers.end() && it->second->buffer->IsInBounds(vaddr, size)) {
|
||||
it->second->tick_accessed_last = m_gc_tick;
|
||||
return *it->second;
|
||||
}
|
||||
|
||||
CacheRange merged {.address = begin, .size = end - begin};
|
||||
using Iterator = decltype(m_buffers.begin());
|
||||
std::vector<Iterator> overlaps;
|
||||
auto first = m_buffers.lower_bound(begin);
|
||||
if (first != m_buffers.begin()) {
|
||||
auto previous = std::prev(first);
|
||||
if (ResolveOverlap(merged, {previous->second->vaddr, previous->second->size})) {
|
||||
first = previous;
|
||||
}
|
||||
}
|
||||
for (auto candidate = first; candidate != m_buffers.end(); ++candidate) {
|
||||
if (candidate->first >= merged.address + merged.size) {
|
||||
break;
|
||||
}
|
||||
if (ResolveOverlap(merged, {candidate->second->vaddr, candidate->second->size})) {
|
||||
overlaps.push_back(candidate);
|
||||
}
|
||||
}
|
||||
for (const auto overlap: overlaps) {
|
||||
auto& old = *overlap->second;
|
||||
std::vector<std::pair<uint64_t, uint64_t>> uploads;
|
||||
m_memory_tracker.ForEachUploadRange(
|
||||
old.vaddr, old.size, false,
|
||||
[&](uint64_t address, uint64_t bytes) noexcept {
|
||||
uploads.emplace_back(address, bytes);
|
||||
},
|
||||
[&]() noexcept {
|
||||
for (const auto& [address, bytes]: uploads) {
|
||||
Upload(command, *old.buffer, old.buffer->Offset(address),
|
||||
reinterpret_cast<const void*>(address), bytes);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
auto cached = std::make_unique<CachedBuffer>();
|
||||
cached->vaddr = merged.address;
|
||||
cached->size = merged.size;
|
||||
cached->tick_accessed_last = m_gc_tick;
|
||||
cached->buffer = std::make_shared<Buffer>(m_graphics, m_scheduler, MemoryUsage::DeviceLocal,
|
||||
merged.address, AllFlags, merged.size);
|
||||
for (const auto overlap: overlaps) {
|
||||
const auto& old = *overlap->second;
|
||||
cached->buffer->CopyFrom(command, *old.buffer, 0, old.vaddr - cached->vaddr, old.size);
|
||||
command.RetainResourceUntilFence(old.buffer);
|
||||
}
|
||||
for (const auto overlap: overlaps) {
|
||||
if (overlap->second->size > m_total_used_memory) {
|
||||
EXIT("BufferCache: allocation accounting underflow\n");
|
||||
}
|
||||
m_total_used_memory -= overlap->second->size;
|
||||
m_buffers.erase(overlap);
|
||||
}
|
||||
m_total_used_memory += cached->size;
|
||||
return *m_buffers.emplace(cached->vaddr, std::move(cached)).first->second;
|
||||
}
|
||||
|
||||
BufferBinding BufferCache::ObtainBuffer(CommandBuffer& command, uint64_t vaddr, uint64_t size,
|
||||
bool is_written, bool is_read, bool is_formatted) {
|
||||
if (command.IsInvalid() || command.IsExecute()) {
|
||||
EXIT("BufferCache: buffer request requires a recording command buffer\n");
|
||||
}
|
||||
std::lock_guard transaction(m_resource_mutex);
|
||||
|
||||
if (is_read && !is_written && size <= CACHING_PAGE_SIZE &&
|
||||
!m_memory_tracker.IsRegionGpuModified(vaddr, size) &&
|
||||
m_memory_tracker.IsRegionCpuModified(vaddr, size)) {
|
||||
const auto alignment = std::max<uint64_t>(
|
||||
m_graphics.physical_device_properties.limits.minUniformBufferOffsetAlignment, 1);
|
||||
if (auto [mapped, offset] = m_stream_buffer.Map(size, alignment, false);
|
||||
mapped != nullptr) {
|
||||
if (Libs::LibKernel::Memory::TryReadBacking(vaddr, mapped, size)) {
|
||||
m_stream_buffer.Commit();
|
||||
return {{}, m_stream_buffer.Handle(), offset};
|
||||
}
|
||||
} else {
|
||||
auto owner = std::make_shared<Buffer>(m_graphics, m_scheduler, MemoryUsage::Upload, 0,
|
||||
AllFlags, size);
|
||||
if (Libs::LibKernel::Memory::TryReadBacking(vaddr, owner->Mapped().data(), size)) {
|
||||
owner->Flush(0, size);
|
||||
return {owner, owner->Handle(), 0};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (is_formatted && is_written) {
|
||||
(void)m_texture_cache.InvalidateMemoryFromGPU(vaddr, size, true);
|
||||
}
|
||||
|
||||
FaultSafeCacheLock lock(this, m_mutex);
|
||||
auto& cached = GetOrCreateBuffer(command, vaddr, size);
|
||||
std::vector<std::pair<uint64_t, uint64_t>> uploads;
|
||||
m_memory_tracker.ForEachUploadRange(
|
||||
vaddr, size, is_written,
|
||||
[&](uint64_t address, uint64_t bytes) noexcept { uploads.emplace_back(address, bytes); },
|
||||
[&]() noexcept {
|
||||
for (const auto& [address, bytes]: uploads) {
|
||||
Upload(command, *cached.buffer, cached.buffer->Offset(address),
|
||||
reinterpret_cast<const void*>(address), bytes);
|
||||
}
|
||||
});
|
||||
if (is_written) {
|
||||
m_gpu_modified_ranges.Add(vaddr, size);
|
||||
}
|
||||
if (is_formatted && is_read && !is_written) {
|
||||
(void)SynchronizeBufferFromImage(*cached.buffer, vaddr, size);
|
||||
}
|
||||
return {cached.buffer, cached.buffer->Handle(), cached.buffer->Offset(vaddr)};
|
||||
}
|
||||
|
||||
std::shared_ptr<Buffer> BufferCache::ObtainNullBuffer() {
|
||||
std::shared_ptr<Buffer> buffer;
|
||||
{
|
||||
FaultSafeCacheLock lock(this, m_mutex);
|
||||
if (m_null_buffer != nullptr) {
|
||||
return m_null_buffer;
|
||||
}
|
||||
m_null_buffer = std::make_shared<Buffer>(m_graphics, m_scheduler, MemoryUsage::DeviceLocal,
|
||||
0, AllFlags, 16);
|
||||
buffer = m_null_buffer;
|
||||
}
|
||||
const std::array<uint8_t, 16> zeros {};
|
||||
Upload(m_scheduler.Current(), *buffer, 0, zeros.data(), zeros.size());
|
||||
return buffer;
|
||||
}
|
||||
|
||||
ImageBufferSource BufferCache::ObtainBufferForImage(uint64_t vaddr, uint64_t size) {
|
||||
if (vaddr == 0 || size == 0 || vaddr >= TRACKER_ADDRESS_SIZE ||
|
||||
size > TRACKER_ADDRESS_SIZE - vaddr) {
|
||||
EXIT("BufferCache: invalid image source\n");
|
||||
}
|
||||
auto find_owner = [&]() {
|
||||
auto owner = m_buffers.upper_bound(vaddr);
|
||||
if (owner == m_buffers.begin()) {
|
||||
return m_buffers.end();
|
||||
}
|
||||
--owner;
|
||||
return owner->second->buffer->IsInBounds(vaddr, size) ? owner : m_buffers.end();
|
||||
};
|
||||
|
||||
{
|
||||
FaultSafeCacheLock lock(this, m_mutex);
|
||||
const bool cpu_modified = m_memory_tracker.IsRegionCpuModified(vaddr, size);
|
||||
const bool gpu_modified = m_memory_tracker.IsRegionGpuModified(vaddr, size);
|
||||
const auto dirty = m_gpu_modified_ranges.Intersections(vaddr, size);
|
||||
const bool has_dirty_buffer_source = !dirty.empty();
|
||||
m_memory_tracker.ValidateGpuDirtyOwnership(m_gpu_modified_ranges, vaddr, size,
|
||||
"image source");
|
||||
|
||||
auto owner = find_owner();
|
||||
if (has_dirty_buffer_source && owner == m_buffers.end()) {
|
||||
CacheRange merged {.address = AlignDown(vaddr),
|
||||
.size = AlignUp(vaddr + size) - AlignDown(vaddr)};
|
||||
using Iterator = decltype(m_buffers.begin());
|
||||
std::vector<Iterator> overlaps;
|
||||
auto first = m_buffers.lower_bound(merged.address);
|
||||
if (first != m_buffers.begin()) {
|
||||
auto previous = std::prev(first);
|
||||
if (ResolveOverlap(merged, {previous->second->vaddr, previous->second->size})) {
|
||||
first = previous;
|
||||
}
|
||||
}
|
||||
for (auto candidate = first; candidate != m_buffers.end(); ++candidate) {
|
||||
if (candidate->first >= merged.address + merged.size) {
|
||||
break;
|
||||
}
|
||||
if (ResolveOverlap(merged, {candidate->second->vaddr, candidate->second->size})) {
|
||||
overlaps.push_back(candidate);
|
||||
}
|
||||
}
|
||||
if (overlaps.empty()) {
|
||||
EXIT("BufferCache: GPU-dirty image source has no native buffer\n");
|
||||
}
|
||||
|
||||
auto cached = std::make_unique<CachedBuffer>();
|
||||
cached->vaddr = merged.address;
|
||||
cached->size = merged.size;
|
||||
cached->tick_accessed_last = m_gc_tick;
|
||||
cached->buffer =
|
||||
std::make_shared<Buffer>(m_graphics, m_scheduler, MemoryUsage::DeviceLocal,
|
||||
merged.address, AllFlags, merged.size);
|
||||
for (const auto overlap: overlaps) {
|
||||
const auto& old = *overlap->second;
|
||||
cached->buffer->CopyFrom(m_scheduler.Current(), *old.buffer, 0,
|
||||
old.vaddr - cached->vaddr, old.size);
|
||||
m_scheduler.Current().RetainResourceUntilFence(old.buffer);
|
||||
}
|
||||
for (const auto overlap: overlaps) {
|
||||
if (overlap->second->size > m_total_used_memory) {
|
||||
EXIT("BufferCache: allocation accounting underflow\n");
|
||||
}
|
||||
m_total_used_memory -= overlap->second->size;
|
||||
m_buffers.erase(overlap);
|
||||
}
|
||||
m_total_used_memory += cached->size;
|
||||
owner = m_buffers.emplace(cached->vaddr, std::move(cached)).first;
|
||||
if (!owner->second->buffer->IsInBounds(vaddr, size)) {
|
||||
EXIT("BufferCache: merged image source does not contain the requested range\n");
|
||||
}
|
||||
}
|
||||
if (owner != m_buffers.end() && !cpu_modified &&
|
||||
(!gpu_modified || has_dirty_buffer_source)) {
|
||||
owner->second->tick_accessed_last = m_gc_tick;
|
||||
return {owner->second->buffer.get(), owner->second->buffer->Offset(vaddr)};
|
||||
}
|
||||
if (has_dirty_buffer_source && owner == m_buffers.end()) {
|
||||
EXIT("BufferCache: GPU-dirty image source could not resolve its native owner\n");
|
||||
}
|
||||
}
|
||||
|
||||
auto [staging, stage_offset] = m_staging_buffer.Map(size, 16);
|
||||
if (staging == nullptr || !Libs::LibKernel::Memory::TryReadBacking(vaddr, staging, size)) {
|
||||
EXIT("BufferCache: failed to read mapped guest image backing\n");
|
||||
}
|
||||
m_staging_buffer.Commit();
|
||||
|
||||
FaultSafeCacheLock lock(this, m_mutex);
|
||||
const auto dirty = m_gpu_modified_ranges.Intersections(vaddr, size);
|
||||
const bool has_dirty_buffer_source = !dirty.empty();
|
||||
auto owner = find_owner();
|
||||
if (has_dirty_buffer_source && owner == m_buffers.end()) {
|
||||
EXIT("BufferCache: GPU-dirty image source lost its native owner\n");
|
||||
}
|
||||
if (owner == m_buffers.end() ||
|
||||
(m_memory_tracker.IsRegionGpuModified(vaddr, size) && !has_dirty_buffer_source)) {
|
||||
return {&m_staging_buffer, stage_offset};
|
||||
}
|
||||
|
||||
auto& cached = *owner->second;
|
||||
cached.tick_accessed_last = m_gc_tick;
|
||||
std::vector<std::pair<uint64_t, uint64_t>> uploads;
|
||||
m_memory_tracker.ForEachUploadRange(
|
||||
vaddr, size, false,
|
||||
[&](uint64_t address, uint64_t upload_size) noexcept {
|
||||
uploads.emplace_back(address, upload_size);
|
||||
},
|
||||
[&]() noexcept {
|
||||
for (const auto& [address, upload_size]: uploads) {
|
||||
cached.buffer->CopyFrom(
|
||||
m_scheduler.Current(), m_staging_buffer, stage_offset + address - vaddr,
|
||||
cached.buffer->Offset(address), upload_size, vk::AccessFlagBits::eHostWrite);
|
||||
}
|
||||
});
|
||||
return {cached.buffer.get(), cached.buffer->Offset(vaddr)};
|
||||
}
|
||||
|
||||
void BufferCache::WriteHostMemory(uint64_t vaddr, std::span<const uint8_t> data) {
|
||||
if (vaddr == 0 || data.empty() || data.size() > UINT64_MAX - vaddr) {
|
||||
EXIT("BufferCache: invalid host DMA write\n");
|
||||
}
|
||||
Libs::LibKernel::Memory::WriteBacking(vaddr, data.data(), data.size());
|
||||
|
||||
FaultSafeCacheLock lock(this, m_mutex);
|
||||
const auto end = vaddr + data.size();
|
||||
for (auto& [address, cached]: m_buffers) {
|
||||
const auto cached_end = address + cached->size;
|
||||
const auto begin = std::max(vaddr, address);
|
||||
const auto range_end = std::min(end, cached_end);
|
||||
if (begin >= range_end) {
|
||||
continue;
|
||||
}
|
||||
Upload(m_scheduler.Current(), *cached->buffer, cached->buffer->Offset(begin),
|
||||
data.data() + begin - vaddr, range_end - begin);
|
||||
cached->tick_accessed_last = m_gc_tick;
|
||||
}
|
||||
}
|
||||
|
||||
void BufferCache::FillBuffer(uint64_t vaddr, uint64_t size, uint32_t value, bool is_gds) {
|
||||
if ((vaddr & 3u) != 0 || size == 0 || (size & 3u) != 0 || size > UINT64_MAX - vaddr) {
|
||||
EXIT("BufferCache: fill range must be dword aligned\n");
|
||||
}
|
||||
if (is_gds) {
|
||||
if (vaddr > m_gds_buffer.Size() || size > m_gds_buffer.Size() - vaddr) {
|
||||
EXIT("BufferCache: GDS fill range is out of bounds\n");
|
||||
}
|
||||
m_gds_buffer.Fill(vaddr, size, value);
|
||||
return;
|
||||
}
|
||||
if (vaddr == 0) {
|
||||
EXIT("BufferCache: invalid fill memory address\n");
|
||||
}
|
||||
(void)m_texture_cache.ClearMeta(vaddr);
|
||||
{
|
||||
std::lock_guard transaction(m_resource_mutex);
|
||||
const auto region = m_texture_cache.QueryRegion(vaddr, size);
|
||||
if (!HasGpuDirtyBytes(vaddr, size) && !region.gpu_image_bytes) {
|
||||
if (region.image_bytes) {
|
||||
m_texture_cache.InvalidateMemory(vaddr, size);
|
||||
}
|
||||
std::array<uint32_t, 4096> values;
|
||||
values.fill(value);
|
||||
const std::span<const uint8_t> bytes {reinterpret_cast<const uint8_t*>(values.data()),
|
||||
sizeof(values)};
|
||||
for (uint64_t offset = 0; offset < size;) {
|
||||
const auto chunk = std::min<uint64_t>(size - offset, bytes.size());
|
||||
WriteHostMemory(vaddr + offset, bytes.first(chunk));
|
||||
offset += chunk;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
auto& command = m_scheduler.Current();
|
||||
auto dst = ObtainBuffer(command, vaddr, size, true, false, true);
|
||||
EXIT_IF(dst.buffer == nullptr || dst.owner == nullptr);
|
||||
command.RetainResourceUntilFence(dst.owner);
|
||||
auto owner = std::static_pointer_cast<Buffer>(dst.owner);
|
||||
owner->Fill(dst.offset, size, value);
|
||||
}
|
||||
|
||||
void BufferCache::CopyBuffer(uint64_t dst_vaddr, uint64_t src_vaddr, uint64_t size, bool dst_gds,
|
||||
bool src_gds) {
|
||||
const bool dst_memory = !dst_gds;
|
||||
const bool src_memory = !src_gds;
|
||||
if ((dst_memory && dst_vaddr == 0) || (src_memory && src_vaddr == 0) || size == 0 ||
|
||||
((dst_vaddr | src_vaddr | size) & 3u) != 0 || size > UINT64_MAX - dst_vaddr ||
|
||||
size > UINT64_MAX - src_vaddr || (dst_gds && src_gds) ||
|
||||
(dst_gds == src_gds && src_vaddr < dst_vaddr + size && dst_vaddr < src_vaddr + size) ||
|
||||
(dst_gds && (dst_vaddr > m_gds_buffer.Size() || size > m_gds_buffer.Size() - dst_vaddr)) ||
|
||||
(src_gds && (src_vaddr > m_gds_buffer.Size() || size > m_gds_buffer.Size() - src_vaddr))) {
|
||||
EXIT("BufferCache: invalid or overlapping copy range\n");
|
||||
}
|
||||
if (src_memory || dst_memory) {
|
||||
std::lock_guard transaction(m_resource_mutex);
|
||||
const auto src_region =
|
||||
src_memory ? m_texture_cache.QueryRegion(src_vaddr, size) : TextureCache::RegionInfo {};
|
||||
const auto dst_region =
|
||||
dst_memory ? m_texture_cache.QueryRegion(dst_vaddr, size) : TextureCache::RegionInfo {};
|
||||
if (src_memory && dst_memory && !HasGpuDirtyBytes(src_vaddr, size) &&
|
||||
!HasGpuDirtyBytes(dst_vaddr, size) && !src_region.gpu_image_bytes &&
|
||||
!dst_region.gpu_image_bytes) {
|
||||
if (dst_region.image_bytes) {
|
||||
m_texture_cache.InvalidateMemory(dst_vaddr, size);
|
||||
}
|
||||
std::array<uint8_t, 64 * 1024> bytes;
|
||||
for (uint64_t offset = 0; offset < size;) {
|
||||
const auto chunk = std::min<uint64_t>(size - offset, bytes.size());
|
||||
if (!Libs::LibKernel::Memory::TryReadBacking(src_vaddr + offset, bytes.data(),
|
||||
chunk)) {
|
||||
EXIT("BufferCache: host DMA source has no direct backing\n");
|
||||
}
|
||||
WriteHostMemory(dst_vaddr + offset, std::span {bytes}.first(chunk));
|
||||
offset += chunk;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
auto& command = m_scheduler.Current();
|
||||
auto src = src_memory ? ObtainBuffer(command, src_vaddr, size, false, true, true)
|
||||
: BufferBinding {.buffer = m_gds_buffer.Handle(), .offset = src_vaddr};
|
||||
auto dst = dst_memory ? ObtainBuffer(command, dst_vaddr, size, true, false, true)
|
||||
: BufferBinding {.buffer = m_gds_buffer.Handle(), .offset = dst_vaddr};
|
||||
EXIT_IF(src.buffer == nullptr || dst.buffer == nullptr || (dst_memory && dst.owner == nullptr));
|
||||
if (src.owner != nullptr) {
|
||||
command.RetainResourceUntilFence(src.owner);
|
||||
}
|
||||
if (dst.owner != nullptr) {
|
||||
command.RetainResourceUntilFence(dst.owner);
|
||||
}
|
||||
if (src.buffer == dst.buffer && src.offset < dst.offset + size &&
|
||||
dst.offset < src.offset + size) {
|
||||
EXIT("BufferCache: resolved Vulkan copy ranges overlap\n");
|
||||
}
|
||||
auto& source = src.owner != nullptr ? *std::static_pointer_cast<Buffer>(src.owner)
|
||||
: src_gds ? m_gds_buffer
|
||||
: m_stream_buffer;
|
||||
auto& destination =
|
||||
dst.owner != nullptr ? *std::static_pointer_cast<Buffer>(dst.owner) : m_gds_buffer;
|
||||
if (source.Handle() != src.buffer || destination.Handle() != dst.buffer) {
|
||||
EXIT("BufferCache: resolved copy owner does not match its Vulkan handle\n");
|
||||
}
|
||||
destination.CopyFrom(command, source, src.offset, dst.offset, size);
|
||||
}
|
||||
|
||||
bool BufferCache::HasPageOverlap(uint64_t vaddr, uint64_t size) {
|
||||
if (vaddr == 0 || size == 0 || vaddr >= TRACKER_ADDRESS_SIZE ||
|
||||
size > TRACKER_ADDRESS_SIZE - vaddr) {
|
||||
EXIT("BufferCache: invalid page-overlap query\n");
|
||||
}
|
||||
FaultSafeCacheLock lock(this, m_mutex);
|
||||
for (const auto& [address, cached]: m_buffers) {
|
||||
if (PageOverlaps(vaddr, size, address, cached->size)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool BufferCache::IsRegionGpuModified(uint64_t vaddr, uint64_t size) {
|
||||
return m_memory_tracker.IsRegionGpuModified(vaddr, size);
|
||||
}
|
||||
|
||||
bool BufferCache::HasGpuDirtyBytes(uint64_t vaddr, uint64_t size) {
|
||||
FaultSafeCacheLock lock(this, m_mutex);
|
||||
return !m_gpu_modified_ranges.Intersections(vaddr, size).empty();
|
||||
}
|
||||
|
||||
bool BufferCache::IsRegionCpuModified(uint64_t vaddr, uint64_t size) {
|
||||
return m_memory_tracker.IsRegionCpuModified(vaddr, size);
|
||||
}
|
||||
|
||||
void BufferCache::RunGarbageCollector() {
|
||||
std::lock_guard transaction(m_resource_mutex);
|
||||
const auto tick = m_gc_tick++;
|
||||
if (m_graphics.CanReportMemoryUsage()) {
|
||||
m_total_used_memory = m_graphics.GetDeviceMemoryUsage();
|
||||
}
|
||||
if (m_total_used_memory < m_trigger_gc_memory) {
|
||||
return;
|
||||
}
|
||||
|
||||
const bool aggressive = m_total_used_memory >= m_critical_gc_memory;
|
||||
const uint64_t age = std::min<uint64_t>(aggressive ? 80 : 160, tick);
|
||||
const size_t limit = aggressive ? 64 : 32;
|
||||
|
||||
std::vector<RetiredBuffer> retires;
|
||||
std::vector<std::pair<RetiredBuffer, std::vector<DownloadCopy>>> dirty_retires;
|
||||
{
|
||||
FaultSafeCacheLock lock(this, m_mutex);
|
||||
std::vector<uint64_t> candidates;
|
||||
for (const auto& [address, owner]: m_buffers) {
|
||||
const auto& cached = *owner;
|
||||
if (tick - std::min(tick, cached.tick_accessed_last) < age) {
|
||||
continue;
|
||||
}
|
||||
candidates.push_back(address);
|
||||
}
|
||||
std::ranges::sort(candidates, [&](uint64_t left, uint64_t right) {
|
||||
return m_buffers.at(left)->tick_accessed_last < m_buffers.at(right)->tick_accessed_last;
|
||||
});
|
||||
if (candidates.size() > limit) {
|
||||
candidates.resize(limit);
|
||||
}
|
||||
for (const auto address: candidates) {
|
||||
auto& cached = *m_buffers.at(address);
|
||||
m_memory_tracker.ValidateGpuDirtyOwnership(m_gpu_modified_ranges, cached.vaddr,
|
||||
cached.size, "garbage collection");
|
||||
retires.push_back({address, cached.size, cached.buffer});
|
||||
// GC runs immediately before submission. Preserve every source referenced by commands
|
||||
// already recorded in the active batch.
|
||||
m_scheduler.Current().RetainResourceUntilFence(cached.buffer);
|
||||
}
|
||||
for (const auto& retire: retires) {
|
||||
if (!m_memory_tracker.IsRegionGpuModified(retire.address, retire.size)) {
|
||||
continue;
|
||||
}
|
||||
auto& copies = dirty_retires.emplace_back(retire, std::vector<DownloadCopy> {}).second;
|
||||
m_memory_tracker.ForEachDownloadRange<false>(
|
||||
retire.address, retire.size,
|
||||
[&](uint64_t address, uint64_t size) noexcept {
|
||||
m_memory_tracker.ValidateGpuDirtyPages(m_gpu_modified_ranges, address, size,
|
||||
"garbage collection");
|
||||
},
|
||||
[&](uint64_t address, uint64_t size) noexcept {
|
||||
for (const auto range: m_gpu_modified_ranges.Intersections(address, size)) {
|
||||
copies.push_back({retire.owner, range.address - retire.address,
|
||||
range.address, range.size});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& [retire, copies]: dirty_retires) {
|
||||
QueueGarbageDownload(copies, std::move(retire));
|
||||
}
|
||||
|
||||
FaultSafeCacheLock lock(this, m_mutex);
|
||||
for (const auto& retire: retires) {
|
||||
auto found = m_buffers.find(retire.address);
|
||||
if (found == m_buffers.end() || found->second->size != retire.size ||
|
||||
found->second->buffer != retire.owner) {
|
||||
EXIT("BufferCache: garbage-collection owner changed during download\n");
|
||||
}
|
||||
if (!m_memory_tracker.IsRegionGpuModified(retire.address, retire.size)) {
|
||||
m_memory_tracker.UntrackMemory(retire.address, retire.size);
|
||||
}
|
||||
if (retire.size > m_total_used_memory) {
|
||||
EXIT("BufferCache: allocation accounting underflow\n");
|
||||
}
|
||||
m_total_used_memory -= retire.size;
|
||||
m_buffers.erase(found);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
#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 <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;
|
||||
};
|
||||
|
||||
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);
|
||||
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 RunGarbageCollector();
|
||||
|
||||
private:
|
||||
friend struct BufferCacheTestAccess;
|
||||
|
||||
struct CacheRange {
|
||||
uint64_t address = 0;
|
||||
uint64_t size = 0;
|
||||
};
|
||||
struct CachedBuffer;
|
||||
struct DownloadCopy;
|
||||
struct DownloadRange;
|
||||
struct RetiredBuffer;
|
||||
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]] bool SynchronizeBufferFromImage(Buffer& buffer, 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 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;
|
||||
MemoryTracker m_memory_tracker;
|
||||
StreamBuffer m_staging_buffer;
|
||||
StreamBuffer m_stream_buffer;
|
||||
StreamBuffer m_download_buffer;
|
||||
StreamBuffer m_device_buffer;
|
||||
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_
|
||||
+6
-5
@@ -1,13 +1,14 @@
|
||||
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_MULTILEVELPAGETABLE_H_
|
||||
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_MULTILEVELPAGETABLE_H_
|
||||
|
||||
#include "common/assert.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
@@ -59,7 +60,7 @@ public:
|
||||
|
||||
[[nodiscard]] Entry& GetOrCreate(size_t 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)];
|
||||
if (bucket == nullptr) {
|
||||
@@ -218,7 +219,7 @@ private:
|
||||
typename CoarseTable::PageRange coarse_range {};
|
||||
typename TrackingTable::PageRange tracking_range {};
|
||||
if (!CoarseTable::TryGetPageRange(address, size, coarse_range) ||
|
||||
!TrackingTable::TryGetPageRange(address, size, tracking_range)) {
|
||||
(!strict_bytes && !TrackingTable::TryGetPageRange(address, size, tracking_range))) {
|
||||
return {};
|
||||
}
|
||||
MembershipList candidates;
|
||||
@@ -229,8 +230,8 @@ private:
|
||||
}
|
||||
std::vector<OwnerT> result;
|
||||
for (const Registration* registration: candidates) {
|
||||
if ((!strict_bytes || Overlaps(registration->ranges, address, size)) &&
|
||||
HasTrackingMembership(registration, tracking_range) &&
|
||||
if ((strict_bytes ? Overlaps(registration->ranges, address, size)
|
||||
: HasTrackingMembership(registration, tracking_range)) &&
|
||||
predicate(registration->owner)) {
|
||||
result.push_back(registration->owner);
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
#include "graphics/host_gpu/renderer/resourceMutex.h"
|
||||
#include "graphics/host_gpu/renderer/cache/resourceMutex.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@
|
||||
namespace Libs::Graphics {
|
||||
|
||||
// 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 {
|
||||
public:
|
||||
class FaultScope final {
|
||||
+13
-4
@@ -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/logging/log.h"
|
||||
@@ -7,6 +7,13 @@
|
||||
|
||||
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) {
|
||||
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::kEight: aniso_ratio = 8.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;
|
||||
const auto result =
|
||||
g_render_ctx->GetGraphicCtx()->device.createSampler(&sampler_info, nullptr, &vk_sampler);
|
||||
const auto result = m_graphics.device.createSampler(&sampler_info, nullptr, &vk_sampler);
|
||||
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess || vk_sampler == nullptr);
|
||||
|
||||
m_samplers.emplace(key, vk_sampler);
|
||||
Vendored
+7
-2
@@ -14,10 +14,14 @@
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
struct GraphicContext;
|
||||
|
||||
class SamplerCache {
|
||||
public:
|
||||
SamplerCache() { EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread()); }
|
||||
~SamplerCache() { KYTY_NOT_IMPLEMENTED; }
|
||||
explicit SamplerCache(GraphicContext& graphics): m_graphics(graphics) {
|
||||
EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread());
|
||||
}
|
||||
~SamplerCache();
|
||||
KYTY_CLASS_NO_COPY(SamplerCache);
|
||||
|
||||
vk::Sampler GetSampler(const ShaderSamplerResource& r);
|
||||
@@ -37,6 +41,7 @@ private:
|
||||
}
|
||||
};
|
||||
|
||||
GraphicContext& m_graphics;
|
||||
Common::Mutex m_mutex;
|
||||
std::unordered_map<SamplerKey, vk::Sampler, SamplerKeyHash> m_samplers;
|
||||
};
|
||||
@@ -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, ©);
|
||||
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
@@ -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_
|
||||
+1949
File diff suppressed because it is too large
Load Diff
+191
@@ -0,0 +1,191 @@
|
||||
#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 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,
|
||||
uint64_t destination_size, 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);
|
||||
|
||||
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 BufferCache;
|
||||
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/tile.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/descriptorCache.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/renderContext.h"
|
||||
#include "graphics/host_gpu/transfer.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
#include "graphics/presentation/displayBuffer.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
@@ -25,15 +22,15 @@ namespace Libs::Graphics {
|
||||
static std::atomic<uint32_t> g_render_color_log_count = 0;
|
||||
|
||||
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
|
||||
void ResolveRenderColorTarget(uint64_t submit_id, CommandBuffer* buffer, const HW::Context& hw,
|
||||
RenderColorInfo* r, uint32_t render_target_slice_offset,
|
||||
uint32_t render_target_slot, bool ignore_target_mask,
|
||||
bool reuse_existing_render_texture) {
|
||||
void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandBuffer& buffer,
|
||||
RenderColorInfo& r,
|
||||
uint32_t render_target_slice_offset,
|
||||
uint32_t render_target_slot, bool ignore_target_mask,
|
||||
bool exact_format) {
|
||||
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(hw)
|
||||
const auto rt_slot = (render_target_slot == UINT32_MAX ? render_target_first_bound_slot(buffer)
|
||||
: render_target_slot);
|
||||
const auto& rt = hw.GetRenderTarget(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;
|
||||
}
|
||||
|
||||
r->target_slot = rt_slot;
|
||||
r->export_mapping = {};
|
||||
r.target_slot = rt_slot;
|
||||
r.export_mapping = {};
|
||||
|
||||
if (rt.base.addr == 0 || mask == 0) {
|
||||
if (graphics_debug_dump_enabled()) {
|
||||
@@ -58,51 +55,43 @@ void ResolveRenderColorTarget(uint64_t submit_id, CommandBuffer* buffer, const H
|
||||
}
|
||||
|
||||
// No color output
|
||||
r->type = RenderColorType::NoColorOutput;
|
||||
r->base_addr = 0;
|
||||
r->vulkan_buffer = nullptr;
|
||||
r->vulkan_view = nullptr;
|
||||
r->format = vk::Format::eUndefined;
|
||||
r->extent = {};
|
||||
r->base_mip_level = 0;
|
||||
r->buffer_size = 0;
|
||||
r->color_clear_enable = false;
|
||||
r->color_clear_value = {};
|
||||
r.type = RenderColorType::NoColorOutput;
|
||||
r.desc = {};
|
||||
r.base_addr = 0;
|
||||
r.image_id = {};
|
||||
r.image_view = nullptr;
|
||||
r.format = vk::Format::eUndefined;
|
||||
r.extent = {};
|
||||
r.base_mip_level = 0;
|
||||
r.base_array_layer = 0;
|
||||
r.buffer_size = 0;
|
||||
r.samples = 1;
|
||||
r.export_mapping = {};
|
||||
r.color_clear_enable = false;
|
||||
r.color_clear_value = {};
|
||||
return;
|
||||
}
|
||||
const bool msaa_compat =
|
||||
color_msaa_single_sample_compatible(rt.attrib.num_samples, rt.attrib.num_fragments);
|
||||
if (!msaa_compat && (rt.attrib.num_samples != 0 || rt.attrib.num_fragments != 0)) {
|
||||
EXIT("multisampled render targets are unsupported\n");
|
||||
const auto samples = render_sample_count(rt.attrib.num_fragments);
|
||||
if (samples == 0 || rt.attrib.num_samples != rt.attrib.num_fragments) {
|
||||
EXIT("unsupported render-target sample configuration: samples=%u fragments=%u\n",
|
||||
rt.attrib.num_samples, rt.attrib.num_fragments);
|
||||
}
|
||||
const auto view = ResolveTargetViewInfo(
|
||||
rt.view.base_array_slice_index, rt.view.last_array_slice_index, render_target_slice_offset);
|
||||
switch (view.type) {
|
||||
case TargetViewType::Image2D: break;
|
||||
case TargetViewType::Image2DArray:
|
||||
EXIT("layered render-target views are unsupported: base=%u count=%u\n", view.base_layer,
|
||||
view.layer_count);
|
||||
case TargetViewType::Image2D:
|
||||
case TargetViewType::Image2DArray: break;
|
||||
case TargetViewType::Unsupported:
|
||||
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,
|
||||
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;
|
||||
if (levels == 0 || levels > 16 || rt.view.current_mip_level >= levels) {
|
||||
EXIT("unsupported render-target mip range: current=%u levels=%u\n",
|
||||
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()) {
|
||||
static std::atomic_uint log_count = 0;
|
||||
const auto log_id = log_count.fetch_add(1, std::memory_order_relaxed);
|
||||
@@ -122,15 +111,26 @@ void ResolveRenderColorTarget(uint64_t submit_id, CommandBuffer* buffer, const H
|
||||
// SRGB clear words are still encoded as normalized component values.
|
||||
// Fast color clears are metadata driven and must be handled explicitly when
|
||||
// that metadata path is implemented; render-pass load must preserve contents.
|
||||
r->color_clear_enable = false;
|
||||
r->color_clear_value = {};
|
||||
r.color_clear_enable = false;
|
||||
r.color_clear_value = {};
|
||||
|
||||
uint32_t width = 0;
|
||||
uint32_t height = 0;
|
||||
uint32_t pitch = 0;
|
||||
uint64_t size = 0;
|
||||
bool tile = false;
|
||||
const bool standard64 =
|
||||
const bool volume = rt.attrib3.dimension == 2;
|
||||
if (rt.attrib3.dimension != 1 && !volume) {
|
||||
EXIT("unsupported render-target dimension: %u\n", rt.attrib3.dimension);
|
||||
}
|
||||
if (!volume && rt.attrib3.depth != 0) {
|
||||
EXIT("2D render target has nonzero depth: %u\n", rt.attrib3.depth);
|
||||
}
|
||||
if (volume && samples != 1) {
|
||||
EXIT("multisampled 3D render targets are unsupported\n");
|
||||
}
|
||||
const uint32_t depth = volume ? rt.attrib3.depth + 1u : 1u;
|
||||
const bool standard64 =
|
||||
rt.attrib3.tile_mode == Prospero::GpuEnumValue(Prospero::TileMode::kStandard64KB);
|
||||
|
||||
switch (rt.attrib3.tile_mode) {
|
||||
@@ -144,6 +144,9 @@ void ResolveRenderColorTarget(uint64_t submit_id, CommandBuffer* buffer, const H
|
||||
if (!tile && levels > 1) {
|
||||
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;
|
||||
height = rt.attrib2.height + 1;
|
||||
@@ -153,16 +156,17 @@ void ResolveRenderColorTarget(uint64_t submit_id, CommandBuffer* buffer, const H
|
||||
if (bytes_per_element == 0) {
|
||||
EXIT("render-target format has no valid element size\n");
|
||||
}
|
||||
const auto transfer_format = ImageOps::RenderTargetTransferFormat(bytes_per_element);
|
||||
if (standard64 &&
|
||||
(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.attrib.num_samples != 0 || rt.attrib.num_fragments != 0 || bytes_per_element != 4 ||
|
||||
rt.pitch.pitch_div8_minus1 != 0 || (rt.base.addr & 0xffffu) != 0 ||
|
||||
rt.info.fmask_compression_enable || rt.info.fmask_data_compression_disable ||
|
||||
rt.info.fmask_one_frag_mode || rt.info.cmask_fast_clear_enable ||
|
||||
rt.info.dcc_compression_enable || rt.info.cmask_is_linear != 0 ||
|
||||
rt.info.cmask_addr_type != 0 || rt.info.alt_tile_mode || rt.cmask.addr != 0 ||
|
||||
rt.fmask.addr != 0 || rt.dcc_addr.addr != 0 || rt.dcc.data_write_on_dcc_clear_to_reg)) {
|
||||
samples != 1 || bytes_per_element != 4 || rt.pitch.pitch_div8_minus1 != 0 ||
|
||||
(rt.base.addr & 0xffffu) != 0 || rt.info.fmask_compression_enable ||
|
||||
rt.info.fmask_data_compression_disable || rt.info.fmask_one_frag_mode ||
|
||||
rt.info.cmask_fast_clear_enable || rt.info.dcc_compression_enable ||
|
||||
rt.info.cmask_is_linear != 0 || rt.info.cmask_addr_type != 0 || rt.info.alt_tile_mode ||
|
||||
rt.cmask.addr != 0 || rt.fmask.addr != 0 || rt.dcc_addr.addr != 0 ||
|
||||
rt.dcc.data_write_on_dcc_clear_to_reg)) {
|
||||
EXIT("unsupported Standard64KB render target: addr=0x%016" PRIx64
|
||||
" 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",
|
||||
@@ -173,10 +177,14 @@ void ResolveRenderColorTarget(uint64_t submit_id, CommandBuffer* buffer, const H
|
||||
if (rt.pitch.pitch_div8_minus1 != 0) {
|
||||
pitch = (rt.pitch.pitch_div8_minus1 + 1u) << 3u;
|
||||
} else if (tile) {
|
||||
pitch = standard64
|
||||
? TileGetTexturePitch(Prospero::GpuEnumValue(Prospero::BufferFormat::k32Float),
|
||||
width, levels, rt.attrib3.tile_mode)
|
||||
: TileGetRenderTargetPitch(width, bytes_per_element);
|
||||
if (volume) {
|
||||
pitch = TileGetTexturePitch(transfer_format, width, levels, rt.attrib3.tile_mode);
|
||||
} else if (standard64) {
|
||||
pitch = TileGetTexturePitch(Prospero::GpuEnumValue(Prospero::BufferFormat::k32Float),
|
||||
width, levels, rt.attrib3.tile_mode);
|
||||
} else {
|
||||
pitch = TileGetRenderTargetPitch(width, bytes_per_element, rt.attrib.num_fragments);
|
||||
}
|
||||
if (pitch == 0) {
|
||||
EXIT("unsupported render-target pitch: width=%u bytes=%u\n", width, bytes_per_element);
|
||||
}
|
||||
@@ -184,149 +192,148 @@ void ResolveRenderColorTarget(uint64_t submit_id, CommandBuffer* buffer, const H
|
||||
pitch = width;
|
||||
}
|
||||
|
||||
if (tile) {
|
||||
TileSizeOffset mip_sizes[16] {};
|
||||
TilePaddedSize mip_padded[16] {};
|
||||
TileVolumeLayout volume_layout {};
|
||||
uint64_t backing_size = 0;
|
||||
if (volume) {
|
||||
if (!tile || !TileGetTextureVolumeLayout(transfer_format, width, height, depth, levels,
|
||||
rt.attrib3.tile_mode, volume_layout)) {
|
||||
EXIT("unsupported 3D render-target layout: %ux%ux%u levels=%u tile=%u\n", width, height,
|
||||
depth, levels, rt.attrib3.tile_mode);
|
||||
}
|
||||
size = volume_layout.block_slice_size;
|
||||
backing_size = volume_layout.total_size;
|
||||
} else if (tile) {
|
||||
TileSizeAlign layout {};
|
||||
bool valid_layout = false;
|
||||
if (standard64) {
|
||||
TileGetTextureSize(Prospero::GpuEnumValue(Prospero::BufferFormat::k32Float), width,
|
||||
height, pitch, levels, rt.attrib3.tile_mode, &layout, nullptr,
|
||||
nullptr);
|
||||
height, pitch, levels, rt.attrib3.tile_mode, &layout, mip_sizes,
|
||||
mip_padded);
|
||||
valid_layout = layout.size != 0 && layout.align == 65536;
|
||||
} else {
|
||||
valid_layout =
|
||||
levels == 1
|
||||
? TileGetRenderTargetSize(width, height, pitch, bytes_per_element, &layout)
|
||||
: TileGetRenderTargetMipLayout(width, height, pitch, bytes_per_element, levels,
|
||||
&layout, nullptr, nullptr);
|
||||
levels == 1 ? TileGetRenderTargetSize(width, height, pitch, bytes_per_element,
|
||||
layout, rt.attrib.num_fragments)
|
||||
: TileGetRenderTargetMipLayout(width, height, pitch, bytes_per_element,
|
||||
levels, layout, mip_sizes, mip_padded);
|
||||
}
|
||||
if (!valid_layout) {
|
||||
EXIT("unsupported render-target layout: %ux%u pitch=%u bytes=%u levels=%u\n", width,
|
||||
height, pitch, bytes_per_element, levels);
|
||||
}
|
||||
size = layout.size;
|
||||
if (rt.slice.slice_div64_minus1 != 0 &&
|
||||
(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
|
||||
"\n",
|
||||
(static_cast<uint64_t>(rt.slice.slice_div64_minus1) + 1u) * 64u, 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};
|
||||
}
|
||||
} 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 (rt.slice.slice_div64_minus1 != 0 &&
|
||||
(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
|
||||
"\n",
|
||||
(static_cast<uint64_t>(rt.slice.slice_div64_minus1) + 1u) * 64u, size);
|
||||
}
|
||||
if (size == 0 || (!volume && size > UINT64_MAX / view.image_layers)) {
|
||||
EXIT("render-target memory footprint is invalid\n");
|
||||
}
|
||||
const auto backing_size = size * view.image_layers;
|
||||
if (!volume) {
|
||||
backing_size = size * view.image_layers;
|
||||
}
|
||||
if (backing_size == 0) {
|
||||
EXIT("render-target backing is empty\n");
|
||||
}
|
||||
if (backing_size > TRACKER_ADDRESS_SIZE - rt.base.addr) {
|
||||
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),
|
||||
std::max(height >> rt.view.current_mip_level, 1u)};
|
||||
const uint32_t view_depth = std::max(depth >> rt.view.current_mip_level, 1u);
|
||||
if (volume &&
|
||||
(view.base_layer >= view_depth || view.layer_count > view_depth - view.base_layer)) {
|
||||
EXIT("3D render-target view exceeds mip depth: base=%u count=%u depth=%u mip=%u\n",
|
||||
view.base_layer, view.layer_count, view_depth, rt.view.current_mip_level);
|
||||
}
|
||||
|
||||
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
|
||||
" 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
|
||||
" 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,
|
||||
" extent=%ux%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 " samples=%u tile=%s\n",
|
||||
rt_slot, rt.base.addr, backing_size, width, height, depth, rt.view.current_mip_level,
|
||||
view_extent.width, view_extent.height, levels, pitch, rt.info.format,
|
||||
rt.info.channel_type, rt.info.channel_order, tile ? "tiled" : "linear",
|
||||
render_to_texture ? "RenderTexture" : "DisplayBuffer", video_image.size,
|
||||
video_image.pitch);
|
||||
rt.info.channel_type, rt.info.channel_order, samples, tile ? "tiled" : "linear");
|
||||
}
|
||||
|
||||
if (render_to_texture) {
|
||||
(void)reuse_existing_render_texture;
|
||||
RenderTargetInfo target {};
|
||||
target.address = rt.base.addr;
|
||||
target.size = backing_size;
|
||||
target.format = target_format.format;
|
||||
target.width = width;
|
||||
target.height = height;
|
||||
target.pitch = pitch;
|
||||
target.bytes_per_element = target_format.bytes_per_element;
|
||||
target.tile_mode = rt.attrib3.tile_mode;
|
||||
target.levels = levels;
|
||||
target.layers = view.image_layers;
|
||||
auto* texture_cache = g_render_ctx->GetTextureCache();
|
||||
auto* buffer_vulkan =
|
||||
texture_cache->FindRenderTarget(buffer, g_render_ctx->GetGraphicCtx(), target);
|
||||
r->type = RenderColorType::RenderTexture;
|
||||
r->base_addr = rt.base.addr;
|
||||
r->vulkan_buffer = buffer_vulkan;
|
||||
r->vulkan_view = texture_cache->GetRenderTargetAttachmentView(
|
||||
g_render_ctx->GetGraphicCtx(), buffer_vulkan, target.format, rt.view.current_mip_level,
|
||||
view.base_layer, view.layer_count);
|
||||
r->format = target.format;
|
||||
r->extent = view_extent;
|
||||
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");
|
||||
TextureCache::ImageDesc desc {};
|
||||
desc.type = TextureCache::BindingType::RenderTarget;
|
||||
desc.info.data = {rt.base.addr, backing_size};
|
||||
desc.info.pixel_format = target_format.format;
|
||||
desc.info.guest_format = transfer_format;
|
||||
desc.info.type = volume ? Prospero::ImageType::kColor3D : Prospero::ImageType::kColor2D;
|
||||
desc.info.extent = {width, height, depth};
|
||||
desc.info.resources = {levels, volume ? 1u : view.image_layers};
|
||||
desc.info.pitch = pitch;
|
||||
desc.info.bytes_per_block = bytes_per_element;
|
||||
desc.info.samples = samples;
|
||||
desc.info.tile_mode = rt.attrib3.tile_mode;
|
||||
for (uint32_t level = 0; level < levels; level++) {
|
||||
if (volume) {
|
||||
desc.info.mip_layout[level] = {
|
||||
volume_layout.level_offsets[level],
|
||||
volume_layout.level_sizes[level],
|
||||
volume_layout.level_widths[level],
|
||||
volume_layout.level_heights[level],
|
||||
};
|
||||
continue;
|
||||
}
|
||||
const auto level_offset =
|
||||
mip_sizes[level].src_size != 0 ? mip_sizes[level].src_offset : mip_sizes[level].offset;
|
||||
const auto level_size =
|
||||
static_cast<uint64_t>(mip_sizes[level].src_size != 0 ? mip_sizes[level].src_size
|
||||
: mip_sizes[level].size) *
|
||||
view.image_layers;
|
||||
desc.info.mip_layout[level] = {
|
||||
level_offset,
|
||||
level_size,
|
||||
mip_padded[level].width,
|
||||
mip_padded[level].height,
|
||||
};
|
||||
}
|
||||
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
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_COLORRENDERTARGET_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/vulkanCommon.h"
|
||||
|
||||
@@ -9,23 +10,18 @@
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
class CommandBuffer;
|
||||
struct VulkanImage;
|
||||
|
||||
namespace HW {
|
||||
class Context;
|
||||
} // namespace HW
|
||||
class RenderCommandBuffer;
|
||||
|
||||
enum class RenderColorType {
|
||||
NoColorOutput,
|
||||
DisplayBuffer,
|
||||
RenderTexture,
|
||||
};
|
||||
|
||||
struct RenderColorInfo {
|
||||
RenderColorType type = RenderColorType::NoColorOutput;
|
||||
VulkanImage* vulkan_buffer = nullptr;
|
||||
vk::ImageView vulkan_view = nullptr;
|
||||
RenderColorType type = RenderColorType::NoColorOutput;
|
||||
TextureCache::ImageDesc desc;
|
||||
ImageId image_id;
|
||||
vk::ImageView image_view = nullptr;
|
||||
vk::Format format = vk::Format::eUndefined;
|
||||
vk::Extent2D extent = {};
|
||||
uint32_t base_mip_level = 0;
|
||||
@@ -33,18 +29,12 @@ struct RenderColorInfo {
|
||||
uint64_t base_addr = 0;
|
||||
uint64_t buffer_size = 0;
|
||||
uint32_t target_slot = 0;
|
||||
uint32_t samples = 1;
|
||||
Prospero::ColorComponentMapping export_mapping;
|
||||
bool color_clear_enable = false;
|
||||
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
|
||||
|
||||
#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 = ®isters;
|
||||
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_
|
||||
@@ -8,65 +8,18 @@
|
||||
#include "graphics/host_gpu/renderer/colorRenderTarget.h"
|
||||
#include "graphics/host_gpu/renderer/debug.h"
|
||||
#include "graphics/host_gpu/renderer/depthRenderTarget.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/image/imageView.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/descriptorCache.h"
|
||||
#include "graphics/host_gpu/renderer/render.h"
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
#include "graphics/host_gpu/transfer.h"
|
||||
#include "graphics/host_gpu/vma.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
#include "graphics/presentation/window.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <bit>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
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() {
|
||||
if (!m_resources.empty()) {
|
||||
@@ -89,176 +42,46 @@ void FenceResourceRetainer::ReleaseAfterFence() noexcept {
|
||||
m_resources.clear();
|
||||
}
|
||||
|
||||
void GraphicsRenderInit() {
|
||||
EXIT_IF(g_render_ctx != nullptr);
|
||||
CommandBuffer::CommandBuffer(CommandScheduler& scheduler)
|
||||
: m_context(scheduler.Context()), m_scheduler(scheduler), m_graphics(scheduler.Graphics()),
|
||||
m_slot(scheduler.AllocateCommandBuffer()) {}
|
||||
|
||||
g_render_ctx = new RenderContext;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
CommandBuffer::~CommandBuffer() {
|
||||
Release();
|
||||
}
|
||||
|
||||
bool CommandBuffer::IsInvalid() const {
|
||||
EXIT_IF(g_render_ctx == 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;
|
||||
return m_slot == nullptr;
|
||||
}
|
||||
|
||||
vk::CommandBuffer CommandBuffer::Handle() const {
|
||||
EXIT_IF(IsInvalid());
|
||||
|
||||
const auto handle = m_pool->buffers[m_index];
|
||||
const auto handle = m_slot->buffer;
|
||||
EXIT_IF(handle == nullptr);
|
||||
return handle;
|
||||
}
|
||||
|
||||
void CommandBuffer::Allocate() {
|
||||
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() {
|
||||
void CommandBuffer::Release() {
|
||||
EXIT_IF(IsInvalid());
|
||||
|
||||
Common::LockGuard lock(m_pool->mutex);
|
||||
Common::LockGuard lock(*m_slot->pool_mutex);
|
||||
|
||||
WaitForFence();
|
||||
|
||||
m_host_stream.Release();
|
||||
|
||||
m_pool->busy[m_index] = false;
|
||||
ResetNativeCommandBuffer(m_pool->buffers[m_index]);
|
||||
m_slot->busy = false;
|
||||
m_slot->Reset();
|
||||
ReleaseResourcesAfterFence();
|
||||
m_index = static_cast<uint32_t>(-1);
|
||||
m_slot = nullptr;
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(!IsInvalid());
|
||||
}
|
||||
|
||||
void CommandBuffer::DeleteAfterFence(VulkanBuffer* buffer) {
|
||||
EXIT_IF(buffer == nullptr);
|
||||
|
||||
m_delete_after_fence.push_back(buffer);
|
||||
void CommandBuffer::RetireBufferAfterFence(std::unique_ptr<VulkanBuffer> buffer) {
|
||||
if (IsInvalid() || m_execute || buffer == nullptr || buffer->buffer == nullptr) {
|
||||
EXIT("cannot retire a buffer on an invalid or submitted command buffer\n");
|
||||
}
|
||||
m_retired_buffers.push_back(std::move(buffer));
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
void CommandBuffer::RecycleDescriptorAfterFence(VulkanDescriptorSet* set) {
|
||||
EXIT_IF(set == nullptr);
|
||||
|
||||
m_descriptor_sets_after_fence.push_back(set);
|
||||
void CommandBuffer::RecycleDescriptorAfterFence(VulkanDescriptorSet& set) {
|
||||
m_descriptor_sets_after_fence.push_back(&set);
|
||||
}
|
||||
|
||||
void CommandBuffer::RecycleDescriptorsAfterFence() {
|
||||
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();
|
||||
}
|
||||
|
||||
void CommandBuffer::Begin() const {
|
||||
EXIT_IF(m_rendering);
|
||||
auto buffer = Handle();
|
||||
|
||||
vk::CommandBufferBeginInfo begin_info {};
|
||||
@@ -296,6 +118,7 @@ void CommandBuffer::Begin() const {
|
||||
}
|
||||
|
||||
void CommandBuffer::End() const {
|
||||
EndRendering();
|
||||
auto buffer = Handle();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void CommandBuffer::Execute() {
|
||||
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);
|
||||
void CommandBuffer::Execute(const SubmitInfo& submit) {
|
||||
EXIT_IF(IsInvalid());
|
||||
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;
|
||||
const bool has_signal = signal_semaphore != nullptr;
|
||||
auto buffer = Handle();
|
||||
auto fence = m_pool->fences[m_index];
|
||||
auto buffer = Handle();
|
||||
auto fence = m_slot->fence;
|
||||
|
||||
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 {};
|
||||
submit_info.sType = vk::StructureType::eSubmitInfo;
|
||||
submit_info.pNext = nullptr;
|
||||
submit_info.waitSemaphoreCount = has_wait ? 1u : 0u;
|
||||
submit_info.pWaitSemaphores = has_wait ? &wait_semaphore : nullptr;
|
||||
submit_info.pWaitDstStageMask = has_wait ? &wait_stage : nullptr;
|
||||
submit_info.pNext = &timeline_info;
|
||||
submit_info.waitSemaphoreCount = submit.num_wait_semaphores;
|
||||
submit_info.pWaitSemaphores = submit.wait_semaphores.data();
|
||||
submit_info.pWaitDstStageMask = submit.wait_stages.data();
|
||||
submit_info.commandBufferCount = 1;
|
||||
submit_info.pCommandBuffers = &buffer;
|
||||
submit_info.signalSemaphoreCount = has_signal ? 1u : 0u;
|
||||
submit_info.pSignalSemaphores = has_signal ? &signal_semaphore : nullptr;
|
||||
submit_info.signalSemaphoreCount = submit.num_signal_semaphores;
|
||||
submit_info.pSignalSemaphores = submit.signal_semaphores.data();
|
||||
|
||||
auto* ctx = g_render_ctx->GetGraphicCtx();
|
||||
EXIT_IF(ctx == nullptr);
|
||||
EXIT_IF(ctx->device == nullptr);
|
||||
const auto& queue = ctx->queues[m_queue];
|
||||
auto& graphics = m_graphics;
|
||||
EXIT_IF(graphics.queue == nullptr);
|
||||
|
||||
auto result = ctx->device.resetFences(1, &fence);
|
||||
auto result = graphics.device.resetFences(1, &fence);
|
||||
if (result != vk::Result::eSuccess) {
|
||||
LOGF("vkResetFences failed before submit: %s (%d)\n", VulkanToString(result).c_str(),
|
||||
static_cast<int>(result));
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
|
||||
|
||||
if (queue.mutex != nullptr) {
|
||||
queue.mutex->Lock();
|
||||
}
|
||||
|
||||
if (Config::GraphicsDebugDumpEnabled()) {
|
||||
LOGF("vkQueueSubmit begin: queue=%d index=%u wait_semaphore=%p signal_semaphore=%p"
|
||||
" debug_op=%u debug_submit=%" PRIu64 " args=%u,%u,%u,%u,0x%016" PRIx64 "\n",
|
||||
m_queue, m_index, static_cast<void*>(wait_semaphore),
|
||||
static_cast<void*>(signal_semaphore), m_debug_op, m_debug_submit_id, m_debug_arg0,
|
||||
m_debug_arg1, m_debug_arg2, m_debug_arg3, m_debug_arg4);
|
||||
LOGF("vkQueueSubmit begin: slot=%u waits=%u signals=%u debug_op=%u debug_submit=%" PRIu64
|
||||
" args=%u,%u,%u,%u,0x%016" PRIx64 "\n",
|
||||
m_slot->id, submit.num_wait_semaphores, submit.num_signal_semaphores, m_debug_op,
|
||||
m_debug_submit_id, m_debug_arg0, m_debug_arg1, m_debug_arg2, m_debug_arg3,
|
||||
m_debug_arg4);
|
||||
}
|
||||
|
||||
result = queue.vk_queue.submit(1, &submit_info, fence);
|
||||
|
||||
if (queue.mutex != nullptr) {
|
||||
queue.mutex->Unlock();
|
||||
{
|
||||
Common::LockGuard lock(graphics.queue_mutex);
|
||||
m_submit_seq = m_scheduler.NextSubmitSequence();
|
||||
result = graphics.queue.submit(1, &submit_info, fence);
|
||||
}
|
||||
|
||||
m_execute = true;
|
||||
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) {
|
||||
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",
|
||||
VulkanToString(result).c_str(), static_cast<int>(result), m_queue, m_index,
|
||||
m_submit_seq, m_debug_op, m_debug_submit_id, m_debug_arg0, m_debug_arg1, m_debug_arg2,
|
||||
m_debug_arg3, m_debug_arg4);
|
||||
VulkanToString(result).c_str(), static_cast<int>(result), m_slot->id, m_submit_seq,
|
||||
m_debug_op, m_debug_submit_id, m_debug_arg0, m_debug_arg1, m_debug_arg2, m_debug_arg3,
|
||||
m_debug_arg4);
|
||||
}
|
||||
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() {
|
||||
FinalizeFence(false);
|
||||
}
|
||||
@@ -412,14 +210,14 @@ void CommandBuffer::WaitForFenceOnly() {
|
||||
if (!m_execute || m_fence_waited) {
|
||||
return;
|
||||
}
|
||||
auto device = g_render_ctx->GetGraphicCtx()->device;
|
||||
auto result = device.waitForFences(1, &m_pool->fences[m_index], VK_TRUE, UINT64_MAX);
|
||||
auto device = m_graphics.device;
|
||||
auto result = device.waitForFences(1, &m_slot->fence, VK_TRUE, UINT64_MAX);
|
||||
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",
|
||||
VulkanToString(result).c_str(), static_cast<int>(result), m_queue, m_index,
|
||||
m_submit_seq, m_debug_op, m_debug_submit_id, m_debug_arg0, m_debug_arg1, m_debug_arg2,
|
||||
m_debug_arg3, m_debug_arg4);
|
||||
VulkanToString(result).c_str(), static_cast<int>(result), m_slot->id, m_submit_seq,
|
||||
m_debug_op, m_debug_submit_id, m_debug_arg0, m_debug_arg1, m_debug_arg2, m_debug_arg3,
|
||||
m_debug_arg4);
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
|
||||
m_fence_waited = true;
|
||||
@@ -436,13 +234,10 @@ void CommandBuffer::FinalizeFence(bool reset_recording) {
|
||||
m_execute = false;
|
||||
m_fence_waited = false;
|
||||
if (reset_recording) {
|
||||
ResetNativeCommandBuffer(m_pool->buffers[m_index]);
|
||||
m_recording_generation++;
|
||||
Common::LockGuard lock(*m_slot->pool_mutex);
|
||||
m_slot->Reset();
|
||||
}
|
||||
}
|
||||
if (reset_recording) {
|
||||
m_host_stream.Reset();
|
||||
}
|
||||
if (was_executed) {
|
||||
ReleaseResourcesAfterFence();
|
||||
}
|
||||
@@ -455,138 +250,71 @@ void CommandBuffer::ReleaseResourcesAfterFence() {
|
||||
}
|
||||
|
||||
void CommandBuffer::DeleteBuffersAfterFence() {
|
||||
for (auto* buffer: m_delete_after_fence) {
|
||||
VulkanDeleteBuffer(g_render_ctx->GetGraphicCtx(), buffer);
|
||||
delete buffer;
|
||||
for (const auto& buffer: m_retired_buffers) {
|
||||
m_graphics.DeleteBuffer(*buffer);
|
||||
}
|
||||
m_delete_after_fence.clear();
|
||||
m_retired_buffers.clear();
|
||||
}
|
||||
|
||||
void CommandBuffer::BeginRenderPass(VulkanFramebuffer* framebuffer, RenderColorInfo* colors,
|
||||
uint32_t requested_color_count, RenderDepthInfo* depth) const {
|
||||
auto buffer = Handle();
|
||||
|
||||
EXIT_IF(framebuffer == nullptr);
|
||||
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++;
|
||||
void CommandBuffer::BeginRendering(const RenderState& state) const {
|
||||
EXIT_IF(state.width == 0 || state.height == 0 || state.num_layers == 0 ||
|
||||
state.num_color_attachments > RENDER_COLOR_ATTACHMENTS_MAX);
|
||||
if (m_rendering && m_render_state == state) {
|
||||
return;
|
||||
}
|
||||
bool with_color = (color_count != 0);
|
||||
EndRendering();
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(!with_depth && !with_color);
|
||||
|
||||
vk::ClearValue clears[RENDER_COLOR_ATTACHMENTS_MAX + 1] = {};
|
||||
for (uint32_t i = 0; i < color_count; i++) {
|
||||
clears[i].color = colors[i].color_clear_value;
|
||||
}
|
||||
clears[color_count].depthStencil = {depth->depth_clear_value, depth->stencil_clear_value};
|
||||
|
||||
vk::Extent2D extent = (with_color ? colors[0].extent : depth->vulkan_buffer->extent);
|
||||
|
||||
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());
|
||||
}
|
||||
std::array<vk::RenderingAttachmentInfo, RENDER_COLOR_ATTACHMENTS_MAX> colors {};
|
||||
for (uint32_t i = 0; i < state.num_color_attachments; i++) {
|
||||
const auto& attachment = state.color_attachments[i];
|
||||
colors[i].sType = vk::StructureType::eRenderingAttachmentInfo;
|
||||
colors[i].imageView = attachment.image_view;
|
||||
colors[i].imageLayout = attachment.image_layout;
|
||||
colors[i].loadOp =
|
||||
attachment.is_clear ? vk::AttachmentLoadOp::eClear : vk::AttachmentLoadOp::eLoad;
|
||||
colors[i].storeOp = vk::AttachmentStoreOp::eStore;
|
||||
colors[i].clearValue.color.uint32 = attachment.clear_value;
|
||||
}
|
||||
|
||||
const auto depth_layout =
|
||||
(with_depth && framebuffer != nullptr ? framebuffer->depth_layout
|
||||
: vk::ImageLayout::eDepthStencilAttachmentOptimal);
|
||||
const auto& depth_stencil = state.depth_stencil_attachment;
|
||||
vk::RenderingAttachmentInfo depth {};
|
||||
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::ImageMemoryBarrier image_memory_barrier {};
|
||||
image_memory_barrier.sType = vk::StructureType::eImageMemoryBarrier;
|
||||
image_memory_barrier.pNext = nullptr;
|
||||
image_memory_barrier.srcAccessMask =
|
||||
vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite;
|
||||
image_memory_barrier.dstAccessMask =
|
||||
(depth_layout == vk::ImageLayout::eDepthStencilReadOnlyOptimal
|
||||
? 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;
|
||||
vk::RenderingAttachmentInfo stencil {};
|
||||
stencil.sType = vk::StructureType::eRenderingAttachmentInfo;
|
||||
stencil.imageView = depth_stencil.image_view;
|
||||
stencil.imageLayout = depth_stencil.image_layout;
|
||||
stencil.loadOp =
|
||||
depth_stencil.stencil_clear ? vk::AttachmentLoadOp::eClear : vk::AttachmentLoadOp::eLoad;
|
||||
stencil.storeOp = vk::AttachmentStoreOp::eStore;
|
||||
stencil.clearValue.depthStencil.stencil = depth_stencil.clear_value[1];
|
||||
|
||||
buffer.pipelineBarrier(
|
||||
vk::PipelineStageFlagBits::eAllGraphics | vk::PipelineStageFlagBits::eComputeShader,
|
||||
vk::PipelineStageFlagBits::eAllGraphics | vk::PipelineStageFlagBits::eComputeShader,
|
||||
vk::DependencyFlags {}, 0, nullptr, 0, nullptr, 1, &image_memory_barrier);
|
||||
|
||||
depth->vulkan_buffer->layout = image_memory_barrier.newLayout;
|
||||
}
|
||||
|
||||
buffer.beginRenderPass(&render_pass_info, vk::SubpassContents::eInline);
|
||||
|
||||
for (uint32_t i = 0; i < color_count; i++) {
|
||||
colors[i].vulkan_buffer->layout = RENDER_COLOR_IMAGE_LAYOUT;
|
||||
}
|
||||
vk::RenderingInfo rendering {};
|
||||
rendering.sType = vk::StructureType::eRenderingInfo;
|
||||
rendering.renderArea.extent = {state.width, state.height};
|
||||
rendering.layerCount = state.num_layers;
|
||||
rendering.colorAttachmentCount = state.num_color_attachments;
|
||||
rendering.pColorAttachments = colors.data();
|
||||
rendering.pDepthAttachment = depth_stencil.has_depth ? &depth : nullptr;
|
||||
rendering.pStencilAttachment = depth_stencil.has_stencil ? &stencil : nullptr;
|
||||
Handle().beginRendering(rendering);
|
||||
m_render_state = state;
|
||||
m_rendering = true;
|
||||
}
|
||||
|
||||
void CommandBuffer::EndRenderPass() const {
|
||||
auto buffer = Handle();
|
||||
|
||||
buffer.endRenderPass();
|
||||
void CommandBuffer::EndRendering() const {
|
||||
if (!m_rendering) {
|
||||
return;
|
||||
}
|
||||
Handle().endRendering();
|
||||
m_rendering = false;
|
||||
m_render_state = {};
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "graphics/guest_gpu/hardwareContext.h"
|
||||
#include "graphics/host_gpu/renderer/render.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cmath>
|
||||
#include <fmt/format.h>
|
||||
@@ -25,8 +26,9 @@ static bool RenderTargetMaskHasMrt(uint32_t mask) {
|
||||
return (mask & ~0x0fu) != 0;
|
||||
}
|
||||
|
||||
static bool RenderTargetMaskHasBoundMrt(const HW::Context& hw) {
|
||||
const auto mask = hw.GetRenderTargetMask();
|
||||
static bool RenderTargetMaskHasBoundMrt(const RenderCommandBuffer& buffer) {
|
||||
const auto& hw = buffer.GetRegisters();
|
||||
const auto mask = hw.GetRenderTargetMask();
|
||||
|
||||
if (!RenderTargetMaskHasMrt(mask)) {
|
||||
return false;
|
||||
@@ -42,8 +44,9 @@ static bool RenderTargetMaskHasBoundMrt(const HW::Context& hw) {
|
||||
return bound_targets > 1;
|
||||
}
|
||||
|
||||
uint32_t render_target_first_bound_slot(const HW::Context& hw) {
|
||||
const auto mask = hw.GetRenderTargetMask();
|
||||
uint32_t render_target_first_bound_slot(const RenderCommandBuffer& buffer) {
|
||||
const auto& hw = buffer.GetRegisters();
|
||||
const auto mask = hw.GetRenderTargetMask();
|
||||
for (uint32_t i = 0; i < 8; i++) {
|
||||
if (render_target_mask_slot(mask, i) != 0 && hw.GetRenderTarget(i).base.addr != 0) {
|
||||
return i;
|
||||
@@ -98,8 +101,6 @@ void sh_print(const char* func, const HW::Shader& /*uc*/) {
|
||||
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> dst;
|
||||
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) {
|
||||
static bool logged = false;
|
||||
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",
|
||||
rt.attrib.num_samples, rt.attrib.num_fragments);
|
||||
logged = true;
|
||||
@@ -358,30 +359,12 @@ static void RtCheck(const HW::RenderTarget& rt) {
|
||||
logged = true;
|
||||
}
|
||||
}
|
||||
if (rt.attrib3.depth != 0x00000000) {
|
||||
static bool logged = false;
|
||||
if (!logged) {
|
||||
LOGF("RenderTarget: temporary: ignoring PS5 color target depth_minus1=0x%08" PRIx32
|
||||
"\n",
|
||||
rt.attrib3.depth);
|
||||
logged = true;
|
||||
}
|
||||
}
|
||||
if (!RenderIsColorTileMode(rt.attrib3.tile_mode)) {
|
||||
EXIT("unknown PS5 render-target tile mode: 0x%08" PRIx32 "\n", rt.attrib3.tile_mode);
|
||||
}
|
||||
if (!RenderIsColorDimension(rt.attrib3.dimension)) {
|
||||
EXIT("unknown PS5 render-target dimension: 0x%08" PRIx32 "\n", rt.attrib3.dimension);
|
||||
}
|
||||
if (rt.attrib3.dimension != 0x00000001) {
|
||||
static bool logged = false;
|
||||
if (!logged) {
|
||||
LOGF("RenderTarget: temporary: using 2D fallback for PS5 color "
|
||||
"dimension=0x%08" PRIx32 "\n",
|
||||
rt.attrib3.dimension);
|
||||
logged = true;
|
||||
}
|
||||
}
|
||||
if (!rt.attrib3.cmask_pipe_aligned) {
|
||||
static bool logged = false;
|
||||
if (!logged) {
|
||||
@@ -429,21 +412,17 @@ static void ZPrint(const char* func, const HW::DepthRenderTarget& z) {
|
||||
LOGF("%s\n", func);
|
||||
|
||||
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.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.zrange_precision = 0x%08" PRIx32 "\n"
|
||||
"\t z_info.embedded_sample_locations = %s\n"
|
||||
"\t z_info.z_compare_base = 0x%08" PRIx32 "\n"
|
||||
"\t z_info.partially_resident = %s\n"
|
||||
"\t z_info.num_mip_levels = 0x%02" PRIx8 "\n"
|
||||
"\t z_info.plane_compression = 0x%02" PRIx8 "\n"
|
||||
"\t z_info.max_mip_level = 0x%02" PRIx8 "\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.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 depth_info.addr5_swizzle_mask = 0x%08" PRIx32 "\n"
|
||||
"\t depth_info.array_mode = 0x%08" PRIx32 "\n"
|
||||
@@ -476,15 +455,15 @@ static void ZPrint(const char* func, const HW::DepthRenderTarget& z) {
|
||||
"\t height = 0x%08" PRIx32 "\n"
|
||||
"\t size.x_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.tile_surface_enable ? "true" : "false",
|
||||
z.z_info.expclear_enabled ? "true" : "false", z.z_info.zrange_precision,
|
||||
z.z_info.embedded_sample_locations ? "true" : "false",
|
||||
z.z_info.partially_resident ? "true" : "false", z.z_info.num_mip_levels,
|
||||
z.z_info.plane_compression, z.stencil_info.format,
|
||||
z.stencil_info.tile_stencil_disable ? "true" : "false",
|
||||
z.stencil_info.expclear_enabled ? "true" : "false", z.stencil_info.tile_mode_index,
|
||||
z.stencil_info.tile_split, z.stencil_info.texture_compatible_stencil ? "true" : "false",
|
||||
z.z_info.format, z.z_info.num_samples,
|
||||
Prospero::GpuEnumValue(z.z_info.texture_compatibility),
|
||||
z.z_info.htile_acceleration ? "true" : "false",
|
||||
z.z_info.expclear_enabled ? "true" : "false",
|
||||
Prospero::GpuEnumValue(z.z_info.z_compare_base),
|
||||
z.z_info.partially_resident ? "true" : "false", z.z_info.max_mip_level,
|
||||
z.stencil_info.format, Prospero::GpuEnumValue(z.stencil_info.texture_compatibility),
|
||||
z.stencil_info.htile_stencil_disabled ? "true" : "false",
|
||||
z.stencil_info.expclear_enabled ? "true" : "false",
|
||||
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.bank_height, z.depth_info.macro_tile_aspect, z.depth_info.num_banks,
|
||||
@@ -500,95 +479,45 @@ static void ZPrint(const char* func, const HW::DepthRenderTarget& z) {
|
||||
}
|
||||
|
||||
// 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) {
|
||||
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.tile_surface_enable != false);
|
||||
EXIT_NOT_IMPLEMENTED(z.z_info.htile_acceleration != 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.num_mip_levels != 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;
|
||||
}
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(z.z_info.max_mip_level != 0);
|
||||
} else {
|
||||
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) {
|
||||
static bool logged = false;
|
||||
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);
|
||||
logged = true;
|
||||
}
|
||||
}
|
||||
// EXIT_NOT_IMPLEMENTED(z.z_info.tile_surface_enable != true);
|
||||
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.num_mip_levels != 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;
|
||||
}
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(z.z_info.max_mip_level != 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.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);
|
||||
} else {
|
||||
// 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.format != 0x00000001);
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -609,14 +538,6 @@ static void ZCheck(const HW::DepthRenderTarget& z) {
|
||||
EXIT_NOT_IMPLEMENTED(z.htile_surface.prefetch_height != 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) {
|
||||
static std::atomic<uint32_t> log_count {0};
|
||||
if (log_count.fetch_add(1, std::memory_order_relaxed) < 16) {
|
||||
@@ -687,7 +608,7 @@ static void ClipCheck(const HW::ClipControl& c) {
|
||||
// dx_linear_attr_clip_enable preserves linear (noperspective) attributes at clip-generated
|
||||
// vertices, which Vulkan provides as part of clipping and interpolation.
|
||||
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_cull_only || c.cull_on_clipping_error_disable ||
|
||||
c.force_viewport_index_from_vs_enable);
|
||||
@@ -932,7 +853,7 @@ static void EqaaCheck(const HW::EqaaControl& c) {
|
||||
c.incoherent_eqaa_reads || c.interpolate_comp_z || c.static_anchor_associations) {
|
||||
static std::atomic<uint32_t> log_count {0};
|
||||
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 +883,8 @@ static void AaCheck(const HW::AaSampleControl& c, const HW::AaConfig& cf) {
|
||||
cf.max_sample_dist != 0 || cf.msaa_exposed_samples != 0) {
|
||||
static std::atomic<uint32_t> log_count {0};
|
||||
if (log_count.fetch_add(1) < 16) {
|
||||
LOGF("\t warning: unsupported PS5 AA/MSAA state, rendering with single-sample "
|
||||
"fallback: samples=%" PRIu8 ", exposed=%" PRIu8 ", max_dist=%" PRIu8 "\n",
|
||||
LOGF("\t warning: unsupported PS5 sample locations use native Vulkan locations: "
|
||||
"samples=%" PRIu8 ", exposed=%" PRIu8 ", max_dist=%" PRIu8 "\n",
|
||||
cf.msaa_num_samples, cf.msaa_exposed_samples, cf.max_sample_dist);
|
||||
}
|
||||
}
|
||||
@@ -1058,8 +979,7 @@ static void VpCheck(const HW::ScreenViewport& vp, const HW::ScanModeControl& smc
|
||||
|
||||
static std::atomic<uint32_t> log_count {0};
|
||||
if (log_count.fetch_add(1) < 16) {
|
||||
LOGF("\t warning: unsupported PS5 MSAA raster state, rendering with single-sample "
|
||||
"fallback\n");
|
||||
LOGF("\t warning: unsupported PS5 MSAA raster controls use native Vulkan defaults\n");
|
||||
}
|
||||
}
|
||||
// EXIT_NOT_IMPLEMENTED(smc.vport_scissor_enable);
|
||||
@@ -1134,10 +1054,10 @@ static ScissorRect ScissorRectClamp(ScissorRect r, uint32_t width, uint32_t heig
|
||||
int max_right = static_cast<int>(width);
|
||||
int max_bottom = static_cast<int>(height);
|
||||
|
||||
r.left = (r.left < 0 ? 0 : (r.left > max_right ? max_right : r.left));
|
||||
r.right = (r.right < 0 ? 0 : (r.right > max_right ? max_right : r.right));
|
||||
r.top = (r.top < 0 ? 0 : (r.top > max_bottom ? max_bottom : r.top));
|
||||
r.bottom = (r.bottom < 0 ? 0 : (r.bottom > max_bottom ? max_bottom : r.bottom));
|
||||
r.left = std::clamp(r.left, 0, max_right);
|
||||
r.right = std::clamp(r.right, 0, max_right);
|
||||
r.top = std::clamp(r.top, 0, max_bottom);
|
||||
r.bottom = std::clamp(r.bottom, 0, max_bottom);
|
||||
|
||||
if (!ScissorRectValid(r)) {
|
||||
r.right = r.left;
|
||||
@@ -1242,8 +1162,9 @@ ScissorRect calc_final_scissor(const HW::ScreenViewport& vp, const HW::ScanModeC
|
||||
return ScissorRectClamp(final, extent.width, extent.height);
|
||||
}
|
||||
|
||||
void hw_check(const HW::Context& hw) {
|
||||
const auto rt_slot = render_target_first_bound_slot(hw);
|
||||
void hw_check(const RenderCommandBuffer& buffer) {
|
||||
const auto& hw = buffer.GetRegisters();
|
||||
const auto rt_slot = render_target_first_bound_slot(buffer);
|
||||
const auto& rt = hw.GetRenderTarget(rt_slot);
|
||||
const auto& bc = hw.GetBlendControl(rt_slot);
|
||||
const auto& bclr = hw.GetBlendColor();
|
||||
@@ -1275,7 +1196,7 @@ void hw_check(const HW::Context& hw) {
|
||||
log_phase("vp");
|
||||
VpCheck(vp, smc);
|
||||
log_phase("z");
|
||||
ZCheck(z);
|
||||
ZCheck(z, d, rc);
|
||||
log_phase("clip");
|
||||
ClipCheck(c);
|
||||
log_phase("rc");
|
||||
@@ -1292,7 +1213,7 @@ void hw_check(const HW::Context& hw) {
|
||||
AaCheck(aa, ac);
|
||||
log_phase("done");
|
||||
|
||||
if (RenderTargetMaskHasBoundMrt(hw)) {
|
||||
if (RenderTargetMaskHasBoundMrt(buffer)) {
|
||||
LOGF("MRT render target mask: 0x%08" PRIx32 "\n", hw.GetRenderTargetMask());
|
||||
for (uint32_t i = 0; i < 8; i++) {
|
||||
const auto& mrt = hw.GetRenderTarget(i);
|
||||
@@ -1313,8 +1234,9 @@ void hw_check(const HW::Context& hw) {
|
||||
// EXIT_NOT_IMPLEMENTED(hw.GetStencilClearValue() != 0);
|
||||
}
|
||||
|
||||
void hw_print(const HW::Context& hw) {
|
||||
const auto rt_slot = render_target_first_bound_slot(hw);
|
||||
void hw_print(const RenderCommandBuffer& buffer) {
|
||||
const auto& hw = buffer.GetRegisters();
|
||||
const auto rt_slot = render_target_first_bound_slot(buffer);
|
||||
const auto& rt = hw.GetRenderTarget(rt_slot);
|
||||
const auto& bc = hw.GetBlendControl(rt_slot);
|
||||
const auto& bclr = hw.GetBlendColor();
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
class RenderCommandBuffer;
|
||||
|
||||
namespace HW {
|
||||
class Context;
|
||||
class Shader;
|
||||
@@ -26,16 +28,15 @@ struct ScissorRect {
|
||||
};
|
||||
|
||||
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();
|
||||
void uc_print(const char* func, const HW::UserConfig& uc);
|
||||
void uc_check(const HW::UserConfig& 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);
|
||||
bool RenderIsColorTileModeLinear(uint32_t tile_mode);
|
||||
void hw_print(const HW::Context& hw);
|
||||
void hw_check(const HW::Context& hw);
|
||||
void hw_print(const RenderCommandBuffer& buffer);
|
||||
void hw_check(const RenderCommandBuffer& buffer);
|
||||
void LogDrawPhase(const char* draw_name, const char* phase);
|
||||
ScissorRect calc_final_scissor(const HW::ScreenViewport& vp, const HW::ScanModeControl& smc,
|
||||
vk::Extent2D extent);
|
||||
|
||||
@@ -10,15 +10,13 @@
|
||||
#include "graphics/guest_gpu/hardwareContext.h"
|
||||
#include "graphics/guest_gpu/tile.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/descriptorCache.h"
|
||||
#include "graphics/host_gpu/renderer/framebufferCache.h"
|
||||
#include "graphics/host_gpu/renderer/image/imageView.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/renderContext.h"
|
||||
#include "graphics/host_gpu/transfer.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
#include "graphics/presentation/displayBuffer.h"
|
||||
|
||||
#include <algorithm>
|
||||
#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);
|
||||
}
|
||||
|
||||
[[nodiscard]] static vk::Format ResolveHostDepthAttachmentFormat(GraphicContext* ctx,
|
||||
const DepthFormatPolicy& policy,
|
||||
bool has_stencil) {
|
||||
[[nodiscard]] static vk::Format ResolveHostDepthAttachmentFormat(const RenderCommandBuffer& buffer,
|
||||
const DepthFormatPolicy& policy,
|
||||
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) {
|
||||
return policy.depth_attachment_format;
|
||||
return supports(policy.depth_attachment_format) ? policy.depth_attachment_format
|
||||
: vk::Format::eUndefined;
|
||||
}
|
||||
switch (policy.depth_format) {
|
||||
case Prospero::DepthFormat::kZ32F: return policy.stencil_attachment_formats.front();
|
||||
case Prospero::DepthFormat::kZ16: {
|
||||
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;
|
||||
for (const auto format: policy.stencil_attachment_formats) {
|
||||
if (supports(format)) {
|
||||
return format;
|
||||
}
|
||||
default: return vk::Format::eUndefined;
|
||||
}
|
||||
return vk::Format::eUndefined;
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
|
||||
void ResolveRenderDepthTarget(uint64_t submit_id, CommandBuffer* buffer, const HW::Context& hw,
|
||||
RenderDepthInfo* r) {
|
||||
void RenderExecutor::ResolveRenderDepthTarget(uint64_t submit_id, RenderCommandBuffer& buffer,
|
||||
RenderDepthInfo& r) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
EXIT_IF(r == nullptr);
|
||||
(void)submit_id;
|
||||
(void)buffer;
|
||||
const auto& hw = buffer.GetRegisters();
|
||||
const auto& z = hw.GetDepthRenderTarget();
|
||||
const auto& rc = hw.GetRenderControl();
|
||||
const auto& dc = hw.GetDepthControl();
|
||||
@@ -106,31 +102,34 @@ void ResolveRenderDepthTarget(uint64_t submit_id, CommandBuffer* buffer, const H
|
||||
if (!depth_active && !stencil_active) {
|
||||
return;
|
||||
}
|
||||
if (!z.z_info.HasValidTextureCompatibility() ||
|
||||
!z.stencil_info.HasValidTextureCompatibility()) {
|
||||
DepthFatal("invalid PS5 depth texture-compatibility encoding");
|
||||
}
|
||||
const bool attachment_unbound =
|
||||
z.z_info.format == Prospero::GpuEnumValue(Prospero::DepthFormat::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.zrange_precision <= 1 && !z.z_info.expclear_enabled &&
|
||||
!z.z_info.embedded_sample_locations && !z.z_info.partially_resident &&
|
||||
z.z_info.num_mip_levels == 0 && z.z_info.plane_compression == 0 &&
|
||||
z.stencil_info.tile_mode_index == 0 && z.stencil_info.tile_split == 0 &&
|
||||
!z.stencil_info.expclear_enabled && !z.stencil_info.texture_compatible_stencil &&
|
||||
!z.stencil_info.partially_resident && z.depth_view.slice_start == 0 &&
|
||||
z.depth_view.slice_max == 0 && z.depth_view.current_mip_level == 0 &&
|
||||
!z.depth_view.depth_write_disable && !z.depth_view.stencil_write_disable &&
|
||||
z.depth_info.addr5_swizzle_mask == 0 && z.depth_info.array_mode == 0 &&
|
||||
z.depth_info.pipe_config == 0 && z.depth_info.bank_width == 0 &&
|
||||
z.depth_info.bank_height == 0 && z.depth_info.macro_tile_aspect == 0 &&
|
||||
z.depth_info.num_banks == 0 && z.htile_surface.linear == 0 &&
|
||||
z.htile_surface.full_cache == 0 && z.htile_surface.htile_uses_preload_win == 0 &&
|
||||
z.htile_surface.preload == 0 && z.htile_surface.prefetch_width == 0 &&
|
||||
z.htile_surface.prefetch_height == 0 && z.htile_surface.dst_outside_zero_to_one == 0 &&
|
||||
z.z_read_base_addr == 0 && z.z_write_base_addr == 0 && z.stencil_read_base_addr == 0 &&
|
||||
z.z_info.num_samples == 0 &&
|
||||
z.z_info.texture_compatibility == Prospero::TextureCompatiblePlaneCompression::kDisable &&
|
||||
!z.z_info.expclear_enabled && !z.z_info.partially_resident && z.z_info.max_mip_level == 0 &&
|
||||
z.stencil_info.texture_compatibility == Prospero::TextureCompatibleStencil::kDisable &&
|
||||
!z.stencil_info.expclear_enabled && !z.stencil_info.partially_resident &&
|
||||
z.depth_view.slice_start == 0 && z.depth_view.slice_max == 0 &&
|
||||
z.depth_view.current_mip_level == 0 && !z.depth_view.depth_write_disable &&
|
||||
!z.depth_view.stencil_write_disable && z.depth_info.addr5_swizzle_mask == 0 &&
|
||||
z.depth_info.array_mode == 0 && z.depth_info.pipe_config == 0 &&
|
||||
z.depth_info.bank_width == 0 && z.depth_info.bank_height == 0 &&
|
||||
z.depth_info.macro_tile_aspect == 0 && z.depth_info.num_banks == 0 &&
|
||||
z.htile_surface.linear == 0 && z.htile_surface.full_cache == 0 &&
|
||||
z.htile_surface.htile_uses_preload_win == 0 && z.htile_surface.preload == 0 &&
|
||||
z.htile_surface.prefetch_width == 0 && z.htile_surface.prefetch_height == 0 &&
|
||||
z.htile_surface.dst_outside_zero_to_one == 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 &&
|
||||
// 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
|
||||
// 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.height_div8_minus1 == 0 && z.slice_div64_minus1 == 0 && z.width == 0 && z.height == 0;
|
||||
if (attachment_unbound) {
|
||||
@@ -142,70 +141,48 @@ void ResolveRenderDepthTarget(uint64_t submit_id, CommandBuffer* buffer, const H
|
||||
}
|
||||
const bool has_stencil =
|
||||
z.stencil_info.format != Prospero::GpuEnumValue(Prospero::StencilFormat::kInvalid);
|
||||
const bool has_htile = z.z_info.tile_surface_enable;
|
||||
const bool msaa_compat = depth_msaa_single_sample_compatible(z.z_info.num_samples);
|
||||
const bool has_htile = z.z_info.htile_acceleration;
|
||||
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(
|
||||
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);
|
||||
switch (view.type) {
|
||||
case TargetViewType::Image2D: break;
|
||||
case TargetViewType::Image2DArray:
|
||||
DepthFatal("layered depth views are unsupported: base=%u count=%u", view.base_layer,
|
||||
view.layer_count);
|
||||
case TargetViewType::Image2D:
|
||||
case TargetViewType::Image2DArray: break;
|
||||
case TargetViewType::Unsupported:
|
||||
DepthFatal("invalid depth view: base=%u last=%u", z.depth_view.slice_start,
|
||||
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 ||
|
||||
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.stencil_info.partially_resident || z.z_info.plane_compression != 0 ||
|
||||
(z.z_info.num_samples != 0 && !msaa_compat) || z.z_info.num_mip_levels != 0 ||
|
||||
z.z_info.tile_mode_index != 0 || z.z_info.zrange_precision > 1 ||
|
||||
z.depth_view.current_mip_level != 0 || z.depth_info.addr5_swizzle_mask != 0 ||
|
||||
z.depth_info.array_mode != 0 || z.depth_info.pipe_config != 0 ||
|
||||
z.depth_info.bank_width != 0 || z.depth_info.bank_height != 0 ||
|
||||
z.depth_info.macro_tile_aspect != 0 || z.depth_info.num_banks != 0 ||
|
||||
z.htile_surface.linear != 0 || z.htile_surface.full_cache != 0 ||
|
||||
z.htile_surface.htile_uses_preload_win != 0 || z.htile_surface.preload != 0 ||
|
||||
z.htile_surface.prefetch_width != 0 || z.htile_surface.prefetch_height != 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 ||
|
||||
z.z_info.partially_resident || z.stencil_info.partially_resident ||
|
||||
z.z_info.max_mip_level != 0 || z.depth_view.current_mip_level != 0 ||
|
||||
z.depth_info.addr5_swizzle_mask != 0 || z.depth_info.array_mode != 0 ||
|
||||
z.depth_info.pipe_config != 0 || z.depth_info.bank_width != 0 ||
|
||||
z.depth_info.bank_height != 0 || z.depth_info.macro_tile_aspect != 0 ||
|
||||
z.depth_info.num_banks != 0 || z.htile_surface.linear != 0 ||
|
||||
z.htile_surface.full_cache != 0 || z.htile_surface.htile_uses_preload_win != 0 ||
|
||||
z.htile_surface.preload != 0 || z.htile_surface.prefetch_width != 0 ||
|
||||
z.htile_surface.prefetch_height != 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)) {
|
||||
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) {
|
||||
// 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) ||
|
||||
z.stencil_info.tile_mode_index != 0 || z.stencil_info.tile_split != 0 ||
|
||||
!htile_stencil_compat || z.stencil_info.texture_compatible_stencil ||
|
||||
z.stencil_read_base_addr == 0 ||
|
||||
!htile_stencil_compat || z.stencil_read_base_addr == 0 ||
|
||||
z.stencil_write_base_addr != z.stencil_read_base_addr ||
|
||||
(z.stencil_read_base_addr & 0xffffu) != 0 || z.depth_view.stencil_write_disable) {
|
||||
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 ||
|
||||
z.stencil_info.tile_mode_index != 0 || z.stencil_info.tile_split != 0 ||
|
||||
!htile_stencil_compat || z.stencil_info.texture_compatible_stencil) {
|
||||
!htile_stencil_compat ||
|
||||
z.stencil_info.texture_compatibility !=
|
||||
Prospero::TextureCompatibleStencil::kDisable) {
|
||||
DepthFatal("stencil state without an active stencil attachment");
|
||||
}
|
||||
if (has_htile) {
|
||||
@@ -236,55 +213,34 @@ void ResolveRenderDepthTarget(uint64_t submit_id, CommandBuffer* buffer, const H
|
||||
DepthFatal("unsupported depth/stencil format pair");
|
||||
}
|
||||
const auto ideal_format = DepthAttachmentFormat(*policy, has_stencil);
|
||||
r->format =
|
||||
ResolveHostDepthAttachmentFormat(g_render_ctx->GetGraphicCtx(), *policy, has_stencil);
|
||||
if (r->format == vk::Format::eUndefined) {
|
||||
r.format = ResolveHostDepthAttachmentFormat(buffer, *policy, has_stencil, samples);
|
||||
if (r.format == vk::Format::eUndefined) {
|
||||
DepthFatal("no host depth/stencil format supports required usage for %s",
|
||||
VulkanToString(ideal_format).c_str());
|
||||
}
|
||||
const uint32_t guest_format = Prospero::GpuEnumValue(policy->guest_format);
|
||||
const uint32_t bytes = policy->bytes_per_element;
|
||||
const auto pitch = TileGetTexturePitch(guest_format, width, 1,
|
||||
Prospero::GpuEnumValue(Prospero::TileMode::kDepth));
|
||||
const auto pitch = TileGetDepthPitch(width, bytes, z.z_info.num_samples);
|
||||
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>(height) + 7u) & ~7ull))) {
|
||||
DepthFatal("encoded depth pitch or height mismatch");
|
||||
}
|
||||
const uint32_t block_width = bytes == 2 ? 256u : 128u;
|
||||
const uint64_t padded_width =
|
||||
(static_cast<uint64_t>(pitch) + block_width - 1u) & ~(block_width - 1u);
|
||||
const uint64_t padded_height =
|
||||
(static_cast<uint64_t>(height) + 127u) & ~static_cast<uint64_t>(127u);
|
||||
if (padded_width > UINT64_MAX / padded_height ||
|
||||
padded_width * padded_height > UINT64_MAX / bytes) {
|
||||
DepthFatal("depth footprint overflow");
|
||||
TileSizeAlign depth_size {};
|
||||
TileSizeAlign stencil_size {};
|
||||
TileSizeAlign htile_size {};
|
||||
if (!TileGetDepthSize(width, height, 0, z.z_info.format, z.stencil_info.format, has_htile,
|
||||
stencil_size, htile_size, depth_size, z.z_info.num_samples) ||
|
||||
depth_size.align != 65536 || depth_size.size == 0 ||
|
||||
(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");
|
||||
}
|
||||
const uint64_t expected_size = padded_width * padded_height * bytes;
|
||||
TileSizeAlign depth_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)) {
|
||||
if (z.pitch_height_valid &&
|
||||
(static_cast<uint64_t>(z.slice_div64_minus1) + 1u) * 64u != depth_size.size) {
|
||||
DepthFatal("depth footprint mismatch: extent=%ux%u pitch=%u expected=0x%016" PRIx64
|
||||
" calculated=0x%016" PRIx64 "/0x%016" PRIx64
|
||||
" encoded_valid=%u encoded=0x%016" PRIx64,
|
||||
width, height, pitch, expected_size, depth_size.size, depth_size.align,
|
||||
" align=0x%016" PRIx64 " encoded_valid=%u encoded=0x%016" PRIx64,
|
||||
width, height, pitch, depth_size.size, depth_size.align,
|
||||
z.pitch_height_valid ? 1u : 0u,
|
||||
(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)) {
|
||||
DepthFatal("layered depth backing range is invalid");
|
||||
}
|
||||
r->htile = has_htile;
|
||||
r->width = width;
|
||||
r->height = height;
|
||||
r->depth_buffer_size = depth_backing_size;
|
||||
r->depth_buffer_vaddr = z.z_read_base_addr;
|
||||
r->stencil_buffer_size = has_stencil ? stencil_backing_size : 0;
|
||||
r->stencil_buffer_vaddr = has_stencil ? z.stencil_read_base_addr : 0;
|
||||
r->htile_buffer_size = has_htile ? htile_backing_size : 0;
|
||||
r->htile_buffer_vaddr = has_htile ? z.htile_data_base_addr : 0;
|
||||
auto* cache = g_render_ctx->GetTextureCache();
|
||||
if (has_htile) {
|
||||
cache->RegisterMeta(r->htile_buffer_vaddr, r->htile_buffer_size, view.image_layers);
|
||||
}
|
||||
if (has_htile && rc.depth_clear_enable && !cache->ClearMeta(z.htile_data_base_addr)) {
|
||||
DepthFatal("failed to acquire HTile metadata for a depth clear");
|
||||
}
|
||||
const bool meta_clear =
|
||||
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.htile = has_htile;
|
||||
r.width = width;
|
||||
r.height = height;
|
||||
r.samples = samples;
|
||||
r.depth_buffer_size = depth_backing_size;
|
||||
r.depth_buffer_vaddr = z.z_read_base_addr;
|
||||
r.stencil_buffer_size = has_stencil ? stencil_backing_size : 0;
|
||||
r.stencil_buffer_vaddr = has_stencil ? z.stencil_read_base_addr : 0;
|
||||
r.htile_buffer_size = has_htile ? htile_backing_size : 0;
|
||||
r.htile_buffer_vaddr = has_htile ? z.htile_data_base_addr : 0;
|
||||
r.depth_clear_enable = rc.depth_clear_enable;
|
||||
r.depth_meta_clear_enable = false;
|
||||
r.depth_load_clear_enable = r.depth_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_min_bounds = hw.GetDepthBoundsMin();
|
||||
r->depth_max_bounds = hw.GetDepthBoundsMax();
|
||||
r.depth_bounds_test_enable = dc.depth_bounds_enable;
|
||||
r.depth_min_bounds = hw.GetDepthBoundsMin();
|
||||
r.depth_max_bounds = hw.GetDepthBoundsMax();
|
||||
|
||||
r->stencil_clear_enable = rc.stencil_clear_enable;
|
||||
r->stencil_clear_value = hw.GetStencilClearValue();
|
||||
r->stencil_test_enable = dc.stencil_enable;
|
||||
r.stencil_clear_enable = rc.stencil_clear_enable;
|
||||
r.stencil_clear_value = hw.GetStencilClearValue();
|
||||
r.stencil_test_enable = dc.stencil_enable;
|
||||
if (dc.stencil_enable) {
|
||||
if (dc.stencilfunc > static_cast<uint8_t>(vk::CompareOp::eAlways) ||
|
||||
(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)) {
|
||||
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_zfail), static_cast<vk::CompareOp>(dc.stencilfunc)};
|
||||
r->stencil_dynamic_front = {sm.stencil_mask,
|
||||
rc.stencil_clear_enable ? 0u : sm.stencil_writemask,
|
||||
sm.stencil_testval};
|
||||
r.stencil_dynamic_front = {sm.stencil_mask,
|
||||
rc.stencil_clear_enable ? 0u : sm.stencil_writemask,
|
||||
sm.stencil_testval};
|
||||
if (dc.backface_enable) {
|
||||
r->stencil_static_back = {ConvertStencilOp(sc.stencil_fail_bf),
|
||||
ConvertStencilOp(sc.stencil_zpass_bf),
|
||||
ConvertStencilOp(sc.stencil_zfail_bf),
|
||||
static_cast<vk::CompareOp>(dc.stencilfunc_bf)};
|
||||
r->stencil_dynamic_back = {sm.stencil_mask_bf,
|
||||
rc.stencil_clear_enable ? 0u : sm.stencil_writemask_bf,
|
||||
sm.stencil_testval_bf};
|
||||
r.stencil_static_back = {ConvertStencilOp(sc.stencil_fail_bf),
|
||||
ConvertStencilOp(sc.stencil_zpass_bf),
|
||||
ConvertStencilOp(sc.stencil_zfail_bf),
|
||||
static_cast<vk::CompareOp>(dc.stencilfunc_bf)};
|
||||
r.stencil_dynamic_back = {sm.stencil_mask_bf,
|
||||
rc.stencil_clear_enable ? 0u : sm.stencil_writemask_bf,
|
||||
sm.stencil_testval_bf};
|
||||
} else {
|
||||
r->stencil_static_back = r->stencil_static_front;
|
||||
r->stencil_dynamic_back = r->stencil_dynamic_front;
|
||||
r.stencil_static_back = r.stencil_static_front;
|
||||
r.stencil_dynamic_back = r.stencil_dynamic_front;
|
||||
}
|
||||
}
|
||||
r->vaddr_num = has_stencil ? 2 : 1;
|
||||
r->vaddr[0] = r->depth_buffer_vaddr;
|
||||
r->size[0] = r->depth_buffer_size;
|
||||
r.vaddr_num = has_stencil ? 2 : 1;
|
||||
r.vaddr[0] = r.depth_buffer_vaddr;
|
||||
r.size[0] = r.depth_buffer_size;
|
||||
if (has_stencil) {
|
||||
r->vaddr[1] = r->stencil_buffer_vaddr;
|
||||
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");
|
||||
r.vaddr[1] = r.stencil_buffer_vaddr;
|
||||
r.size[1] = r.stencil_buffer_size;
|
||||
}
|
||||
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) {
|
||||
const bool with_depth =
|
||||
target.format != vk::Format::eUndefined && target.vulkan_buffer != nullptr;
|
||||
|
||||
if (with_depth && !depth_attachment_read_only(&target)) {
|
||||
g_render_ctx->GetTextureCache()->MarkGpuWritten(target.vulkan_buffer);
|
||||
vk::ImageAspectFlags RenderDepthInfo::AttachmentWriteAspects() const {
|
||||
if (format == vk::Format::eUndefined) {
|
||||
return {};
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_DEPTHRENDERTARGET_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/vulkanCommon.h"
|
||||
|
||||
@@ -9,22 +11,19 @@
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
class CommandBuffer;
|
||||
struct DepthStencilVulkanImage;
|
||||
|
||||
namespace HW {
|
||||
class Context;
|
||||
} // namespace HW
|
||||
class RenderCommandBuffer;
|
||||
|
||||
inline constexpr bool depth_htile_stencil_acceleration_compatible(bool has_stencil, bool has_htile,
|
||||
bool acceleration_disabled) {
|
||||
return acceleration_disabled || (has_stencil && has_htile);
|
||||
bool htile_stencil_disabled) {
|
||||
return htile_stencil_disabled || (has_stencil && has_htile);
|
||||
}
|
||||
|
||||
struct RenderDepthInfo {
|
||||
TextureCache::ImageDesc desc;
|
||||
vk::Format format = vk::Format::eUndefined;
|
||||
uint32_t width = 0;
|
||||
uint32_t height = 0;
|
||||
uint32_t samples = 1;
|
||||
bool htile = false;
|
||||
uint64_t depth_buffer_size = 0;
|
||||
uint64_t depth_buffer_vaddr = 0;
|
||||
@@ -52,31 +51,46 @@ struct RenderDepthInfo {
|
||||
PipelineStencilStaticState stencil_static_back;
|
||||
PipelineStencilDynamicState stencil_dynamic_front;
|
||||
PipelineStencilDynamicState stencil_dynamic_back;
|
||||
DepthStencilVulkanImage* vulkan_buffer = nullptr;
|
||||
vk::ImageView vulkan_view = nullptr;
|
||||
uint64_t vaddr[3] = {};
|
||||
uint64_t size[3] = {};
|
||||
int vaddr_num = 0;
|
||||
ImageId image_id;
|
||||
vk::ImageView image_view = nullptr;
|
||||
uint64_t vaddr[3] = {};
|
||||
uint64_t size[3] = {};
|
||||
int vaddr_num = 0;
|
||||
|
||||
[[nodiscard]] vk::ImageAspectFlags AttachmentWriteAspects() const;
|
||||
};
|
||||
|
||||
inline bool depth_attachment_read_only(const RenderDepthInfo* depth) {
|
||||
EXIT_IF(depth == nullptr);
|
||||
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 bool depth_attachment_read_only(const RenderDepthInfo& depth) {
|
||||
return !depth.AttachmentWriteAspects();
|
||||
}
|
||||
|
||||
inline vk::ImageLayout depth_attachment_layout(const RenderDepthInfo* depth) {
|
||||
return depth_attachment_read_only(depth) ? vk::ImageLayout::eDepthStencilReadOnlyOptimal
|
||||
: vk::ImageLayout::eDepthStencilAttachmentOptimal;
|
||||
inline vk::ImageLayout depth_attachment_layout(const RenderDepthInfo& depth) {
|
||||
const auto available = ImageViewOps::DepthAspectMask(depth.format);
|
||||
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
|
||||
|
||||
#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_
|
||||
@@ -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
|
||||
@@ -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,753 @@
|
||||
#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;
|
||||
if (range && info.IsVolume()) {
|
||||
range->base_layer = 0;
|
||||
range->layer_count = 1;
|
||||
}
|
||||
|
||||
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_
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user