Merge upstream/main into fix/recompiler-descriptor-phi-web
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
cd43c270d6
|
|
@ -20,7 +20,73 @@ env:
|
|||
-DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache
|
||||
|
||||
jobs:
|
||||
changes:
|
||||
if: github.event_name == 'pull_request'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
outputs:
|
||||
relinker: ${{ steps.filter.outputs.relinker }}
|
||||
code: ${{ steps.filter.outputs.code }}
|
||||
steps:
|
||||
- uses: dorny/paths-filter@v4
|
||||
id: filter
|
||||
with:
|
||||
predicate-quantifier: some-with-excludes
|
||||
filters: |
|
||||
relinker:
|
||||
- 'core/relinker/**'
|
||||
- 'core/libs/prx/libc/src/specifics/windows/**'
|
||||
- 'CMakeLists.txt'
|
||||
- '.github/workflows/build.yml'
|
||||
code:
|
||||
- '**'
|
||||
- '!**/*.md'
|
||||
- '!docs/**'
|
||||
|
||||
relinker:
|
||||
needs: changes
|
||||
if: ${{ !cancelled() && (needs.changes.result != 'success' || needs.changes.outputs.relinker == 'true') }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 15
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
compiler: g++
|
||||
- os: windows-latest
|
||||
compiler: g++
|
||||
- os: macos-latest
|
||||
compiler: clang++
|
||||
env:
|
||||
WINLIBS_RELEASE: 15.2.0posix-14.0.0-ucrt-r7
|
||||
WINLIBS_ZIP: winlibs-x86_64-posix-seh-gcc-15.2.0-mingw-w64ucrt-14.0.0-r7.zip
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- if: runner.os == 'Windows'
|
||||
id: toolchain
|
||||
uses: actions/cache@v6
|
||||
with:
|
||||
path: C:\winlibs
|
||||
key: ${{ env.WINLIBS_ZIP }}
|
||||
- if: runner.os == 'Windows' && steps.toolchain.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
curl -sSfL -o "$RUNNER_TEMP/winlibs.zip" "https://github.com/brechtsanders/winlibs_mingw/releases/download/$WINLIBS_RELEASE/$WINLIBS_ZIP"
|
||||
7z x "$RUNNER_TEMP/winlibs.zip" -oC:/winlibs > /dev/null
|
||||
- if: runner.os == 'Windows'
|
||||
run: echo 'C:\winlibs\mingw64\bin' >> "$GITHUB_PATH"
|
||||
- run: cmake -S . -B build-relinker -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=${{ matrix.compiler }} -DANYPS5_RELINKER_ONLY=ON -DBUILD_TESTING=ON
|
||||
- run: cmake --build build-relinker --parallel 3
|
||||
- run: ctest --test-dir build-relinker --output-on-failure --timeout 120
|
||||
|
||||
linux:
|
||||
needs: changes
|
||||
if: ${{ !cancelled() && (needs.changes.result != 'success' || needs.changes.outputs.code == 'true') }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
|
|
@ -34,15 +100,26 @@ jobs:
|
|||
evict-old-files: job
|
||||
- name: Install lavapipe for the GPU execution tests
|
||||
run: sudo apt-get update && sudo apt-get install -y --no-install-recommends libvulkan1 mesa-vulkan-drivers
|
||||
- run: cmake -S . -B build $CONFIGURE
|
||||
- run: cmake -S . -B build $CONFIGURE -DANYPS5_ENABLE_SPIRV_TOOLS=ON
|
||||
- name: Check that every test executable is registered with ctest
|
||||
run: python3 tools/check_test_registration.py build
|
||||
- run: cmake --build build
|
||||
- run: cmake --build build --target libs
|
||||
- run: ctest --test-dir build --output-on-failure --timeout 120 --repeat until-fail:3
|
||||
- run: ctest --test-dir build --output-on-failure --timeout 120 --repeat until-fail:3 --parallel 4 --output-log ctest.log
|
||||
env:
|
||||
ANYPS5_REQUIRE_VULKAN: 1
|
||||
GALLIUM_OVERRIDE_CPU_CAPS: avx
|
||||
- name: Report tests slower than 30 s
|
||||
if: ${{ !cancelled() }}
|
||||
shell: bash
|
||||
run: |
|
||||
[ -f ctest.log ] || exit 0
|
||||
awk '/Test +#[0-9]+: / && $NF == "sec" { for (i = 1; i <= NF; i++) if ($i ~ /^#[0-9]+:$/) name = $(i + 1); t = $(NF - 1) + 0; if (t > max[name]) max[name] = t }
|
||||
END { for (name in max) if (max[name] > 30) printf "::warning title=Slow test::%s took %.1f s on %s; tests must finish in 30 s (CONTRIBUTING.md)\n", name, max[name], ENVIRON["RUNNER_OS"] }' ctest.log
|
||||
|
||||
windows:
|
||||
needs: changes
|
||||
if: ${{ !cancelled() && (needs.changes.result != 'success' || needs.changes.outputs.code == 'true') }}
|
||||
runs-on: windows-latest
|
||||
timeout-minutes: 45
|
||||
env:
|
||||
|
|
@ -73,9 +150,19 @@ jobs:
|
|||
evict-old-files: job
|
||||
- shell: cmd
|
||||
run: cmake -S . -B build %CONFIGURE%
|
||||
- name: Check that every test executable is registered with ctest
|
||||
shell: cmd
|
||||
run: python tools/check_test_registration.py build
|
||||
- shell: cmd
|
||||
run: cmake --build build
|
||||
- shell: cmd
|
||||
run: cmake --build build --target libs
|
||||
- shell: cmd
|
||||
run: ctest --test-dir build --output-on-failure --timeout 120 --repeat until-fail:3
|
||||
run: ctest --test-dir build --output-on-failure --timeout 120 --repeat until-fail:3 --parallel 4 --output-log ctest.log
|
||||
- name: Report tests slower than 30 s
|
||||
if: ${{ !cancelled() }}
|
||||
shell: bash
|
||||
run: |
|
||||
[ -f ctest.log ] || exit 0
|
||||
awk '/Test +#[0-9]+: / && $NF == "sec" { for (i = 1; i <= NF; i++) if ($i ~ /^#[0-9]+:$/) name = $(i + 1); t = $(NF - 1) + 0; if (t > max[name]) max[name] = t }
|
||||
END { for (name in max) if (max[name] > 30) printf "::warning title=Slow test::%s took %.1f s on %s; tests must finish in 30 s (CONTRIBUTING.md)\n", name, max[name], ENVIRON["RUNNER_OS"] }' ctest.log
|
||||
|
|
|
|||
|
|
@ -2,6 +2,10 @@ name: Progress report
|
|||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'core/libs/prx/**'
|
||||
- 'tools/progress.py'
|
||||
- '.github/workflows/progress-report.yml'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ function(add_test_executable target)
|
|||
endfunction()
|
||||
|
||||
option(BUILD_TESTING "Build and register relinker tests" OFF)
|
||||
option(ANYPS5_RELINKER_ONLY "Build the relinker without system libraries or third-party dependencies" OFF)
|
||||
if(BUILD_TESTING)
|
||||
enable_testing()
|
||||
if(MINGW)
|
||||
|
|
@ -23,13 +24,30 @@ if(BUILD_TESTING)
|
|||
endif()
|
||||
|
||||
option(ANYPS5_ENABLE_SPIRV_TOOLS "Enable SPIRV-Tools validation and optimization" OFF)
|
||||
option(APS5_ENABLE_TIMING_LOG "Enable frame timing logging" OFF)
|
||||
option(APS5_ENABLE_TIMING_LOG "Write frame timings asynchronously to frame-timing.log" OFF)
|
||||
add_compile_definitions(APS5_ENABLE_TIMING_LOG=$<BOOL:${APS5_ENABLE_TIMING_LOG}>)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static -static-libgcc -static-libstdc++")
|
||||
if(NOT APPLE)
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static -static-libgcc -static-libstdc++")
|
||||
endif()
|
||||
|
||||
if(BUILD_TESTING)
|
||||
find_package(Python3 QUIET COMPONENTS Interpreter)
|
||||
if(Python3_Interpreter_FOUND)
|
||||
add_test(NAME progress_report COMMAND ${Python3_EXECUTABLE}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_progress.py)
|
||||
add_test(NAME import_audit COMMAND ${Python3_EXECUTABLE}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_import_audit.py)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(ANYPS5_RELINKER_ONLY)
|
||||
add_subdirectory(core/relinker)
|
||||
return()
|
||||
endif()
|
||||
|
||||
set(SDL_SHARED OFF CACHE BOOL "" FORCE)
|
||||
set(SDL_STATIC ON CACHE BOOL "" FORCE)
|
||||
|
|
|
|||
|
|
@ -13,10 +13,13 @@
|
|||
- The relinker uses only the C++20 standard library.
|
||||
- Third-party code is added as a submodule under `3rdparty/` and built from source, not found on the system.
|
||||
- Don't add tests that only check that a symbol is exported: a missing export already fails at startup.
|
||||
- Each test finishes in 30 s on the CI runners, Vulkan tests included (Linux runs them on lavapipe), counting the first run with no shader cache. ctest runs in parallel there, so a slow test competes for the cores and times out on some runs and not others. Split or shrink a test that gets close instead of raising its timeout. CI reports every test over 30 s as a warning on the pull request.
|
||||
|
||||
## Build and test
|
||||
|
||||
Toolchains are listed in the [README](README.md#build).
|
||||
Toolchains are listed in the [build instructions](docs/dev/BUILD.md).
|
||||
|
||||
For relinker changes, use the [relinker-only build](docs/dev/BUILD.md#relinker-only) to build and test without third-party submodules, system libraries or a GPU. It also works on macOS. Changes to system libraries or shaders still need the full build and relevant runtime tests.
|
||||
|
||||
```
|
||||
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=ON
|
||||
|
|
|
|||
|
|
@ -131,6 +131,15 @@ endforeach()
|
|||
add_custom_target(libs DEPENDS ${PATCHED_OUTPUTS})
|
||||
|
||||
if(BUILD_TESTING)
|
||||
add_test_executable(guest_offline_imports_tests tests/GuestOfflineImports.cpp)
|
||||
target_include_directories(guest_offline_imports_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_link_libraries(guest_offline_imports_tests PRIVATE libSceHttp libSceNpEntitlementAccess libSceRudp libc)
|
||||
configure_windows_unwind(guest_offline_imports_tests)
|
||||
add_test(NAME guest_offline_imports COMMAND guest_offline_imports_tests)
|
||||
if(WIN32)
|
||||
set_tests_properties(guest_offline_imports PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceHttp>")
|
||||
endif()
|
||||
add_test_executable(savedata_memory_metadata_tests tests/SaveDataMemoryMetadata.cpp)
|
||||
target_include_directories(savedata_memory_metadata_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_link_libraries(savedata_memory_metadata_tests PRIVATE libSceSaveData.native)
|
||||
|
|
|
|||
|
|
@ -1219,6 +1219,8 @@ using NetCtlCallback = void (*)(int, void*);
|
|||
struct HttpEpoll {};
|
||||
using HttpEpollHandle = HttpEpoll*;
|
||||
using HttpsCallback = int (*)(int, unsigned int, void* const*, int, void*);
|
||||
using HttpRedirectCallback = int (*)(int, std::int32_t, std::int32_t*, const char*, void*);
|
||||
using HttpCookieRecvCallback = int (*)(int, const char*, const char*, std::uint64_t, void*);
|
||||
|
||||
struct HttpNBEvent { std::uint8_t opaque[64]; };
|
||||
|
||||
|
|
@ -1243,6 +1245,14 @@ struct Http2AsyncResult {
|
|||
void* reserved;
|
||||
};
|
||||
|
||||
struct Http2AsyncOption {
|
||||
KernelEqueue equeue;
|
||||
int user_event_id;
|
||||
std::uint8_t padding[4];
|
||||
void* user_data;
|
||||
void* reserved;
|
||||
};
|
||||
|
||||
struct NpTitleId { char data[13]; char pad[3]; };
|
||||
struct NpTitleSecret { std::uint8_t data[128]; };
|
||||
struct NpContentRestriction { std::uint8_t opaque[128]; };
|
||||
|
|
@ -1266,6 +1276,10 @@ struct NpEntitlementAccessAddcontEntitlementInfo {
|
|||
std::uint32_t download_status;
|
||||
};
|
||||
|
||||
struct NpEntitlementAccessEntitlementKey {
|
||||
std::uint8_t data[16];
|
||||
};
|
||||
|
||||
|
||||
|
||||
struct NpUniversalDataSystemInitParam {
|
||||
|
|
@ -1375,7 +1389,7 @@ struct SaveDataMountInfo {
|
|||
};
|
||||
|
||||
struct SceSaveDataTitleId { char data[10]; char pad[2]; };
|
||||
struct SceSaveDataDirName { char data[33]; char pad[3]; };
|
||||
struct SceSaveDataDirName { char data[32]; };
|
||||
struct SaveDataSearchInfo { std::uint8_t opaque[128]; };
|
||||
struct SaveDataMemoryData { void* buf; std::size_t buf_size; std::size_t offset; };
|
||||
|
||||
|
|
@ -1838,12 +1852,6 @@ struct LibcHeapInfo {
|
|||
|
||||
using Info = LibcHeapInfo;
|
||||
|
||||
#define VA_ARGS \
|
||||
std::uint64_t rdi, std::uint64_t rsi, std::uint64_t rdx, std::uint64_t rcx, \
|
||||
std::uint64_t r8, std::uint64_t r9, std::uint64_t overflow_arg_area, \
|
||||
__m128 xmm0, __m128 xmm1, __m128 xmm2, __m128 xmm3, \
|
||||
__m128 xmm4, __m128 xmm5, __m128 xmm6, __m128 xmm7, ...
|
||||
|
||||
struct Packet {
|
||||
std::uint32_t* addr;
|
||||
std::uint32_t dw_num;
|
||||
|
|
|
|||
|
|
@ -91,6 +91,7 @@ inline bool IsDynamicNameTag(std::int64_t tag) {
|
|||
return tag == kDtNeeded || tag == kDtSoname || tag == kDtRpath || tag == kDtRunpath;
|
||||
}
|
||||
constexpr std::uint32_t kShtDynamic = 6u;
|
||||
constexpr std::uint32_t kShtGnuVerdef = 0x6ffffffdu;
|
||||
constexpr std::uint32_t kShtGnuVerneed = 0x6ffffffeu;
|
||||
constexpr std::uint32_t kShtDynsym = 11u;
|
||||
constexpr std::uint32_t kShtGnuHash = 0x6ffffff6u;
|
||||
|
|
|
|||
|
|
@ -337,6 +337,8 @@ void ElfNidPatcher::PatchNids(std::vector<std::uint8_t>& elf, const std::string&
|
|||
}
|
||||
if (shdr.sh_type == kShtGnuVersym)
|
||||
versymOffset = static_cast<std::size_t>(shdr.sh_offset);
|
||||
if (shdr.sh_type == kShtGnuVerdef)
|
||||
throw std::runtime_error("unsupported .gnu.version_d section: symbol version definitions cannot be preserved");
|
||||
}
|
||||
|
||||
if (dynSymOffset == 0u) throw std::runtime_error("no .dynsym section");
|
||||
|
|
|
|||
|
|
@ -0,0 +1,209 @@
|
|||
import re
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
SHT_NULL = 0
|
||||
SHT_STRTAB = 3
|
||||
SHT_DYNSYM = 11
|
||||
SHT_GNU_HASH = 0x6FFFFFF6
|
||||
SHT_GNU_VERDEF = 0x6FFFFFFD
|
||||
SHT_GNU_VERSYM = 0x6FFFFFFF
|
||||
SHN_UNDEF = 0
|
||||
SHN_ABS = 0xFFF1
|
||||
STB_GLOBAL = 1
|
||||
STT_FUNC = 2
|
||||
|
||||
LIBRARY_NAME = "libsymbols"
|
||||
DYNSYM_OFFSET = 0x200
|
||||
DYNSTR_OFFSET = 0x300
|
||||
HASH_OFFSET = 0x500
|
||||
VERSYM_OFFSET = 0x580
|
||||
VERDEF_OFFSET = 0x600
|
||||
SHOFF = 0x800
|
||||
BUCKETS = 4
|
||||
HASHED_FROM = 3
|
||||
NID_NAME = re.compile(r"^[A-Za-z0-9+-]{11}$")
|
||||
|
||||
SONAME = "libsymbols.so"
|
||||
VERSIONS = [(1, [SONAME]), (2, ["VERS_ONE"]), (3, ["VERS_TWO", "VERS_ONE"])]
|
||||
MARKERS = [("VERS_ONE", 2, 0x1100), ("VERS_TWO", 3, 0x1101)]
|
||||
DEFINITIONS = [
|
||||
("sceAlphaComputeLength", 2, 0x1200),
|
||||
("sceAlphaCompareStrings", 2, 0x1220),
|
||||
("sceAlphaFindCharacter", 2, 0x1240),
|
||||
("sceBetaCopyString", 3, 0x1260),
|
||||
("sceBetaConcatenateStrings", 3, 0x1280),
|
||||
("sceBetaSearchCharacter", 3, 0x12A0),
|
||||
]
|
||||
IMPORTS = ["jeJEvW13vxk", "e0m0E5Sq2N8"]
|
||||
|
||||
|
||||
def gnu_hash(name):
|
||||
value = 5381
|
||||
for byte in name.encode("utf-8"):
|
||||
value = (value * 33 + byte) & 0xFFFFFFFF
|
||||
return value
|
||||
|
||||
|
||||
def symbols(versioned):
|
||||
entries = [(None, 1, SHN_UNDEF, 0, 0)]
|
||||
entries += [(name, 1, SHN_UNDEF, 0, 0) for name in IMPORTS]
|
||||
if versioned:
|
||||
entries += [(name, version, SHN_ABS, 0, value) for name, version, value in MARKERS]
|
||||
entries += [(name, version, 9, STT_FUNC, value) for name, version, value in DEFINITIONS]
|
||||
else:
|
||||
entries += [(name, 1, 9, STT_FUNC, value) for name, _, value in DEFINITIONS]
|
||||
return entries
|
||||
|
||||
|
||||
def string_table(names):
|
||||
offsets = {}
|
||||
table = bytearray(b"\x00")
|
||||
for name in names:
|
||||
offsets.setdefault(name, len(table))
|
||||
table += name.encode("utf-8") + b"\x00"
|
||||
return table, offsets
|
||||
|
||||
|
||||
def build_image(entries, versioned):
|
||||
image = bytearray(0x1000)
|
||||
image[:16] = b"\x7fELF\x02\x01\x01" + bytes(9)
|
||||
names = [entry[0] for entry in entries if entry[0]]
|
||||
if versioned:
|
||||
names += [name for _, version_names in VERSIONS for name in version_names]
|
||||
strings, offsets = string_table(names)
|
||||
for index, (name, version, shndx, symbol_type, value) in enumerate(entries):
|
||||
info = (STB_GLOBAL << 4) | symbol_type
|
||||
struct.pack_into("<IBBHQQ", image, DYNSYM_OFFSET + index * 24,
|
||||
offsets[name] if name else 0, info, 0, shndx, value, 0x10 if symbol_type else 0)
|
||||
image[DYNSTR_OFFSET:DYNSTR_OFFSET + len(strings)] = strings
|
||||
|
||||
chain_count = len(entries) - HASHED_FROM
|
||||
bucket_offset = HASH_OFFSET + 16 + 8
|
||||
chain_offset = bucket_offset + BUCKETS * 4
|
||||
struct.pack_into("<IIII", image, HASH_OFFSET, BUCKETS, HASHED_FROM, 1, 6)
|
||||
for index in range(HASHED_FROM, len(entries)):
|
||||
name = entries[index][0]
|
||||
bucket = gnu_hash(name) % BUCKETS
|
||||
if struct.unpack_from("<I", image, bucket_offset + bucket * 4)[0] == 0:
|
||||
struct.pack_into("<I", image, bucket_offset + bucket * 4, index)
|
||||
chain = gnu_hash(name)
|
||||
if index + 1 == len(entries) or gnu_hash(entries[index + 1][0]) % BUCKETS != bucket:
|
||||
chain |= 1
|
||||
struct.pack_into("<I", image, chain_offset + (index - HASHED_FROM) * 4, chain)
|
||||
|
||||
verdef_end = 0
|
||||
if versioned:
|
||||
for index, (_, version, _, _, _) in enumerate(entries):
|
||||
struct.pack_into("<H", image, VERSYM_OFFSET + index * 2, version)
|
||||
|
||||
position = VERDEF_OFFSET
|
||||
for version_index, version_names in VERSIONS:
|
||||
entry_size = 20 + 8 * len(version_names)
|
||||
struct.pack_into("<HHHHIII", image, position, 1, 1 if version_index == 1 else 0, version_index,
|
||||
len(version_names), gnu_hash(version_names[0]), 20,
|
||||
0 if version_index == len(VERSIONS) else entry_size)
|
||||
aux = position + 20
|
||||
for name_index, name in enumerate(version_names):
|
||||
struct.pack_into("<II", image, aux, offsets[name], 0 if name_index + 1 == len(version_names) else 8)
|
||||
aux += 8
|
||||
position += entry_size
|
||||
verdef_end = position
|
||||
|
||||
sections = [
|
||||
(0, SHT_NULL, 0, 0, 0, 0, 0, 0, 0, 0),
|
||||
(0, SHT_DYNSYM, 2, 0, DYNSYM_OFFSET, len(entries) * 24, 2, 1, 8, 24),
|
||||
(0, SHT_STRTAB, 2, 0, DYNSTR_OFFSET, len(strings), 0, 0, 1, 0),
|
||||
(0, SHT_GNU_HASH, 2, 0, HASH_OFFSET, chain_offset - HASH_OFFSET + chain_count * 4, 1, 0, 8, 0),
|
||||
]
|
||||
if versioned:
|
||||
sections.append((0, SHT_GNU_VERSYM, 2, 0, VERSYM_OFFSET, len(entries) * 2, 1, 0, 2, 2))
|
||||
sections.append((0, SHT_GNU_VERDEF, 2, 0, VERDEF_OFFSET, verdef_end - VERDEF_OFFSET, 2, len(VERSIONS), 4, 0))
|
||||
struct.pack_into("<HHIQQQIHHHHHH", image, 16, 3, 62, 1, 0, 0, SHOFF, 0, 64, 0, 0, 64, len(sections), 0)
|
||||
for index, section in enumerate(sections):
|
||||
struct.pack_into("<IIQQQQIIQQ", image, SHOFF + index * 64, *section)
|
||||
return image
|
||||
|
||||
|
||||
def sections(image):
|
||||
shoff, = struct.unpack_from("<Q", image, 0x28)
|
||||
shentsize, shnum = struct.unpack_from("<HH", image, 0x3A)
|
||||
return [struct.unpack_from("<IIQQQQIIQQ", image, shoff + index * shentsize) for index in range(shnum)]
|
||||
|
||||
|
||||
def read_dynsym(image):
|
||||
headers = sections(image)
|
||||
dynsym = next(section for section in headers if section[1] == SHT_DYNSYM)
|
||||
strings = headers[dynsym[6]]
|
||||
entries = []
|
||||
for index in range(dynsym[5] // 24):
|
||||
name_offset, _, _, shndx, value, _ = struct.unpack_from("<IBBHQQ", image, dynsym[4] + index * 24)
|
||||
name = None
|
||||
if name_offset:
|
||||
end = image.index(0, strings[4] + name_offset)
|
||||
name = image[strings[4] + name_offset:end].decode("utf-8")
|
||||
entries.append((name, shndx, value))
|
||||
return entries
|
||||
|
||||
|
||||
def patch(patcher, image, filename):
|
||||
with tempfile.TemporaryDirectory(prefix="anyps5-symbol-versions-") as directory:
|
||||
path = Path(directory) / filename
|
||||
path.write_bytes(image)
|
||||
result = subprocess.run([patcher, LIBRARY_NAME, str(path)], capture_output=True)
|
||||
return result, path.read_bytes()
|
||||
|
||||
|
||||
def test_versioned_library_is_rejected(patcher):
|
||||
image = bytes(build_image(symbols(True), True))
|
||||
assert any(section[1] == SHT_GNU_VERDEF for section in sections(image))
|
||||
|
||||
result, on_disk = patch(patcher, image, SONAME)
|
||||
assert result.returncode == 2, (result.returncode, result.stderr)
|
||||
message = result.stderr.decode("utf-8", errors="replace")
|
||||
assert message.startswith("FAIL: "), message
|
||||
assert ".gnu.version_d" in message, message
|
||||
assert result.stdout == b"", result.stdout
|
||||
assert on_disk == image, "the rejected library was written back modified"
|
||||
|
||||
|
||||
def test_unversioned_library_is_still_patched(patcher):
|
||||
image = bytes(build_image(symbols(False), False))
|
||||
assert not any(section[1] == SHT_GNU_VERDEF for section in sections(image))
|
||||
before = read_dynsym(image)
|
||||
|
||||
result, patched = patch(patcher, image, SONAME)
|
||||
assert result.returncode == 0, result.stderr.decode("utf-8", errors="replace")
|
||||
assert result.stdout.decode("utf-8", errors="replace").startswith("OK: "), result.stdout
|
||||
|
||||
after = read_dynsym(patched)
|
||||
assert len(after) == len(before), (before, after)
|
||||
assert [entry[0] for entry in after] != [entry[0] for entry in before], "no export was renamed"
|
||||
|
||||
for name, _, value in DEFINITIONS:
|
||||
found = [entry for entry in after if entry[2] == value]
|
||||
assert len(found) == 1, (name, found)
|
||||
renamed, shndx, _ = found[0]
|
||||
assert renamed != name, (name, found)
|
||||
assert NID_NAME.match(renamed), (name, found)
|
||||
assert shndx == 9, (name, found)
|
||||
|
||||
for name in IMPORTS:
|
||||
found = [entry for entry in after if entry[0] == name]
|
||||
assert found == [(name, SHN_UNDEF, 0)], (name, found)
|
||||
|
||||
assert len({entry[0] for entry in after if entry[0]}) == len([entry for entry in after if entry[0]])
|
||||
|
||||
|
||||
def main():
|
||||
patcher = Path(sys.argv[1]).resolve()
|
||||
test_versioned_library_is_rejected(patcher)
|
||||
test_unversioned_library_is_still_patched(patcher)
|
||||
print("NID patcher symbol version tests passed")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -12,8 +12,7 @@
|
|||
extern "C" {
|
||||
|
||||
// unknown signature
|
||||
APS5_EXPORT("gQkqkLttcpw", sceAgcAcb_gQkqkLttcpw);
|
||||
void* APS5_VABI sceAgcAcb_gQkqkLttcpw (void) {
|
||||
void* APS5_VABI sceAgcAcbAtomicGds_0900(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return nullptr;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -209,14 +209,15 @@ if(BUILD_TESTING)
|
|||
endif()
|
||||
|
||||
add_test_executable(agc_shader_fusion_tests EXCLUDE_FROM_ALL tests/ShaderFusion.cpp)
|
||||
target_include_directories(agc_shader_fusion_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(agc_shader_fusion_tests PRIVATE libSceAgc libc)
|
||||
target_include_directories(agc_shader_fusion_tests PRIVATE ${LIBS_INCLUDE_DIR} ${CMAKE_SOURCE_DIR}/core/shader/recompiler ${CMAKE_SOURCE_DIR}/3rdparty/Vulkan-Headers/include)
|
||||
target_link_libraries(agc_shader_fusion_tests PRIVATE libSceAgc libSceAgcDriver libc)
|
||||
set_target_properties(agc_shader_fusion_tests PROPERTIES CXX_EXTENSIONS OFF)
|
||||
configure_windows_unwind(agc_shader_fusion_tests)
|
||||
|
||||
if(BUILD_TESTING)
|
||||
set_target_properties(agc_shader_fusion_tests PROPERTIES EXCLUDE_FROM_ALL OFF)
|
||||
add_test(NAME agc_shader_fusion COMMAND agc_shader_fusion_tests)
|
||||
set_tests_properties(agc_shader_fusion PROPERTIES SKIP_RETURN_CODE 77)
|
||||
if(WIN32)
|
||||
set_tests_properties(agc_shader_fusion PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
|
|
@ -236,9 +237,13 @@ if(BUILD_TESTING)
|
|||
endif()
|
||||
endif()
|
||||
|
||||
add_test_executable(agc_interpolant_mapping_tests EXCLUDE_FROM_ALL tests/InterpolantMapping.cpp)
|
||||
add_test_executable(agc_interpolant_mapping_tests EXCLUDE_FROM_ALL
|
||||
tests/InterpolantMapping.cpp
|
||||
Shader/src/InterpolantMapping.cpp
|
||||
Shader/src/ShaderUtils.cpp
|
||||
)
|
||||
target_include_directories(agc_interpolant_mapping_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(agc_interpolant_mapping_tests PRIVATE libSceAgc libc)
|
||||
target_link_libraries(agc_interpolant_mapping_tests PRIVATE libSceAgcDriver libc)
|
||||
set_target_properties(agc_interpolant_mapping_tests PROPERTIES CXX_EXTENSIONS OFF)
|
||||
configure_windows_unwind(agc_interpolant_mapping_tests)
|
||||
|
||||
|
|
@ -252,9 +257,15 @@ if(BUILD_TESTING)
|
|||
endif()
|
||||
|
||||
if(BUILD_TESTING)
|
||||
add_test_executable(guest_shader_link_tests ${LIBS_INCLUDE_DIR}/tests/GuestShaderLink.cpp)
|
||||
add_test_executable(guest_shader_link_tests
|
||||
${LIBS_INCLUDE_DIR}/tests/GuestShaderLink.cpp
|
||||
Shader/src/LinkShaders.cpp
|
||||
Shader/src/PrimState.cpp
|
||||
Shader/src/InterpolantMapping.cpp
|
||||
Shader/src/ShaderUtils.cpp
|
||||
)
|
||||
target_include_directories(guest_shader_link_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(guest_shader_link_tests PRIVATE libSceAgc libc)
|
||||
target_link_libraries(guest_shader_link_tests PRIVATE libSceAgc libSceAgcDriver libc)
|
||||
configure_windows_unwind(guest_shader_link_tests)
|
||||
add_test(NAME guest_shader_link COMMAND guest_shader_link_tests)
|
||||
if(WIN32)
|
||||
|
|
@ -358,3 +369,22 @@ if(BUILD_TESTING)
|
|||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
add_test_executable(agc_shader_helpers_tests EXCLUDE_FROM_ALL
|
||||
tests/ShaderHelpers.cpp
|
||||
Shader/src/LinkShaders.cpp
|
||||
Shader/src/PrimState.cpp
|
||||
Shader/src/InterpolantMapping.cpp
|
||||
Shader/src/ShaderUtils.cpp
|
||||
)
|
||||
target_include_directories(agc_shader_helpers_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(agc_shader_helpers_tests PRIVATE libc libSceAgcDriver)
|
||||
configure_windows_unwind(agc_shader_helpers_tests)
|
||||
if(BUILD_TESTING)
|
||||
set_target_properties(agc_shader_helpers_tests PROPERTIES EXCLUDE_FROM_ALL FALSE)
|
||||
add_test(NAME agc_shader_helpers COMMAND agc_shader_helpers_tests)
|
||||
if(WIN32)
|
||||
set_tests_properties(agc_shader_helpers PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
endif()
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ std::uint32_t available(const CommandBuffer& buffer, const char* function) {
|
|||
const auto bottom = reinterpret_cast<std::uintptr_t>(buffer.bottom);
|
||||
const auto top = reinterpret_cast<std::uintptr_t>(buffer.top);
|
||||
const auto up = reinterpret_cast<std::uintptr_t>(buffer.cursor_up);
|
||||
const auto down = reinterpret_cast<std::uintptr_t>(buffer.cursor_down);
|
||||
const auto down = buffer.cursor_down != nullptr ? reinterpret_cast<std::uintptr_t>(buffer.cursor_down) : top;
|
||||
Require(((bottom | top | up | down) & 3u) == 0, function, "misaligned command buffer");
|
||||
Require(bottom <= up && up <= down && down <= top, function, "invalid command buffer cursors");
|
||||
Require(bottom != 0 || top == 0, function, "null command buffer storage");
|
||||
|
|
|
|||
|
|
@ -35,8 +35,7 @@ std::uint32_t APS5_VABI sceAgcDcbSetIndexSizeGetSize() {
|
|||
return 12;
|
||||
}
|
||||
|
||||
APS5_EXPORT("-KRzWekV120", sceAgcUnknown__MKRzWekV120);
|
||||
std::uint32_t* APS5_VABI sceAgcUnknown__MKRzWekV120(CommandBuffer* buf, std::uint8_t indexSize, std::uint8_t cachePolicy, std::uint8_t perInstanceObjectId) {
|
||||
std::uint32_t* APS5_VABI sceAgcDcbSetIndexSize_0600(CommandBuffer* buf, std::uint8_t indexSize, std::uint8_t cachePolicy, std::uint8_t perInstanceObjectId) {
|
||||
Agc::Command::CheckBits(perInstanceObjectId, 1, __func__);
|
||||
auto* packet = sceAgcDcbSetIndexSize(buf, indexSize, cachePolicy);
|
||||
packet[2] |= static_cast<std::uint32_t>(perInstanceObjectId) << 14u;
|
||||
|
|
|
|||
|
|
@ -26,8 +26,7 @@ uint64_t APS5_VABI sceAgcDcbContextStateOpGetSize(uint32_t operation) {
|
|||
return 27 * sizeof(std::uint32_t);
|
||||
}
|
||||
|
||||
APS5_EXPORT("qj7QZpgr9Uw", sceAgcDcbContextStateAnotherOp);
|
||||
uint32_t* APS5_VABI sceAgcDcbContextStateAnotherOp(CommandBuffer* buf, uint32_t operation) {
|
||||
uint32_t* APS5_VABI sceAgcDcbContextStateOp_0100(CommandBuffer* buf, uint32_t operation) {
|
||||
Agc::Command::Require(buf != nullptr, __func__, "null command buffer");
|
||||
Agc::Command::Require(operation <= 3, __func__, "invalid context state operation");
|
||||
const auto* function = __func__;
|
||||
|
|
|
|||
|
|
@ -9,8 +9,7 @@
|
|||
|
||||
extern "C" {
|
||||
|
||||
APS5_EXPORT("23LRUSvYu1M", sceAgcUnknownInitState);
|
||||
int APS5_VABI sceAgcUnknownInitState(std::uint32_t* state, std::uint32_t version) {
|
||||
int APS5_VABI sceAgcInit_0090(std::uint32_t* state, std::uint32_t version) {
|
||||
Agc::Command::CheckAddress(reinterpret_cast<std::uintptr_t>(state), alignof(std::uint32_t), __func__);
|
||||
Agc::Command::Require(version < 14, __func__, "unsupported AGC initialization version");
|
||||
return 0;
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ struct SizeAlign {
|
|||
std::size_t m_align;
|
||||
};
|
||||
|
||||
extern "C" int APS5_VABI sceAgcUnknownFuseShaderHalves(Shader* fused_result, const Shader* front, const Shader* back, void* scratch_mem);
|
||||
extern "C" int APS5_VABI sceAgcUnknownGetFusedShaderSize(SizeAlign* dst, const Shader* front, const Shader* back);
|
||||
extern "C" int APS5_VABI sceAgcFuseShaderHalves_0200(Shader* fused_result, const Shader* front, const Shader* back, void* scratch_mem);
|
||||
extern "C" int APS5_VABI sceAgcGetFusedShaderSize_0080(SizeAlign* dst, const Shader* front, const Shader* back);
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -17,8 +17,7 @@ uint32_t APS5_VABI sceAgcGetPacketSize(uint32_t* packet) {
|
|||
return ((packet[0] >> 16u) & 0x3fffu) + 2u;
|
||||
}
|
||||
|
||||
APS5_EXPORT("V++UgBtQhn0", sceAgcGetDataPacketPayloadAddressUnk);
|
||||
int APS5_VABI sceAgcGetDataPacketPayloadAddressUnk(uint32_t** addr, uint32_t* cmd, int type) {
|
||||
int APS5_VABI sceAgcGetDataPacketPayloadAddress_0090(uint32_t** addr, uint32_t* cmd, int type) {
|
||||
Agc::Command::CheckAddress(reinterpret_cast<std::uintptr_t>(addr), alignof(uint32_t*), __func__);
|
||||
Agc::Command::CheckAddress(reinterpret_cast<std::uintptr_t>(cmd), alignof(uint32_t), __func__);
|
||||
if (type != 0) {
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ constexpr std::uint32_t SPI_SHADER_PGM_RSRC2_HS = 0x10Bu;
|
|||
constexpr std::uint32_t SCodeEnd = 0xbf9f0000u;
|
||||
constexpr std::uint32_t SNop = 0xbf800000u;
|
||||
constexpr std::uint32_t SSetpcS6 = 0xbe802006u;
|
||||
constexpr std::uint32_t SSwappcNullS6 = 0xbefd2106u;
|
||||
constexpr std::uint8_t FusedCodeAlignmentLog2 = 8;
|
||||
constexpr std::size_t FusedCodeAlignment = std::size_t{1} << FusedCodeAlignmentLog2;
|
||||
constexpr char TrailerMagic[8] = {'b', 'a', 'r', 'e', 'f', 'o', 'o', 't'};
|
||||
|
|
@ -51,7 +52,7 @@ ShaderRegister& FindRegister(ShaderRegister* regs, std::uint32_t count, std::uin
|
|||
for (std::uint32_t i = 0; regs != nullptr && i < count; ++i) {
|
||||
if (regs[i].offset == offset && occurrence-- == 0) return regs[i];
|
||||
}
|
||||
throw std::runtime_error("sceAgcUnknownFuseShaderHalves: shader half lacks register " + std::to_string(offset));
|
||||
throw std::runtime_error("sceAgcFuseShaderHalves_0200: shader half lacks register " + std::to_string(offset));
|
||||
}
|
||||
|
||||
void MergeMax(ShaderRegister& dst, const ShaderRegister& src, std::uint32_t shift, std::uint32_t mask) {
|
||||
|
|
@ -114,7 +115,7 @@ std::uint32_t FrontProgramBytes(const char* function, const Shader* front) {
|
|||
return value;
|
||||
};
|
||||
while (end > 0 && word(end - 1) == SCodeEnd) --end;
|
||||
if (end == 0 || word(end - 1) != SSetpcS6) Fail(function, "front half does not end with s_setpc_b64 s[6:7]");
|
||||
if (end == 0 || (word(end - 1) != SSetpcS6 && word(end - 1) != SSwappcNullS6)) Fail(function, "front half does not end with s_setpc_b64 s[6:7] or s_swappc_b64 null, s[6:7]");
|
||||
return static_cast<std::uint32_t>((end - 1) * 4);
|
||||
}
|
||||
|
||||
|
|
@ -187,11 +188,11 @@ void SetProgramAddress(std::vector<ShaderRegister>& regs, std::uint32_t loOffset
|
|||
regs[i + 1].value = (regs[i + 1].value & 0xFFFFFF00u) | static_cast<std::uint32_t>((address >> 40u) & 0xFFu);
|
||||
return;
|
||||
}
|
||||
throw std::runtime_error("sceAgcUnknownFuseShaderHalves: back half has no program address register pair");
|
||||
throw std::runtime_error("sceAgcFuseShaderHalves_0200: back half has no program address register pair");
|
||||
}
|
||||
|
||||
int FuseGeometryHalves(Shader* fused_result, const Shader* front, const Shader* back, void* scratch_mem) {
|
||||
constexpr auto fn = "sceAgcUnknownFuseShaderHalves";
|
||||
constexpr auto fn = "sceAgcFuseShaderHalves_0200";
|
||||
if (scratch_mem == nullptr) Fail(fn, "geometry halves need fused shader memory");
|
||||
auto layout = ComputeGeometryLayout(fn, front, back);
|
||||
const auto scratch = reinterpret_cast<std::uintptr_t>(scratch_mem);
|
||||
|
|
@ -251,8 +252,7 @@ int FuseGeometryHalves(Shader* fused_result, const Shader* front, const Shader*
|
|||
|
||||
extern "C" {
|
||||
|
||||
APS5_EXPORT("fd5Bp5tGTgo", sceAgcUnknownFuseShaderHalves);
|
||||
int APS5_VABI sceAgcUnknownFuseShaderHalves(Shader* fused_result, const Shader* front, const Shader* back, void* scratch_mem) {
|
||||
int APS5_VABI sceAgcFuseShaderHalves_0200(Shader* fused_result, const Shader* front, const Shader* back, void* scratch_mem) {
|
||||
if (fused_result == nullptr || front == nullptr || back == nullptr) APS5_INVALID_ARG_EX;
|
||||
if (!ValidHalves(front, back)) return GRAPHICS5_ERROR_INVALID_SHADER_HALVES;
|
||||
const bool isGs = GeometryHalves(front);
|
||||
|
|
@ -298,15 +298,14 @@ int APS5_VABI sceAgcUnknownFuseShaderHalves(Shader* fused_result, const Shader*
|
|||
}
|
||||
|
||||
int APS5_VABI sceAgcFuseShaderHalves_nid_postfix(Shader* fused_result, const Shader* front, const Shader* back, void* scratch_mem) {
|
||||
return sceAgcUnknownFuseShaderHalves(fused_result, front, back, scratch_mem);
|
||||
return sceAgcFuseShaderHalves_0200(fused_result, front, back, scratch_mem);
|
||||
}
|
||||
|
||||
APS5_EXPORT("dolOmWH+huQ", sceAgcUnknownGetFusedShaderSize);
|
||||
int APS5_VABI sceAgcUnknownGetFusedShaderSize(SizeAlign* dst, const Shader* front, const Shader* back) {
|
||||
int APS5_VABI sceAgcGetFusedShaderSize_0080(SizeAlign* dst, const Shader* front, const Shader* back) {
|
||||
if (dst == nullptr || front == nullptr || back == nullptr) APS5_INVALID_ARG_EX;
|
||||
if (!ValidHalves(front, back)) return GRAPHICS5_ERROR_INVALID_SHADER_HALVES;
|
||||
if (GeometryHalves(front)) {
|
||||
const auto layout = ComputeGeometryLayout("sceAgcUnknownGetFusedShaderSize", front, back);
|
||||
const auto layout = ComputeGeometryLayout("sceAgcGetFusedShaderSize_0080", front, back);
|
||||
dst->m_size = layout.totalBytes + FusedCodeAlignment - 1;
|
||||
dst->m_align = FusedCodeAlignmentLog2;
|
||||
return 0;
|
||||
|
|
@ -316,8 +315,7 @@ int APS5_VABI sceAgcUnknownGetFusedShaderSize(SizeAlign* dst, const Shader* fron
|
|||
return 0;
|
||||
}
|
||||
|
||||
APS5_EXPORT("k0E7vkgqAuE", sceAgcCreateInterpolantMappingVsPs);
|
||||
int APS5_VABI sceAgcCreateInterpolantMappingVsPs(ShaderRegister* regs, const Shader* vs, const Shader* ps) {
|
||||
int APS5_VABI sceAgcUpdateInterpolantMapping_0100(ShaderRegister* regs, const Shader* vs, const Shader* ps) {
|
||||
(void)regs;
|
||||
(void)vs;
|
||||
(void)ps;
|
||||
|
|
|
|||
|
|
@ -28,8 +28,7 @@ int APS5_VABI sceAgcJumpPatchSetTarget(uint32_t* cmd, const volatile uint32_t* t
|
|||
return 0;
|
||||
}
|
||||
|
||||
APS5_EXPORT("Ikfdt-rIqCE", sceAgcUnknown_Ikfdt_MrIqCE);
|
||||
int APS5_VABI sceAgcUnknown_Ikfdt_MrIqCE(uint32_t* cmd, uint64_t cache_policy, const volatile uint32_t* target, uint32_t size_in_dwords) {
|
||||
int APS5_VABI sceAgcJumpPatchSetTarget_0300(uint32_t* cmd, uint64_t cache_policy, const volatile uint32_t* target, uint32_t size_in_dwords) {
|
||||
Agc::Command::CheckBits(cache_policy, 3u, __func__);
|
||||
PatchJump(cmd, target, size_in_dwords, __func__);
|
||||
cmd[3] = (cmd[3] & ~(3u << 28u)) | (static_cast<std::uint32_t>(cache_policy) << 28u);
|
||||
|
|
|
|||
|
|
@ -1,10 +1,14 @@
|
|||
#include "prx/libSceAgc/Shader/include/InterpolantMapping.hpp"
|
||||
|
||||
#include <cstdio>
|
||||
#include <array>
|
||||
#include <algorithm>
|
||||
#include <stdexcept>
|
||||
#include <prx/libc/include/General.hpp>
|
||||
|
||||
#include "SceShaders.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/ShaderPreparationScope.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/Driver.hpp"
|
||||
#include "prx/libSceAgc/Shader/include/ShaderUtils.hpp"
|
||||
#include "prx/libSceAgc/Shader/include/ShaderConstants.hpp"
|
||||
|
||||
|
|
@ -53,9 +57,22 @@ int CreateInterpolantMapping(const char* fn, ShaderRegister* regs, const Shader*
|
|||
if (regs == nullptr) {
|
||||
throw std::runtime_error(std::string(fn) + ": regs is null");
|
||||
}
|
||||
if (ps != nullptr && ps->num_input_semantics > 32) {
|
||||
throw std::runtime_error(std::string(fn) + ": input semantic count exceeds 32");
|
||||
}
|
||||
|
||||
AgcDriver::ShaderPreparationScope transaction;
|
||||
std::array<ShaderRegister, 32> values{};
|
||||
auto* output = regs;
|
||||
regs = values.data();
|
||||
if (ps == nullptr || ps->num_input_semantics == 0) {
|
||||
FillIdentityInterpolants(regs, 0);
|
||||
if (ps != nullptr) {
|
||||
AgcDriverResolveShaderAbi_nid_postfix(ps, {regs, 32}, {});
|
||||
if (gs != nullptr) AgcDriverResolveGraphicsAbi_nid_postfix(gs, ps, 0);
|
||||
}
|
||||
transaction.Commit();
|
||||
std::copy(values.begin(), values.end(), output);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -82,6 +99,10 @@ int CreateInterpolantMapping(const char* fn, ShaderRegister* regs, const Shader*
|
|||
}
|
||||
|
||||
FillIdentityInterpolants(regs, ps->num_input_semantics);
|
||||
AgcDriverResolveShaderAbi_nid_postfix(ps, {regs, 32}, {});
|
||||
AgcDriverResolveGraphicsAbi_nid_postfix(gs, ps, 0);
|
||||
transaction.Commit();
|
||||
std::copy(values.begin(), values.end(), output);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
#include "SceShaders.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/ShaderPreparationScope.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/Driver.hpp"
|
||||
#include "prx/libc/include/general/VabiMacros.hpp"
|
||||
#include "prx/libSceAgc/Shader/include/ShaderConstants.hpp"
|
||||
#include <array>
|
||||
|
|
@ -20,10 +22,13 @@ extern "C" int APS5_VABI sceAgcLinkShaders(ShaderRegister* context, ShaderRegist
|
|||
|
||||
// The simple vertex/pixel pipeline combines two primitive context registers
|
||||
// and 32 interpolant registers. Publish only after both helpers succeed.
|
||||
AgcDriver::ShaderPreparationScope transaction;
|
||||
std::array<ShaderRegister, 34> contextValues{};
|
||||
std::array<ShaderRegister, 3> primitiveValues{};
|
||||
sceAgcCreatePrimState(contextValues.data(), primitiveValues.data(), nullptr, vertex, primitiveType);
|
||||
sceAgcCreateInterpolantMapping(contextValues.data() + 2, vertex, pixel);
|
||||
AgcDriverResolveGraphicsAbi_nid_postfix(vertex, pixel, primitiveType);
|
||||
transaction.Commit();
|
||||
std::copy(contextValues.begin(), contextValues.end(), context);
|
||||
std::copy(primitiveValues.begin(), primitiveValues.end(), primitive);
|
||||
return 0;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
#include "prx/libSceAgc/Shader/include/PrimState.hpp"
|
||||
|
||||
#include <cstdio>
|
||||
#include <array>
|
||||
#include <algorithm>
|
||||
#include <stdexcept>
|
||||
#include <prx/libc/include/General.hpp>
|
||||
|
||||
#include "SceShaders.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/Driver.hpp"
|
||||
#include "prx/libSceAgc/Shader/include/ShaderUtils.hpp"
|
||||
#include "prx/libSceAgc/Shader/include/ShaderConstants.hpp"
|
||||
|
||||
|
|
@ -22,44 +25,26 @@ int APS5_VABI sceAgcCreatePrimState(ShaderRegister* cx_regs, ShaderRegister* uc_
|
|||
throw std::runtime_error(std::string(__func__) + ": invalid shader type or missing special registers");
|
||||
}
|
||||
(void)GraphicsPrimTypeToGsOut(prim_type);
|
||||
if (cx_regs != nullptr) {
|
||||
const auto valid = [](const Shader* shader) { return shader->specials->vgt_shader_stages_en.offset == ShaderRegs::VGT_SHADER_STAGES_EN && shader->specials->vgt_gs_out_prim_type.offset == ShaderRegs::VGT_GS_OUT_PRIM_TYPE; };
|
||||
if (!valid(gs) || (hs != nullptr && !valid(hs))) {
|
||||
throw std::runtime_error(std::string(__func__) + ": invalid context register offsets");
|
||||
}
|
||||
const auto valid = [](const Shader* shader) { return shader->specials->vgt_shader_stages_en.offset == ShaderRegs::VGT_SHADER_STAGES_EN && shader->specials->vgt_gs_out_prim_type.offset == ShaderRegs::VGT_GS_OUT_PRIM_TYPE; };
|
||||
if (!valid(gs) || (hs != nullptr && !valid(hs))) {
|
||||
throw std::runtime_error(std::string(__func__) + ": invalid context register offsets");
|
||||
}
|
||||
if (uc_regs != nullptr && (gs->specials->ge_cntl.offset != ShaderRegs::GE_CNTL || gs->specials->ge_user_vgpr_en.offset != ShaderRegs::GE_USER_VGPR_EN || (hs != nullptr && hs->specials->ge_user_vgpr_en.offset != ShaderRegs::GE_USER_VGPR_EN))) {
|
||||
if (gs->specials->ge_cntl.offset != ShaderRegs::GE_CNTL || gs->specials->ge_user_vgpr_en.offset != ShaderRegs::GE_USER_VGPR_EN || (hs != nullptr && hs->specials->ge_user_vgpr_en.offset != ShaderRegs::GE_USER_VGPR_EN)) {
|
||||
throw std::runtime_error(std::string(__func__) + ": invalid user configuration register offsets");
|
||||
}
|
||||
|
||||
if (cx_regs != nullptr) {
|
||||
cx_regs[0] = gs->specials->vgt_shader_stages_en;
|
||||
if ((cx_regs[0].value & ShaderRegs::VGT_SHADER_STAGES_GS_BIT) != 0) {
|
||||
cx_regs[1] = gs->specials->vgt_gs_out_prim_type;
|
||||
} else {
|
||||
cx_regs[1].offset = ShaderRegs::VGT_GS_OUT_PRIM_TYPE;
|
||||
cx_regs[1].value = GraphicsPrimTypeToGsOut(prim_type);
|
||||
}
|
||||
|
||||
if (hs != nullptr) {
|
||||
cx_regs[0].value |= hs->specials->vgt_shader_stages_en.value;
|
||||
if ((cx_regs[0].value & ShaderRegs::VGT_SHADER_STAGES_GS_BIT) == 0) {
|
||||
cx_regs[1] = hs->specials->vgt_gs_out_prim_type;
|
||||
}
|
||||
}
|
||||
std::array<ShaderRegister, 2> contextValues{gs->specials->vgt_shader_stages_en, gs->specials->vgt_gs_out_prim_type};
|
||||
if ((contextValues[0].value & ShaderRegs::VGT_SHADER_STAGES_GS_BIT) == 0) {
|
||||
contextValues[1] = {ShaderRegs::VGT_GS_OUT_PRIM_TYPE, GraphicsPrimTypeToGsOut(prim_type)};
|
||||
}
|
||||
|
||||
if (uc_regs != nullptr) {
|
||||
uc_regs[0] = gs->specials->ge_cntl;
|
||||
uc_regs[1] = gs->specials->ge_user_vgpr_en;
|
||||
uc_regs[2].offset = ShaderRegs::VGT_PRIMITIVE_TYPE;
|
||||
uc_regs[2].value = prim_type;
|
||||
|
||||
if (hs != nullptr) {
|
||||
uc_regs[1] = hs->specials->ge_user_vgpr_en;
|
||||
}
|
||||
if (hs != nullptr) {
|
||||
contextValues[0].value |= hs->specials->vgt_shader_stages_en.value;
|
||||
if ((contextValues[0].value & ShaderRegs::VGT_SHADER_STAGES_GS_BIT) == 0) contextValues[1] = hs->specials->vgt_gs_out_prim_type;
|
||||
}
|
||||
|
||||
std::array<ShaderRegister, 3> primitiveValues{gs->specials->ge_cntl, hs != nullptr ? hs->specials->ge_user_vgpr_en : gs->specials->ge_user_vgpr_en, ShaderRegister{ShaderRegs::VGT_PRIMITIVE_TYPE, prim_type}};
|
||||
const std::array<const Shader*, 2> stages{hs, gs};
|
||||
AgcDriverResolveGraphicsStagesAbi_nid_postfix(std::span(stages).subspan(hs == nullptr ? 1u : 0u), contextValues, primitiveValues);
|
||||
if (cx_regs != nullptr) std::copy(contextValues.begin(), contextValues.end(), cx_regs);
|
||||
if (uc_regs != nullptr) std::copy(primitiveValues.begin(), primitiveValues.end(), uc_regs);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -80,8 +80,7 @@ int APS5_VABI sceAgcCbMemsetExclusive() {
|
|||
return 0;
|
||||
}
|
||||
|
||||
APS5_EXPORT("7Wa3aeJgeVU", sceAgcUnknown_7Wa3aeJgeVU);
|
||||
int APS5_VABI sceAgcUnknown_7Wa3aeJgeVU() {
|
||||
int APS5_VABI sceAgcBranchPatchSetThenTarget_0300() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -101,8 +100,7 @@ int APS5_VABI sceAgcGetShaderInstrumentation() {
|
|||
return 0;
|
||||
}
|
||||
|
||||
APS5_EXPORT("rP5xLdOf26k", sceAgcUnknown_rP5xLdOf26k);
|
||||
int APS5_VABI sceAgcUnknown_rP5xLdOf26k() {
|
||||
int APS5_VABI sceAgcBranchPatchSetElseTarget_0300() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,15 +18,15 @@ extern "C" int APS5_VABI sceAgcInit(std::uint32_t version);
|
|||
extern "C" void* APS5_VABI sceAgcGetRegisterDefaults();
|
||||
extern "C" void* APS5_VABI sceAgcGetRegisterDefaultsInternal();
|
||||
extern "C" void* APS5_VABI sceAgcGetRegisterDefaults2Internal(std::uint32_t version);
|
||||
extern "C" int APS5_VABI sceAgcUnknownInitState(std::uint32_t* state, std::uint32_t version);
|
||||
extern "C" int APS5_VABI sceAgcInit_0090(std::uint32_t* state, std::uint32_t version);
|
||||
extern "C" std::uint32_t* APS5_VABI sceAgcDcbDrawIndexAuto(CommandBuffer* buf, std::uint32_t indexCount, std::uint64_t modifier);
|
||||
extern "C" std::uint32_t* APS5_VABI sceAgcDcbDrawIndexIndirect(CommandBuffer* buf, std::uint32_t dataOffsetInBytes, std::uint64_t modifier);
|
||||
extern "C" std::uint32_t* APS5_VABI sceAgcDcbDrawIndexIndirectMulti(CommandBuffer* buf, std::uint32_t dataOffsetInBytes, std::uint32_t countIndirect, std::uint32_t maxCountOrCount, const volatile void* countAddress, std::uint32_t strideInBytes, std::uint64_t modifier);
|
||||
extern "C" int APS5_VABI sceAgcWaitRegMemPatchReference(std::uint32_t* cmd, std::uint64_t reference);
|
||||
extern "C" int APS5_VABI sceAgcWaitRegMemPatchMask(std::uint32_t* cmd, std::uint64_t mask);
|
||||
extern "C" int APS5_VABI sceAgcGetDataPacketPayloadAddressUnk(std::uint32_t** addr, std::uint32_t* cmd, int type);
|
||||
extern "C" int APS5_VABI sceAgcGetDataPacketPayloadAddress_0090(std::uint32_t** addr, std::uint32_t* cmd, int type);
|
||||
extern "C" std::uint32_t* APS5_VABI sceAgcCbSetShRegisterRangeDirect(CommandBuffer* buf, std::uint32_t offset, const std::uint32_t* values, std::uint32_t numValues);
|
||||
extern "C" std::uint32_t* APS5_VABI sceAgcDcbContextStateAnotherOp(CommandBuffer* buf, std::uint32_t operation);
|
||||
extern "C" std::uint32_t* APS5_VABI sceAgcDcbContextStateOp_0100(CommandBuffer* buf, std::uint32_t operation);
|
||||
extern "C" std::uint32_t* APS5_VABI sceAgcDcbPushMarker(CommandBuffer* buf, const char* str, std::uint32_t color);
|
||||
extern "C" std::uint32_t* APS5_VABI sceAgcDcbPopMarker(CommandBuffer* buf);
|
||||
extern "C" std::uint32_t* APS5_VABI sceAgcDcbSetMarker(CommandBuffer* buf, const char* str, std::uint32_t color);
|
||||
|
|
@ -101,6 +101,12 @@ void testPackets() {
|
|||
exhausted.buffer.cursor_down = exhausted.words.data() + 2;
|
||||
expectFailure([&] { Agc::Command::WriteNop(&exhausted.buffer, 3, __func__); });
|
||||
check(exhausted.buffer.cursor_up == exhausted.words.data(), "failed allocation advanced cursor");
|
||||
std::array<std::uint32_t, 8> scratch{};
|
||||
CommandBuffer noDown{scratch.data(), scratch.data() + scratch.size(), scratch.data(), nullptr, nullptr, nullptr, 0};
|
||||
Agc::Command::WriteNop(&noDown, 6, __func__);
|
||||
check(noDown.cursor_up == scratch.data() + 6, "buffer without a down cursor did not use its top as the limit");
|
||||
expectFailure([&] { Agc::Command::WriteNop(&noDown, 3, __func__); });
|
||||
check(noDown.cursor_up == scratch.data() + 6, "allocation past the top of a buffer without a down cursor advanced its cursor");
|
||||
}
|
||||
|
||||
void testClearState() {
|
||||
|
|
@ -222,7 +228,7 @@ void testContextState() {
|
|||
source.buffer.reserved_dw = 2;
|
||||
source.buffer.callback = growContext;
|
||||
source.buffer.user_data = &growth;
|
||||
auto* first = sceAgcDcbContextStateAnotherOp(&source.buffer, operation);
|
||||
auto* first = sceAgcDcbContextStateOp_0100(&source.buffer, operation);
|
||||
check(first == (split == 0 ? growth.destination.words.data() : source.words.data()), "incorrect first context packet address");
|
||||
check(growth.calls == (requested == 0 ? 0u : 1u), "incorrect context callback count");
|
||||
auto* end = requested == 0 ? source.words.data() + totals[operation] : growth.destination.words.data() + totals[operation] - split;
|
||||
|
|
@ -243,24 +249,24 @@ void testContextState() {
|
|||
}
|
||||
}
|
||||
}
|
||||
expectFailure([] { sceAgcDcbContextStateAnotherOp(nullptr, 0); });
|
||||
expectFailure([] { sceAgcDcbContextStateOp_0100(nullptr, 0); });
|
||||
Storage invalid;
|
||||
expectFailure([&] { sceAgcDcbContextStateAnotherOp(&invalid.buffer, 4); });
|
||||
expectFailure([&] { sceAgcDcbContextStateOp_0100(&invalid.buffer, 4); });
|
||||
check(invalid.buffer.cursor_up == invalid.words.data(), "invalid context operation advanced cursor");
|
||||
invalid.buffer.cursor_up = invalid.words.data() + 1;
|
||||
invalid.buffer.cursor_down = invalid.words.data();
|
||||
expectFailure([&] { sceAgcDcbContextStateAnotherOp(&invalid.buffer, 0); });
|
||||
expectFailure([&] { sceAgcDcbContextStateOp_0100(&invalid.buffer, 0); });
|
||||
invalid.buffer.cursor_up = invalid.words.data();
|
||||
expectFailure([&] { sceAgcDcbContextStateAnotherOp(&invalid.buffer, 1); });
|
||||
expectFailure([&] { sceAgcDcbContextStateOp_0100(&invalid.buffer, 1); });
|
||||
ContextGrowth growth{{}, invalid.words.data(), 22};
|
||||
invalid.buffer.callback = growContext;
|
||||
invalid.buffer.user_data = &growth;
|
||||
growth.success = false;
|
||||
expectFailure([&] { sceAgcDcbContextStateAnotherOp(&invalid.buffer, 1); });
|
||||
expectFailure([&] { sceAgcDcbContextStateOp_0100(&invalid.buffer, 1); });
|
||||
growth.calls = 0;
|
||||
growth.success = true;
|
||||
growth.destination.buffer.cursor_down = growth.destination.words.data() + 21;
|
||||
expectFailure([&] { sceAgcDcbContextStateAnotherOp(&invalid.buffer, 1); });
|
||||
expectFailure([&] { sceAgcDcbContextStateOp_0100(&invalid.buffer, 1); });
|
||||
check(invalid.buffer.cursor_up == growth.destination.words.data(), "failed reservation advanced cursor");
|
||||
}
|
||||
|
||||
|
|
@ -322,19 +328,19 @@ void testPacketPayloadAddress() {
|
|||
Storage storage;
|
||||
auto* packet = sceAgcCbSetShRegisterRangeDirect(&storage.buffer, 0x8c, nullptr, 4);
|
||||
std::uint32_t* payload = nullptr;
|
||||
check(sceAgcGetDataPacketPayloadAddressUnk(&payload, packet, 1) == 0 && payload == packet + 2, "incorrect register packet payload address");
|
||||
check(sceAgcGetDataPacketPayloadAddress_0090(&payload, packet, 1) == 0 && payload == packet + 2, "incorrect register packet payload address");
|
||||
const std::array<std::uint32_t, 4> values{11, 22, 33, 44};
|
||||
std::copy(values.begin(), values.end(), payload);
|
||||
const std::array<std::uint32_t, 6> expected{0xc0047600u, 0x8c, 11, 22, 33, 44};
|
||||
check(std::equal(expected.begin(), expected.end(), packet), "payload write corrupted register packet");
|
||||
check(sceAgcGetDataPacketPayloadAddressUnk(&payload, packet, 0) == 0 && payload == packet + 1, "incorrect generic packet payload address");
|
||||
check(sceAgcGetDataPacketPayloadAddress_0090(&payload, packet, 0) == 0 && payload == packet + 1, "incorrect generic packet payload address");
|
||||
packet[0] = 0xffff1000u;
|
||||
check(sceAgcGetDataPacketPayloadAddressUnk(&payload, packet, 0) == 0 && payload == nullptr, "empty payload marker was not recognized");
|
||||
check(sceAgcGetDataPacketPayloadAddressUnk(&payload, packet, -1) == 0 && payload == packet + 2, "nonzero payload type did not skip two words");
|
||||
expectFailure([&] { sceAgcGetDataPacketPayloadAddressUnk(nullptr, packet, 1); });
|
||||
expectFailure([&] { sceAgcGetDataPacketPayloadAddressUnk(&payload, nullptr, 1); });
|
||||
check(sceAgcGetDataPacketPayloadAddress_0090(&payload, packet, 0) == 0 && payload == nullptr, "empty payload marker was not recognized");
|
||||
check(sceAgcGetDataPacketPayloadAddress_0090(&payload, packet, -1) == 0 && payload == packet + 2, "nonzero payload type did not skip two words");
|
||||
expectFailure([&] { sceAgcGetDataPacketPayloadAddress_0090(nullptr, packet, 1); });
|
||||
expectFailure([&] { sceAgcGetDataPacketPayloadAddress_0090(&payload, nullptr, 1); });
|
||||
auto* misaligned = reinterpret_cast<std::uint32_t*>(reinterpret_cast<unsigned char*>(packet) + 1);
|
||||
expectFailure([&] { sceAgcGetDataPacketPayloadAddressUnk(&payload, misaligned, 0); });
|
||||
expectFailure([&] { sceAgcGetDataPacketPayloadAddress_0090(&payload, misaligned, 0); });
|
||||
check(payload == packet + 2, "invalid packet changed output address");
|
||||
}
|
||||
|
||||
|
|
@ -367,10 +373,10 @@ void testMemory() {
|
|||
|
||||
void testDefaults() {
|
||||
std::uint32_t state = 0x12345678;
|
||||
check(sceAgcUnknownInitState(&state, 8) == 0 && state == 0x12345678, "AGC initialization failed or modified caller state");
|
||||
check(sceAgcUnknownInitState(&state, 13) == 0 && state == 0x12345678, "AGC version 13 initialization changed caller state");
|
||||
expectFailure([] { sceAgcUnknownInitState(nullptr, 8); });
|
||||
expectFailure([&] { sceAgcUnknownInitState(&state, 14); });
|
||||
check(sceAgcInit_0090(&state, 8) == 0 && state == 0x12345678, "AGC initialization failed or modified caller state");
|
||||
check(sceAgcInit_0090(&state, 13) == 0 && state == 0x12345678, "AGC version 13 initialization changed caller state");
|
||||
expectFailure([] { sceAgcInit_0090(nullptr, 8); });
|
||||
expectFailure([&] { sceAgcInit_0090(&state, 14); });
|
||||
check(sceAgcInit(8) == 0, "AGC version initialization failed");
|
||||
expectFailure([] { sceAgcInit(14); });
|
||||
expectFailure([] { sceAgcInit(0xffffffffu); });
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
#include <stdexcept>
|
||||
|
||||
extern "C" std::uint64_t APS5_VABI sceAgcDcbContextStateOpGetSize(std::uint32_t operation);
|
||||
extern "C" std::uint32_t* APS5_VABI sceAgcDcbContextStateAnotherOp(CommandBuffer* buf, std::uint32_t operation);
|
||||
extern "C" std::uint32_t* APS5_VABI sceAgcDcbContextStateOp_0100(CommandBuffer* buf, std::uint32_t operation);
|
||||
|
||||
namespace {
|
||||
|
||||
|
|
@ -34,7 +34,7 @@ void testSizes() {
|
|||
std::array<std::uint32_t, 64> words{};
|
||||
CommandBuffer buffer{words.data(), words.data() + words.size(), words.data(), words.data() + words.size(),
|
||||
nullptr, nullptr, 0};
|
||||
check(sceAgcDcbContextStateAnotherOp(&buffer, operation) == words.data(), "context state op did not start at the cursor");
|
||||
check(sceAgcDcbContextStateOp_0100(&buffer, operation) == words.data(), "context state op did not start at the cursor");
|
||||
const auto written = static_cast<std::uint64_t>(buffer.cursor_up - words.data()) * sizeof(std::uint32_t);
|
||||
check(written == sizes[operation], "context state size differs from the written packets");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,10 +4,12 @@
|
|||
#include "prx/libSceAgc/Shader/include/ShaderConstants.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <stdexcept>
|
||||
#include <span>
|
||||
|
||||
extern "C" int APS5_VABI sceAgcCreateInterpolantMapping(ShaderRegister* regs, const Shader* gs, const Shader* ps);
|
||||
extern "C" int APS5_VABI sceAgcUnknownCreateInterpolantMapping(ShaderRegister* regs, const Shader* gs, const Shader* ps);
|
||||
|
|
@ -15,6 +17,10 @@ extern "C" int APS5_VABI sceAgcUnknownCreateInterpolantMapping(ShaderRegister* r
|
|||
namespace {
|
||||
|
||||
using Registers = std::array<ShaderRegister, 32>;
|
||||
const Shader* mappedPixel = nullptr;
|
||||
Registers preparedMapping{};
|
||||
unsigned mappings = 0;
|
||||
unsigned links = 0;
|
||||
|
||||
void check(bool condition, const char* message) {
|
||||
if (!condition) throw std::runtime_error(message);
|
||||
|
|
@ -82,6 +88,7 @@ void testMapping() {
|
|||
|
||||
auto split = filled();
|
||||
check(sceAgcUnknownCreateInterpolantMapping(split.data(), &gs, &ps) == 0, "split f16 mapping failed");
|
||||
check(std::memcmp(split.data(), preparedMapping.data(), sizeof(split)) == 0, "prepared split mapping differs from published registers");
|
||||
for (std::uint32_t i = 0; i < inputs.size(); ++i) {
|
||||
check(split[i].offset == ShaderRegs::SPI_PS_INPUT_CNTL_0 + i, "interpolant register offset changed");
|
||||
}
|
||||
|
|
@ -94,6 +101,7 @@ void testMapping() {
|
|||
|
||||
auto regular = filled();
|
||||
check(sceAgcCreateInterpolantMapping(regular.data(), &gs, &ps) == 0, "mapping failed");
|
||||
check(std::memcmp(regular.data(), preparedMapping.data(), sizeof(regular)) == 0, "prepared mapping differs from published registers");
|
||||
check(regular[3].value == split[3].value && regular[4].value == split[4].value, "mappings differ outside the high f16 half mode");
|
||||
checkIdentity(regular, static_cast<std::uint32_t>(inputs.size()), "unused interpolants are not the identity");
|
||||
}
|
||||
|
|
@ -117,11 +125,24 @@ void testRejections() {
|
|||
|
||||
}
|
||||
|
||||
extern "C" void AgcDriverResolveShaderAbi_nid_postfix(const Shader* shader, std::span<const ShaderRegister> context, std::span<const ShaderRegister> primitive) {
|
||||
check(shader != nullptr && context.size() == preparedMapping.size() && primitive.empty(), "invalid mapping preparation call");
|
||||
mappedPixel = shader;
|
||||
std::copy(context.begin(), context.end(), preparedMapping.begin());
|
||||
++mappings;
|
||||
}
|
||||
|
||||
extern "C" void AgcDriverResolveGraphicsAbi_nid_postfix(const Shader* vertex, const Shader* pixel, std::uint32_t primitiveType) {
|
||||
check(vertex != nullptr && pixel == mappedPixel && primitiveType == 0u, "invalid mapping link call");
|
||||
++links;
|
||||
}
|
||||
|
||||
int main() {
|
||||
try {
|
||||
testIdentity();
|
||||
testMapping();
|
||||
testRejections();
|
||||
check(mappings == 3u && links == 2u, "mapping preparation call count changed");
|
||||
LibcRunShutdown_nid_postfix();
|
||||
std::puts("AGC interpolant mapping tests passed");
|
||||
return 0;
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
#include <tuple>
|
||||
|
||||
extern "C" int APS5_VABI sceAgcGetDataPacketPayloadRange(SceAgcMemoryRange* range, std::uint32_t* cmd, int type);
|
||||
extern "C" int APS5_VABI sceAgcGetDataPacketPayloadAddressUnk(std::uint32_t** addr, std::uint32_t* cmd, int type);
|
||||
extern "C" int APS5_VABI sceAgcGetDataPacketPayloadAddress_0090(std::uint32_t** addr, std::uint32_t* cmd, int type);
|
||||
|
||||
namespace {
|
||||
|
||||
|
|
@ -42,7 +42,7 @@ void testRanges() {
|
|||
check(range.size == size, "payload range size mismatch");
|
||||
check(words[0] == header && words[1] == 0x12345678u, "payload range query modified the packet");
|
||||
std::uint32_t* address = nullptr;
|
||||
check(sceAgcGetDataPacketPayloadAddressUnk(&address, words.data(), type) == 0 && address == range.base,
|
||||
check(sceAgcGetDataPacketPayloadAddress_0090(&address, words.data(), type) == 0 && address == range.base,
|
||||
"payload range base differs from the payload address");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
#include "prx/libSceAgc/Misc/include/ShaderFusion.hpp"
|
||||
#include "prx/libSceAgcDriver/tests/execution/VulkanTestDevice.hpp"
|
||||
#include "prx/libSceAgc/Shader/include/ShaderConstants.hpp"
|
||||
#include "prx/libc/include/Shutdown.hpp"
|
||||
|
||||
|
|
@ -15,6 +16,7 @@ namespace {
|
|||
constexpr std::uint32_t SCodeEnd = 0xbf9f0000u;
|
||||
constexpr std::uint32_t SNop = 0xbf800000u;
|
||||
constexpr std::uint32_t SSetpcS6 = 0xbe802006u;
|
||||
constexpr std::uint32_t SSwappcNullS6 = 0xbefd2106u;
|
||||
constexpr std::uint32_t SMovS0 = 0xbe800380u;
|
||||
constexpr std::uint32_t VMovV0 = 0x7e000280u;
|
||||
constexpr std::uint32_t SEndpgm = 0xbf810000u;
|
||||
|
|
@ -67,8 +69,8 @@ struct Halves {
|
|||
Shader front{};
|
||||
Shader back{};
|
||||
|
||||
Halves() {
|
||||
frontCode = {SMovS0, VMovV0, SSetpcS6, SCodeEnd, SCodeEnd};
|
||||
explicit Halves(std::uint32_t terminator = SSetpcS6) {
|
||||
frontCode = {SMovS0, VMovV0, terminator, SCodeEnd, SCodeEnd};
|
||||
const auto codeBytes = static_cast<std::uint32_t>(frontCode.size() * 4);
|
||||
std::array<std::uint32_t, 2> magic{};
|
||||
std::memcpy(magic.data(), "barefoot", 8);
|
||||
|
|
@ -123,16 +125,16 @@ std::uint8_t* aligned(std::vector<std::uint8_t>& storage, std::size_t alignment)
|
|||
return storage.data() + ((alignment - address % alignment) % alignment);
|
||||
}
|
||||
|
||||
void testFusion(std::size_t misalignment) {
|
||||
Halves halves;
|
||||
void testFusion(std::size_t misalignment, std::uint32_t terminator = SSetpcS6) {
|
||||
Halves halves(terminator);
|
||||
SizeAlign size{};
|
||||
check(sceAgcUnknownGetFusedShaderSize(&size, &halves.front, &halves.back) == 0, "fused size query failed");
|
||||
check(sceAgcGetFusedShaderSize_0080(&size, &halves.front, &halves.back) == 0, "fused size query failed");
|
||||
check(size.m_align == 8, "fused shaders are not 256-byte aligned");
|
||||
check(size.m_size > 256 + 255 + halves.back.shader_size + sizeof(Shader), "fused size does not cover the alignment padding, code and header");
|
||||
std::vector<std::uint8_t> storage(size.m_size + 512, 0xcd);
|
||||
auto* scratch = aligned(storage, 256) + misalignment;
|
||||
Shader fused{};
|
||||
check(sceAgcUnknownFuseShaderHalves(&fused, &halves.front, &halves.back, scratch) == 0, "fusion failed");
|
||||
check(sceAgcFuseShaderHalves_0200(&fused, &halves.front, &halves.back, scratch) == 0, "fusion failed");
|
||||
for (std::size_t i = 0; i < 16; ++i) check(scratch[size.m_size + i] == 0xcd, "fusion wrote past the reported size");
|
||||
auto* memory = scratch + (256 - misalignment) % 256;
|
||||
const auto base = reinterpret_cast<std::uintptr_t>(memory);
|
||||
|
|
@ -173,38 +175,38 @@ void testRejections() {
|
|||
Halves halves;
|
||||
SizeAlign size{};
|
||||
Shader fused{};
|
||||
expectFailure([&] { sceAgcUnknownGetFusedShaderSize(nullptr, &halves.front, &halves.back); });
|
||||
expectFailure([&] { sceAgcUnknownFuseShaderHalves(&fused, &halves.front, &halves.back, nullptr); });
|
||||
expectFailure([&] { sceAgcGetFusedShaderSize_0080(nullptr, &halves.front, &halves.back); });
|
||||
expectFailure([&] { sceAgcFuseShaderHalves_0200(&fused, &halves.front, &halves.back, nullptr); });
|
||||
{
|
||||
Halves swapped;
|
||||
check(sceAgcUnknownGetFusedShaderSize(&size, &swapped.back, &swapped.front) == InvalidShaderHalves, "swapped halves were accepted by the size query");
|
||||
check(sceAgcUnknownFuseShaderHalves(&fused, &swapped.back, &swapped.front, nullptr) == InvalidShaderHalves, "swapped halves were fused");
|
||||
check(sceAgcGetFusedShaderSize_0080(&size, &swapped.back, &swapped.front) == InvalidShaderHalves, "swapped halves were accepted by the size query");
|
||||
check(sceAgcFuseShaderHalves_0200(&fused, &swapped.back, &swapped.front, nullptr) == InvalidShaderHalves, "swapped halves were fused");
|
||||
}
|
||||
{
|
||||
Halves stages;
|
||||
stages.backSpecials.vgt_shader_stages_en.value = 0;
|
||||
std::vector<std::uint8_t> storage(4096);
|
||||
check(sceAgcUnknownFuseShaderHalves(&fused, &stages.front, &stages.back, storage.data()) == InvalidShaderHalves, "halves with different GS stages were fused");
|
||||
check(sceAgcFuseShaderHalves_0200(&fused, &stages.front, &stages.back, storage.data()) == InvalidShaderHalves, "halves with different GS stages were fused");
|
||||
}
|
||||
{
|
||||
Halves noJump;
|
||||
noJump.frontCode[2] = SNop;
|
||||
expectFailure([&] { sceAgcUnknownGetFusedShaderSize(&size, &noJump.front, &noJump.back); });
|
||||
expectFailure([&] { sceAgcGetFusedShaderSize_0080(&size, &noJump.front, &noJump.back); });
|
||||
}
|
||||
{
|
||||
Halves noTrailer;
|
||||
noTrailer.frontCode[5] = 0;
|
||||
expectFailure([&] { sceAgcUnknownGetFusedShaderSize(&size, &noTrailer.front, &noTrailer.back); });
|
||||
expectFailure([&] { sceAgcGetFusedShaderSize_0080(&size, &noTrailer.front, &noTrailer.back); });
|
||||
}
|
||||
{
|
||||
Halves hardware;
|
||||
hardware.frontSh[2].value |= 1u << 16u;
|
||||
expectFailure([&] { sceAgcUnknownGetFusedShaderSize(&size, &hardware.front, &hardware.back); });
|
||||
expectFailure([&] { sceAgcGetFusedShaderSize_0080(&size, &hardware.front, &hardware.back); });
|
||||
}
|
||||
{
|
||||
Halves constants;
|
||||
constants.back.embedded_constant_buffer_size_dqw = 1;
|
||||
expectFailure([&] { sceAgcUnknownGetFusedShaderSize(&size, &constants.front, &constants.back); });
|
||||
expectFailure([&] { sceAgcGetFusedShaderSize_0080(&size, &constants.front, &constants.back); });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -228,10 +230,10 @@ void testHullHalves() {
|
|||
back.specials = &specials;
|
||||
back.type = static_cast<std::uint8_t>(ShaderRegs::ShaderBinaryType::HsBack);
|
||||
SizeAlign size{};
|
||||
check(sceAgcUnknownGetFusedShaderSize(&size, &front, &back) == 0 && size.m_size == backSh.size() * sizeof(ShaderRegister) && size.m_align == 4, "hull size query changed");
|
||||
check(sceAgcGetFusedShaderSize_0080(&size, &front, &back) == 0 && size.m_size == backSh.size() * sizeof(ShaderRegister) && size.m_align == 4, "hull size query changed");
|
||||
std::vector<ShaderRegister> scratch(backSh.size());
|
||||
Shader fused{};
|
||||
check(sceAgcUnknownFuseShaderHalves(&fused, &front, &back, scratch.data()) == 0, "hull fusion failed");
|
||||
check(sceAgcFuseShaderHalves_0200(&fused, &front, &back, scratch.data()) == 0, "hull fusion failed");
|
||||
check(fused.type == static_cast<std::uint8_t>(ShaderRegs::ShaderBinaryType::Hs) && fused.code == back.code && fused.sh_registers == scratch.data() && fused.user_data == nullptr, "hull halves were not fused at the register level");
|
||||
const auto address = reinterpret_cast<std::uint64_t>(front.code);
|
||||
check(scratch[0].value == 0x11u && scratch[1].value == 0x22u, "hull checksums were not taken from the front half");
|
||||
|
|
@ -244,9 +246,12 @@ void testHullHalves() {
|
|||
|
||||
int main() {
|
||||
try {
|
||||
const auto device = OpenVulkanTestDevice();
|
||||
if (!device) return VulkanTestSkipped;
|
||||
testFusion(0);
|
||||
testFusion(0xa0);
|
||||
testFusion(4);
|
||||
testFusion(0, SSwappcNullS6);
|
||||
testRejections();
|
||||
testHullHalves();
|
||||
LibcRunShutdown_nid_postfix();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,129 @@
|
|||
#include "SceShaders.hpp"
|
||||
#include "prx/libSceAgc/Shader/include/PrimState.hpp"
|
||||
#include "prx/libSceAgc/Shader/include/InterpolantMapping.hpp"
|
||||
#include "prx/libSceAgc/Shader/include/ShaderConstants.hpp"
|
||||
#include "prx/libc/include/general/VabiMacros.hpp"
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <span>
|
||||
#include <stdexcept>
|
||||
#include <iostream>
|
||||
|
||||
extern "C" int APS5_VABI sceAgcLinkShaders(ShaderRegister*, ShaderRegister*, const void*, const Shader*, const Shader*, std::uint32_t);
|
||||
extern "C" int APS5_VABI sceAgcCreatePrimState(ShaderRegister*, ShaderRegister*, const Shader*, const Shader*, std::uint32_t);
|
||||
extern "C" int APS5_VABI sceAgcCreateInterpolantMapping(ShaderRegister*, const Shader*, const Shader*);
|
||||
|
||||
namespace {
|
||||
std::array<ShaderRegister, 2> preparedContext;
|
||||
std::array<ShaderRegister, 3> preparedPrimitive;
|
||||
std::size_t stageCount = 0;
|
||||
unsigned preparations = 0;
|
||||
unsigned mappings = 0;
|
||||
unsigned links = 0;
|
||||
bool rejectLink = false;
|
||||
const Shader* mappedPixel = nullptr;
|
||||
|
||||
void Require(bool condition) {
|
||||
if (!condition) throw std::runtime_error("shader helper preparation mismatch");
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void AgcDriverResolveGraphicsStagesAbi_nid_postfix(std::span<const Shader* const> stages, std::span<const ShaderRegister> context, std::span<const ShaderRegister> primitive) {
|
||||
Require(context.size() == 2 && primitive.size() == 3);
|
||||
std::copy(context.begin(), context.end(), preparedContext.begin());
|
||||
std::copy(primitive.begin(), primitive.end(), preparedPrimitive.begin());
|
||||
stageCount = stages.size();
|
||||
++preparations;
|
||||
}
|
||||
|
||||
extern "C" void AgcDriverResolveShaderAbi_nid_postfix(const Shader* shader, std::span<const ShaderRegister> context, std::span<const ShaderRegister> primitive) {
|
||||
Require(context.size() == 32 && primitive.empty());
|
||||
mappedPixel = shader;
|
||||
++mappings;
|
||||
}
|
||||
|
||||
extern "C" void AgcDriverResolveGraphicsAbi_nid_postfix(const Shader* vertex, const Shader* pixel, std::uint32_t primitiveType) {
|
||||
if (rejectLink) throw std::runtime_error("injected link failure");
|
||||
Require(vertex != nullptr && pixel == mappedPixel && (primitiveType == 0 || primitiveType == 7));
|
||||
++links;
|
||||
}
|
||||
|
||||
int main() {
|
||||
try {
|
||||
using namespace ShaderRegs;
|
||||
ShaderSpecialRegs special{};
|
||||
special.vgt_shader_stages_en = {VGT_SHADER_STAGES_EN, 0};
|
||||
special.vgt_gs_out_prim_type = {VGT_GS_OUT_PRIM_TYPE, 2};
|
||||
special.ge_cntl = {GE_CNTL, 0x123};
|
||||
special.ge_user_vgpr_en = {GE_USER_VGPR_EN, 7};
|
||||
Shader vertex{};
|
||||
vertex.type = static_cast<std::uint8_t>(ShaderBinaryType::Gs);
|
||||
vertex.specials = &special;
|
||||
Shader hull{};
|
||||
hull.type = static_cast<std::uint8_t>(ShaderBinaryType::Hs);
|
||||
hull.specials = &special;
|
||||
Shader pixel{};
|
||||
pixel.type = static_cast<std::uint8_t>(ShaderBinaryType::Ps);
|
||||
ShaderSemantic input{};
|
||||
input.semantic = 9;
|
||||
pixel.input_semantics = &input;
|
||||
std::array<ShaderRegister, 32> interpolants{};
|
||||
for (const auto count : {0u, 1u}) {
|
||||
pixel.num_input_semantics = count;
|
||||
for (const bool primitiveFirst : {false, true}) {
|
||||
for (unsigned outputs = 1; outputs <= 3; ++outputs) {
|
||||
for (const auto* hs : {static_cast<const Shader*>(nullptr), static_cast<const Shader*>(&hull)}) {
|
||||
preparations = mappings = links = 0;
|
||||
std::array<ShaderRegister, 3> context{};
|
||||
std::array<ShaderRegister, 4> primitive{};
|
||||
context.back().value = primitive.back().value = 0xdeadbeef;
|
||||
const auto prepare = [&] { Require(sceAgcCreatePrimState(outputs & 1 ? context.data() : nullptr, outputs & 2 ? primitive.data() : nullptr, hs, &vertex, 7) == 0); };
|
||||
if (primitiveFirst) prepare();
|
||||
Require(sceAgcCreateInterpolantMapping(interpolants.data(), &vertex, &pixel) == 0);
|
||||
if (!primitiveFirst) prepare();
|
||||
Require(preparations == 1 && mappings == 1 && links == 1 && stageCount == (hs ? 2u : 1u));
|
||||
Require(preparedContext[0].offset == VGT_SHADER_STAGES_EN && preparedContext[1].offset == VGT_GS_OUT_PRIM_TYPE);
|
||||
Require(preparedPrimitive[0].value == 0x123 && preparedPrimitive[1].value == 7 && preparedPrimitive[2].value == 7);
|
||||
Require(context.back().value == 0xdeadbeef && primitive.back().value == 0xdeadbeef);
|
||||
for (unsigned i = 0; i < 2; ++i) Require(context[i].value == (outputs & 1 ? preparedContext[i].value : 0));
|
||||
for (unsigned i = 0; i < 3; ++i) Require(primitive[i].value == (outputs & 2 ? preparedPrimitive[i].value : 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
std::array<ShaderRegister, 34> linkedContext{};
|
||||
std::array<ShaderRegister, 3> linkedPrimitive{};
|
||||
for (auto& value : linkedContext) value.value = 0xdeadbeef;
|
||||
for (auto& value : linkedPrimitive) value.value = 0xdeadbeef;
|
||||
for (auto& value : interpolants) value.value = 0xdeadbeef;
|
||||
rejectLink = true;
|
||||
bool rejected = false;
|
||||
try { sceAgcLinkShaders(linkedContext.data(), linkedPrimitive.data(), nullptr, &vertex, &pixel, 7); }
|
||||
catch (const std::runtime_error&) { rejected = true; }
|
||||
Require(rejected);
|
||||
for (const auto value : linkedContext) Require(value.value == 0xdeadbeef);
|
||||
for (const auto value : linkedPrimitive) Require(value.value == 0xdeadbeef);
|
||||
rejected = false;
|
||||
try { sceAgcCreateInterpolantMapping(interpolants.data(), &vertex, &pixel); }
|
||||
catch (const std::runtime_error&) { rejected = true; }
|
||||
Require(rejected);
|
||||
for (const auto value : interpolants) Require(value.value == 0xdeadbeef);
|
||||
rejectLink = false;
|
||||
pixel.num_input_semantics = 33;
|
||||
rejected = false;
|
||||
try { sceAgcCreateInterpolantMapping(interpolants.data(), &vertex, &pixel); }
|
||||
catch (const std::runtime_error&) { rejected = true; }
|
||||
Require(rejected);
|
||||
for (const auto value : interpolants) Require(value.value == 0xdeadbeef);
|
||||
pixel.num_input_semantics = 1;
|
||||
Require(sceAgcLinkShaders(linkedContext.data(), linkedPrimitive.data(), nullptr, &vertex, &pixel, 7) == 0);
|
||||
preparations = mappings = links = 0;
|
||||
Require(sceAgcCreatePrimState(nullptr, nullptr, nullptr, nullptr, 7) == 0);
|
||||
Require(sceAgcCreateInterpolantMapping(interpolants.data(), &vertex, nullptr) == 0);
|
||||
Require(preparations == 0 && mappings == 0 && links == 0);
|
||||
std::cout << "shader helper preparation tests passed\n";
|
||||
} catch (const std::exception& error) {
|
||||
std::cerr << error.what() << '\n';
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
|
@ -126,6 +126,8 @@ set(agcDriverSources
|
|||
${CMAKE_CURRENT_SOURCE_DIR}/Execution/src/GuestMemory.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Execution/src/ShaderMemory.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Execution/src/BdaFeatures.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Execution/src/SubgroupClock.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Execution/src/ShaderDeviceProfile.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Execution/src/Pm4.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Execution/src/VulkanDevice.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Execution/src/AspectFit.cpp
|
||||
|
|
@ -180,12 +182,12 @@ target_include_directories(libSceAgcDriver PRIVATE
|
|||
${CMAKE_SOURCE_DIR}/3rdparty/SPIRV-Headers/include
|
||||
)
|
||||
|
||||
file(GLOB_RECURSE agcShaderCacheGuardSources CONFIGURE_DEPENDS
|
||||
file(GLOB_RECURSE agcShaderCacheGuardSources CONFIGURE_DEPENDS RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/*.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/*.hpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/*.h"
|
||||
)
|
||||
list(FILTER agcShaderCacheGuardSources EXCLUDE REGEX "^${CMAKE_CURRENT_SOURCE_DIR}/(tests|tools)/")
|
||||
list(FILTER agcShaderCacheGuardSources EXCLUDE REGEX "^(tests|tools)/")
|
||||
add_shader_recompiler(shader_recompiler_agc_driver libc
|
||||
GUARD_SOURCES
|
||||
${agcShaderCacheGuardSources}
|
||||
|
|
@ -202,6 +204,16 @@ set_target_properties(libSceAgcDriver PROPERTIES CXX_EXTENSIONS OFF)
|
|||
configure_windows_unwind(libSceAgcDriver)
|
||||
|
||||
if(BUILD_TESTING)
|
||||
add_test_executable(agc_write_watch_coverage_tests tests/WriteWatchCoverage.cpp)
|
||||
target_include_directories(agc_write_watch_coverage_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(agc_write_watch_coverage_tests PRIVATE libc)
|
||||
configure_windows_unwind(agc_write_watch_coverage_tests)
|
||||
add_test(NAME agc_write_watch_coverage COMMAND agc_write_watch_coverage_tests)
|
||||
if(WIN32)
|
||||
set_tests_properties(agc_write_watch_coverage PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
|
||||
add_test_executable(agc_profile_output_tests tests/ProfileOutput.cpp)
|
||||
target_include_directories(agc_profile_output_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(agc_profile_output_tests PRIVATE libc)
|
||||
|
|
@ -229,6 +241,7 @@ add_test_executable(agc_driver_tests EXCLUDE_FROM_ALL
|
|||
target_include_directories(agc_driver_tests PRIVATE
|
||||
${LIBS_INCLUDE_DIR}
|
||||
${CMAKE_SOURCE_DIR}/core/shader/recompiler
|
||||
${CMAKE_SOURCE_DIR}/3rdparty/Vulkan-Headers/include
|
||||
)
|
||||
|
||||
target_link_libraries(agc_driver_tests PRIVATE
|
||||
|
|
@ -273,11 +286,35 @@ target_link_libraries(agc_structurizer_tests PRIVATE shader_recompiler_agc_drive
|
|||
set_target_properties(agc_structurizer_tests PROPERTIES CXX_EXTENSIONS OFF)
|
||||
configure_windows_unwind(agc_structurizer_tests)
|
||||
|
||||
add_test_executable(agc_subgroup_clock_tests EXCLUDE_FROM_ALL
|
||||
tests/SubgroupClock.cpp
|
||||
Execution/src/SubgroupClock.cpp
|
||||
)
|
||||
target_include_directories(agc_subgroup_clock_tests PRIVATE ${LIBS_INCLUDE_DIR} ${CMAKE_SOURCE_DIR}/3rdparty/Vulkan-Headers/include)
|
||||
set_target_properties(agc_subgroup_clock_tests PROPERTIES CXX_EXTENSIONS OFF)
|
||||
configure_windows_unwind(agc_subgroup_clock_tests)
|
||||
if(BUILD_TESTING)
|
||||
set_target_properties(agc_subgroup_clock_tests PROPERTIES EXCLUDE_FROM_ALL OFF)
|
||||
add_test(NAME agc_subgroup_clock COMMAND agc_subgroup_clock_tests)
|
||||
endif()
|
||||
|
||||
add_test_executable(agc_spirv_module_tests EXCLUDE_FROM_ALL tests/SpirvModule.cpp)
|
||||
target_link_libraries(agc_spirv_module_tests PRIVATE shader_recompiler_agc_driver libc)
|
||||
set_target_properties(agc_spirv_module_tests PROPERTIES CXX_EXTENSIONS OFF)
|
||||
configure_windows_unwind(agc_spirv_module_tests)
|
||||
|
||||
add_test_executable(agc_spirv_narrow_constant_store_tests EXCLUDE_FROM_ALL tests/SpirvNarrowConstantStore.cpp)
|
||||
target_link_libraries(agc_spirv_narrow_constant_store_tests PRIVATE shader_recompiler_agc_driver libc)
|
||||
set_target_properties(agc_spirv_narrow_constant_store_tests PROPERTIES CXX_EXTENSIONS OFF)
|
||||
configure_windows_unwind(agc_spirv_narrow_constant_store_tests)
|
||||
if(ANYPS5_ENABLE_SPIRV_TOOLS)
|
||||
target_link_libraries(agc_spirv_narrow_constant_store_tests PRIVATE SPIRV-Tools-static)
|
||||
add_test_executable(agc_spirv_optimizer_tests EXCLUDE_FROM_ALL tests/SpirvOptimizer.cpp)
|
||||
target_link_libraries(agc_spirv_optimizer_tests PRIVATE shader_recompiler_agc_driver libc SPIRV-Tools-static)
|
||||
set_target_properties(agc_spirv_optimizer_tests PROPERTIES CXX_EXTENSIONS OFF)
|
||||
configure_windows_unwind(agc_spirv_optimizer_tests)
|
||||
endif()
|
||||
|
||||
add_test_executable(agc_dpp_write_tests EXCLUDE_FROM_ALL tests/DppWrite.cpp)
|
||||
target_link_libraries(agc_dpp_write_tests PRIVATE shader_recompiler_agc_driver libc)
|
||||
set_target_properties(agc_dpp_write_tests PROPERTIES CXX_EXTENSIONS OFF)
|
||||
|
|
@ -288,6 +325,20 @@ target_link_libraries(agc_masked_select_tests PRIVATE shader_recompiler_agc_driv
|
|||
set_target_properties(agc_masked_select_tests PROPERTIES CXX_EXTENSIONS OFF)
|
||||
configure_windows_unwind(agc_masked_select_tests)
|
||||
|
||||
add_test_executable(agc_wave_control_decode_tests EXCLUDE_FROM_ALL tests/WaveControlDecode.cpp)
|
||||
target_include_directories(agc_wave_control_decode_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(agc_wave_control_decode_tests PRIVATE shader_recompiler_agc_driver libc)
|
||||
set_target_properties(agc_wave_control_decode_tests PROPERTIES CXX_EXTENSIONS OFF)
|
||||
configure_windows_unwind(agc_wave_control_decode_tests)
|
||||
if(BUILD_TESTING)
|
||||
set_target_properties(agc_wave_control_decode_tests PROPERTIES EXCLUDE_FROM_ALL OFF)
|
||||
add_test(NAME agc_wave_control_decode COMMAND agc_wave_control_decode_tests)
|
||||
if(WIN32)
|
||||
set_tests_properties(agc_wave_control_decode PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
add_test_executable(agc_vop1_control_decode_tests EXCLUDE_FROM_ALL tests/Vop1ControlDecode.cpp)
|
||||
target_link_libraries(agc_vop1_control_decode_tests PRIVATE shader_recompiler_agc_driver libc)
|
||||
set_target_properties(agc_vop1_control_decode_tests PROPERTIES CXX_EXTENSIONS OFF)
|
||||
|
|
@ -332,6 +383,12 @@ target_link_libraries(agc_shader_disk_cache_tests PRIVATE shader_recompiler_agc_
|
|||
set_target_properties(agc_shader_disk_cache_tests PROPERTIES CXX_EXTENSIONS OFF)
|
||||
configure_windows_unwind(agc_shader_disk_cache_tests)
|
||||
|
||||
add_test_executable(agc_shader_device_profile_tests EXCLUDE_FROM_ALL tests/ShaderDeviceProfile.cpp Execution/src/ShaderDeviceProfile.cpp)
|
||||
target_include_directories(agc_shader_device_profile_tests PRIVATE ${LIBS_INCLUDE_DIR} ${CMAKE_SOURCE_DIR}/3rdparty/Vulkan-Headers/include)
|
||||
target_link_libraries(agc_shader_device_profile_tests PRIVATE shader_recompiler_agc_driver libc)
|
||||
set_target_properties(agc_shader_device_profile_tests PROPERTIES CXX_EXTENSIONS OFF)
|
||||
configure_windows_unwind(agc_shader_device_profile_tests)
|
||||
|
||||
add_test_executable(agc_shader_control_flow_tests EXCLUDE_FROM_ALL
|
||||
tests/ControlFlow.cpp
|
||||
)
|
||||
|
|
@ -370,10 +427,24 @@ add_custom_command(
|
|||
VERBATIM
|
||||
)
|
||||
|
||||
set(agcSampleArrayShaderSource ${CMAKE_CURRENT_SOURCE_DIR}/tests/shaders/SampleArray.comp)
|
||||
set(agcSampleArrayShaderSpv ${agcTestShaderGeneratedRoot}/SampleArray.spv)
|
||||
set(agcSampleArrayShaderHeader ${agcTestShaderGeneratedRoot}/SampleArray_spv.h)
|
||||
add_custom_command(
|
||||
OUTPUT "${agcSampleArrayShaderHeader}"
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory "${agcTestShaderGeneratedRoot}"
|
||||
COMMAND "$<TARGET_FILE:glslang-standalone>" -V --target-env vulkan1.1 -o "${agcSampleArrayShaderSpv}" "${agcSampleArrayShaderSource}"
|
||||
COMMAND ${CMAKE_COMMAND} -DINPUT=${agcSampleArrayShaderSpv} -DOUTPUT=${agcSampleArrayShaderHeader}
|
||||
-DSYMBOL=SAMPLE_ARRAY_SPV -P "${CMAKE_CURRENT_SOURCE_DIR}/embed_spirv.cmake"
|
||||
DEPENDS "${agcSampleArrayShaderSource}" glslang-standalone
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
add_test_executable(agc_driver_recorder_tests EXCLUDE_FROM_ALL
|
||||
tests/Recorder.cpp
|
||||
tests/ResidentPresent.cpp
|
||||
${agcSampleLodShaderHeader}
|
||||
${agcSampleArrayShaderHeader}
|
||||
)
|
||||
|
||||
target_include_directories(agc_driver_recorder_tests PRIVATE
|
||||
|
|
@ -392,6 +463,20 @@ target_link_libraries(agc_driver_recorder_tests PRIVATE
|
|||
set_target_properties(agc_driver_recorder_tests PROPERTIES CXX_EXTENSIONS OFF)
|
||||
configure_windows_unwind(agc_driver_recorder_tests)
|
||||
|
||||
add_test_executable(agc_recorder_kept_bytes_tests EXCLUDE_FROM_ALL tests/RecorderKeptBytes.cpp)
|
||||
target_include_directories(agc_recorder_kept_bytes_tests PRIVATE ${LIBS_INCLUDE_DIR} ${CMAKE_SOURCE_DIR}/core/shader/recompiler ${CMAKE_SOURCE_DIR}/3rdparty/Vulkan-Headers/include)
|
||||
target_link_libraries(agc_recorder_kept_bytes_tests PRIVATE libSceAgcDriver libc)
|
||||
set_target_properties(agc_recorder_kept_bytes_tests PROPERTIES CXX_EXTENSIONS OFF)
|
||||
configure_windows_unwind(agc_recorder_kept_bytes_tests)
|
||||
if(BUILD_TESTING)
|
||||
set_target_properties(agc_recorder_kept_bytes_tests PROPERTIES EXCLUDE_FROM_ALL OFF)
|
||||
add_test(NAME agc_recorder_kept_bytes COMMAND agc_recorder_kept_bytes_tests)
|
||||
if(WIN32)
|
||||
set_tests_properties(agc_recorder_kept_bytes PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
add_test_executable(agc_driver_pm4_tests EXCLUDE_FROM_ALL
|
||||
tests/Pm4.cpp
|
||||
)
|
||||
|
|
@ -403,6 +488,7 @@ target_include_directories(agc_driver_pm4_tests PRIVATE
|
|||
target_link_libraries(agc_driver_pm4_tests PRIVATE
|
||||
libSceAgcDriver
|
||||
libc
|
||||
libkernel
|
||||
)
|
||||
|
||||
set_target_properties(agc_driver_pm4_tests PROPERTIES CXX_EXTENSIONS OFF)
|
||||
|
|
@ -464,6 +550,7 @@ target_include_directories(agc_driver_graphics_tests PRIVATE
|
|||
target_link_libraries(agc_driver_graphics_tests PRIVATE
|
||||
shader_recompiler_agc_driver
|
||||
libc
|
||||
libkernel
|
||||
)
|
||||
|
||||
set_target_properties(agc_driver_graphics_tests PROPERTIES CXX_EXTENSIONS OFF)
|
||||
|
|
@ -520,17 +607,90 @@ foreach(agcExecutionTestSource IN LISTS agcExecutionTestSources)
|
|||
list(APPEND agcExecutionTests ${agcExecutionTest})
|
||||
endforeach()
|
||||
|
||||
target_include_directories(agc_driver_prepared_exports_tests PRIVATE $<TARGET_PROPERTY:shader_recompiler_agc_driver,INTERFACE_INCLUDE_DIRECTORIES>)
|
||||
|
||||
target_include_directories(agc_driver_prepared_publication_tests PRIVATE $<TARGET_PROPERTY:shader_recompiler_agc_driver,INTERFACE_INCLUDE_DIRECTORIES>)
|
||||
|
||||
target_include_directories(agc_driver_prepared_graphics_tests PRIVATE $<TARGET_PROPERTY:shader_recompiler_agc_driver,INTERFACE_INCLUDE_DIRECTORIES>)
|
||||
|
||||
target_include_directories(agc_driver_prepared_shaders_tests PRIVATE
|
||||
$<TARGET_PROPERTY:shader_recompiler_agc_driver,INTERFACE_INCLUDE_DIRECTORIES>
|
||||
)
|
||||
|
||||
if(BUILD_TESTING)
|
||||
foreach(agcTest IN ITEMS agc_driver_tests agc_shader_memory_tests agc_structurizer_tests agc_spirv_module_tests agc_dpp_write_tests agc_masked_select_tests agc_vop1_control_decode_tests agc_validate_program_tests agc_shader_disk_cache_tests agc_shader_control_flow_tests agc_driver_flip_tests agc_driver_pm4_tests agc_driver_graphics_tests ${agcExecutionTests})
|
||||
add_test(NAME agc_shader_bindless COMMAND agc_shader_memory_tests --bindless)
|
||||
set_tests_properties(agc_shader_bindless PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
|
||||
if(WIN32)
|
||||
set_tests_properties(agc_shader_bindless PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
foreach(agcTest IN ITEMS agc_driver_tests agc_shader_memory_tests agc_structurizer_tests agc_spirv_module_tests agc_dpp_write_tests agc_masked_select_tests agc_vop1_control_decode_tests agc_validate_program_tests agc_shader_disk_cache_tests agc_shader_device_profile_tests agc_shader_control_flow_tests agc_driver_flip_tests agc_driver_pm4_tests agc_driver_graphics_tests agc_driver_bda_device_tests ${agcExecutionTests})
|
||||
if(agcTest STREQUAL "agc_driver_constant_store_tests")
|
||||
continue()
|
||||
endif()
|
||||
string(REGEX REPLACE "_tests$" "" agcTestName "${agcTest}")
|
||||
set_target_properties(${agcTest} PROPERTIES EXCLUDE_FROM_ALL OFF)
|
||||
add_test(NAME ${agcTestName} COMMAND ${agcTest})
|
||||
set_tests_properties(${agcTestName} PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77)
|
||||
set_tests_properties(${agcTestName} PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
|
||||
if(WIN32)
|
||||
set_tests_properties(${agcTestName} PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
endforeach()
|
||||
set_tests_properties(agc_driver_flat_store PROPERTIES RUN_SERIAL TRUE)
|
||||
add_test(NAME agc_driver_pm4_failure COMMAND agc_driver_pm4_tests failure)
|
||||
set_tests_properties(agc_driver_pm4_failure PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77)
|
||||
if(WIN32)
|
||||
set_tests_properties(agc_driver_pm4_failure PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
|
||||
foreach(agcTest IN ITEMS agc_spirv_narrow_constant_store_tests agc_spirv_optimizer_tests agc_driver_constant_store_tests)
|
||||
if(NOT TARGET ${agcTest})
|
||||
continue()
|
||||
endif()
|
||||
set_target_properties(${agcTest} PROPERTIES EXCLUDE_FROM_ALL OFF)
|
||||
string(REGEX REPLACE "_tests$" "" agcTestBaseName "${agcTest}")
|
||||
foreach(agcSpirvMode IN ITEMS default full none)
|
||||
if(agcSpirvMode STREQUAL "default")
|
||||
set(agcTestName "${agcTestBaseName}")
|
||||
set(agcSpirvEnvironment "--unset=APS5_SPIRV_OPT")
|
||||
else()
|
||||
set(agcTestName "${agcTestBaseName}_${agcSpirvMode}")
|
||||
set(agcSpirvEnvironment "APS5_SPIRV_OPT=${agcSpirvMode}")
|
||||
endif()
|
||||
add_test(NAME ${agcTestName} COMMAND ${CMAKE_COMMAND} -E env
|
||||
${agcSpirvEnvironment} "$<TARGET_FILE:${agcTest}>")
|
||||
set_tests_properties(${agcTestName} PROPERTIES
|
||||
ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" TIMEOUT 60 SKIP_RETURN_CODE 77)
|
||||
if(agcTest STREQUAL "agc_driver_constant_store_tests")
|
||||
set_tests_properties(${agcTestName} PROPERTIES RUN_SERIAL TRUE)
|
||||
endif()
|
||||
if(WIN32)
|
||||
set_tests_properties(${agcTestName} PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
endforeach()
|
||||
endforeach()
|
||||
foreach(preparedMode IN ITEMS indirect uncached debug debug_failure)
|
||||
set(preparedTest "agc_driver_prepared_shaders_${preparedMode}")
|
||||
if(preparedMode STREQUAL "indirect")
|
||||
add_test(NAME ${preparedTest} COMMAND agc_driver_prepared_shaders_tests --indirect)
|
||||
elseif(preparedMode STREQUAL "debug_failure")
|
||||
add_test(NAME ${preparedTest} COMMAND agc_driver_prepared_shaders_tests --fail-before-registration)
|
||||
set_tests_properties(${preparedTest} PROPERTIES WILL_FAIL TRUE TIMEOUT 30)
|
||||
else()
|
||||
add_test(NAME ${preparedTest} COMMAND agc_driver_prepared_shaders_tests)
|
||||
endif()
|
||||
set_tests_properties(${preparedTest} PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
|
||||
if(preparedMode STREQUAL "uncached")
|
||||
set_property(TEST ${preparedTest} APPEND PROPERTY ENVIRONMENT "APS5_NO_DISPATCH_CACHE=1")
|
||||
elseif(preparedMode STREQUAL "debug" OR preparedMode STREQUAL "debug_failure")
|
||||
set_property(TEST ${preparedTest} APPEND PROPERTY ENVIRONMENT "APS5_PROFILE_DRAW=1" "APS5_NO_UNLOCKED_DEVICE=1")
|
||||
endif()
|
||||
if(WIN32)
|
||||
set_tests_properties(${preparedTest} PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
option(AGC_BUILD_VISUAL_TEST "Enable the standalone AGC SPIR-V visual test" OFF)
|
||||
|
|
@ -568,6 +728,8 @@ if(AGC_BUILD_VISUAL_TEST)
|
|||
Execution/src/PresentationScaler.cpp
|
||||
Execution/src/GuestMemory.cpp
|
||||
Execution/src/BdaFeatures.cpp
|
||||
Execution/src/SubgroupClock.cpp
|
||||
Execution/src/ShaderDeviceProfile.cpp
|
||||
Graphics/src/Draw.cpp
|
||||
Graphics/src/DepthSurface.cpp
|
||||
Graphics/src/ColorTargetLayout.cpp
|
||||
|
|
@ -769,6 +931,14 @@ if(BUILD_TESTING)
|
|||
set_tests_properties(scalar_saveexec PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
add_test_executable(scalar_swappc_tests ${LIBS_INCLUDE_DIR}/tests/ScalarSwappc.cpp)
|
||||
target_link_libraries(scalar_swappc_tests PRIVATE shader_recompiler_agc_driver libc)
|
||||
configure_windows_unwind(scalar_swappc_tests)
|
||||
add_test(NAME scalar_swappc COMMAND scalar_swappc_tests)
|
||||
if(WIN32)
|
||||
set_tests_properties(scalar_swappc PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
add_test_executable(scalar_bitcount64_tests ${LIBS_INCLUDE_DIR}/tests/ScalarBitCount64.cpp)
|
||||
target_link_libraries(scalar_bitcount64_tests PRIVATE shader_recompiler_agc_driver libc)
|
||||
configure_windows_unwind(scalar_bitcount64_tests)
|
||||
|
|
@ -785,6 +955,14 @@ if(BUILD_TESTING)
|
|||
set_tests_properties(scalar_scratch_load PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
add_test_executable(scalar_call_tests ${LIBS_INCLUDE_DIR}/tests/ScalarCall.cpp)
|
||||
target_link_libraries(scalar_call_tests PRIVATE shader_recompiler_agc_driver libc)
|
||||
configure_windows_unwind(scalar_call_tests)
|
||||
add_test(NAME scalar_call COMMAND scalar_call_tests)
|
||||
if(WIN32)
|
||||
set_tests_properties(scalar_call PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
add_test_executable(image_sample_gather_tests ${LIBS_INCLUDE_DIR}/tests/ImageSampleGather.cpp)
|
||||
target_link_libraries(image_sample_gather_tests PRIVATE shader_recompiler_agc_driver libc)
|
||||
configure_windows_unwind(image_sample_gather_tests)
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@ std::size_t DisplayBufferSize(const DisplayBuffer& buffer);
|
|||
std::vector<std::byte> DecodeDisplayBuffer(const DisplayBuffer& buffer, std::span<const std::byte> source);
|
||||
std::vector<std::byte> ReadDisplayBuffer(const DisplayBuffer& buffer);
|
||||
std::array<std::byte, 4> DisplayBufferClearPixel(const DisplayBuffer& buffer, Graphics::DccKeys keys);
|
||||
std::size_t DisplayBufferKeyBytes(const DisplayBuffer& buffer);
|
||||
Graphics::DccKeys DisplayBufferKeys(const DisplayBuffer& buffer);
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_DRIVER_HPP
|
||||
#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_DRIVER_HPP
|
||||
|
||||
#include <span>
|
||||
#include "SceTypes.hpp"
|
||||
#include "SceShaders.hpp"
|
||||
|
||||
|
|
@ -16,5 +17,8 @@ extern "C" void AgcDriverUnlockVulkanLoader_nid_postfix();
|
|||
extern "C" void AgcDriverShutdown_nid_postfix();
|
||||
extern "C" void AgcDriverSuspendPoint_nid_postfix();
|
||||
extern "C" void AgcDriverRegisterShader_nid_postfix(const Shader* shader);
|
||||
extern "C" void AgcDriverResolveShaderAbi_nid_postfix(const Shader* shader, std::span<const ShaderRegister> context, std::span<const ShaderRegister> primitive);
|
||||
extern "C" void AgcDriverResolveGraphicsStagesAbi_nid_postfix(std::span<const Shader* const> stages, std::span<const ShaderRegister> context, std::span<const ShaderRegister> primitive);
|
||||
extern "C" void AgcDriverResolveGraphicsAbi_nid_postfix(const Shader* vertex, const Shader* pixel, std::uint32_t primitiveType);
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -32,6 +32,15 @@ struct DrawDecode {
|
|||
std::vector<ShaderRecompiler::ProgramRole> roles;
|
||||
};
|
||||
|
||||
void DecodeGraphicsPrograms(DrawDecode& decoded, const QueueState& queue, const ShaderRegistry& registry, bool staticAbi, bool includeFragment);
|
||||
|
||||
struct PreparedGraphicsStage {
|
||||
std::shared_ptr<const ShaderSnapshot> snapshot;
|
||||
PreparedShaders::Entry entry;
|
||||
};
|
||||
|
||||
std::vector<PreparedGraphicsStage> PrepareGraphicsStages(const DrawDecode& decoded, const ShaderRecompiler::SpirvTarget& target);
|
||||
|
||||
struct DrawRecipeRecord {
|
||||
std::vector<std::weak_ptr<const DispatchVariant>> stages;
|
||||
std::shared_ptr<const DrawRecipe> recipe;
|
||||
|
|
|
|||
|
|
@ -56,6 +56,9 @@ public:
|
|||
void Present(const PresentationWindow& window, const DisplayBuffer* buffer, bool opaque, void (*gpuReady)(void*), void* context);
|
||||
void ReleaseWindow(void* window);
|
||||
void RegisterShader(const Shader* shader);
|
||||
void ResolveShaderAbi(const Shader* shader, std::span<const ShaderRegister> context, std::span<const ShaderRegister> primitive);
|
||||
void ResolveGraphicsStagesAbi(std::span<const Shader* const> stages, std::span<const ShaderRegister> context, std::span<const ShaderRegister> primitive);
|
||||
void ResolveGraphicsAbi(const Shader* vertex, const Shader* pixel, std::uint32_t primitiveType);
|
||||
|
||||
private:
|
||||
friend class SampledReadScope;
|
||||
|
|
@ -68,6 +71,7 @@ private:
|
|||
static bool& onWorkerThread();
|
||||
static void copyCommands(Submission& submission, const std::uint32_t* guest, std::size_t words);
|
||||
static bool copySegment(Submission& submission, const std::uint32_t* guest, std::size_t words, std::size_t& budget);
|
||||
static void readRegisterLists(Submission& submission);
|
||||
void waitForFlipRoom(const Submission& submission);
|
||||
void reserveOutputs(Submission& submission);
|
||||
void executeRewindTail(const Submission& stalled);
|
||||
|
|
@ -93,7 +97,7 @@ private:
|
|||
void lookupDispatch(std::uint64_t address, const Submission& submission, std::uint64_t key, bool noDispatchCache, bool traceCache, bool profile, std::span<const ShaderRecompiler::MemoryRegion> memory, DispatchPhaseTiming& phaseTiming, std::array<double, DriverPhaseCount>& phaseMs, std::shared_ptr<const ShaderRecompiler::RecompileResult>& compiledResult, std::shared_ptr<DispatchVariant>& keepVariant, std::vector<ShaderRecompiler::MemoryRegion>& captured, std::vector<std::uint32_t>& liveWords, bool& dataHit, bool& cached, bool& validated, std::shared_ptr<DispatchEntry>& missedEntry, bool& missedDiffering);
|
||||
void insertDispatch(std::uint64_t address, std::uint64_t key, bool noDispatchCache, bool profile, const std::shared_ptr<const ShaderSnapshot>& registeredShader, std::uint64_t forgetAtCapture, std::span<const ShaderRecompiler::MemoryRegion> memory, const std::shared_ptr<ShaderMemory>& shaderMemory, const std::vector<ShaderRecompiler::MemoryRegion>& captured, const std::shared_ptr<const ShaderRecompiler::ResourceCapture>& capture, const std::shared_ptr<const ShaderRecompiler::RecompileResult>& compiledResult, const std::shared_ptr<DispatchEntry>& missedEntry, bool missedDiffering, std::shared_ptr<DispatchVariant>& attachVariant, DispatchPhaseTiming& phaseTiming);
|
||||
void dispatch(QueueState& queue, std::span<const std::uint32_t> packet, const Submission& submission, std::uint64_t indirectArguments = 0);
|
||||
void verifyDataHit(const ShaderSnapshot& snapshot, std::size_t codeOffset, std::uint64_t deviceSerial, ShaderRecompiler::RecompileRequest request, std::span<const ShaderRecompiler::MemoryRegion> memory, std::uint64_t address, const DispatchVariant& variant, std::span<const std::uint32_t> liveWords, const ShaderRecompiler::RecompileResult& patched);
|
||||
void verifyDataHit(const ShaderSnapshot& snapshot, std::size_t codeOffset, ShaderRecompiler::RecompileRequest request, std::span<const ShaderRecompiler::MemoryRegion> memory, std::uint64_t address, const DispatchVariant& variant, std::span<const std::uint32_t> liveWords, const ShaderRecompiler::RecompileResult& patched);
|
||||
static bool drawEntries();
|
||||
static bool verifyDrawEntries();
|
||||
static bool registerKeyEnabled();
|
||||
|
|
@ -110,7 +114,7 @@ private:
|
|||
std::shared_ptr<DrawDecode> decodeDraw(const QueueState& queue, const Submission& submission);
|
||||
void resolveDrawDecode(const QueueState& queue, const Submission& submission, std::shared_ptr<const DrawDecode>& decode, bool registerKey, std::uint64_t drawKey, bool profile);
|
||||
void lookupDraw(const Submission& submission, const std::shared_ptr<VulkanDevice>& localDevice, const Graphics::State& graphics, const ShaderRecompiler::ShaderPixelStageInfo& pixel, const std::vector<DrawProgram>& programs, const std::vector<ShaderRecompiler::ProgramRole>& roles, const std::vector<std::optional<ShaderRecompiler::ShaderVertexStageInfo>>& vertexInfos, bool useDrawEntries, bool registerKey, bool profile, std::uint64_t& drawKey, std::shared_ptr<DrawEntry>& entry, std::vector<std::shared_ptr<DispatchVariant>>& matched, std::vector<std::vector<ShaderRecompiler::MemoryRegion>>& matchedRegions, bool& drawHit, bool& verifyHit, DrawPhaseTiming& phaseTiming, std::array<double, DrawDriverPhaseCount>& phaseMs);
|
||||
ShaderRecompiler::RecompileResult compileDrawStage(std::size_t i, std::uint32_t pushOffset, const QueueState& queue, const Submission& submission, const std::vector<DrawProgram>& programs, const Graphics::State& graphics, const ShaderRecompiler::ShaderPixelStageInfo& pixel, const std::vector<std::optional<ShaderRecompiler::ShaderVertexStageInfo>>& vertexInfos, std::vector<ShaderRecompiler::MemoryRegion>& memory, const std::vector<ShaderRecompiler::LinkedProgram>& linked, const Pm4::DrawParameters& drawParameters, const std::shared_ptr<VulkanDevice>& localDevice, ShaderMemory& shaderMemory, std::vector<StageCapture>& stageCaptures, std::vector<bool>& recompiled, bool drawHit, const std::vector<std::shared_ptr<DispatchVariant>>& matched, const std::vector<std::vector<ShaderRecompiler::MemoryRegion>>& matchedRegions, bool profile, std::uint64_t dumpTarget, std::uint64_t dumpSlot1, std::uint64_t& captures, DrawPhaseTiming& phaseTiming, std::array<double, DrawDriverPhaseCount>& phaseMs, std::string& rejected);
|
||||
ShaderRecompiler::RecompileResult materializeDrawStage(std::size_t i, std::uint32_t pushOffset, const QueueState& queue, const Submission& submission, const std::vector<DrawProgram>& programs, const Graphics::State& graphics, const ShaderRecompiler::ShaderPixelStageInfo& pixel, const std::vector<std::optional<ShaderRecompiler::ShaderVertexStageInfo>>& vertexInfos, std::vector<ShaderRecompiler::MemoryRegion>& memory, const std::vector<ShaderRecompiler::LinkedProgram>& linked, const Pm4::DrawParameters& drawParameters, const std::shared_ptr<VulkanDevice>& localDevice, ShaderMemory& shaderMemory, std::vector<StageCapture>& stageCaptures, std::vector<bool>& recompiled, bool drawHit, const std::vector<std::shared_ptr<DispatchVariant>>& matched, const std::vector<std::vector<ShaderRecompiler::MemoryRegion>>& matchedRegions, bool profile, std::uint64_t dumpTarget, std::uint64_t dumpSlot1, std::uint64_t& captures, DrawPhaseTiming& phaseTiming, std::array<double, DrawDriverPhaseCount>& phaseMs, std::string& rejected);
|
||||
void cacheDrawStages(bool useDrawEntries, bool drawHit, const Pm4::DrawParameters& drawParameters, const std::optional<Graphics::IndirectDrawPath>& indirectCpu, const std::vector<DrawProgram>& programs, const std::vector<StageCapture>& stageCaptures, const std::vector<std::optional<ShaderRecompiler::ShaderVertexStageInfo>>& vertexInfos, const std::vector<std::vector<Graphics::DecodeRead>>& decodeReads, bool verifyHit, const std::vector<std::shared_ptr<DispatchVariant>>& matched, std::vector<std::shared_ptr<DispatchVariant>>& fresh, std::uint64_t drawKey, bool registerKey, const std::shared_ptr<const DrawDecode>& decode, DrawPhaseTiming& phaseTiming);
|
||||
static bool drawPrecheck();
|
||||
std::optional<DrawVerdict> precheckDraw(const QueueState& queue, const Submission& submission, std::span<const std::uint32_t> packet, const Pm4::DrawParameters& drawParameters, std::string& rejected, bool& traceIndirect);
|
||||
|
|
@ -215,8 +219,8 @@ private:
|
|||
void dumpSampleCounters(std::uint64_t address);
|
||||
template <typename TWork>
|
||||
static void timed(double WorkerProfile::*bucket, TWork&& work);
|
||||
template <typename TWork>
|
||||
void tolerate(const char* kind, TWork&& work);
|
||||
FrameTiming* frameTiming();
|
||||
FrameTiming* includeTimingSubmission(const Submission& submission, bool firstSegment);
|
||||
void execute(const Submission& submission);
|
||||
void markCompleted(std::uint64_t serial);
|
||||
static const std::atomic<std::uint64_t>*& workerQueued();
|
||||
|
|
@ -229,6 +233,7 @@ private:
|
|||
|
||||
std::map<std::uint32_t, QueueWorker> workers;
|
||||
std::uint64_t frameSerial = 0;
|
||||
std::shared_ptr<FrameTiming> pendingFrameTiming;
|
||||
|
||||
std::atomic<std::uint64_t> flipsCounted{0};
|
||||
std::atomic<std::uint64_t> flipSerial{0};
|
||||
|
|
|
|||
|
|
@ -26,13 +26,20 @@ struct Submission {
|
|||
std::map<std::size_t, std::shared_ptr<IFlipRequest>> flips;
|
||||
std::map<std::size_t, std::shared_ptr<IRenderingWait>> renderingWaits;
|
||||
std::map<std::size_t, std::size_t> conditionalEnds;
|
||||
std::map<std::size_t, std::vector<std::uint32_t>> registerLists;
|
||||
bool suspend = false;
|
||||
bool waitFree = false;
|
||||
|
||||
std::uint64_t received = 0;
|
||||
std::vector<std::uint64_t> labelWrites;
|
||||
std::set<std::size_t> heldAtSubmit;
|
||||
std::chrono::steady_clock::time_point receivedAt{};
|
||||
std::chrono::steady_clock::time_point copiedAt{};
|
||||
std::chrono::steady_clock::time_point validatedAt{};
|
||||
std::chrono::steady_clock::time_point roomReadyAt{};
|
||||
std::chrono::steady_clock::time_point enqueuedAt{};
|
||||
std::chrono::steady_clock::time_point dequeuedAt{};
|
||||
std::chrono::steady_clock::time_point orderedAt{};
|
||||
const std::uint32_t* rewindTail = nullptr;
|
||||
std::size_t rewindWords = 0;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_DRIVER_SHADERREGISTRY_HPP
|
||||
|
||||
#include "prx/libSceAgcDriver/Execution/include/ShaderMemory.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/QueueState.hpp"
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
|
|
@ -14,16 +15,45 @@
|
|||
|
||||
namespace AgcDriver::DriverDetail {
|
||||
|
||||
struct HandleMemos {
|
||||
struct ShaderSnapshot;
|
||||
using ShaderRegistry = std::map<std::uint64_t, std::shared_ptr<const ShaderSnapshot>>;
|
||||
|
||||
struct PreparedShaderState {
|
||||
struct Entry {
|
||||
std::uint64_t key = 0;
|
||||
std::size_t codeOffset;
|
||||
std::shared_ptr<const ShaderRecompiler::SourceHandle> handle;
|
||||
std::shared_ptr<const std::string> failure;
|
||||
};
|
||||
struct Rectangle {
|
||||
std::uint64_t vertexId;
|
||||
std::uint64_t fragmentId;
|
||||
std::shared_ptr<const ShaderRecompiler::RectListShaders> shaders;
|
||||
};
|
||||
struct RectangleProgress {
|
||||
std::weak_ptr<const ShaderSnapshot> fragment;
|
||||
std::size_t vertexCount;
|
||||
std::size_t fragmentCount;
|
||||
};
|
||||
struct GraphicsAbi {
|
||||
std::vector<std::uint64_t> key;
|
||||
std::weak_ptr<const ShaderRegistry> registry;
|
||||
std::vector<std::weak_ptr<const ShaderSnapshot>> stages;
|
||||
};
|
||||
std::vector<Entry> entries;
|
||||
std::vector<std::vector<std::uint64_t>> registeredAbis;
|
||||
std::vector<GraphicsAbi> graphicsAbis;
|
||||
std::vector<Rectangle> rectangles;
|
||||
std::vector<RectangleProgress> rectangleProgress;
|
||||
std::vector<std::weak_ptr<const ShaderSnapshot>> fragments;
|
||||
bool rectangleRequested = false;
|
||||
};
|
||||
struct PreparedShaders : PreparedShaderState {
|
||||
std::mutex mutex;
|
||||
std::array<Entry, 8> entries;
|
||||
std::size_t next = 0;
|
||||
std::atomic<std::uint32_t> poisoned{0};
|
||||
};
|
||||
|
||||
struct RegisteredShaderState {
|
||||
Registers shader;
|
||||
Registers context;
|
||||
Registers userConfig;
|
||||
};
|
||||
|
||||
struct ShaderSnapshot {
|
||||
|
|
@ -32,17 +62,22 @@ struct ShaderSnapshot {
|
|||
std::uint8_t type;
|
||||
std::vector<std::uint32_t> code;
|
||||
std::vector<std::byte> header;
|
||||
std::unique_ptr<HandleMemos> handles = std::make_unique<HandleMemos>();
|
||||
std::shared_ptr<PreparedShaders> prepared = std::make_shared<PreparedShaders>();
|
||||
std::shared_ptr<const RegisteredShaderState> registeredState;
|
||||
};
|
||||
|
||||
using ShaderRegistry = std::map<std::uint64_t, std::shared_ptr<const ShaderSnapshot>>;
|
||||
|
||||
bool FailureMemo();
|
||||
std::shared_ptr<const ShaderSnapshot> ReadRawComputeShader(std::uint64_t address);
|
||||
|
||||
std::uint64_t NullPixelProgramAddress();
|
||||
void PublishRegisteredShader(std::shared_ptr<ShaderRegistry>& registry, const std::shared_ptr<const ShaderSnapshot>& snapshot);
|
||||
|
||||
std::shared_ptr<const ShaderRecompiler::SourceHandle> SourceHandleFor(const ShaderSnapshot& snapshot, std::size_t codeOffset, std::uint64_t deviceSerial, const ShaderRecompiler::RecompileRequest& request, bool bypass, const std::string** poisoned = nullptr);
|
||||
void ResolvePreparedGraphics(const ShaderSnapshot& front, const std::shared_ptr<const ShaderSnapshot>& fragment, std::uint32_t primitiveType, const ShaderRecompiler::SpirvTarget& target);
|
||||
|
||||
ShaderRecompiler::RectListShaders PreparedRectangle(const ShaderSnapshot& snapshot, std::uint64_t vertexId, std::uint64_t fragmentId);
|
||||
|
||||
std::shared_ptr<const ShaderRecompiler::SourceHandle> SourceHandleFor(const ShaderSnapshot& snapshot, std::size_t codeOffset, const ShaderRecompiler::RecompileRequest& request);
|
||||
ShaderRecompiler::PreparedShaderInvocation InvocationFor(const ShaderSnapshot& snapshot, std::size_t codeOffset, const ShaderRecompiler::RecompileRequest& request);
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,17 +5,15 @@
|
|||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <functional>
|
||||
#include <iomanip>
|
||||
#include <locale>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <sstream>
|
||||
#include <ostream>
|
||||
#include <stdexcept>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include "prx/libc/include/general/LogMacros.hpp"
|
||||
|
||||
namespace AgcDriver {
|
||||
|
||||
|
|
@ -30,23 +28,68 @@ public:
|
|||
std::uint64_t bytes = 0;
|
||||
};
|
||||
|
||||
explicit FrameTiming(std::uint64_t id) : id(id) {}
|
||||
explicit FrameTiming(std::uint64_t id, bool localMetrics = false) : localMetrics(localMetrics), id(id), workerStart(Clock::now()) {}
|
||||
|
||||
static FrameTiming* Preparation() {
|
||||
if (!APS5_ENABLE_TIMING_LOG) return nullptr;
|
||||
static FrameTiming timing(0);
|
||||
return &timing;
|
||||
}
|
||||
|
||||
static FrameTiming* Async() {
|
||||
if (!APS5_ENABLE_TIMING_LOG) return nullptr;
|
||||
static FrameTiming timing(0);
|
||||
return &timing;
|
||||
}
|
||||
|
||||
void CollectBackground() {
|
||||
const auto collect = [&](FrameTiming* source, auto& destination) {
|
||||
if (source == nullptr) return;
|
||||
std::scoped_lock lock(mutex, source->mutex);
|
||||
for (auto& [key, metric] : source->metrics) {
|
||||
if (metric.count == 0) continue;
|
||||
auto& target = destination[key];
|
||||
target.total += metric.total;
|
||||
target.maximum = std::max(target.maximum, metric.maximum);
|
||||
target.count += metric.count;
|
||||
target.bytes += metric.bytes;
|
||||
metric = {};
|
||||
}
|
||||
};
|
||||
collect(Preparation(), metrics);
|
||||
collect(Async(), asyncMetrics);
|
||||
}
|
||||
|
||||
Metric* Get(const char* scope, const char* stage) {
|
||||
std::lock_guard lock(mutex);
|
||||
std::unique_lock lock(mutex, std::defer_lock);
|
||||
if (!localMetrics) lock.lock();
|
||||
return &metrics[{scope, stage}];
|
||||
}
|
||||
|
||||
void Add(Metric* metric, Clock::duration elapsed, std::uint64_t bytes = 0) {
|
||||
std::lock_guard lock(mutex);
|
||||
std::unique_lock lock(mutex, std::defer_lock);
|
||||
if (!localMetrics) lock.lock();
|
||||
metric->total += elapsed;
|
||||
metric->maximum = std::max(metric->maximum, elapsed);
|
||||
++metric->count;
|
||||
metric->bytes += bytes;
|
||||
}
|
||||
|
||||
void NotePacket(std::uint64_t serial, std::size_t offset, std::uint32_t opcode, std::size_t words, Clock::duration elapsed) {
|
||||
std::unique_lock lock(mutex, std::defer_lock);
|
||||
if (!localMetrics) lock.lock();
|
||||
++packetCount;
|
||||
packetWords += words;
|
||||
if (elapsed > slowPacketTime) {
|
||||
slowPacketTime = elapsed;
|
||||
slowPacketSerial = serial;
|
||||
slowPacketOffset = offset;
|
||||
slowPacketOpcode = opcode;
|
||||
}
|
||||
}
|
||||
|
||||
void IncludeSubmission(std::uint64_t serial, Clock::time_point received, Clock::time_point enqueued, Clock::time_point dequeued, bool firstSegment) {
|
||||
if (serial == 0 || received > enqueued || enqueued > dequeued) throw std::runtime_error("Frame timing: invalid submission timestamps");
|
||||
if (serial == 0 || received == Clock::time_point{} || received > enqueued || enqueued > dequeued) throw std::runtime_error("Frame timing: invalid submission timestamps");
|
||||
if (firstSerial == 0) {
|
||||
firstSerial = serial;
|
||||
start = received;
|
||||
|
|
@ -66,6 +109,7 @@ public:
|
|||
flipOffset = offset;
|
||||
flipReceived = received;
|
||||
flipReached = reached;
|
||||
localMetrics = false;
|
||||
}
|
||||
|
||||
Clock::time_point FlipReached() {
|
||||
|
|
@ -107,39 +151,52 @@ public:
|
|||
gpuGapMs += gapMs;
|
||||
}
|
||||
|
||||
void Print(std::uint32_t outputHandle, std::int32_t buffer, std::int64_t argument, Clock::time_point finished, Clock::duration interval) {
|
||||
std::function<void(std::ostream&)> Capture(std::uint32_t outputHandle, std::int32_t buffer, std::int64_t argument, Clock::time_point finished, Clock::duration interval) {
|
||||
std::lock_guard lock(mutex);
|
||||
if (firstSerial == 0 || flipSerial == 0) throw std::runtime_error("Frame timing: incomplete submission lineage");
|
||||
std::ostringstream output;
|
||||
output.imbue(std::locale::classic());
|
||||
output << std::fixed << std::setprecision(3);
|
||||
output << "[FrameTiming] frame=" << id << " submissions=" << firstSerial << ':' << lastSerial;
|
||||
output << " output=" << outputHandle;
|
||||
output << " flip=" << flipSerial << ':' << flipOffset << " buffer=" << buffer << " argument=" << argument;
|
||||
output << " endpoint=flip_complete display_confirmed=0";
|
||||
output << " first_submit_to_flip_ms=" << milliseconds(finished - start);
|
||||
output << " flip_submit_to_complete_ms=" << milliseconds(finished - flipReceived);
|
||||
output << " first_submit_to_execute_ms=" << milliseconds(executionStart - start);
|
||||
output << " execute_to_flip_packet_ms=" << milliseconds(flipReached - executionStart);
|
||||
output << " flip_packet_to_complete_ms=" << milliseconds(finished - flipReached);
|
||||
auto accounted = Clock::duration::zero();
|
||||
for (const auto scope : {"Driver.Packet", "Driver.Suspend", "Driver.Worker", "Driver.Completion"}) {
|
||||
const auto it = metrics.find({scope, "total"});
|
||||
if (it != metrics.end()) accounted += it->second.total;
|
||||
}
|
||||
output << " worker_unattributed_ms=" << milliseconds(flipReached - executionStart - accounted);
|
||||
if (interval != Clock::duration::zero()) output << " flip_interval_ms=" << milliseconds(interval);
|
||||
output << " batches_at_flip=" << batchesAtFlip << " unsignaled_at_flip=" << unsignaledAtFlip << " batches_at_blit=" << batchesAtBlit << " batches_after_flip=" << batchesAfterFlip;
|
||||
output << " gpu_busy_ms=" << gpuBusyMs << " gpu_gap_ms=" << gpuGapMs;
|
||||
output << " last_submit_after_flip_ms=" << milliseconds(lastSubmitAfterFlip) << " blit_submit_after_flip_ms=" << milliseconds(blitSubmitAfterFlip);
|
||||
output << " metrics=inclusive(count,sum_ms,max_ms[,bytes])";
|
||||
for (const auto& [key, metric] : metrics) {
|
||||
if (metric.count == 0) continue;
|
||||
output << ' ' << key.first << '.' << key.second << "=(" << metric.count << ',' << milliseconds(metric.total) << ',' << milliseconds(metric.maximum);
|
||||
if (metric.bytes != 0) output << ',' << metric.bytes;
|
||||
output << ')';
|
||||
}
|
||||
APS5_LOG_TIMING("%s", output.str().c_str());
|
||||
return [asyncMetrics = asyncMetrics, metrics = metrics, id = id, firstSerial = firstSerial, lastSerial = lastSerial, flipSerial = flipSerial, flipOffset = flipOffset, workerStart = workerStart, start = start, executionStart = executionStart, flipReceived = flipReceived, flipReached = flipReached, batchesAtFlip = batchesAtFlip, unsignaledAtFlip = unsignaledAtFlip, batchesAtBlit = batchesAtBlit, batchesAfterFlip = batchesAfterFlip, gpuBusyMs = gpuBusyMs, gpuGapMs = gpuGapMs, lastSubmitAfterFlip = lastSubmitAfterFlip, blitSubmitAfterFlip = blitSubmitAfterFlip, packetCount = packetCount, packetWords = packetWords, slowPacketTime = slowPacketTime, slowPacketSerial = slowPacketSerial, slowPacketOffset = slowPacketOffset, slowPacketOpcode = slowPacketOpcode, outputHandle, buffer, argument, finished, interval](std::ostream& output) {
|
||||
output << std::fixed << std::setprecision(3);
|
||||
output << "[FrameTiming] frame=" << id << " submissions=" << firstSerial << ':' << lastSerial;
|
||||
output << " output=" << outputHandle;
|
||||
output << " flip=" << flipSerial << ':' << flipOffset << " buffer=" << buffer << " argument=" << argument;
|
||||
output << " endpoint=flip_complete display_confirmed=0 worker_queue=0 background_metrics=completed_since_previous_flip";
|
||||
output << " first_submit_at_ms=" << milliseconds(start.time_since_epoch());
|
||||
output << " first_execute_at_ms=" << milliseconds(executionStart.time_since_epoch());
|
||||
output << " flip_packet_at_ms=" << milliseconds(flipReached.time_since_epoch());
|
||||
output << " flip_complete_at_ms=" << milliseconds(finished.time_since_epoch());
|
||||
output << " first_submit_to_flip_ms=" << milliseconds(finished - start);
|
||||
output << " flip_submit_to_complete_ms=" << milliseconds(finished - flipReceived);
|
||||
output << " first_submit_to_execute_ms=" << milliseconds(executionStart - start);
|
||||
output << " execute_to_flip_packet_ms=" << milliseconds(flipReached - executionStart);
|
||||
output << " flip_packet_to_complete_ms=" << milliseconds(finished - flipReached);
|
||||
auto accounted = Clock::duration::zero();
|
||||
for (const auto scope : {"Driver.Packet", "Driver.Suspend", "Driver.WorkerWait", "Driver.Completion", "Driver.EndSubmission", "Driver.Rewind"}) {
|
||||
const auto it = metrics.find({scope, "total"});
|
||||
if (it != metrics.end()) accounted += it->second.total;
|
||||
}
|
||||
output << " worker_begin_at_ms=" << milliseconds(workerStart.time_since_epoch());
|
||||
output << " worker_span_ms=" << milliseconds(flipReached - workerStart);
|
||||
output << " worker_accounted_ms=" << milliseconds(accounted);
|
||||
output << " worker_unattributed_ms=" << milliseconds(flipReached - workerStart - accounted);
|
||||
if (interval != Clock::duration::zero()) output << " flip_interval_ms=" << milliseconds(interval);
|
||||
output << " batches_at_flip=" << batchesAtFlip << " unsignaled_at_flip=" << unsignaledAtFlip << " batches_at_blit=" << batchesAtBlit << " batches_after_flip=" << batchesAfterFlip;
|
||||
output << " gpu_busy_ms=" << gpuBusyMs << " gpu_gap_ms=" << gpuGapMs;
|
||||
output << " last_submit_after_flip_ms=" << milliseconds(lastSubmitAfterFlip) << " blit_submit_after_flip_ms=" << milliseconds(blitSubmitAfterFlip);
|
||||
output << " packet_count=" << packetCount << " packet_dwords=" << packetWords;
|
||||
output << " slow_packet_submission=" << slowPacketSerial << " slow_packet_offset=" << slowPacketOffset << " slow_packet_opcode=" << slowPacketOpcode << " slow_packet_ms=" << milliseconds(slowPacketTime);
|
||||
output << " metrics=inclusive(count,sum_ms,max_ms[,bytes])";
|
||||
const auto writeMetrics = [&](const auto& entries, const char* prefix) {
|
||||
for (const auto& [key, metric] : entries) {
|
||||
if (metric.count == 0) continue;
|
||||
output << ' ' << prefix << key.first << '.' << key.second << "=(" << metric.count << ',' << milliseconds(metric.total) << ',' << milliseconds(metric.maximum);
|
||||
if (metric.bytes != 0) output << ',' << metric.bytes;
|
||||
output << ')';
|
||||
}
|
||||
};
|
||||
writeMetrics(metrics, "");
|
||||
writeMetrics(asyncMetrics, "Async.");
|
||||
output << '\n';
|
||||
};
|
||||
}
|
||||
|
||||
private:
|
||||
|
|
@ -148,12 +205,21 @@ private:
|
|||
}
|
||||
|
||||
std::mutex mutex;
|
||||
bool localMetrics = false;
|
||||
std::map<std::pair<std::string_view, std::string_view>, Metric> metrics;
|
||||
std::map<std::pair<std::string_view, std::string_view>, Metric> asyncMetrics;
|
||||
std::uint64_t packetCount = 0;
|
||||
std::uint64_t packetWords = 0;
|
||||
Clock::duration slowPacketTime{};
|
||||
std::uint64_t slowPacketSerial = 0;
|
||||
std::size_t slowPacketOffset = 0;
|
||||
std::uint32_t slowPacketOpcode = 0;
|
||||
std::uint64_t id;
|
||||
std::uint64_t firstSerial = 0;
|
||||
std::uint64_t lastSerial = 0;
|
||||
std::uint64_t flipSerial = 0;
|
||||
std::size_t flipOffset = 0;
|
||||
Clock::time_point workerStart{};
|
||||
Clock::time_point start{};
|
||||
Clock::time_point executionStart{};
|
||||
Clock::time_point flipReceived{};
|
||||
|
|
@ -196,7 +262,7 @@ public:
|
|||
if (frame != nullptr) {
|
||||
total = frame->Get(scope, "total");
|
||||
}
|
||||
start = Clock::now();
|
||||
if (frame != nullptr) start = Clock::now();
|
||||
previous = start;
|
||||
}
|
||||
|
||||
|
|
@ -204,10 +270,20 @@ public:
|
|||
PerformanceTimer& operator=(const PerformanceTimer&) = delete;
|
||||
|
||||
~PerformanceTimer() {
|
||||
Finish();
|
||||
}
|
||||
|
||||
void Finish() {
|
||||
if (frame == nullptr) return;
|
||||
const auto end = Clock::now();
|
||||
if (tail != nullptr) frame->Add(tail, end - previous);
|
||||
frame->Add(total, end - start);
|
||||
frame = nullptr;
|
||||
}
|
||||
|
||||
void FinishPacket(std::uint64_t serial, std::size_t offset, std::uint32_t opcode, std::size_t words) {
|
||||
if (frame != nullptr) frame->NotePacket(serial, offset, opcode, words, Clock::now() - start);
|
||||
Finish();
|
||||
}
|
||||
|
||||
void Mark(const char* stage, std::uint64_t bytes = 0) {
|
||||
|
|
@ -225,8 +301,8 @@ private:
|
|||
const char* scope;
|
||||
FrameTiming::Metric* total = nullptr;
|
||||
FrameTiming::Metric* tail = nullptr;
|
||||
Clock::time_point start;
|
||||
Clock::time_point previous;
|
||||
Clock::time_point start{};
|
||||
Clock::time_point previous{};
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,9 @@
|
|||
|
||||
namespace AgcDriver::Pm4 {
|
||||
|
||||
constexpr std::size_t GdsBytes = 0x10000;
|
||||
std::uint64_t GdsAddress();
|
||||
|
||||
struct DrawParameters {
|
||||
std::uint64_t indexAddress;
|
||||
std::uint32_t indexCount;
|
||||
|
|
@ -83,6 +86,9 @@ inline std::size_t PacketWords(std::uint32_t header) { return FillerPacket(heade
|
|||
std::string_view UnsupportedReason(std::uint32_t header);
|
||||
void Validate(std::span<const std::uint32_t> packet, std::uint32_t queue);
|
||||
void Execute(std::span<const std::uint32_t> packet, QueueState& queue);
|
||||
inline bool IndirectRegisterOpcode(std::uint32_t opcode) { return opcode == 0x63 || opcode == 0x64 || opcode == 0x9f; }
|
||||
std::vector<std::uint32_t> ReadIndirectRegisters(std::span<const std::uint32_t> packet);
|
||||
void ExecuteIndirectRegisters(std::span<const std::uint32_t> packet, std::span<const std::uint32_t> pairs, QueueState& queue);
|
||||
bool AccessesMemory(std::uint32_t header);
|
||||
// Whether an ACQUIRE_MEM packet asks only for GPU cache actions (no CPU-visible memory
|
||||
// synchronization): such a packet needs a pipeline barrier, not a device drain.
|
||||
|
|
|
|||
|
|
@ -24,12 +24,17 @@ public:
|
|||
}
|
||||
|
||||
~ProfileOutput() {
|
||||
Stop();
|
||||
}
|
||||
|
||||
void Stop() {
|
||||
std::lock_guard stopLock(stopMutex);
|
||||
{
|
||||
std::lock_guard lock(mutex);
|
||||
stopping = true;
|
||||
}
|
||||
changed.notify_one();
|
||||
worker.join();
|
||||
if (worker.joinable()) worker.join();
|
||||
}
|
||||
|
||||
bool Write(std::string text) {
|
||||
|
|
@ -117,6 +122,7 @@ private:
|
|||
|
||||
std::function<void(std::string_view)> writer;
|
||||
const std::size_t capacity;
|
||||
std::mutex stopMutex;
|
||||
std::mutex mutex;
|
||||
std::condition_variable changed;
|
||||
std::condition_variable idle;
|
||||
|
|
|
|||
|
|
@ -148,15 +148,18 @@ inline Registers InitialContextRegisters() {
|
|||
{0xc, 0}, {0xd, 0x40004000}, {0x81, 0x80000000}, {0x82, 0x40004000},
|
||||
{0x90, 0x80000000}, {0x91, 0x40004000},
|
||||
{0x105, 0}, {0x106, 0}, {0x107, 0}, {0x108, 0},
|
||||
{0x1b1, 0}, {0x1b6, 0}, {0x1c3, 0}, {0x1c4, 0}, {0x1c5, 0},
|
||||
{0x1b1, 0}, {0x1b3, 0}, {0x1b4, 0}, {0x1b6, 0}, {0x1c3, 0}, {0x1c4, 0}, {0x1c5, 0},
|
||||
{0x1ff, 0}, {0x292, 2}, {0x293, 0}, {0x29b, 0},
|
||||
{0x2ce, 0}, {0x2d3, 0}, {0x2d5, 0}, {0x2d6, 0}, {0x2db, 0},
|
||||
{0x2dc, 0xaa00}, {0x2e4, 0}, {0x2f8, 0}, {0x2f9, 0x2d},
|
||||
{0x2dc, 0xaa00}, {0x2df, 0}, {0x2e4, 0}, {0x2f8, 0}, {0x2f9, 0x2d},
|
||||
{0x30e, 0xffffffff}, {0x30f, 0xffffffff}, {0x313, 0x6000},
|
||||
{0x318, 0}, {0x31b, 0}, {0x31c, 0}, {0x31d, 0},
|
||||
{0x390, 0}, {0x3b0, 0}, {0x3b8, 0}
|
||||
};
|
||||
for (std::uint32_t i = 0; i < 8; ++i) result.emplace(0x1e0 + i, 0x20010001);
|
||||
for (std::uint32_t i = 0; i < 8; ++i) {
|
||||
result.emplace(0x1e0 + i, 0x20010001);
|
||||
result.emplace(0x31c + 0xfu * i, 0);
|
||||
}
|
||||
for (std::uint32_t i = 0; i < 16; ++i) {
|
||||
result.emplace(0x94 + 2 * i, 0x80000000);
|
||||
result.emplace(0x95 + 2 * i, 0x40004000);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_SHADERDEVICEPROFILE_HPP
|
||||
#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_SHADERDEVICEPROFILE_HPP
|
||||
|
||||
#include "Recompiler.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Context.hpp"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace AgcDriver {
|
||||
|
||||
class ShaderDeviceProfile {
|
||||
public:
|
||||
ShaderDeviceProfile(const ShaderRecompiler::SpirvTarget& target, const VkDeviceCreateInfo& deviceInfo, const VkPhysicalDeviceLimits& limits);
|
||||
ShaderDeviceProfile(const ShaderDeviceProfile&) = delete;
|
||||
ShaderDeviceProfile& operator=(const ShaderDeviceProfile&) = delete;
|
||||
ShaderRecompiler::SpirvTarget Target() const { return target; }
|
||||
const VkPhysicalDeviceLimits& Limits() const { return limits; }
|
||||
bool NullDescriptors() const { return nullDescriptors; }
|
||||
|
||||
private:
|
||||
ShaderRecompiler::SpirvTarget target;
|
||||
const VkPhysicalDeviceLimits limits;
|
||||
std::vector<std::uint32_t> capabilities;
|
||||
std::vector<std::string> extensions;
|
||||
std::vector<std::string_view> extensionViews;
|
||||
bool nullDescriptors = false;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
namespace ShaderRecompiler {
|
||||
struct SourceHandle;
|
||||
class PreparedShaderInvocation;
|
||||
}
|
||||
|
||||
namespace AgcDriver {
|
||||
|
|
@ -61,6 +62,7 @@ public:
|
|||
// (the driver's memoized ShaderRecompiler::ResolveSource result) the capture skips the source
|
||||
// resolution.
|
||||
std::shared_ptr<const ShaderRecompiler::ResourceCapture> Capture(const ShaderRecompiler::RecompileRequest& request, const ShaderRecompiler::SourceHandle* handle = nullptr);
|
||||
std::shared_ptr<const ShaderRecompiler::ResourceCapture> Capture(const ShaderRecompiler::PreparedShaderInvocation& invocation);
|
||||
[[nodiscard]] std::vector<ShaderRecompiler::MemoryRegion> Regions() const;
|
||||
// The page regions read since the previous call (or construction), a word read again
|
||||
// included, the initial regions excluded: one stage's own reads on the draw path's shared
|
||||
|
|
@ -70,6 +72,7 @@ public:
|
|||
static void CountHandleMemo(bool hit);
|
||||
|
||||
private:
|
||||
std::shared_ptr<const ShaderRecompiler::ResourceCapture> capture(const ShaderRecompiler::RecompileRequest& request, const ShaderRecompiler::SourceHandle* handle, const ShaderRecompiler::PreparedShaderInvocation* invocation);
|
||||
static constexpr std::size_t PageBytes = 4096;
|
||||
static constexpr std::size_t PageWords = PageBytes / sizeof(std::uint32_t);
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_SHADERPREPARATION_HPP
|
||||
#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_SHADERPREPARATION_HPP
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace AgcDriver::DriverDetail {
|
||||
|
||||
struct ShaderSnapshot;
|
||||
struct PreparedShaderState;
|
||||
|
||||
class ShaderPreparationTransaction {
|
||||
public:
|
||||
ShaderPreparationTransaction();
|
||||
~ShaderPreparationTransaction();
|
||||
ShaderPreparationTransaction(const ShaderPreparationTransaction&) = delete;
|
||||
ShaderPreparationTransaction& operator=(const ShaderPreparationTransaction&) = delete;
|
||||
PreparedShaderState& Edit(const ShaderSnapshot& snapshot);
|
||||
const PreparedShaderState& Read(const ShaderSnapshot& snapshot) const;
|
||||
void Commit();
|
||||
|
||||
private:
|
||||
struct State;
|
||||
std::unique_ptr<State> state;
|
||||
State* root = nullptr;
|
||||
bool committed = false;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_SHADERPREPARATIONSCOPE_HPP
|
||||
#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_SHADERPREPARATIONSCOPE_HPP
|
||||
|
||||
namespace AgcDriver::DriverDetail {
|
||||
class ShaderPreparationTransaction;
|
||||
}
|
||||
|
||||
extern "C" AgcDriver::DriverDetail::ShaderPreparationTransaction* AgcDriverBeginShaderPreparation_nid_postfix();
|
||||
extern "C" void AgcDriverCommitShaderPreparation_nid_postfix(AgcDriver::DriverDetail::ShaderPreparationTransaction* transaction);
|
||||
extern "C" void AgcDriverEndShaderPreparation_nid_postfix(AgcDriver::DriverDetail::ShaderPreparationTransaction* transaction) noexcept;
|
||||
|
||||
namespace AgcDriver {
|
||||
|
||||
class ShaderPreparationScope {
|
||||
public:
|
||||
ShaderPreparationScope() : transaction(AgcDriverBeginShaderPreparation_nid_postfix()) {}
|
||||
~ShaderPreparationScope() { AgcDriverEndShaderPreparation_nid_postfix(transaction); }
|
||||
ShaderPreparationScope(const ShaderPreparationScope&) = delete;
|
||||
ShaderPreparationScope& operator=(const ShaderPreparationScope&) = delete;
|
||||
void Commit() { AgcDriverCommitShaderPreparation_nid_postfix(transaction); }
|
||||
|
||||
private:
|
||||
DriverDetail::ShaderPreparationTransaction* transaction;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_SUBGROUPCLOCK_HPP
|
||||
#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_SUBGROUPCLOCK_HPP
|
||||
|
||||
#include <vulkan/vulkan.h>
|
||||
#include <string_view>
|
||||
|
||||
namespace AgcDriver {
|
||||
|
||||
bool NarrowSubgroupClock(VkDriverId driver, std::string_view deviceName);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -161,6 +161,7 @@ public:
|
|||
bool Presentable() const;
|
||||
bool PrimitiveListRestart() const;
|
||||
bool SamplerFilterMinmax() const;
|
||||
bool ConservativeRasterization() const;
|
||||
// A presentation is a few steps so the presenter holds GuestMemory::GpuMutex only while it
|
||||
// touches the queue. Presentations are slots (FlipInFlight() + 1, each with its own command
|
||||
// buffer, fence, kept resident image and dump buffer): RetirePresents(keep) (no mutex) retires
|
||||
|
|
@ -299,6 +300,7 @@ private:
|
|||
// The device's Graphics::Context: a copy of the one built at setup (its instance functions
|
||||
// resolved then, its function table filled then), or with APS5_NO_CONTEXT_CACHE=1 built anew.
|
||||
Graphics::Context graphicsContext() const;
|
||||
ShaderRecompiler::SpirvTarget buildTarget() const;
|
||||
Graphics::Context buildContext() const;
|
||||
// Body of Dispatch and DispatchIndirect: `arguments` 0 dispatches x, y, z groups.
|
||||
IndirectOutcome dispatch(const ShaderRecompiler::RecompileResult& shader, std::uint32_t x, std::uint32_t y, std::uint32_t z, std::uint64_t arguments, std::span<const Graphics::GuestMemorySnapshot> snapshots, std::uint64_t programAddress, std::shared_ptr<PreparedDispatch> prepared, std::shared_ptr<const Recipe>* recipe);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,85 @@
|
|||
#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_WRITEWATCHCOVERAGE_HPP
|
||||
#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_WRITEWATCHCOVERAGE_HPP
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include <limits>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace AgcDriver::GuestMemory {
|
||||
|
||||
class WriteWatchCoverage {
|
||||
public:
|
||||
void Initialize(std::uint64_t address, std::size_t bytes) {
|
||||
base = address;
|
||||
size = bytes;
|
||||
excluded.assign(bytes / BlockBytes + (bytes % BlockBytes != 0), 0);
|
||||
excludedGenerations.clear();
|
||||
}
|
||||
|
||||
bool Covers(std::uint64_t address, std::size_t bytes) const {
|
||||
if (bytes == 0 || bytes > size || address < base || address - base > size - bytes) return false;
|
||||
const auto first = (address - base) / BlockBytes;
|
||||
const auto last = (address - base + bytes - 1) / BlockBytes;
|
||||
return std::none_of(excluded.begin() + first, excluded.begin() + last + 1, [](auto generation) { return generation != 0; });
|
||||
}
|
||||
|
||||
bool Exclude(std::uint64_t address, std::size_t bytes, std::uint64_t generation = 1) {
|
||||
if (bytes == 0 || size == 0) return false;
|
||||
if (address < base) {
|
||||
if (base - address >= bytes) return false;
|
||||
bytes -= base - address;
|
||||
address = base;
|
||||
}
|
||||
const auto offset = address - base;
|
||||
if (offset >= size) return false;
|
||||
bytes = static_cast<std::size_t>(std::min<std::uint64_t>(bytes, size - offset));
|
||||
const auto first = offset / BlockBytes;
|
||||
const auto last = (offset + bytes - 1) / BlockBytes;
|
||||
bool changed = false;
|
||||
for (auto block = first; block <= last; ++block) {
|
||||
changed = changed || excluded[block] == 0;
|
||||
auto& stamp = excludedGenerations[block];
|
||||
stamp = std::max(stamp, std::max<std::uint64_t>(generation, 1));
|
||||
excluded[block] = 1;
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
bool Restore(std::uint64_t address, std::size_t bytes, std::uint64_t generation = std::numeric_limits<std::uint64_t>::max()) {
|
||||
if (bytes == 0 || size == 0) return false;
|
||||
if (address < base) {
|
||||
if (base - address >= bytes) return false;
|
||||
bytes -= base - address;
|
||||
address = base;
|
||||
}
|
||||
const auto offset = address - base;
|
||||
if (offset >= size) return false;
|
||||
bytes = static_cast<std::size_t>(std::min<std::uint64_t>(bytes, size - offset));
|
||||
const auto first = offset / BlockBytes + (offset % BlockBytes != 0);
|
||||
const auto stop = offset + bytes;
|
||||
const auto end = stop / BlockBytes + (stop == size && stop % BlockBytes != 0);
|
||||
bool changed = false;
|
||||
for (auto block = first; block < end; ++block) {
|
||||
if (excluded[block] != 0 && excludedGenerations.at(block) < generation) {
|
||||
changed = true;
|
||||
excluded[block] = 0;
|
||||
excludedGenerations.erase(block);
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr std::size_t BlockBytes = 65536;
|
||||
std::uint64_t base = 0;
|
||||
std::size_t size = 0;
|
||||
std::vector<std::uint8_t> excluded;
|
||||
std::unordered_map<std::uint64_t, std::uint64_t> excludedGenerations;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -3,6 +3,7 @@
|
|||
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/PerformanceTimer.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/DccMetadata.hpp"
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <cstdio>
|
||||
|
|
@ -10,6 +11,7 @@
|
|||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
namespace AgcDriver {
|
||||
namespace {
|
||||
|
|
@ -50,6 +52,40 @@ void decodeTexel(const std::byte* texel, std::byte* pixel, bool rgba, bool tenBi
|
|||
pixel[3] = texel[3];
|
||||
}
|
||||
|
||||
constexpr std::uint32_t MetaBlockEdge = 512;
|
||||
constexpr std::size_t MetaBlockKeys = 4096;
|
||||
constexpr std::uint32_t KeyEdge = 8;
|
||||
|
||||
std::array<std::uint32_t, 2> displayableKeyPixel(const DisplayBuffer& buffer, std::size_t key) {
|
||||
const auto pitch = (buffer.width + MetaBlockEdge - 1u) / MetaBlockEdge;
|
||||
const auto block = static_cast<std::uint32_t>(key / MetaBlockKeys);
|
||||
const auto offset = static_cast<std::uint32_t>(key % MetaBlockKeys);
|
||||
std::uint32_t x = 0;
|
||||
std::uint32_t y = 0;
|
||||
for (std::uint32_t bit = 0; bit < 6; ++bit) {
|
||||
x |= ((offset >> (2u * bit)) & 1u) << bit;
|
||||
y |= ((offset >> (2u * bit + 1u)) & 1u) << bit;
|
||||
}
|
||||
return {block % pitch * MetaBlockEdge + x * KeyEdge, block / pitch * MetaBlockEdge + y * KeyEdge};
|
||||
}
|
||||
|
||||
void traceDisplayKeys(const DisplayBuffer& buffer, const std::uint8_t* keys, std::size_t count) {
|
||||
static const bool trace = std::getenv("APS5_TRACE_DCC_KEYS") != nullptr;
|
||||
if (!trace) return;
|
||||
constexpr std::size_t MaxRuns = 256;
|
||||
std::string runs;
|
||||
std::size_t begin = 0;
|
||||
for (std::size_t run = 0; begin < count && run < MaxRuns; ++run) {
|
||||
const auto end = static_cast<std::size_t>(std::find_if(keys + begin, keys + count, [&](std::uint8_t key) { return key != keys[begin]; }) - keys);
|
||||
char text[32];
|
||||
std::snprintf(text, sizeof(text), "%s%02x x%zu", run == 0 ? "" : ", ", keys[begin], end - begin);
|
||||
runs += text;
|
||||
begin = end;
|
||||
}
|
||||
if (begin < count) runs += ", ...";
|
||||
std::fprintf(stderr, "[dcc-keys] display keys 0x%llx+0x%zx: %s\n", static_cast<unsigned long long>(buffer.dccAddress), count, runs.c_str());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
std::size_t DisplayBufferSize(const DisplayBuffer& buffer) {
|
||||
|
|
@ -117,6 +153,35 @@ std::array<std::byte, 4> DisplayBufferClearPixel(const DisplayBuffer& buffer, Gr
|
|||
return pixel;
|
||||
}
|
||||
|
||||
std::size_t DisplayBufferKeyBytes(const DisplayBuffer& buffer) {
|
||||
const auto bytes = DisplayBufferSize(buffer);
|
||||
require(buffer.tilingMode == 0, "VideoOut: a linear display buffer has no DCC keys");
|
||||
return Graphics::DccKeyCount(Graphics::TextureTileMode::kR64KBX, 4, buffer.width, buffer.height, bytes);
|
||||
}
|
||||
|
||||
Graphics::DccKeys DisplayBufferKeys(const DisplayBuffer& buffer) {
|
||||
const auto count = DisplayBufferKeyBytes(buffer);
|
||||
require(buffer.dccAddress != 0, "VideoOut: a display buffer without DCC metadata has no keys");
|
||||
const auto keys = Graphics::CurrentDccKeys(buffer.dccAddress, DisplayBufferSize(buffer), count);
|
||||
if (keys == Graphics::DccKeys::Uncompressed || Graphics::IsDccClear(keys)) return keys;
|
||||
char differing[128] = "";
|
||||
if (keys == Graphics::DccKeys::Mixed && GuestMemory::Accessible(reinterpret_cast<const void*>(buffer.dccAddress), count)) {
|
||||
const auto* bytes = reinterpret_cast<const std::uint8_t*>(buffer.dccAddress);
|
||||
traceDisplayKeys(buffer, bytes, count);
|
||||
const auto* other = std::find_if(bytes + 1, bytes + count, [&](std::uint8_t key) { return key != bytes[0]; });
|
||||
if (other == bytes + count) {
|
||||
std::snprintf(differing, sizeof(differing), ": every key is 0x%02x", bytes[0]);
|
||||
} else {
|
||||
const auto key = static_cast<std::size_t>(other - bytes);
|
||||
const auto [x, y] = displayableKeyPixel(buffer, key);
|
||||
std::snprintf(differing, sizeof(differing), ": key %zu (pixels %u..%u x %u..%u in the displayable order) is 0x%02x, key 0 is 0x%02x", key, x, x + KeyEdge - 1u, y, y + KeyEdge - 1u, *other, bytes[0]);
|
||||
}
|
||||
}
|
||||
char message[384];
|
||||
std::snprintf(message, sizeof(message), "VideoOut: display buffer 0x%llx reads %s DCC keys at 0x%llx over its 0x%zx key bytes%s: presenting DCC metadata that is not uniformly uncompressed or fast-cleared is not implemented", static_cast<unsigned long long>(buffer.address), Graphics::DccKeysName(keys), static_cast<unsigned long long>(buffer.dccAddress), count, differing);
|
||||
throw std::runtime_error(message);
|
||||
}
|
||||
|
||||
std::vector<std::byte> ReadDisplayBuffer(const DisplayBuffer& buffer) {
|
||||
const auto size = DisplayBufferSize(buffer);
|
||||
static const bool traceGpu = std::getenv("APS5_TRACE_GPU") != nullptr;
|
||||
|
|
|
|||
|
|
@ -1,22 +1,23 @@
|
|||
#include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp"
|
||||
#include "Optimization/ResourceProgram.hpp"
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
namespace AgcDriver::DriverDetail {
|
||||
|
||||
void Driver::verifyDataHit(const ShaderSnapshot& snapshot, std::size_t codeOffset, std::uint64_t deviceSerial, ShaderRecompiler::RecompileRequest request, std::span<const ShaderRecompiler::MemoryRegion> memory, std::uint64_t address, const DispatchVariant& variant, std::span<const std::uint32_t> liveWords, const ShaderRecompiler::RecompileResult& patched) {
|
||||
void Driver::verifyDataHit(const ShaderSnapshot& snapshot, std::size_t codeOffset, ShaderRecompiler::RecompileRequest request, std::span<const ShaderRecompiler::MemoryRegion> memory, std::uint64_t address, const DispatchVariant& variant, std::span<const std::uint32_t> liveWords, const ShaderRecompiler::RecompileResult& patched) {
|
||||
const auto fail = [&](const char* what, std::size_t position, std::size_t slot) {
|
||||
std::fprintf(stderr, "[dispatch-cache] APS5_VERIFY_DATA_HITS: %s disagrees (program 0x%llx, position %zu, slot %zu)\n", what, static_cast<unsigned long long>(address), position, slot);
|
||||
std::fflush(stderr);
|
||||
std::abort();
|
||||
};
|
||||
auto verifyMemory = std::make_shared<ShaderMemory>(memory, &queryPendingWrite, &observePendingWrite, hookWaitCounter());
|
||||
const auto handle = SourceHandleFor(snapshot, codeOffset, deviceSerial, request, false);
|
||||
const auto capture = verifyMemory->Capture(request, handle.get());
|
||||
const auto invocation = InvocationFor(snapshot, codeOffset, request);
|
||||
const auto capture = verifyMemory->Capture(invocation);
|
||||
const auto regions = verifyMemory->Regions();
|
||||
request.context.memory = regions;
|
||||
const auto result = ShaderRecompiler::Recompile(request, *capture);
|
||||
const auto result = invocation.Materialize(*capture);
|
||||
std::size_t position = 0, run = 0;
|
||||
for (const auto& region : regions) {
|
||||
const bool registered = std::any_of(memory.begin(), memory.end(), [&](const auto& known) { return region.guestAddress >= known.guestAddress && region.guestAddress < known.guestAddress + known.bytes.size(); });
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
#include "prx/libSceAgcDriver/Execution/include/ProfileOutput.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/PerformanceTimer.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/Driver/Diagnostics.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp"
|
||||
|
|
@ -11,6 +12,7 @@
|
|||
namespace AgcDriver::DriverDetail {
|
||||
|
||||
void Driver::dispatch(QueueState& queue, std::span<const std::uint32_t> packet, const Submission& submission, std::uint64_t indirectArguments) {
|
||||
PerformanceTimer timing("Driver.Dispatch");
|
||||
const auto address = (static_cast<std::uint64_t>(readRegister(queue.shader, 0x20c)) << 8u) | (static_cast<std::uint64_t>(readRegister(queue.shader, 0x20d) & 0xffu) << 40u);
|
||||
auto it = submission.shaders->upper_bound(address);
|
||||
std::shared_ptr<const ShaderSnapshot> registeredShader;
|
||||
|
|
@ -26,7 +28,7 @@ void Driver::dispatch(QueueState& queue, std::span<const std::uint32_t> packet,
|
|||
for (std::uint32_t i = 0; i < userCount; ++i) {
|
||||
userData.push_back(readUserData(queue.shader, 0x240 + i));
|
||||
}
|
||||
auto compute = Graphics::DecodeComputeStageInfo(queue.shader);
|
||||
auto compute = Graphics::DecodeComputeStageInfo(queue.shader, snapshot.header);
|
||||
std::vector<ShaderRecompiler::MemoryRegion> memory{{snapshot.codeAddress, std::as_bytes(std::span(snapshot.code))}};
|
||||
if (!snapshot.header.empty()) memory.push_back({snapshot.headerAddress, snapshot.header});
|
||||
|
||||
|
|
@ -98,13 +100,11 @@ void Driver::dispatch(QueueState& queue, std::span<const std::uint32_t> packet,
|
|||
if (probeThis) std::fprintf(stderr, "[gpu] probing dispatch %llu of 0x%llx\n", static_cast<unsigned long long>(probeDispatch.second), static_cast<unsigned long long>(address));
|
||||
}
|
||||
|
||||
if (FailureMemo() && snapshot.handles->poisoned.load(std::memory_order_relaxed) != 0) {
|
||||
const std::string* poisoned = nullptr;
|
||||
if (SourceHandleFor(snapshot, codeOffset, localDevice->Serial(), request, probeThis, &poisoned) == nullptr && poisoned != nullptr) {
|
||||
pendingDispatchPhases().outcome = DispatchOutcome::SkippedMemo;
|
||||
return;
|
||||
}
|
||||
}
|
||||
struct ProbeScope {
|
||||
bool active;
|
||||
explicit ProbeScope(bool active) : active(active) { if (active) ShaderRecompiler::SetDebugProbeActive(true); }
|
||||
~ProbeScope() { if (active) ShaderRecompiler::SetDebugProbeActive(false); }
|
||||
} probeScope{probeThis};
|
||||
const bool noDispatchCache = noDispatchCacheEnv || probeThis;
|
||||
std::uint64_t key = 0xcbf29ce484222325ull;
|
||||
const auto mix = [&](std::uint64_t value) {
|
||||
|
|
@ -179,10 +179,13 @@ void Driver::dispatch(QueueState& queue, std::span<const std::uint32_t> packet,
|
|||
previous.key = key;
|
||||
}
|
||||
|
||||
if (!stampValidate()) mix(reinterpret_cast<std::uintptr_t>(registeredShader.get()));
|
||||
mix(reinterpret_cast<std::uintptr_t>(registeredShader.get()));
|
||||
phaseTiming.Phase(PhaseKey);
|
||||
timing.Mark("decode_key_device_setup");
|
||||
lookupDispatch(address, submission, key, noDispatchCache, traceCache, profile, memory, phaseTiming, phaseMs, compiledResult, keepVariant, captured, liveWords, dataHit, cached, validated, missedEntry, missedDiffering);
|
||||
timing.Mark("lookup_cache");
|
||||
if (cached) {
|
||||
require(keepVariant != nullptr && keepVariant->shader == registeredShader, "dispatch cache belongs to another registered shader");
|
||||
captureMs += phaseTiming.Elapsed();
|
||||
} else {
|
||||
shaderMemory = std::make_shared<ShaderMemory>(memory, &queryPendingWrite, &observePendingWrite, hookWaitCounter());
|
||||
|
|
@ -190,18 +193,14 @@ void Driver::dispatch(QueueState& queue, std::span<const std::uint32_t> packet,
|
|||
|
||||
static const bool dumpShaders = std::getenv("APS5_DUMP_SHADERS") != nullptr;
|
||||
try {
|
||||
const auto invocation = InvocationFor(snapshot, codeOffset, request);
|
||||
timing.Mark("prepared_invocation");
|
||||
|
||||
struct ProbeScope {
|
||||
bool active;
|
||||
explicit ProbeScope(bool active) : active(active) { if (active) ShaderRecompiler::SetDebugProbeActive(true); }
|
||||
~ProbeScope() { if (active) ShaderRecompiler::SetDebugProbeActive(false); }
|
||||
} probeScope{probeThis};
|
||||
const auto waitedBefore = traceCapSync() ? Graphics::Recorder::ThreadWaitedMs() : 0.0;
|
||||
forgetAtCapture = GuestMemory::ForgetSerial();
|
||||
const auto handle = SourceHandleFor(snapshot, codeOffset, localDevice->Serial(), request, probeThis);
|
||||
capture = [&] {
|
||||
const SampledReadScope sampling(evidenceReads);
|
||||
return shaderMemory->Capture(request, handle.get());
|
||||
return shaderMemory->Capture(invocation);
|
||||
}();
|
||||
captured = shaderMemory->Regions();
|
||||
request.context.memory = captured;
|
||||
|
|
@ -211,10 +210,11 @@ void Driver::dispatch(QueueState& queue, std::span<const std::uint32_t> packet,
|
|||
if (dumpShaders) static_cast<void>(dumpRequest(address, request));
|
||||
const auto started = std::chrono::steady_clock::now();
|
||||
|
||||
static const bool reuseCapture = std::getenv("APS5_NO_CAPTURE_REUSE") == nullptr;
|
||||
bool memoHit = false;
|
||||
compiledResult = reuseCapture ? ShaderRecompiler::Recompile(request, *capture, &memoHit) : std::make_shared<const ShaderRecompiler::RecompileResult>(ShaderRecompiler::Recompile(request));
|
||||
if (compiledResult->cacheHit || memoHit) ++cacheHits;
|
||||
|
||||
timing.Mark("capture_resources");
|
||||
compiledResult = invocation.Materialize(*capture);
|
||||
timing.Mark("materialize");
|
||||
if (compiledResult->cacheHit) ++cacheHits;
|
||||
const auto elapsed = std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - started).count();
|
||||
static double totalMs = 0;
|
||||
totalMs += elapsed;
|
||||
|
|
@ -233,7 +233,7 @@ void Driver::dispatch(QueueState& queue, std::span<const std::uint32_t> packet,
|
|||
phaseTiming.Phase(PhaseRecompile);
|
||||
insertDispatch(address, key, noDispatchCache, profile, registeredShader, forgetAtCapture, memory, shaderMemory, captured, capture, compiledResult, missedEntry, missedDiffering, attachVariant, phaseTiming);
|
||||
}
|
||||
if (verifyDataHits() && dataHit) verifyDataHit(snapshot, codeOffset, localDevice->Serial(), request, memory, address, *keepVariant, liveWords, *compiledResult);
|
||||
if (verifyDataHits() && dataHit) verifyDataHit(snapshot, codeOffset, request, memory, address, *keepVariant, liveWords, *compiledResult);
|
||||
|
||||
if (recordQueuedLabelsAfterCapture(submission.queue, captured)) {
|
||||
dispatch(queue, packet, submission, indirectArguments);
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ DrawVerdict Driver::draw(QueueState& queue, std::span<const std::uint32_t> packe
|
|||
if (!graphics.stages.mesh) return;
|
||||
auto& words = programs.front().userData;
|
||||
require(programs.front().firstUserSgpr == 0 && words.size() >= ShaderRecompiler::MeshIndexBufferUserWord + 4, "mesh program lacks the hidden user words");
|
||||
const auto descriptor = Graphics::MeshIndexBufferDescriptor(parameters, programs.front().binary.codeAddress);
|
||||
const auto descriptor = Graphics::MeshIndexBufferDescriptor(parameters);
|
||||
std::copy(descriptor.begin(), descriptor.end(), words.begin() + ShaderRecompiler::MeshIndexBufferUserWord);
|
||||
};
|
||||
if (!drawParameters.indirect) setMeshIndexBuffer(drawParameters);
|
||||
|
|
@ -204,7 +204,7 @@ DrawVerdict Driver::draw(QueueState& queue, std::span<const std::uint32_t> packe
|
|||
memory.insert(memory.end(), matchedRegions[i].begin(), matchedRegions[i].end());
|
||||
} else {
|
||||
resultIndex[i] = results.size();
|
||||
results.push_back(compileDrawStage(i, pushCursorBytes, queue, submission, programs, graphics, pixel, vertexInfos, memory, linked, drawParameters, localDevice, shaderMemory, stageCaptures, recompiled, drawHit, matched, matchedRegions, profile, dumpTarget, dumpSlot1, captures, phaseTiming, phaseMs, rejected));
|
||||
results.push_back(materializeDrawStage(i, pushCursorBytes, queue, submission, programs, graphics, pixel, vertexInfos, memory, linked, drawParameters, localDevice, shaderMemory, stageCaptures, recompiled, drawHit, matched, matchedRegions, profile, dumpTarget, dumpSlot1, captures, phaseTiming, phaseMs, rejected));
|
||||
if (!rejected.empty()) return DrawVerdict::Rejected;
|
||||
programResults[i] = &results.back();
|
||||
}
|
||||
|
|
@ -234,7 +234,7 @@ DrawVerdict Driver::draw(QueueState& queue, std::span<const std::uint32_t> packe
|
|||
const auto buildRectList = [&] {
|
||||
phaseTiming.Phase(DrawRowVectors);
|
||||
require(programs.size() == 2 && programResults[0] != nullptr && programResults[1] != nullptr, "rect-list requires vertex and fragment programs");
|
||||
auto rectangle = ShaderRecompiler::BuildRectListShaders(*programResults[0], *programResults[1], localDevice->Target());
|
||||
auto rectangle = PreparedRectangle(*programs[0].snapshot, programResults[0]->variantId, programResults[1]->variantId);
|
||||
if (rectListBuilt) {
|
||||
results[rectIndex] = std::move(rectangle.control);
|
||||
results[rectIndex + 1] = std::move(rectangle.evaluation);
|
||||
|
|
@ -329,7 +329,7 @@ DrawVerdict Driver::draw(QueueState& queue, std::span<const std::uint32_t> packe
|
|||
auto& result = results[resultIndex[programIndex]];
|
||||
const auto pushBytes = result.pushConstants.size();
|
||||
decodeVertexInfo(programIndex);
|
||||
result = compileDrawStage(programIndex, pushOffsets[programIndex], queue, submission, programs, graphics, pixel, vertexInfos, memory, linked, drawParameters, localDevice, shaderMemory, stageCaptures, recompiled, drawHit, matched, matchedRegions, profile, dumpTarget, dumpSlot1, captures, phaseTiming, phaseMs, rejected);
|
||||
result = materializeDrawStage(programIndex, pushOffsets[programIndex], queue, submission, programs, graphics, pixel, vertexInfos, memory, linked, drawParameters, localDevice, shaderMemory, stageCaptures, recompiled, drawHit, matched, matchedRegions, profile, dumpTarget, dumpSlot1, captures, phaseTiming, phaseMs, rejected);
|
||||
if (!rejected.empty()) return DrawVerdict::Rejected;
|
||||
require(result.pushConstants.size() == pushBytes, "patched program changed its push constant layout");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,17 @@
|
|||
#include "prx/libSceAgcDriver/Execution/include/PerformanceTimer.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/Driver/Diagnostics.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/Pm4.hpp"
|
||||
#include "Optimization/ResourceProgram.hpp"
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
namespace AgcDriver::DriverDetail {
|
||||
|
||||
ShaderRecompiler::RecompileResult Driver::compileDrawStage(std::size_t i, std::uint32_t pushOffset, const QueueState& queue, const Submission& submission, const std::vector<DrawProgram>& programs, const Graphics::State& graphics, const ShaderRecompiler::ShaderPixelStageInfo& pixel, const std::vector<std::optional<ShaderRecompiler::ShaderVertexStageInfo>>& vertexInfos, std::vector<ShaderRecompiler::MemoryRegion>& memory, const std::vector<ShaderRecompiler::LinkedProgram>& linked, const Pm4::DrawParameters& drawParameters, const std::shared_ptr<VulkanDevice>& localDevice, ShaderMemory& shaderMemory, std::vector<StageCapture>& stageCaptures, std::vector<bool>& recompiled, bool drawHit, const std::vector<std::shared_ptr<DispatchVariant>>& matched, const std::vector<std::vector<ShaderRecompiler::MemoryRegion>>& matchedRegions, bool profile, std::uint64_t dumpTarget, std::uint64_t dumpSlot1, std::uint64_t& captures, DrawPhaseTiming& phaseTiming, std::array<double, DrawDriverPhaseCount>& phaseMs, std::string& rejected) {
|
||||
ShaderRecompiler::RecompileResult Driver::materializeDrawStage(std::size_t i, std::uint32_t pushOffset, const QueueState& queue, const Submission& submission, const std::vector<DrawProgram>& programs, const Graphics::State& graphics, const ShaderRecompiler::ShaderPixelStageInfo& pixel, const std::vector<std::optional<ShaderRecompiler::ShaderVertexStageInfo>>& vertexInfos, std::vector<ShaderRecompiler::MemoryRegion>& memory, const std::vector<ShaderRecompiler::LinkedProgram>& linked, const Pm4::DrawParameters& drawParameters, const std::shared_ptr<VulkanDevice>& localDevice, ShaderMemory& shaderMemory, std::vector<StageCapture>& stageCaptures, std::vector<bool>& recompiled, bool drawHit, const std::vector<std::shared_ptr<DispatchVariant>>& matched, const std::vector<std::vector<ShaderRecompiler::MemoryRegion>>& matchedRegions, bool profile, std::uint64_t dumpTarget, std::uint64_t dumpSlot1, std::uint64_t& captures, DrawPhaseTiming& phaseTiming, std::array<double, DrawDriverPhaseCount>& phaseMs, std::string& rejected) {
|
||||
PerformanceTimer timing("Shader.DrawStage");
|
||||
using Stage = ShaderRecompiler::ShaderStage;
|
||||
phaseTiming.Phase(DrawRowVectors);
|
||||
const auto& program = programs[i];
|
||||
|
|
@ -21,18 +24,14 @@ ShaderRecompiler::RecompileResult Driver::compileDrawStage(std::size_t i, std::u
|
|||
ShaderRecompiler::GraphicsCompileContext{program.firstUserSgpr, linked, graphics.stages.mesh, graphics.stages.tessellation, {drawParameters.indexAddress, drawParameters.indexCount, drawParameters.indexSize, drawParameters.instanceCount}}
|
||||
};
|
||||
const auto waitedBefore = traceCapSync() || profile ? Graphics::Recorder::ThreadWaitedMs() : 0.0;
|
||||
const std::string* poisoned = nullptr;
|
||||
const auto handle = SourceHandleFor(*program.snapshot, program.codeOffset, localDevice->Serial(), request, false, FailureMemo() ? &poisoned : nullptr);
|
||||
if (handle == nullptr && poisoned != nullptr) {
|
||||
rejected = *poisoned;
|
||||
return {};
|
||||
}
|
||||
const auto invocation = InvocationFor(*program.snapshot, program.codeOffset, request);
|
||||
timing.Mark("prepared_invocation");
|
||||
auto& stageCapture = stageCaptures[i];
|
||||
stageCapture.forgetSerial = GuestMemory::ForgetSerial();
|
||||
stageCapture.pushOffset = pushOffset;
|
||||
const auto capture = [&] {
|
||||
const SampledReadScope sampling(evidenceReads);
|
||||
return shaderMemory.Capture(request, handle.get());
|
||||
return shaderMemory.Capture(invocation);
|
||||
}();
|
||||
|
||||
stageCapture.regions = shaderMemory.TakeRecentRegions();
|
||||
|
|
@ -73,10 +72,11 @@ ShaderRecompiler::RecompileResult Driver::compileDrawStage(std::size_t i, std::u
|
|||
}
|
||||
}
|
||||
|
||||
static const bool reuseCapture = std::getenv("APS5_NO_CAPTURE_REUSE") == nullptr;
|
||||
phaseTiming.Phase(DrawRowCapture);
|
||||
|
||||
stageCapture.compiled = reuseCapture ? ShaderRecompiler::Recompile(request, *capture) : std::make_shared<const ShaderRecompiler::RecompileResult>(ShaderRecompiler::Recompile(request));
|
||||
timing.Mark("capture_resources");
|
||||
stageCapture.compiled = invocation.Materialize(*capture);
|
||||
timing.Mark("materialize");
|
||||
ShaderRecompiler::RecompileResult result = *stageCapture.compiled;
|
||||
phaseTiming.Phase(DrawRowRecompile);
|
||||
return result;
|
||||
|
|
|
|||
|
|
@ -5,27 +5,38 @@
|
|||
|
||||
namespace AgcDriver::DriverDetail {
|
||||
|
||||
std::shared_ptr<DrawDecode> Driver::decodeDraw(const QueueState& queue, const Submission& submission) {
|
||||
namespace {
|
||||
|
||||
std::uint32_t ReadGraphicsRegister(const Registers& registers, std::uint32_t offset) {
|
||||
const auto found = registers.find(offset);
|
||||
return found == registers.end() ? 0u : found->second;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void DecodeGraphicsPrograms(DrawDecode& decoded, const QueueState& queue, const ShaderRegistry& registry, bool staticAbi, bool includeFragment) {
|
||||
using Stage = ShaderRecompiler::ShaderStage;
|
||||
using Role = ShaderRecompiler::ProgramRole;
|
||||
const auto programAddress = [&](std::uint32_t base) {
|
||||
Graphics::NoteRegisterRead(Graphics::RegisterBank::Shader, base);
|
||||
Graphics::NoteRegisterRead(Graphics::RegisterBank::Shader, base + 1);
|
||||
const auto high = readRegister(queue.shader, base + 1);
|
||||
const auto high = ReadGraphicsRegister(queue.shader, base + 1);
|
||||
require((high & ~0xffu) == 0, "reserved graphics program address bits are set");
|
||||
return (static_cast<std::uint64_t>(readRegister(queue.shader, base)) << 8u) | (static_cast<std::uint64_t>(high) << 40u);
|
||||
return (static_cast<std::uint64_t>(ReadGraphicsRegister(queue.shader, base)) << 8u) | (static_cast<std::uint64_t>(high) << 40u);
|
||||
};
|
||||
const bool pixelSkipped = Graphics::PixelProgramSkipped(queue);
|
||||
const auto prepare = [&](std::uint64_t address, std::uint8_t type, Stage stage, std::uint32_t rsrc2, std::uint32_t userDataBase) {
|
||||
const bool nullPixel = address == 0 && stage == Stage::Fragment;
|
||||
const bool nullPixel = stage == Stage::Fragment && (address == 0 || pixelSkipped);
|
||||
if (nullPixel) address = NullPixelProgramAddress();
|
||||
auto it = submission.shaders->upper_bound(address);
|
||||
require(it != submission.shaders->begin(), "graphics program does not belong to a registered shader");
|
||||
auto it = registry.upper_bound(address);
|
||||
require(it != registry.begin(), "graphics program does not belong to a registered shader");
|
||||
--it;
|
||||
const auto& snapshot = *it->second;
|
||||
require(address - snapshot.codeAddress < snapshot.code.size() * sizeof(std::uint32_t), "graphics program is outside registered shader code");
|
||||
require((address - snapshot.codeAddress) % sizeof(std::uint32_t) == 0, "graphics entry point is not dword aligned");
|
||||
require(snapshot.type == type, "graphics program refers to an incompatible shader binary type");
|
||||
Graphics::NoteRegisterRead(Graphics::RegisterBank::Shader, rsrc2);
|
||||
const auto resources = nullPixel && !queue.shader.contains(rsrc2) ? 0u : readRegister(queue.shader, rsrc2);
|
||||
const auto resources = nullPixel && !queue.shader.contains(rsrc2) ? 0u : ReadGraphicsRegister(queue.shader, rsrc2);
|
||||
const auto userCount = ((resources >> 1u) & 0x1fu) | (((resources >> 27u) & 1u) << 5u);
|
||||
require(userCount <= 32, "graphics user SGPR count exceeds the register bank");
|
||||
const auto codeOffset = static_cast<std::size_t>((address - snapshot.codeAddress) / sizeof(std::uint32_t));
|
||||
|
|
@ -40,13 +51,12 @@ std::shared_ptr<DrawDecode> Driver::decodeDraw(const QueueState& queue, const Su
|
|||
};
|
||||
for (std::uint32_t i = 0; i < userCount; ++i) {
|
||||
Graphics::NoteRegisterRead(Graphics::RegisterBank::Shader, userDataBase + i);
|
||||
result.userData.push_back(readUserData(queue.shader, userDataBase + i));
|
||||
result.userData.push_back((staticAbi ? ReadGraphicsRegister(queue.shader, userDataBase + i) : readUserData(queue.shader, userDataBase + i)));
|
||||
}
|
||||
return result;
|
||||
};
|
||||
{
|
||||
auto product = std::make_shared<DrawDecode>();
|
||||
product->state = Graphics::DecodeState(queue);
|
||||
auto* product = &decoded;
|
||||
auto& programs = product->programs;
|
||||
auto& roles = product->roles;
|
||||
programs.reserve(5);
|
||||
|
|
@ -60,9 +70,10 @@ std::shared_ptr<DrawDecode> Driver::decodeDraw(const QueueState& queue, const Su
|
|||
program.userData.insert(program.userData.begin(), 8, 0);
|
||||
Graphics::NoteRegisterRead(Graphics::RegisterBank::Shader, pointerBase);
|
||||
Graphics::NoteRegisterRead(Graphics::RegisterBank::Shader, pointerBase + 1);
|
||||
if (staticAbi) return;
|
||||
if (!pointerRequired && !queue.shader.contains(pointerBase) && !queue.shader.contains(pointerBase + 1)) return;
|
||||
const auto low = readRegister(queue.shader, pointerBase);
|
||||
const auto high = readRegister(queue.shader, pointerBase + 1);
|
||||
const auto low = ReadGraphicsRegister(queue.shader, pointerBase);
|
||||
const auto high = ReadGraphicsRegister(queue.shader, pointerBase + 1);
|
||||
const auto address = static_cast<std::uint64_t>(low) | (static_cast<std::uint64_t>(high) << 32u);
|
||||
require(address != 0 || !pointerRequired, "merged shader user-data address is null");
|
||||
if (address == 0) return;
|
||||
|
|
@ -78,8 +89,8 @@ std::shared_ptr<DrawDecode> Driver::decodeDraw(const QueueState& queue, const Su
|
|||
append(0x0c8, 2, Stage::TessellationEvaluation, 0x08b, 0x08c, Role::Domain);
|
||||
} else if (graphics.stages.path == Graphics::ShaderPath::Geometry) {
|
||||
const auto frontAddress = programAddress(0xc8);
|
||||
auto snapshot = submission.shaders->upper_bound(frontAddress);
|
||||
require(snapshot != submission.shaders->begin(), "geometry front program is not registered");
|
||||
auto snapshot = registry.upper_bound(frontAddress);
|
||||
require(snapshot != registry.begin(), "geometry front program is not registered");
|
||||
--snapshot;
|
||||
const auto type = snapshot->second->type;
|
||||
require(type == 2 || type == 4, "invalid geometry front binary type");
|
||||
|
|
@ -89,18 +100,25 @@ std::shared_ptr<DrawDecode> Driver::decodeDraw(const QueueState& queue, const Su
|
|||
} else {
|
||||
append(0xc8, 2, Stage::Vertex, 0x8b, 0x8c, Role::Main);
|
||||
}
|
||||
const bool nullPixel = Graphics::PixelProgramUnset(queue);
|
||||
if (nullPixel) {
|
||||
const auto rejection = Graphics::NullPixelProgramRejection(queue);
|
||||
require(rejection.empty(), rejection.c_str());
|
||||
if (includeFragment) {
|
||||
if (pixelSkipped) {
|
||||
const auto rejection = Graphics::NullPixelProgramRejection(queue);
|
||||
require(rejection.empty(), rejection.c_str());
|
||||
}
|
||||
append(0x008, 1, Stage::Fragment, 0x00b, 0x00c, Role::Fragment);
|
||||
programs.back().firstUserSgpr = 0;
|
||||
}
|
||||
append(0x008, 1, Stage::Fragment, 0x00b, 0x00c, Role::Fragment);
|
||||
programs.back().firstUserSgpr = 0;
|
||||
product->pixel = Graphics::DecodePixelStageInfo(queue.context, Graphics::ExportMappings(graphics), nullPixel);
|
||||
return product;
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<DrawDecode> Driver::decodeDraw(const QueueState& queue, const Submission& submission) {
|
||||
auto product = std::make_shared<DrawDecode>();
|
||||
product->state = Graphics::DecodeState(queue);
|
||||
DecodeGraphicsPrograms(*product, queue, *submission.shaders, false, true);
|
||||
product->pixel = Graphics::DecodePixelStageInfo(queue.context, Graphics::ExportMappings(product->state), Graphics::PixelProgramSkipped(queue));
|
||||
return product;
|
||||
}
|
||||
|
||||
void Driver::resolveDrawDecode(const QueueState& queue, const Submission& submission, std::shared_ptr<const DrawDecode>& decode, bool registerKey, std::uint64_t drawKey, bool profile) {
|
||||
if (decode == nullptr || verifyDrawRecipe()) {
|
||||
std::vector<Graphics::RegisterRead> readLog;
|
||||
|
|
|
|||
|
|
@ -63,6 +63,7 @@ void Driver::lookupDraw(const Submission& submission, const std::shared_ptr<Vulk
|
|||
mix(pixel.inputAddr);
|
||||
for (const bool flag : {pixel.wave32, pixel.hasPerspectiveCenterVgpr, pixel.perspectiveCentroid, pixel.posX, pixel.posY, pixel.posZ, pixel.posW, pixel.frontFace, pixel.ancillary, pixel.sampleShading, pixel.noPerspective, pixel.linearCentroid, pixel.pixelKillEnable, pixel.depthExportEnable, pixel.sampleMaskExportEnable, pixel.earlyZ, pixel.executeOnNoop}) mix(flag);
|
||||
mix(static_cast<std::uint64_t>(pixel.conservativeZExport));
|
||||
mix(pixel.orderedPixelShader);
|
||||
for (const auto value : pixel.targetOutputMode) mix(value);
|
||||
for (const auto value : pixel.targetExportMapping) mix(value);
|
||||
std::lock_guard cacheLock(drawCacheMutex);
|
||||
|
|
|
|||
|
|
@ -23,13 +23,15 @@ std::optional<DrawVerdict> Driver::precheckDraw(const QueueState& queue, const S
|
|||
localDevice->ColorMetadataPass(*pass);
|
||||
return DrawVerdict::Drawn;
|
||||
}
|
||||
rejected = Graphics::DepthMaintenanceRejection(queue);
|
||||
if (!rejected.empty()) return DrawVerdict::Rejected;
|
||||
static const bool traceIndirectEnabled = std::getenv("APS5_TRACE_INDIRECT_DRAWS") != nullptr;
|
||||
traceIndirect = traceIndirectEnabled;
|
||||
if (traceIndirect && drawParameters.indirect) std::fprintf(stderr, "[draw] indirect packet %s args 0x%llx count %u reached\n", Pm4::Name(packet[0]).c_str(), static_cast<unsigned long long>(drawParameters.indirect->arguments), drawParameters.indirect->count);
|
||||
{
|
||||
const auto targetMask = queue.context.find(0x8e);
|
||||
const auto shaderMask = queue.context.find(0x8f);
|
||||
const bool colorWrites = targetMask != queue.context.end() && shaderMask != queue.context.end() && (targetMask->second & shaderMask->second) != 0;
|
||||
const bool colorWrites = targetMask != queue.context.end() && shaderMask != queue.context.end() && Graphics::ColorWriteMask(queue.context) != 0;
|
||||
if (!colorWrites && !queue.shader.contains(0x8)) {
|
||||
const auto word = [&](std::uint32_t offset) { const auto it = queue.context.find(offset); return it == queue.context.end() ? 0u : it->second; };
|
||||
if ((word(0x200) & 3u) == 0 || ((word(0x010) & 3u) == 0 && (word(0x011) & 1u) == 0)) return DrawVerdict::Nothing;
|
||||
|
|
|
|||
|
|
@ -1,20 +1,37 @@
|
|||
#include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp"
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
namespace AgcDriver::DriverDetail {
|
||||
|
||||
std::uint64_t Driver::drawRegisterKey(const QueueState& queue, const ShaderRegistry& registry, std::uint64_t deviceSerial) {
|
||||
static const bool allUserWords = std::getenv("APS5_DRAW_KEY_ALL_USER_WORDS") != nullptr;
|
||||
std::uint64_t key = 0xcbf29ce484222325ull;
|
||||
const auto mix = [&](std::uint64_t value) {
|
||||
key ^= value;
|
||||
key *= 0x100000001b3ull;
|
||||
};
|
||||
const auto userEnd = [&](std::uint32_t base) {
|
||||
if (allUserWords) return base + 32u;
|
||||
const auto resources = queue.shader.find(base - 1);
|
||||
if (resources == queue.shader.end()) return base;
|
||||
const auto count = ((resources->second >> 1u) & 0x1fu) | (((resources->second >> 27u) & 1u) << 5u);
|
||||
return base + std::min(count, 32u);
|
||||
};
|
||||
const std::array<std::pair<std::uint32_t, std::uint32_t>, 3> users{{{0x00cu, userEnd(0x00cu)}, {0x08cu, userEnd(0x08cu)}, {0x10cu, userEnd(0x10cu)}}};
|
||||
const auto unread = [&](std::uint32_t offset) {
|
||||
return std::any_of(users.begin(), users.end(), [&](const auto& user) { return offset >= user.second && offset < user.first + 32u; });
|
||||
};
|
||||
mix(deviceSerial);
|
||||
for (const auto& range : Graphics::DrawKeyRegisters) {
|
||||
const auto& bank = range.bank == Graphics::RegisterBank::Context ? queue.context : range.bank == Graphics::RegisterBank::Shader ? queue.shader : queue.userConfig;
|
||||
mix((static_cast<std::uint64_t>(range.bank) << 32u) | range.first);
|
||||
const auto end = range.first + range.count;
|
||||
const bool shader = range.bank == Graphics::RegisterBank::Shader;
|
||||
for (auto it = bank.lower_bound(range.first); it != bank.end() && it->first < end; ++it) {
|
||||
if (shader && unread(it->first)) continue;
|
||||
mix(it->first);
|
||||
mix(it->second);
|
||||
}
|
||||
|
|
@ -77,11 +94,11 @@ bool Driver::sameDecode(const DrawDecode& a, const DrawDecode& b) {
|
|||
for (std::size_t i = 0; i < s.blends.size(); ++i) {
|
||||
if (!sameBlend(s.blends[i], t.blends[i])) return false;
|
||||
}
|
||||
if (s.hasColorTarget != t.hasColorTarget || s.rectList != t.rectList || s.renderExtent.width != t.renderExtent.width || s.renderExtent.height != t.renderExtent.height || s.topology != t.topology || s.negativeOneToOne != t.negativeOneToOne || s.depthClamp != t.depthClamp || s.cullMode != t.cullMode || s.frontFace != t.frontFace || !sameBlend(s.blend, t.blend) || s.blendConstants != t.blendConstants) return false;
|
||||
if (s.hasColorTarget != t.hasColorTarget || s.rectList != t.rectList || s.renderExtent.width != t.renderExtent.width || s.renderExtent.height != t.renderExtent.height || s.topology != t.topology || s.negativeOneToOne != t.negativeOneToOne || s.depthClamp != t.depthClamp || s.conservativeRasterization != t.conservativeRasterization || s.cullMode != t.cullMode || s.frontFace != t.frontFace || !sameBlend(s.blend, t.blend) || s.blendConstants != t.blendConstants) return false;
|
||||
if (std::memcmp(&s.viewport, &t.viewport, sizeof(VkViewport)) != 0 || std::memcmp(&s.scissor, &t.scissor, sizeof(VkRect2D)) != 0) return false;
|
||||
const auto& p = a.pixel;
|
||||
const auto& q = b.pixel;
|
||||
if (p.interpolatorCount != q.interpolatorCount || p.interpolatorSettings != q.interpolatorSettings || p.wave32 != q.wave32 || p.inputAddr != q.inputAddr || p.hasPerspectiveCenterVgpr != q.hasPerspectiveCenterVgpr || p.perspectiveCentroid != q.perspectiveCentroid || p.posX != q.posX || p.posY != q.posY || p.posZ != q.posZ || p.posW != q.posW || p.frontFace != q.frontFace || p.ancillary != q.ancillary || p.sampleShading != q.sampleShading || p.noPerspective != q.noPerspective || p.linearCentroid != q.linearCentroid || p.pixelKillEnable != q.pixelKillEnable || p.depthExportEnable != q.depthExportEnable || p.sampleMaskExportEnable != q.sampleMaskExportEnable || p.earlyZ != q.earlyZ || p.executeOnNoop != q.executeOnNoop || p.conservativeZExport != q.conservativeZExport || p.targetOutputMode != q.targetOutputMode || p.targetExportMapping != q.targetExportMapping) return false;
|
||||
if (p.interpolatorCount != q.interpolatorCount || p.interpolatorSettings != q.interpolatorSettings || p.wave32 != q.wave32 || p.inputAddr != q.inputAddr || p.hasPerspectiveCenterVgpr != q.hasPerspectiveCenterVgpr || p.perspectiveCentroid != q.perspectiveCentroid || p.posX != q.posX || p.posY != q.posY || p.posZ != q.posZ || p.posW != q.posW || p.frontFace != q.frontFace || p.ancillary != q.ancillary || p.sampleShading != q.sampleShading || p.noPerspective != q.noPerspective || p.linearCentroid != q.linearCentroid || p.pixelKillEnable != q.pixelKillEnable || p.depthExportEnable != q.depthExportEnable || p.sampleMaskExportEnable != q.sampleMaskExportEnable || p.earlyZ != q.earlyZ || p.executeOnNoop != q.executeOnNoop || p.conservativeZExport != q.conservativeZExport || p.orderedPixelShader != q.orderedPixelShader || p.targetOutputMode != q.targetOutputMode || p.targetExportMapping != q.targetExportMapping) return false;
|
||||
if (a.roles != b.roles || a.programs.size() != b.programs.size()) return false;
|
||||
for (std::size_t i = 0; i < a.programs.size(); ++i) {
|
||||
const auto& x = a.programs[i];
|
||||
|
|
|
|||
|
|
@ -109,3 +109,14 @@ extern "C" void AgcDriverReleaseWindow_nid_postfix(void* window) {
|
|||
extern "C" void AgcDriverReportFailure_nid_postfix(std::exception_ptr error) {
|
||||
AgcDriver::ReportFailure(error);
|
||||
}
|
||||
|
||||
extern "C" void AgcDriverResolveShaderAbi_nid_postfix(const Shader* shader, std::span<const ShaderRegister> context, std::span<const ShaderRegister> primitive) {
|
||||
AgcDriver::DriverDetail::Driver::Get().ResolveShaderAbi(shader, context, primitive);
|
||||
}
|
||||
|
||||
extern "C" void AgcDriverResolveGraphicsAbi_nid_postfix(const Shader* vertex, const Shader* pixel, std::uint32_t primitiveType) {
|
||||
AgcDriver::DriverDetail::Driver::Get().ResolveGraphicsAbi(vertex, pixel, primitiveType);
|
||||
}
|
||||
extern "C" void AgcDriverResolveGraphicsStagesAbi_nid_postfix(std::span<const Shader* const> stages, std::span<const ShaderRegister> context, std::span<const ShaderRegister> primitive) {
|
||||
AgcDriver::DriverDetail::Driver::Get().ResolveGraphicsStagesAbi(stages, context, primitive);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
#include "prx/libSceAgcDriver/Execution/include/Driver/Diagnostics.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/WorkerSampler.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/ProfileOutput.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp"
|
||||
|
||||
namespace AgcDriver::DriverDetail {
|
||||
|
|
@ -44,6 +45,7 @@ void Driver::stop() {
|
|||
device.Reset();
|
||||
replacedDevices.clear();
|
||||
Graphics::ShutdownGuestBufferWorkers();
|
||||
ProfileOutput_nid_no_patch().Stop();
|
||||
stopped = true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,17 +29,11 @@ void Driver::timed(double WorkerProfile::*bucket, TWork&& work) {
|
|||
}
|
||||
}
|
||||
|
||||
template <typename TWork>
|
||||
void Driver::tolerate(const char* kind, TWork&& work) {
|
||||
try {
|
||||
work();
|
||||
} catch (const std::exception& error) {
|
||||
reportSkip(kind, error.what());
|
||||
}
|
||||
}
|
||||
|
||||
void Driver::execute(const Submission& submission) {
|
||||
auto* submissionTiming = includeTimingSubmission(submission, true);
|
||||
if (submission.suspend) {
|
||||
PerformanceContext timingContext(submissionTiming);
|
||||
PerformanceTimer timing("Driver.Suspend");
|
||||
|
||||
static const bool suspendDrain = std::getenv("APS5_SUSPEND_DRAIN") != nullptr;
|
||||
static const bool profile = std::getenv("APS5_PROFILE_DRAW") != nullptr;
|
||||
|
|
@ -113,9 +107,13 @@ void Driver::execute(const Submission& submission) {
|
|||
const auto packet = std::span(submission.commands).subspan(cursor, count);
|
||||
const auto opcode = (header >> 8u) & 0xffu;
|
||||
auto nextCursor = cursor + count;
|
||||
if (APS5_ENABLE_TIMING_LOG && submission.queue == 0 && pendingFrameTiming == nullptr) includeTimingSubmission(submission, false);
|
||||
PerformanceContext timingContext(submission.queue == 0 ? pendingFrameTiming.get() : FrameTiming::Async());
|
||||
PerformanceTimer timing("Driver.Packet");
|
||||
std::shared_lock deviceUse(deviceReplacement, std::defer_lock);
|
||||
if (opcode != 0x3c && opcode != 0x93 && header != RenderingWaitPacketHeader && header != FlipPacketHeader) deviceUse.lock();
|
||||
|
||||
timing.Mark("device_lock");
|
||||
GuestMemory::SetCurrentPacket(header == FlipPacketHeader ? 0xffffu : opcode, submission.queue);
|
||||
CaptureTrace::Log("packet submission=%llu queue=%x offset=%zu header=%08x words=%zu", static_cast<unsigned long long>(submission.serial), submission.queue, cursor, header, packet.size());
|
||||
if (Pm4::Predicated(header) && queue.predication.operation != 0) {
|
||||
|
|
@ -132,6 +130,7 @@ void Driver::execute(const Submission& submission) {
|
|||
|
||||
const auto flushStart = profilePackets ? std::chrono::steady_clock::now() : std::chrono::steady_clock::time_point{};
|
||||
flushBetweenPackets(submission.queue, header, opcode == 0x49 || opcode == 0x37);
|
||||
timing.Mark("flush_between_packets");
|
||||
PacketTimer packetTimer{profilePackets, header == FlipPacketHeader ? 0xffffu : opcode, submission.queue, packetProfile, std::chrono::steady_clock::now()};
|
||||
|
||||
if (profilePackets) {
|
||||
|
|
@ -170,6 +169,7 @@ void Driver::execute(const Submission& submission) {
|
|||
bool wroteOnGpu = false, endOfPipeInterrupt = false, interruptDeferred = false, drawPacket = false, sampleDump = false;
|
||||
const bool drains = preparePacketMemory(submission, queue, packet, header, opcode, wroteOnGpu, endOfPipeInterrupt, interruptDeferred, drawPacket, sampleDump);
|
||||
traceLabel(packet, submission.queue);
|
||||
timing.Mark("prepare_memory");
|
||||
|
||||
const bool waitPacket = opcode == 0x3c || opcode == 0x93 || header == RenderingWaitPacketHeader;
|
||||
struct Progress {
|
||||
|
|
@ -201,10 +201,14 @@ void Driver::execute(const Submission& submission) {
|
|||
if (batchesAtFlip != 0) flipSerial = batchesAtFlip;
|
||||
flipBatchesUnsignaled += unsignaledAtFlip;
|
||||
|
||||
auto frame = std::make_shared<FrameTiming>(++frameSerial);
|
||||
timing.Mark("flip_flush");
|
||||
timing.FinishPacket(submission.serial, cursor, 0xffffu, count);
|
||||
auto frame = APS5_ENABLE_TIMING_LOG ? std::move(pendingFrameTiming) : std::make_shared<FrameTiming>(++frameSerial);
|
||||
require(frame != nullptr, "flip has no frame timing");
|
||||
const auto now = FrameTiming::Clock::now();
|
||||
frame->IncludeSubmission(submission.serial, now, now, now, true);
|
||||
frame->SetFlip(submission.serial, cursor, now, now);
|
||||
if (!APS5_ENABLE_TIMING_LOG) frame->IncludeSubmission(submission.serial, now, now, now, true);
|
||||
frame->SetFlip(submission.serial, cursor, APS5_ENABLE_TIMING_LOG ? submission.receivedAt : now, now);
|
||||
if (APS5_ENABLE_TIMING_LOG) frame->CollectBackground();
|
||||
frame->NoteFlipBatches(batchesAtFlip, unsignaledAtFlip);
|
||||
CaptureTrace::Log("flip frame=%llu submission=%llu offset=%zu batch=%llu unsignaled=%llu", static_cast<unsigned long long>(frameSerial), static_cast<unsigned long long>(submission.serial), cursor, static_cast<unsigned long long>(batchesAtFlip), static_cast<unsigned long long>(unsignaledAtFlip));
|
||||
submission.flips.at(cursor)->GpuReady(frame);
|
||||
|
|
@ -234,7 +238,7 @@ void Driver::execute(const Submission& submission) {
|
|||
}
|
||||
} else if (drawPacket) {
|
||||
bool drawn = false;
|
||||
timed(&WorkerProfile::drawMs, [&] { tolerate("draw", [&] {
|
||||
timed(&WorkerProfile::drawMs, [&] {
|
||||
static const bool traceDraws = std::getenv("APS5_TRACE_DRAWS") != nullptr;
|
||||
static const bool profileDraws = std::getenv("APS5_PROFILE_DRAW") != nullptr;
|
||||
const auto color = (static_cast<std::uint64_t>(readRegister(queue.context, 0x390)) << 40u) | (static_cast<std::uint64_t>(readRegister(queue.context, 0x318)) << 8u);
|
||||
|
|
@ -243,36 +247,14 @@ void Driver::execute(const Submission& submission) {
|
|||
if (profileDraws) Graphics::CountDrawSkip(kind, std::chrono::duration<double, std::micro>(std::chrono::steady_clock::now() - started).count());
|
||||
};
|
||||
|
||||
const auto skipped = [&](const std::string& what) {
|
||||
if (traceDraws) std::fprintf(stderr, "[draw] target 0x%llx mask 0x%x failed: %.160s\n",static_cast<unsigned long long>(color), readRegister(queue.context, 0x8e), what.c_str());
|
||||
|
||||
char suffix[80];
|
||||
std::snprintf(suffix, sizeof(suffix), " [%s, color target 0x%llx]", Pm4::Name(header).c_str(), static_cast<unsigned long long>(color));
|
||||
|
||||
static std::set<std::pair<std::uint64_t, std::string>> dumpedTargets;
|
||||
const std::string reason = what.substr(0, 48);
|
||||
if (dumpedTargets.insert({color, reason}).second) {
|
||||
char name[64];
|
||||
std::snprintf(name, sizeof(name), "draw_%llx_%08x.regs", static_cast<unsigned long long>(color), static_cast<std::uint32_t>(std::hash<std::string>{}(reason)));
|
||||
if (std::FILE* file = std::fopen(name, "w")) {
|
||||
std::fprintf(file, "# %s\n", what.c_str());
|
||||
recent.Print(file, "# packet %s\n");
|
||||
for (const auto& [offset, value] : queue.context) std::fprintf(file, "context %x %08x\n", offset, value);
|
||||
for (const auto& [offset, value] : queue.userConfig) std::fprintf(file, "uconfig %x %08x\n", offset, value);
|
||||
for (const auto& [offset, value] : queue.shader) std::fprintf(file, "shader %x %08x\n", offset, value);
|
||||
std::fclose(file);
|
||||
}
|
||||
}
|
||||
reportSkip("draw", what + suffix);
|
||||
};
|
||||
try {
|
||||
std::string rejected;
|
||||
const auto verdict = draw(queue, packet, submission, rejected);
|
||||
drawn = verdict == DrawVerdict::Drawn;
|
||||
CaptureTrace::Log("draw submission=%llu queue=%x offset=%zu target=%llx mask=%x verdict=%d reason=%.256s", static_cast<unsigned long long>(submission.serial), submission.queue, cursor, static_cast<unsigned long long>(color), readRegister(queue.context, 0x8e), static_cast<int>(verdict), rejected.c_str());
|
||||
if (verdict == DrawVerdict::Rejected) {
|
||||
skipped(rejected);
|
||||
countSkip(Graphics::DrawSkip::Prechecked);
|
||||
throw std::runtime_error(rejected);
|
||||
} else if (verdict == DrawVerdict::Nothing) {
|
||||
countSkip(Graphics::DrawSkip::Nothing);
|
||||
} else if (traceDraws) {
|
||||
|
|
@ -280,10 +262,10 @@ void Driver::execute(const Submission& submission) {
|
|||
}
|
||||
} catch (const std::exception& error) {
|
||||
CaptureTrace::Log("draw-error submission=%llu offset=%zu reason=%.256s", static_cast<unsigned long long>(submission.serial), cursor, error.what());
|
||||
skipped(error.what());
|
||||
countSkip(Graphics::DrawSkip::Thrown);
|
||||
throw;
|
||||
}
|
||||
}); });
|
||||
});
|
||||
finishDrawPacket(drawn);
|
||||
} else if (opcode == 0x22) {
|
||||
recordQueuedLabelsBeforeRead(submission.queue);
|
||||
|
|
@ -294,7 +276,8 @@ void Driver::execute(const Submission& submission) {
|
|||
dumpSampleCounters(packet[2] | (static_cast<std::uint64_t>(packet[3]) << 32u));
|
||||
} else if (opcode != 0x42 && opcode != 0x46 && opcode != 0x58) {
|
||||
if (!wroteOnGpu) {
|
||||
Pm4::Execute(packet, queue);
|
||||
if (Pm4::IndirectRegisterOpcode(opcode)) Pm4::ExecuteIndirectRegisters(packet, submission.registerLists.at(cursor), queue);
|
||||
else Pm4::Execute(packet, queue);
|
||||
if (opcode == 0x49 || opcode == 0x37) {
|
||||
if (const auto label = Pm4::DecodeLabelWrite(packet)) noteLabelStore(label->address, label->Bytes(), ++eventSerial);
|
||||
}
|
||||
|
|
@ -302,9 +285,14 @@ void Driver::execute(const Submission& submission) {
|
|||
if (endOfPipeInterrupt && !interruptDeferred) AgcDriverDeliverEopInterrupt(submission.queue);
|
||||
}
|
||||
if (drawPacket || (sampleDump && wroteOnGpu)) Graphics::Recorder::CountRecordedWork();
|
||||
timing.Mark(header == RenderingWaitPacketHeader ? "rendering_wait" : opcode == 0x3c || opcode == 0x93 ? "memory_wait" : opcode == 0x15 || opcode == 0x16 ? "dispatch" : drawPacket ? "draw" : "execute");
|
||||
if (submission.queue == 0) timing.FinishPacket(submission.serial, cursor, opcode, count);
|
||||
else timing.Finish();
|
||||
cursor = nextCursor;
|
||||
}
|
||||
|
||||
PerformanceContext endContext(submission.queue == 0 ? frameTiming() : FrameTiming::Async());
|
||||
PerformanceTimer endTiming("Driver.EndSubmission");
|
||||
static const bool submitAtEnd = std::getenv("APS5_SUBMIT_AT_END") != nullptr;
|
||||
if (!deferredLabels().labels.empty() || Graphics::Recorder::PendingLabelSince().has_value() || Graphics::Recorder::RecordedWorkSinceSubmit() != 0) {
|
||||
auto& costs = submissionCosts(submission.queue);
|
||||
|
|
@ -320,6 +308,7 @@ void Driver::execute(const Submission& submission) {
|
|||
recordDeferredLabels(localDevice.get(), submission.queue);
|
||||
if (localDevice != nullptr) localDevice->SubmitRecorded(submission.queue == 0);
|
||||
}
|
||||
endTiming.Finish();
|
||||
if (submission.rewindTail != nullptr) executeRewindTail(submission);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,8 @@ bool Driver::preparePacketMemory(const Submission& submission, QueueState& queue
|
|||
|
||||
bool orderedAlready = false;
|
||||
|
||||
endOfPipeInterrupt = opcode == 0x49 && ((packet[2] >> 24u) & 7u) != 0;
|
||||
const auto interruptSelect = (packet[2] >> 24u) & 7u;
|
||||
endOfPipeInterrupt = opcode == 0x49 && interruptSelect != 0 && interruptSelect != 3;
|
||||
interruptDeferred = false;
|
||||
if (!drainAll && endOfPipeInterrupt) {
|
||||
const auto label = Pm4::DecodeLabelWrite(packet);
|
||||
|
|
|
|||
|
|
@ -2,12 +2,42 @@
|
|||
#include "prx/libSceAgcDriver/Execution/include/Driver/Diagnostics.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/Driver/Synchronization/SynchronizationStatistics.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/Driver/Queues/WorkerAffinity.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/Driver/Synchronization/WaitMemory.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/WorkerSampler.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/PerformanceTimer.hpp"
|
||||
#include <cstdlib>
|
||||
|
||||
namespace AgcDriver::DriverDetail {
|
||||
|
||||
FrameTiming* Driver::frameTiming() {
|
||||
if (!APS5_ENABLE_TIMING_LOG) return nullptr;
|
||||
if (pendingFrameTiming == nullptr) pendingFrameTiming = std::make_shared<FrameTiming>(++frameSerial, true);
|
||||
return pendingFrameTiming.get();
|
||||
}
|
||||
|
||||
FrameTiming* Driver::includeTimingSubmission(const Submission& submission, bool firstSegment) {
|
||||
if (!APS5_ENABLE_TIMING_LOG) return nullptr;
|
||||
auto* frame = submission.queue == 0 ? frameTiming() : FrameTiming::Async();
|
||||
require(submission.receivedAt != FrameTiming::Clock::time_point{} && submission.receivedAt <= submission.enqueuedAt && submission.enqueuedAt <= submission.dequeuedAt && submission.dequeuedAt <= submission.orderedAt, "invalid submission timing order");
|
||||
if (submission.queue == 0) frame->IncludeSubmission(submission.serial, submission.receivedAt, submission.enqueuedAt, submission.orderedAt, firstSegment);
|
||||
else if (firstSegment) {
|
||||
frame->Add(frame->Get("Submission", "accept"), submission.enqueuedAt - submission.receivedAt);
|
||||
frame->Add(frame->Get("Submission", "queue"), submission.orderedAt - submission.enqueuedAt);
|
||||
}
|
||||
if (firstSegment) {
|
||||
frame->Add(frame->Get("Submission", "dequeue"), submission.dequeuedAt - submission.enqueuedAt);
|
||||
frame->Add(frame->Get("Submission", "order_wait"), submission.orderedAt - submission.dequeuedAt);
|
||||
if (!submission.suspend) {
|
||||
frame->Add(frame->Get("Submission", "copy"), submission.copiedAt - submission.receivedAt, submission.commands.size() * sizeof(std::uint32_t));
|
||||
frame->Add(frame->Get("Submission", "validate"), submission.validatedAt - submission.copiedAt);
|
||||
frame->Add(frame->Get("Submission", "flip_room_wait"), submission.roomReadyAt - submission.validatedAt);
|
||||
frame->Add(frame->Get("Submission", "reserve_enqueue"), submission.enqueuedAt - submission.roomReadyAt);
|
||||
}
|
||||
}
|
||||
return frame;
|
||||
}
|
||||
|
||||
void Driver::markCompleted(std::uint64_t serial) {
|
||||
completedOutOfOrder.insert(serial);
|
||||
while (!completedOutOfOrder.empty() && *completedOutOfOrder.begin() == completed + 1) {
|
||||
|
|
@ -52,12 +82,25 @@ void Driver::run(std::uint32_t id) noexcept {
|
|||
submission = Submission{};
|
||||
static const bool traceGpu = std::getenv("APS5_TRACE_GPU") != nullptr;
|
||||
{
|
||||
PerformanceContext timingContext(id == 0 ? frameTiming() : FrameTiming::Async());
|
||||
PerformanceTimer timing("Driver.WorkerWait");
|
||||
std::unique_lock lock(mutex);
|
||||
timing.Mark("mutex_wait");
|
||||
auto& worker = workers.at(id);
|
||||
auto& pending = worker.pending;
|
||||
workerQueued() = &worker.queued;
|
||||
if (traceGpu && pending.empty()) std::fprintf(stderr, "[gpu] %.1f idle queue=0x%x\n", TraceMs(), id);
|
||||
const auto ready = [&] { return failure || stopping || !pending.empty(); };
|
||||
const auto poll = [&](const auto& done) {
|
||||
#ifdef _WIN32
|
||||
lock.unlock();
|
||||
PollSleep();
|
||||
lock.lock();
|
||||
return done();
|
||||
#else
|
||||
return changed.wait_for(lock, std::chrono::milliseconds(1), done);
|
||||
#endif
|
||||
};
|
||||
|
||||
while (!ready()) {
|
||||
if (!completionsPending() || (id != 0 && Graphics::Recorder::PendingCompletionLabels() == 0)) {
|
||||
|
|
@ -66,24 +109,29 @@ void Driver::run(std::uint32_t id) noexcept {
|
|||
if (id == 0) queue0Dormant.store(false, std::memory_order_relaxed);
|
||||
break;
|
||||
}
|
||||
if (changed.wait_for(lock, std::chrono::milliseconds(1), ready)) break;
|
||||
if (poll(ready)) break;
|
||||
lock.unlock();
|
||||
reapCompletionLabels();
|
||||
lock.lock();
|
||||
}
|
||||
timing.Mark("idle_and_completion_poll");
|
||||
rethrowFailure();
|
||||
if (stopping || shutdownToken.stop_requested() || pending.empty()) {
|
||||
break;
|
||||
}
|
||||
submission = std::move(pending.front());
|
||||
pending.pop_front();
|
||||
if (APS5_ENABLE_TIMING_LOG) submission.dequeuedAt = std::chrono::steady_clock::now();
|
||||
if (id == 0) queue0Executing = submission.suspend ? 0 : submission.received;
|
||||
if (submission.waitFree) {
|
||||
orderHolders.fetch_add(1, std::memory_order_acq_rel);
|
||||
while (!failure && !stopping && !orderReleased(id, submission.received)) changed.wait_for(lock, std::chrono::milliseconds(1));
|
||||
const auto released = [&] { return failure || stopping || orderReleased(id, submission.received); };
|
||||
while (!released()) poll(released);
|
||||
orderHolders.fetch_sub(1, std::memory_order_acq_rel);
|
||||
rethrowFailure();
|
||||
}
|
||||
if (APS5_ENABLE_TIMING_LOG) submission.orderedAt = std::chrono::steady_clock::now();
|
||||
timing.Mark("dequeue_order");
|
||||
worker.queued.fetch_sub(1, std::memory_order_acq_rel);
|
||||
if (profile && submission.enqueuedAt != std::chrono::steady_clock::time_point{}) costs.dequeueNs += static_cast<std::uint64_t>(std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::steady_clock::now() - submission.enqueuedAt).count());
|
||||
}
|
||||
|
|
@ -97,6 +145,8 @@ void Driver::run(std::uint32_t id) noexcept {
|
|||
}
|
||||
if (traceGpu) std::fprintf(stderr, "[gpu] %.1f done serial=%llu queue=0x%x\n", TraceMs(), static_cast<unsigned long long>(submission.serial), id);
|
||||
const auto completeStart = profile ? std::chrono::steady_clock::now() : std::chrono::steady_clock::time_point{};
|
||||
PerformanceContext completionContext(id == 0 ? frameTiming() : FrameTiming::Async());
|
||||
PerformanceTimer completionTiming("Driver.Completion");
|
||||
bool notify = true;
|
||||
{
|
||||
std::lock_guard lock(mutex);
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
#include "prx/libSceAgcDriver/Execution/include/PerformanceTimer.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/Driver/Queues/Submission.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/Driver/Diagnostics.hpp"
|
||||
|
|
@ -103,6 +104,19 @@ bool Driver::copySegment(Submission& submission, const std::uint32_t* guest, std
|
|||
return false;
|
||||
}
|
||||
|
||||
void Driver::readRegisterLists(Submission& submission) {
|
||||
submission.registerLists.clear();
|
||||
for (std::size_t cursor = 0; cursor < submission.commands.size(); cursor += Pm4::PacketWords(submission.commands[cursor])) {
|
||||
const auto header = submission.commands[cursor];
|
||||
if ((header >> 30u) != 3u || !Pm4::IndirectRegisterOpcode((header >> 8u) & 0xffu)) continue;
|
||||
try {
|
||||
submission.registerLists.emplace(cursor, Pm4::ReadIndirectRegisters(std::span<const std::uint32_t>(submission.commands).subspan(cursor, Pm4::PacketWords(header))));
|
||||
} catch (const std::exception& error) {
|
||||
throw std::runtime_error("AGC driver: " + Pm4::Name(header) + " at DWORD " + std::to_string(cursor) + ": " + error.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Driver::waitForFlipRoom(const Submission& submission) {
|
||||
for (std::size_t cursor = 0; cursor < submission.commands.size(); cursor += Pm4::PacketWords(submission.commands[cursor])) {
|
||||
if (submission.commands[cursor] != FlipPacketHeader) continue;
|
||||
|
|
@ -141,6 +155,7 @@ void Driver::reserveOutputs(Submission& submission) {
|
|||
}
|
||||
|
||||
void Driver::executeRewindTail(const Submission& stalled) {
|
||||
PerformanceTimer timing("Driver.Rewind");
|
||||
std::atomic_ref<std::uint32_t> control(*const_cast<std::uint32_t*>(stalled.rewindTail - 1));
|
||||
if ((control.load(std::memory_order_acquire) & 0x80000000u) == 0) {
|
||||
noteWaitBlocked(stalled.queue, reinterpret_cast<std::uint64_t>(stalled.rewindTail - 1), true);
|
||||
|
|
@ -157,9 +172,14 @@ void Driver::executeRewindTail(const Submission& stalled) {
|
|||
}
|
||||
Submission tail{};
|
||||
tail.queue = stalled.queue;
|
||||
if (APS5_ENABLE_TIMING_LOG) tail.receivedAt = std::chrono::steady_clock::now();
|
||||
copyCommands(tail, stalled.rewindTail, stalled.rewindWords);
|
||||
if (APS5_ENABLE_TIMING_LOG) tail.copiedAt = std::chrono::steady_clock::now();
|
||||
validate(tail, stalled.rewindTail);
|
||||
readRegisterLists(tail);
|
||||
if (APS5_ENABLE_TIMING_LOG) tail.validatedAt = std::chrono::steady_clock::now();
|
||||
waitForFlipRoom(tail);
|
||||
if (APS5_ENABLE_TIMING_LOG) tail.roomReadyAt = std::chrono::steady_clock::now();
|
||||
{
|
||||
std::lock_guard lock(mutex);
|
||||
rethrowFailure();
|
||||
|
|
@ -169,10 +189,13 @@ void Driver::executeRewindTail(const Submission& stalled) {
|
|||
tail.serial = stalled.serial;
|
||||
tail.received = ++eventSerial;
|
||||
}
|
||||
if (APS5_ENABLE_TIMING_LOG) tail.enqueuedAt = tail.dequeuedAt = tail.orderedAt = std::chrono::steady_clock::now();
|
||||
timing.Finish();
|
||||
execute(tail);
|
||||
}
|
||||
|
||||
void Driver::Submit(const Packet* packet, std::uint32_t queue) {
|
||||
const auto receivedAt = APS5_ENABLE_TIMING_LOG ? std::chrono::steady_clock::now() : std::chrono::steady_clock::time_point{};
|
||||
CheckFailure();
|
||||
require(queue == 0 || (queue >= 0x20 && queue < 0x58), "unsupported compute queue");
|
||||
GuestMemory::CheckRange(packet, sizeof(Packet), alignof(Packet));
|
||||
|
|
@ -180,6 +203,7 @@ void Driver::Submit(const Packet* packet, std::uint32_t queue) {
|
|||
require(descriptor.flags == 0, "nonzero submission flags are not implemented");
|
||||
Submission submission{};
|
||||
submission.queue = queue;
|
||||
submission.receivedAt = receivedAt;
|
||||
static const bool profile = std::getenv("APS5_PROFILE_DRAW") != nullptr;
|
||||
auto& costs = submissionCosts(queue);
|
||||
const auto start = profile ? std::chrono::steady_clock::now() : std::chrono::steady_clock::time_point{};
|
||||
|
|
@ -189,8 +213,12 @@ void Driver::Submit(const Packet* packet, std::uint32_t queue) {
|
|||
copyCommands(submission, descriptor.addr, descriptor.dw_num);
|
||||
}
|
||||
const auto copied = profile ? std::chrono::steady_clock::now() : start;
|
||||
if (APS5_ENABLE_TIMING_LOG) submission.copiedAt = std::chrono::steady_clock::now();
|
||||
validate(submission, descriptor.addr);
|
||||
readRegisterLists(submission);
|
||||
if (APS5_ENABLE_TIMING_LOG) submission.validatedAt = std::chrono::steady_clock::now();
|
||||
waitForFlipRoom(submission);
|
||||
if (APS5_ENABLE_TIMING_LOG) submission.roomReadyAt = std::chrono::steady_clock::now();
|
||||
static const bool trace = std::getenv("APS5_TRACE_GPU") != nullptr;
|
||||
if (trace) std::fprintf(stderr, "[gpu] %.1f submit queue=0x%x dwords=%zu at %p\n", TraceMs(), queue, submission.commands.size(), static_cast<const void*>(descriptor.addr));
|
||||
const auto validated = profile ? std::chrono::steady_clock::now() : start;
|
||||
|
|
@ -211,6 +239,7 @@ void Driver::Submit(const Packet* packet, std::uint32_t queue) {
|
|||
costs.validateNs += static_cast<std::uint64_t>(std::chrono::duration_cast<std::chrono::nanoseconds>(validated - copied).count());
|
||||
costs.copyNs += static_cast<std::uint64_t>(std::chrono::duration_cast<std::chrono::nanoseconds>((copied - start) + (now - validated)).count());
|
||||
}
|
||||
if (APS5_ENABLE_TIMING_LOG) submission.enqueuedAt = std::chrono::steady_clock::now();
|
||||
enqueue(std::move(submission));
|
||||
++accepted;
|
||||
}
|
||||
|
|
@ -218,6 +247,7 @@ void Driver::Submit(const Packet* packet, std::uint32_t queue) {
|
|||
}
|
||||
|
||||
void Driver::SuspendPoint() {
|
||||
const auto receivedAt = APS5_ENABLE_TIMING_LOG ? std::chrono::steady_clock::now() : std::chrono::steady_clock::time_point{};
|
||||
require(!onWorkerThread(), "worker cannot suspend itself");
|
||||
std::unique_lock lock(mutex);
|
||||
rethrowFailure();
|
||||
|
|
@ -228,6 +258,7 @@ void Driver::SuspendPoint() {
|
|||
boundary.suspend = true;
|
||||
|
||||
boundary.queue = 0;
|
||||
boundary.receivedAt = receivedAt;
|
||||
boundary.enqueuedAt = std::chrono::steady_clock::now();
|
||||
enqueue(std::move(boundary));
|
||||
++accepted;
|
||||
|
|
|
|||
|
|
@ -10,7 +10,9 @@ std::uint64_t WorkerAffinityMask() {
|
|||
if (std::getenv("APS5_NO_WORKER_AFFINITY") != nullptr) return std::uint64_t{0};
|
||||
const auto requested = CpuTopology::MaskFromEnvironment("APS5_WORKER_AFFINITY_MASK");
|
||||
if (requested != 0) return requested;
|
||||
#ifndef __linux__
|
||||
if (std::getenv("APS5_WORKER_AFFINITY") == nullptr) return std::uint64_t{0};
|
||||
#endif
|
||||
const auto& layout = CpuTopology::Get();
|
||||
return layout.hybrid ? layout.performant : std::uint64_t{0};
|
||||
}();
|
||||
|
|
|
|||
|
|
@ -1,8 +1,15 @@
|
|||
#include "prx/libc/include/general/LogMacros.hpp"
|
||||
#include "ControlFlow/GraphBuilder.hpp"
|
||||
#include "ControlFlow/Structurizer.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/PerformanceTimer.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/Driver/Diagnostics.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
|
||||
#include "CacheKey.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/ShaderPreparation.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/ShaderPreparationScope.hpp"
|
||||
#include "CompiledVariant.hpp"
|
||||
#include "Optimization/ResourceProgram.hpp"
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
|
@ -78,71 +85,458 @@ std::shared_ptr<const ShaderSnapshot> ReadRawComputeShader(std::uint64_t address
|
|||
throw std::runtime_error("AGC driver: raw compute program has no reachable end within mapped code or the size limit");
|
||||
}
|
||||
|
||||
bool FailureMemo() {
|
||||
static const bool memo = std::getenv("APS5_NO_FAILURE_MEMO") == nullptr;
|
||||
return memo;
|
||||
|
||||
struct ShaderPreparationTransaction::State {
|
||||
struct Change {
|
||||
std::shared_ptr<PreparedShaders> destination;
|
||||
PreparedShaderState prepared;
|
||||
};
|
||||
static std::mutex writers;
|
||||
static thread_local State* active;
|
||||
std::unique_lock<std::mutex> lock{writers};
|
||||
std::map<PreparedShaders*, Change> changes;
|
||||
bool failed = false;
|
||||
};
|
||||
|
||||
std::mutex ShaderPreparationTransaction::State::writers;
|
||||
thread_local ShaderPreparationTransaction::State* ShaderPreparationTransaction::State::active = nullptr;
|
||||
|
||||
ShaderPreparationTransaction::ShaderPreparationTransaction() {
|
||||
if (State::active == nullptr) {
|
||||
state = std::make_unique<State>();
|
||||
State::active = state.get();
|
||||
}
|
||||
root = State::active;
|
||||
}
|
||||
|
||||
std::shared_ptr<const ShaderRecompiler::SourceHandle> SourceHandleFor(const ShaderSnapshot& snapshot, std::size_t codeOffset, std::uint64_t deviceSerial, const ShaderRecompiler::RecompileRequest& request, bool bypass, const std::string** poisoned) {
|
||||
static const bool enabled = std::getenv("APS5_NO_SOURCE_HANDLE_CACHE") == nullptr && std::getenv("APS5_NO_CAPTURE_REUSE") == nullptr;
|
||||
static const bool verify = std::getenv("APS5_VERIFY_SOURCE_HANDLE") != nullptr;
|
||||
if (!enabled || bypass || ShaderRecompiler::DebugProbeActive() || !request.useCache) return nullptr;
|
||||
std::uint64_t key = 0xcbf29ce484222325ull;
|
||||
for (const auto value : {static_cast<std::uint64_t>(codeOffset), deviceSerial, ShaderRecompiler::RecompileCacheKey::ContextHash(request)}) {
|
||||
key ^= value;
|
||||
key *= 0x100000001b3ull;
|
||||
ShaderPreparationTransaction::~ShaderPreparationTransaction() {
|
||||
if (!committed) root->failed = true;
|
||||
if (state != nullptr) State::active = nullptr;
|
||||
}
|
||||
|
||||
PreparedShaderState& ShaderPreparationTransaction::Edit(const ShaderSnapshot& snapshot) {
|
||||
require(!committed, "shader preparation transaction is already committed");
|
||||
auto& changes = root->changes;
|
||||
const auto found = changes.find(snapshot.prepared.get());
|
||||
if (found != changes.end()) return found->second.prepared;
|
||||
std::lock_guard lock(snapshot.prepared->mutex);
|
||||
return changes.emplace(snapshot.prepared.get(), State::Change{snapshot.prepared, *snapshot.prepared}).first->second.prepared;
|
||||
}
|
||||
|
||||
const PreparedShaderState& ShaderPreparationTransaction::Read(const ShaderSnapshot& snapshot) const {
|
||||
require(!committed, "shader preparation transaction is already committed");
|
||||
const auto found = root->changes.find(snapshot.prepared.get());
|
||||
return found != root->changes.end() ? found->second.prepared : *snapshot.prepared;
|
||||
}
|
||||
|
||||
void ShaderPreparationTransaction::Commit() {
|
||||
require(!committed && !root->failed, "shader preparation transaction was aborted");
|
||||
if (state != nullptr) {
|
||||
std::vector<std::unique_lock<std::mutex>> locks;
|
||||
locks.reserve(state->changes.size());
|
||||
for (const auto& [key, change] : state->changes) locks.emplace_back(change.destination->mutex);
|
||||
for (auto& [key, change] : state->changes) {
|
||||
auto& destination = *change.destination;
|
||||
destination.entries.swap(change.prepared.entries);
|
||||
destination.registeredAbis.swap(change.prepared.registeredAbis);
|
||||
destination.graphicsAbis.swap(change.prepared.graphicsAbis);
|
||||
destination.rectangles.swap(change.prepared.rectangles);
|
||||
destination.rectangleProgress.swap(change.prepared.rectangleProgress);
|
||||
destination.fragments.swap(change.prepared.fragments);
|
||||
std::swap(destination.rectangleRequested, change.prepared.rectangleRequested);
|
||||
}
|
||||
}
|
||||
if (request.graphics.has_value()) {
|
||||
for (const auto& linked : request.graphics->linkedPrograms) {
|
||||
for (const auto value : {static_cast<std::uint64_t>(linked.role), linked.binary.codeAddress}) {
|
||||
key ^= value;
|
||||
key *= 0x100000001b3ull;
|
||||
committed = true;
|
||||
}
|
||||
|
||||
void PublishRegisteredShader(std::shared_ptr<ShaderRegistry>& registry, const std::shared_ptr<const ShaderSnapshot>& snapshot) {
|
||||
ShaderPreparationTransaction transaction;
|
||||
if (registry != nullptr) {
|
||||
const auto found = registry->find(snapshot->codeAddress);
|
||||
if (found != registry->end()) {
|
||||
const auto& current = *found->second;
|
||||
if (current.headerAddress == snapshot->headerAddress && current.type == snapshot->type && current.code == snapshot->code && current.header == snapshot->header) {
|
||||
transaction.Commit();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
auto& memos = *snapshot.handles;
|
||||
auto next = registry != nullptr ? std::make_shared<ShaderRegistry>(*registry) : std::make_shared<ShaderRegistry>();
|
||||
next->insert_or_assign(snapshot->codeAddress, snapshot);
|
||||
registry = std::move(next);
|
||||
transaction.Commit();
|
||||
}
|
||||
|
||||
std::shared_ptr<const ShaderRecompiler::SourceHandle> SourceHandleFor(const ShaderSnapshot& snapshot, std::size_t codeOffset, const ShaderRecompiler::RecompileRequest& request) {
|
||||
struct PreparedKeyStorage {};
|
||||
auto& key = HostThreadLocal<std::vector<std::uint64_t>, PreparedKeyStorage>();
|
||||
ShaderRecompiler::BuildPreparedShaderKey(request, key);
|
||||
std::lock_guard lock(snapshot.prepared->mutex);
|
||||
for (const auto& entry : snapshot.prepared->entries) {
|
||||
if (entry.codeOffset == codeOffset && ShaderRecompiler::MatchesPreparedShader(request, *entry.handle, key)) return entry.handle;
|
||||
}
|
||||
if (snapshot.header.empty()) {
|
||||
if (snapshot.type != 0 || request.shader.stage != ShaderRecompiler::ShaderStage::Compute) throw std::runtime_error("AGC driver: unregistered program is not a compute shader");
|
||||
APS5_LOG_ERR("Compute shader 0x%llx was not registered; preparing its artifact at dispatch", static_cast<unsigned long long>(snapshot.codeAddress));
|
||||
auto handle = ShaderRecompiler::PrepareShader(request);
|
||||
snapshot.prepared->entries.push_back({codeOffset, handle});
|
||||
return handle;
|
||||
}
|
||||
std::string layouts;
|
||||
for (const auto& entry : snapshot.prepared->entries) {
|
||||
if (entry.codeOffset != codeOffset || entry.handle == nullptr || entry.handle->artifact == nullptr) continue;
|
||||
const auto& layout = entry.handle->artifact->layout;
|
||||
layouts += " [" + std::to_string(layout.pushConstantOffsetBytes) + "," + std::to_string(layout.pushConstantSizeBytes) + "]";
|
||||
}
|
||||
throw std::runtime_error("AGC driver: prepared shader artifact is missing for the requested static ABI: address=" + std::to_string(request.shader.codeAddress) + " stage=" + std::to_string(static_cast<std::uint32_t>(request.shader.stage)) + " wave=" + std::to_string(request.context.waveSize) + " pushOffset=" + std::to_string(request.layout.pushConstantOffsetBytes) + " pushCapacity=" + std::to_string(request.layout.pushConstantSizeBytes) + " preparedLayouts=" + layouts);
|
||||
}
|
||||
|
||||
ShaderRecompiler::RectListShaders PreparedRectangle(const ShaderSnapshot& snapshot, std::uint64_t vertexId, std::uint64_t fragmentId) {
|
||||
std::lock_guard lock(snapshot.prepared->mutex);
|
||||
for (const auto& entry : snapshot.prepared->rectangles) {
|
||||
if (entry.vertexId == vertexId && entry.fragmentId == fragmentId) {
|
||||
require(entry.shaders != nullptr, "prepared rectangle artifact is missing");
|
||||
return *entry.shaders;
|
||||
}
|
||||
}
|
||||
throw std::runtime_error("AGC driver: prepared rectangle artifacts are missing");
|
||||
}
|
||||
|
||||
ShaderRecompiler::PreparedShaderInvocation InvocationFor(const ShaderSnapshot& snapshot, std::size_t codeOffset, const ShaderRecompiler::RecompileRequest& request) {
|
||||
require(codeOffset < snapshot.code.size(), "prepared shader code offset is outside the snapshot");
|
||||
const auto code = std::span(snapshot.code).subspan(codeOffset);
|
||||
require(request.shader.code.data() == code.data() && request.shader.code.size() == code.size(), "prepared invocation does not refer to registered code");
|
||||
auto invocationRequest = request;
|
||||
struct PreparedKeyStorage {};
|
||||
auto& key = HostThreadLocal<std::vector<std::uint64_t>, PreparedKeyStorage>();
|
||||
ShaderRecompiler::BuildPreparedShaderKey(request, key);
|
||||
std::lock_guard lock(snapshot.prepared->mutex);
|
||||
for (const auto& entry : snapshot.prepared->entries) {
|
||||
if (entry.codeOffset != codeOffset) continue;
|
||||
invocationRequest.shader.code = ShaderRecompiler::GetPreparedCode(*entry.handle);
|
||||
if (auto invocation = ShaderRecompiler::PreparedShaderInvocation::TryCreate(invocationRequest, entry.handle, key)) return std::move(*invocation);
|
||||
}
|
||||
if (snapshot.header.empty()) {
|
||||
if (snapshot.type != 0 || request.shader.stage != ShaderRecompiler::ShaderStage::Compute) throw std::runtime_error("AGC driver: unregistered program is not a compute shader");
|
||||
APS5_LOG_ERR("Compute shader 0x%llx was not registered; preparing its artifact at dispatch", static_cast<unsigned long long>(snapshot.codeAddress));
|
||||
auto handle = ShaderRecompiler::PrepareShader(request);
|
||||
invocationRequest = request;
|
||||
invocationRequest.shader.code = ShaderRecompiler::GetPreparedCode(*handle);
|
||||
auto invocation = ShaderRecompiler::PreparedShaderInvocation::TryCreate(invocationRequest, handle, key);
|
||||
if (!invocation.has_value()) throw std::runtime_error("AGC driver: raw compute artifact does not match its invocation");
|
||||
snapshot.prepared->entries.push_back({codeOffset, std::move(handle)});
|
||||
return std::move(*invocation);
|
||||
}
|
||||
std::string layouts;
|
||||
for (const auto& entry : snapshot.prepared->entries) {
|
||||
if (entry.codeOffset != codeOffset || entry.handle == nullptr || entry.handle->artifact == nullptr) continue;
|
||||
const auto& layout = entry.handle->artifact->layout;
|
||||
layouts += " [" + std::to_string(layout.pushConstantOffsetBytes) + "," + std::to_string(layout.pushConstantSizeBytes) + "]";
|
||||
}
|
||||
throw std::runtime_error("AGC driver: prepared shader artifact is missing for the requested static ABI: address=" + std::to_string(request.shader.codeAddress) + " stage=" + std::to_string(static_cast<std::uint32_t>(request.shader.stage)) + " wave=" + std::to_string(request.context.waveSize) + " pushOffset=" + std::to_string(request.layout.pushConstantOffsetBytes) + " pushCapacity=" + std::to_string(request.layout.pushConstantSizeBytes) + " preparedLayouts=" + layouts);
|
||||
}
|
||||
namespace {
|
||||
|
||||
template<typename TValue>
|
||||
std::vector<TValue> ReadHeaderArray(const ShaderSnapshot& snapshot, const TValue* pointer, std::size_t count) {
|
||||
if (count == 0) return {};
|
||||
const auto address = reinterpret_cast<std::uintptr_t>(pointer);
|
||||
if (address < snapshot.headerAddress || address - snapshot.headerAddress > snapshot.header.size()) throw std::runtime_error("AGC driver: shader metadata is outside the registered header");
|
||||
const auto offset = static_cast<std::size_t>(address - snapshot.headerAddress);
|
||||
if (count > (snapshot.header.size() - offset) / sizeof(TValue)) throw std::runtime_error("AGC driver: truncated shader metadata");
|
||||
std::vector<TValue> result(count);
|
||||
std::memcpy(result.data(), snapshot.header.data() + offset, count * sizeof(TValue));
|
||||
return result;
|
||||
}
|
||||
|
||||
Shader ReadHeader(const ShaderSnapshot& snapshot) {
|
||||
Shader header;
|
||||
std::memcpy(&header, snapshot.header.data(), sizeof(header));
|
||||
return header;
|
||||
}
|
||||
|
||||
RegisteredShaderState DecodeRegisteredState(const ShaderSnapshot& snapshot) {
|
||||
const auto header = ReadHeader(snapshot);
|
||||
RegisteredShaderState state{{}, InitialContextRegisters(), {{0x24a, 0}, {0x24b, 0}}};
|
||||
for (const auto reg : ReadHeaderArray(snapshot, header.sh_registers, header.num_sh_registers)) {
|
||||
state.shader.insert_or_assign(reg.offset, reg.value);
|
||||
}
|
||||
for (const auto reg : ReadHeaderArray(snapshot, header.cx_registers, header.num_cx_registers)) {
|
||||
state.context.insert_or_assign(reg.offset, reg.value);
|
||||
}
|
||||
if (header.specials != nullptr) {
|
||||
const auto special = ReadHeaderArray(snapshot, header.specials, 1).front();
|
||||
state.context[special.vgt_shader_stages_en.offset] = special.vgt_shader_stages_en.value;
|
||||
state.context[special.vgt_gs_out_prim_type.offset] = special.vgt_gs_out_prim_type.value;
|
||||
state.userConfig[special.ge_cntl.offset] = special.ge_cntl.value;
|
||||
state.userConfig[special.ge_user_vgpr_en.offset] = special.ge_user_vgpr_en.value;
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
std::uint32_t RegisterValue(const Registers& registers, std::uint32_t offset) {
|
||||
const auto found = registers.find(offset);
|
||||
if (found == registers.end()) throw std::runtime_error("AGC driver: missing static shader ABI register " + std::to_string(offset));
|
||||
return found->second;
|
||||
}
|
||||
|
||||
void BuildRegisteredAbiKey(const QueueState& state, const VulkanDevice& device, std::vector<std::uint64_t>& key) {
|
||||
key.clear();
|
||||
key.reserve(7u + state.shader.size() + state.context.size() + state.userConfig.size());
|
||||
key.insert(key.end(), {device.Serial(), ShaderRecompiler::DebugProbeActive(), ShaderRecompiler::RayTracingStrict(), ShaderRecompiler::RayTracingMiss()});
|
||||
for (const auto* registers : {&state.shader, &state.context, &state.userConfig}) {
|
||||
key.push_back(registers->size());
|
||||
for (const auto& [offset, value] : *registers) key.push_back((static_cast<std::uint64_t>(offset) << 32u) | value);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<PreparedShaders::Entry> PrepareRegistered(const ShaderSnapshot& snapshot, const VulkanDevice& device, const QueueState& state, bool registration) {
|
||||
using Stage = ShaderRecompiler::ShaderStage;
|
||||
const auto header = ReadHeader(snapshot);
|
||||
std::uint32_t programRegister;
|
||||
std::uint32_t resourceRegister;
|
||||
std::uint32_t firstUser = 0;
|
||||
Stage stage;
|
||||
switch (snapshot.type) {
|
||||
case 0: stage = Stage::Compute; programRegister = 0x20c; resourceRegister = 0x213; break;
|
||||
case 1: stage = Stage::Fragment; programRegister = 0x008; resourceRegister = 0x00b; break;
|
||||
case 2: stage = Stage::Vertex; programRegister = 0x0c8; resourceRegister = 0x08b; firstUser = 8; break;
|
||||
case 4: stage = Stage::Mesh; programRegister = 0x0c8; resourceRegister = 0x08b; break;
|
||||
case 5: stage = Stage::Local; programRegister = 0x148; resourceRegister = 0x10b; firstUser = 8; break;
|
||||
case 6: stage = Stage::Mesh; programRegister = 0x088; resourceRegister = 0x08b; break;
|
||||
case 7: stage = Stage::TessellationControl; programRegister = 0x108; resourceRegister = 0x10b; break;
|
||||
default: throw std::runtime_error("AGC driver: unsupported registered shader type");
|
||||
}
|
||||
const auto high = RegisterValue(state.shader, programRegister + 1);
|
||||
if ((high & ~0xffu) != 0) throw std::runtime_error("AGC driver: invalid registered program address");
|
||||
const auto address = (static_cast<std::uint64_t>(RegisterValue(state.shader, programRegister)) << 8u) | (static_cast<std::uint64_t>(high) << 40u);
|
||||
if (address < snapshot.codeAddress || address - snapshot.codeAddress >= snapshot.code.size() * 4u) throw std::runtime_error("AGC driver: registered entry point is outside shader code");
|
||||
const auto codeOffset = static_cast<std::size_t>((address - snapshot.codeAddress) / 4u);
|
||||
const auto code = std::span(snapshot.code).subspan(codeOffset);
|
||||
const auto decoded = ShaderRecompiler::RdnaInstructionDecoder{}.Decode(code);
|
||||
std::optional<ShaderRecompiler::ShaderComputeStageInfo> compute;
|
||||
std::optional<ShaderRecompiler::ShaderPixelStageInfo> pixel;
|
||||
std::optional<ShaderRecompiler::ShaderVertexStageInfo> vertex;
|
||||
std::optional<ShaderRecompiler::GraphicsCompileContext> graphics;
|
||||
std::uint32_t wave;
|
||||
const auto resources = RegisterValue(state.shader, resourceRegister);
|
||||
auto userCount = (resources >> 1u) & 0x1fu;
|
||||
if (stage != Stage::Compute) userCount |= ((resources >> 27u) & 1u) << 5u;
|
||||
if (userCount > 32) throw std::runtime_error("AGC driver: static user SGPR count exceeds the register bank");
|
||||
if (stage == Stage::Compute) {
|
||||
compute = Graphics::DecodeComputeStageInfo(state.shader, snapshot.header);
|
||||
const auto special = ReadHeaderArray(snapshot, header.specials, 1).front();
|
||||
wave = (special.dispatch_modifier & 0x8000u) != 0 ? 32u : 64u;
|
||||
} else if (stage == Stage::Fragment) {
|
||||
const auto count = RegisterValue(state.context, 0x1b6) & 0x3fu;
|
||||
if (registration && count != 0) return {};
|
||||
pixel = Graphics::DecodePixelStageInfo(state.context, {});
|
||||
wave = pixel->wave32 ? 32u : 64u;
|
||||
} else {
|
||||
const auto routing = RegisterValue(state.context, 0x2d5);
|
||||
wave = (routing & 0x00400000u) != 0 ? 32u : 64u;
|
||||
if ((routing & 0x20u) != 0 || stage == Stage::Mesh || (routing & 4u) != 0) {
|
||||
if (registration) return {};
|
||||
auto stageState = state;
|
||||
stageState.context[0x1b6] = 0;
|
||||
const auto stages = Graphics::DecodeShaderStages(stageState);
|
||||
graphics = ShaderRecompiler::GraphicsCompileContext{0, {}, stages.mesh, stages.tessellation, {}};
|
||||
if (stages.mesh) { stage = Stage::Mesh; firstUser = 0; }
|
||||
if (stages.tessellation && snapshot.type == 2) stage = Stage::TessellationEvaluation;
|
||||
}
|
||||
if (firstUser == 0) userCount += 8;
|
||||
}
|
||||
std::vector<std::uint32_t> userData(userCount);
|
||||
if (stage != Stage::Compute && stage != Stage::Fragment && snapshot.type != 6) vertex = Graphics::DecodeVertexStageInfo(snapshot.header, snapshot.headerAddress, userData, nullptr, true);
|
||||
const ShaderRecompiler::SwappcInfo swappc{vertex.has_value(), firstUser, userCount};
|
||||
auto graph = ShaderRecompiler::GraphBuilder{}.Build(decoded, &swappc);
|
||||
ShaderRecompiler::Structurizer{}.Structurize(graph);
|
||||
const std::array<ShaderRecompiler::MemoryRegion, 2> memory{{{snapshot.codeAddress, std::as_bytes(std::span(snapshot.code))}, {snapshot.headerAddress, snapshot.header}}};
|
||||
ShaderRecompiler::RecompileRequest request{{stage, address, code, snapshot.headerAddress, snapshot.header}, {wave, firstUser, userData, compute, pixel, vertex, memory}, stage == Stage::Compute ? device.ComputeTarget(wave) : device.Target(), {0, 0, 0, 128}, graphics};
|
||||
if (graphics && graphics->mesh) request.layout.pushConstantSizeBytes = ShaderRecompiler::MeshDrawPushOffsetBytes;
|
||||
std::vector<PreparedShaders::Entry> entries;
|
||||
const auto append = [&] {
|
||||
PerformanceTimer timing("Shader.PrepareArtifact");
|
||||
entries.push_back({codeOffset, ShaderRecompiler::PrepareShader(request)});
|
||||
};
|
||||
append();
|
||||
if (compute) {
|
||||
request.context.compute->partialThreads = {1, 1, 1};
|
||||
append();
|
||||
} else if (pixel) {
|
||||
request.layout.pushConstantSizeBytes = 0;
|
||||
append();
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
std::vector<PreparedGraphicsStage> PrepareGraphicsStages(const DrawDecode& decoded, const ShaderRecompiler::SpirvTarget& target) {
|
||||
ShaderPreparationTransaction transaction;
|
||||
require(decoded.programs.size() == decoded.roles.size(), "graphics ABI program roles are incomplete");
|
||||
for (const auto& program : decoded.programs) require(program.snapshot != nullptr, "graphics ABI has no registered shader snapshot");
|
||||
std::vector<ShaderRecompiler::ProgramRole> expected;
|
||||
if (decoded.state.stages.path == Graphics::ShaderPath::Tessellation) expected = {ShaderRecompiler::ProgramRole::Local, ShaderRecompiler::ProgramRole::Hull, ShaderRecompiler::ProgramRole::Domain};
|
||||
else {
|
||||
expected.push_back(ShaderRecompiler::ProgramRole::Main);
|
||||
if (decoded.state.stages.path == Graphics::ShaderPath::Geometry && !decoded.programs.empty() && decoded.programs.front().snapshot->type == 4) expected.push_back(ShaderRecompiler::ProgramRole::GeometryBack);
|
||||
}
|
||||
if (!decoded.roles.empty() && decoded.roles.back() == ShaderRecompiler::ProgramRole::Fragment) expected.push_back(ShaderRecompiler::ProgramRole::Fragment);
|
||||
require(expected == decoded.roles, "graphics ABI linked programs do not match the stage routing");
|
||||
std::vector<ShaderRecompiler::LinkedProgram> linked;
|
||||
std::vector<ShaderRecompiler::MemoryRegion> memory;
|
||||
for (std::size_t index = 0; index < decoded.programs.size(); ++index) {
|
||||
const auto& program = decoded.programs[index];
|
||||
require(program.snapshot != nullptr && program.codeOffset < program.snapshot->code.size(), "graphics ABI has an invalid registered entry point");
|
||||
const auto code = std::span(program.snapshot->code).subspan(program.codeOffset);
|
||||
require(program.binary.code.data() == code.data() && program.binary.code.size() == code.size() && program.binary.codeAddress == program.snapshot->codeAddress + program.codeOffset * sizeof(std::uint32_t), "graphics ABI entry point differs from its snapshot");
|
||||
linked.push_back({decoded.roles[index], program.binary, program.userDataBase, program.firstUserSgpr, program.userData});
|
||||
memory.insert(memory.end(), program.memory.begin(), program.memory.end());
|
||||
}
|
||||
std::vector<PreparedGraphicsStage> prepared;
|
||||
std::uint32_t pushOffset = 0;
|
||||
const auto capacity = decoded.state.stages.mesh ? ShaderRecompiler::MeshDrawPushOffsetBytes : Graphics::PipelinePushConstantBytes;
|
||||
for (std::size_t index = 0; index < decoded.programs.size(); ++index) {
|
||||
if (decoded.roles[index] == ShaderRecompiler::ProgramRole::GeometryBack) continue;
|
||||
const auto& program = decoded.programs[index];
|
||||
const bool fragment = program.binary.stage == ShaderRecompiler::ShaderStage::Fragment;
|
||||
const auto wave = fragment ? decoded.state.stages.fragmentWaveSize : decoded.state.stages.vertexWaveSize;
|
||||
std::optional<ShaderRecompiler::ShaderVertexStageInfo> vertex;
|
||||
if (!fragment) vertex = Graphics::DecodeVertexStageInfo(program.binary.header, program.binary.headerAddress, program.userData, nullptr, true);
|
||||
ShaderRecompiler::RecompileRequest request{program.binary, {wave, program.firstUserSgpr, program.userData, {}, fragment ? std::optional(decoded.pixel) : std::nullopt, vertex, memory}, target, {0, 0, pushOffset, capacity - pushOffset}, ShaderRecompiler::GraphicsCompileContext{program.firstUserSgpr, linked, decoded.state.stages.mesh, decoded.state.stages.tessellation, {}}};
|
||||
std::vector<std::uint64_t> key;
|
||||
ShaderRecompiler::BuildPreparedShaderKey(request, key);
|
||||
std::shared_ptr<const ShaderRecompiler::SourceHandle> handle;
|
||||
for (const auto& entry : transaction.Read(*program.snapshot).entries) {
|
||||
if (entry.codeOffset == program.codeOffset && ShaderRecompiler::MatchesPreparedShader(request, *entry.handle, key)) {
|
||||
handle = entry.handle;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (handle == nullptr) {
|
||||
PerformanceTimer timing("Shader.PrepareArtifact");
|
||||
handle = ShaderRecompiler::PrepareShader(request);
|
||||
}
|
||||
const auto bytes = handle->artifact->bindings.pushConstantSizeBytes;
|
||||
require(bytes <= capacity - pushOffset, "prepared graphics stages exceed the push constant block");
|
||||
prepared.push_back({program.snapshot, {program.codeOffset, handle}});
|
||||
pushOffset += bytes;
|
||||
}
|
||||
transaction.Commit();
|
||||
return prepared;
|
||||
}
|
||||
|
||||
void Driver::ResolveGraphicsStagesAbi(std::span<const Shader* const> stages, std::span<const ShaderRegister> context, std::span<const ShaderRegister> primitive) {
|
||||
PerformanceContext timingContext(FrameTiming::Preparation());
|
||||
PerformanceTimer timing("Shader.ResolveGraphicsStagesAbi");
|
||||
ShaderPreparationTransaction transaction;
|
||||
CheckFailure();
|
||||
require(!stages.empty(), "graphics ABI has no shader headers");
|
||||
QueueState state{};
|
||||
std::shared_ptr<const ShaderRegistry> registry;
|
||||
std::shared_ptr<const ShaderSnapshot> owner;
|
||||
{
|
||||
std::lock_guard lock(memos.mutex);
|
||||
for (const auto& entry : memos.entries) {
|
||||
if (entry.key != key || (entry.handle == nullptr && entry.failure == nullptr)) continue;
|
||||
if (entry.handle == nullptr) {
|
||||
if (verify) {
|
||||
bool resolved = true;
|
||||
try {
|
||||
static_cast<void>(ShaderRecompiler::ResolveSource(request));
|
||||
} catch (const std::exception&) {
|
||||
resolved = false;
|
||||
}
|
||||
if (resolved) throw std::runtime_error("AGC driver: source handle memo poisoned but the source resolved");
|
||||
}
|
||||
ShaderMemory::CountHandleMemo(true);
|
||||
if (poisoned == nullptr) throw std::runtime_error(*entry.failure);
|
||||
*poisoned = entry.failure.get();
|
||||
return nullptr;
|
||||
}
|
||||
if (verify && ShaderRecompiler::ResolveSource(request)->source != entry.handle->source) throw std::runtime_error("AGC driver: source handle memo answered a different source");
|
||||
ShaderMemory::CountHandleMemo(true);
|
||||
return entry.handle;
|
||||
std::lock_guard lock(mutex);
|
||||
registry = shaders;
|
||||
for (const auto* shader : stages) {
|
||||
GuestMemory::CheckRange(shader, sizeof(Shader), alignof(Shader));
|
||||
const auto address = reinterpret_cast<std::uintptr_t>(const_cast<const void*>(shader->code));
|
||||
require(registry != nullptr && registry->contains(address), "graphics ABI refers to an unregistered shader");
|
||||
if (owner == nullptr) owner = registry->at(address);
|
||||
const auto& snapshot = *registry->at(address);
|
||||
require(snapshot.headerAddress == reinterpret_cast<std::uintptr_t>(shader), "graphics ABI refers to a replaced shader header");
|
||||
require(snapshot.registeredState != nullptr, "registered shader state is missing");
|
||||
const auto& registered = *snapshot.registeredState;
|
||||
for (const auto& [offset, value] : registered.shader) state.shader.insert_or_assign(offset, value);
|
||||
for (const auto& [offset, value] : registered.context) state.context.insert_or_assign(offset, value);
|
||||
for (const auto& [offset, value] : registered.userConfig) state.userConfig.insert_or_assign(offset, value);
|
||||
}
|
||||
}
|
||||
std::shared_ptr<const ShaderRecompiler::SourceHandle> handle;
|
||||
try {
|
||||
handle = ShaderRecompiler::ResolveSource(request);
|
||||
} catch (const std::exception& error) {
|
||||
if (FailureMemo()) {
|
||||
std::lock_guard lock(memos.mutex);
|
||||
memos.entries[memos.next] = {key, nullptr, std::make_shared<const std::string>(error.what())};
|
||||
memos.next = (memos.next + 1) % memos.entries.size();
|
||||
memos.poisoned.fetch_add(1, std::memory_order_relaxed);
|
||||
for (const auto reg : context) state.context.insert_or_assign(reg.offset, reg.value);
|
||||
for (const auto reg : primitive) state.userConfig.insert_or_assign(reg.offset, reg.value);
|
||||
const auto localDevice = device.Load();
|
||||
require(localDevice != nullptr, "graphics ABI device is missing");
|
||||
struct GraphicsAbiKeyStorage {};
|
||||
auto& key = HostThreadLocal<std::vector<std::uint64_t>, GraphicsAbiKeyStorage>();
|
||||
BuildRegisteredAbiKey(state, *localDevice, key);
|
||||
const auto primitiveType = state.userConfig.find(0x242u);
|
||||
for (const auto& abi : transaction.Read(*owner).graphicsAbis) {
|
||||
if (abi.key != key || abi.registry.lock() != registry) continue;
|
||||
for (const auto& stage : abi.stages) {
|
||||
const auto snapshot = stage.lock();
|
||||
require(snapshot != nullptr, "prepared graphics ABI lost a registered stage");
|
||||
if (snapshot->type != 1) ResolvePreparedGraphics(*snapshot, {}, primitiveType == state.userConfig.end() ? 0u : primitiveType->second, localDevice->Target());
|
||||
}
|
||||
throw;
|
||||
transaction.Commit();
|
||||
return;
|
||||
}
|
||||
ShaderMemory::CountHandleMemo(false);
|
||||
if (handle == nullptr) return nullptr;
|
||||
std::lock_guard lock(memos.mutex);
|
||||
memos.entries[memos.next] = {key, handle, nullptr};
|
||||
memos.next = (memos.next + 1) % memos.entries.size();
|
||||
return handle;
|
||||
DrawDecode decoded{};
|
||||
decoded.state.stages = Graphics::DecodeShaderStages(state);
|
||||
DecodeGraphicsPrograms(decoded, state, *registry, true, false);
|
||||
auto prepared = PrepareGraphicsStages(decoded, localDevice->Target());
|
||||
for (auto& stage : prepared) {
|
||||
const auto& entries = transaction.Read(*stage.snapshot).entries;
|
||||
const auto duplicate = std::ranges::any_of(entries, [&](const auto& existing) { return existing.codeOffset == stage.entry.codeOffset && existing.handle->artifact == stage.entry.handle->artifact; });
|
||||
if (!duplicate) transaction.Edit(*stage.snapshot).entries.push_back(std::move(stage.entry));
|
||||
}
|
||||
for (const auto& stage : prepared) {
|
||||
if (stage.snapshot->type == 1) continue;
|
||||
ResolvePreparedGraphics(*stage.snapshot, {}, primitiveType == state.userConfig.end() ? 0u : primitiveType->second, localDevice->Target());
|
||||
}
|
||||
PreparedShaderState::GraphicsAbi abi{key, registry, {}};
|
||||
for (const auto& stage : prepared) abi.stages.push_back(stage.snapshot);
|
||||
auto& graphicsAbis = transaction.Edit(*owner).graphicsAbis;
|
||||
std::erase_if(graphicsAbis, [](const auto& entry) { return entry.registry.expired(); });
|
||||
graphicsAbis.push_back(std::move(abi));
|
||||
transaction.Commit();
|
||||
}
|
||||
|
||||
void Driver::ResolveShaderAbi(const Shader* shader, std::span<const ShaderRegister> context, std::span<const ShaderRegister> primitive) {
|
||||
PerformanceContext timingContext(FrameTiming::Preparation());
|
||||
PerformanceTimer timing("Shader.ResolveAbi");
|
||||
ShaderPreparationTransaction transaction;
|
||||
CheckFailure();
|
||||
GuestMemory::CheckRange(shader, sizeof(Shader), alignof(Shader));
|
||||
if (shader->type != 0 && shader->type != 1) {
|
||||
const std::array<const Shader*, 1> stages{shader};
|
||||
ResolveGraphicsStagesAbi(stages, context, primitive);
|
||||
transaction.Commit();
|
||||
return;
|
||||
}
|
||||
std::shared_ptr<const ShaderSnapshot> snapshot;
|
||||
{
|
||||
std::lock_guard lock(mutex);
|
||||
const auto address = reinterpret_cast<std::uintptr_t>(const_cast<const void*>(shader->code));
|
||||
require(shaders != nullptr && shaders->contains(address), "static ABI refers to an unregistered shader");
|
||||
snapshot = shaders->at(address);
|
||||
require(snapshot->headerAddress == reinterpret_cast<std::uintptr_t>(shader), "static ABI refers to a replaced shader header");
|
||||
}
|
||||
require(snapshot->registeredState != nullptr, "registered shader state is missing");
|
||||
QueueState state{};
|
||||
state.shader = snapshot->registeredState->shader;
|
||||
state.context = snapshot->registeredState->context;
|
||||
state.userConfig = snapshot->registeredState->userConfig;
|
||||
for (const auto reg : context) state.context[reg.offset] = reg.value;
|
||||
for (const auto reg : primitive) state.userConfig[reg.offset] = reg.value;
|
||||
const auto localDevice = device.Load();
|
||||
require(localDevice != nullptr, "shader registration device is missing");
|
||||
struct RegisteredAbiKeyStorage {};
|
||||
auto& key = HostThreadLocal<std::vector<std::uint64_t>, RegisteredAbiKeyStorage>();
|
||||
BuildRegisteredAbiKey(state, *localDevice, key);
|
||||
const auto& registeredAbis = transaction.Read(*snapshot).registeredAbis;
|
||||
if (std::ranges::find(registeredAbis, key) != registeredAbis.end()) {
|
||||
transaction.Commit();
|
||||
return;
|
||||
}
|
||||
auto entries = PrepareRegistered(*snapshot, *localDevice, state, false);
|
||||
auto& prepared = transaction.Edit(*snapshot);
|
||||
for (auto& entry : entries) {
|
||||
const auto duplicate = std::ranges::any_of(prepared.entries, [&](const auto& existing) { return existing.handle->artifact == entry.handle->artifact; });
|
||||
if (!duplicate) prepared.entries.push_back(std::move(entry));
|
||||
}
|
||||
prepared.registeredAbis.push_back(key);
|
||||
transaction.Commit();
|
||||
}
|
||||
|
||||
alignas(256) static const std::uint32_t NullPixelCode[64] = {0xbf810000u};
|
||||
|
|
@ -161,7 +555,97 @@ std::uint64_t NullPixelProgramAddress() {
|
|||
return reinterpret_cast<std::uintptr_t>(NullPixelCode);
|
||||
}
|
||||
|
||||
void Driver::ResolveGraphicsAbi(const Shader* vertex, const Shader* pixel, std::uint32_t primitiveType) {
|
||||
PerformanceContext timingContext(FrameTiming::Preparation());
|
||||
PerformanceTimer timing("Shader.ResolveGraphicsAbi");
|
||||
CheckFailure();
|
||||
if (primitiveType != 0 && primitiveType != 7 && primitiveType != 17) return;
|
||||
ShaderPreparationTransaction transaction;
|
||||
require(vertex != nullptr && pixel != nullptr, "rectangle ABI requires vertex and fragment shaders");
|
||||
std::shared_ptr<const ShaderSnapshot> front;
|
||||
std::shared_ptr<const ShaderSnapshot> fragment;
|
||||
{
|
||||
std::lock_guard lock(mutex);
|
||||
GuestMemory::CheckRange(vertex, sizeof(Shader), alignof(Shader));
|
||||
GuestMemory::CheckRange(pixel, sizeof(Shader), alignof(Shader));
|
||||
const auto lookup = [&](const Shader* shader) {
|
||||
const auto address = reinterpret_cast<std::uintptr_t>(const_cast<const void*>(shader->code));
|
||||
require(shaders != nullptr && shaders->contains(address), "rectangle ABI refers to an unregistered shader");
|
||||
const auto snapshot = shaders->at(address);
|
||||
require(snapshot->headerAddress == reinterpret_cast<std::uintptr_t>(shader), "rectangle ABI refers to a replaced shader header");
|
||||
return snapshot;
|
||||
};
|
||||
front = lookup(vertex);
|
||||
fragment = lookup(pixel);
|
||||
}
|
||||
require(front != fragment, "rectangle stages refer to the same shader");
|
||||
const auto localDevice = device.Load();
|
||||
require(localDevice != nullptr, "shader registration device is missing");
|
||||
ResolvePreparedGraphics(*front, fragment, primitiveType, localDevice->Target());
|
||||
transaction.Commit();
|
||||
}
|
||||
|
||||
void ResolvePreparedGraphics(const ShaderSnapshot& front, const std::shared_ptr<const ShaderSnapshot>& fragment, std::uint32_t primitiveType, const ShaderRecompiler::SpirvTarget& target) {
|
||||
ShaderPreparationTransaction transaction;
|
||||
require(fragment.get() != &front, "rectangle stages refer to the same shader");
|
||||
std::vector<std::shared_ptr<const ShaderSnapshot>> fragments;
|
||||
{
|
||||
const auto& current = transaction.Read(front);
|
||||
const bool newFragment = fragment && !std::ranges::any_of(current.fragments, [&](const auto& entry) { return entry.lock() == fragment; });
|
||||
const bool newRequest = !current.rectangleRequested && (primitiveType == 7 || primitiveType == 17);
|
||||
const bool expiredFragment = std::ranges::any_of(current.fragments, [](const auto& entry) { return entry.expired(); });
|
||||
if (newFragment || newRequest || expiredFragment) {
|
||||
auto& prepared = transaction.Edit(front);
|
||||
std::erase_if(prepared.fragments, [](const auto& entry) { return entry.expired(); });
|
||||
std::erase_if(prepared.rectangleProgress, [](const auto& entry) { return entry.fragment.expired(); });
|
||||
if (newFragment) prepared.fragments.push_back(fragment);
|
||||
prepared.rectangleRequested |= newRequest;
|
||||
}
|
||||
const auto& prepared = transaction.Read(front);
|
||||
if (!prepared.rectangleRequested) {
|
||||
transaction.Commit();
|
||||
return;
|
||||
}
|
||||
for (const auto& entry : prepared.fragments) {
|
||||
if (auto snapshot = entry.lock()) fragments.push_back(std::move(snapshot));
|
||||
}
|
||||
}
|
||||
for (const auto& pixel : fragments) {
|
||||
const auto& current = transaction.Read(front);
|
||||
const auto& pixelPrepared = transaction.Read(*pixel);
|
||||
require(!current.entries.empty() && !pixelPrepared.entries.empty(), "rectangle ABI has missing stage artifacts");
|
||||
const auto progress = std::ranges::find_if(current.rectangleProgress, [&](const auto& entry) { return entry.fragment.lock() == pixel; });
|
||||
const auto vertexCount = progress == current.rectangleProgress.end() ? 0u : progress->vertexCount;
|
||||
const auto fragmentCount = progress == current.rectangleProgress.end() ? 0u : progress->fragmentCount;
|
||||
if (vertexCount == current.entries.size() && fragmentCount == pixelPrepared.entries.size()) continue;
|
||||
auto& prepared = transaction.Edit(front);
|
||||
std::vector<PreparedShaders::Rectangle> rectangles;
|
||||
for (std::size_t vertexIndex = 0; vertexIndex < prepared.entries.size(); ++vertexIndex) {
|
||||
const auto& vertexEntry = prepared.entries[vertexIndex];
|
||||
for (std::size_t fragmentIndex = vertexIndex < vertexCount ? fragmentCount : 0u; fragmentIndex < pixelPrepared.entries.size(); ++fragmentIndex) {
|
||||
const auto& fragmentEntry = pixelPrepared.entries[fragmentIndex];
|
||||
const auto& vertexArtifact = ShaderRecompiler::GetPreparedArtifact(*vertexEntry.handle);
|
||||
const auto& fragmentArtifact = ShaderRecompiler::GetPreparedArtifact(*fragmentEntry.handle);
|
||||
const auto exists = [&](const auto& entry) { return entry.vertexId == vertexArtifact.variantId && entry.fragmentId == fragmentArtifact.variantId; };
|
||||
if (std::ranges::any_of(prepared.rectangles, exists) || std::ranges::any_of(rectangles, exists)) continue;
|
||||
ShaderRecompiler::RecompileResult vertexResult;
|
||||
ShaderRecompiler::RecompileResult fragmentResult;
|
||||
static_cast<ShaderRecompiler::CompiledShaderArtifact&>(vertexResult) = vertexArtifact;
|
||||
static_cast<ShaderRecompiler::CompiledShaderArtifact&>(fragmentResult) = fragmentArtifact;
|
||||
rectangles.push_back({vertexArtifact.variantId, fragmentArtifact.variantId, std::make_shared<const ShaderRecompiler::RectListShaders>(ShaderRecompiler::BuildRectListShaders(vertexResult, fragmentResult, target))});
|
||||
}
|
||||
}
|
||||
prepared.rectangles.insert(prepared.rectangles.end(), std::make_move_iterator(rectangles.begin()), std::make_move_iterator(rectangles.end()));
|
||||
std::erase_if(prepared.rectangleProgress, [&](const auto& entry) { return entry.fragment.expired() || entry.fragment.lock() == pixel; });
|
||||
prepared.rectangleProgress.push_back({pixel, prepared.entries.size(), pixelPrepared.entries.size()});
|
||||
}
|
||||
transaction.Commit();
|
||||
}
|
||||
|
||||
void Driver::RegisterShader(const Shader* shader) {
|
||||
PerformanceContext timingContext(FrameTiming::Preparation());
|
||||
PerformanceTimer timing("Shader.Register");
|
||||
ShaderPreparationTransaction transaction;
|
||||
CheckFailure();
|
||||
GuestMemory::CheckRange(shader, sizeof(Shader), 1);
|
||||
Shader fields;
|
||||
|
|
@ -178,6 +662,38 @@ void Driver::RegisterShader(const Shader* shader) {
|
|||
snapshot.header.resize(fields.header_size);
|
||||
std::memcpy(snapshot.header.data(), static_cast<const void*>(shader), fields.header_size);
|
||||
|
||||
{
|
||||
std::lock_guard lock(mutex);
|
||||
rethrowFailure();
|
||||
if (shaders != nullptr) {
|
||||
const auto found = shaders->find(snapshot.codeAddress);
|
||||
if (found != shaders->end()) {
|
||||
const auto& current = *found->second;
|
||||
if (current.headerAddress == snapshot.headerAddress && current.type == snapshot.type && current.code == snapshot.code && current.header == snapshot.header) {
|
||||
transaction.Commit();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<VulkanDevice> localDevice = device.Load();
|
||||
if (localDevice == nullptr) {
|
||||
std::lock_guard gpuLock(GuestMemory::GpuMutex());
|
||||
if (device == nullptr) device = std::make_shared<VulkanDevice>();
|
||||
localDevice = device;
|
||||
}
|
||||
snapshot.registeredState = std::make_shared<const RegisteredShaderState>(DecodeRegisteredState(snapshot));
|
||||
QueueState registered{};
|
||||
registered.shader = snapshot.registeredState->shader;
|
||||
registered.context = snapshot.registeredState->context;
|
||||
registered.userConfig = snapshot.registeredState->userConfig;
|
||||
snapshot.prepared->entries = PrepareRegistered(snapshot, *localDevice, registered, true);
|
||||
if ((snapshot.type == 0 || snapshot.type == 1) && !snapshot.prepared->entries.empty()) {
|
||||
std::vector<std::uint64_t> key;
|
||||
BuildRegisteredAbiKey(registered, *localDevice, key);
|
||||
snapshot.prepared->registeredAbis.push_back(std::move(key));
|
||||
}
|
||||
static const char* traceRegs = std::getenv("APS5_TRACE_SHADER_REGS");
|
||||
if (traceRegs != nullptr && (std::string(traceRegs) == "all" || std::strtoull(traceRegs, nullptr, 16) == snapshot.codeAddress)) {
|
||||
const auto print = [](const ShaderRegister* registers, std::uint32_t count) {
|
||||
|
|
@ -195,18 +711,39 @@ void Driver::RegisterShader(const Shader* shader) {
|
|||
}
|
||||
std::lock_guard lock(mutex);
|
||||
rethrowFailure();
|
||||
const auto address = snapshot.codeAddress;
|
||||
|
||||
if (shaders == nullptr) shaders = std::make_shared<ShaderRegistry>();
|
||||
else if (shaders.use_count() != 1) shaders = std::make_shared<ShaderRegistry>(*shaders);
|
||||
shaders->insert_or_assign(address, std::make_shared<const ShaderSnapshot>(std::move(snapshot)));
|
||||
PublishRegisteredShader(shaders, std::make_shared<const ShaderSnapshot>(std::move(snapshot)));
|
||||
if (shaders->find(NullPixelProgramAddress()) == shaders->end()) {
|
||||
ShaderSnapshot null{NullPixelProgramAddress(), reinterpret_cast<std::uintptr_t>(&NullPixelShader), NullPixelShader.type, {}, {}};
|
||||
null.code.assign(std::begin(NullPixelCode), std::end(NullPixelCode));
|
||||
null.header.resize(sizeof(Shader));
|
||||
std::memcpy(null.header.data(), &NullPixelShader, sizeof(Shader));
|
||||
shaders->insert_or_assign(NullPixelProgramAddress(), std::make_shared<const ShaderSnapshot>(std::move(null)));
|
||||
auto nullRegisteredState = DecodeRegisteredState(null);
|
||||
nullRegisteredState.shader.emplace(0x008u, static_cast<std::uint32_t>(null.codeAddress >> 8u));
|
||||
nullRegisteredState.shader.emplace(0x009u, static_cast<std::uint32_t>(null.codeAddress >> 40u));
|
||||
nullRegisteredState.shader.emplace(0x00bu, 0u);
|
||||
nullRegisteredState.context.emplace(0x1b3u, 0x2u);
|
||||
nullRegisteredState.context.emplace(0x1b4u, 0x2u);
|
||||
null.registeredState = std::make_shared<const RegisteredShaderState>(std::move(nullRegisteredState));
|
||||
QueueState nullState{};
|
||||
nullState.shader = null.registeredState->shader;
|
||||
nullState.context = null.registeredState->context;
|
||||
nullState.userConfig = null.registeredState->userConfig;
|
||||
null.prepared->entries = PrepareRegistered(null, *localDevice, nullState, true);
|
||||
PublishRegisteredShader(shaders, std::make_shared<const ShaderSnapshot>(std::move(null)));
|
||||
}
|
||||
transaction.Commit();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extern "C" AgcDriver::DriverDetail::ShaderPreparationTransaction* AgcDriverBeginShaderPreparation_nid_postfix() {
|
||||
return new AgcDriver::DriverDetail::ShaderPreparationTransaction();
|
||||
}
|
||||
|
||||
extern "C" void AgcDriverCommitShaderPreparation_nid_postfix(AgcDriver::DriverDetail::ShaderPreparationTransaction* transaction) {
|
||||
transaction->Commit();
|
||||
}
|
||||
|
||||
extern "C" void AgcDriverEndShaderPreparation_nid_postfix(AgcDriver::DriverDetail::ShaderPreparationTransaction* transaction) noexcept {
|
||||
delete transaction;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@
|
|||
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/Pm4.hpp"
|
||||
#include <cstdlib>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
|
@ -287,10 +289,14 @@ void Driver::waitMemory(std::span<const std::uint32_t> packet, std::uint32_t que
|
|||
if (!warned && std::chrono::steady_clock::now() - start > std::chrono::milliseconds(waitTimeoutMs())) {
|
||||
warned = true;
|
||||
++outcomes.timedOut;
|
||||
static std::set<std::uint64_t> reported;
|
||||
static std::uint64_t timeouts = 0;
|
||||
if (++timeouts % 20 == 0) std::fprintf(stderr, "[gpu] %llu GPU waits have timed out\n", static_cast<unsigned long long>(timeouts));
|
||||
if (!reported.insert(packet[2] | (static_cast<std::uint64_t>(packet[3]) << 32u)).second) return;
|
||||
{
|
||||
static std::mutex reportedMutex;
|
||||
static std::set<std::uint64_t> reported;
|
||||
static std::uint64_t timeouts = 0;
|
||||
const std::lock_guard lock(reportedMutex);
|
||||
if (++timeouts % 20 == 0) std::fprintf(stderr, "[gpu] %llu GPU waits have timed out\n", static_cast<unsigned long long>(timeouts));
|
||||
if (!reported.insert(packet[2] | (static_cast<std::uint64_t>(packet[3]) << 32u)).second) return;
|
||||
}
|
||||
const bool wide = ((packet[0] >> 8u) & 0xffu) == 0x93u;
|
||||
const std::uint64_t reference = wide ? packet[4] | (static_cast<std::uint64_t>(packet[5]) << 32u) : packet[4];
|
||||
const std::uint64_t mask = wide ? packet[6] | (static_cast<std::uint64_t>(packet[7]) << 32u) : packet[5];
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#include "prx/libSceAgcDriver/Execution/include/ProfileOutput.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/WriteWatchCoverage.hpp"
|
||||
#include "ThreadOwned.hpp"
|
||||
#include "prx/libc/include/GuestAllocations.hpp"
|
||||
#include "prx/libc/include/GuestArena.hpp"
|
||||
|
|
@ -26,9 +27,43 @@
|
|||
#endif
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <cerrno>
|
||||
#include <cstdint>
|
||||
#include <fcntl.h>
|
||||
#include <fstream>
|
||||
#include <pthread.h>
|
||||
#include <sstream>
|
||||
#include <sys/ioctl.h>
|
||||
#include <unistd.h>
|
||||
#if __has_include(<linux/fs.h>)
|
||||
#include <linux/fs.h>
|
||||
#endif
|
||||
#if !defined(PROCMAP_QUERY)
|
||||
struct procmap_query {
|
||||
std::uint64_t size;
|
||||
std::uint64_t query_flags;
|
||||
std::uint64_t query_addr;
|
||||
std::uint64_t vma_start;
|
||||
std::uint64_t vma_end;
|
||||
std::uint64_t vma_flags;
|
||||
std::uint64_t vma_page_size;
|
||||
std::uint64_t vma_offset;
|
||||
std::uint64_t inode;
|
||||
std::uint32_t dev_major;
|
||||
std::uint32_t dev_minor;
|
||||
std::uint32_t vma_name_size;
|
||||
std::uint32_t build_id_size;
|
||||
std::uint64_t vma_name_addr;
|
||||
std::uint64_t build_id_addr;
|
||||
};
|
||||
static_assert(sizeof(procmap_query) == 104);
|
||||
enum : std::uint64_t {
|
||||
PROCMAP_QUERY_VMA_READABLE = 0x01,
|
||||
PROCMAP_QUERY_VMA_WRITABLE = 0x02,
|
||||
PROCMAP_QUERY_COVERING_OR_NEXT_VMA = 0x10,
|
||||
};
|
||||
#define PROCMAP_QUERY _IOWR('f', 17, struct procmap_query)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
namespace AgcDriver::GuestMemory {
|
||||
|
|
@ -478,6 +513,23 @@ struct PageRun {
|
|||
bool writable;
|
||||
};
|
||||
|
||||
#if !defined(_WIN32)
|
||||
int ProcMapsQueryFd() {
|
||||
static const int fd = [] {
|
||||
if (std::getenv("APS5_NO_PROCMAP_QUERY") != nullptr) return -1;
|
||||
const int opened = open("/proc/self/maps", O_RDONLY | O_CLOEXEC);
|
||||
if (opened < 0) return -1;
|
||||
procmap_query probe{};
|
||||
probe.size = sizeof(probe);
|
||||
probe.query_flags = PROCMAP_QUERY_COVERING_OR_NEXT_VMA;
|
||||
if (ioctl(opened, PROCMAP_QUERY, &probe) == 0 || errno == ENOENT) return opened;
|
||||
close(opened);
|
||||
return -1;
|
||||
}();
|
||||
return fd;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Calls `emit` with consecutive runs of uniform accessibility covering [address, address + bytes) in
|
||||
// order, stopping early when it returns false. Returns false when the address space cannot be queried.
|
||||
template <class Emit>
|
||||
|
|
@ -535,6 +587,31 @@ bool describePages(std::uintptr_t address, std::size_t bytes, Emit&& emit) {
|
|||
if (!emit(PageRun{cursor, next, readable, writable})) return true;
|
||||
cursor = next;
|
||||
#else
|
||||
if (const int fd = ProcMapsQueryFd(); fd >= 0) {
|
||||
procmap_query query{};
|
||||
query.size = sizeof(query);
|
||||
query.query_flags = PROCMAP_QUERY_COVERING_OR_NEXT_VMA;
|
||||
query.query_addr = cursor;
|
||||
if (ioctl(fd, PROCMAP_QUERY, &query) == 0) {
|
||||
if (query.vma_end <= cursor || query.vma_start >= query.vma_end) return false;
|
||||
if (query.vma_start > cursor) {
|
||||
const auto gapEnd = std::min<std::uintptr_t>(end, query.vma_start);
|
||||
if (!emit(PageRun{cursor, gapEnd, false, false})) return true;
|
||||
cursor = gapEnd;
|
||||
continue;
|
||||
}
|
||||
const bool readable = (query.vma_flags & PROCMAP_QUERY_VMA_READABLE) != 0;
|
||||
const auto next = std::min<std::uintptr_t>(end, query.vma_end);
|
||||
if (!emit(PageRun{cursor, next, readable, readable && (query.vma_flags & PROCMAP_QUERY_VMA_WRITABLE) != 0})) return true;
|
||||
cursor = next;
|
||||
continue;
|
||||
}
|
||||
if (errno == ENOENT) {
|
||||
static_cast<void>(emit(PageRun{cursor, end, false, false}));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
std::ifstream maps("/proc/self/maps");
|
||||
if (!maps.is_open()) return false;
|
||||
std::string line;
|
||||
|
|
@ -667,9 +744,15 @@ std::vector<std::pair<std::uint64_t, std::uint64_t>> CommittedRanges(std::uint64
|
|||
namespace {
|
||||
|
||||
constexpr std::size_t WriteBlockBytes = 65536;
|
||||
constexpr std::size_t WritePageBytes = 4096;
|
||||
constexpr std::size_t WritePagesPerBlock = WriteBlockBytes / WritePageBytes;
|
||||
|
||||
enum class StampKind : std::uint8_t { Cpu, Driver, ImportWindow };
|
||||
|
||||
#ifdef _WIN32
|
||||
void watchPrivateMapping(std::uintptr_t address, std::size_t bytes, std::uint64_t generation);
|
||||
#endif
|
||||
|
||||
struct WriteTracker {
|
||||
std::mutex mutex;
|
||||
bool initialized = false;
|
||||
|
|
@ -684,6 +767,7 @@ struct WriteTracker {
|
|||
// records in the same block (the title's per-job slots are 0x20 apart) must not count.
|
||||
std::vector<std::uint32_t> cpuBlocks;
|
||||
std::vector<std::uint32_t> writtenBlocks;
|
||||
WriteWatchCoverage coverage;
|
||||
#else
|
||||
static constexpr std::size_t LeafBlocks = std::size_t{1} << 16;
|
||||
static constexpr std::size_t LeafCount = std::size_t{1} << 15;
|
||||
|
|
@ -727,7 +811,9 @@ struct WriteTracker {
|
|||
blocks.assign(size / WriteBlockBytes + 1, 0);
|
||||
cpuBlocks.assign(size / WriteBlockBytes + 1, 0);
|
||||
writtenBlocks.assign(size / WriteBlockBytes + 1, 0);
|
||||
coverage.Initialize(base, size);
|
||||
pages.resize(1u << 16);
|
||||
GuestArena::GuestArenaSetPrivateMappingObserver_nid_postfix(&watchPrivateMapping);
|
||||
#else
|
||||
watched = GuestWriteWatch::GuestWriteWatchAvailable_nid_postfix();
|
||||
if (watched) leaves.resize(LeafCount);
|
||||
|
|
@ -736,7 +822,7 @@ struct WriteTracker {
|
|||
|
||||
bool covers(std::uint64_t address, std::size_t bytes) const {
|
||||
#ifdef _WIN32
|
||||
return address >= base && address - base <= size - bytes;
|
||||
return coverage.Covers(address, bytes);
|
||||
#else
|
||||
return address + bytes <= LeafCount * LeafBlocks * WriteBlockBytes && GuestWriteWatch::GuestWriteWatchCovers_nid_postfix(static_cast<std::uintptr_t>(address), bytes);
|
||||
#endif
|
||||
|
|
@ -771,6 +857,24 @@ struct WriteTracker {
|
|||
};
|
||||
std::unordered_map<std::uint64_t, DriverPieces> driverPieces;
|
||||
|
||||
struct CpuPages {
|
||||
std::array<std::uint32_t, WritePagesPerBlock> pages{};
|
||||
};
|
||||
std::unordered_map<std::uint64_t, CpuPages> cpuPages;
|
||||
|
||||
void noteCpuStore(std::uint64_t begin, std::uint64_t end, std::uint32_t stampGeneration) {
|
||||
for (auto block = blockOf(begin); block <= blockOf(end - 1); ++block) {
|
||||
const auto blockStart = blockBegin(block);
|
||||
const auto from = std::max(begin, blockStart) - blockStart;
|
||||
const auto to = std::min<std::uint64_t>(end, blockStart + WriteBlockBytes) - blockStart;
|
||||
auto& entry = cpuPages[block];
|
||||
for (auto page = from / WritePageBytes; page <= (to - 1) / WritePageBytes; ++page) {
|
||||
auto& stamp = entry.pages[page];
|
||||
stamp = std::max(stamp, stampGeneration);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void noteDriverStore(std::uint64_t block, std::uint64_t address, std::uint64_t end, std::uint32_t stampGeneration) {
|
||||
const auto begin = blockBegin(block);
|
||||
const auto from = static_cast<std::uint32_t>(std::max(address, begin) - begin);
|
||||
|
|
@ -851,6 +955,21 @@ std::unique_lock<std::mutex> lockTracker(WriteTracker& tracker) {
|
|||
return lock;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
void watchPrivateMapping(std::uintptr_t address, std::size_t bytes, std::uint64_t generation) {
|
||||
auto& tracker = Tracker();
|
||||
const auto lock = lockTracker(tracker);
|
||||
if (!tracker.watched || bytes == 0 || bytes > tracker.size || address < tracker.base || address - tracker.base > tracker.size - bytes) return;
|
||||
tracker.coverage.Restore(address, bytes, generation);
|
||||
++tracker.generation;
|
||||
for (auto block = tracker.blockOf(address); block <= tracker.blockOf(address + bytes - 1); ++block) {
|
||||
tracker.driverPieces.erase(block);
|
||||
tracker.stamp(block, tracker.generation, StampKind::Cpu);
|
||||
}
|
||||
unwatchSerial.fetch_add(1, std::memory_order_release);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Collect epochs are per thread and globally unique: every bump takes a fresh value from one counter,
|
||||
// so a memo entry (stamped with the epoch of its walk) can only match the thread that made it, and
|
||||
// only until that thread's next ordering point (the queue workers bump at theirs, see the header). A
|
||||
|
|
@ -894,6 +1013,7 @@ void stampWrittenRun(void* context, std::uintptr_t begin, std::uintptr_t end) {
|
|||
auto& [tracker, kind] = *static_cast<StampRuns*>(context);
|
||||
if (end <= begin) return;
|
||||
const auto generation = tracker.generation.load(std::memory_order_relaxed);
|
||||
if (kind != StampKind::Driver) tracker.noteCpuStore(begin, end, generation);
|
||||
for (auto block = tracker.blockOf(begin); block <= tracker.blockOf(end - 1); ++block) tracker.stamp(block, generation, kind);
|
||||
collectDirtyRuns.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
|
|
@ -918,7 +1038,11 @@ bool walkWrites(WriteTracker& tracker, std::uint64_t first, std::uint64_t stop,
|
|||
return false;
|
||||
}
|
||||
if (count != 0) dirty = true;
|
||||
for (ULONG_PTR i = 0; i < count; ++i) tracker.stamp(tracker.blockOf(reinterpret_cast<std::uintptr_t>(tracker.pages[i])), tracker.generation, kind);
|
||||
for (ULONG_PTR i = 0; i < count; ++i) {
|
||||
const auto page = reinterpret_cast<std::uintptr_t>(tracker.pages[i]);
|
||||
tracker.stamp(tracker.blockOf(page), tracker.generation, kind);
|
||||
if (kind != StampKind::Driver) tracker.noteCpuStore(page, page + WritePageBytes, tracker.generation);
|
||||
}
|
||||
if (count < tracker.pages.size()) break;
|
||||
cursor = reinterpret_cast<std::uintptr_t>(tracker.pages[count - 1]) + (granularity != 0 ? granularity : page);
|
||||
}
|
||||
|
|
@ -930,7 +1054,11 @@ bool walkWrites(WriteTracker& tracker, std::uint64_t first, std::uint64_t stop,
|
|||
std::size_t count = tracker.pages.size();
|
||||
DWORD granularity = 4096;
|
||||
if (!GuestArena::GuestArenaCollectWrites_nid_postfix(cursor, static_cast<std::size_t>(stop - cursor), tracker.pages.data(), &count, true)) return false;
|
||||
for (ULONG_PTR i = 0; i < count; ++i) tracker.stamp(tracker.blockOf(reinterpret_cast<std::uintptr_t>(tracker.pages[i])), tracker.generation, kind);
|
||||
for (ULONG_PTR i = 0; i < count; ++i) {
|
||||
const auto page = reinterpret_cast<std::uintptr_t>(tracker.pages[i]);
|
||||
tracker.stamp(tracker.blockOf(page), tracker.generation, kind);
|
||||
if (kind != StampKind::Driver) tracker.noteCpuStore(page, page + WritePageBytes, tracker.generation);
|
||||
}
|
||||
if (count < tracker.pages.size()) break;
|
||||
cursor = reinterpret_cast<std::uintptr_t>(tracker.pages[count - 1]) + (granularity != 0 ? granularity : page);
|
||||
}
|
||||
|
|
@ -1013,11 +1141,10 @@ std::uint64_t CollectEpochBumps() {
|
|||
|
||||
namespace {
|
||||
|
||||
void unwatchLocked(const WriteTracker& tracker, std::uint64_t address, std::size_t bytes) {
|
||||
void unwatchLocked(WriteTracker& tracker, std::uint64_t address, std::size_t bytes) {
|
||||
if (!tracker.watched) return;
|
||||
#ifdef _WIN32
|
||||
static_cast<void>(address);
|
||||
static_cast<void>(bytes);
|
||||
if (tracker.coverage.Exclude(address, bytes, GuestArena::GuestArenaCommitGeneration_nid_postfix())) unwatchSerial.fetch_add(1, std::memory_order_release);
|
||||
#else
|
||||
if (GuestWriteWatch::GuestWriteWatchUnregister_nid_postfix(reinterpret_cast<const void*>(address), bytes)) unwatchSerial.fetch_add(1, std::memory_order_release);
|
||||
#endif
|
||||
|
|
@ -1154,7 +1281,16 @@ bool StoredOver(std::uint64_t address, std::size_t bytes, std::uint64_t generati
|
|||
const auto last = tracker.blockOf(end - 1);
|
||||
for (auto block = first; block <= last; ++block) {
|
||||
if (tracker.writtenStampOf(block) <= generation) continue;
|
||||
if (tracker.cpuStampOf(block) > generation) return true;
|
||||
if (tracker.cpuStampOf(block) > generation) {
|
||||
const auto begin = tracker.blockBegin(block);
|
||||
const auto from = std::max(address, begin) - begin;
|
||||
const auto to = std::min<std::uint64_t>(end, begin + WriteBlockBytes) - begin;
|
||||
const auto found = tracker.cpuPages.find(block);
|
||||
if (found == tracker.cpuPages.end()) return true;
|
||||
for (auto page = from / WritePageBytes; page <= (to - 1) / WritePageBytes; ++page) {
|
||||
if (found->second.pages[page] > generation) return true;
|
||||
}
|
||||
}
|
||||
const auto found = tracker.driverPieces.find(block);
|
||||
if (found == tracker.driverPieces.end()) return true;
|
||||
const auto& entry = found->second;
|
||||
|
|
|
|||
|
|
@ -10,7 +10,21 @@
|
|||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
extern "C" void* APS5_VABI mmap_nid_postfix(void* address, std::size_t length, int protection, int flags, int descriptor, std::int64_t offset) noexcept;
|
||||
|
||||
namespace AgcDriver::Pm4 {
|
||||
|
||||
std::uint64_t GdsAddress() {
|
||||
static const std::uint64_t address = [] {
|
||||
constexpr int ReadWrite = 0x3;
|
||||
constexpr int PrivateAnonymous = 0x1002;
|
||||
void* mapped = mmap_nid_postfix(nullptr, GdsBytes, ReadWrite, PrivateAnonymous, -1, 0);
|
||||
if (mapped == nullptr || mapped == reinterpret_cast<void*>(static_cast<std::uintptr_t>(-1))) throw std::runtime_error("cannot allocate the global data share");
|
||||
return reinterpret_cast<std::uint64_t>(mapped);
|
||||
}();
|
||||
return address;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
std::string ToHex(std::uint32_t value) {
|
||||
|
|
@ -99,17 +113,6 @@ std::vector<std::byte> copySource(std::uint64_t source, std::size_t bytes, bool
|
|||
}
|
||||
|
||||
constexpr std::uint32_t DmaSelectGds = 1;
|
||||
constexpr std::size_t GdsBytes = 0x10000;
|
||||
|
||||
struct GdsStorage {
|
||||
std::mutex mutex;
|
||||
std::array<std::byte, GdsBytes> bytes{};
|
||||
};
|
||||
|
||||
GdsStorage& Gds() {
|
||||
static GdsStorage storage;
|
||||
return storage;
|
||||
}
|
||||
|
||||
bool gdsRange(std::uint64_t offset, std::size_t bytes) {
|
||||
return offset <= GdsBytes && bytes <= GdsBytes - offset;
|
||||
|
|
@ -118,9 +121,7 @@ bool gdsRange(std::uint64_t offset, std::size_t bytes) {
|
|||
std::vector<std::byte> dmaSourceBytes(std::span<const std::uint32_t> packet) {
|
||||
const std::size_t bytes = packet[6] & 0x3ffffffu;
|
||||
if (dmaSource(packet) != DmaSelectGds) return copySource(address(packet[2], packet[3]), bytes, dmaSource(packet) == 2);
|
||||
auto& gds = Gds();
|
||||
std::lock_guard lock(gds.mutex);
|
||||
return {gds.bytes.begin() + packet[2], gds.bytes.begin() + packet[2] + bytes};
|
||||
return copySource(GdsAddress() + packet[2], bytes, false);
|
||||
}
|
||||
|
||||
void copyMemory(std::uint64_t source, std::uint64_t destination, std::size_t bytes, bool immediate) {
|
||||
|
|
@ -129,6 +130,22 @@ void copyMemory(std::uint64_t source, std::uint64_t destination, std::size_t byt
|
|||
GuestMemory::Write(destination, copySource(source, bytes, immediate));
|
||||
}
|
||||
|
||||
constexpr std::uint32_t CopyDataGpuClockSource = 18;
|
||||
constexpr std::uint32_t CopyDataCachePolicy = (3u << 13u) | (3u << 25u);
|
||||
|
||||
std::uint64_t gpuClockCount() {
|
||||
return static_cast<std::uint64_t>(std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::steady_clock::now().time_since_epoch()).count() / 10);
|
||||
}
|
||||
|
||||
std::vector<std::byte> copyDataSource(std::span<const std::uint32_t> packet, std::size_t bytes) {
|
||||
const auto source = ((packet[1] & 0xfu) << 1u) | ((packet[1] >> 30u) & 1u);
|
||||
if (source != CopyDataGpuClockSource) return copySource(address(packet[2], packet[3]), bytes, source >= 10);
|
||||
const auto value = gpuClockCount();
|
||||
std::vector<std::byte> data(bytes);
|
||||
for (std::size_t i = 0; i < bytes; ++i) data[i] = static_cast<std::byte>(value >> (i * 8));
|
||||
return data;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
std::string Name(std::uint32_t header) {
|
||||
|
|
@ -416,12 +433,12 @@ void Validate(std::span<const std::uint32_t> packet, std::uint32_t queue) {
|
|||
}
|
||||
case 0x40: {
|
||||
size(6);
|
||||
require((packet[1] & ~0x40110f0fu) == 0, "COPY_DATA engine, cache or reserved fields are not implemented");
|
||||
require((packet[1] & ~(0x40110f0fu | CopyDataCachePolicy)) == 0, "COPY_DATA engine or reserved fields are not implemented");
|
||||
const auto source = ((packet[1] & 0xfu) << 1u) | ((packet[1] >> 30u) & 1u);
|
||||
const auto destination = ((packet[1] >> 8u) & 0xfu) << 1u;
|
||||
require(destination == 2 || destination == 4, "COPY_DATA register or GDS destination is not implemented");
|
||||
require(source == 2 || source == 4 || source == 5 || source == 10 || source == 11, "COPY_DATA register, GDS or reference-clock source is not implemented");
|
||||
require(source < 10 || ((packet[1] & 0x10000u) == 0 && packet[3] == 0), "64-bit immediate COPY_DATA is not implemented");
|
||||
require(source == 2 || source == 4 || source == 5 || source == 10 || source == 11 || source == CopyDataGpuClockSource, "COPY_DATA register, GDS or reference-clock source is not implemented");
|
||||
require(source < 10 || source == CopyDataGpuClockSource || ((packet[1] & 0x10000u) == 0 && packet[3] == 0), "64-bit immediate COPY_DATA is not implemented");
|
||||
break;
|
||||
}
|
||||
case 0x50:
|
||||
|
|
@ -549,7 +566,7 @@ std::optional<StoreWrite> ResolveStore(std::span<const std::uint32_t> packet, co
|
|||
const std::size_t bytes = (packet[1] & 0x10000u) != 0 ? 8 : 4;
|
||||
const auto destination = address(packet[4], packet[5]);
|
||||
if (!fits(destination, bytes)) return std::nullopt;
|
||||
return StoreWrite{destination, {}, copySource(address(packet[2], packet[3]), bytes, source >= 10)};
|
||||
return StoreWrite{destination, {}, copyDataSource(packet, bytes)};
|
||||
}
|
||||
case 0x50: {
|
||||
if (packet.size() < 7 || dmaDestination(packet) == DmaSelectGds) return std::nullopt;
|
||||
|
|
@ -715,6 +732,28 @@ DrawParameters ResolveDraw(std::span<const std::uint32_t> packet, const QueueSta
|
|||
return {address, count, indexSize, queue.instanceCount, packet.back(), true, indexOffset->second, 0};
|
||||
}
|
||||
|
||||
std::vector<std::uint32_t> ReadIndirectRegisters(std::span<const std::uint32_t> packet) {
|
||||
require(packet.size() == 5 && IndirectRegisterOpcode((packet[0] >> 8u) & 0xffu), "expected indirect register packet");
|
||||
std::vector<std::uint32_t> pairs(static_cast<std::size_t>(packet[4]) * 2);
|
||||
// Named for the [hooksync] attribution (the read goes through the flush hook).
|
||||
const GuestMemory::ReadSiteScope site(GuestMemory::ReadSite::Registers);
|
||||
GuestMemory::Read(address(packet[1], packet[2]), std::as_writable_bytes(std::span(pairs)), 4);
|
||||
return pairs;
|
||||
}
|
||||
|
||||
void ExecuteIndirectRegisters(std::span<const std::uint32_t> packet, std::span<const std::uint32_t> pairs, QueueState& queue) {
|
||||
const auto opcode = (packet[0] >> 8u) & 0xffu;
|
||||
require(packet.size() == 5 && IndirectRegisterOpcode(opcode), "expected indirect register packet");
|
||||
require(pairs.size() == static_cast<std::size_t>(packet[4]) * 2, "indirect register list does not match its packet");
|
||||
for (std::size_t i = 0; i < pairs.size(); i += 2) registerOffset(pairs[i]);
|
||||
for (std::size_t i = 0; i < pairs.size(); i += 2) writeRegister(queue, opcode, registerOffset(pairs[i]), pairs[i + 1]);
|
||||
if (queue.savedContext.has_value() && TraceContextState()) {
|
||||
std::fprintf(stderr, "[context] indirect 0x%x:", opcode);
|
||||
for (std::size_t i = 0; i < pairs.size(); i += 2) std::fprintf(stderr, " %x", registerOffset(pairs[i]));
|
||||
std::fprintf(stderr, "\n");
|
||||
}
|
||||
}
|
||||
|
||||
void Execute(std::span<const std::uint32_t> packet, QueueState& queue) {
|
||||
const auto opcode = (packet[0] >> 8u) & 0xffu;
|
||||
switch (opcode) {
|
||||
|
|
@ -763,20 +802,9 @@ void Execute(std::span<const std::uint32_t> packet, QueueState& queue) {
|
|||
case 0x26: queue.indexBase = address(packet[1], packet[2]); return;
|
||||
case 0x2a: queue.indexType = packet[1]; return;
|
||||
case 0x2f: queue.instanceCount = packet[1]; return;
|
||||
case 0x63: case 0x64: case 0x9f: {
|
||||
std::vector<std::uint32_t> pairs(static_cast<std::size_t>(packet[4]) * 2);
|
||||
// Named for the [hooksync] attribution (the read goes through the flush hook).
|
||||
const GuestMemory::ReadSiteScope site(GuestMemory::ReadSite::Registers);
|
||||
GuestMemory::Read(address(packet[1], packet[2]), std::as_writable_bytes(std::span(pairs)), 4);
|
||||
for (std::size_t i = 0; i < pairs.size(); i += 2) registerOffset(pairs[i]);
|
||||
for (std::size_t i = 0; i < pairs.size(); i += 2) writeRegister(queue, opcode, registerOffset(pairs[i]), pairs[i + 1]);
|
||||
if (queue.savedContext.has_value() && TraceContextState()) {
|
||||
std::fprintf(stderr, "[context] indirect 0x%x:", opcode);
|
||||
for (std::size_t i = 0; i < pairs.size(); i += 2) std::fprintf(stderr, " %x", registerOffset(pairs[i]));
|
||||
std::fprintf(stderr, "\n");
|
||||
}
|
||||
case 0x63: case 0x64: case 0x9f:
|
||||
ExecuteIndirectRegisters(packet, ReadIndirectRegisters(packet), queue);
|
||||
return;
|
||||
}
|
||||
case 0x59: break;
|
||||
case 0x3c: case 0x93: {
|
||||
// The waited-on value is written by the CPU or another queue; poll it like the CP would.
|
||||
|
|
@ -834,21 +862,17 @@ void Execute(std::span<const std::uint32_t> packet, QueueState& queue) {
|
|||
return;
|
||||
}
|
||||
case 0x40: {
|
||||
const auto source = ((packet[1] & 0xfu) << 1u) | ((packet[1] >> 30u) & 1u);
|
||||
copyMemory(address(packet[2], packet[3]), address(packet[4], packet[5]), (packet[1] & 0x10000u) != 0 ? 8 : 4, source >= 10);
|
||||
const auto destination = address(packet[4], packet[5]);
|
||||
const auto data = copyDataSource(packet, (packet[1] & 0x10000u) != 0 ? 8 : 4);
|
||||
GuestMemory::CheckRange(reinterpret_cast<void*>(destination), data.size(), 1, true);
|
||||
GuestMemory::Write(destination, data);
|
||||
return;
|
||||
}
|
||||
case 0x50: {
|
||||
const std::size_t bytes = packet[6] & 0x3ffffffu;
|
||||
if (bytes == 0) return;
|
||||
auto data = dmaSourceBytes(packet);
|
||||
if (dmaDestination(packet) == DmaSelectGds) {
|
||||
auto& gds = Gds();
|
||||
std::lock_guard lock(gds.mutex);
|
||||
std::copy(data.begin(), data.end(), gds.bytes.begin() + packet[4]);
|
||||
return;
|
||||
}
|
||||
const auto destination = address(packet[4], packet[5]);
|
||||
const auto destination = dmaDestination(packet) == DmaSelectGds ? GdsAddress() + packet[4] : address(packet[4], packet[5]);
|
||||
GuestMemory::CheckRange(reinterpret_cast<void*>(destination), bytes, 1, true);
|
||||
GuestMemory::Write(destination, data);
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,80 @@
|
|||
#include "prx/libSceAgcDriver/Execution/include/ShaderDeviceProfile.hpp"
|
||||
#include "BdaAbi.hpp"
|
||||
#include <spirv/unified1/spirv.hpp>
|
||||
#include <algorithm>
|
||||
|
||||
namespace AgcDriver {
|
||||
namespace {
|
||||
|
||||
template<typename TFeatures>
|
||||
const TFeatures* findFeatures(const void* chain, VkStructureType type) {
|
||||
auto* current = static_cast<const VkBaseInStructure*>(chain);
|
||||
while (current != nullptr) {
|
||||
if (current->sType == type) return reinterpret_cast<const TFeatures*>(current);
|
||||
current = current->pNext;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ShaderDeviceProfile::ShaderDeviceProfile(const ShaderRecompiler::SpirvTarget& target, const VkDeviceCreateInfo& deviceInfo, const VkPhysicalDeviceLimits& limits) : target(target), limits(limits), capabilities(target.supportedCapabilities.begin(), target.supportedCapabilities.end()) {
|
||||
using Graphics::Require;
|
||||
Require(deviceInfo.pEnabledFeatures != nullptr, "shader device profile requires enabled core features");
|
||||
const auto& core = *deviceInfo.pEnabledFeatures;
|
||||
const auto* robustness = findFeatures<VkPhysicalDeviceRobustness2FeaturesEXT>(deviceInfo.pNext, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ROBUSTNESS_2_FEATURES_EXT);
|
||||
nullDescriptors = robustness != nullptr && robustness->nullDescriptor == VK_TRUE;
|
||||
const auto* bda = findFeatures<VkPhysicalDeviceBufferDeviceAddressFeatures>(deviceInfo.pNext, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES);
|
||||
const auto* bytes = findFeatures<VkPhysicalDevice8BitStorageFeatures>(deviceInfo.pNext, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_8BIT_STORAGE_FEATURES);
|
||||
const auto* indexing = findFeatures<VkPhysicalDeviceDescriptorIndexingFeatures>(deviceInfo.pNext, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES);
|
||||
const auto* barycentric = findFeatures<VkPhysicalDeviceFragmentShaderBarycentricFeaturesKHR>(deviceInfo.pNext, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADER_BARYCENTRIC_FEATURES_KHR);
|
||||
const auto* mesh = findFeatures<VkPhysicalDeviceMeshShaderFeaturesEXT>(deviceInfo.pNext, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MESH_SHADER_FEATURES_EXT);
|
||||
const auto* clock = findFeatures<VkPhysicalDeviceShaderClockFeaturesKHR>(deviceInfo.pNext, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_CLOCK_FEATURES_KHR);
|
||||
const auto* maintenance8 = findFeatures<VkPhysicalDeviceMaintenance8FeaturesKHR>(deviceInfo.pNext, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_8_FEATURES_KHR);
|
||||
Require(target.bdaAbiVersion == ShaderRecompiler::BdaAbi::Version, "shader device profile has an incompatible BDA ABI");
|
||||
Require(bda != nullptr && bda->bufferDeviceAddress == VK_TRUE && core.shaderInt64 == VK_TRUE, "shader runtime requires enabled bufferDeviceAddress and shaderInt64");
|
||||
Require(bytes != nullptr && bytes->storageBuffer8BitAccess == VK_TRUE, "shader runtime requires enabled storageBuffer8BitAccess");
|
||||
Require(nullDescriptors, "shader runtime requires enabled nullDescriptor");
|
||||
Require(core.vertexPipelineStoresAndAtomics == VK_TRUE && core.fragmentStoresAndAtomics == VK_TRUE, "shader runtime requires enabled graphics stores and atomics");
|
||||
Require(limits.maxPushConstantsSize >= ShaderRecompiler::RuntimeAbi::PushConstantDwords * sizeof(std::uint32_t) && limits.maxBoundDescriptorSets > ShaderRecompiler::RuntimeAbi::DescriptorSet, "shader runtime ABI exceeds device limits");
|
||||
Require(limits.maxStorageBufferRange >= sizeof(ShaderRecompiler::RuntimeAbi::ShaderData), "shader runtime ShaderData exceeds device limits");
|
||||
std::ranges::sort(capabilities);
|
||||
capabilities.erase(std::unique(capabilities.begin(), capabilities.end()), capabilities.end());
|
||||
const auto hasCapability = [&](spv::Capability capability) { return std::ranges::binary_search(capabilities, static_cast<std::uint32_t>(capability)); };
|
||||
Require(hasCapability(spv::CapabilityShader) && hasCapability(spv::CapabilityInt64) && hasCapability(spv::CapabilityPhysicalStorageBufferAddresses) && hasCapability(spv::CapabilityStorageBuffer8BitAccess), "shader device profile is missing runtime capabilities");
|
||||
const auto checkFeature = [&](spv::Capability capability, bool enabled, const char* name) {
|
||||
Require(!hasCapability(capability) || enabled, std::string("shader device profile advertises a disabled feature: ") + name);
|
||||
};
|
||||
checkFeature(spv::CapabilityFloat64, core.shaderFloat64 == VK_TRUE, "shaderFloat64");
|
||||
checkFeature(spv::CapabilityTessellation, core.tessellationShader == VK_TRUE, "tessellationShader");
|
||||
checkFeature(spv::CapabilityImageGatherExtended, core.shaderImageGatherExtended == VK_TRUE, "shaderImageGatherExtended");
|
||||
checkFeature(spv::CapabilityMinLod, core.shaderResourceMinLod == VK_TRUE, "shaderResourceMinLod");
|
||||
checkFeature(spv::CapabilityStorageImageMultisample, core.shaderStorageImageMultisample == VK_TRUE, "shaderStorageImageMultisample");
|
||||
checkFeature(spv::CapabilityStorageImageReadWithoutFormat, core.shaderStorageImageReadWithoutFormat == VK_TRUE, "shaderStorageImageReadWithoutFormat");
|
||||
checkFeature(spv::CapabilityStorageImageWriteWithoutFormat, core.shaderStorageImageWriteWithoutFormat == VK_TRUE, "shaderStorageImageWriteWithoutFormat");
|
||||
checkFeature(spv::CapabilitySampledImageArrayDynamicIndexing, core.shaderSampledImageArrayDynamicIndexing == VK_TRUE, "shaderSampledImageArrayDynamicIndexing");
|
||||
checkFeature(spv::CapabilityStorageImageArrayDynamicIndexing, core.shaderStorageImageArrayDynamicIndexing == VK_TRUE, "shaderStorageImageArrayDynamicIndexing");
|
||||
const bool sampledNonUniform = indexing != nullptr && indexing->shaderSampledImageArrayNonUniformIndexing == VK_TRUE;
|
||||
const bool storageNonUniform = indexing != nullptr && indexing->shaderStorageImageArrayNonUniformIndexing == VK_TRUE;
|
||||
checkFeature(spv::CapabilitySampledImageArrayNonUniformIndexing, sampledNonUniform, "shaderSampledImageArrayNonUniformIndexing");
|
||||
checkFeature(spv::CapabilityStorageImageArrayNonUniformIndexing, storageNonUniform, "shaderStorageImageArrayNonUniformIndexing");
|
||||
checkFeature(spv::CapabilityShaderNonUniform, sampledNonUniform || storageNonUniform, "descriptor indexing");
|
||||
const bool barycentricEnabled = barycentric != nullptr && barycentric->fragmentShaderBarycentric == VK_TRUE;
|
||||
const bool meshEnabled = mesh != nullptr && mesh->meshShader == VK_TRUE;
|
||||
checkFeature(spv::CapabilityFragmentBarycentricKHR, barycentricEnabled, "fragmentShaderBarycentric");
|
||||
checkFeature(spv::CapabilityMeshShadingEXT, meshEnabled, "meshShader");
|
||||
checkFeature(spv::CapabilityShaderClockKHR, clock != nullptr && clock->shaderSubgroupClock == VK_TRUE && clock->shaderDeviceClock == VK_TRUE, "shader clock");
|
||||
Require(!target.fragmentShaderBarycentricEnabled || barycentricEnabled, "shader device profile exposes disabled barycentrics");
|
||||
Require(!target.mesh || meshEnabled, "shader device profile exposes disabled mesh shaders");
|
||||
Require(!target.nonConstantImageOffsets || (maintenance8 != nullptr && maintenance8->maintenance8 == VK_TRUE), "shader device profile exposes disabled maintenance8");
|
||||
Require(!target.tessellation || core.tessellationShader == VK_TRUE, "shader device profile exposes disabled tessellation");
|
||||
for (const auto extension : target.supportedExtensions) extensions.emplace_back(extension);
|
||||
std::ranges::sort(extensions);
|
||||
extensions.erase(std::unique(extensions.begin(), extensions.end()), extensions.end());
|
||||
for (const auto& extension : extensions) extensionViews.emplace_back(extension);
|
||||
Require(std::ranges::find(extensionViews, "SPV_KHR_physical_storage_buffer") != extensionViews.end() && std::ranges::find(extensionViews, "SPV_KHR_8bit_storage") != extensionViews.end(), "shader device profile is missing runtime extensions");
|
||||
this->target.supportedCapabilities = capabilities;
|
||||
this->target.supportedExtensions = extensionViews;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -212,6 +212,14 @@ void ShaderMemory::CountHandleMemo(bool hit) {
|
|||
}
|
||||
|
||||
std::shared_ptr<const ShaderRecompiler::ResourceCapture> ShaderMemory::Capture(const ShaderRecompiler::RecompileRequest& request, const ShaderRecompiler::SourceHandle* handle) {
|
||||
return capture(request, handle, nullptr);
|
||||
}
|
||||
|
||||
std::shared_ptr<const ShaderRecompiler::ResourceCapture> ShaderMemory::Capture(const ShaderRecompiler::PreparedShaderInvocation& invocation) {
|
||||
return capture(invocation.Request(), nullptr, &invocation);
|
||||
}
|
||||
|
||||
std::shared_ptr<const ShaderRecompiler::ResourceCapture> ShaderMemory::capture(const ShaderRecompiler::RecompileRequest& request, const ShaderRecompiler::SourceHandle* handle, const ShaderRecompiler::PreparedShaderInvocation* invocation) {
|
||||
// The capture's word and page reads (through `read`) are attributed to it ([hooksync], [guestmem]).
|
||||
const GuestMemory::ReadSiteScope site(GuestMemory::ReadSite::Capture);
|
||||
const bool profile = CaptureProfiled();
|
||||
|
|
@ -225,7 +233,7 @@ std::shared_ptr<const ShaderRecompiler::ResourceCapture> ShaderMemory::Capture(c
|
|||
runtime.userContext = this;
|
||||
runtime.readMemory = &read;
|
||||
runtime.readSpecializationMemory = &read;
|
||||
auto capture = handle != nullptr ? ShaderRecompiler::CaptureResources(request, runtime, *handle) : ShaderRecompiler::CaptureResources(request, runtime);
|
||||
auto capture = invocation != nullptr ? invocation->Capture(runtime) : handle != nullptr ? ShaderRecompiler::CaptureResources(request, runtime, *handle) : ShaderRecompiler::CaptureResources(request, runtime);
|
||||
if (profile) {
|
||||
totals.captureNanoseconds += NanosecondsSince(started);
|
||||
totals.resolveNanoseconds += capture->sourceNanoseconds;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
#include "prx/libSceAgcDriver/Execution/include/SubgroupClock.hpp"
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
|
||||
namespace AgcDriver {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr std::array<std::string_view, 19> NarrowClockFamilies{
|
||||
"NAVI21", "NAVI22", "VANGOGH", "NAVI23", "NAVI24", "REMBRANDT", "RAPHAEL_MENDOCINO",
|
||||
"NAVI31", "NAVI32", "NAVI33", "PHOENIX", "PHOENIX2",
|
||||
"STRIX1", "STRIX_HALO", "KRACKAN1", "GFX1153", "GFX1156",
|
||||
"GFX1170", "GFX1171",
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
bool NarrowSubgroupClock(VkDriverId driver, std::string_view deviceName) {
|
||||
if (driver != VK_DRIVER_ID_MESA_RADV) return false;
|
||||
constexpr std::string_view prefix = "(RADV ";
|
||||
const auto start = deviceName.rfind(prefix);
|
||||
if (start == std::string_view::npos || deviceName.empty() || deviceName.back() != ')') return false;
|
||||
const auto family = deviceName.substr(start + prefix.size(), deviceName.size() - start - prefix.size() - 1u);
|
||||
return std::find(NarrowClockFamilies.begin(), NarrowClockFamilies.end(), family) != NarrowClockFamilies.end();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,10 +1,13 @@
|
|||
#include "prx/libSceAgcDriver/Execution/include/ProfileOutput.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/PipelineSpecialization.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/CaptureTrace.hpp"
|
||||
#include "BdaAbi.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/Driver.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/PerformanceTimer.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/BdaFeatures.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/SubgroupClock.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/ShaderDeviceProfile.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/PresentationScaler.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/DccMetadata.hpp"
|
||||
|
|
@ -187,12 +190,14 @@ struct VulkanDevice::State {
|
|||
VkPhysicalDeviceSubgroupProperties subgroup{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_PROPERTIES};
|
||||
std::vector<std::uint32_t> capabilities{1};
|
||||
std::vector<std::string_view> spirvExtensions;
|
||||
std::unique_ptr<const ShaderDeviceProfile> shaderProfile;
|
||||
bool tessellationShader = false;
|
||||
bool meshShader = false;
|
||||
bool fragmentShaderBarycentric = false;
|
||||
bool geometryShader = false;
|
||||
bool sampleRateShading = false;
|
||||
bool shaderClock = false;
|
||||
bool narrowSubgroupClock = false;
|
||||
// VK_EXT_descriptor_indexing with non-uniform image array indexing (bindless image tables in
|
||||
// graphics stages, and compute workgroups wider than a wave).
|
||||
bool descriptorIndexing = false;
|
||||
|
|
@ -208,13 +213,17 @@ struct VulkanDevice::State {
|
|||
bool depthBiasClamp = false;
|
||||
bool occlusionQueryPrecise = false;
|
||||
VkDeviceSize hostImportAlignment = 0;
|
||||
bool dmaBufImport = false;
|
||||
bool depthRangeUnrestricted = false;
|
||||
bool samplerAnisotropy = false;
|
||||
bool textureCompressionBC = false;
|
||||
bool samplerFilterMinmax = false;
|
||||
bool fragmentShaderPixelInterlock = false;
|
||||
bool conservativeRasterization = false;
|
||||
// VK_KHR_timeline_semaphore enabled: the recorder's unlocked waits are available.
|
||||
bool timelineSemaphores = false;
|
||||
bool computeWave32 = false;
|
||||
std::uint32_t maxComputeSubgroupSize = 0;
|
||||
// Indirect draw features enabled (see Graphics::Context).
|
||||
bool drawIndirectFirstInstance = false;
|
||||
bool multiDrawIndirect = false;
|
||||
|
|
@ -523,6 +532,7 @@ struct VulkanDevice::State {
|
|||
resourceCache.Clear();
|
||||
Graphics::ClearCachedTextures(device);
|
||||
Graphics::ClearImageMirrors(device);
|
||||
Graphics::ClearHostImports(device);
|
||||
patternBuffers.clear();
|
||||
descriptorCache.reset();
|
||||
emptyBuffer.reset();
|
||||
|
|
@ -713,6 +723,7 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window) : state(std::make_u
|
|||
state->capabilities.push_back(spv::CapabilityGroupNonUniform);
|
||||
if ((state->subgroup.supportedOperations & VK_SUBGROUP_FEATURE_BALLOT_BIT) != 0) state->capabilities.push_back(spv::CapabilityGroupNonUniformBallot);
|
||||
if ((state->subgroup.supportedOperations & VK_SUBGROUP_FEATURE_SHUFFLE_BIT) != 0) state->capabilities.push_back(spv::CapabilityGroupNonUniformShuffle);
|
||||
if ((state->subgroup.supportedOperations & VK_SUBGROUP_FEATURE_ARITHMETIC_BIT) != 0) state->capabilities.push_back(spv::CapabilityGroupNonUniformArithmetic);
|
||||
}
|
||||
APS5_LOG_OUT("Selected GPU name=%s vendor=0x%x device=0x%x subgroup=%u", state->properties.deviceName, state->properties.vendorID, state->properties.deviceID, state->subgroup.subgroupSize);
|
||||
state->InstanceFunction<PFN_vkGetPhysicalDeviceMemoryProperties>("vkGetPhysicalDeviceMemoryProperties")(selected, &state->memoryProperties);
|
||||
|
|
@ -749,12 +760,27 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window) : state(std::make_u
|
|||
state->InstanceFunction<PFN_vkGetPhysicalDeviceFeatures2>("vkGetPhysicalDeviceFeatures2")(selected, &features);
|
||||
state->fragmentShaderBarycentric = barycentricFeatures.fragmentShaderBarycentric == VK_TRUE;
|
||||
}
|
||||
VkPhysicalDeviceFragmentShaderInterlockFeaturesEXT interlockFeatures{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADER_INTERLOCK_FEATURES_EXT};
|
||||
if (hasExtension(VK_EXT_FRAGMENT_SHADER_INTERLOCK_EXTENSION_NAME)) {
|
||||
VkPhysicalDeviceFeatures2 features{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2, &interlockFeatures};
|
||||
state->InstanceFunction<PFN_vkGetPhysicalDeviceFeatures2>("vkGetPhysicalDeviceFeatures2")(selected, &features);
|
||||
state->fragmentShaderPixelInterlock = interlockFeatures.fragmentShaderPixelInterlock == VK_TRUE;
|
||||
}
|
||||
interlockFeatures = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADER_INTERLOCK_FEATURES_EXT};
|
||||
interlockFeatures.fragmentShaderPixelInterlock = VK_TRUE;
|
||||
VkPhysicalDeviceShaderClockFeaturesKHR clockFeatures{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_CLOCK_FEATURES_KHR};
|
||||
if (hasExtension(VK_KHR_SHADER_CLOCK_EXTENSION_NAME)) {
|
||||
VkPhysicalDeviceFeatures2 features{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2, &clockFeatures};
|
||||
state->InstanceFunction<PFN_vkGetPhysicalDeviceFeatures2>("vkGetPhysicalDeviceFeatures2")(selected, &features);
|
||||
state->shaderClock = clockFeatures.shaderSubgroupClock == VK_TRUE && clockFeatures.shaderDeviceClock == VK_TRUE;
|
||||
}
|
||||
if (state->shaderClock && hasExtension(VK_KHR_DRIVER_PROPERTIES_EXTENSION_NAME)) {
|
||||
VkPhysicalDeviceDriverProperties driver{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES};
|
||||
VkPhysicalDeviceProperties2 driverProperties{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2, &driver};
|
||||
state->InstanceFunction<PFN_vkGetPhysicalDeviceProperties2>("vkGetPhysicalDeviceProperties2")(selected, &driverProperties);
|
||||
state->narrowSubgroupClock = NarrowSubgroupClock(driver.driverID, state->properties.deviceName);
|
||||
APS5_LOG_OUT("Shader clock driver=%d narrowSubgroupClock=%d", static_cast<int>(driver.driverID), state->narrowSubgroupClock ? 1 : 0);
|
||||
}
|
||||
std::vector<const char*> deviceExtensions;
|
||||
if (window != nullptr) deviceExtensions.assign(presentationExtensions.begin(), presentationExtensions.end());
|
||||
if (state->fragmentShaderBarycentric) {
|
||||
|
|
@ -762,6 +788,11 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window) : state(std::make_u
|
|||
state->capabilities.push_back(spv::CapabilityFragmentBarycentricKHR);
|
||||
state->spirvExtensions.push_back("SPV_KHR_fragment_shader_barycentric");
|
||||
}
|
||||
if (state->fragmentShaderPixelInterlock) {
|
||||
deviceExtensions.push_back(VK_EXT_FRAGMENT_SHADER_INTERLOCK_EXTENSION_NAME);
|
||||
state->capabilities.push_back(spv::CapabilityFragmentShaderPixelInterlockEXT);
|
||||
state->spirvExtensions.push_back("SPV_EXT_fragment_shader_interlock");
|
||||
}
|
||||
if (state->shaderClock) {
|
||||
deviceExtensions.push_back(VK_KHR_SHADER_CLOCK_EXTENSION_NAME);
|
||||
state->capabilities.push_back(spv::CapabilityShaderClockKHR);
|
||||
|
|
@ -773,6 +804,7 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window) : state(std::make_u
|
|||
state->InstanceFunction<PFN_vkGetPhysicalDeviceFeatures2>("vkGetPhysicalDeviceFeatures2")(selected, &features);
|
||||
}
|
||||
const bool bufferInt64Atomics = atomicInt64Features.shaderBufferInt64Atomics == VK_TRUE;
|
||||
if (bufferInt64Atomics) state->capabilities.push_back(spv::CapabilityInt64Atomics);
|
||||
atomicInt64Features = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_ATOMIC_INT64_FEATURES_KHR};
|
||||
atomicInt64Features.shaderBufferInt64Atomics = VK_TRUE;
|
||||
if (bufferInt64Atomics) deviceExtensions.push_back(VK_KHR_SHADER_ATOMIC_INT64_EXTENSION_NAME);
|
||||
|
|
@ -813,6 +845,13 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window) : state(std::make_u
|
|||
state->samplerFilterMinmax = minmaxProperties.filterMinmaxSingleComponentFormats == VK_TRUE && minmaxProperties.filterMinmaxImageComponentMapping == VK_TRUE;
|
||||
if (state->samplerFilterMinmax) deviceExtensions.push_back(VK_EXT_SAMPLER_FILTER_MINMAX_EXTENSION_NAME);
|
||||
}
|
||||
if (hasExtension(VK_EXT_CONSERVATIVE_RASTERIZATION_EXTENSION_NAME)) {
|
||||
VkPhysicalDeviceConservativeRasterizationPropertiesEXT conservativeProperties{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CONSERVATIVE_RASTERIZATION_PROPERTIES_EXT};
|
||||
VkPhysicalDeviceProperties2 properties{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2, &conservativeProperties};
|
||||
state->InstanceFunction<PFN_vkGetPhysicalDeviceProperties2>("vkGetPhysicalDeviceProperties2")(selected, &properties);
|
||||
state->conservativeRasterization = conservativeProperties.primitiveOverestimationSize <= 1.0f / 256.0f && conservativeProperties.degenerateTrianglesRasterized == VK_TRUE;
|
||||
if (state->conservativeRasterization) deviceExtensions.push_back(VK_EXT_CONSERVATIVE_RASTERIZATION_EXTENSION_NAME);
|
||||
}
|
||||
// Indirect draws with a GPU-side count (DRAW_INDIRECT_MULTI with count_indirect); a device
|
||||
// without it resolves such draws on the CPU.
|
||||
state->drawIndirectCount = hasExtension(VK_KHR_DRAW_INDIRECT_COUNT_EXTENSION_NAME);
|
||||
|
|
@ -820,7 +859,8 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window) : state(std::make_u
|
|||
// Guest memory is host memory: importing it lets address-based shaders use it in place instead of
|
||||
// copying every registered allocation per draw.
|
||||
if (hasExtension(VK_EXT_EXTERNAL_MEMORY_HOST_EXTENSION_NAME) && std::getenv("APS5_NO_HOST_IMPORT") == nullptr) {
|
||||
VkPhysicalDeviceExternalMemoryHostPropertiesEXT hostProperties{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_MEMORY_HOST_PROPERTIES_EXT};
|
||||
VkPhysicalDeviceDriverProperties driverProperties{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES};
|
||||
VkPhysicalDeviceExternalMemoryHostPropertiesEXT hostProperties{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_MEMORY_HOST_PROPERTIES_EXT, &driverProperties};
|
||||
VkPhysicalDeviceProperties2 properties{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2, &hostProperties};
|
||||
state->InstanceFunction<PFN_vkGetPhysicalDeviceProperties2>("vkGetPhysicalDeviceProperties2")(selected, &properties);
|
||||
state->hostImportAlignment = hostProperties.minImportedHostPointerAlignment;
|
||||
|
|
@ -832,6 +872,13 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window) : state(std::make_u
|
|||
}
|
||||
#endif
|
||||
deviceExtensions.push_back(VK_EXT_EXTERNAL_MEMORY_HOST_EXTENSION_NAME);
|
||||
#ifndef _WIN32
|
||||
state->dmaBufImport = driverProperties.driverID != VK_DRIVER_ID_NVIDIA_PROPRIETARY && hasExtension(VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME) && hasExtension(VK_EXT_EXTERNAL_MEMORY_DMA_BUF_EXTENSION_NAME);
|
||||
if (state->dmaBufImport) {
|
||||
deviceExtensions.push_back(VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME);
|
||||
deviceExtensions.push_back(VK_EXT_EXTERNAL_MEMORY_DMA_BUF_EXTENSION_NAME);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
VkPhysicalDevicePrimitiveTopologyListRestartFeaturesEXT listRestartFeatures{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRIMITIVE_TOPOLOGY_LIST_RESTART_FEATURES_EXT};
|
||||
if (hasExtension(VK_EXT_PRIMITIVE_TOPOLOGY_LIST_RESTART_EXTENSION_NAME)) {
|
||||
|
|
@ -914,6 +961,9 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window) : state(std::make_u
|
|||
enabled.occlusionQueryPrecise = available.occlusionQueryPrecise;
|
||||
state->occlusionQueryPrecise = enabled.occlusionQueryPrecise == VK_TRUE;
|
||||
// Recompiled storage-image access declares no format (the guest descriptor decides it).
|
||||
enabled.shaderStorageImageMultisample = available.shaderStorageImageMultisample;
|
||||
if (enabled.shaderStorageImageMultisample) state->capabilities.push_back(spv::CapabilityStorageImageMultisample);
|
||||
state->capabilities.push_back(spv::CapabilityImageMSArray);
|
||||
enabled.shaderStorageImageWriteWithoutFormat = available.shaderStorageImageWriteWithoutFormat;
|
||||
enabled.shaderStorageImageReadWithoutFormat = available.shaderStorageImageReadWithoutFormat;
|
||||
// Gathers with non-constant offsets (ImageGatherExtended).
|
||||
|
|
@ -980,6 +1030,10 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window) : state(std::make_u
|
|||
barycentricFeatures.pNext = byteFeatures.pNext;
|
||||
byteFeatures.pNext = &barycentricFeatures;
|
||||
}
|
||||
if (state->fragmentShaderPixelInterlock) {
|
||||
interlockFeatures.pNext = byteFeatures.pNext;
|
||||
byteFeatures.pNext = &interlockFeatures;
|
||||
}
|
||||
if (state->shaderClock) {
|
||||
clockFeatures.pNext = byteFeatures.pNext;
|
||||
byteFeatures.pNext = &clockFeatures;
|
||||
|
|
@ -1011,7 +1065,7 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window) : state(std::make_u
|
|||
state->timelineSemaphores = timelineFeatures.timelineSemaphore == VK_TRUE;
|
||||
}
|
||||
VkPhysicalDeviceSubgroupSizeControlFeaturesEXT subgroupSizeFeatures{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_SIZE_CONTROL_FEATURES_EXT};
|
||||
if (hasExtension(VK_EXT_SUBGROUP_SIZE_CONTROL_EXTENSION_NAME) && state->subgroup.subgroupSize > 32u) {
|
||||
if (hasExtension(VK_EXT_SUBGROUP_SIZE_CONTROL_EXTENSION_NAME) && state->subgroup.subgroupSize >= 32u) {
|
||||
VkPhysicalDeviceFeatures2 features{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2, &subgroupSizeFeatures};
|
||||
state->InstanceFunction<PFN_vkGetPhysicalDeviceFeatures2>("vkGetPhysicalDeviceFeatures2")(selected, &features);
|
||||
VkPhysicalDeviceSubgroupSizeControlPropertiesEXT subgroupSize{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_SIZE_CONTROL_PROPERTIES_EXT};
|
||||
|
|
@ -1019,6 +1073,7 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window) : state(std::make_u
|
|||
state->InstanceFunction<PFN_vkGetPhysicalDeviceProperties2>("vkGetPhysicalDeviceProperties2")(selected, &sizeProperties);
|
||||
state->computeWave32 = subgroupSizeFeatures.subgroupSizeControl == VK_TRUE && subgroupSize.minSubgroupSize <= 32u && subgroupSize.maxSubgroupSize >= 32u &&
|
||||
(subgroupSize.requiredSubgroupSizeStages & VK_SHADER_STAGE_COMPUTE_BIT) != 0;
|
||||
state->maxComputeSubgroupSize = subgroupSize.maxSubgroupSize;
|
||||
}
|
||||
if (state->computeWave32) {
|
||||
subgroupSizeFeatures = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_SIZE_CONTROL_FEATURES_EXT};
|
||||
|
|
@ -1056,14 +1111,33 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window) : state(std::make_u
|
|||
byteFeatures.pNext = &pipelineFeatures;
|
||||
}
|
||||
bdaFeatures.pNext = &byteFeatures;
|
||||
VkPhysicalDeviceRobustness2FeaturesEXT robustness2{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ROBUSTNESS_2_FEATURES_EXT};
|
||||
Graphics::Require(hasExtension(VK_EXT_ROBUSTNESS_2_EXTENSION_NAME), "shader runtime requires VK_EXT_robustness2");
|
||||
VkPhysicalDeviceFeatures2 queried{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2, &robustness2};
|
||||
state->InstanceFunction<PFN_vkGetPhysicalDeviceFeatures2>("vkGetPhysicalDeviceFeatures2")(selected, &queried);
|
||||
Graphics::Require(robustness2.nullDescriptor == VK_TRUE, "shader runtime requires nullDescriptor");
|
||||
robustness2 = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ROBUSTNESS_2_FEATURES_EXT};
|
||||
robustness2.nullDescriptor = VK_TRUE;
|
||||
robustness2.pNext = bdaFeatures.pNext;
|
||||
bdaFeatures.pNext = &robustness2;
|
||||
deviceExtensions.push_back(VK_EXT_ROBUSTNESS_2_EXTENSION_NAME);
|
||||
deviceInfo.enabledExtensionCount = static_cast<std::uint32_t>(deviceExtensions.size());
|
||||
deviceInfo.ppEnabledExtensionNames = deviceExtensions.data();
|
||||
deviceInfo.pNext = &bdaFeatures;
|
||||
auto shaderProfile = std::make_unique<const ShaderDeviceProfile>(buildTarget(), deviceInfo, state->properties.limits);
|
||||
check(state->InstanceFunction<PFN_vkCreateDevice>("vkCreateDevice")(selected, &deviceInfo, nullptr, &state->device), "vkCreateDevice");
|
||||
state->shaderProfile = std::move(shaderProfile);
|
||||
state->DeviceFunction<PFN_vkGetDeviceQueue>("vkGetDeviceQueue")(state->device, family, 0, &state->queue);
|
||||
APS5_LOG_OUT("Vulkan device ready device=%p queue=%p family=%u", reinterpret_cast<void*>(state->device), reinterpret_cast<void*>(state->queue), family);
|
||||
VkCommandPoolCreateInfo poolInfo{VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO};
|
||||
poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
|
||||
poolInfo.queueFamilyIndex = family;
|
||||
check(state->DeviceFunction<PFN_vkCreateCommandPool>("vkCreateCommandPool")(state->device, &poolInfo, nullptr, &state->pool), "vkCreateCommandPool");
|
||||
static const bool procTable = std::getenv("APS5_NO_PROC_TABLE") == nullptr;
|
||||
if (procTable) {
|
||||
Graphics::FillDeviceFunctions(graphicsContext(), state->deviceFunctions);
|
||||
state->functionsReady = true;
|
||||
}
|
||||
Graphics::PrepareImportWatch(graphicsContext());
|
||||
state->bufferPool = std::make_shared<Graphics::BufferPool>(graphicsContext());
|
||||
state->emptyBuffer = std::make_unique<Graphics::Buffer>(graphicsContext(), Graphics::EmptyBufferBytes, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT);
|
||||
|
|
@ -1075,14 +1149,6 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window) : state(std::make_u
|
|||
state->samplerCache = std::make_unique<Graphics::SamplerCache>();
|
||||
state->recorder = std::make_unique<Graphics::Recorder>(graphicsContext(), state->timelineSemaphores);
|
||||
state->recorder->Activate();
|
||||
// The entry points every record site uses, resolved once (APS5_NO_PROC_TABLE=1: per call, as
|
||||
// before), and the context copy graphicsContext() hands out from here on: built after the
|
||||
// recorder and the descriptor cache exist, so it carries them.
|
||||
static const bool procTable = std::getenv("APS5_NO_PROC_TABLE") == nullptr;
|
||||
if (procTable) {
|
||||
Graphics::FillDeviceFunctions(graphicsContext(), state->deviceFunctions);
|
||||
state->functionsReady = true;
|
||||
}
|
||||
state->context = buildContext();
|
||||
state->contextReady = true;
|
||||
if (window != nullptr) {
|
||||
|
|
@ -1498,7 +1564,8 @@ bool VulkanDevice::FillBuffer(std::uint64_t address, std::size_t bytes, std::spa
|
|||
recorder.EndGpuTiming(timing, bytes);
|
||||
recorder.MarkCovered(filledAccess);
|
||||
phase(recordMs);
|
||||
recorder.NotePendingWrite(address, bytes);
|
||||
if (uniform && pattern[0] == (pattern[0] & 0xffu) * 0x01010101u) recorder.NotePendingFill(address, bytes, static_cast<std::uint8_t>(pattern[0]));
|
||||
else recorder.NotePendingWrite(address, bytes);
|
||||
GuestMemory::MarkWritten(address, bytes);
|
||||
phase(notesMs);
|
||||
return true;
|
||||
|
|
@ -1905,37 +1972,30 @@ std::shared_ptr<Graphics::StorageTexture> PresentableResident(const Graphics::Co
|
|||
return resident;
|
||||
}
|
||||
|
||||
Graphics::DccKeys DisplayKeys(const DisplayBuffer& buffer, std::size_t bytes) {
|
||||
const auto keys = Graphics::CurrentDccKeys(buffer.dccAddress, bytes);
|
||||
if (keys != Graphics::DccKeys::Uncompressed && !Graphics::IsDccClear(keys)) {
|
||||
char message[256];
|
||||
std::snprintf(message, sizeof(message), "VideoOut: display buffer 0x%llx reads %s DCC keys at 0x%llx: presenting DCC metadata that is not uniformly uncompressed or fast-cleared is not implemented", static_cast<unsigned long long>(buffer.address), Graphics::DccKeysName(keys), static_cast<unsigned long long>(buffer.dccAddress));
|
||||
throw std::runtime_error(message);
|
||||
}
|
||||
return keys;
|
||||
Graphics::DccKeys ResidentKeys(const Graphics::StorageTexture& resident) {
|
||||
const auto& own = resident.Descriptor();
|
||||
GuestMemory::CollectWritesUncached(own.dccAddress, Graphics::DccKeyBytes(resident.GuestBytes()));
|
||||
return Graphics::ProvedClearKeys(own, resident.GuestBytes(), resident.KeyProof());
|
||||
}
|
||||
|
||||
bool ResidentServesDisplay(const Graphics::StorageTexture& resident, const DisplayBuffer& buffer, std::size_t bytes) {
|
||||
const auto keys = DisplayKeys(buffer, bytes);
|
||||
bool ResidentServesDisplay(const Graphics::StorageTexture& resident, const DisplayBuffer& buffer) {
|
||||
const auto keys = DisplayBufferKeys(buffer);
|
||||
if (keys == Graphics::DccKeys::ClearRegister) {
|
||||
char message[320];
|
||||
std::snprintf(message, sizeof(message), "VideoOut: display buffer 0x%llx reads register-clear DCC keys at 0x%llx over the pending image 0x%llx (DCC 0x%llx, filled keys %s): whether its results precede the clear is not modeled", static_cast<unsigned long long>(buffer.address), static_cast<unsigned long long>(buffer.dccAddress), static_cast<unsigned long long>(resident.Descriptor().baseAddress), static_cast<unsigned long long>(resident.Descriptor().dccAddress), Graphics::DccKeysName(resident.FilledKeys()));
|
||||
throw std::runtime_error(message);
|
||||
}
|
||||
if (Graphics::IsDccClear(resident.FilledKeys()) && resident.FilledKeys() == keys) return false;
|
||||
return Graphics::StorageImageServesKeys(resident, buffer.dccAddress);
|
||||
return Graphics::KeysServeSurface(resident.Descriptor().dccAddress, resident.UploadedKeys(), resident.FilledKeys(), buffer.dccAddress, [&] { return ResidentKeys(resident); }, [&] { return keys; });
|
||||
}
|
||||
|
||||
bool ResidentKeysMoved(const Graphics::StorageTexture& resident) {
|
||||
const auto& own = resident.Descriptor();
|
||||
if (own.dccAddress == 0) return false;
|
||||
GuestMemory::CollectWritesUncached(own.dccAddress, Graphics::DccKeyBytes(resident.GuestBytes()));
|
||||
return Graphics::ProvedClearKeys(own, resident.GuestBytes(), resident.KeyProof()) != resident.UploadedKeys();
|
||||
return resident.Descriptor().dccAddress != 0 && ResidentKeys(resident) != resident.UploadedKeys();
|
||||
}
|
||||
|
||||
std::optional<std::array<std::byte, 4>> CompressedClearPixel(const DisplayBuffer& buffer, std::size_t bytes) {
|
||||
GuestMemory::FlushGpuWrites(buffer.address, bytes);
|
||||
const auto keys = DisplayKeys(buffer, bytes);
|
||||
const auto keys = DisplayBufferKeys(buffer);
|
||||
if (keys == Graphics::DccKeys::Uncompressed) return std::nullopt;
|
||||
return DisplayBufferClearPixel(buffer, keys);
|
||||
}
|
||||
|
|
@ -1989,7 +2049,7 @@ bool VulkanDevice::PresentDisplayBuffer(const DisplayBuffer& buffer) {
|
|||
if (!NoResidentPresent()) {
|
||||
bool pending = false;
|
||||
resident = PresentableResident(graphicsContext(), buffer, filter, pending, convert);
|
||||
if (resident != nullptr && buffer.dccAddress != 0 && !ResidentServesDisplay(*resident, buffer, bytes)) {
|
||||
if (resident != nullptr && buffer.dccAddress != 0 && !ResidentServesDisplay(*resident, buffer)) {
|
||||
resident.reset();
|
||||
convert = false;
|
||||
}
|
||||
|
|
@ -2389,10 +2449,16 @@ std::string VulkanDevice::DeviceName() const {
|
|||
}
|
||||
|
||||
ShaderRecompiler::SpirvTarget VulkanDevice::Target() const {
|
||||
require(state->shaderProfile != nullptr, "shader device profile is unavailable");
|
||||
return state->shaderProfile->Target();
|
||||
}
|
||||
|
||||
ShaderRecompiler::SpirvTarget VulkanDevice::buildTarget() const {
|
||||
const auto& limits = state->properties.limits;
|
||||
ShaderRecompiler::SpirvTarget target{VK_API_VERSION_1_1, state->meshShader ? 0x00010400u : 0x00010300u, state->subgroup.subgroupSize, ShaderRecompiler::BdaAbi::Version, state->capabilities, state->spirvExtensions, false, {limits.maxComputeWorkGroupSize[0], limits.maxComputeWorkGroupSize[1], limits.maxComputeWorkGroupSize[2]}, limits.maxComputeWorkGroupInvocations, limits.maxComputeSharedMemorySize, {}, {}};
|
||||
target.fragmentShaderBarycentricEnabled = state->fragmentShaderBarycentric;
|
||||
target.nonConstantImageOffsets = state->maintenance8;
|
||||
target.narrowSubgroupClock = state->narrowSubgroupClock;
|
||||
target.srgbDecodeFormats = state->srgbDecodeFormats;
|
||||
if (state->meshShader) {
|
||||
const auto& mesh = state->meshLimits;
|
||||
|
|
@ -2410,6 +2476,10 @@ bool VulkanDevice::SamplerFilterMinmax() const {
|
|||
return state->samplerFilterMinmax;
|
||||
}
|
||||
|
||||
bool VulkanDevice::ConservativeRasterization() const {
|
||||
return state->conservativeRasterization;
|
||||
}
|
||||
|
||||
Graphics::Context VulkanDevice::graphicsContext() const {
|
||||
static const bool noCache = std::getenv("APS5_NO_CONTEXT_CACHE") != nullptr;
|
||||
if (state->contextReady && !noCache) return state->context;
|
||||
|
|
@ -2445,6 +2515,7 @@ Graphics::Context VulkanDevice::buildContext() const {
|
|||
state->depthClamp,
|
||||
state->hostImportAlignment
|
||||
};
|
||||
context.dmaBufImport = state->dmaBufImport;
|
||||
context.recorder = state->recorder.get();
|
||||
context.descriptorCache = state->descriptorCache.get();
|
||||
context.samplerCache = state->samplerCache.get();
|
||||
|
|
@ -2453,6 +2524,7 @@ Graphics::Context VulkanDevice::buildContext() const {
|
|||
context.depthBounds = state->depthBounds;
|
||||
context.depthBiasClamp = state->depthBiasClamp;
|
||||
context.samplerFilterMinmax = state->samplerFilterMinmax;
|
||||
context.conservativeRasterization = state->conservativeRasterization;
|
||||
context.drawIndirectCount = state->drawIndirectCount;
|
||||
context.occlusionQueryPrecise = state->occlusionQueryPrecise;
|
||||
context.emptyBuffer = state->emptyBuffer ? state->emptyBuffer->Handle() : VK_NULL_HANDLE;
|
||||
|
|
@ -2462,6 +2534,7 @@ Graphics::Context VulkanDevice::buildContext() const {
|
|||
context.imageInt64Atomics = state->imageInt64Atomics;
|
||||
context.geometryShader = state->geometryShader;
|
||||
context.sampleRateShading = state->sampleRateShading;
|
||||
context.nullDescriptors = state->shaderProfile != nullptr && state->shaderProfile->NullDescriptors();
|
||||
context.primitiveListRestart = state->primitiveListRestart;
|
||||
context.imageViewMinLod = state->imageViewMinLod;
|
||||
context.pipelineExecutableInfo = state->pipelineExecutableInfo;
|
||||
|
|
@ -2482,6 +2555,10 @@ void VulkanDevice::ColorMetadataPass(const Graphics::ColorMetadataPass& pass) {
|
|||
}
|
||||
|
||||
void VulkanDevice::Draw(const Graphics::State& graphics, const Pm4::DrawParameters& draw, std::span<const Graphics::CompiledShader> shaders, std::span<const Graphics::GuestMemorySnapshot> snapshots, std::shared_ptr<const DrawRecipe>* recipe) {
|
||||
for (const auto& shader : shaders) {
|
||||
require(shader.program != nullptr, "missing compiled shader");
|
||||
ShaderRecompiler::RuntimeAbi::RequireVersion(shader.program->runtimeAbiVersion);
|
||||
}
|
||||
// Two stdout lines per draw cost ~1.3 ms per frame of the queue-0 worker (part of it under the
|
||||
// GPU mutex); APS5_TRACE_DRAWS=1 restores them.
|
||||
static const bool trace = std::getenv("APS5_TRACE_DRAWS") != nullptr;
|
||||
|
|
@ -2888,6 +2965,7 @@ struct RecordedDispatch {
|
|||
DispatchTimer* timer;
|
||||
// Whether the data refresh recorded anything.
|
||||
bool refreshed = false;
|
||||
bool resourceKeyMatched = false;
|
||||
};
|
||||
|
||||
VkDevice VulkanDevice::Device() const {
|
||||
|
|
@ -2991,6 +3069,7 @@ std::uint64_t VulkanDevice::presync(std::span<const std::pair<std::uint64_t, std
|
|||
}
|
||||
|
||||
std::shared_ptr<PreparedDispatch> VulkanDevice::PrepareDispatch(const ShaderRecompiler::RecompileResult& shader, std::span<const Graphics::GuestMemorySnapshot> snapshots) {
|
||||
ShaderRecompiler::RuntimeAbi::RequireVersion(shader.runtimeAbiVersion);
|
||||
// APS5_LOCKED_BUILD=1: the whole build under the mutex, as before the split.
|
||||
static const bool lockedBuild = std::getenv("APS5_LOCKED_BUILD") != nullptr;
|
||||
if (lockedBuild || shader.spirv.size() < 5 || shader.spirv[0] != 0x07230203u) return nullptr;
|
||||
|
|
@ -3010,7 +3089,7 @@ std::shared_ptr<PreparedDispatch> VulkanDevice::PrepareDispatch(const ShaderReco
|
|||
prepared->phaseMs[which] += std::chrono::duration<double, std::milli>(now - phaseStart).count();
|
||||
phaseStart = now;
|
||||
};
|
||||
if (ResourceCacheEnabled() && shader.variantId != 0) {
|
||||
if (ResourceCacheEnabled() && shader.PipelineVariantId() != 0) {
|
||||
prepared->key = DispatchContentKey(compute, context.device);
|
||||
phase(PreparedDispatch::PrepareKey);
|
||||
cached = state->resourceCache.Find(prepared->key);
|
||||
|
|
@ -3206,7 +3285,7 @@ void VulkanDevice::recordDispatch(RecordedDispatch& record) {
|
|||
// after an earlier dispatch's reads of the buffers by that dispatch's trailing barrier. A
|
||||
// recipe compares the two 64-bit hashes first: equal hashes mean the buffers hold the words.
|
||||
const bool differs = record.dataRefresh == RecordedDispatch::DataRefresh::Words || resources.DataWordsHash() != record.dataWordsHash;
|
||||
if (differs && resources.RefreshData(commands, *record.shader, &recorder)) {
|
||||
if (differs && (record.resourceKeyMatched ? resources.refreshData(commands, *record.shader, &recorder) : resources.RefreshData(commands, *record.shader, &recorder))) {
|
||||
covered = 0;
|
||||
++d.templateRefreshed;
|
||||
d.templateRevalidateMs += record.revalidateMs;
|
||||
|
|
@ -3283,6 +3362,7 @@ void VulkanDevice::recordDispatch(RecordedDispatch& record) {
|
|||
}
|
||||
|
||||
VulkanDevice::IndirectOutcome VulkanDevice::dispatch(const ShaderRecompiler::RecompileResult& shader, std::uint32_t x, std::uint32_t y, std::uint32_t z, std::uint64_t arguments, std::span<const Graphics::GuestMemorySnapshot> snapshots, std::uint64_t programAddress, std::shared_ptr<PreparedDispatch> prepared, std::shared_ptr<const Recipe>* recipeOut) {
|
||||
ShaderRecompiler::RuntimeAbi::RequireVersion(shader.runtimeAbiVersion);
|
||||
PerformanceTimer timing("Vulkan.Dispatch");
|
||||
if (recipeOut != nullptr) *recipeOut = nullptr;
|
||||
// Group counts for the trace lines; an indirect dispatch does not know them.
|
||||
|
|
@ -3306,7 +3386,7 @@ VulkanDevice::IndirectOutcome VulkanDevice::dispatch(const ShaderRecompiler::Rec
|
|||
}
|
||||
// Pipelines are shared by dispatches of one compiled variant; descriptor set layouts built from the
|
||||
// same bindings are compatible, so the pipeline layout of the first dispatch serves them all.
|
||||
const std::uint64_t pipelineKey = shader.variantId != 0 ? (shader.variantId << 1u) | (pushStages != 0 ? 1u : 0u) : 0u;
|
||||
const std::uint64_t pipelineKey = shader.PipelineVariantId() != 0 ? (shader.PipelineVariantId() << 1u) | (pushStages != 0 ? 1u : 0u) : 0u;
|
||||
std::shared_ptr<ComputePipelineObjects> objects;
|
||||
if (pipelineKey != 0) {
|
||||
std::lock_guard pipelines(state->computePipelinesMutex);
|
||||
|
|
@ -3322,7 +3402,7 @@ VulkanDevice::IndirectOutcome VulkanDevice::dispatch(const ShaderRecompiler::Rec
|
|||
const auto lookupsBefore = Graphics::DeviceProcLookups();
|
||||
std::shared_ptr<Graphics::ShaderResources> resources;
|
||||
ResourceCache::Key contentKey;
|
||||
const bool cacheable = ResourceCacheEnabled() && shader.variantId != 0;
|
||||
const bool cacheable = ResourceCacheEnabled() && shader.PipelineVariantId() != 0;
|
||||
// The object came from the resource cache: a compute template whose data buffers take this
|
||||
// dispatch's words at the record (ShaderResources::RefreshData).
|
||||
bool fromCache = false;
|
||||
|
|
@ -3475,9 +3555,11 @@ VulkanDevice::IndirectOutcome VulkanDevice::dispatch(const ShaderRecompiler::Rec
|
|||
pipelineInfo.stage.stage = VK_SHADER_STAGE_COMPUTE_BIT;
|
||||
pipelineInfo.stage.module = objects->module;
|
||||
pipelineInfo.stage.pName = "main";
|
||||
const Graphics::PipelineSpecialization specialization(shader);
|
||||
pipelineInfo.stage.pSpecializationInfo = specialization.Info();
|
||||
VkPipelineShaderStageRequiredSubgroupSizeCreateInfoEXT requiredSubgroup{VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_REQUIRED_SUBGROUP_SIZE_CREATE_INFO_EXT};
|
||||
requiredSubgroup.requiredSubgroupSize = shader.hostSubgroupSize;
|
||||
if (state->computeWave32 && shader.hostSubgroupSize == 32u) pipelineInfo.stage.pNext = &requiredSubgroup;
|
||||
requiredSubgroup.requiredSubgroupSize = std::min(shader.hostSubgroupSize, state->maxComputeSubgroupSize);
|
||||
if (state->computeWave32 && (shader.hostSubgroupSize == 32u || shader.hostSubgroupSize > state->maxComputeSubgroupSize)) pipelineInfo.stage.pNext = &requiredSubgroup;
|
||||
pipelineInfo.layout = objects->layout;
|
||||
if (profile && shader.spirv.size() > 100000) std::fprintf(stderr, "[dispatch] creating a pipeline for %zu SPIR-V words (program 0x%llx)\n", shader.spirv.size(), static_cast<unsigned long long>(programAddress));
|
||||
check(state->DeviceFunction<PFN_vkCreateComputePipelines>("vkCreateComputePipelines")(state->device, context.pipelineCache, 1, &pipelineInfo, nullptr, &objects->pipeline), "vkCreateComputePipelines");
|
||||
|
|
@ -3496,6 +3578,7 @@ VulkanDevice::IndirectOutcome VulkanDevice::dispatch(const ShaderRecompiler::Rec
|
|||
auto& recorder = *state->recorder;
|
||||
IndirectOutcome outcome{0, 0};
|
||||
RecordedDispatch record{&context, &shaders[0], resources, objects, pushStages, &pushBytes, x, y, z, arguments, nullptr, programAddress, fromCache && TemplateDataRefresh() ? RecordedDispatch::DataRefresh::Words : RecordedDispatch::DataRefresh::None, 0, revalidateMs, &timer};
|
||||
record.resourceKeyMatched = fromCache;
|
||||
if (arguments != 0) decideIndirect(record, outcome, groupsText);
|
||||
// The indirect hold total below still covers the whole call of a CPU-resolved one.
|
||||
const bool indirect = record.arguments != 0 || outcome.cpuReason != 0;
|
||||
|
|
@ -3523,7 +3606,7 @@ VulkanDevice::IndirectOutcome VulkanDevice::dispatch(const ShaderRecompiler::Rec
|
|||
static auto lastReport = std::chrono::steady_clock::now();
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
const auto ms = std::chrono::duration<double, std::milli>(now - syncStart).count();
|
||||
auto& waits = byProgram[programAddress != 0 ? programAddress : shader.variantId];
|
||||
auto& waits = byProgram[programAddress != 0 ? programAddress : shader.PipelineVariantId()];
|
||||
++waits.count;
|
||||
waits.ms += ms;
|
||||
++syncs;
|
||||
|
|
@ -3571,6 +3654,7 @@ VulkanDevice::IndirectOutcome VulkanDevice::dispatch(const ShaderRecompiler::Rec
|
|||
}
|
||||
|
||||
RecipeOutcome VulkanDevice::DispatchRecipe(const ShaderRecompiler::RecompileResult& shader, std::uint32_t x, std::uint32_t y, std::uint32_t z, std::uint64_t arguments, std::uint64_t programAddress, const std::shared_ptr<RecipeHit>& hit, IndirectOutcome& outcome, const std::shared_ptr<PreparedDispatch>& verify, bool refreshByWords) {
|
||||
ShaderRecompiler::RuntimeAbi::RequireVersion(shader.runtimeAbiVersion);
|
||||
PerformanceTimer timing("Vulkan.DispatchRecipe");
|
||||
outcome = {0, 0};
|
||||
char groupsText[40];
|
||||
|
|
@ -3620,7 +3704,7 @@ RecipeOutcome VulkanDevice::DispatchRecipe(const ShaderRecompiler::RecompileResu
|
|||
return RecipeOutcome::Rebuild;
|
||||
}
|
||||
std::shared_ptr<ComputePipelineObjects> mapped;
|
||||
const auto pipelineKey = (shader.variantId << 1u) | (recipe.pushes ? 1u : 0u);
|
||||
const auto pipelineKey = (shader.PipelineVariantId() << 1u) | (recipe.pushes ? 1u : 0u);
|
||||
{
|
||||
std::lock_guard pipelines(state->computePipelinesMutex);
|
||||
if (const auto found = state->computePipelines.find(pipelineKey); found != state->computePipelines.end()) mapped = found->second;
|
||||
|
|
@ -3665,6 +3749,7 @@ RecipeOutcome VulkanDevice::DispatchRecipe(const ShaderRecompiler::RecompileResu
|
|||
const auto dataRefresh = !TemplateDataRefresh() ? RecordedDispatch::DataRefresh::None : refreshByWords ? RecordedDispatch::DataRefresh::Words : RecordedDispatch::DataRefresh::Hash;
|
||||
if (dataRefresh == RecordedDispatch::DataRefresh::Words) counters.refreshByWords.fetch_add(1, std::memory_order_relaxed);
|
||||
RecordedDispatch record{&context, &shaders[0], hit->resources, hit->objects, recipe.pushes ? VkShaderStageFlags{VK_SHADER_STAGE_COMPUTE_BIT} : VkShaderStageFlags{0}, &recipe.pushBytes, x, y, z, arguments, nullptr, programAddress, dataRefresh, recipe.dataWordsHash, 0, &timer};
|
||||
record.resourceKeyMatched = verify != nullptr;
|
||||
if (arguments != 0) decideIndirect(record, outcome, groupsText);
|
||||
const bool indirect = record.arguments != 0 || outcome.cpuReason != 0;
|
||||
const auto recordStart = profile ? std::chrono::steady_clock::now() : std::chrono::steady_clock::time_point{};
|
||||
|
|
|
|||
|
|
@ -12,11 +12,12 @@ namespace AgcDriver::Graphics {
|
|||
enum class ColorTileMode : std::uint32_t {
|
||||
Linear = 0,
|
||||
Standard4KB = 5,
|
||||
Standard64KB = 9,
|
||||
RenderTarget = 0x1b
|
||||
};
|
||||
|
||||
constexpr TextureTileMode ColorTextureTileMode(ColorTileMode mode) {
|
||||
return mode == ColorTileMode::Linear ? TextureTileMode::kLinear : mode == ColorTileMode::Standard4KB ? TextureTileMode::kStandard4KB : TextureTileMode::kR64KBX;
|
||||
return mode == ColorTileMode::Linear ? TextureTileMode::kLinear : mode == ColorTileMode::Standard4KB ? TextureTileMode::kStandard4KB : mode == ColorTileMode::Standard64KB ? TextureTileMode::kStandard64KB : TextureTileMode::kR64KBX;
|
||||
}
|
||||
|
||||
ColorTileMode DecodeColorTileMode(std::uint32_t attrib3);
|
||||
|
|
|
|||
|
|
@ -106,6 +106,7 @@ struct Context {
|
|||
bool depthClamp = false;
|
||||
// Nonzero when VK_EXT_external_memory_host is enabled: the required host pointer alignment.
|
||||
VkDeviceSize hostImportAlignment = 0;
|
||||
bool dmaBufImport = false;
|
||||
RenderCache* renderCache = nullptr;
|
||||
DrawQueue* drawQueue = nullptr;
|
||||
GraphicsPipelineCache* graphicsPipelines = nullptr;
|
||||
|
|
@ -124,6 +125,7 @@ struct Context {
|
|||
bool depthBounds = false;
|
||||
bool depthBiasClamp = false;
|
||||
bool samplerFilterMinmax = false;
|
||||
bool conservativeRasterization = false;
|
||||
VkBuffer emptyBuffer = VK_NULL_HANDLE;
|
||||
// The device's list of recorded dispatches whose copied written buffers await a CPU write-back
|
||||
// (VulkanDevice's State::copiedWriters; the draw counterpart is DrawCopiedWriters): an indirect
|
||||
|
|
@ -137,6 +139,7 @@ struct Context {
|
|||
bool imageInt64Atomics = false;
|
||||
bool geometryShader = false;
|
||||
bool sampleRateShading = false;
|
||||
bool nullDescriptors = false;
|
||||
bool primitiveListRestart = false;
|
||||
bool imageViewMinLod = false;
|
||||
bool pipelineExecutableInfo = false;
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
#include "prx/libSceAgcDriver/Graphics/include/GuestTextureResource.hpp"
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
|
||||
namespace AgcDriver::Graphics {
|
||||
|
|
@ -22,6 +23,8 @@ enum class DccKeys { Uncompressed, Clear0000, Clear0001, Clear1110, Clear1111, C
|
|||
|
||||
const char* DccKeysName(DccKeys keys);
|
||||
std::size_t DccKeyBytes(std::uint64_t surfaceBytes);
|
||||
std::size_t DccKeyCount(TextureTileMode tileMode, std::uint32_t elementBytes, std::uint32_t width, std::uint32_t height, std::uint64_t surfaceBytes);
|
||||
std::size_t DccKeyCount(const GuestTextureResource& surface, std::uint64_t surfaceBytes);
|
||||
|
||||
template<typename ReadFollowed, typename ReadNamed>
|
||||
bool KeysServeSurface(std::uint64_t followedDcc, DccKeys uploaded, DccKeys filled, std::uint64_t namedDcc, ReadFollowed&& readFollowed, ReadNamed&& readNamed) {
|
||||
|
|
@ -34,15 +37,18 @@ bool KeysServeSurface(std::uint64_t followedDcc, DccKeys uploaded, DccKeys fille
|
|||
DccKeys ReadDccKeys(std::uint64_t metaAddress, std::uint64_t surfaceBytes);
|
||||
bool IsDccClear(DccKeys keys);
|
||||
DccKeys CurrentDccKeys(std::uint64_t metaAddress, std::uint64_t surfaceBytes);
|
||||
DccKeys CurrentDccKeys(std::uint64_t metaAddress, std::uint64_t surfaceBytes, std::size_t keyCount);
|
||||
// Stores "uncompressed" keys over the surface's metadata on the CPU (a guest memory write: it waits
|
||||
// for recorded GPU work that writes the keys first).
|
||||
void MarkDccUncompressed(std::uint64_t metaAddress, std::uint64_t surfaceBytes);
|
||||
void MarkDccUncompressed(std::uint64_t metaAddress, std::uint64_t surfaceBytes, std::size_t keyCount);
|
||||
// The same after a write-back recorded under GuestMemory::GpuMutex: when the keys are in host-imported
|
||||
// memory and a recorder is active, the store is a fill recorded into the open batch (ordered behind
|
||||
// the title's key-writing kernels like the write-back itself, nothing waits on the CPU) and the range
|
||||
// reads as uncompressed from ReadDccKeys while that batch is pending; otherwise the CPU store above.
|
||||
// APS5_CPU_DCC_KEYS=1 always stores on the CPU.
|
||||
void MarkDccUncompressed(const Context& context, std::uint64_t metaAddress, std::uint64_t surfaceBytes);
|
||||
void MarkDccUncompressed(const Context& context, std::uint64_t metaAddress, std::uint64_t surfaceBytes, std::size_t keyCount);
|
||||
// Fills `bytes` with the texel a 0000/0001/1110/1111 clear code stands for ("1" is 1.0 or the integer
|
||||
// maximum; the alpha channel is the last one in memory when alphaOnMsb, else the first, and 3-channel
|
||||
// formats have none). False when the format has no encoding here.
|
||||
|
|
@ -82,6 +88,7 @@ DccKeyProofCounts KeyProofCounts();
|
|||
// A surface's texels as a read sees them: the guest bytes, or the clear value of fast-cleared keys.
|
||||
void ReadTextureSurface(const GuestTextureResource& resource, DccKeys keys, std::span<std::byte> bytes);
|
||||
void NoteKeysFillOnGpu(std::uint64_t begin, std::size_t count, DccKeys keys);
|
||||
std::optional<DccKeys> WaitForKeyWriters(const GuestTextureResource& resource, std::uint64_t guestBytes);
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ struct DrawInputCopy {
|
|||
DrawInputCopy CopyDrawInput(const Context& context, Recorder* recorder, std::uint64_t address, std::size_t bytes, std::size_t alignment, Recorder::SnapshotUse use);
|
||||
void KeepDrawInput(Recorder* recorder, std::uint64_t address, const DrawInputCopy& copy, Recorder::SnapshotUse use, std::uint32_t derived);
|
||||
|
||||
std::array<std::uint32_t, 4> MeshIndexBufferDescriptor(const Pm4::DrawParameters& draw, std::uint64_t unreadAddress);
|
||||
std::array<std::uint32_t, 4> MeshIndexBufferDescriptor(const Pm4::DrawParameters& draw);
|
||||
|
||||
struct MeshArguments {
|
||||
std::uint32_t groups;
|
||||
|
|
|
|||
|
|
@ -27,9 +27,11 @@ struct HostImport {
|
|||
VkDeviceMemory memory;
|
||||
VkDeviceAddress address;
|
||||
void* alias = nullptr;
|
||||
std::weak_ptr<const GuestAllocations::Range> range {};
|
||||
// Identity for the life of this import (see HostImportSerial); 0 until first asked for.
|
||||
std::uint64_t serial = 0;
|
||||
bool unwatched = false;
|
||||
bool dmaBuf = false;
|
||||
};
|
||||
|
||||
enum class ImportWatch : std::uint8_t { Watch, Unwatch };
|
||||
|
|
@ -40,9 +42,11 @@ struct ImportProbe {
|
|||
std::uint32_t pages = 0;
|
||||
std::uint32_t writtenAtImport = 0;
|
||||
std::uint32_t writtenAfterSubmit = 0;
|
||||
std::uint32_t writtenByCpu = 0;
|
||||
};
|
||||
|
||||
ImportProbe ProbeImportWriteProtection(const Context& context);
|
||||
ImportProbe ProbeDmaBufImportWriteProtection(const Context& context);
|
||||
ImportWatch PrepareImportWatch(const Context& context);
|
||||
void SetImportWatch(const Context& context, ImportWatch watch);
|
||||
|
||||
|
|
@ -149,6 +153,7 @@ struct MirrorStats {
|
|||
};
|
||||
MirrorStats MirrorCounters();
|
||||
void ClearImageMirrors(VkDevice device);
|
||||
void ClearHostImports(VkDevice device);
|
||||
|
||||
struct AddressCopy {
|
||||
std::uint64_t begin;
|
||||
|
|
@ -199,6 +204,7 @@ public:
|
|||
void UploadPrepare(bool addressable);
|
||||
void UploadFinish(bool addressable);
|
||||
VkDescriptorBufferInfo Descriptor(std::uint64_t address, std::size_t bytes, std::uint32_t& adjustment) const;
|
||||
static std::uint64_t ViewBytes(std::uint64_t bytes, std::uint32_t adjustment);
|
||||
std::vector<ShaderRecompiler::BdaAbi::Range> AddressRanges() const;
|
||||
// The BDA table of the cached address space when it serves this upload alone (an address-based
|
||||
// build with no region outside it): its ranges, immutable while the space lives, and the
|
||||
|
|
@ -340,6 +346,8 @@ private:
|
|||
// The cached address space this build maps through (its lease pins the ranges); `regions` then
|
||||
// holds only the regions outside it (V#s, snapshots, ranges copied per build).
|
||||
std::shared_ptr<const AddressSpace> space;
|
||||
mutable std::uint64_t writeTableSerial = 0;
|
||||
mutable std::shared_ptr<const std::vector<ShaderRecompiler::BdaAbi::Range>> writeTableRanges;
|
||||
// Import registry epoch when `direct` pointers were taken at acquire time; they are reused while
|
||||
// no import was destroyed since.
|
||||
std::uint64_t importsEpoch = 0;
|
||||
|
|
|
|||
|
|
@ -17,6 +17,13 @@ enum class TextureTileMode {
|
|||
kS64KBX,
|
||||
kD64KBX,
|
||||
kR64KBX,
|
||||
kD256B,
|
||||
kD4KB,
|
||||
kD64KB,
|
||||
kS64KBT,
|
||||
kD64KBT,
|
||||
kS4KBX,
|
||||
kD4KBX,
|
||||
// Upstream's name for SW_64KB_R_X (tile mode 0x1b).
|
||||
RenderTarget64KB = kR64KBX
|
||||
};
|
||||
|
|
@ -28,10 +35,23 @@ constexpr std::uint32_t XorSwizzleMode(TextureTileMode mode) {
|
|||
case TextureTileMode::kS64KBX: return 25u;
|
||||
case TextureTileMode::kD64KBX: return 26u;
|
||||
case TextureTileMode::kR64KBX: return 27u;
|
||||
case TextureTileMode::kS64KBT: return 17u;
|
||||
case TextureTileMode::kD64KBT: return 18u;
|
||||
case TextureTileMode::kS4KBX: return 21u;
|
||||
case TextureTileMode::kD4KBX: return 22u;
|
||||
default: return 0u;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr std::uint32_t EquationSwizzleMode(TextureTileMode mode) {
|
||||
switch (mode) {
|
||||
case TextureTileMode::kD256B: return 2u;
|
||||
case TextureTileMode::kD4KB: return 6u;
|
||||
case TextureTileMode::kD64KB: return 10u;
|
||||
default: return XorSwizzleMode(mode);
|
||||
}
|
||||
}
|
||||
|
||||
enum class TextureDimension {
|
||||
k1D,
|
||||
k2D,
|
||||
|
|
@ -61,6 +81,7 @@ struct GuestTextureResource {
|
|||
// DCC metadata of a compressed surface, or 0 (see DccMetadata.hpp).
|
||||
std::uint64_t dccAddress = 0;
|
||||
bool dccAlphaOnMsb = false;
|
||||
bool dccPipeAligned = false;
|
||||
std::uint32_t minLod = 0;
|
||||
std::uint32_t allocatedMipCount = 0;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,76 @@
|
|||
#ifndef CORE_LIBS_AGCDRIVER_GRAPHICS_PIPELINESPECIALIZATION_HPP
|
||||
#define CORE_LIBS_AGCDRIVER_GRAPHICS_PIPELINESPECIALIZATION_HPP
|
||||
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Shaders.hpp"
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <spirv/unified1/spirv.hpp>
|
||||
#include <vector>
|
||||
|
||||
namespace AgcDriver::Graphics {
|
||||
|
||||
class PipelineSpecialization {
|
||||
public:
|
||||
static void Validate(const ShaderRecompiler::RecompileResult& shader) {
|
||||
const auto& words = shader.spirv;
|
||||
Require(words.size() >= 5 && words[0] == spv::MagicNumber && words[3] != 0, "invalid specialization module header");
|
||||
std::set<std::uint32_t> unsignedTypes;
|
||||
std::map<std::uint32_t, std::uint32_t> constants;
|
||||
std::map<std::uint32_t, std::uint32_t> decorations;
|
||||
std::set<std::uint32_t> constantIds;
|
||||
std::set<std::uint32_t> supplied;
|
||||
for (const auto& constant : shader.specialization) Require(supplied.insert(constant.id).second, "duplicate pipeline specialization ID");
|
||||
for (std::size_t cursor = 5; cursor < words.size();) {
|
||||
const auto count = words[cursor] >> 16u;
|
||||
const auto op = static_cast<spv::Op>(words[cursor] & 0xffffu);
|
||||
Require(count != 0 && count <= words.size() - cursor, "truncated specialization module instruction");
|
||||
const auto validId = [&](std::uint32_t id) { Require(id != 0 && id < words[3], "specialization ID exceeds module bound"); };
|
||||
if (op == spv::OpTypeInt) {
|
||||
Require(count == 4, "malformed integer type");
|
||||
if (words[cursor + 2] == 32u && words[cursor + 3] == 0u) unsignedTypes.insert(words[cursor + 1]);
|
||||
} else if (op == spv::OpSpecConstant) {
|
||||
Require(count == 4, "specialization constant must have a 32-bit literal");
|
||||
validId(words[cursor + 2]);
|
||||
Require(constants.emplace(words[cursor + 2], words[cursor + 1]).second, "duplicate specialization result ID");
|
||||
} else if (op == spv::OpDecorate && count >= 3 && words[cursor + 2] == spv::DecorationSpecId) {
|
||||
Require(count == 4, "malformed SpecId decoration");
|
||||
validId(words[cursor + 1]);
|
||||
Require(decorations.emplace(words[cursor + 1], words[cursor + 3]).second && constantIds.insert(words[cursor + 3]).second, "duplicate SpecId decoration");
|
||||
} else if (op == spv::OpSpecConstantTrue || op == spv::OpSpecConstantFalse || op == spv::OpSpecConstantComposite || op == spv::OpSpecConstantOp) {
|
||||
throw std::runtime_error("AGC graphics: unsupported specialization constant representation");
|
||||
}
|
||||
cursor += count;
|
||||
}
|
||||
Require(constants.size() == decorations.size(), "specialization declarations and SpecId decorations disagree");
|
||||
for (const auto& [id, type] : constants) {
|
||||
Require(unsignedTypes.contains(type), "specialization constant must be uint32");
|
||||
const auto decoration = decorations.find(id);
|
||||
Require(decoration != decorations.end() && supplied.contains(decoration->second), "required pipeline specialization value is missing");
|
||||
}
|
||||
}
|
||||
|
||||
explicit PipelineSpecialization(const ShaderRecompiler::RecompileResult& shader) {
|
||||
Validate(shader);
|
||||
entries.reserve(shader.specialization.size());
|
||||
for (std::size_t index = 0; index < shader.specialization.size(); ++index) {
|
||||
const auto offset = index * sizeof(ShaderRecompiler::PipelineSpecializationConstant) + offsetof(ShaderRecompiler::PipelineSpecializationConstant, value);
|
||||
Require(offset <= std::numeric_limits<std::uint32_t>::max(), "pipeline specialization offset exceeds Vulkan limits");
|
||||
entries.push_back({shader.specialization[index].id, static_cast<std::uint32_t>(offset), sizeof(std::uint32_t)});
|
||||
}
|
||||
info.mapEntryCount = static_cast<std::uint32_t>(entries.size());
|
||||
info.pMapEntries = entries.data();
|
||||
info.dataSize = shader.specialization.size() * sizeof(ShaderRecompiler::PipelineSpecializationConstant);
|
||||
info.pData = shader.specialization.data();
|
||||
}
|
||||
|
||||
[[nodiscard]] const VkSpecializationInfo* Info() const { return entries.empty() ? nullptr : &info; }
|
||||
|
||||
private:
|
||||
std::vector<VkSpecializationMapEntry> entries;
|
||||
VkSpecializationInfo info{};
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -67,7 +67,7 @@ public:
|
|||
// continuing draw the command buffer without ending it.
|
||||
bool ContinuesRenderPass(std::uint64_t key) const;
|
||||
VkCommandBuffer CommandsInRenderPass();
|
||||
void LeaveRenderPassOpen(std::uint64_t key, std::uint32_t timing, bool continuable);
|
||||
void LeaveRenderPassOpen(std::uint64_t key, std::uint32_t timing, bool continuable, std::function<void(VkCommandBuffer)> afterPass = {});
|
||||
// DCC "uncompressed" key stores (DccMetadata.cpp StoreUncompressedOnGpu): queued on the open
|
||||
// batch and recorded as one run (one barrier pair for every queued fill) at Submit, before a
|
||||
// label store (RecordStore), or before a command that writes or reads a queued range (the
|
||||
|
|
@ -86,14 +86,10 @@ public:
|
|||
bool Idle() const { return open == nullptr && inFlight.empty(); }
|
||||
// Whether recorded work still has completion actions (write-backs the CPU must see) to run.
|
||||
bool HasCompletions() const;
|
||||
// The object lives at least until the batch open now completed; it is then destroyed AFTER the
|
||||
// finishing thread released GuestMemory::GpuMutex, on a release thread of its own (never under
|
||||
// a hold, see Recorder.cpp ReleaseDeferredKeeps; the finishing thread destroys it itself when
|
||||
// the thread's queue is full, APS5_RELEASE_QUEUE_MAX batches, or with APS5_RELEASE_ON_UNLOCK=1;
|
||||
// APS5_RELEASE_UNDER_LOCK=1 destroys it in the reap as before), so its destructor must need
|
||||
// neither the mutex nor the recorder nor a particular thread. ~Recorder joins the release
|
||||
// thread and waits for every release in progress before the device goes.
|
||||
void Keep(std::shared_ptr<void> object);
|
||||
void Keep(std::shared_ptr<void> object, std::size_t bytes = 0);
|
||||
static constexpr std::size_t KeptBytesBudget = std::size_t{512} << 20u;
|
||||
void BoundKeptBytes();
|
||||
std::size_t InFlightKeptBytes() const { return inFlightKeptBytes; }
|
||||
enum class SnapshotUse : std::uint8_t { Storage, Vertex, Index16, Index32 };
|
||||
static constexpr std::size_t DrawSnapshotBudget = std::size_t{256} << 20u;
|
||||
static constexpr std::size_t DrawSnapshotEntries = 1024;
|
||||
|
|
@ -103,6 +99,7 @@ public:
|
|||
void KeepDrawSnapshot(std::uint64_t address, std::size_t bytes, std::uint64_t generation, std::uint64_t registryGeneration, std::shared_ptr<Buffer> buffer, SnapshotUse use = SnapshotUse::Storage, std::uint32_t derived = 0);
|
||||
void OnComplete(std::function<void()> action);
|
||||
void NotePendingWrite(std::uint64_t address, std::size_t bytes);
|
||||
void NotePendingFill(std::uint64_t address, std::size_t bytes, std::uint8_t value);
|
||||
// Notes several [begin, end) ranges and publishes the snapshot once (a dispatch writes many buffers).
|
||||
void NotePendingWrites(std::span<const std::pair<std::uint64_t, std::uint64_t>> ranges);
|
||||
bool PendingWriteOverlaps(std::uint64_t address, std::size_t bytes) const;
|
||||
|
|
@ -112,6 +109,13 @@ public:
|
|||
bool PendingWriteSettled(std::uint64_t address, std::size_t bytes) const;
|
||||
std::uint64_t LastWriteNote(std::uint64_t address, std::size_t bytes) const;
|
||||
std::uint64_t NewestWriteNote(std::uint64_t address, std::size_t bytes) const;
|
||||
struct PendingWrite {
|
||||
std::uint64_t begin;
|
||||
std::uint64_t end;
|
||||
std::uint64_t note;
|
||||
int value;
|
||||
};
|
||||
std::vector<PendingWrite> PendingWritesOver(std::uint64_t address, std::size_t bytes) const;
|
||||
// Batch read tracking: the guest ranges the recorded work reads IN PLACE through a host import
|
||||
// and the GPU has not executed yet (a V# element bound in place, a region the GPU copies out of
|
||||
// an import, an address-based build's leased heaps, indirect arguments, a GPU-direct storage
|
||||
|
|
@ -491,9 +495,11 @@ private:
|
|||
VkCommandBuffer commands = VK_NULL_HANDLE;
|
||||
VkFence fence = VK_NULL_HANDLE;
|
||||
std::vector<std::shared_ptr<void>> kept;
|
||||
std::size_t keptBytes = 0;
|
||||
std::vector<std::function<void()>> completions;
|
||||
std::vector<std::pair<std::uint64_t, std::uint64_t>> writes;
|
||||
std::vector<std::uint64_t> writeNotes;
|
||||
std::vector<std::int16_t> writeValues;
|
||||
// In-place reads (see NotePendingRead), dying with the batch: a finished batch's reads are done.
|
||||
struct Read {
|
||||
std::uint64_t begin;
|
||||
|
|
@ -581,6 +587,7 @@ private:
|
|||
bool continuable = false;
|
||||
std::uint64_t key = 0;
|
||||
std::uint32_t timing = NoTiming;
|
||||
std::function<void(VkCommandBuffer)> afterPass;
|
||||
} renderPass;
|
||||
// A pass ended in this batch: Submit records the host-read barrier its draws left out.
|
||||
bool hostReadOwed = false;
|
||||
|
|
@ -676,7 +683,7 @@ private:
|
|||
// Returns false when the locked path must run instead (no timeline, nested acquisition, off).
|
||||
bool syncThroughUnlocked(std::uint64_t address, std::uint64_t end, int source, const void* site);
|
||||
// `source` is the CountSync source the wait is attributed to.
|
||||
void finish(std::unique_ptr<Batch> batch, bool wait, int source);
|
||||
void finish(std::unique_ptr<Batch> batch, bool wait, int source, bool releaseNow = false);
|
||||
void release(Batch& batch) noexcept;
|
||||
static bool overlaps(const Batch& batch, std::uint64_t address, std::uint64_t end);
|
||||
// The first noted read of `batch` overlapping [address, end), or null.
|
||||
|
|
@ -687,7 +694,7 @@ private:
|
|||
// Appends one range to the open batch; returns whether the snapshot must be rebuilt for it.
|
||||
// `ownLabel`: the range is a label's own store (NoteLabel, AfterCompletions), which does not
|
||||
// overwrite the table entries it covers; any other range flags them (table mutex, briefly).
|
||||
bool noteWrite(std::uint64_t address, std::size_t bytes, bool ownLabel = false);
|
||||
bool noteWrite(std::uint64_t address, std::size_t bytes, bool ownLabel = false, int value = -1);
|
||||
// Appends one range to `batch` (open or in flight) and publishes the snapshot if needed.
|
||||
void noteWriteOn(Batch& batch, std::uint64_t address, std::size_t bytes, bool ownLabel = false);
|
||||
void noteLabelOn(Batch& batch, std::uint64_t address, std::span<const std::byte> bytes, std::uint64_t stamp, std::uint32_t queue, bool behindCompletion = false);
|
||||
|
|
@ -731,6 +738,7 @@ private:
|
|||
// snapshot (a rebuild from inside a completion must not drop them) until finish returns.
|
||||
std::vector<const Batch*> finishing;
|
||||
std::uint64_t submissions = 0;
|
||||
std::size_t inFlightKeptBytes = 0;
|
||||
std::uint64_t writeNoteCount = 0;
|
||||
// Command buffers and fences of completed batches, reused by later ones (hundreds of batches per
|
||||
// frame would otherwise allocate and free their objects each time).
|
||||
|
|
|
|||
|
|
@ -11,14 +11,14 @@
|
|||
namespace AgcDriver::Graphics {
|
||||
|
||||
ShaderRecompiler::ShaderPixelStageInfo DecodePixelStageInfo(const Registers& context, const std::array<std::uint8_t, 8>& exportMappings, bool nullProgram = false);
|
||||
ShaderRecompiler::ShaderComputeStageInfo DecodeComputeStageInfo(const Registers& shader);
|
||||
ShaderRecompiler::ShaderComputeStageInfo DecodeComputeStageInfo(const Registers& shader, std::span<const std::byte> header);
|
||||
// A guest range DecodeVertexStageInfo read (an attribute word, a vertex V#) with the bytes as read:
|
||||
// the draw cache validates them by value with the stage's capture (design_cpu_final rule RD).
|
||||
struct DecodeRead {
|
||||
std::uint64_t address;
|
||||
std::vector<std::byte> bytes;
|
||||
};
|
||||
ShaderRecompiler::ShaderVertexStageInfo DecodeVertexStageInfo(std::span<const std::byte> header, std::uint64_t headerAddress, std::span<const std::uint32_t> userData, std::vector<DecodeRead>* reads = nullptr);
|
||||
ShaderRecompiler::ShaderVertexStageInfo DecodeVertexStageInfo(std::span<const std::byte> header, std::uint64_t headerAddress, std::span<const std::uint32_t> userData, std::vector<DecodeRead>* reads = nullptr, bool staticAbi = false);
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@
|
|||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace AgcDriver { class VulkanDevice; }
|
||||
|
||||
namespace AgcDriver::Graphics {
|
||||
|
||||
class Recorder;
|
||||
|
|
@ -60,9 +62,6 @@ public:
|
|||
VkDescriptorSet set = VK_NULL_HANDLE;
|
||||
VkDescriptorPool pool = VK_NULL_HANDLE;
|
||||
};
|
||||
// A set of `layout` needing `sizes` descriptors from the pool chain, opening a pool when no pool
|
||||
// has room; a null set when the needs exceed what one chain pool holds (the caller then makes a
|
||||
// dedicated pool, as before).
|
||||
SetAllocation Allocate(VkDescriptorSetLayout layout, std::span<const VkDescriptorPoolSize> sizes);
|
||||
void Free(const SetAllocation& allocation) noexcept;
|
||||
// APS5_PROFILE_DRAW counters: layouts served from the map / created, sets allocated, pools opened.
|
||||
|
|
@ -71,6 +70,7 @@ public:
|
|||
std::uint64_t layoutMisses = 0;
|
||||
std::uint64_t sets = 0;
|
||||
std::uint64_t pools = 0;
|
||||
std::uint64_t dedicatedPools = 0;
|
||||
};
|
||||
Stats Counters() const;
|
||||
|
||||
|
|
@ -82,6 +82,7 @@ private:
|
|||
mutable std::mutex mutex;
|
||||
std::map<std::vector<std::uint32_t>, VkDescriptorSetLayout> layouts;
|
||||
std::vector<VkDescriptorPool> pools;
|
||||
std::vector<VkDescriptorPool> dedicated;
|
||||
Stats stats;
|
||||
};
|
||||
|
||||
|
|
@ -228,6 +229,8 @@ public:
|
|||
std::vector<std::pair<std::uint64_t, std::uint64_t>> PresyncSurfaces() const;
|
||||
|
||||
private:
|
||||
friend class AgcDriver::VulkanDevice;
|
||||
bool refreshData(VkCommandBuffer commands, const CompiledShader& shader, Recorder* recorder);
|
||||
struct DescribedRange {
|
||||
const char* kind;
|
||||
std::uint64_t address;
|
||||
|
|
@ -396,7 +399,6 @@ private:
|
|||
// Whether the layout is this object's own (no cache) and destroyed with it.
|
||||
bool ownsLayout = false;
|
||||
VkDescriptorSet _set = VK_NULL_HANDLE;
|
||||
// Dedicated pool of this object's set (no cache, or a set too large for a cache pool).
|
||||
VkDescriptorPool pool = VK_NULL_HANDLE;
|
||||
// The cache pool the set was allocated from, freed back to it on release.
|
||||
VkDescriptorPool cachePool = VK_NULL_HANDLE;
|
||||
|
|
@ -414,6 +416,7 @@ private:
|
|||
std::vector<bool> storageAtomic;
|
||||
std::vector<bool> storageAtomic64;
|
||||
std::vector<std::shared_ptr<Sampler>> samplers;
|
||||
std::shared_ptr<Sampler> paddingSampler;
|
||||
bool reusable = false;
|
||||
std::vector<DirectRegion> directRegions;
|
||||
std::vector<ValidatedSurface> validatedTextures;
|
||||
|
|
@ -434,6 +437,7 @@ private:
|
|||
// still to look up (index into `bindings`; the DescriptorBinding lives in the compiled shader),
|
||||
// the descriptor counts the set was sized for, and the compute stage of a deferred build.
|
||||
std::vector<Binding> bindings;
|
||||
std::vector<std::uint32_t> refreshResourceKey;
|
||||
struct DeferredImages {
|
||||
const ShaderRecompiler::DescriptorBinding* binding;
|
||||
std::size_t index;
|
||||
|
|
|
|||
|
|
@ -41,6 +41,9 @@ struct ColorTarget {
|
|||
// DCC metadata of a compressed target (CB_COLOR_INFO DCC_ENABLE), or 0 (see DccMetadata.hpp).
|
||||
std::uint64_t dccAddress = 0;
|
||||
bool dccAlphaOnMsb = false;
|
||||
bool dccPipeAligned = false;
|
||||
std::uint64_t cmaskAddress = 0;
|
||||
std::size_t cmaskBytes = 0;
|
||||
std::uint64_t surfaceAddress = 0;
|
||||
VkExtent2D surfaceExtent{};
|
||||
std::uint32_t mipCount = 1;
|
||||
|
|
@ -89,6 +92,7 @@ struct State {
|
|||
VkViewport viewport;
|
||||
bool negativeOneToOne;
|
||||
bool depthClamp = false;
|
||||
VkConservativeRasterizationModeEXT conservativeRasterization = VK_CONSERVATIVE_RASTERIZATION_MODE_DISABLED_EXT;
|
||||
VkRect2D scissor;
|
||||
VkCullModeFlags cullMode;
|
||||
VkFrontFace frontFace;
|
||||
|
|
@ -100,6 +104,8 @@ ShaderStages DecodeShaderStages(const QueueState& queue);
|
|||
State DecodeState(const QueueState& queue);
|
||||
std::array<std::uint8_t, 8> ExportMappings(const State& state);
|
||||
ColorTarget DecodeColorBuffer(const Registers& context, std::uint32_t slot);
|
||||
std::size_t CmaskBytes(std::uint32_t width, std::uint32_t height);
|
||||
std::uint32_t ColorWriteMask(const Registers& context);
|
||||
|
||||
struct ColorMetadataPass {
|
||||
enum class Mode { EliminateFastClear, DccDecompress };
|
||||
|
|
@ -107,11 +113,12 @@ struct ColorMetadataPass {
|
|||
std::vector<ColorTarget> targets;
|
||||
};
|
||||
std::optional<ColorMetadataPass> DecodeColorMetadataPass(const QueueState& queue);
|
||||
std::string DepthMaintenanceRejection(const QueueState& queue);
|
||||
// The message DecodeState (or the pixel stage decode after it) would throw for the register rules
|
||||
// this precheck covers, evaluated without exceptions before the draw is decoded; empty when they
|
||||
// pass (DecodeState still checks everything). A register a rule needs that is absent is no verdict.
|
||||
std::string DrawRejection(const QueueState& queue, bool indexed);
|
||||
bool PixelProgramUnset(const QueueState& queue);
|
||||
bool PixelProgramSkipped(const QueueState& queue);
|
||||
std::string NullPixelProgramRejection(const QueueState& queue);
|
||||
|
||||
// The recording facade of the draw decoders (design_cpu_final M8, step 8a): every register read
|
||||
|
|
|
|||
|
|
@ -107,6 +107,8 @@ private:
|
|||
std::unique_ptr<CommandBatch> upload;
|
||||
};
|
||||
|
||||
VkFormat AttachmentProxyFormat(const Context& context, VkFormat format);
|
||||
|
||||
// A guest texture a shader writes through a storage image. It is uploaded like a sampled texture;
|
||||
// after the GPU work completes its results are stored to guest memory (retiled, changed bytes only),
|
||||
// either at once (WriteBack) or deferred: MarkDirty keeps them on the GPU until something reads that
|
||||
|
|
@ -131,6 +133,9 @@ public:
|
|||
// buffer's format and mark the image dirty like a storage write.
|
||||
bool Attachable() const { return attachable; }
|
||||
VkImageView AttachmentView(VkFormat format, std::uint32_t mip = 0, std::uint32_t depthSlice = 0);
|
||||
VkImageView AttachmentProxyView();
|
||||
void RecordAttachmentProxyLoad(VkCommandBuffer commands, VkImageLayout attachmentLayout) const;
|
||||
void RecordAttachmentProxyStore(VkCommandBuffer commands, VkImageLayout attachmentLayout) const;
|
||||
void WriteBack();
|
||||
// Deferred write-back (APS5_EAGER_WRITEBACK=1 stores at once instead).
|
||||
void MarkDirty();
|
||||
|
|
@ -278,6 +283,7 @@ public:
|
|||
DccKeys UploadedKeys() const { return uploadedKeys; }
|
||||
DccKeys FilledKeys() const { return filledKeys; }
|
||||
DccKeyProof& KeyProof() const { return keyProof; }
|
||||
DccKeys ProvedKeys() const;
|
||||
bool ServesKeysAt(std::uint64_t dccAddress) const;
|
||||
// Brings the image up to date with guest memory before another use; returns whether its content
|
||||
// was still current (nothing uploaded).
|
||||
|
|
@ -421,7 +427,7 @@ private:
|
|||
std::vector<std::byte> original;
|
||||
// DCC keys the image content was uploaded under: a fast-cleared surface starts as its clear value.
|
||||
DccKeys uploadedKeys = DccKeys::Uncompressed;
|
||||
DccKeys filledKeys = DccKeys::Uncompressed;
|
||||
mutable DccKeys filledKeys = DccKeys::Uncompressed;
|
||||
mutable DccKeyProof keyProof;
|
||||
struct ForeignKeyProof {
|
||||
std::uint64_t dccAddress = 0;
|
||||
|
|
@ -459,6 +465,9 @@ private:
|
|||
std::map<std::pair<std::uint32_t, bool>, VkImageView> uintViews;
|
||||
bool attachable = false;
|
||||
std::map<std::tuple<VkFormat, std::uint32_t, std::uint32_t>, VkImageView> attachmentViews;
|
||||
VkImage proxyImage = VK_NULL_HANDLE;
|
||||
VkDeviceMemory proxyMemory = VK_NULL_HANDLE;
|
||||
VkImageView proxyView = VK_NULL_HANDLE;
|
||||
VkFormat storageFormat = VK_FORMAT_UNDEFINED;
|
||||
// Results are on the GPU only (guarded by the pending-write registry lock).
|
||||
bool dirty = false;
|
||||
|
|
|
|||
|
|
@ -38,7 +38,42 @@ inline constexpr TextureSwizzleEquation kTextureSwizzleEquations[] = {
|
|||
{27u, 4u, {0x00000000u, 0x00000000u, 0x00000001u, 0x00000002u, 0x00001000u, 0x00002000u, 0x00004000u, 0x00000004u, 0x08008008u, 0x04010010u, 0x02020040u, 0x01040020u, 0x00008000u, 0x00000010u, 0x00040000u, 0x00000040u}},
|
||||
{27u, 8u, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000001u, 0x00001000u, 0x00000002u, 0x00000004u, 0x00002000u, 0x08008008u, 0x04010010u, 0x02020040u, 0x01040020u, 0x00004000u, 0x00000008u, 0x00010000u, 0x00000040u}},
|
||||
{27u, 16u, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000001u, 0x00001000u, 0x00000002u, 0x00002000u, 0x08008008u, 0x04010010u, 0x02020040u, 0x01040020u, 0x00004000u, 0x00000004u, 0x00008000u, 0x00000010u}},
|
||||
// Thick (3D) standard swizzles SW_4KB_S and SW_64KB_S use the S3 patterns, keyed 0x100 | SW_MODE.
|
||||
{2u, 1u, {0x00000001u, 0x00000002u, 0x00000004u, 0x00002000u, 0x00001000u, 0x00004000u, 0x00000008u, 0x00008000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}},
|
||||
{2u, 2u, {0x00000000u, 0x00000001u, 0x00000002u, 0x00000004u, 0x00001000u, 0x00002000u, 0x00004000u, 0x00000008u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}},
|
||||
{2u, 4u, {0x00000000u, 0x00000000u, 0x00000001u, 0x00000002u, 0x00001000u, 0x00002000u, 0x00004000u, 0x00000004u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}},
|
||||
{2u, 8u, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000001u, 0x00001000u, 0x00000002u, 0x00000004u, 0x00002000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}},
|
||||
{2u, 16u, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000001u, 0x00001000u, 0x00000002u, 0x00002000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}},
|
||||
{6u, 1u, {0x00000001u, 0x00000002u, 0x00000004u, 0x00002000u, 0x00001000u, 0x00004000u, 0x00000008u, 0x00008000u, 0x00010000u, 0x00000010u, 0x00020000u, 0x00000020u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}},
|
||||
{6u, 2u, {0x00000000u, 0x00000001u, 0x00000002u, 0x00000004u, 0x00001000u, 0x00002000u, 0x00004000u, 0x00000008u, 0x00008000u, 0x00000010u, 0x00010000u, 0x00000020u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}},
|
||||
{6u, 4u, {0x00000000u, 0x00000000u, 0x00000001u, 0x00000002u, 0x00001000u, 0x00002000u, 0x00004000u, 0x00000004u, 0x00008000u, 0x00000008u, 0x00010000u, 0x00000010u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}},
|
||||
{6u, 8u, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000001u, 0x00001000u, 0x00000002u, 0x00000004u, 0x00002000u, 0x00004000u, 0x00000008u, 0x00008000u, 0x00000010u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}},
|
||||
{6u, 16u, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000001u, 0x00001000u, 0x00000002u, 0x00002000u, 0x00004000u, 0x00000004u, 0x00008000u, 0x00000008u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}},
|
||||
{10u, 1u, {0x00000001u, 0x00000002u, 0x00000004u, 0x00002000u, 0x00001000u, 0x00004000u, 0x00000008u, 0x00008000u, 0x00010000u, 0x00000010u, 0x00020000u, 0x00000020u, 0x00040000u, 0x00000040u, 0x00080000u, 0x00000080u}},
|
||||
{10u, 2u, {0x00000000u, 0x00000001u, 0x00000002u, 0x00000004u, 0x00001000u, 0x00002000u, 0x00004000u, 0x00000008u, 0x00008000u, 0x00000010u, 0x00010000u, 0x00000020u, 0x00020000u, 0x00000040u, 0x00040000u, 0x00000080u}},
|
||||
{10u, 4u, {0x00000000u, 0x00000000u, 0x00000001u, 0x00000002u, 0x00001000u, 0x00002000u, 0x00004000u, 0x00000004u, 0x00008000u, 0x00000008u, 0x00010000u, 0x00000010u, 0x00020000u, 0x00000020u, 0x00040000u, 0x00000040u}},
|
||||
{10u, 8u, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000001u, 0x00001000u, 0x00000002u, 0x00000004u, 0x00002000u, 0x00004000u, 0x00000008u, 0x00008000u, 0x00000010u, 0x00010000u, 0x00000020u, 0x00020000u, 0x00000040u}},
|
||||
{10u, 16u, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000001u, 0x00001000u, 0x00000002u, 0x00002000u, 0x00004000u, 0x00000004u, 0x00008000u, 0x00000008u, 0x00010000u, 0x00000010u, 0x00020000u, 0x00000020u}},
|
||||
{17u, 1u, {0x00000001u, 0x00000002u, 0x00000004u, 0x00000008u, 0x00001000u, 0x00002000u, 0x00004000u, 0x00008000u, 0x00010080u, 0x00080010u, 0x00020040u, 0x00040020u, 0x00040000u, 0x00000040u, 0x00080000u, 0x00000080u}},
|
||||
{17u, 2u, {0x00000000u, 0x00000001u, 0x00000002u, 0x00000004u, 0x00001000u, 0x00002000u, 0x00004000u, 0x00000008u, 0x00008080u, 0x00040010u, 0x00010040u, 0x00020020u, 0x00020000u, 0x00000040u, 0x00040000u, 0x00000080u}},
|
||||
{17u, 4u, {0x00000000u, 0x00000000u, 0x00000001u, 0x00000002u, 0x00001000u, 0x00002000u, 0x00004000u, 0x00000004u, 0x00008040u, 0x00040008u, 0x00010020u, 0x00020010u, 0x00020000u, 0x00000020u, 0x00040000u, 0x00000040u}},
|
||||
{17u, 8u, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000001u, 0x00001000u, 0x00002000u, 0x00000002u, 0x00000004u, 0x00004040u, 0x00020008u, 0x00008020u, 0x00010010u, 0x00010000u, 0x00000020u, 0x00020000u, 0x00000040u}},
|
||||
{17u, 16u, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00001000u, 0x00002000u, 0x00000001u, 0x00000002u, 0x00004020u, 0x00020004u, 0x00008010u, 0x00010008u, 0x00010000u, 0x00000010u, 0x00020000u, 0x00000020u}},
|
||||
{18u, 1u, {0x00000001u, 0x00000002u, 0x00000004u, 0x00002000u, 0x00001000u, 0x00004000u, 0x00000008u, 0x00008000u, 0x00010080u, 0x00080010u, 0x00020040u, 0x00040020u, 0x00040000u, 0x00000040u, 0x00080000u, 0x00000080u}},
|
||||
{18u, 2u, {0x00000000u, 0x00000001u, 0x00000002u, 0x00000004u, 0x00001000u, 0x00002000u, 0x00004000u, 0x00000008u, 0x00008080u, 0x00040010u, 0x00010040u, 0x00020020u, 0x00020000u, 0x00000040u, 0x00040000u, 0x00000080u}},
|
||||
{18u, 4u, {0x00000000u, 0x00000000u, 0x00000001u, 0x00000002u, 0x00001000u, 0x00002000u, 0x00004000u, 0x00000004u, 0x00008040u, 0x00040008u, 0x00010020u, 0x00020010u, 0x00020000u, 0x00000020u, 0x00040000u, 0x00000040u}},
|
||||
{18u, 8u, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000001u, 0x00001000u, 0x00000002u, 0x00000004u, 0x00002000u, 0x00004040u, 0x00020008u, 0x00008020u, 0x00010010u, 0x00010000u, 0x00000020u, 0x00020000u, 0x00000040u}},
|
||||
{18u, 16u, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000001u, 0x00001000u, 0x00000002u, 0x00002000u, 0x00004020u, 0x00020004u, 0x00008010u, 0x00010008u, 0x00010000u, 0x00000010u, 0x00020000u, 0x00000020u}},
|
||||
{21u, 1u, {0x00000001u, 0x00000002u, 0x00000004u, 0x00000008u, 0x00001000u, 0x00002000u, 0x00004000u, 0x00008000u, 0x08010080u, 0x04080010u, 0x02020040u, 0x01040020u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}},
|
||||
{21u, 2u, {0x00000000u, 0x00000001u, 0x00000002u, 0x00000004u, 0x00001000u, 0x00002000u, 0x00004000u, 0x00000008u, 0x08008080u, 0x04040010u, 0x02010040u, 0x01020020u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}},
|
||||
{21u, 4u, {0x00000000u, 0x00000000u, 0x00000001u, 0x00000002u, 0x00001000u, 0x00002000u, 0x00004000u, 0x00000004u, 0x08008040u, 0x04040008u, 0x02010020u, 0x01020010u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}},
|
||||
{21u, 8u, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000001u, 0x00001000u, 0x00002000u, 0x00000002u, 0x00000004u, 0x08004040u, 0x04020008u, 0x02008020u, 0x01010010u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}},
|
||||
{21u, 16u, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00001000u, 0x00002000u, 0x00000001u, 0x00000002u, 0x08004020u, 0x04020004u, 0x02008010u, 0x01010008u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}},
|
||||
{22u, 1u, {0x00000001u, 0x00000002u, 0x00000004u, 0x00002000u, 0x00001000u, 0x00004000u, 0x00000008u, 0x00008000u, 0x08010080u, 0x04080010u, 0x02020040u, 0x01040020u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}},
|
||||
{22u, 2u, {0x00000000u, 0x00000001u, 0x00000002u, 0x00000004u, 0x00001000u, 0x00002000u, 0x00004000u, 0x00000008u, 0x08008080u, 0x04040010u, 0x02010040u, 0x01020020u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}},
|
||||
{22u, 4u, {0x00000000u, 0x00000000u, 0x00000001u, 0x00000002u, 0x00001000u, 0x00002000u, 0x00004000u, 0x00000004u, 0x08008040u, 0x04040008u, 0x02010020u, 0x01020010u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}},
|
||||
{22u, 8u, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000001u, 0x00001000u, 0x00000002u, 0x00000004u, 0x00002000u, 0x08004040u, 0x04020008u, 0x02008020u, 0x01010010u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}},
|
||||
{22u, 16u, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000001u, 0x00001000u, 0x00000002u, 0x00002000u, 0x08004020u, 0x04020004u, 0x02008010u, 0x01010008u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}},
|
||||
// Thick (3D) standard swizzles SW_4KB_S, SW_64KB_S and SW_64KB_S_X use the S3 patterns, keyed 0x100 | SW_MODE.
|
||||
{0x105u, 1u, {0x00000001u, 0x00000002u, 0x01000000u, 0x00001000u, 0x02000000u, 0x00002000u, 0x00000004u, 0x04000000u, 0x00004000u, 0x00000008u, 0x08000000u, 0x00008000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}},
|
||||
{0x105u, 2u, {0x00000000u, 0x00000001u, 0x01000000u, 0x00001000u, 0x02000000u, 0x00002000u, 0x00000002u, 0x04000000u, 0x00004000u, 0x00000004u, 0x08000000u, 0x00008000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}},
|
||||
{0x105u, 4u, {0x00000000u, 0x00000000u, 0x00000001u, 0x00001000u, 0x01000000u, 0x00002000u, 0x00000002u, 0x02000000u, 0x00004000u, 0x00000004u, 0x04000000u, 0x00008000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}},
|
||||
|
|
@ -49,6 +84,11 @@ inline constexpr TextureSwizzleEquation kTextureSwizzleEquations[] = {
|
|||
{0x109u, 4u, {0x00000000u, 0x00000000u, 0x00000001u, 0x00001000u, 0x01000000u, 0x00002000u, 0x00000002u, 0x02000000u, 0x00004000u, 0x00000004u, 0x04000000u, 0x00008000u, 0x00000008u, 0x08000000u, 0x00010000u, 0x00000010u}},
|
||||
{0x109u, 8u, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000001u, 0x01000000u, 0x00001000u, 0x00000002u, 0x02000000u, 0x00002000u, 0x00000004u, 0x04000000u, 0x00004000u, 0x00000008u, 0x08000000u, 0x00008000u, 0x00000010u}},
|
||||
{0x109u, 16u, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x01000000u, 0x00001000u, 0x00000001u, 0x02000000u, 0x00002000u, 0x00000002u, 0x04000000u, 0x00004000u, 0x00000004u, 0x08000000u, 0x00008000u, 0x00000008u}},
|
||||
{0x119u, 1u, {0x00000001u, 0x00000002u, 0x01000000u, 0x00001000u, 0x02000000u, 0x00002000u, 0x00000004u, 0x04000000u, 0x40004040u, 0x20020008u, 0x08010020u, 0x10008010u, 0x00000010u, 0x10000000u, 0x00010000u, 0x00000020u}},
|
||||
{0x119u, 2u, {0x00000000u, 0x00000001u, 0x01000000u, 0x00001000u, 0x02000000u, 0x00002000u, 0x00000002u, 0x04000000u, 0x40004020u, 0x20020004u, 0x08010010u, 0x10008008u, 0x00000008u, 0x10000000u, 0x00010000u, 0x00000010u}},
|
||||
{0x119u, 4u, {0x00000000u, 0x00000000u, 0x00000001u, 0x00001000u, 0x01000000u, 0x00002000u, 0x00000002u, 0x02000000u, 0x20004020u, 0x10020004u, 0x04010010u, 0x08008008u, 0x00000008u, 0x08000000u, 0x00010000u, 0x00000010u}},
|
||||
{0x119u, 8u, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000001u, 0x01000000u, 0x00001000u, 0x00000002u, 0x02000000u, 0x20002020u, 0x10010004u, 0x04008010u, 0x08004008u, 0x00000008u, 0x08000000u, 0x00008000u, 0x00000010u}},
|
||||
{0x119u, 16u, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x01000000u, 0x00001000u, 0x00000001u, 0x02000000u, 0x20002010u, 0x10010002u, 0x04008008u, 0x08004004u, 0x00000004u, 0x08000000u, 0x00008000u, 0x00000008u}},
|
||||
};
|
||||
|
||||
inline const TextureSwizzleEquation* FindTextureSwizzleEquation(std::uint32_t swizzleMode, std::uint32_t elementBytes) {
|
||||
|
|
|
|||
|
|
@ -20,37 +20,39 @@ struct VertexFormat {
|
|||
|
||||
inline VertexFormat DecodeVertexFormat(const ShaderRecompiler::VertexAttribute& attribute) {
|
||||
Require(attribute.components >= 1 && attribute.components <= 4, "invalid vertex attribute component count");
|
||||
const auto components = attribute.formatComponents == 0u ? attribute.components : attribute.formatComponents;
|
||||
Require(components >= 1u && components <= 4u, "invalid vertex format component count");
|
||||
const auto format = (attribute.resource.fields[3] >> 12u) & 0x7fu;
|
||||
switch (format) {
|
||||
case 1: { const std::array formats{VK_FORMAT_R8_UNORM}; const auto count = std::min(attribute.components, 1u); return {formats[count - 1], count * 1u, 1u, "f32"}; }
|
||||
case 2: { const std::array formats{VK_FORMAT_R8_SNORM}; const auto count = std::min(attribute.components, 1u); return {formats[count - 1], count * 1u, 1u, "f32"}; }
|
||||
case 3: { const std::array formats{VK_FORMAT_R8_USCALED}; const auto count = std::min(attribute.components, 1u); return {formats[count - 1], count * 1u, 1u, "f32"}; }
|
||||
case 4: { const std::array formats{VK_FORMAT_R8_SSCALED}; const auto count = std::min(attribute.components, 1u); return {formats[count - 1], count * 1u, 1u, "f32"}; }
|
||||
case 5: { const std::array formats{VK_FORMAT_R8_UINT}; const auto count = std::min(attribute.components, 1u); return {formats[count - 1], count * 1u, 1u, "u32"}; }
|
||||
case 6: { const std::array formats{VK_FORMAT_R8_SINT}; const auto count = std::min(attribute.components, 1u); return {formats[count - 1], count * 1u, 1u, "i32"}; }
|
||||
case 7: { const std::array formats{VK_FORMAT_R16_UNORM}; const auto count = std::min(attribute.components, 1u); return {formats[count - 1], count * 2u, 2u, "f32"}; }
|
||||
case 8: { const std::array formats{VK_FORMAT_R16_SNORM}; const auto count = std::min(attribute.components, 1u); return {formats[count - 1], count * 2u, 2u, "f32"}; }
|
||||
case 9: { const std::array formats{VK_FORMAT_R16_USCALED}; const auto count = std::min(attribute.components, 1u); return {formats[count - 1], count * 2u, 2u, "f32"}; }
|
||||
case 10: { const std::array formats{VK_FORMAT_R16_SSCALED}; const auto count = std::min(attribute.components, 1u); return {formats[count - 1], count * 2u, 2u, "f32"}; }
|
||||
case 11: { const std::array formats{VK_FORMAT_R16_UINT}; const auto count = std::min(attribute.components, 1u); return {formats[count - 1], count * 2u, 2u, "u32"}; }
|
||||
case 12: { const std::array formats{VK_FORMAT_R16_SINT}; const auto count = std::min(attribute.components, 1u); return {formats[count - 1], count * 2u, 2u, "i32"}; }
|
||||
case 13: { const std::array formats{VK_FORMAT_R16_SFLOAT}; const auto count = std::min(attribute.components, 1u); return {formats[count - 1], count * 2u, 2u, "f32"}; }
|
||||
case 14: { const std::array formats{VK_FORMAT_R8_UNORM, VK_FORMAT_R8G8_UNORM}; const auto count = std::min(attribute.components, 2u); return {formats[count - 1], count * 1u, 1u, "f32"}; }
|
||||
case 15: { const std::array formats{VK_FORMAT_R8_SNORM, VK_FORMAT_R8G8_SNORM}; const auto count = std::min(attribute.components, 2u); return {formats[count - 1], count * 1u, 1u, "f32"}; }
|
||||
case 16: { const std::array formats{VK_FORMAT_R8_USCALED, VK_FORMAT_R8G8_USCALED}; const auto count = std::min(attribute.components, 2u); return {formats[count - 1], count * 1u, 1u, "f32"}; }
|
||||
case 17: { const std::array formats{VK_FORMAT_R8_SSCALED, VK_FORMAT_R8G8_SSCALED}; const auto count = std::min(attribute.components, 2u); return {formats[count - 1], count * 1u, 1u, "f32"}; }
|
||||
case 18: { const std::array formats{VK_FORMAT_R8_UINT, VK_FORMAT_R8G8_UINT}; const auto count = std::min(attribute.components, 2u); return {formats[count - 1], count * 1u, 1u, "u32"}; }
|
||||
case 19: { const std::array formats{VK_FORMAT_R8_SINT, VK_FORMAT_R8G8_SINT}; const auto count = std::min(attribute.components, 2u); return {formats[count - 1], count * 1u, 1u, "i32"}; }
|
||||
case 20: { const std::array formats{VK_FORMAT_R32_UINT}; const auto count = std::min(attribute.components, 1u); return {formats[count - 1], count * 4u, 4u, "u32"}; }
|
||||
case 21: { const std::array formats{VK_FORMAT_R32_SINT}; const auto count = std::min(attribute.components, 1u); return {formats[count - 1], count * 4u, 4u, "i32"}; }
|
||||
case 22: { const std::array formats{VK_FORMAT_R32_SFLOAT}; const auto count = std::min(attribute.components, 1u); return {formats[count - 1], count * 4u, 4u, "f32"}; }
|
||||
case 23: { const std::array formats{VK_FORMAT_R16_UNORM, VK_FORMAT_R16G16_UNORM}; const auto count = std::min(attribute.components, 2u); return {formats[count - 1], count * 2u, 2u, "f32"}; }
|
||||
case 24: { const std::array formats{VK_FORMAT_R16_SNORM, VK_FORMAT_R16G16_SNORM}; const auto count = std::min(attribute.components, 2u); return {formats[count - 1], count * 2u, 2u, "f32"}; }
|
||||
case 25: { const std::array formats{VK_FORMAT_R16_USCALED, VK_FORMAT_R16G16_USCALED}; const auto count = std::min(attribute.components, 2u); return {formats[count - 1], count * 2u, 2u, "f32"}; }
|
||||
case 26: { const std::array formats{VK_FORMAT_R16_SSCALED, VK_FORMAT_R16G16_SSCALED}; const auto count = std::min(attribute.components, 2u); return {formats[count - 1], count * 2u, 2u, "f32"}; }
|
||||
case 27: { const std::array formats{VK_FORMAT_R16_UINT, VK_FORMAT_R16G16_UINT}; const auto count = std::min(attribute.components, 2u); return {formats[count - 1], count * 2u, 2u, "u32"}; }
|
||||
case 28: { const std::array formats{VK_FORMAT_R16_SINT, VK_FORMAT_R16G16_SINT}; const auto count = std::min(attribute.components, 2u); return {formats[count - 1], count * 2u, 2u, "i32"}; }
|
||||
case 29: { const std::array formats{VK_FORMAT_R16_SFLOAT, VK_FORMAT_R16G16_SFLOAT}; const auto count = std::min(attribute.components, 2u); return {formats[count - 1], count * 2u, 2u, "f32"}; }
|
||||
case 1: { const std::array formats{VK_FORMAT_R8_UNORM}; const auto count = std::min(components, 1u); return {formats[count - 1], count * 1u, 1u, "f32"}; }
|
||||
case 2: { const std::array formats{VK_FORMAT_R8_SNORM}; const auto count = std::min(components, 1u); return {formats[count - 1], count * 1u, 1u, "f32"}; }
|
||||
case 3: { const std::array formats{VK_FORMAT_R8_USCALED}; const auto count = std::min(components, 1u); return {formats[count - 1], count * 1u, 1u, "f32"}; }
|
||||
case 4: { const std::array formats{VK_FORMAT_R8_SSCALED}; const auto count = std::min(components, 1u); return {formats[count - 1], count * 1u, 1u, "f32"}; }
|
||||
case 5: { const std::array formats{VK_FORMAT_R8_UINT}; const auto count = std::min(components, 1u); return {formats[count - 1], count * 1u, 1u, "u32"}; }
|
||||
case 6: { const std::array formats{VK_FORMAT_R8_SINT}; const auto count = std::min(components, 1u); return {formats[count - 1], count * 1u, 1u, "i32"}; }
|
||||
case 7: { const std::array formats{VK_FORMAT_R16_UNORM}; const auto count = std::min(components, 1u); return {formats[count - 1], count * 2u, 2u, "f32"}; }
|
||||
case 8: { const std::array formats{VK_FORMAT_R16_SNORM}; const auto count = std::min(components, 1u); return {formats[count - 1], count * 2u, 2u, "f32"}; }
|
||||
case 9: { const std::array formats{VK_FORMAT_R16_USCALED}; const auto count = std::min(components, 1u); return {formats[count - 1], count * 2u, 2u, "f32"}; }
|
||||
case 10: { const std::array formats{VK_FORMAT_R16_SSCALED}; const auto count = std::min(components, 1u); return {formats[count - 1], count * 2u, 2u, "f32"}; }
|
||||
case 11: { const std::array formats{VK_FORMAT_R16_UINT}; const auto count = std::min(components, 1u); return {formats[count - 1], count * 2u, 2u, "u32"}; }
|
||||
case 12: { const std::array formats{VK_FORMAT_R16_SINT}; const auto count = std::min(components, 1u); return {formats[count - 1], count * 2u, 2u, "i32"}; }
|
||||
case 13: { const std::array formats{VK_FORMAT_R16_SFLOAT}; const auto count = std::min(components, 1u); return {formats[count - 1], count * 2u, 2u, "f32"}; }
|
||||
case 14: { const std::array formats{VK_FORMAT_R8_UNORM, VK_FORMAT_R8G8_UNORM}; const auto count = std::min(components, 2u); return {formats[count - 1], count * 1u, 1u, "f32"}; }
|
||||
case 15: { const std::array formats{VK_FORMAT_R8_SNORM, VK_FORMAT_R8G8_SNORM}; const auto count = std::min(components, 2u); return {formats[count - 1], count * 1u, 1u, "f32"}; }
|
||||
case 16: { const std::array formats{VK_FORMAT_R8_USCALED, VK_FORMAT_R8G8_USCALED}; const auto count = std::min(components, 2u); return {formats[count - 1], count * 1u, 1u, "f32"}; }
|
||||
case 17: { const std::array formats{VK_FORMAT_R8_SSCALED, VK_FORMAT_R8G8_SSCALED}; const auto count = std::min(components, 2u); return {formats[count - 1], count * 1u, 1u, "f32"}; }
|
||||
case 18: { const std::array formats{VK_FORMAT_R8_UINT, VK_FORMAT_R8G8_UINT}; const auto count = std::min(components, 2u); return {formats[count - 1], count * 1u, 1u, "u32"}; }
|
||||
case 19: { const std::array formats{VK_FORMAT_R8_SINT, VK_FORMAT_R8G8_SINT}; const auto count = std::min(components, 2u); return {formats[count - 1], count * 1u, 1u, "i32"}; }
|
||||
case 20: { const std::array formats{VK_FORMAT_R32_UINT}; const auto count = std::min(components, 1u); return {formats[count - 1], count * 4u, 4u, "u32"}; }
|
||||
case 21: { const std::array formats{VK_FORMAT_R32_SINT}; const auto count = std::min(components, 1u); return {formats[count - 1], count * 4u, 4u, "i32"}; }
|
||||
case 22: { const std::array formats{VK_FORMAT_R32_SFLOAT}; const auto count = std::min(components, 1u); return {formats[count - 1], count * 4u, 4u, "f32"}; }
|
||||
case 23: { const std::array formats{VK_FORMAT_R16_UNORM, VK_FORMAT_R16G16_UNORM}; const auto count = std::min(components, 2u); return {formats[count - 1], count * 2u, 2u, "f32"}; }
|
||||
case 24: { const std::array formats{VK_FORMAT_R16_SNORM, VK_FORMAT_R16G16_SNORM}; const auto count = std::min(components, 2u); return {formats[count - 1], count * 2u, 2u, "f32"}; }
|
||||
case 25: { const std::array formats{VK_FORMAT_R16_USCALED, VK_FORMAT_R16G16_USCALED}; const auto count = std::min(components, 2u); return {formats[count - 1], count * 2u, 2u, "f32"}; }
|
||||
case 26: { const std::array formats{VK_FORMAT_R16_SSCALED, VK_FORMAT_R16G16_SSCALED}; const auto count = std::min(components, 2u); return {formats[count - 1], count * 2u, 2u, "f32"}; }
|
||||
case 27: { const std::array formats{VK_FORMAT_R16_UINT, VK_FORMAT_R16G16_UINT}; const auto count = std::min(components, 2u); return {formats[count - 1], count * 2u, 2u, "u32"}; }
|
||||
case 28: { const std::array formats{VK_FORMAT_R16_SINT, VK_FORMAT_R16G16_SINT}; const auto count = std::min(components, 2u); return {formats[count - 1], count * 2u, 2u, "i32"}; }
|
||||
case 29: { const std::array formats{VK_FORMAT_R16_SFLOAT, VK_FORMAT_R16G16_SFLOAT}; const auto count = std::min(components, 2u); return {formats[count - 1], count * 2u, 2u, "f32"}; }
|
||||
case 36: return {VK_FORMAT_B10G11R11_UFLOAT_PACK32, 4u, 4u, "f32"};
|
||||
case 50: return {VK_FORMAT_A2B10G10R10_UNORM_PACK32, 4u, 4u, "f32"};
|
||||
case 51: return {VK_FORMAT_A2B10G10R10_SNORM_PACK32, 4u, 4u, "f32"};
|
||||
|
|
@ -58,28 +60,28 @@ inline VertexFormat DecodeVertexFormat(const ShaderRecompiler::VertexAttribute&
|
|||
case 53: return {VK_FORMAT_A2B10G10R10_SSCALED_PACK32, 4u, 4u, "f32"};
|
||||
case 54: return {VK_FORMAT_A2B10G10R10_UINT_PACK32, 4u, 4u, "u32"};
|
||||
case 55: return {VK_FORMAT_A2B10G10R10_SINT_PACK32, 4u, 4u, "i32"};
|
||||
case 56: { const std::array formats{VK_FORMAT_R8_UNORM, VK_FORMAT_R8G8_UNORM, VK_FORMAT_R8G8B8_UNORM, VK_FORMAT_R8G8B8A8_UNORM}; const auto count = std::min(attribute.components, 4u); return {formats[count - 1], count * 1u, 1u, "f32"}; }
|
||||
case 57: { const std::array formats{VK_FORMAT_R8_SNORM, VK_FORMAT_R8G8_SNORM, VK_FORMAT_R8G8B8_SNORM, VK_FORMAT_R8G8B8A8_SNORM}; const auto count = std::min(attribute.components, 4u); return {formats[count - 1], count * 1u, 1u, "f32"}; }
|
||||
case 58: { const std::array formats{VK_FORMAT_R8_USCALED, VK_FORMAT_R8G8_USCALED, VK_FORMAT_R8G8B8_USCALED, VK_FORMAT_R8G8B8A8_USCALED}; const auto count = std::min(attribute.components, 4u); return {formats[count - 1], count * 1u, 1u, "f32"}; }
|
||||
case 59: { const std::array formats{VK_FORMAT_R8_SSCALED, VK_FORMAT_R8G8_SSCALED, VK_FORMAT_R8G8B8_SSCALED, VK_FORMAT_R8G8B8A8_SSCALED}; const auto count = std::min(attribute.components, 4u); return {formats[count - 1], count * 1u, 1u, "f32"}; }
|
||||
case 60: { const std::array formats{VK_FORMAT_R8_UINT, VK_FORMAT_R8G8_UINT, VK_FORMAT_R8G8B8_UINT, VK_FORMAT_R8G8B8A8_UINT}; const auto count = std::min(attribute.components, 4u); return {formats[count - 1], count * 1u, 1u, "u32"}; }
|
||||
case 61: { const std::array formats{VK_FORMAT_R8_SINT, VK_FORMAT_R8G8_SINT, VK_FORMAT_R8G8B8_SINT, VK_FORMAT_R8G8B8A8_SINT}; const auto count = std::min(attribute.components, 4u); return {formats[count - 1], count * 1u, 1u, "i32"}; }
|
||||
case 62: { const std::array formats{VK_FORMAT_R32_UINT, VK_FORMAT_R32G32_UINT}; const auto count = std::min(attribute.components, 2u); return {formats[count - 1], count * 4u, 4u, "u32"}; }
|
||||
case 63: { const std::array formats{VK_FORMAT_R32_SINT, VK_FORMAT_R32G32_SINT}; const auto count = std::min(attribute.components, 2u); return {formats[count - 1], count * 4u, 4u, "i32"}; }
|
||||
case 64: { const std::array formats{VK_FORMAT_R32_SFLOAT, VK_FORMAT_R32G32_SFLOAT}; const auto count = std::min(attribute.components, 2u); return {formats[count - 1], count * 4u, 4u, "f32"}; }
|
||||
case 65: { const std::array formats{VK_FORMAT_R16_UNORM, VK_FORMAT_R16G16_UNORM, VK_FORMAT_R16G16B16_UNORM, VK_FORMAT_R16G16B16A16_UNORM}; const auto count = std::min(attribute.components, 4u); return {formats[count - 1], count * 2u, 2u, "f32"}; }
|
||||
case 66: { const std::array formats{VK_FORMAT_R16_SNORM, VK_FORMAT_R16G16_SNORM, VK_FORMAT_R16G16B16_SNORM, VK_FORMAT_R16G16B16A16_SNORM}; const auto count = std::min(attribute.components, 4u); return {formats[count - 1], count * 2u, 2u, "f32"}; }
|
||||
case 67: { const std::array formats{VK_FORMAT_R16_USCALED, VK_FORMAT_R16G16_USCALED, VK_FORMAT_R16G16B16_USCALED, VK_FORMAT_R16G16B16A16_USCALED}; const auto count = std::min(attribute.components, 4u); return {formats[count - 1], count * 2u, 2u, "f32"}; }
|
||||
case 68: { const std::array formats{VK_FORMAT_R16_SSCALED, VK_FORMAT_R16G16_SSCALED, VK_FORMAT_R16G16B16_SSCALED, VK_FORMAT_R16G16B16A16_SSCALED}; const auto count = std::min(attribute.components, 4u); return {formats[count - 1], count * 2u, 2u, "f32"}; }
|
||||
case 69: { const std::array formats{VK_FORMAT_R16_UINT, VK_FORMAT_R16G16_UINT, VK_FORMAT_R16G16B16_UINT, VK_FORMAT_R16G16B16A16_UINT}; const auto count = std::min(attribute.components, 4u); return {formats[count - 1], count * 2u, 2u, "u32"}; }
|
||||
case 70: { const std::array formats{VK_FORMAT_R16_SINT, VK_FORMAT_R16G16_SINT, VK_FORMAT_R16G16B16_SINT, VK_FORMAT_R16G16B16A16_SINT}; const auto count = std::min(attribute.components, 4u); return {formats[count - 1], count * 2u, 2u, "i32"}; }
|
||||
case 71: { const std::array formats{VK_FORMAT_R16_SFLOAT, VK_FORMAT_R16G16_SFLOAT, VK_FORMAT_R16G16B16_SFLOAT, VK_FORMAT_R16G16B16A16_SFLOAT}; const auto count = std::min(attribute.components, 4u); return {formats[count - 1], count * 2u, 2u, "f32"}; }
|
||||
case 72: { const std::array formats{VK_FORMAT_R32_UINT, VK_FORMAT_R32G32_UINT, VK_FORMAT_R32G32B32_UINT}; const auto count = std::min(attribute.components, 3u); return {formats[count - 1], count * 4u, 4u, "u32"}; }
|
||||
case 73: { const std::array formats{VK_FORMAT_R32_SINT, VK_FORMAT_R32G32_SINT, VK_FORMAT_R32G32B32_SINT}; const auto count = std::min(attribute.components, 3u); return {formats[count - 1], count * 4u, 4u, "i32"}; }
|
||||
case 74: { const std::array formats{VK_FORMAT_R32_SFLOAT, VK_FORMAT_R32G32_SFLOAT, VK_FORMAT_R32G32B32_SFLOAT}; const auto count = std::min(attribute.components, 3u); return {formats[count - 1], count * 4u, 4u, "f32"}; }
|
||||
case 75: { const std::array formats{VK_FORMAT_R32_UINT, VK_FORMAT_R32G32_UINT, VK_FORMAT_R32G32B32_UINT, VK_FORMAT_R32G32B32A32_UINT}; const auto count = std::min(attribute.components, 4u); return {formats[count - 1], count * 4u, 4u, "u32"}; }
|
||||
case 76: { const std::array formats{VK_FORMAT_R32_SINT, VK_FORMAT_R32G32_SINT, VK_FORMAT_R32G32B32_SINT, VK_FORMAT_R32G32B32A32_SINT}; const auto count = std::min(attribute.components, 4u); return {formats[count - 1], count * 4u, 4u, "i32"}; }
|
||||
case 77: { const std::array formats{VK_FORMAT_R32_SFLOAT, VK_FORMAT_R32G32_SFLOAT, VK_FORMAT_R32G32B32_SFLOAT, VK_FORMAT_R32G32B32A32_SFLOAT}; const auto count = std::min(attribute.components, 4u); return {formats[count - 1], count * 4u, 4u, "f32"}; }
|
||||
case 56: { const std::array formats{VK_FORMAT_R8_UNORM, VK_FORMAT_R8G8_UNORM, VK_FORMAT_R8G8B8_UNORM, VK_FORMAT_R8G8B8A8_UNORM}; const auto count = std::min(components, 4u); return {formats[count - 1], count * 1u, 1u, "f32"}; }
|
||||
case 57: { const std::array formats{VK_FORMAT_R8_SNORM, VK_FORMAT_R8G8_SNORM, VK_FORMAT_R8G8B8_SNORM, VK_FORMAT_R8G8B8A8_SNORM}; const auto count = std::min(components, 4u); return {formats[count - 1], count * 1u, 1u, "f32"}; }
|
||||
case 58: { const std::array formats{VK_FORMAT_R8_USCALED, VK_FORMAT_R8G8_USCALED, VK_FORMAT_R8G8B8_USCALED, VK_FORMAT_R8G8B8A8_USCALED}; const auto count = std::min(components, 4u); return {formats[count - 1], count * 1u, 1u, "f32"}; }
|
||||
case 59: { const std::array formats{VK_FORMAT_R8_SSCALED, VK_FORMAT_R8G8_SSCALED, VK_FORMAT_R8G8B8_SSCALED, VK_FORMAT_R8G8B8A8_SSCALED}; const auto count = std::min(components, 4u); return {formats[count - 1], count * 1u, 1u, "f32"}; }
|
||||
case 60: { const std::array formats{VK_FORMAT_R8_UINT, VK_FORMAT_R8G8_UINT, VK_FORMAT_R8G8B8_UINT, VK_FORMAT_R8G8B8A8_UINT}; const auto count = std::min(components, 4u); return {formats[count - 1], count * 1u, 1u, "u32"}; }
|
||||
case 61: { const std::array formats{VK_FORMAT_R8_SINT, VK_FORMAT_R8G8_SINT, VK_FORMAT_R8G8B8_SINT, VK_FORMAT_R8G8B8A8_SINT}; const auto count = std::min(components, 4u); return {formats[count - 1], count * 1u, 1u, "i32"}; }
|
||||
case 62: { const std::array formats{VK_FORMAT_R32_UINT, VK_FORMAT_R32G32_UINT}; const auto count = std::min(components, 2u); return {formats[count - 1], count * 4u, 4u, "u32"}; }
|
||||
case 63: { const std::array formats{VK_FORMAT_R32_SINT, VK_FORMAT_R32G32_SINT}; const auto count = std::min(components, 2u); return {formats[count - 1], count * 4u, 4u, "i32"}; }
|
||||
case 64: { const std::array formats{VK_FORMAT_R32_SFLOAT, VK_FORMAT_R32G32_SFLOAT}; const auto count = std::min(components, 2u); return {formats[count - 1], count * 4u, 4u, "f32"}; }
|
||||
case 65: { const std::array formats{VK_FORMAT_R16_UNORM, VK_FORMAT_R16G16_UNORM, VK_FORMAT_R16G16B16_UNORM, VK_FORMAT_R16G16B16A16_UNORM}; const auto count = std::min(components, 4u); return {formats[count - 1], count * 2u, 2u, "f32"}; }
|
||||
case 66: { const std::array formats{VK_FORMAT_R16_SNORM, VK_FORMAT_R16G16_SNORM, VK_FORMAT_R16G16B16_SNORM, VK_FORMAT_R16G16B16A16_SNORM}; const auto count = std::min(components, 4u); return {formats[count - 1], count * 2u, 2u, "f32"}; }
|
||||
case 67: { const std::array formats{VK_FORMAT_R16_USCALED, VK_FORMAT_R16G16_USCALED, VK_FORMAT_R16G16B16_USCALED, VK_FORMAT_R16G16B16A16_USCALED}; const auto count = std::min(components, 4u); return {formats[count - 1], count * 2u, 2u, "f32"}; }
|
||||
case 68: { const std::array formats{VK_FORMAT_R16_SSCALED, VK_FORMAT_R16G16_SSCALED, VK_FORMAT_R16G16B16_SSCALED, VK_FORMAT_R16G16B16A16_SSCALED}; const auto count = std::min(components, 4u); return {formats[count - 1], count * 2u, 2u, "f32"}; }
|
||||
case 69: { const std::array formats{VK_FORMAT_R16_UINT, VK_FORMAT_R16G16_UINT, VK_FORMAT_R16G16B16_UINT, VK_FORMAT_R16G16B16A16_UINT}; const auto count = std::min(components, 4u); return {formats[count - 1], count * 2u, 2u, "u32"}; }
|
||||
case 70: { const std::array formats{VK_FORMAT_R16_SINT, VK_FORMAT_R16G16_SINT, VK_FORMAT_R16G16B16_SINT, VK_FORMAT_R16G16B16A16_SINT}; const auto count = std::min(components, 4u); return {formats[count - 1], count * 2u, 2u, "i32"}; }
|
||||
case 71: { const std::array formats{VK_FORMAT_R16_SFLOAT, VK_FORMAT_R16G16_SFLOAT, VK_FORMAT_R16G16B16_SFLOAT, VK_FORMAT_R16G16B16A16_SFLOAT}; const auto count = std::min(components, 4u); return {formats[count - 1], count * 2u, 2u, "f32"}; }
|
||||
case 72: { const std::array formats{VK_FORMAT_R32_UINT, VK_FORMAT_R32G32_UINT, VK_FORMAT_R32G32B32_UINT}; const auto count = std::min(components, 3u); return {formats[count - 1], count * 4u, 4u, "u32"}; }
|
||||
case 73: { const std::array formats{VK_FORMAT_R32_SINT, VK_FORMAT_R32G32_SINT, VK_FORMAT_R32G32B32_SINT}; const auto count = std::min(components, 3u); return {formats[count - 1], count * 4u, 4u, "i32"}; }
|
||||
case 74: { const std::array formats{VK_FORMAT_R32_SFLOAT, VK_FORMAT_R32G32_SFLOAT, VK_FORMAT_R32G32B32_SFLOAT}; const auto count = std::min(components, 3u); return {formats[count - 1], count * 4u, 4u, "f32"}; }
|
||||
case 75: { const std::array formats{VK_FORMAT_R32_UINT, VK_FORMAT_R32G32_UINT, VK_FORMAT_R32G32B32_UINT, VK_FORMAT_R32G32B32A32_UINT}; const auto count = std::min(components, 4u); return {formats[count - 1], count * 4u, 4u, "u32"}; }
|
||||
case 76: { const std::array formats{VK_FORMAT_R32_SINT, VK_FORMAT_R32G32_SINT, VK_FORMAT_R32G32B32_SINT, VK_FORMAT_R32G32B32A32_SINT}; const auto count = std::min(components, 4u); return {formats[count - 1], count * 4u, 4u, "i32"}; }
|
||||
case 77: { const std::array formats{VK_FORMAT_R32_SFLOAT, VK_FORMAT_R32G32_SFLOAT, VK_FORMAT_R32G32B32_SFLOAT, VK_FORMAT_R32G32B32A32_SFLOAT}; const auto count = std::min(components, 4u); return {formats[count - 1], count * 4u, 4u, "f32"}; }
|
||||
default: throw std::runtime_error("AGC graphics: unsupported vertex format " + std::to_string(format));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,16 @@ std::uint32_t standardOffset(std::uint32_t x, std::uint32_t y, std::uint32_t ele
|
|||
}
|
||||
}
|
||||
|
||||
std::uint32_t standard64Extra(std::uint32_t x, std::uint32_t y, std::uint32_t elementBytes) {
|
||||
switch (elementBytes) {
|
||||
case 1u: return ((x << 7) & 0x2000u) ^ ((x << 8) & 0x8000u) ^ ((y << 6) & 0x1000u) ^ ((y << 7) & 0x4000u);
|
||||
case 2u: return ((x << 7) & 0x2000u) ^ ((x << 8) & 0x8000u) ^ ((y << 7) & 0x1000u) ^ ((y << 8) & 0x4000u);
|
||||
case 4u: return ((x << 8) & 0x2000u) ^ ((x << 9) & 0x8000u) ^ ((y << 7) & 0x1000u) ^ ((y << 8) & 0x4000u);
|
||||
case 8u: return ((x << 8) & 0x2000u) ^ ((x << 9) & 0x8000u) ^ ((y << 8) & 0x1000u) ^ ((y << 9) & 0x4000u);
|
||||
default: return ((x << 9) & 0x2000u) ^ ((x << 10) & 0x8000u) ^ ((y << 8) & 0x1000u) ^ ((y << 9) & 0x4000u);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ColorTileMode DecodeColorTileMode(std::uint32_t attrib3) {
|
||||
|
|
@ -35,7 +45,7 @@ ColorTileMode DecodeColorTileMode(std::uint32_t attrib3) {
|
|||
const auto mode = (attrib3 >> 14u) & 0x1fu;
|
||||
const auto fmaskMode = (attrib3 >> 19u) & 0x1fu;
|
||||
require(fmaskMode == 0 || fmaskMode == 0x18, "AGC graphics: unsupported color FMASK swizzle mode");
|
||||
require(mode == 0 || mode == 5 || mode == 0x1b, "AGC graphics: unsupported color tile mode");
|
||||
require(mode == 0 || mode == 5 || mode == 9 || mode == 0x1b, "AGC graphics: unsupported color tile mode");
|
||||
return static_cast<ColorTileMode>(mode);
|
||||
}
|
||||
|
||||
|
|
@ -70,18 +80,18 @@ const SwizzleTables& renderTargetTables(std::uint32_t bytesPerElement, std::uint
|
|||
return tables[index];
|
||||
}
|
||||
|
||||
const SwizzleTables& standardTables(std::uint32_t bytesPerElement, std::uint32_t blockWidth, std::uint32_t blockHeight) {
|
||||
static std::once_flag once[5];
|
||||
static SwizzleTables tables[5];
|
||||
const SwizzleTables& standardTables(std::uint32_t bytesPerElement, std::uint32_t blockWidth, std::uint32_t blockHeight, bool block64KB) {
|
||||
static std::once_flag once[2][5];
|
||||
static SwizzleTables tables[2][5];
|
||||
const auto index = static_cast<std::size_t>(std::countr_zero(bytesPerElement));
|
||||
std::call_once(once[index], [&] {
|
||||
auto& table = tables[index];
|
||||
std::call_once(once[block64KB][index], [&] {
|
||||
auto& table = tables[block64KB][index];
|
||||
table.x.resize(blockWidth);
|
||||
table.y.resize(blockHeight);
|
||||
for (std::uint32_t x = 0; x < blockWidth; ++x) table.x[x] = standardOffset(x, 0, bytesPerElement);
|
||||
for (std::uint32_t y = 0; y < blockHeight; ++y) table.y[y] = standardOffset(0, y, bytesPerElement);
|
||||
for (std::uint32_t x = 0; x < blockWidth; ++x) table.x[x] = standardOffset(x, 0, bytesPerElement) ^ (block64KB ? standard64Extra(x, 0, bytesPerElement) : 0u);
|
||||
for (std::uint32_t y = 0; y < blockHeight; ++y) table.y[y] = standardOffset(0, y, bytesPerElement) ^ (block64KB ? standard64Extra(0, y, bytesPerElement) : 0u);
|
||||
});
|
||||
return tables[index];
|
||||
return tables[block64KB][index];
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -108,13 +118,16 @@ ColorTargetLayout::ColorTargetLayout(std::uint32_t width, std::uint32_t height,
|
|||
yOffsets = tables.y.data();
|
||||
break;
|
||||
}
|
||||
case ColorTileMode::Standard4KB: {
|
||||
case ColorTileMode::Standard4KB:
|
||||
case ColorTileMode::Standard64KB: {
|
||||
const auto log2Bytes = static_cast<std::uint32_t>(std::countr_zero(bytesPerElement));
|
||||
blockWidth = 1u << (6u - (log2Bytes + 1u) / 2u);
|
||||
blockHeight = 1u << (6u - log2Bytes / 2u);
|
||||
const bool block64KB = mode == ColorTileMode::Standard64KB;
|
||||
const auto log2Side = block64KB ? 8u : 6u;
|
||||
blockWidth = 1u << (log2Side - log2Bytes / 2u);
|
||||
blockHeight = 1u << (log2Side - (log2Bytes + 1u) / 2u);
|
||||
pitch = (width + blockWidth - 1u) / blockWidth * blockWidth;
|
||||
paddedHeight = (height + blockHeight - 1u) / blockHeight * blockHeight;
|
||||
const auto& tables = standardTables(bytesPerElement, blockWidth, blockHeight);
|
||||
const auto& tables = standardTables(bytesPerElement, blockWidth, blockHeight, block64KB);
|
||||
xOffsets = tables.x.data();
|
||||
yOffsets = tables.y.data();
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -44,6 +44,18 @@ bool AllKeysEqual(const std::uint8_t* keys, std::size_t count, std::uint8_t firs
|
|||
return true;
|
||||
}
|
||||
|
||||
DccKeys ByteKeys(std::uint8_t key) {
|
||||
switch (key) {
|
||||
case 0x00: return DccKeys::Clear0000;
|
||||
case 0x40: return DccKeys::Clear0001;
|
||||
case 0x80: return DccKeys::Clear1110;
|
||||
case 0xc0: return DccKeys::Clear1111;
|
||||
case 0x20: return DccKeys::ClearRegister;
|
||||
case 0xff: return DccKeys::Uncompressed;
|
||||
default: return DccKeys::Mixed;
|
||||
}
|
||||
}
|
||||
|
||||
// APS5_PROFILE_DRAW: key scans, their bytes and time, printed as [dcc] every 10 s, with the hits of
|
||||
// the GPU-store memo below and the "uncompressed" key stores by where they were made. Flush syncs
|
||||
// stay 0 (no key read syncs the recorder); the field is kept so the line's shape does not change.
|
||||
|
|
@ -226,7 +238,7 @@ bool StoreUncompressedOnGpu(const Context& context, Recorder& recorder, std::uin
|
|||
// Queued on the open batch and recorded with the batch's other key stores as one run (or before
|
||||
// a later command writing the keys; see Recorder::QueueKeyStore).
|
||||
recorder.QueueKeyStore(import->buffer, first, last, std::move(seed), begin, begin + count);
|
||||
recorder.NotePendingWrite(begin, count);
|
||||
recorder.NotePendingFill(begin, count, 0xff);
|
||||
GuestMemory::MarkWritten(begin, count);
|
||||
// After the note: an unlocked memo lookup drops an entry the snapshot does not cover yet. The
|
||||
// entry is kept only for the active recorder (the one the flush hook and the lookups consult).
|
||||
|
|
@ -380,13 +392,27 @@ std::size_t DccKeyBytes(std::uint64_t surfaceBytes) {
|
|||
return static_cast<std::size_t>(surfaceBytes / KeyBytes);
|
||||
}
|
||||
|
||||
std::size_t DccKeyCount(TextureTileMode tileMode, std::uint32_t elementBytes, std::uint32_t width, std::uint32_t height, std::uint64_t surfaceBytes) {
|
||||
const auto keys = DccKeyBytes(surfaceBytes);
|
||||
if (tileMode != TextureTileMode::kR64KBX || elementBytes != 4) return keys;
|
||||
constexpr std::uint64_t MetaBlockEdge = 512;
|
||||
constexpr std::uint64_t MetaBlockBytes = 4096;
|
||||
const auto console = (width + MetaBlockEdge - 1) / MetaBlockEdge * ((height + MetaBlockEdge - 1) / MetaBlockEdge) * MetaBlockBytes;
|
||||
return std::max(keys, static_cast<std::size_t>(console));
|
||||
}
|
||||
|
||||
std::size_t DccKeyCount(const GuestTextureResource& surface, std::uint64_t surfaceBytes) {
|
||||
const bool single = surface.mipCount == 1 && surface.baseArray == 0 && surface.depthOrLastArray == 0 && (surface.dimension == TextureDimension::k2D || surface.dimension == TextureDimension::k2DArray);
|
||||
if (!single || !surface.dccPipeAligned || surface.tileMode != TextureTileMode::kR64KBX) return DccKeyBytes(surfaceBytes);
|
||||
return DccKeyCount(surface.tileMode, BytesPerElement(surface.format), surface.width, surface.height, surfaceBytes);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
// ReadDccKeys, saying in `memoized` whether the answer came from the pending-store memo rather
|
||||
// than the bytes.
|
||||
DccKeys readDccKeys(std::uint64_t metaAddress, std::uint64_t surfaceBytes, bool& memoized) {
|
||||
DccKeys readDccKeys(std::uint64_t metaAddress, std::size_t count, bool& memoized) {
|
||||
memoized = false;
|
||||
const auto count = static_cast<std::size_t>(surfaceBytes / KeyBytes);
|
||||
if (metaAddress == 0 || count == 0 || !GuestMemory::Accessible(reinterpret_cast<const void*>(metaAddress), count)) return DccKeys::Unreadable;
|
||||
// A driver store still pending on the GPU is not in the bytes yet.
|
||||
if (const auto memo = MemoizedKeys(metaAddress, metaAddress + count)) {
|
||||
|
|
@ -412,22 +438,13 @@ DccKeys readDccKeys(std::uint64_t metaAddress, std::uint64_t surfaceBytes, bool&
|
|||
const auto start = ScanProfileEnabled() ? std::chrono::steady_clock::now() : std::chrono::steady_clock::time_point{};
|
||||
const bool uniform = AllKeysEqual(keys + 1, count - 1, first);
|
||||
if (ScanProfileEnabled()) CountScan(count, start);
|
||||
if (!uniform) return DccKeys::Mixed;
|
||||
switch (first) {
|
||||
case 0x00: return DccKeys::Clear0000;
|
||||
case 0x40: return DccKeys::Clear0001;
|
||||
case 0x80: return DccKeys::Clear1110;
|
||||
case 0xc0: return DccKeys::Clear1111;
|
||||
case 0x20: return DccKeys::ClearRegister;
|
||||
case 0xff: return DccKeys::Uncompressed;
|
||||
default: return DccKeys::Mixed;
|
||||
}
|
||||
return uniform ? ByteKeys(first) : DccKeys::Mixed;
|
||||
}
|
||||
|
||||
DccKeys textureClearKeys(const GuestTextureResource& resource, std::uint64_t guestBytes, bool& memoized) {
|
||||
memoized = false;
|
||||
if (resource.dccAddress == 0) return DccKeys::Uncompressed;
|
||||
const auto keys = readDccKeys(resource.dccAddress, guestBytes, memoized);
|
||||
const auto keys = readDccKeys(resource.dccAddress, DccKeyBytes(guestBytes), memoized);
|
||||
if (keys == DccKeys::Uncompressed) return keys;
|
||||
if (IsConvertedTextureFormat(resource.format) && (keys == DccKeys::Clear0001 || keys == DccKeys::Clear1110)) throw std::runtime_error(std::string("AGC graphics: DCC clear code ") + DccKeysName(keys) + " of converted texture format " + std::to_string(resource.format) + " is not implemented");
|
||||
std::byte probe[16]{};
|
||||
|
|
@ -441,15 +458,7 @@ DccKeys textureClearKeys(const GuestTextureResource& resource, std::uint64_t gue
|
|||
return keys;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
DccKeys ReadDccKeys(std::uint64_t metaAddress, std::uint64_t surfaceBytes) {
|
||||
bool memoized = false;
|
||||
return readDccKeys(metaAddress, surfaceBytes, memoized);
|
||||
}
|
||||
|
||||
DccKeys CurrentDccKeys(std::uint64_t metaAddress, std::uint64_t surfaceBytes) {
|
||||
const auto count = static_cast<std::size_t>(surfaceBytes / KeyBytes);
|
||||
DccKeys currentDccKeys(std::uint64_t metaAddress, std::size_t count) {
|
||||
if (metaAddress != 0 && count != 0 && GuestMemory::GpuMutex().HeldByThisThread()) {
|
||||
if (auto* recorder = Recorder::Active(); recorder != nullptr && recorder->PendingWriteOverlaps(metaAddress, count)) {
|
||||
if (const auto keys = PendingStoreKeys(*recorder, metaAddress, count)) return *keys;
|
||||
|
|
@ -457,7 +466,35 @@ DccKeys CurrentDccKeys(std::uint64_t metaAddress, std::uint64_t surfaceBytes) {
|
|||
recorder->SyncThrough(metaAddress, count);
|
||||
}
|
||||
}
|
||||
return ReadDccKeys(metaAddress, surfaceBytes);
|
||||
bool memoized = false;
|
||||
return readDccKeys(metaAddress, count, memoized);
|
||||
}
|
||||
|
||||
std::size_t StoredKeyCount(std::uint64_t metaAddress, std::uint64_t surfaceBytes, std::size_t keyCount) {
|
||||
const auto keys = DccKeyBytes(surfaceBytes);
|
||||
Require(keyCount >= keys, "a DCC key range covers fewer keys than one per 256 surface bytes");
|
||||
if (metaAddress == 0 || keyCount == 0) return 0;
|
||||
if (GuestMemory::Accessible(reinterpret_cast<const void*>(metaAddress), keyCount, true)) return keyCount;
|
||||
if (keyCount == keys) return 0;
|
||||
char message[256];
|
||||
std::snprintf(message, sizeof(message), "AGC graphics: DCC metadata 0x%llx is not writable over the 0x%zx key bytes of its surface (0x%zx at one per 256 bytes): metadata smaller than the console's DCC extent is not modeled", static_cast<unsigned long long>(metaAddress), keyCount, keys);
|
||||
throw std::runtime_error(message);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
DccKeys ReadDccKeys(std::uint64_t metaAddress, std::uint64_t surfaceBytes) {
|
||||
bool memoized = false;
|
||||
return readDccKeys(metaAddress, DccKeyBytes(surfaceBytes), memoized);
|
||||
}
|
||||
|
||||
DccKeys CurrentDccKeys(std::uint64_t metaAddress, std::uint64_t surfaceBytes) {
|
||||
return currentDccKeys(metaAddress, DccKeyBytes(surfaceBytes));
|
||||
}
|
||||
|
||||
DccKeys CurrentDccKeys(std::uint64_t metaAddress, std::uint64_t surfaceBytes, std::size_t keyCount) {
|
||||
Require(keyCount >= DccKeyBytes(surfaceBytes), "a DCC key range covers fewer keys than one per 256 surface bytes");
|
||||
return currentDccKeys(metaAddress, keyCount);
|
||||
}
|
||||
|
||||
bool IsDccClear(DccKeys keys) {
|
||||
|
|
@ -465,8 +502,12 @@ bool IsDccClear(DccKeys keys) {
|
|||
}
|
||||
|
||||
void MarkDccUncompressed(std::uint64_t metaAddress, std::uint64_t surfaceBytes) {
|
||||
const auto count = static_cast<std::size_t>(surfaceBytes / KeyBytes);
|
||||
if (metaAddress == 0 || count == 0 || !GuestMemory::Accessible(reinterpret_cast<const void*>(metaAddress), count, true)) return;
|
||||
MarkDccUncompressed(metaAddress, surfaceBytes, DccKeyBytes(surfaceBytes));
|
||||
}
|
||||
|
||||
void MarkDccUncompressed(std::uint64_t metaAddress, std::uint64_t surfaceBytes, std::size_t keyCount) {
|
||||
const auto count = StoredKeyCount(metaAddress, surfaceBytes, keyCount);
|
||||
if (count == 0) return;
|
||||
// The bytes are trusted only when no recorded work writes them (lock-free, with or without
|
||||
// GuestMemory::GpuMutex); otherwise the store runs and waits through the flush hook. A pending
|
||||
// GPU store of the driver's own is no reason to skip: a title kernel recorded into the same
|
||||
|
|
@ -476,8 +517,12 @@ void MarkDccUncompressed(std::uint64_t metaAddress, std::uint64_t surfaceBytes)
|
|||
}
|
||||
|
||||
void MarkDccUncompressed(const Context& context, std::uint64_t metaAddress, std::uint64_t surfaceBytes) {
|
||||
const auto count = static_cast<std::size_t>(surfaceBytes / KeyBytes);
|
||||
if (metaAddress == 0 || count == 0 || !GuestMemory::Accessible(reinterpret_cast<const void*>(metaAddress), count, true)) return;
|
||||
MarkDccUncompressed(context, metaAddress, surfaceBytes, DccKeyBytes(surfaceBytes));
|
||||
}
|
||||
|
||||
void MarkDccUncompressed(const Context& context, std::uint64_t metaAddress, std::uint64_t surfaceBytes, std::size_t keyCount) {
|
||||
const auto count = StoredKeyCount(metaAddress, surfaceBytes, keyCount);
|
||||
if (count == 0) return;
|
||||
auto* recorder = CpuKeysOnly() || !GuestMemory::GpuMutex().HeldByThisThread() ? nullptr : Recorder::Active();
|
||||
// The bytes are scanned only when no recorded work writes them: a pending clear kernel's keys are
|
||||
// not in memory yet, so the bytes could read as uncompressed while the kernel will store a clear
|
||||
|
|
@ -583,4 +628,63 @@ void ReadTextureSurface(const GuestTextureResource& resource, DccKeys keys, std:
|
|||
else FillDccClear(ResolveTextureFormat(resource.format), keys, resource.dccAlphaOnMsb, bytes);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
DccKeys EncodedKeys(const GuestTextureResource& resource, DccKeys keys) {
|
||||
std::byte probe[16]{};
|
||||
if (!IsDccClear(keys) || !FillDccClear(ResolveTextureFormat(resource.format), keys, resource.dccAlphaOnMsb, std::span(probe, std::min<std::size_t>(sizeof(probe), BytesPerElement(resource.format))))) return DccKeys::Uncompressed;
|
||||
return keys;
|
||||
}
|
||||
|
||||
bool PendingFillsLeaveUncompressed(const Recorder& recorder, const GuestTextureResource& resource, std::size_t count) {
|
||||
const auto begin = resource.dccAddress;
|
||||
if (!GuestMemory::Accessible(reinterpret_cast<const void*>(begin), count) || AnyShadowedOverlaps(begin, count)) return false;
|
||||
auto writes = recorder.PendingWritesOver(begin, count);
|
||||
std::sort(writes.begin(), writes.end(), [](const Recorder::PendingWrite& a, const Recorder::PendingWrite& b) { return a.note > b.note; });
|
||||
std::vector<std::pair<std::uint64_t, std::uint64_t>> unwritten{{begin, begin + count}};
|
||||
int value = -1;
|
||||
bool mixed = false;
|
||||
const auto see = [&](int key) {
|
||||
if (value < 0) value = key;
|
||||
else if (value != key) mixed = true;
|
||||
};
|
||||
for (const auto& write : writes) {
|
||||
if (mixed || unwritten.empty()) break;
|
||||
std::vector<std::pair<std::uint64_t, std::uint64_t>> rest;
|
||||
bool covered = false;
|
||||
for (const auto& [from, to] : unwritten) {
|
||||
if (write.end <= from || to <= write.begin) {
|
||||
rest.emplace_back(from, to);
|
||||
continue;
|
||||
}
|
||||
covered = true;
|
||||
if (from < write.begin) rest.emplace_back(from, write.begin);
|
||||
if (write.end < to) rest.emplace_back(write.end, to);
|
||||
}
|
||||
unwritten = std::move(rest);
|
||||
if (covered && write.value >= 0) see(write.value);
|
||||
}
|
||||
for (const auto& [from, to] : unwritten) {
|
||||
if (mixed) break;
|
||||
const auto* keys = reinterpret_cast<const std::uint8_t*>(from);
|
||||
if (AllKeysEqual(keys + 1, static_cast<std::size_t>(to - from) - 1, keys[0])) see(keys[0]);
|
||||
else mixed = true;
|
||||
}
|
||||
return mixed || (value >= 0 && EncodedKeys(resource, ByteKeys(static_cast<std::uint8_t>(value))) == DccKeys::Uncompressed);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
std::optional<DccKeys> WaitForKeyWriters(const GuestTextureResource& resource, std::uint64_t guestBytes) {
|
||||
const auto count = static_cast<std::size_t>(guestBytes / KeyBytes);
|
||||
const auto metaAddress = resource.dccAddress;
|
||||
if (metaAddress == 0 || count == 0 || !GuestMemory::GpuMutex().HeldByThisThread()) return std::nullopt;
|
||||
auto* recorder = Recorder::Active();
|
||||
if (recorder == nullptr || !recorder->PendingWriteOverlaps(metaAddress, count) || PendingStoreKeys(*recorder, metaAddress, count).has_value()) return std::nullopt;
|
||||
if (PendingFillsLeaveUncompressed(*recorder, resource, count)) return DccKeys::Uncompressed;
|
||||
Recorder::CountSync(2);
|
||||
recorder->SyncThrough(metaAddress, count);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ std::uint32_t GuestFormatFor(VkFormat format, std::uint32_t elementBytes) {
|
|||
|
||||
// The color buffer as a single-mip 2D surface descriptor (tile mode SW_64KB_R_X).
|
||||
GuestTextureResource SurfaceForTarget(const ColorTarget& color) {
|
||||
Require(color.tileMode == ColorTileMode::RenderTarget || color.tileMode == ColorTileMode::Standard4KB, "only 4 KiB standard and 64 KiB tiled color targets are resident");
|
||||
Require(color.tileMode != ColorTileMode::Linear, "linear color targets are not resident");
|
||||
const bool chain = color.mipCount > 1;
|
||||
GuestTextureResource surface{};
|
||||
surface.baseAddress = chain ? color.surfaceAddress : color.address;
|
||||
|
|
@ -62,6 +62,7 @@ GuestTextureResource SurfaceForTarget(const ColorTarget& color) {
|
|||
surface.dstSelW = 7;
|
||||
surface.dccAddress = color.dccAddress;
|
||||
surface.dccAlphaOnMsb = color.dccAlphaOnMsb;
|
||||
surface.dccPipeAligned = color.dccPipeAligned;
|
||||
return surface;
|
||||
}
|
||||
|
||||
|
|
@ -69,6 +70,11 @@ GuestTextureResource SurfaceForTarget(const ColorTarget& color) {
|
|||
|
||||
namespace {
|
||||
|
||||
std::size_t colorKeyCount(const ColorTarget& color, std::size_t bytes) {
|
||||
if (color.mipCount != 1 || color.depth != 1 || !color.dccPipeAligned) return DccKeyBytes(bytes);
|
||||
return DccKeyCount(ColorTextureTileMode(color.tileMode), color.elementBytes, color.extent.width, color.extent.height, bytes);
|
||||
}
|
||||
|
||||
std::array<std::byte, 16> clearTexel(const ColorTarget& color, DccKeys keys) {
|
||||
std::array<std::byte, 16> texel{};
|
||||
const auto elementBytes = static_cast<std::size_t>(color.elementBytes);
|
||||
|
|
@ -90,16 +96,19 @@ bool clearToTexel(StorageTexture& image, const std::array<std::byte, 16>& texel,
|
|||
return image.FillClear(std::span<const std::uint32_t, 4>(pattern), StorageTexture::WholeImage, refusal);
|
||||
}
|
||||
|
||||
void writeTexels(const ColorTarget& color, const std::array<std::byte, 16>& texel) {
|
||||
const auto elementBytes = static_cast<std::size_t>(color.elementBytes);
|
||||
std::vector<std::byte> texels(color.bytes);
|
||||
for (std::size_t offset = 0; offset + elementBytes <= texels.size(); offset += elementBytes) std::memcpy(texels.data() + offset, texel.data(), elementBytes);
|
||||
GuestMemory::Write(color.address, texels);
|
||||
}
|
||||
|
||||
void storeClearTexels(const Context& context, const ColorTarget& color, const std::array<std::byte, 16>& texel) {
|
||||
StorageTexture::FlushPending(color.address, color.bytes, nullptr, "fast-clear materialization");
|
||||
const auto keys = ReadDccKeys(color.dccAddress, color.bytes);
|
||||
if (!IsDccClear(keys)) return;
|
||||
const auto current = keys == DccKeys::ClearRegister ? texel : clearTexel(color, keys);
|
||||
const auto elementBytes = static_cast<std::size_t>(color.elementBytes);
|
||||
std::vector<std::byte> texels(color.bytes);
|
||||
for (std::size_t offset = 0; offset + elementBytes <= texels.size(); offset += elementBytes) std::memcpy(texels.data() + offset, current.data(), elementBytes);
|
||||
GuestMemory::Write(color.address, texels);
|
||||
MarkDccUncompressed(context, color.dccAddress, color.bytes);
|
||||
writeTexels(color, keys == DccKeys::ClearRegister ? texel : clearTexel(color, keys));
|
||||
MarkDccUncompressed(context, color.dccAddress, color.bytes, colorKeyCount(color, color.bytes));
|
||||
}
|
||||
|
||||
void materializeRegisterClear(const Context& context, const ColorTarget& color, StorageTexture& resident) {
|
||||
|
|
@ -118,13 +127,31 @@ void materializeRegisterClear(const Context& context, const ColorTarget& color,
|
|||
}
|
||||
}
|
||||
if (cleared) {
|
||||
MarkDccUncompressed(context, color.dccAddress, color.bytes);
|
||||
MarkDccUncompressed(context, color.dccAddress, color.bytes, colorKeyCount(color, color.bytes));
|
||||
return;
|
||||
}
|
||||
storeClearTexels(context, color, texel);
|
||||
resident.Refresh();
|
||||
}
|
||||
|
||||
bool materializeCmaskClear(const Context& context, const ColorTarget& color, StorageTexture* resident) {
|
||||
if (color.cmaskAddress == 0) return false;
|
||||
const auto metadataBytes = static_cast<std::uint64_t>(color.cmaskBytes) * 256u;
|
||||
const auto state = CurrentDccKeys(color.cmaskAddress, metadataBytes);
|
||||
if (state == DccKeys::Uncompressed) return false;
|
||||
Require(color.dccAddress == 0, std::string("CMASK of a DCC color target that is not all expanded is not modeled (") + DccKeysName(state) + ")");
|
||||
Require(state == DccKeys::Clear0000, std::string("CMASK whose tiles are not all fast-cleared or all expanded is not modeled (") + DccKeysName(state) + ")");
|
||||
const auto texel = clearTexel(color, DccKeys::ClearRegister);
|
||||
const char* refusal = nullptr;
|
||||
if (resident == nullptr || !clearToTexel(*resident, texel, color.elementBytes, refusal)) {
|
||||
StorageTexture::FlushPending(color.address, color.bytes, nullptr, "fast-clear materialization");
|
||||
writeTexels(color, texel);
|
||||
if (resident != nullptr) resident->Refresh();
|
||||
}
|
||||
MarkDccUncompressed(context, color.cmaskAddress, metadataBytes);
|
||||
return true;
|
||||
}
|
||||
|
||||
void imageBarrier(const Context& context, VkCommandBuffer commands, VkImage image, VkImageLayout oldLayout, VkImageLayout newLayout, VkPipelineStageFlags sourceStage, VkPipelineStageFlags destinationStage, VkAccessFlags sourceAccess, VkAccessFlags destinationAccess) {
|
||||
VkImageMemoryBarrier barrier{VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER};
|
||||
barrier.srcAccessMask = sourceAccess;
|
||||
|
|
@ -438,12 +465,12 @@ bool ValidationKey(const Context& context, std::span<const CompiledShader> shade
|
|||
Require(shader.program != nullptr, "missing compiled shader");
|
||||
const auto& program = *shader.program;
|
||||
const bool generated = state.rectList && (shader.stage == Stage::TessellationControl || shader.stage == Stage::TessellationEvaluation);
|
||||
if (!generated && program.variantId == 0) {
|
||||
if (!generated && program.PipelineVariantId() == 0) {
|
||||
keyed = false;
|
||||
break;
|
||||
}
|
||||
add(shader.stage);
|
||||
add(generated ? std::uint64_t{0} : program.variantId);
|
||||
add(generated ? std::uint64_t{0} : program.PipelineVariantId());
|
||||
add(shader.pushConstantOffset);
|
||||
add(program.pushConstants.size());
|
||||
add(program.bdaAbiVersion);
|
||||
|
|
@ -452,6 +479,7 @@ bool ValidationKey(const Context& context, std::span<const CompiledShader> shade
|
|||
add(attribute.location);
|
||||
add(attribute.components);
|
||||
add(attribute.fetchIndex);
|
||||
add(attribute.formatComponents);
|
||||
// The data format bits of the V# decide the attribute's signature.
|
||||
add((attribute.resource.fields[3] >> 12u) & 0x7fu);
|
||||
}
|
||||
|
|
@ -601,11 +629,12 @@ void CheckBufferAliases(std::span<const CompiledShader> shaders, const ColorTarg
|
|||
|
||||
}
|
||||
|
||||
std::array<std::uint32_t, 4> MeshIndexBufferDescriptor(const Pm4::DrawParameters& draw, std::uint64_t unreadAddress) {
|
||||
const auto address = draw.indexed ? draw.indexAddress : unreadAddress;
|
||||
const auto bytes = draw.indexed ? (static_cast<std::uint64_t>(draw.indexCount) * draw.indexSize + 3u) & ~std::uint64_t{3} : 4u;
|
||||
Require(address != 0 && bytes != 0 && bytes <= 0xffffffffu && (address >> 48u) == 0, "invalid mesh index buffer range");
|
||||
std::array<std::uint32_t, 4> MeshIndexBufferDescriptor(const Pm4::DrawParameters& draw) {
|
||||
constexpr std::uint32_t RawWord3 = 0x31016facu;
|
||||
if (!draw.indexed) return {0u, 0u, 0u, RawWord3};
|
||||
const auto address = draw.indexAddress;
|
||||
const auto bytes = (static_cast<std::uint64_t>(draw.indexCount) * draw.indexSize + 3u) & ~std::uint64_t{3};
|
||||
Require(address != 0 && bytes != 0 && bytes <= 0xffffffffu && (address >> 48u) == 0, "invalid mesh index buffer range");
|
||||
return {static_cast<std::uint32_t>(address), static_cast<std::uint32_t>(address >> 32u) & 0xffffu, static_cast<std::uint32_t>(bytes), RawWord3};
|
||||
}
|
||||
|
||||
|
|
@ -895,6 +924,7 @@ std::shared_ptr<StorageTexture> refreshResidentTarget(const Context& context, co
|
|||
std::lock_guard lock(reportMutex);
|
||||
if (reported.insert(color.address).second) std::fprintf(stderr, "[gpu] color target 0x%llx stays non-resident: %s\n", static_cast<unsigned long long>(color.address), error.what());
|
||||
}
|
||||
if (resident != nullptr) materializeCmaskClear(context, color, resident.get());
|
||||
if (profile) {
|
||||
const auto lookupUs = std::chrono::duration<double, std::micro>(std::chrono::steady_clock::now() - lookupStart).count();
|
||||
++outcome.targetLookups;
|
||||
|
|
@ -934,7 +964,7 @@ ResolvedResources resolveDrawResources(const Context& context, const State& stat
|
|||
// The render target and index ranges stay out of the key (see DrawResourceKey); a hit repeats
|
||||
// the alias checks instead. Debug aid: APS5_NO_DRAW_KEY_TRIM=1 keys them as before.
|
||||
static const bool trimKey = std::getenv("APS5_NO_DRAW_KEY_TRIM") == nullptr;
|
||||
resolved.cacheable = recordable && !noDrawResourceCache && !noTextureCache && std::all_of(shaders.begin(), shaders.end(), [](const CompiledShader& shader) { return shader.program != nullptr && shader.program->variantId != 0; });
|
||||
resolved.cacheable = recordable && !noDrawResourceCache && !noTextureCache && std::all_of(shaders.begin(), shaders.end(), [](const CompiledShader& shader) { return shader.program != nullptr && shader.program->PipelineVariantId() != 0; });
|
||||
if (resolved.cacheable) {
|
||||
resolved.contentKey = DrawResourceKey(context, shaders, state.color, draw.indexAddress, indexBytes, !trimKey);
|
||||
if (auto cached = SharedResourceCache().Find(resolved.contentKey)) {
|
||||
|
|
@ -1182,6 +1212,7 @@ struct RecordedDraw {
|
|||
std::shared_ptr<Framebuffer> framebuffer;
|
||||
std::span<const VkImageView> targetViews;
|
||||
std::vector<std::shared_ptr<StorageTexture>> targets;
|
||||
std::vector<std::shared_ptr<StorageTexture>> proxies;
|
||||
const IndirectRecord* indirect = nullptr;
|
||||
std::span<const ShaderResources::MovedBuffer> moved;
|
||||
bool listed = false;
|
||||
|
|
@ -1358,6 +1389,10 @@ void recordDraw(const Context& context, const State& state, const Pm4::DrawParam
|
|||
if (continued) {
|
||||
record.pipeline->Continue(commands, state);
|
||||
} else {
|
||||
for (const auto& proxy : record.proxies) {
|
||||
proxy->RecordAttachmentProxyLoad(commands, VK_IMAGE_LAYOUT_GENERAL);
|
||||
countBarrier(2);
|
||||
}
|
||||
const VkMemoryBarrier before{VK_STRUCTURE_TYPE_MEMORY_BARRIER, nullptr, VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_INDEX_READ_BIT | VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT | VK_ACCESS_UNIFORM_READ_BIT | VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT};
|
||||
context.Resolved(&DeviceFunctions::cmdPipelineBarrier, "vkCmdPipelineBarrier")(commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_VERTEX_INPUT_BIT | inputs.shaderStages, 0, 1, &before, 0, nullptr, 0, nullptr);
|
||||
countBarrier(1);
|
||||
|
|
@ -1391,7 +1426,16 @@ void recordDraw(const Context& context, const State& state, const Pm4::DrawParam
|
|||
// The pass stays open for the next draw of these attachments; the recorder ends it (and the
|
||||
// draw class range) before anything else is recorded. A draw that wrote memory owes the next
|
||||
// one a barrier, so its pass cannot be continued.
|
||||
recorder->LeaveRenderPassOpen(passKey, drawTiming, !resources.WritesMemory());
|
||||
std::function<void(VkCommandBuffer)> storeProxies;
|
||||
if (!continued && !record.proxies.empty()) {
|
||||
storeProxies = [proxies = record.proxies](VkCommandBuffer passCommands) {
|
||||
for (const auto& proxy : proxies) {
|
||||
proxy->RecordAttachmentProxyStore(passCommands, VK_IMAGE_LAYOUT_GENERAL);
|
||||
Recorder::CountBarriers(Recorder::CommandClass::Draw, 2);
|
||||
}
|
||||
};
|
||||
}
|
||||
recorder->LeaveRenderPassOpen(passKey, drawTiming, !resources.WritesMemory(), std::move(storeProxies));
|
||||
timer.phase(PhaseRecord);
|
||||
keepRecordedDraw(*recorder, record.resources, std::move(record.pipeline), std::move(record.framebuffer), inputs, std::move(record.targets), std::move(scratch), std::move(checkRecords), record.listed, record.completion, outcome);
|
||||
timer.phase(PhaseKeep);
|
||||
|
|
@ -1480,6 +1524,7 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters&
|
|||
// Resident target: the surface's cached storage image is attached directly and marked dirty
|
||||
// afterwards, so nothing is copied in or out per draw.
|
||||
std::shared_ptr<StorageTexture> resident;
|
||||
bool proxied = false;
|
||||
// Linear pixels converted on the CPU, for targets the GPU detiler does not handle.
|
||||
std::unique_ptr<Buffer> transfer;
|
||||
// Guest bytes in host memory, and their device-local copies for the detiler.
|
||||
|
|
@ -1508,7 +1553,7 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters&
|
|||
timer.phase(PhaseSetup);
|
||||
// Debug aid: APS5_NO_RESIDENT_TARGETS=1 copies every target in and out again.
|
||||
static const bool residentTargets = std::getenv("APS5_NO_RESIDENT_TARGETS") == nullptr;
|
||||
if ((binding.gpuTiling || (color.tileMode == ColorTileMode::Standard4KB && context.detiler != nullptr)) && residentTargets) {
|
||||
if ((binding.gpuTiling || (color.tileMode != ColorTileMode::Linear && context.detiler != nullptr)) && residentTargets) {
|
||||
// The lookup refreshes the image on every draw (StorageTexture::Refresh: FlushPending,
|
||||
// CollectWrites over the target's pages, the DCC key scan of TextureClearKeys, then
|
||||
// UnchangedSince). The page walk is skipped while the worker's collect epoch lasts
|
||||
|
|
@ -1524,9 +1569,11 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters&
|
|||
}
|
||||
if (binding.resident != nullptr) {
|
||||
timer.phase(PhaseReadTarget);
|
||||
targetViews.push_back(binding.resident->AttachmentView(color.format, color.mip, color.depthSlice));
|
||||
binding.proxied = AttachmentProxyFormat(context, color.format) != VK_FORMAT_UNDEFINED;
|
||||
targetViews.push_back(binding.proxied ? binding.resident->AttachmentProxyView() : binding.resident->AttachmentView(color.format, color.mip, color.depthSlice));
|
||||
continue;
|
||||
}
|
||||
materializeCmaskClear(context, color, nullptr);
|
||||
Require(!color.mipTail, "rendering into a packed mip tail needs the resident image of its surface");
|
||||
Require(color.depth == 1, "rendering into a 3D color target needs the resident image of its surface");
|
||||
if (binding.gpuTiling) {
|
||||
|
|
@ -1683,6 +1730,13 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters&
|
|||
APS5_LOG_CHARS_OUT_DEBUG("Creating Pipeline");
|
||||
// The state is copied only when a mask must change.
|
||||
auto masked = maskedState(state, inputs.fragmentOutputs);
|
||||
if (std::any_of(targets.begin(), targets.end(), [](const TargetBinding& binding) { return binding.proxied; })) recipeOut = nullptr;
|
||||
for (std::size_t index = 0; index < targets.size(); ++index) {
|
||||
if (!targets[index].proxied) continue;
|
||||
if (!masked.has_value()) masked = state;
|
||||
masked->colors[index].format = AttachmentProxyFormat(context, state.colors[index].format);
|
||||
masked->blends.at(state.colors[index].exportIndex).colorWriteMask &= VK_COLOR_COMPONENT_R_BIT;
|
||||
}
|
||||
const State& pipelineState = masked.has_value() ? *masked : state;
|
||||
auto pipeline = CachedPipeline(context, pipelineState, inputs.vertexInput, *resources, shaders, lean ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
|
||||
// Resident targets' views are stable while their storage image lives, so the framebuffer is
|
||||
|
|
@ -1702,6 +1756,9 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters&
|
|||
record.framebuffer = framebuffer;
|
||||
record.targetViews = targetViews;
|
||||
record.targets = owners;
|
||||
for (const auto& binding : targets) {
|
||||
if (binding.proxied) record.proxies.push_back(binding.resident);
|
||||
}
|
||||
record.indirect = args != nullptr ? &indirect : nullptr;
|
||||
record.listed = listed;
|
||||
record.completion = completion;
|
||||
|
|
@ -1800,6 +1857,11 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters&
|
|||
rewritten = recordIndirectArguments(context, commands, recorder, recorded, indirect, scratch, argumentBuffer, argumentOffset, countBarrier);
|
||||
}
|
||||
for (auto& binding : targets) {
|
||||
if (binding.proxied) {
|
||||
binding.resident->RecordAttachmentProxyLoad(commands, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
|
||||
countBarrier(2);
|
||||
continue;
|
||||
}
|
||||
if (binding.resident != nullptr) {
|
||||
imageBarrier(context, commands, binding.resident->Image(), VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT);
|
||||
countBarrier();
|
||||
|
|
@ -1835,6 +1897,11 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters&
|
|||
context.Resolved(&DeviceFunctions::cmdEndRenderPass, "vkCmdEndRenderPass")(commands);
|
||||
APS5_LOG_CHARS_OUT_DEBUG("Render pass ended");
|
||||
for (auto& binding : targets) {
|
||||
if (binding.proxied) {
|
||||
binding.resident->RecordAttachmentProxyStore(commands, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
|
||||
countBarrier(2);
|
||||
continue;
|
||||
}
|
||||
if (binding.resident != nullptr) {
|
||||
imageBarrier(context, commands, binding.resident->Image(), VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_GENERAL, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT);
|
||||
countBarrier();
|
||||
|
|
@ -1930,7 +1997,8 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters&
|
|||
if (binding.gpuTiling) GuestMemory::WriteChanged(binding.color.address, binding.tiled->Bytes(), binding.original);
|
||||
else WriteColorTarget(binding.color, binding.transfer->Bytes());
|
||||
// The stored texels are the whole target now, so later reads must see them rather than a fast clear.
|
||||
MarkDccUncompressed(binding.color.dccAddress, ColorTargetLayout(binding.color.extent.width, binding.color.extent.height, binding.color.tileMode, binding.color.elementBytes).Bytes());
|
||||
const auto targetBytes = ColorTargetLayout(binding.color.extent.width, binding.color.extent.height, binding.color.tileMode, binding.color.elementBytes).Bytes();
|
||||
MarkDccUncompressed(binding.color.dccAddress, targetBytes, colorKeyCount(binding.color, targetBytes));
|
||||
}
|
||||
timer.phase(PhaseWriteBack);
|
||||
if (profile && traceDraws) {
|
||||
|
|
@ -2067,28 +2135,39 @@ DrawRecipeOutcome DrawWithRecipe(const Context& context, const State& state, con
|
|||
return result;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
std::shared_ptr<StorageTexture> metadataPassResident(const Context& context, const ColorTarget& color) {
|
||||
if (color.tileMode == ColorTileMode::Linear || context.detiler == nullptr) return nullptr;
|
||||
std::shared_ptr<StorageTexture> resident;
|
||||
try {
|
||||
resident = CachedStorageSurface(context, SurfaceForTarget(color));
|
||||
} catch (const std::exception&) {
|
||||
return nullptr;
|
||||
}
|
||||
return resident != nullptr && resident->GuestBytes() == color.bytes ? resident : nullptr;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void RunColorMetadataPass(const Context& context, const ColorMetadataPass& pass) {
|
||||
for (const auto& color : pass.targets) {
|
||||
if (color.cmaskAddress != 0) {
|
||||
const auto resident = metadataPassResident(context, color);
|
||||
if (materializeCmaskClear(context, color, resident.get()) && resident != nullptr) resident->MarkDirty();
|
||||
}
|
||||
if (color.dccAddress == 0) continue;
|
||||
auto keys = CurrentDccKeys(color.dccAddress, color.bytes);
|
||||
if (keys == DccKeys::Uncompressed) continue;
|
||||
Require(IsDccClear(keys), std::string("CB metadata pass over DCC keys that are ") + DccKeysName(keys) + " (per-block metadata is not modeled)");
|
||||
const auto texel = clearTexel(color, keys);
|
||||
std::shared_ptr<StorageTexture> resident;
|
||||
if ((color.tileMode == ColorTileMode::RenderTarget || color.tileMode == ColorTileMode::Standard4KB) && context.detiler != nullptr) {
|
||||
try {
|
||||
resident = CachedStorageSurface(context, SurfaceForTarget(color));
|
||||
} catch (const std::exception&) {
|
||||
resident = nullptr;
|
||||
}
|
||||
if (resident != nullptr && resident->GuestBytes() != color.bytes) resident = nullptr;
|
||||
}
|
||||
const auto resident = metadataPassResident(context, color);
|
||||
if (resident != nullptr) {
|
||||
const char* refusal = nullptr;
|
||||
const bool current = keys == DccKeys::ClearRegister ? clearToTexel(*resident, texel, color.elementBytes, refusal) : StorageTexture::FindPending(color.address, color.bytes) == resident || resident->UploadedKeys() == keys;
|
||||
if (current) {
|
||||
resident->MarkDirty();
|
||||
MarkDccUncompressed(context, color.dccAddress, color.bytes);
|
||||
MarkDccUncompressed(context, color.dccAddress, color.bytes, colorKeyCount(color, color.bytes));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,9 +13,14 @@
|
|||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <fcntl.h>
|
||||
#include <linux/udmabuf.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <bit>
|
||||
#include <condition_variable>
|
||||
#include <iterator>
|
||||
|
|
@ -81,6 +86,8 @@ struct GuestBufferMemory::AddressSpace {
|
|||
std::vector<CopiedRange> copied;
|
||||
// The BDA table entries of `base`, in its order.
|
||||
std::vector<ShaderRecompiler::BdaAbi::Range> ranges;
|
||||
mutable std::mutex tableMutex;
|
||||
mutable std::map<std::vector<std::pair<std::uint64_t, std::uint64_t>>, std::pair<std::uint64_t, std::shared_ptr<const std::vector<ShaderRecompiler::BdaAbi::Range>>>> writeTables;
|
||||
};
|
||||
|
||||
namespace {
|
||||
|
|
@ -128,6 +135,7 @@ struct HostImports {
|
|||
std::uint64_t epoch = 1;
|
||||
VkDevice watchDevice = VK_NULL_HANDLE;
|
||||
bool unwatchImports = false;
|
||||
bool unwatchDmaBufImports = false;
|
||||
};
|
||||
|
||||
HostImports& Imports() {
|
||||
|
|
@ -170,9 +178,7 @@ void retireImport(const Context& context, HostImports& state, std::map<std::uint
|
|||
state.imports.erase(it);
|
||||
}
|
||||
|
||||
const char* createImport(const Context& context, HostImport& entry, VkResult& failure) {
|
||||
const auto bytes = entry.bytes;
|
||||
void* const host = entry.alias != nullptr ? entry.alias : reinterpret_cast<void*>(entry.base);
|
||||
const char* bindImport(const Context& context, HostImport& entry, VkExternalMemoryHandleTypeFlagBits handleType, const void* import, std::uint32_t importTypes, bool& allocated, VkResult& failure) {
|
||||
const auto failed = [&](const char* step, VkResult result) -> const char* {
|
||||
if (entry.buffer) context.Function<PFN_vkDestroyBuffer>("vkDestroyBuffer")(context.device, entry.buffer, nullptr);
|
||||
if (entry.memory) context.Function<PFN_vkFreeMemory>("vkFreeMemory")(context.device, entry.memory, nullptr);
|
||||
|
|
@ -181,25 +187,24 @@ const char* createImport(const Context& context, HostImport& entry, VkResult& fa
|
|||
failure = result;
|
||||
return step;
|
||||
};
|
||||
const VkExternalMemoryBufferCreateInfo external{VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_BUFFER_CREATE_INFO, nullptr, VK_EXTERNAL_MEMORY_HANDLE_TYPE_HOST_ALLOCATION_BIT_EXT};
|
||||
allocated = false;
|
||||
const VkExternalMemoryBufferCreateInfo external{VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_BUFFER_CREATE_INFO, nullptr, static_cast<VkExternalMemoryHandleTypeFlags>(handleType)};
|
||||
VkBufferCreateInfo info{VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, &external};
|
||||
info.size = bytes;
|
||||
info.size = entry.bytes;
|
||||
// INDIRECT_BUFFER: DISPATCH_INDIRECT group counts are read in place (VulkanDevice::DispatchIndirect).
|
||||
info.usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT;
|
||||
info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
if (const auto result = context.Function<PFN_vkCreateBuffer>("vkCreateBuffer")(context.device, &info, nullptr, &entry.buffer); result != VK_SUCCESS) return failed("vkCreateBuffer", result);
|
||||
VkMemoryHostPointerPropertiesEXT pointer{VK_STRUCTURE_TYPE_MEMORY_HOST_POINTER_PROPERTIES_EXT};
|
||||
if (const auto result = context.Function<PFN_vkGetMemoryHostPointerPropertiesEXT>("vkGetMemoryHostPointerPropertiesEXT")(context.device, VK_EXTERNAL_MEMORY_HANDLE_TYPE_HOST_ALLOCATION_BIT_EXT, host, &pointer); result != VK_SUCCESS) return failed("vkGetMemoryHostPointerPropertiesEXT", result);
|
||||
VkMemoryRequirements requirements{};
|
||||
context.Function<PFN_vkGetBufferMemoryRequirements>("vkGetBufferMemoryRequirements")(context.device, entry.buffer, &requirements);
|
||||
const auto types = requirements.memoryTypeBits & pointer.memoryTypeBits;
|
||||
const auto types = requirements.memoryTypeBits & importTypes;
|
||||
if (types == 0) return failed("memory type selection", VK_ERROR_FORMAT_NOT_SUPPORTED);
|
||||
const VkImportMemoryHostPointerInfoEXT import{VK_STRUCTURE_TYPE_IMPORT_MEMORY_HOST_POINTER_INFO_EXT, nullptr, VK_EXTERNAL_MEMORY_HANDLE_TYPE_HOST_ALLOCATION_BIT_EXT, host};
|
||||
const VkMemoryAllocateFlagsInfo flags{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_FLAGS_INFO, &import, VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT, 0};
|
||||
const VkMemoryAllocateFlagsInfo flags{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_FLAGS_INFO, import, VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT, 0};
|
||||
VkMemoryAllocateInfo allocation{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, &flags};
|
||||
allocation.allocationSize = bytes;
|
||||
allocation.allocationSize = entry.bytes;
|
||||
allocation.memoryTypeIndex = static_cast<std::uint32_t>(std::countr_zero(types));
|
||||
if (const auto result = context.Function<PFN_vkAllocateMemory>("vkAllocateMemory")(context.device, &allocation, nullptr, &entry.memory); result != VK_SUCCESS) return failed("vkAllocateMemory", result);
|
||||
allocated = true;
|
||||
if (const auto result = context.Function<PFN_vkBindBufferMemory>("vkBindBufferMemory")(context.device, entry.buffer, entry.memory, 0); result != VK_SUCCESS) return failed("vkBindBufferMemory", result);
|
||||
const VkBufferDeviceAddressInfo addressInfo{VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO, nullptr, entry.buffer};
|
||||
entry.address = context.Function<PFN_vkGetBufferDeviceAddressKHR>("vkGetBufferDeviceAddressKHR")(context.device, &addressInfo);
|
||||
|
|
@ -207,6 +212,62 @@ const char* createImport(const Context& context, HostImport& entry, VkResult& fa
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
const char* createHostPointerImport(const Context& context, HostImport& entry, VkResult& failure) {
|
||||
void* const host = entry.alias != nullptr ? entry.alias : reinterpret_cast<void*>(entry.base);
|
||||
VkMemoryHostPointerPropertiesEXT pointer{VK_STRUCTURE_TYPE_MEMORY_HOST_POINTER_PROPERTIES_EXT};
|
||||
if (const auto result = context.Function<PFN_vkGetMemoryHostPointerPropertiesEXT>("vkGetMemoryHostPointerPropertiesEXT")(context.device, VK_EXTERNAL_MEMORY_HANDLE_TYPE_HOST_ALLOCATION_BIT_EXT, host, &pointer); result != VK_SUCCESS) {
|
||||
failure = result;
|
||||
return "vkGetMemoryHostPointerPropertiesEXT";
|
||||
}
|
||||
const VkImportMemoryHostPointerInfoEXT import{VK_STRUCTURE_TYPE_IMPORT_MEMORY_HOST_POINTER_INFO_EXT, nullptr, VK_EXTERNAL_MEMORY_HANDLE_TYPE_HOST_ALLOCATION_BIT_EXT, host};
|
||||
bool allocated = false;
|
||||
return bindImport(context, entry, VK_EXTERNAL_MEMORY_HANDLE_TYPE_HOST_ALLOCATION_BIT_EXT, &import, pointer.memoryTypeBits, allocated, failure);
|
||||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
int udmabufDevice() {
|
||||
static const int device = open("/dev/udmabuf", O_RDWR | O_CLOEXEC);
|
||||
return device;
|
||||
}
|
||||
|
||||
const char* createDmaBufImport(const Context& context, HostImport& entry, int file, std::uint64_t offset, VkResult& failure) {
|
||||
udmabuf_create request{};
|
||||
request.memfd = static_cast<std::uint32_t>(file);
|
||||
request.flags = UDMABUF_FLAGS_CLOEXEC;
|
||||
request.offset = offset;
|
||||
request.size = entry.bytes;
|
||||
const int device = udmabufDevice();
|
||||
const int buffer = device < 0 ? -1 : ioctl(device, UDMABUF_CREATE, &request);
|
||||
close(file);
|
||||
if (buffer < 0) {
|
||||
failure = VK_ERROR_INVALID_EXTERNAL_HANDLE;
|
||||
return device < 0 ? "open /dev/udmabuf" : "UDMABUF_CREATE";
|
||||
}
|
||||
VkMemoryFdPropertiesKHR properties{VK_STRUCTURE_TYPE_MEMORY_FD_PROPERTIES_KHR};
|
||||
if (const auto result = context.Function<PFN_vkGetMemoryFdPropertiesKHR>("vkGetMemoryFdPropertiesKHR")(context.device, VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT, buffer, &properties); result != VK_SUCCESS) {
|
||||
close(buffer);
|
||||
failure = result;
|
||||
return "vkGetMemoryFdPropertiesKHR";
|
||||
}
|
||||
const VkImportMemoryFdInfoKHR import{VK_STRUCTURE_TYPE_IMPORT_MEMORY_FD_INFO_KHR, nullptr, VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT, buffer};
|
||||
bool allocated = false;
|
||||
const char* step = bindImport(context, entry, VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT, &import, properties.memoryTypeBits, allocated, failure);
|
||||
if (!allocated) close(buffer);
|
||||
entry.dmaBuf = step == nullptr;
|
||||
return step;
|
||||
}
|
||||
#endif
|
||||
|
||||
const char* createImport(const Context& context, HostImport& entry, VkResult& failure) {
|
||||
const char* step = createHostPointerImport(context, entry, failure);
|
||||
#ifndef _WIN32
|
||||
int file = -1;
|
||||
std::uint64_t offset = 0;
|
||||
if (step != nullptr && context.dmaBufImport && GuestArena::GuestArenaSharedBacking_nid_postfix(static_cast<std::uintptr_t>(entry.base), static_cast<std::size_t>(entry.bytes), &file, &offset)) step = createDmaBufImport(context, entry, file, offset, failure);
|
||||
#endif
|
||||
return step;
|
||||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
enum class ImportWatchRequest : std::uint8_t { Probe, Watch, Unwatch };
|
||||
|
||||
|
|
@ -226,11 +287,14 @@ void decideImportWatch(const Context& context, HostImports& state) {
|
|||
if (state.watchDevice == context.device) return;
|
||||
#ifdef _WIN32
|
||||
state.watchDevice = context.device;
|
||||
state.unwatchImports = false;
|
||||
state.unwatchImports = true;
|
||||
state.unwatchDmaBufImports = false;
|
||||
if (context.hostImportAlignment != 0 && GuestMemory::WriteWatched()) std::fprintf(stderr, "[write-watch] host imports are compared on Windows because driver writes can arrive after the import window\n");
|
||||
#else
|
||||
const auto request = importWatchRequest();
|
||||
state.watchDevice = context.device;
|
||||
state.unwatchImports = false;
|
||||
state.unwatchDmaBufImports = false;
|
||||
if (context.hostImportAlignment == 0 || !GuestMemory::WriteWatched()) return;
|
||||
if (request == ImportWatchRequest::Watch) {
|
||||
std::fprintf(stderr, "[write-watch] host imports stay watched (APS5_WRITE_WATCH_IMPORTS=watch)\n");
|
||||
|
|
@ -238,6 +302,7 @@ void decideImportWatch(const Context& context, HostImports& state) {
|
|||
}
|
||||
if (request == ImportWatchRequest::Unwatch) {
|
||||
state.unwatchImports = true;
|
||||
state.unwatchDmaBufImports = true;
|
||||
std::fprintf(stderr, "[write-watch] host imports are compared, not watched (APS5_WRITE_WATCH_IMPORTS=unwatch)\n");
|
||||
return;
|
||||
}
|
||||
|
|
@ -245,16 +310,36 @@ void decideImportWatch(const Context& context, HostImports& state) {
|
|||
if (probe.failure != nullptr) {
|
||||
state.unwatchImports = true;
|
||||
std::fprintf(stderr, "[write-watch] host imports resolve write protection: unknown (probe failed at %s, %d); imported ranges are compared\n", probe.failure, static_cast<int>(probe.result));
|
||||
} else {
|
||||
state.unwatchImports = probe.writtenAfterSubmit != 0;
|
||||
std::fprintf(stderr, "[write-watch] host imports resolve write protection: %s (%u of %u scratch pages written after a GPU read, %u after the import); imported ranges %s\n", state.unwatchImports ? "yes" : "no", probe.writtenAfterSubmit, probe.pages, probe.writtenAtImport, state.unwatchImports ? "are compared" : "stay watched");
|
||||
}
|
||||
state.unwatchDmaBufImports = state.unwatchImports;
|
||||
if (!context.dmaBufImport) return;
|
||||
const auto dmaBuf = ProbeDmaBufImportWriteProtection(context);
|
||||
if (dmaBuf.failure != nullptr) {
|
||||
state.unwatchDmaBufImports = true;
|
||||
std::fprintf(stderr, "[write-watch] dma-buf imports keep write protection: unknown (probe failed at %s, %d); imported ranges are compared\n", dmaBuf.failure, static_cast<int>(dmaBuf.result));
|
||||
return;
|
||||
}
|
||||
state.unwatchImports = probe.writtenAfterSubmit != 0;
|
||||
std::fprintf(stderr, "[write-watch] host imports resolve write protection: %s (%u of %u scratch pages written after a GPU read, %u after the import); imported ranges %s\n", state.unwatchImports ? "yes" : "no", probe.writtenAfterSubmit, probe.pages, probe.writtenAtImport, state.unwatchImports ? "are compared" : "stay watched");
|
||||
state.unwatchDmaBufImports = dmaBuf.writtenAfterSubmit != 0 || dmaBuf.writtenByCpu == 0;
|
||||
std::fprintf(stderr, "[write-watch] dma-buf imports keep write protection: %s (%u of %u scratch pages written after a GPU read, %u after the import, %u seen after a CPU store); imported ranges %s\n", state.unwatchDmaBufImports ? "no" : "yes", dmaBuf.writtenAfterSubmit, dmaBuf.pages, dmaBuf.writtenAtImport, dmaBuf.writtenByCpu, state.unwatchDmaBufImports ? "are compared" : "stay watched");
|
||||
#endif
|
||||
}
|
||||
|
||||
std::shared_ptr<const GuestAllocations::Range> leasedRangeOwner(const GuestAllocations::Lease& lease, std::uint64_t base) {
|
||||
const auto found = std::lower_bound(lease.begin(), lease.end(), base, [](const auto& range, std::uint64_t value) { return range->address < value; });
|
||||
return found != lease.end() && (*found)->address == base ? *found : nullptr;
|
||||
}
|
||||
|
||||
bool sameRange(const HostImport& entry, const GuestAllocations::Lease& lease) {
|
||||
const auto current = leasedRangeOwner(lease, entry.base);
|
||||
return current != nullptr && !entry.range.owner_before(current) && !current.owner_before(entry.range);
|
||||
}
|
||||
|
||||
const HostImport* importAllocation(const Context& context, HostImports& state, std::uint64_t base, std::uint64_t bytes, const GuestAllocations::Lease& lease) {
|
||||
if (const auto found = state.imports.find(base); found != state.imports.end()) {
|
||||
if (found->second.bytes == bytes) return &found->second;
|
||||
if (found->second.bytes == bytes && sameRange(found->second, lease)) return &found->second;
|
||||
retireImport(context, state, found, lease);
|
||||
}
|
||||
const auto alignment = context.hostImportAlignment;
|
||||
|
|
@ -281,6 +366,7 @@ const HostImport* importAllocation(const Context& context, HostImports& state, s
|
|||
return nullptr;
|
||||
}
|
||||
HostImport entry{base, bytes, VK_NULL_HANDLE, VK_NULL_HANDLE, 0};
|
||||
entry.range = leasedRangeOwner(lease, base);
|
||||
#ifdef _WIN32
|
||||
// Drivers pin imported pages, so every page must be committed and accessible.
|
||||
bool writable = true;
|
||||
|
|
@ -312,16 +398,21 @@ const HostImport* importAllocation(const Context& context, HostImports& state, s
|
|||
}
|
||||
entry.alias = GuestArena::GuestArenaMapAlias_nid_postfix(static_cast<std::uintptr_t>(base), static_cast<std::size_t>(bytes));
|
||||
}
|
||||
#else
|
||||
if (!GuestMemory::Accessible(reinterpret_cast<const void*>(base), static_cast<std::size_t>(bytes), true)) {
|
||||
state.failed.insert(base);
|
||||
return nullptr;
|
||||
}
|
||||
#endif
|
||||
decideImportWatch(context, state);
|
||||
if (state.unwatchImports) GuestMemory::Unwatch(base, bytes);
|
||||
entry.unwatched = state.unwatchImports;
|
||||
VkResult result = VK_SUCCESS;
|
||||
const char* step = nullptr;
|
||||
GuestMemory::ImportWatched(base, bytes, [&] {
|
||||
step = createImport(context, entry, result);
|
||||
return step == nullptr;
|
||||
});
|
||||
entry.unwatched = step == nullptr && (entry.dmaBuf ? state.unwatchDmaBufImports : state.unwatchImports);
|
||||
if (entry.unwatched) GuestMemory::Unwatch(base, bytes);
|
||||
if (step != nullptr) {
|
||||
#ifdef _WIN32
|
||||
GuestArena::GuestArenaUnmapAlias_nid_postfix(entry.alias);
|
||||
|
|
@ -337,6 +428,18 @@ const HostImport* importAllocation(const Context& context, HostImports& state, s
|
|||
std::fprintf(stderr, "[gpu] 0x%llx+0x%llx state 0x%lx protect 0x%lx type 0x%lx allocation 0x%llx\n", static_cast<unsigned long long>(cursor), static_cast<unsigned long long>(std::min(regionEnd, base + bytes) - cursor), info.State, info.Protect, info.Type, reinterpret_cast<unsigned long long>(info.AllocationBase));
|
||||
cursor = regionEnd;
|
||||
}
|
||||
#else
|
||||
static const bool trace = std::getenv("APS5_TRACE_HOST_IMPORT") != nullptr;
|
||||
if (std::FILE* maps = trace ? std::fopen("/proc/self/maps", "r") : nullptr) {
|
||||
char line[512];
|
||||
while (std::fgets(line, sizeof(line), maps) != nullptr) {
|
||||
unsigned long long first = 0;
|
||||
unsigned long long last = 0;
|
||||
if (std::sscanf(line, "%llx-%llx", &first, &last) != 2 || last <= base || first >= base + bytes) continue;
|
||||
std::fprintf(stderr, "[gpu] %s", line);
|
||||
}
|
||||
std::fclose(maps);
|
||||
}
|
||||
#endif
|
||||
return nullptr;
|
||||
}
|
||||
|
|
@ -389,11 +492,13 @@ void refreshImports(const Context& context, HostImports& state, const GuestAlloc
|
|||
state.refreshedGeneration = generation;
|
||||
for (auto it = state.imports.begin(); it != state.imports.end();) {
|
||||
const auto* range = leasedRangeAt(lease, it->first);
|
||||
if (range != nullptr && range->bytes == it->second.bytes) {
|
||||
if (range != nullptr && range->bytes == it->second.bytes && sameRange(it->second, lease)) {
|
||||
if (it->second.unwatched) GuestMemory::Unwatch(it->first, it->second.bytes);
|
||||
++it;
|
||||
continue;
|
||||
}
|
||||
static const bool trace = std::getenv("APS5_TRACE_HOST_IMPORT") != nullptr;
|
||||
if (trace && range != nullptr && range->bytes == it->second.bytes) std::fprintf(stderr, "[gpu] host import of 0x%llx+0x%llx retired: the range was mapped again\n", static_cast<unsigned long long>(it->first), static_cast<unsigned long long>(it->second.bytes));
|
||||
const auto next = std::next(it);
|
||||
retireImport(context, state, it, lease);
|
||||
it = next;
|
||||
|
|
@ -1013,35 +1118,31 @@ void ClearImageMirrors(VkDevice device) {
|
|||
state.heapBytes = 0;
|
||||
state.device = VK_NULL_HANDLE;
|
||||
}
|
||||
Spaces().current.store(nullptr);
|
||||
}
|
||||
|
||||
ImportProbe ProbeImportWriteProtection(const Context& context) {
|
||||
ImportProbe probe;
|
||||
void ClearHostImports(VkDevice device) {
|
||||
auto& state = Imports();
|
||||
std::lock_guard lock(state.mutex);
|
||||
if (state.device != device) return;
|
||||
for (const auto& [address, entry] : state.imports) {
|
||||
state.destroyBuffer(state.device, entry.buffer, nullptr);
|
||||
state.freeMemory(state.device, entry.memory, nullptr);
|
||||
#ifdef _WIN32
|
||||
static_cast<void>(context);
|
||||
probe.failure = "the Linux write watch";
|
||||
return probe;
|
||||
#else
|
||||
if (context.hostImportAlignment == 0) {
|
||||
probe.failure = "host import support";
|
||||
return probe;
|
||||
GuestArena::GuestArenaUnmapAlias_nid_postfix(entry.alias);
|
||||
#endif
|
||||
}
|
||||
if (!GuestWriteWatch::GuestWriteWatchAvailable_nid_postfix()) {
|
||||
probe.failure = "the write watch";
|
||||
return probe;
|
||||
}
|
||||
constexpr std::uint64_t page = 4096;
|
||||
const std::uint64_t alignment = std::max<std::uint64_t>(context.hostImportAlignment, page);
|
||||
const std::uint64_t bytes = (4 * page + alignment - 1) / alignment * alignment;
|
||||
probe.pages = static_cast<std::uint32_t>(bytes / page);
|
||||
void* raw = mmap(nullptr, bytes + alignment, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||
if (raw == MAP_FAILED) {
|
||||
probe.failure = "mmap";
|
||||
return probe;
|
||||
}
|
||||
const auto base = (reinterpret_cast<std::uint64_t>(raw) + alignment - 1) / alignment * alignment;
|
||||
auto* scratch = reinterpret_cast<volatile std::uint8_t*>(base);
|
||||
for (std::uint64_t offset = 0; offset < bytes; offset += page) scratch[offset] = 1;
|
||||
state.imports.clear();
|
||||
state.failed.clear();
|
||||
state.device = VK_NULL_HANDLE;
|
||||
state.refreshedGeneration = 0;
|
||||
++state.epoch;
|
||||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
namespace {
|
||||
|
||||
void runImportProbe(const Context& context, std::uint64_t base, std::uint64_t bytes, const std::function<const char*(HostImport&, VkResult&)>& importStep, ImportProbe& probe) {
|
||||
GuestWriteWatch::GuestWriteWatchRegister_nid_postfix(reinterpret_cast<const void*>(base), bytes);
|
||||
HostImport import{base, bytes, VK_NULL_HANDLE, VK_NULL_HANDLE, 0};
|
||||
VkBuffer destination = VK_NULL_HANDLE;
|
||||
|
|
@ -1058,7 +1159,7 @@ ImportProbe ProbeImportWriteProtection(const Context& context) {
|
|||
if (!collect(quiet)) return "the first collect";
|
||||
if (!collect(quiet)) return "the second collect";
|
||||
if (quiet != 0) return "an unwritten scratch range";
|
||||
if (const char* step = createImport(context, import, probe.result)) return step;
|
||||
if (const char* step = importStep(import, probe.result)) return step;
|
||||
if (!collect(probe.writtenAtImport)) return "the collect after the import";
|
||||
VkBufferCreateInfo info{VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO};
|
||||
info.size = bytes;
|
||||
|
|
@ -1094,6 +1195,8 @@ ImportProbe ProbeImportWriteProtection(const Context& context) {
|
|||
if ((probe.result = context.Function<PFN_vkWaitForFences>("vkWaitForFences")(context.device, 1, &fence, VK_TRUE, 10'000'000'000ull)) != VK_SUCCESS) return "vkWaitForFences";
|
||||
submitted = false;
|
||||
if (!collect(probe.writtenAfterSubmit)) return "the collect after the submission";
|
||||
*reinterpret_cast<volatile std::uint8_t*>(base) = 2;
|
||||
if (!collect(probe.writtenByCpu)) return "the collect after a CPU store";
|
||||
return nullptr;
|
||||
};
|
||||
probe.failure = run();
|
||||
|
|
@ -1104,11 +1207,92 @@ ImportProbe ProbeImportWriteProtection(const Context& context) {
|
|||
if (destinationMemory != VK_NULL_HANDLE) context.Function<PFN_vkFreeMemory>("vkFreeMemory")(context.device, destinationMemory, nullptr);
|
||||
if (import.buffer != VK_NULL_HANDLE) destroyImport(context, import);
|
||||
GuestWriteWatch::GuestWriteWatchUnregister_nid_postfix(reinterpret_cast<const void*>(base), bytes);
|
||||
}
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
ImportProbe ProbeImportWriteProtection(const Context& context) {
|
||||
ImportProbe probe;
|
||||
#ifdef _WIN32
|
||||
static_cast<void>(context);
|
||||
probe.failure = "the Linux write watch";
|
||||
return probe;
|
||||
#else
|
||||
if (context.hostImportAlignment == 0) {
|
||||
probe.failure = "host import support";
|
||||
return probe;
|
||||
}
|
||||
if (!GuestWriteWatch::GuestWriteWatchAvailable_nid_postfix()) {
|
||||
probe.failure = "the write watch";
|
||||
return probe;
|
||||
}
|
||||
constexpr std::uint64_t page = 4096;
|
||||
const std::uint64_t alignment = std::max<std::uint64_t>(context.hostImportAlignment, page);
|
||||
const std::uint64_t bytes = (4 * page + alignment - 1) / alignment * alignment;
|
||||
probe.pages = static_cast<std::uint32_t>(bytes / page);
|
||||
void* raw = mmap(nullptr, bytes + alignment, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||
if (raw == MAP_FAILED) {
|
||||
probe.failure = "mmap";
|
||||
return probe;
|
||||
}
|
||||
const auto base = (reinterpret_cast<std::uint64_t>(raw) + alignment - 1) / alignment * alignment;
|
||||
auto* scratch = reinterpret_cast<volatile std::uint8_t*>(base);
|
||||
for (std::uint64_t offset = 0; offset < bytes; offset += page) scratch[offset] = 1;
|
||||
runImportProbe(context, base, bytes, [&](HostImport& import, VkResult& result) { return createImport(context, import, result); }, probe);
|
||||
munmap(raw, bytes + alignment);
|
||||
return probe;
|
||||
#endif
|
||||
}
|
||||
|
||||
ImportProbe ProbeDmaBufImportWriteProtection(const Context& context) {
|
||||
ImportProbe probe;
|
||||
#ifdef _WIN32
|
||||
static_cast<void>(context);
|
||||
probe.failure = "the Linux write watch";
|
||||
return probe;
|
||||
#else
|
||||
if (!context.dmaBufImport) {
|
||||
probe.failure = "dma-buf import support";
|
||||
return probe;
|
||||
}
|
||||
if (!GuestWriteWatch::GuestWriteWatchAvailable_nid_postfix()) {
|
||||
probe.failure = "the write watch";
|
||||
return probe;
|
||||
}
|
||||
constexpr std::uint64_t page = 4096;
|
||||
const std::uint64_t alignment = std::max<std::uint64_t>(context.hostImportAlignment, page);
|
||||
const std::uint64_t bytes = (4 * page + alignment - 1) / alignment * alignment;
|
||||
probe.pages = static_cast<std::uint32_t>(bytes / page);
|
||||
const int file = memfd_create("aps5-dma-buf-import-probe", MFD_CLOEXEC | MFD_ALLOW_SEALING);
|
||||
if (file < 0) {
|
||||
probe.failure = "memfd_create";
|
||||
return probe;
|
||||
}
|
||||
void* mapped = MAP_FAILED;
|
||||
if (ftruncate(file, static_cast<off_t>(bytes)) == 0 && fcntl(file, F_ADD_SEALS, F_SEAL_SHRINK) == 0) mapped = mmap(nullptr, bytes, PROT_READ | PROT_WRITE, MAP_SHARED, file, 0);
|
||||
if (mapped == MAP_FAILED) {
|
||||
close(file);
|
||||
probe.failure = "the shared scratch mapping";
|
||||
return probe;
|
||||
}
|
||||
const auto base = reinterpret_cast<std::uint64_t>(mapped);
|
||||
auto* scratch = reinterpret_cast<volatile std::uint8_t*>(base);
|
||||
for (std::uint64_t offset = 0; offset < bytes; offset += page) scratch[offset] = 1;
|
||||
runImportProbe(context, base, bytes, [&](HostImport& import, VkResult& result) -> const char* {
|
||||
const int copy = dup(file);
|
||||
if (copy < 0) {
|
||||
result = VK_ERROR_INITIALIZATION_FAILED;
|
||||
return "dup";
|
||||
}
|
||||
return createDmaBufImport(context, import, copy, 0, result);
|
||||
}, probe);
|
||||
munmap(mapped, bytes);
|
||||
close(file);
|
||||
return probe;
|
||||
#endif
|
||||
}
|
||||
|
||||
ImportWatch PrepareImportWatch(const Context& context) {
|
||||
auto& state = Imports();
|
||||
std::lock_guard lock(state.mutex);
|
||||
|
|
@ -1121,6 +1305,7 @@ void SetImportWatch(const Context& context, ImportWatch watch) {
|
|||
std::lock_guard lock(state.mutex);
|
||||
state.watchDevice = context.device;
|
||||
state.unwatchImports = watch == ImportWatch::Unwatch;
|
||||
state.unwatchDmaBufImports = state.unwatchImports;
|
||||
}
|
||||
|
||||
const HostImport* HostImportFor(const Context& context, std::uint64_t address, std::size_t bytes) {
|
||||
|
|
@ -1446,7 +1631,9 @@ const GuestBufferMemory::Region* GuestBufferMemory::owner(std::uint64_t address)
|
|||
|
||||
void GuestBufferMemory::addDescriptorRegion(std::uint64_t address, std::size_t bytes, bool atomic) {
|
||||
validate(address, bytes);
|
||||
switch (baseOverlap(address, address + bytes, nullptr)) {
|
||||
const auto begin = address & ~std::uint64_t{3};
|
||||
const auto end = begin == address ? address + bytes : (address + bytes + 3) & ~std::uint64_t{3};
|
||||
switch (baseOverlap(begin, end, nullptr)) {
|
||||
case BaseOverlap::Inside:
|
||||
return;
|
||||
case BaseOverlap::Partial:
|
||||
|
|
@ -1458,9 +1645,9 @@ void GuestBufferMemory::addDescriptorRegion(std::uint64_t address, std::size_t b
|
|||
}
|
||||
// `writable` here means the bytes come from live guest memory (not a snapshot), whether or not
|
||||
// the shader stores to them; what is written back is decided by Writes() alone.
|
||||
Region region{address, address + bytes, true, {}, nullptr};
|
||||
Region region{begin, end, true, {}, nullptr};
|
||||
region.atomic = atomic;
|
||||
auto committed = GuestMemory::DescribeCommitted(address, bytes);
|
||||
auto committed = GuestMemory::DescribeCommitted(begin, static_cast<std::size_t>(end - begin));
|
||||
if (!committed.whole) {
|
||||
// A GPU heap bound whole while the guest commits its pages on demand, or a descriptor left
|
||||
region.sparse = true;
|
||||
|
|
@ -2302,11 +2489,18 @@ VkDescriptorBufferInfo GuestBufferMemory::Descriptor(std::uint64_t address, std:
|
|||
const auto base = region.direct != nullptr ? region.direct->base : region.mirror != nullptr ? region.mirror->base : region.begin;
|
||||
const auto offset = address - base;
|
||||
Require(context.limits.minStorageBufferOffsetAlignment != 0, "no storage buffer offset alignment");
|
||||
adjustment = static_cast<std::uint32_t>(offset % context.limits.minStorageBufferOffsetAlignment);
|
||||
Require(adjustment % 4 == 0, "guest buffer view off the storage buffer offset alignment is not DWORD aligned");
|
||||
Require(bytes + adjustment <= context.limits.maxStorageBufferRange, "guest buffer view exceeds descriptor range limit");
|
||||
Require(base % 4 == 0, "a guest buffer view in a GPU owner that does not start at a DWORD boundary is not implemented");
|
||||
adjustment = static_cast<std::uint32_t>(offset % std::max<VkDeviceSize>(context.limits.minStorageBufferOffsetAlignment, 4));
|
||||
const auto range = ViewBytes(bytes, adjustment);
|
||||
const auto end = region.direct != nullptr ? region.direct->base + region.direct->bytes : region.end;
|
||||
Require(address - adjustment + range <= end, "guest buffer view exceeds its GPU owner");
|
||||
Require(range <= context.limits.maxStorageBufferRange, "guest buffer view exceeds descriptor range limit");
|
||||
const auto handle = region.direct != nullptr ? region.direct->buffer : region.mirror != nullptr ? region.mirror->buffer->Handle() : region.buffer->Handle();
|
||||
return {handle, offset - adjustment, bytes + adjustment};
|
||||
return {handle, offset - adjustment, range};
|
||||
}
|
||||
|
||||
std::uint64_t GuestBufferMemory::ViewBytes(std::uint64_t bytes, std::uint32_t adjustment) {
|
||||
return adjustment % 4 == 0 ? bytes + adjustment : 4 * (adjustment / 4 + bytes / 4 + 1);
|
||||
}
|
||||
|
||||
ShaderRecompiler::BdaAbi::Range GuestBufferMemory::addressRange(const Region& region) {
|
||||
|
|
@ -2322,19 +2516,70 @@ std::vector<ShaderRecompiler::BdaAbi::Range> GuestBufferMemory::AddressRanges()
|
|||
std::vector<ShaderRecompiler::BdaAbi::Range> result;
|
||||
result.reserve(regions.size() + (space != nullptr ? space->ranges.size() : 0));
|
||||
for (const auto& region : regions) result.push_back(addressRange(region));
|
||||
if (space == nullptr) return result;
|
||||
// The table is searched by address (the recompiler's lookup bisects it): both lists are sorted
|
||||
// and disjoint, so a merge keeps it so.
|
||||
if (space != nullptr) {
|
||||
std::vector<ShaderRecompiler::BdaAbi::Range> combined;
|
||||
combined.reserve(result.size() + space->ranges.size());
|
||||
std::merge(space->ranges.begin(), space->ranges.end(), result.begin(), result.end(), std::back_inserter(combined), [](const auto& left, const auto& right) { return left.begin < right.begin; });
|
||||
result = std::move(combined);
|
||||
}
|
||||
if (writes.empty()) return result;
|
||||
auto sortedWrites = writes;
|
||||
std::sort(sortedWrites.begin(), sortedWrites.end());
|
||||
std::vector<std::pair<std::uint64_t, std::uint64_t>> intervals;
|
||||
for (const auto& interval : sortedWrites) {
|
||||
if (!intervals.empty() && interval.first <= intervals.back().second) intervals.back().second = std::max(intervals.back().second, interval.second);
|
||||
else intervals.push_back(interval);
|
||||
}
|
||||
std::vector<ShaderRecompiler::BdaAbi::Range> merged;
|
||||
merged.reserve(result.size() + space->ranges.size());
|
||||
std::merge(space->ranges.begin(), space->ranges.end(), result.begin(), result.end(), std::back_inserter(merged), [](const auto& left, const auto& right) { return left.begin < right.begin; });
|
||||
merged.reserve(result.size() + intervals.size() * 2u);
|
||||
std::size_t firstWrite = 0;
|
||||
for (const auto& range : result) {
|
||||
if ((range.permissions & ShaderRecompiler::BdaAbi::Write) != 0u) {
|
||||
merged.push_back(range);
|
||||
continue;
|
||||
}
|
||||
while (firstWrite < intervals.size() && intervals[firstWrite].second <= range.begin) ++firstWrite;
|
||||
auto cursor = range.begin;
|
||||
const auto append = [&](std::uint64_t end, bool written) {
|
||||
if (cursor == end) return;
|
||||
auto part = range;
|
||||
part.begin = cursor;
|
||||
part.end = end;
|
||||
part.deviceAddress += cursor - range.begin;
|
||||
if (written) {
|
||||
const auto* region = owner(cursor);
|
||||
Require(region != nullptr && region->writable && (region->mirror == nullptr || region->mirror->writable), "runtime buffer write has no writable GPU owner");
|
||||
part.permissions |= ShaderRecompiler::BdaAbi::Write;
|
||||
}
|
||||
merged.push_back(part);
|
||||
cursor = end;
|
||||
};
|
||||
for (auto index = firstWrite; index < intervals.size() && intervals[index].first < range.end; ++index) {
|
||||
append(std::max(cursor, intervals[index].first), false);
|
||||
append(std::min(range.end, intervals[index].second), true);
|
||||
}
|
||||
append(range.end, false);
|
||||
}
|
||||
return merged;
|
||||
}
|
||||
|
||||
std::optional<GuestBufferMemory::CachedTable> GuestBufferMemory::CachedAddressTable() const {
|
||||
if (space == nullptr || !regions.empty()) return std::nullopt;
|
||||
Require(uploaded && !committed, "guest GPU address ranges are not available");
|
||||
return CachedTable{space->serial, &space->ranges};
|
||||
if (writes.empty()) return CachedTable{space->serial, &space->ranges};
|
||||
if (writeTableRanges != nullptr) return CachedTable{writeTableSerial, writeTableRanges.get()};
|
||||
std::lock_guard lock(space->tableMutex);
|
||||
const auto found = space->writeTables.find(writes);
|
||||
if (found != space->writeTables.end()) {
|
||||
writeTableSerial = found->second.first;
|
||||
writeTableRanges = found->second.second;
|
||||
} else {
|
||||
writeTableRanges = std::make_shared<const std::vector<ShaderRecompiler::BdaAbi::Range>>(AddressRanges());
|
||||
writeTableSerial = Spaces().serials.fetch_add(1, std::memory_order_relaxed) + 1;
|
||||
if (space->writeTables.size() >= 64u) space->writeTables.erase(space->writeTables.begin());
|
||||
space->writeTables.emplace(writes, std::pair{writeTableSerial, writeTableRanges});
|
||||
}
|
||||
return CachedTable{writeTableSerial, writeTableRanges.get()};
|
||||
}
|
||||
|
||||
bool GuestBufferMemory::HasCopiedWrites() const {
|
||||
|
|
|
|||
|
|
@ -18,8 +18,15 @@ TextureTileMode resolveTileMode(std::uint32_t raw) {
|
|||
switch (raw) {
|
||||
case 0x00: return TextureTileMode::kLinear;
|
||||
case 0x01: return TextureTileMode::kStandard256B;
|
||||
case 0x02: return TextureTileMode::kD256B;
|
||||
case 0x05: return TextureTileMode::kStandard4KB;
|
||||
case 0x06: return TextureTileMode::kD4KB;
|
||||
case 0x09: return TextureTileMode::kStandard64KB;
|
||||
case 0x0a: return TextureTileMode::kD64KB;
|
||||
case 0x11: return TextureTileMode::kS64KBT;
|
||||
case 0x12: return TextureTileMode::kD64KBT;
|
||||
case 0x15: return TextureTileMode::kS4KBX;
|
||||
case 0x16: return TextureTileMode::kD4KBX;
|
||||
case 0x18: return TextureTileMode::kZ64KBX;
|
||||
case 0x19: return TextureTileMode::kS64KBX;
|
||||
case 0x1a: return TextureTileMode::kD64KBX;
|
||||
|
|
@ -64,7 +71,7 @@ GuestTextureResource DecodeTextureResource(std::span<const std::uint32_t> words)
|
|||
const auto bcSwizzle = (words[3] >> 25u) & 0x7u;
|
||||
const auto typeRaw = (words[3] >> 28u) & 0xfu;
|
||||
|
||||
const auto depth = (words[4] >> 0u) & 0x1fffu;
|
||||
auto depth = (words[4] >> 0u) & 0x1fffu;
|
||||
const auto baseArray = (words[4] >> 16u) & 0x1fffu;
|
||||
|
||||
const auto arrayPitch = (words[5] >> 0u) & 0xfu;
|
||||
|
|
@ -108,7 +115,6 @@ GuestTextureResource DecodeTextureResource(std::span<const std::uint32_t> words)
|
|||
static_cast<void>(maxCompBlkSize);
|
||||
// Surfaces are always written uncompressed here (render targets and storage images bypass DCC), so
|
||||
// only the fast-clear keys in DCC metadata change what a read returns (see DccMetadata.hpp).
|
||||
static_cast<void>(metaPipeAligned);
|
||||
static_cast<void>(writeCompress);
|
||||
if (dccColorTransf) {
|
||||
static bool reported = false;
|
||||
|
|
@ -135,7 +141,7 @@ GuestTextureResource DecodeTextureResource(std::span<const std::uint32_t> words)
|
|||
// hardware never addresses them, so the view ends at the surface's last level.
|
||||
if (baseLevel <= maxMip) lastLevel = std::min(lastLevel, maxMip);
|
||||
// XOR swizzles fold a pipe/bank XOR into the low address bits; only unmodified 64 KiB bases are modeled.
|
||||
Require(XorSwizzleMode(tileMode) == 0 || (baseAddress & 0xffffu) == 0, "guest texture descriptor combines an XOR swizzle with a pipe/bank XOR base which is not implemented");
|
||||
Require(XorSwizzleMode(tileMode) == 0 || (baseAddress & (tileMode == TextureTileMode::kS4KBX || tileMode == TextureTileMode::kD4KBX ? 0xfffu : 0xffffu)) == 0, "guest texture descriptor combines an XOR swizzle with a pipe/bank XOR base which is not implemented");
|
||||
|
||||
switch (dimension) {
|
||||
case TextureDimension::k1D:
|
||||
|
|
@ -159,7 +165,11 @@ GuestTextureResource DecodeTextureResource(std::span<const std::uint32_t> words)
|
|||
case TextureDimension::kCube:
|
||||
Require(width == height, "guest cube texture descriptor is not square");
|
||||
Require(baseArray <= depth, "guest cube texture descriptor has a base array past its last array slice");
|
||||
Require((depth - baseArray + 1u) % 6u == 0, "guest cube texture descriptor does not contain a multiple of 6 array slices");
|
||||
if (baseArray == depth) depth += 5u;
|
||||
if ((depth - baseArray + 1u) % 6u != 0) {
|
||||
Require(false, "guest cube texture descriptor does not contain a multiple of 6 array slices" +
|
||||
describe() + " (base array " + std::to_string(baseArray) + ")");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -182,6 +192,7 @@ GuestTextureResource DecodeTextureResource(std::span<const std::uint32_t> words)
|
|||
result.dstSelW = static_cast<std::uint8_t>(dstSelW);
|
||||
result.dccAddress = metaCompress ? metaAddr << 8u : 0u;
|
||||
result.dccAlphaOnMsb = dccAlphaPos;
|
||||
result.dccPipeAligned = metaPipeAligned;
|
||||
result.minLod = minLod;
|
||||
if (baseLevel > maxMip) {
|
||||
Require(LevelsFitAllocation(result, lastLevel + 1u), "guest texture descriptor starts past the surface's last mip level at levels that would move the surface's own" + describe());
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
#include "prx/libSceAgcDriver/Execution/include/ProfileOutput.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/PerformanceTimer.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Pipeline.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/PipelineSpecialization.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/VertexInput.hpp"
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
|
|
@ -92,6 +94,7 @@ void ValidateViewport(const Context& context, const VkViewport& viewport) {
|
|||
}
|
||||
|
||||
Pipeline::Pipeline(const Context& context, const State& state, const VertexInputLayout& vertexInput, const ShaderResources& resources, std::span<const CompiledShader> shaders, VkImageLayout attachmentLayout) : context(context), _modules(shaders.size()), attachments(state.colors.size() + (state.depth ? 1u : 0u)), colorAttachments(state.colors.size()), depthBounds(state.depth.has_value() && state.depthBoundsTest), depthBias(state.depth.has_value() && state.depthBias) {
|
||||
PerformanceTimer timing("Vulkan.GraphicsPipeline");
|
||||
// A cached pipeline may outlive its device's teardown (see ClearCachedPipelines); it must not keep
|
||||
// the buffer pool, which is reset with the device, alive past it.
|
||||
this->context.bufferPool.reset();
|
||||
|
|
@ -101,6 +104,7 @@ Pipeline::Pipeline(const Context& context, const State& state, const VertexInput
|
|||
Require(!depthBounds || context.depthBounds, "device does not support the depth bounds test");
|
||||
Require(!depthBias || state.depthBiasClamp == 0.0f || context.depthBiasClamp, "device does not support depth bias clamping");
|
||||
Require(!state.negativeOneToOne || context.depthClipControl, "negative-one-to-one depth clipping requires VK_EXT_depth_clip_control with depthClipControl enabled");
|
||||
Require(state.conservativeRasterization == VK_CONSERVATIVE_RASTERIZATION_MODE_DISABLED_EXT || context.conservativeRasterization, "conservative rasterization requires VK_EXT_conservative_rasterization with at most 1/256 pixel of overestimation and degenerate triangles rasterized");
|
||||
if (state.rectList) Require(context.tessellationShader && context.limits.maxTessellationPatchSize >= 4, "rect-list requires tessellation with four output control points");
|
||||
if (state.stages.tessellation) {
|
||||
Require(context.tessellationShader, "device does not support tessellation shaders");
|
||||
|
|
@ -117,6 +121,8 @@ Pipeline::Pipeline(const Context& context, const State& state, const VertexInput
|
|||
Require(pushStages == 0 || context.limits.maxPushConstantsSize >= PipelinePushConstantBytes, "graphics push constant range exceeds device limit");
|
||||
try {
|
||||
std::vector<VkPipelineShaderStageCreateInfo> stages(shaders.size());
|
||||
std::vector<PipelineSpecialization> specializations;
|
||||
specializations.reserve(shaders.size());
|
||||
for (std::uint32_t i = 0; i < shaders.size(); ++i) {
|
||||
const auto& shader = *shaders[i].program;
|
||||
VkShaderModuleCreateInfo module{VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO};
|
||||
|
|
@ -128,6 +134,8 @@ Pipeline::Pipeline(const Context& context, const State& state, const VertexInput
|
|||
stages[i].stage = stage;
|
||||
stages[i].module = _modules[i];
|
||||
stages[i].pName = "main";
|
||||
specializations.emplace_back(shader);
|
||||
stages[i].pSpecializationInfo = specializations.back().Info();
|
||||
}
|
||||
// A descriptor set layout with the same bindings as this one is compatible with the pipeline
|
||||
// layout, so later draws bind their own ShaderResources' set under it.
|
||||
|
|
@ -208,6 +216,9 @@ Pipeline::Pipeline(const Context& context, const State& state, const VertexInput
|
|||
raster.frontFace = state.frontFace;
|
||||
raster.depthBiasEnable = depthBias;
|
||||
raster.lineWidth = 1;
|
||||
VkPipelineRasterizationConservativeStateCreateInfoEXT conservative{VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_CONSERVATIVE_STATE_CREATE_INFO_EXT};
|
||||
conservative.conservativeRasterizationMode = state.conservativeRasterization;
|
||||
if (state.conservativeRasterization != VK_CONSERVATIVE_RASTERIZATION_MODE_DISABLED_EXT) raster.pNext = &conservative;
|
||||
VkPipelineMultisampleStateCreateInfo samples{VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO};
|
||||
samples.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
|
||||
VkPipelineDepthStencilStateCreateInfo depthStencil{VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO};
|
||||
|
|
@ -241,7 +252,9 @@ Pipeline::Pipeline(const Context& context, const State& state, const VertexInput
|
|||
pipelineInfo.pDynamicState = &dynamic;
|
||||
pipelineInfo.layout = layout;
|
||||
pipelineInfo.renderPass = renderPass;
|
||||
timing.Mark("modules_and_state");
|
||||
Check(context.Function<PFN_vkCreateGraphicsPipelines>("vkCreateGraphicsPipelines")(context.device, context.pipelineCache, 1, &pipelineInfo, nullptr, &pipeline), "vkCreateGraphicsPipelines");
|
||||
timing.Mark("create");
|
||||
LogPipelineStatistics_nid_no_patch(context, pipeline);
|
||||
} catch (...) {
|
||||
release();
|
||||
|
|
@ -367,9 +380,9 @@ std::vector<std::byte> pipelineKey(const Context& context, const State& state, c
|
|||
for (const auto& shader : shaders) {
|
||||
Require(shader.program != nullptr, "missing compiled shader");
|
||||
const bool generated = state.rectList && (shader.stage == Stage::TessellationControl || shader.stage == Stage::TessellationEvaluation);
|
||||
if (!generated && shader.program->variantId == 0) return {};
|
||||
if (!generated && shader.program->PipelineVariantId() == 0) return {};
|
||||
append(key, shader.stage);
|
||||
append(key, generated ? std::uint64_t{0} : shader.program->variantId);
|
||||
append(key, generated ? std::uint64_t{0} : shader.program->PipelineVariantId());
|
||||
// Where the stage's push constants sit in the block (AssemblePushConstants).
|
||||
append(key, shader.pushConstantOffset);
|
||||
}
|
||||
|
|
@ -397,6 +410,7 @@ std::vector<std::byte> pipelineKey(const Context& context, const State& state, c
|
|||
append(key, state.frontFace);
|
||||
append(key, state.negativeOneToOne);
|
||||
append(key, state.depthClamp && context.depthClamp);
|
||||
append(key, state.conservativeRasterization);
|
||||
append(key, state.blends.size());
|
||||
for (const auto& blend : state.blends) append(key, blend);
|
||||
for (const auto value : state.blendConstants) append(key, value);
|
||||
|
|
|
|||
|
|
@ -328,11 +328,15 @@ struct DeferredBatch {
|
|||
};
|
||||
struct DeferredBatchesTag {};
|
||||
auto& DeferredBatches() { return HostThreadLocal<std::vector<DeferredBatch>, DeferredBatchesTag>(); }
|
||||
struct DeferredBytesTag {};
|
||||
auto& DeferredBytes() { return HostThreadLocal<std::size_t, DeferredBytesTag>(); }
|
||||
std::vector<DeferredBatch> TakeDeferred() {
|
||||
auto taken = std::move(DeferredBatches());
|
||||
DeferredBatches().clear();
|
||||
DeferredBytes() = 0;
|
||||
return taken;
|
||||
}
|
||||
std::atomic<std::uint64_t> deferredPending{0};
|
||||
// APS5_PROFILE_DRAW, for the [recorder] line: batches and objects released after an unlock and the
|
||||
// time that took, on the release thread and inline (on the unlocking thread: the kill switch, a
|
||||
// full queue, a stopping thread or a failed hand-off), the batches that went inline because the
|
||||
// queue was full, and the longest queue seen (in batches).
|
||||
std::atomic<std::uint64_t> threadReleases{0}, threadObjects{0}, threadReleaseUs{0};
|
||||
std::atomic<std::uint64_t> inlineReleases{0}, inlineObjects{0}, inlineReleaseUs{0}, inlineOverBound{0};
|
||||
std::atomic<std::uint64_t> releaseQueueMax{0};
|
||||
|
|
@ -446,8 +450,7 @@ void ReleaseDeferredKeeps() {
|
|||
if (DeferredBatches().empty()) return;
|
||||
// Taken off the thread's list first: a destructor that took and released the mutex (none is
|
||||
// known to) would re-enter here and must find nothing.
|
||||
auto releasing = std::move(DeferredBatches());
|
||||
DeferredBatches().clear();
|
||||
auto releasing = TakeDeferred();
|
||||
if (ReleaseOnUnlock()) {
|
||||
DestroyDeferred(std::move(releasing), false);
|
||||
return;
|
||||
|
|
@ -1134,11 +1137,7 @@ Recorder::~Recorder() {
|
|||
// other threads (an unlock hook that found the queue stopping or full) are waited for before
|
||||
// the caller destroys the device. The count is global: a newer recorder's batches (device
|
||||
// replacement) are waited for too, which only prolongs the spin.
|
||||
if (!DeferredBatches().empty()) {
|
||||
auto own = std::move(DeferredBatches());
|
||||
DeferredBatches().clear();
|
||||
DestroyDeferred(std::move(own), false);
|
||||
}
|
||||
if (!DeferredBatches().empty()) DestroyDeferred(TakeDeferred(), false);
|
||||
JoinReleaseThread();
|
||||
while (deferredPending.load(std::memory_order_acquire) != 0) std::this_thread::yield();
|
||||
{
|
||||
|
|
@ -1370,10 +1369,13 @@ VkCommandBuffer Recorder::CommandsInRenderPass() {
|
|||
return open->commands;
|
||||
}
|
||||
|
||||
void Recorder::LeaveRenderPassOpen(std::uint64_t key, std::uint32_t timing, bool continuable) {
|
||||
void Recorder::LeaveRenderPassOpen(std::uint64_t key, std::uint32_t timing, bool continuable, std::function<void(VkCommandBuffer)> afterPass) {
|
||||
Require(open != nullptr, "no batch is open for the render pass");
|
||||
auto& pass = open->renderPass;
|
||||
if (!pass.open) pass.timing = timing;
|
||||
if (!pass.open) {
|
||||
pass.timing = timing;
|
||||
pass.afterPass = std::move(afterPass);
|
||||
}
|
||||
pass.open = true;
|
||||
pass.key = key;
|
||||
pass.continuable = continuable;
|
||||
|
|
@ -1386,6 +1388,7 @@ void Recorder::endOpenRenderPass() {
|
|||
// host sees them at the batch's fence).
|
||||
recordBarrier(open->commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT);
|
||||
CountBarriers(CommandClass::Draw);
|
||||
if (pass.afterPass) pass.afterPass(open->commands);
|
||||
EndGpuTiming(pass.timing);
|
||||
open->coveredAccess = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
open->hostReadOwed = true;
|
||||
|
|
@ -2186,9 +2189,21 @@ std::size_t Recorder::UnsignaledBatches() const {
|
|||
return static_cast<std::size_t>(std::count_if(inFlight.begin(), inFlight.end(), [&](const auto& batch) { return !signaled(*batch); }));
|
||||
}
|
||||
|
||||
void Recorder::Keep(std::shared_ptr<void> object) {
|
||||
void Recorder::Keep(std::shared_ptr<void> object, std::size_t bytes) {
|
||||
ensureOpen();
|
||||
open->kept.push_back(std::move(object));
|
||||
open->keptBytes += bytes;
|
||||
}
|
||||
|
||||
void Recorder::BoundKeptBytes() {
|
||||
if (!GuestMemory::GpuMutex().HeldByThisThread()) return;
|
||||
if (open != nullptr && open->keptBytes >= KeptBytesBudget) Submit();
|
||||
if (DeferredBytes() != 0 && inFlightKeptBytes + DeferredBytes() > 2 * KeptBytesBudget) DestroyDeferred(TakeDeferred(), false);
|
||||
while (!inFlight.empty() && inFlightKeptBytes > 2 * KeptBytesBudget) {
|
||||
auto batch = std::move(inFlight.front());
|
||||
inFlight.pop_front();
|
||||
finish(std::move(batch), true, 4, true);
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
|
@ -2247,13 +2262,14 @@ void Recorder::OnComplete(std::function<void()> action) {
|
|||
writeBackCompletions.fetch_add(1, std::memory_order_acq_rel);
|
||||
}
|
||||
|
||||
bool Recorder::noteWrite(std::uint64_t address, std::size_t bytes, bool ownLabel) {
|
||||
bool Recorder::noteWrite(std::uint64_t address, std::size_t bytes, bool ownLabel, int value) {
|
||||
CaptureTrace::Log("buffer-write batch=%llu address=%llx bytes=%zu label=%d", static_cast<unsigned long long>(submissions + 1), static_cast<unsigned long long>(address), bytes, ownLabel);
|
||||
if (bytes == 0) return false;
|
||||
ensureOpen();
|
||||
const auto end = address + bytes;
|
||||
open->writes.emplace_back(address, end);
|
||||
open->writeNotes.push_back(++writeNoteCount);
|
||||
open->writeValues.push_back(static_cast<std::int16_t>(value));
|
||||
if (!ownLabel) markOverwritten(address, end);
|
||||
// A poller waiting on this range learns that the open batch may now hold its producer.
|
||||
if (activeRecorder == this) writeGeneration.fetch_add(1, std::memory_order_release);
|
||||
|
|
@ -2279,6 +2295,7 @@ void Recorder::noteWriteOn(Batch& batch, std::uint64_t address, std::size_t byte
|
|||
// moves as well, so a poller re-consults the label table for a completion label.
|
||||
batch.writes.emplace_back(address, address + bytes);
|
||||
batch.writeNotes.push_back(++writeNoteCount);
|
||||
batch.writeValues.push_back(-1);
|
||||
if (!ownLabel) markOverwritten(address, address + bytes);
|
||||
if (activeRecorder == this) writeGeneration.fetch_add(1, std::memory_order_release);
|
||||
if (!SnapshotCovers(address, address + bytes)) publishPendingWrites();
|
||||
|
|
@ -2300,6 +2317,12 @@ void Recorder::NotePendingWrite(std::uint64_t address, std::size_t bytes) {
|
|||
std::atomic_thread_fence(std::memory_order_seq_cst);
|
||||
}
|
||||
|
||||
void Recorder::NotePendingFill(std::uint64_t address, std::size_t bytes, std::uint8_t value) {
|
||||
if (!noteWrite(address, bytes, false, value)) return;
|
||||
publishPendingWrites();
|
||||
std::atomic_thread_fence(std::memory_order_seq_cst);
|
||||
}
|
||||
|
||||
void Recorder::NotePendingWrites(std::span<const std::pair<std::uint64_t, std::uint64_t>> ranges) {
|
||||
bool publish = false;
|
||||
for (const auto& [begin, end] : ranges) {
|
||||
|
|
@ -2387,6 +2410,20 @@ std::uint64_t Recorder::NewestWriteNote(std::uint64_t address, std::size_t bytes
|
|||
return newest;
|
||||
}
|
||||
|
||||
std::vector<Recorder::PendingWrite> Recorder::PendingWritesOver(std::uint64_t address, std::size_t bytes) const {
|
||||
std::vector<PendingWrite> found;
|
||||
if (bytes == 0) return found;
|
||||
const auto end = address + bytes;
|
||||
const auto scan = [&](const Batch& batch) {
|
||||
for (std::size_t i = 0; i < batch.writes.size(); ++i) {
|
||||
if (address < batch.writes[i].second && batch.writes[i].first < end) found.push_back({batch.writes[i].first, batch.writes[i].second, batch.writeNotes[i], batch.writeValues[i]});
|
||||
}
|
||||
};
|
||||
if (open != nullptr) scan(*open);
|
||||
for (const auto& batch : inFlight) scan(*batch);
|
||||
return found;
|
||||
}
|
||||
|
||||
bool Recorder::ReadTracking() {
|
||||
return ReadTrackingEnabled();
|
||||
}
|
||||
|
|
@ -2725,6 +2762,7 @@ bool Recorder::writtenBackSince(std::uint64_t sequence, std::uint64_t begin, std
|
|||
}
|
||||
|
||||
void Recorder::Submit() {
|
||||
PerformanceTimer frameTiming("Recorder.Submit");
|
||||
// The work count is cleared even when nothing is open: the driver counts a dispatch after its
|
||||
// call returns (outside the mutex), so a submit by another thread in between leaves a stale
|
||||
// count behind, and the callers that act on it would otherwise take the mutex for nothing at
|
||||
|
|
@ -2785,6 +2823,7 @@ void Recorder::Submit() {
|
|||
newestSubmittedAt = batch->submittedAt;
|
||||
}
|
||||
inFlight.push_back(std::move(batch));
|
||||
inFlightKeptBytes += inFlight.back()->keptBytes;
|
||||
if (activeRecorder == this) pendingLabelSince.store(NoPendingLabel, std::memory_order_release);
|
||||
}
|
||||
|
||||
|
|
@ -2833,6 +2872,7 @@ VkResult WaitTimeline(VkDevice device, VkSemaphore timeline, PFN_vkWaitSemaphore
|
|||
}
|
||||
|
||||
void Recorder::WaitSerial(std::uint64_t serial) {
|
||||
PerformanceTimer frameTiming("Recorder.WaitSerial");
|
||||
if (timeline == VK_NULL_HANDLE || serial == 0) return;
|
||||
static const bool profile = std::getenv("APS5_PROFILE_DRAW") != nullptr;
|
||||
const auto start = profile ? std::chrono::steady_clock::now() : std::chrono::steady_clock::time_point{};
|
||||
|
|
@ -2862,6 +2902,7 @@ void Recorder::FinishUpTo(std::uint64_t serial) {
|
|||
}
|
||||
|
||||
void Recorder::Sync() {
|
||||
PerformanceTimer frameTiming("Recorder.Sync");
|
||||
const auto source = std::exchange(announcedSource, 4);
|
||||
const void* site = std::exchange(announcedSite, nullptr);
|
||||
Submit();
|
||||
|
|
@ -3134,6 +3175,7 @@ bool Recorder::syncThroughUnlocked(std::uint64_t address, std::uint64_t end, int
|
|||
}
|
||||
|
||||
bool Recorder::Reap() {
|
||||
PerformanceTimer frameTiming("Recorder.Reap");
|
||||
const bool profile = DrawProfiled();
|
||||
const auto start = profile ? std::chrono::steady_clock::now() : std::chrono::steady_clock::time_point{};
|
||||
std::uint64_t retired = 0;
|
||||
|
|
@ -3153,7 +3195,8 @@ bool Recorder::Reap() {
|
|||
return inFlight.empty();
|
||||
}
|
||||
|
||||
void Recorder::finish(std::unique_ptr<Batch> batch, bool wait, int source) {
|
||||
void Recorder::finish(std::unique_ptr<Batch> batch, bool wait, int source, bool releaseNow) {
|
||||
inFlightKeptBytes -= batch->keptBytes;
|
||||
// The batch's writes stay published until its completions ran, on every exit path.
|
||||
struct Finishing {
|
||||
Recorder& recorder;
|
||||
|
|
@ -3265,7 +3308,7 @@ void Recorder::finish(std::unique_ptr<Batch> batch, bool wait, int source) {
|
|||
const auto keptStart = profile ? std::chrono::steady_clock::now() : std::chrono::steady_clock::time_point{};
|
||||
// Deferred only from inside a hold: the unlock that releases the list is this thread's own, and
|
||||
// a caller finishing batches without the mutex (a test) might never make one.
|
||||
if (ReleaseUnderLock() || !GuestMemory::GpuMutex().HeldByThisThread()) {
|
||||
if (releaseNow || ReleaseUnderLock() || !GuestMemory::GpuMutex().HeldByThisThread()) {
|
||||
batch->completions.clear();
|
||||
batch->kept.clear();
|
||||
} else {
|
||||
|
|
@ -3276,6 +3319,7 @@ void Recorder::finish(std::unique_ptr<Batch> batch, bool wait, int source) {
|
|||
try {
|
||||
DeferredBatches().push_back({std::move(batch->kept), std::move(batch->completions)});
|
||||
deferredPending.fetch_add(1, std::memory_order_acq_rel);
|
||||
DeferredBytes() += batch->keptBytes;
|
||||
} catch (...) {
|
||||
}
|
||||
batch->completions.clear();
|
||||
|
|
|
|||
|
|
@ -38,12 +38,6 @@ std::uint32_t read(const Registers& registers, std::uint32_t offset, RegisterBan
|
|||
return it->second;
|
||||
}
|
||||
|
||||
std::uint32_t readOr(const Registers& registers, std::uint32_t offset, RegisterBank bank, std::uint32_t fallback) {
|
||||
NoteRegisterRead(bank, offset);
|
||||
const auto it = registers.find(offset);
|
||||
return it == registers.end() ? fallback : it->second;
|
||||
}
|
||||
|
||||
template <typename T> T _readHeaderPod(std::span<const std::byte> header, std::uint64_t headerAddress, const void* pointer) {
|
||||
if (pointer == nullptr) throw std::runtime_error("AGC graphics: null AGC header pointer");
|
||||
const auto address = reinterpret_cast<std::uint64_t>(pointer);
|
||||
|
|
@ -68,7 +62,7 @@ template <typename T> void _readHeaderArray(std::span<const std::byte> header, s
|
|||
|
||||
}
|
||||
|
||||
ShaderRecompiler::ShaderComputeStageInfo DecodeComputeStageInfo(const Registers& shader) {
|
||||
ShaderRecompiler::ShaderComputeStageInfo DecodeComputeStageInfo(const Registers& shader, std::span<const std::byte> header) {
|
||||
const auto numThreadX = read(shader, computeNumThreadX, RegisterBank::Shader);
|
||||
const auto numThreadY = read(shader, computeNumThreadY, RegisterBank::Shader);
|
||||
const auto numThreadZ = read(shader, computeNumThreadZ, RegisterBank::Shader);
|
||||
|
|
@ -76,8 +70,13 @@ ShaderRecompiler::ShaderComputeStageInfo DecodeComputeStageInfo(const Registers&
|
|||
throw std::runtime_error("AGC graphics: COMPUTE_NUM_THREAD_X/Y/Z must be nonzero");
|
||||
}
|
||||
const auto rsrc2 = read(shader, computePgmRsrc2, RegisterBank::Shader);
|
||||
std::uint32_t scratchDwords = 0;
|
||||
if ((rsrc2 & 0x1u) != 0) {
|
||||
throw std::runtime_error("AGC graphics: COMPUTE_PGM_RSRC2.SCRATCH_EN is unsupported");
|
||||
if (header.size() < sizeof(Shader)) throw std::runtime_error("AGC graphics: COMPUTE_PGM_RSRC2.SCRATCH_EN without an AGC shader header");
|
||||
Shader agcShader;
|
||||
std::memcpy(&agcShader, header.data(), sizeof(Shader));
|
||||
scratchDwords = agcShader.scratch_size_dw_per_thread;
|
||||
if (scratchDwords == 0) throw std::runtime_error("AGC graphics: COMPUTE_PGM_RSRC2.SCRATCH_EN with a zero scratch size");
|
||||
}
|
||||
// Debug aid: APS5_LDS_SLACK=<dwords> grows every dispatch's LDS allocation by that much, to tell
|
||||
// whether a program depends on addresses past its declared allocation.
|
||||
|
|
@ -87,18 +86,20 @@ ShaderRecompiler::ShaderComputeStageInfo DecodeComputeStageInfo(const Registers&
|
|||
std::min(((rsrc2 >> 15u) & 0x1FFu) * 128u + ldsSlack, 16384u),
|
||||
{((rsrc2 >> 7u) & 0x1u) != 0, ((rsrc2 >> 8u) & 0x1u) != 0, ((rsrc2 >> 9u) & 0x1u) != 0},
|
||||
((rsrc2 >> 10u) & 0x1u) != 0,
|
||||
((rsrc2 >> 11u) & 0x3u) + 1u
|
||||
((rsrc2 >> 11u) & 0x3u) + 1u,
|
||||
{},
|
||||
scratchDwords
|
||||
};
|
||||
}
|
||||
|
||||
ShaderRecompiler::ShaderPixelStageInfo DecodePixelStageInfo(const Registers& context, const std::array<std::uint8_t, 8>& exportMappings, bool nullProgram) {
|
||||
const auto inControl = read(context, spiPsInControl, RegisterBank::Context);
|
||||
const auto inControl = nullProgram ? 0u : read(context, spiPsInControl, RegisterBank::Context);
|
||||
const auto inputNum = inControl & 0x3Fu;
|
||||
if (inputNum > 32u) {
|
||||
throw std::runtime_error("AGC graphics: SPI_PS_IN_CONTROL input count exceeds 32");
|
||||
}
|
||||
const auto ena = nullProgram ? readOr(context, spiPsInputEna, RegisterBank::Context, defaultPixelInputs) : read(context, spiPsInputEna, RegisterBank::Context);
|
||||
const auto addr = nullProgram ? readOr(context, spiPsInputAddr, RegisterBank::Context, defaultPixelInputs) : read(context, spiPsInputAddr, RegisterBank::Context);
|
||||
const auto ena = nullProgram ? defaultPixelInputs : read(context, spiPsInputEna, RegisterBank::Context);
|
||||
const auto addr = nullProgram ? defaultPixelInputs : read(context, spiPsInputAddr, RegisterBank::Context);
|
||||
const auto activeInputs = ena & addr;
|
||||
using ShaderRecompiler::PixelInput;
|
||||
using ShaderRecompiler::PixelInputBit;
|
||||
|
|
@ -115,14 +116,14 @@ ShaderRecompiler::ShaderPixelStageInfo DecodePixelStageInfo(const Registers& con
|
|||
for (std::uint32_t i = 0; i < inputNum; ++i) {
|
||||
interpolatorSettings[i] = read(context, spiPsInputCntl0 + i, RegisterBank::Context);
|
||||
}
|
||||
const auto shaderControl = read(context, dbShaderControl, RegisterBank::Context);
|
||||
const auto shaderControl = nullProgram ? 0u : read(context, dbShaderControl, RegisterBank::Context);
|
||||
// Bits 9 and 11 are EXEC_ON_HIER_FAIL and ALPHA_TO_MASK_DISABLE on GFX10; neither changes what the
|
||||
// recompiled pixel shader computes.
|
||||
const auto conservativeZExport = (shaderControl >> 13u) & 0x3u;
|
||||
if (conservativeZExport == 3u) {
|
||||
throw std::runtime_error("AGC graphics: DB_SHADER_CONTROL.CONSERVATIVE_Z_EXPORT uses the reserved value 3");
|
||||
}
|
||||
const auto colFormat = nullProgram ? readOr(context, spiShaderColFormat, RegisterBank::Context, 0u) : read(context, spiShaderColFormat, RegisterBank::Context);
|
||||
const auto colFormat = nullProgram ? 0u : read(context, spiShaderColFormat, RegisterBank::Context);
|
||||
std::array<std::uint8_t, 8> targetOutputMode{};
|
||||
for (std::uint32_t i = 0; i < 8u; ++i) {
|
||||
targetOutputMode[i] = static_cast<std::uint8_t>((colFormat >> (4u * i)) & 0xFu);
|
||||
|
|
@ -154,12 +155,13 @@ ShaderRecompiler::ShaderPixelStageInfo DecodePixelStageInfo(const Registers& con
|
|||
.earlyZ = zOrder == 1u && !pixelKillEnable && !depthExportEnable && !sampleMaskExportEnable,
|
||||
.executeOnNoop = ((shaderControl >> 10u) & 0x1u) != 0,
|
||||
.conservativeZExport = static_cast<ShaderRecompiler::ConservativeZExport>(conservativeZExport),
|
||||
.orderedPixelShader = ((shaderControl >> 16u) & 0x1u) != 0,
|
||||
.targetOutputMode = targetOutputMode,
|
||||
.targetExportMapping = exportMappings
|
||||
};
|
||||
}
|
||||
|
||||
ShaderRecompiler::ShaderVertexStageInfo DecodeVertexStageInfo(std::span<const std::byte> header, std::uint64_t headerAddress, std::span<const std::uint32_t> userData, std::vector<DecodeRead>* reads) {
|
||||
ShaderRecompiler::ShaderVertexStageInfo DecodeVertexStageInfo(std::span<const std::byte> header, std::uint64_t headerAddress, std::span<const std::uint32_t> userData, std::vector<DecodeRead>* reads, bool staticAbi) {
|
||||
if (header.size() < sizeof(Shader)) throw std::runtime_error("AGC graphics: shader header is smaller than the fixed AGC header");
|
||||
Shader shader;
|
||||
std::memcpy(&shader, header.data(), sizeof(Shader));
|
||||
|
|
@ -188,14 +190,21 @@ ShaderRecompiler::ShaderVertexStageInfo DecodeVertexStageInfo(std::span<const st
|
|||
_readHeaderArray(header, headerAddress, shader.input_semantics, shader.num_input_semantics, semantics.data());
|
||||
const auto attribTableAddr = static_cast<std::uint64_t>(userData[static_cast<std::uint32_t>(vertexAttribReg)]) | (static_cast<std::uint64_t>(userData[static_cast<std::uint32_t>(vertexAttribReg) + 1u]) << 32u);
|
||||
const auto bufferTableAddr = static_cast<std::uint64_t>(userData[static_cast<std::uint32_t>(vertexBufferReg)]) | (static_cast<std::uint64_t>(userData[static_cast<std::uint32_t>(vertexBufferReg) + 1u]) << 32u);
|
||||
if (attribTableAddr == 0) throw std::runtime_error("AGC graphics: null vertex attribute table address");
|
||||
if (bufferTableAddr == 0) throw std::runtime_error("AGC graphics: null vertex buffer table address");
|
||||
if (!staticAbi && attribTableAddr == 0) throw std::runtime_error("AGC graphics: null vertex attribute table address");
|
||||
if (!staticAbi && bufferTableAddr == 0) throw std::runtime_error("AGC graphics: null vertex buffer table address");
|
||||
info.fetchEmbedded = true;
|
||||
info.fetchAttribReg = static_cast<std::uint32_t>(vertexAttribReg);
|
||||
info.fetchBufferReg = static_cast<std::uint32_t>(vertexBufferReg);
|
||||
for (std::uint32_t i = 0; i < shader.num_input_semantics; ++i) {
|
||||
const auto& semantic = semantics[i];
|
||||
if (semantic.static_vb_index == 1 || semantic.static_attribute == 1) throw std::runtime_error("AGC graphics: statically bound vertex attributes are not implemented");
|
||||
if (staticAbi) {
|
||||
auto& destination = info.resourcesDst.at(info.resourcesNum++);
|
||||
destination.registerStart = static_cast<std::int32_t>(semantic.hardware_mapping);
|
||||
destination.registersNum = static_cast<std::int32_t>(semantic.size_in_elements);
|
||||
destination.attrId = static_cast<std::int32_t>(semantic.semantic);
|
||||
continue;
|
||||
}
|
||||
std::array<std::byte, 4> attribWordBytes{};
|
||||
const auto attribWordAddress = attribTableAddr + static_cast<std::uint64_t>(semantic.semantic) * 4u;
|
||||
AgcDriver::GuestMemory::Read(attribWordAddress, attribWordBytes, 4);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
#include "prx/libSceAgcDriver/Execution/include/ProfileOutput.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/PerformanceTimer.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/CaptureTrace.hpp"
|
||||
#include "ThreadOwned.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp"
|
||||
|
|
@ -13,6 +14,7 @@
|
|||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/Pm4.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/DccMetadata.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/TextureFormat.hpp"
|
||||
|
|
@ -282,6 +284,7 @@ std::shared_ptr<StorageTexture> sampledStorageSource(const Context& context, con
|
|||
// `guestBytes` is the surface size when the caller described the surface already (0: described here).
|
||||
std::shared_ptr<Texture> cachedTexture(const Context& context, std::span<const std::uint32_t> words, const GuestTextureResource& resource, VkComponentMapping components, std::uint64_t guestBytes = 0, bool depthCompare = false) {
|
||||
CaptureTrace::Log("sampled-lookup address=%llx width=%u height=%u dcc=%llx", static_cast<unsigned long long>(resource.baseAddress), resource.width, resource.height, static_cast<unsigned long long>(resource.dccAddress));
|
||||
if (auto* recorder = Recorder::Active(); recorder != nullptr) recorder->BoundKeptBytes();
|
||||
if (auto depth = DepthSurfaceTexture(context, words, resource, components)) return depth;
|
||||
const auto depthBitsWidth = words.size() >= 4 ? ShaderRecompiler::DepthBitsTextureWidth(words[1], words[3]) : 0u;
|
||||
if (depthBitsWidth == 32u) {
|
||||
|
|
@ -569,6 +572,7 @@ std::shared_ptr<StorageTexture> cachedStorageTexture(const Context& context, std
|
|||
static const bool disabled = std::getenv("APS5_NO_TEXTURE_CACHE") != nullptr;
|
||||
if (disabled) return std::make_shared<StorageTexture>(context, *context.detiler, viewed, mip);
|
||||
static_cast<void>(words);
|
||||
if (auto* recorder = Recorder::Active(); recorder != nullptr) recorder->BoundKeptBytes();
|
||||
const bool profile = LookupOutcomes::Profiled();
|
||||
auto start = profile ? std::chrono::steady_clock::now() : std::chrono::steady_clock::time_point{};
|
||||
auto& counters = TextureCounts();
|
||||
|
|
@ -883,7 +887,61 @@ double ShaderResources::phase(BuildPhase which) {
|
|||
return ms;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
void ValidateDescriptorLayout(const Context& context, std::span<const VkDescriptorSetLayoutBinding> bindings) {
|
||||
std::map<VkDescriptorType, std::uint64_t> heapTotals;
|
||||
std::map<std::pair<VkShaderStageFlags, VkDescriptorType>, std::uint64_t> stageTotals;
|
||||
std::map<VkShaderStageFlags, std::uint64_t> stageResources;
|
||||
for (const auto& layout : bindings) {
|
||||
heapTotals[layout.descriptorType] += layout.descriptorCount;
|
||||
for (std::uint32_t bit = 0u; bit < 32u; ++bit) {
|
||||
const auto stage = VkShaderStageFlags{1u} << bit;
|
||||
if ((layout.stageFlags & stage) == 0u) continue;
|
||||
const auto count = stageTotals[{stage, layout.descriptorType}] += layout.descriptorCount;
|
||||
const auto all = stageResources[stage] += layout.descriptorType == VK_DESCRIPTOR_TYPE_SAMPLER ? 0u : layout.descriptorCount;
|
||||
Require(all <= context.limits.maxPerStageResources, "typed heaps exceed per-stage resource capacity");
|
||||
if (layout.descriptorType == VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE) Require(count <= context.limits.maxPerStageDescriptorSampledImages, "typed heaps exceed per-stage sampled image capacity");
|
||||
if (layout.descriptorType == VK_DESCRIPTOR_TYPE_STORAGE_IMAGE) Require(count <= context.limits.maxPerStageDescriptorStorageImages, "typed heaps exceed per-stage storage image capacity");
|
||||
if (layout.descriptorType == VK_DESCRIPTOR_TYPE_SAMPLER) Require(count <= context.limits.maxPerStageDescriptorSamplers, "typed heaps exceed per-stage sampler capacity");
|
||||
}
|
||||
}
|
||||
Require(heapTotals[VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE] <= context.limits.maxDescriptorSetSampledImages && heapTotals[VK_DESCRIPTOR_TYPE_STORAGE_IMAGE] <= context.limits.maxDescriptorSetStorageImages && heapTotals[VK_DESCRIPTOR_TYPE_SAMPLER] <= context.limits.maxDescriptorSetSamplers, "typed heaps exceed descriptor set capacity");
|
||||
}
|
||||
|
||||
void ValidateRuntimeResources(const CompiledShader& shader, std::span<const std::uint32_t> words) {
|
||||
Require(shader.program != nullptr, "missing compiled shader");
|
||||
const auto& program = *shader.program;
|
||||
const auto& descriptors = program.bindings;
|
||||
constexpr auto bindingCount = static_cast<std::uint32_t>(ShaderRecompiler::RuntimeAbi::Binding::Count);
|
||||
std::array<const ShaderRecompiler::DescriptorBinding*, bindingCount> heaps{};
|
||||
if (program.runtimeImageCount != 0u) {
|
||||
for (const auto& binding : descriptors) {
|
||||
if (binding.role != ShaderRecompiler::DescriptorRole::GuestImages) continue;
|
||||
auto& heap = heaps[binding.binding % bindingCount];
|
||||
Require(heap == nullptr, "duplicate runtime image binding");
|
||||
heap = &binding;
|
||||
}
|
||||
}
|
||||
Require(words.size() == program.shaderDataDwords, "invalid compact shader data size");
|
||||
constexpr auto metadataWords = sizeof(ShaderRecompiler::RuntimeAbi::ResourceMetadata) / sizeof(std::uint32_t);
|
||||
Require(program.imageMetadataDword <= words.size() && program.runtimeImageCount <= (words.size() - program.imageMetadataDword) / metadataWords, "runtime image metadata exceeds shader data");
|
||||
for (const auto index : program.runtimeImageResources) {
|
||||
Require(index < program.runtimeImageCount, "runtime image resource exceeds its compact layout");
|
||||
const auto offset = program.imageMetadataDword + index * metadataWords;
|
||||
const auto kind = words[offset];
|
||||
const auto first = words[offset + 1u];
|
||||
const auto count = words[offset + 2u];
|
||||
Require(count != 0u, "runtime image metadata has no descriptor elements");
|
||||
Require(kind < heaps.size() && heaps[kind] != nullptr, "runtime metadata references an unbound image heap");
|
||||
Require(first < heaps[kind]->count && count <= heaps[kind]->count - first, "runtime metadata exceeds its bound heap");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void ShaderResources::buildPrepare(std::span<const CompiledShader> shaders, const ColorTarget* target, std::uint64_t indexAddress, std::size_t indexBytes) {
|
||||
PerformanceTimer frameTiming("Resources.Prepare");
|
||||
const auto stageStart = std::chrono::steady_clock::now();
|
||||
phaseStart = stageStart;
|
||||
if (BuildProfiled()) {
|
||||
|
|
@ -892,9 +950,11 @@ void ShaderResources::buildPrepare(std::span<const CompiledShader> shaders, cons
|
|||
}
|
||||
try {
|
||||
Require(!shaders.empty() && context.limits.maxBoundDescriptorSets >= 1, "shader descriptor set exceeds device limits");
|
||||
if (shaders.size() == 1u) refreshResourceKey = ContentKey(shaders.front(), false, true);
|
||||
std::set<std::uint32_t> occupied;
|
||||
for (const auto& shader : shaders) {
|
||||
Require(shader.program != nullptr, "missing compiled shader");
|
||||
ShaderRecompiler::RuntimeAbi::RequireVersion(shader.program->runtimeAbiVersion);
|
||||
const VkShaderStageFlags flags = VulkanStage(shader.stage);
|
||||
std::uint64_t stageStorageBuffers = 0;
|
||||
std::uint64_t stageResources = 0;
|
||||
|
|
@ -906,7 +966,7 @@ void ShaderResources::buildPrepare(std::span<const CompiledShader> shaders, cons
|
|||
Require(binding.descriptorSet == 0, "unexpected descriptor set: every shader resource must use descriptor set zero");
|
||||
Require(occupied.insert(binding.binding).second, "duplicate shader binding");
|
||||
const bool addressRole = binding.role == ShaderRecompiler::DescriptorRole::BdaPagetable || binding.role == ShaderRecompiler::DescriptorRole::FaultBuffer;
|
||||
const bool bufferRole = addressRole || binding.role == ShaderRecompiler::DescriptorRole::GuestBuffers || binding.role == ShaderRecompiler::DescriptorRole::ShaderData || binding.role == ShaderRecompiler::DescriptorRole::FlattenedSrt;
|
||||
const bool bufferRole = addressRole || binding.role == ShaderRecompiler::DescriptorRole::GuestBuffers || binding.role == ShaderRecompiler::DescriptorRole::ShaderData || binding.role == ShaderRecompiler::DescriptorRole::FlattenedSrt || binding.role == ShaderRecompiler::DescriptorRole::Gds;
|
||||
const bool imageRole = binding.role == ShaderRecompiler::DescriptorRole::GuestImages || binding.role == ShaderRecompiler::DescriptorRole::GuestSamplers;
|
||||
if (imageRole) {
|
||||
if (binding.kind == ShaderRecompiler::DescriptorKind::SampledImage || binding.kind == ShaderRecompiler::DescriptorKind::StorageImage) stageResources += binding.count;
|
||||
|
|
@ -945,9 +1005,18 @@ void ShaderResources::buildPrepare(std::span<const CompiledShader> shaders, cons
|
|||
} else if (addressRole) {
|
||||
item.allocations.push_back(allocations.size());
|
||||
allocations.push_back({0, 0, false, nullptr, binding.role});
|
||||
} else if (binding.role == ShaderRecompiler::DescriptorRole::Gds) {
|
||||
Require(binding.count == 1 && binding.guestDescriptor.empty(), "invalid GDS descriptor contract");
|
||||
const auto gds = Pm4::GdsAddress();
|
||||
guestMemory.AddWritable(gds, Pm4::GdsBytes, true);
|
||||
item.allocations.push_back(allocations.size());
|
||||
allocations.push_back({gds, Pm4::GdsBytes, true, nullptr, ShaderRecompiler::DescriptorRole::ShaderData, true});
|
||||
} else {
|
||||
Require(binding.count == 1, "shader data and flattened SRT descriptors must not be arrays");
|
||||
Require(!binding.guestDescriptor.empty(), "empty shader data descriptor");
|
||||
if (binding.role == ShaderRecompiler::DescriptorRole::ShaderData) {
|
||||
ValidateRuntimeResources(shader, binding.guestDescriptor);
|
||||
}
|
||||
item.allocations.push_back(addDataBuffer(binding.guestDescriptor));
|
||||
if (binding.role == ShaderRecompiler::DescriptorRole::ShaderData) shaderData = static_cast<std::int64_t>(item.allocations.back());
|
||||
}
|
||||
|
|
@ -976,6 +1045,7 @@ void ShaderResources::buildPrepare(std::span<const CompiledShader> shaders, cons
|
|||
if (context.descriptorCache != nullptr && !noLayoutCache) {
|
||||
_layout = context.descriptorCache->Layout(layoutKey, description);
|
||||
} else {
|
||||
ValidateDescriptorLayout(context, description);
|
||||
VkDescriptorSetLayoutCreateInfo info{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO};
|
||||
info.bindingCount = static_cast<std::uint32_t>(description.size());
|
||||
info.pBindings = description.data();
|
||||
|
|
@ -1020,6 +1090,8 @@ void ShaderResources::buildPrepare(std::span<const CompiledShader> shaders, cons
|
|||
}
|
||||
|
||||
void ShaderResources::buildComplete() {
|
||||
PerformanceTimer frameTiming("Resources.Complete");
|
||||
Require(!completed, "cannot update a published descriptor heap");
|
||||
const auto stageStart = std::chrono::steady_clock::now();
|
||||
phaseStart = stageStart;
|
||||
try {
|
||||
|
|
@ -1047,7 +1119,7 @@ void ShaderResources::buildComplete() {
|
|||
std::size_t imageCount = 0;
|
||||
for (const auto& binding : bindings) {
|
||||
bufferCount += binding.allocations.size();
|
||||
imageCount += binding.imageAllocations.size();
|
||||
if (binding.layout.descriptorType != VK_DESCRIPTOR_TYPE_STORAGE_BUFFER) imageCount += binding.layout.descriptorCount;
|
||||
}
|
||||
std::vector<VkDescriptorBufferInfo> buffers;
|
||||
std::vector<VkDescriptorImageInfo> images;
|
||||
|
|
@ -1068,15 +1140,24 @@ void ShaderResources::buildComplete() {
|
|||
break;
|
||||
case VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE:
|
||||
write.pImageInfo = images.data() + images.size();
|
||||
for (const auto index : binding.imageAllocations) images.push_back({VK_NULL_HANDLE, textureFirstLayer[index] ? textures[index]->FirstLayerView() : textures[index]->View(), textures[index]->Layout()});
|
||||
for (const auto index : binding.imageAllocations) {
|
||||
if (index == std::numeric_limits<std::size_t>::max()) images.push_back({VK_NULL_HANDLE, VK_NULL_HANDLE, VK_IMAGE_LAYOUT_GENERAL});
|
||||
else images.push_back({VK_NULL_HANDLE, textureFirstLayer[index] ? textures[index]->FirstLayerView() : textures[index]->View(), textures[index]->Layout()});
|
||||
}
|
||||
Require(binding.imageAllocations.size() == binding.layout.descriptorCount, "descriptor allocations disagree with compact binding");
|
||||
break;
|
||||
case VK_DESCRIPTOR_TYPE_STORAGE_IMAGE:
|
||||
write.pImageInfo = images.data() + images.size();
|
||||
for (const auto index : binding.imageAllocations) images.push_back({VK_NULL_HANDLE, storageAtomic64[index] ? storageTextures[index]->Atomic64View(storageMips[index], storageFirstLayer[index]) : storageAtomic[index] ? storageTextures[index]->AtomicView(storageMips[index], storageFirstLayer[index]) : storageTextures[index]->StorageView(storageMips[index], storageFirstLayer[index]), VK_IMAGE_LAYOUT_GENERAL});
|
||||
for (const auto index : binding.imageAllocations) {
|
||||
if (index == std::numeric_limits<std::size_t>::max()) images.push_back({VK_NULL_HANDLE, VK_NULL_HANDLE, VK_IMAGE_LAYOUT_GENERAL});
|
||||
else images.push_back({VK_NULL_HANDLE, storageAtomic64[index] ? storageTextures[index]->Atomic64View(storageMips[index], storageFirstLayer[index]) : storageAtomic[index] ? storageTextures[index]->AtomicView(storageMips[index], storageFirstLayer[index]) : storageTextures[index]->StorageView(storageMips[index], storageFirstLayer[index]), VK_IMAGE_LAYOUT_GENERAL});
|
||||
}
|
||||
Require(binding.imageAllocations.size() == binding.layout.descriptorCount, "descriptor allocations disagree with compact binding");
|
||||
break;
|
||||
case VK_DESCRIPTOR_TYPE_SAMPLER:
|
||||
write.pImageInfo = images.data() + images.size();
|
||||
for (const auto index : binding.imageAllocations) images.push_back({samplers[index]->Handle(), VK_NULL_HANDLE, VK_IMAGE_LAYOUT_UNDEFINED});
|
||||
Require(binding.imageAllocations.size() == binding.layout.descriptorCount, "descriptor allocations disagree with compact binding");
|
||||
break;
|
||||
default: throw std::runtime_error("AGC graphics: ShaderResources encountered an unknown descriptor type while writing the descriptor set");
|
||||
}
|
||||
|
|
@ -1177,42 +1258,41 @@ void ShaderResources::reportDescriptorCaches() const {
|
|||
const auto descriptors = context.descriptorCache != nullptr ? context.descriptorCache->Counters() : DescriptorCache::Stats{};
|
||||
const auto samplerHits = context.samplerCache != nullptr ? context.samplerCache->Hits() : 0;
|
||||
const auto samplerMisses = context.samplerCache != nullptr ? context.samplerCache->Misses() : 0;
|
||||
AgcDriver::ProfilePrint_nid_no_patch("[descriptors] layouts %llu hits / %llu created, sets %llu from %llu pools, samplers %llu hits / %llu created\n", static_cast<unsigned long long>(descriptors.layoutHits), static_cast<unsigned long long>(descriptors.layoutMisses), static_cast<unsigned long long>(descriptors.sets), static_cast<unsigned long long>(descriptors.pools), static_cast<unsigned long long>(samplerHits), static_cast<unsigned long long>(samplerMisses));
|
||||
AgcDriver::ProfilePrint_nid_no_patch("[descriptors] layouts %llu hits / %llu created, sets %llu from %llu pools and %llu dedicated pools, samplers %llu hits / %llu created\n", static_cast<unsigned long long>(descriptors.layoutHits), static_cast<unsigned long long>(descriptors.layoutMisses), static_cast<unsigned long long>(descriptors.sets), static_cast<unsigned long long>(descriptors.pools), static_cast<unsigned long long>(descriptors.dedicatedPools), static_cast<unsigned long long>(samplerHits), static_cast<unsigned long long>(samplerMisses));
|
||||
}
|
||||
|
||||
std::vector<std::uint32_t> ShaderResources::ContentKey(const CompiledShader& shader, bool dataWords, bool movableBuffers) {
|
||||
template<typename TVisitor>
|
||||
void VisitContentKey(const CompiledShader& shader, bool dataWords, bool movableBuffers, const TVisitor& visit) {
|
||||
Require(shader.program != nullptr, "missing compiled shader");
|
||||
const auto& program = *shader.program;
|
||||
std::vector<std::uint32_t> key;
|
||||
key.reserve(8 + program.bindings.size() * 12);
|
||||
key.push_back(dataWords ? 1u : 0u);
|
||||
key.push_back(static_cast<std::uint32_t>(shader.stage));
|
||||
key.push_back(static_cast<std::uint32_t>(program.variantId));
|
||||
key.push_back(static_cast<std::uint32_t>(program.variantId >> 32u));
|
||||
key.push_back(static_cast<std::uint32_t>(program.bindings.size()));
|
||||
visit(dataWords ? 1u : 0u);
|
||||
visit(static_cast<std::uint32_t>(shader.stage));
|
||||
visit(static_cast<std::uint32_t>(program.PipelineVariantId()));
|
||||
visit(static_cast<std::uint32_t>(program.PipelineVariantId() >> 32u));
|
||||
visit(static_cast<std::uint32_t>(program.bindings.size()));
|
||||
const auto packBits = [&](const std::vector<bool>& bits) {
|
||||
key.push_back(static_cast<std::uint32_t>(bits.size()));
|
||||
visit(static_cast<std::uint32_t>(bits.size()));
|
||||
std::uint32_t word = 0;
|
||||
for (std::size_t i = 0; i < bits.size(); ++i) {
|
||||
if (bits[i]) word |= 1u << (i % 32u);
|
||||
if (i % 32u == 31u || i + 1 == bits.size()) {
|
||||
key.push_back(word);
|
||||
visit(word);
|
||||
word = 0;
|
||||
}
|
||||
}
|
||||
};
|
||||
for (const auto& binding : program.bindings) {
|
||||
key.insert(key.end(), {static_cast<std::uint32_t>(binding.kind), static_cast<std::uint32_t>(binding.role), binding.descriptorSet, binding.binding, binding.count, binding.readOnly ? 1u : 0u, binding.imageShape.has_value() ? static_cast<std::uint32_t>(*binding.imageShape) + 1u : 0u, static_cast<std::uint32_t>(binding.guestDescriptor.size())});
|
||||
for (const auto word : {static_cast<std::uint32_t>(binding.kind), static_cast<std::uint32_t>(binding.role), binding.descriptorSet, binding.binding, binding.count, binding.readOnly ? 1u : 0u, binding.imageShape.has_value() ? static_cast<std::uint32_t>(*binding.imageShape) + 1u : 0u, static_cast<std::uint32_t>(binding.guestDescriptor.size())}) visit(word);
|
||||
if (movableBuffers && binding.role == ShaderRecompiler::DescriptorRole::GuestBuffers && binding.guestDescriptor.size() == static_cast<std::size_t>(binding.count) * 4u) {
|
||||
for (std::uint32_t element = 0; element < binding.count; ++element) {
|
||||
const auto* words = binding.guestDescriptor.data() + static_cast<std::size_t>(element) * 4u;
|
||||
const bool written = element >= binding.bufferWritten.size() || binding.bufferWritten[element];
|
||||
const bool empty = words[2] == 0 || (words[0] == 0 && (words[1] & 0xffffu) == 0);
|
||||
if (written || empty) key.insert(key.end(), words, words + 4);
|
||||
else key.insert(key.end(), {0u, words[1] & 0xffff0000u, 0u, words[3]});
|
||||
if (written || empty) { for (std::uint32_t index = 0; index < 4u; ++index) visit(words[index]); }
|
||||
else { for (const auto word : {0u, words[1] & 0xffff0000u, 0u, words[3]}) visit(word); }
|
||||
}
|
||||
} else if ((dataWords && !movableBuffers) || !DataRole(binding.role)) {
|
||||
key.insert(key.end(), binding.guestDescriptor.begin(), binding.guestDescriptor.end());
|
||||
for (const auto word : binding.guestDescriptor) visit(word);
|
||||
}
|
||||
packBits(binding.imageWritten);
|
||||
packBits(binding.samplerDepthCompare);
|
||||
|
|
@ -1223,12 +1303,29 @@ std::vector<std::uint32_t> ShaderResources::ContentKey(const CompiledShader& sha
|
|||
// Read-only elements are bound without a write set: an object built for one written set
|
||||
// must not serve a build with another (the variant implies it, this makes it explicit).
|
||||
packBits(binding.bufferWritten);
|
||||
key.push_back(static_cast<std::uint32_t>(binding.imageSamplers.size()));
|
||||
key.insert(key.end(), binding.imageSamplers.begin(), binding.imageSamplers.end());
|
||||
visit(static_cast<std::uint32_t>(binding.imageSamplers.size()));
|
||||
for (const auto sampler : binding.imageSamplers) visit(sampler);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::uint32_t> ShaderResources::ContentKey(const CompiledShader& shader, bool dataWords, bool movableBuffers) {
|
||||
Require(shader.program != nullptr, "missing compiled shader");
|
||||
std::vector<std::uint32_t> key;
|
||||
key.reserve(8u + shader.program->bindings.size() * 12u);
|
||||
VisitContentKey(shader, dataWords, movableBuffers, [&](std::uint32_t word) { key.push_back(word); });
|
||||
return key;
|
||||
}
|
||||
|
||||
bool MatchesContentKey(const CompiledShader& shader, std::span<const std::uint32_t> key) {
|
||||
std::size_t cursor = 0;
|
||||
bool equal = true;
|
||||
VisitContentKey(shader, false, true, [&](std::uint32_t word) {
|
||||
equal &= cursor < key.size() && key[cursor] == word;
|
||||
++cursor;
|
||||
});
|
||||
return equal && cursor == key.size();
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
// APS5_PROFILE_DRAW: Revalidate outcomes and time, printed as [rescache] every 10 s next to the
|
||||
|
|
@ -1462,7 +1559,7 @@ bool ShaderResources::fastRevalidate(std::uint64_t serialBefore, std::span<const
|
|||
// since its generation (the query below). The surface's own keys come from the same
|
||||
// proof when they are the image's, else from the texture's.
|
||||
const auto& own = source->Descriptor();
|
||||
const auto sourceKeys = own.dccAddress != 0 ? ProvedClearKeys(own, source->GuestBytes(), source->KeyProof()) : DccKeys::Uncompressed;
|
||||
const auto sourceKeys = source->ProvedKeys();
|
||||
if (sourceKeys != source->UploadedKeys()) return fail(FastFail::Keys);
|
||||
const auto keys = surface.resource.dccAddress == 0 ? DccKeys::Uncompressed : SameKeySurface(source, surface.resource, surface.bytes) ? sourceKeys : ProvedClearKeys(surface.resource, surface.bytes, textures[i]->KeyProof());
|
||||
if (surface.resource.dccAddress != 0 && surface.resource.dccAddress != own.dccAddress && (keys != DccKeys::Uncompressed || !StorageImageServesKeys(*source, surface.resource.dccAddress))) return fail(FastFail::Keys);
|
||||
|
|
@ -1504,7 +1601,7 @@ bool ShaderResources::fastRevalidate(std::uint64_t serialBefore, std::span<const
|
|||
// Refresh's unchanged branch (its key compare against the keys the content was
|
||||
// uploaded under, through the image's proof; the memory query below), minus its
|
||||
// byte-compare fallback; the image's generation is left where it is.
|
||||
if (!keyProofs || ProvedClearKeys(own, bytes, image->KeyProof()) != image->UploadedKeys()) return fail(FastFail::StorageKeys);
|
||||
if (!keyProofs || image->ProvedKeys() != image->UploadedKeys()) return fail(FastFail::StorageKeys);
|
||||
countKeysProven(true);
|
||||
}
|
||||
if (!unchanged) query(address, address + bytes, image, nullptr, {i, true, false});
|
||||
|
|
@ -1666,17 +1763,23 @@ ShaderResources::OwnRefreshFallback ShaderResources::refreshOwnObjects(std::span
|
|||
for (const auto& binding : shader.program->bindings) {
|
||||
if (binding.role != ShaderRecompiler::DescriptorRole::GuestImages) continue;
|
||||
if (binding.kind == ShaderRecompiler::DescriptorKind::SampledImage) {
|
||||
for (std::uint32_t element = 0; element < binding.count; ++element, ++textureIndex) {
|
||||
for (std::uint32_t element = 0; element < binding.count; ++element) {
|
||||
const auto words = std::span<const std::uint32_t>(binding.guestDescriptor).subspan(static_cast<std::size_t>(element) * 8u, 8u);
|
||||
if (words[0] == 0u && (words[1] & 0xffu) == 0u) continue;
|
||||
if (textureIndex >= textures.size()) return OwnRefreshFallback::Uncached;
|
||||
const auto overlap = listed(textureIndex, false);
|
||||
const auto index = textureIndex++;
|
||||
const auto overlap = listed(index, false);
|
||||
if (overlap == overlapping.end()) continue;
|
||||
if (const auto fallback = refreshView(textureIndex, *overlap); fallback != OwnRefreshFallback::Count) return fallback;
|
||||
if (const auto fallback = refreshView(index, *overlap); fallback != OwnRefreshFallback::Count) return fallback;
|
||||
}
|
||||
} else if (binding.kind == ShaderRecompiler::DescriptorKind::StorageImage) {
|
||||
for (std::uint32_t element = 0; element < binding.count; ++element, ++storageIndex) {
|
||||
for (std::uint32_t element = 0; element < binding.count; ++element) {
|
||||
const auto words = std::span<const std::uint32_t>(binding.guestDescriptor).subspan(static_cast<std::size_t>(element) * 8u, 8u);
|
||||
if (words[0] == 0u && (words[1] & 0xffu) == 0u) continue;
|
||||
if (storageIndex >= storageTextures.size()) return OwnRefreshFallback::Uncached;
|
||||
if (listed(storageIndex, true) == overlapping.end()) continue;
|
||||
if (const auto fallback = refreshStorage(storageIndex); fallback != OwnRefreshFallback::Count) return fallback;
|
||||
const auto index = storageIndex++;
|
||||
if (listed(index, true) == overlapping.end()) continue;
|
||||
if (const auto fallback = refreshStorage(index); fallback != OwnRefreshFallback::Count) return fallback;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1715,6 +1818,7 @@ bool ShaderResources::Revalidate(std::span<const CompiledShader> shaders, ProofR
|
|||
const auto elementWords = binding.count != 0 ? binding.guestDescriptor.size() / binding.count : 0;
|
||||
for (std::uint32_t element = 0; element < binding.count; ++element) {
|
||||
const auto words = std::span<const std::uint32_t>(binding.guestDescriptor).subspan(static_cast<std::size_t>(element) * elementWords, elementWords);
|
||||
if (words[0] == 0u && (words[1] & 0xffu) == 0u) continue;
|
||||
const auto resource = DecodeTextureResource(words);
|
||||
const VkComponentMapping components = ViewComponents(resource);
|
||||
if (textureIndex >= textures.size() || cachedTexture(context, words, resource, components, 0, !binding.imageDepthCompare.empty() && binding.imageDepthCompare.at(element)) != textures[textureIndex]) return false;
|
||||
|
|
@ -1723,6 +1827,7 @@ bool ShaderResources::Revalidate(std::span<const CompiledShader> shaders, ProofR
|
|||
} else if (binding.kind == ShaderRecompiler::DescriptorKind::StorageImage) {
|
||||
for (std::uint32_t element = 0; element < binding.count; ++element) {
|
||||
const auto words = std::span<const std::uint32_t>(binding.guestDescriptor).subspan(static_cast<std::size_t>(element) * 8u, 8u);
|
||||
if (words[0] == 0u && (words[1] & 0xffu) == 0u) continue;
|
||||
if (storageIndex >= storageTextures.size()) return false;
|
||||
std::shared_ptr<StorageTexture> expected;
|
||||
if (SameAsPreviousStorageElement(binding, element) && StorageDedupeEnabled()) expected = storageTextures[storageIndex - 1];
|
||||
|
|
@ -2094,6 +2199,7 @@ DescriptorCache::DescriptorCache(const Context& context) : context(context), des
|
|||
|
||||
DescriptorCache::~DescriptorCache() {
|
||||
for (const auto pool : pools) destroyPool(context.device, pool, nullptr);
|
||||
for (const auto pool : dedicated) destroyPool(context.device, pool, nullptr);
|
||||
for (const auto& [key, layout] : layouts) destroyLayout(context.device, layout, nullptr);
|
||||
}
|
||||
|
||||
|
|
@ -2104,6 +2210,7 @@ VkDescriptorSetLayout DescriptorCache::Layout(std::span<const std::uint32_t> key
|
|||
++stats.layoutHits;
|
||||
return found->second;
|
||||
}
|
||||
ValidateDescriptorLayout(context, bindings);
|
||||
++stats.layoutMisses;
|
||||
VkDescriptorSetLayoutCreateInfo info{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO};
|
||||
info.bindingCount = static_cast<std::uint32_t>(bindings.size());
|
||||
|
|
@ -2115,21 +2222,54 @@ VkDescriptorSetLayout DescriptorCache::Layout(std::span<const std::uint32_t> key
|
|||
}
|
||||
|
||||
namespace {
|
||||
std::uint32_t SetDescriptorLimit(const VkPhysicalDeviceLimits& limits, VkDescriptorType type) {
|
||||
switch (type) {
|
||||
case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER: return limits.maxDescriptorSetStorageBuffers;
|
||||
case VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE: return limits.maxDescriptorSetSampledImages;
|
||||
case VK_DESCRIPTOR_TYPE_STORAGE_IMAGE: return limits.maxDescriptorSetStorageImages;
|
||||
case VK_DESCRIPTOR_TYPE_SAMPLER: return limits.maxDescriptorSetSamplers;
|
||||
default: throw std::runtime_error("AGC graphics: descriptor set uses an unsupported descriptor type " + std::to_string(type));
|
||||
}
|
||||
}
|
||||
|
||||
// What one chain pool holds; a set needing more of any type gets a dedicated pool.
|
||||
constexpr std::uint32_t ChainPoolSets = 1024;
|
||||
constexpr std::array<VkDescriptorPoolSize, 4> ChainPoolSizes{{{VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 4096}, {VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 1024}, {VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1024}, {VK_DESCRIPTOR_TYPE_SAMPLER, 512}}};
|
||||
constexpr std::array<VkDescriptorPoolSize, 4> ChainPoolCreateSizes{{{VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 4096 + 2}, {VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 1024}, {VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1024}, {VK_DESCRIPTOR_TYPE_SAMPLER, 512}}};
|
||||
}
|
||||
|
||||
DescriptorCache::SetAllocation DescriptorCache::Allocate(VkDescriptorSetLayout layout, std::span<const VkDescriptorPoolSize> sizes) {
|
||||
bool fits = true;
|
||||
for (const auto& size : sizes) {
|
||||
Require(size.descriptorCount <= SetDescriptorLimit(context.limits, size.type), "descriptor set exceeds the device's per-set descriptor limit");
|
||||
const auto capacity = std::find_if(ChainPoolSizes.begin(), ChainPoolSizes.end(), [&](const auto& item) { return item.type == size.type; });
|
||||
if (capacity == ChainPoolSizes.end() || size.descriptorCount > capacity->descriptorCount) return {};
|
||||
if (capacity == ChainPoolSizes.end() || size.descriptorCount > capacity->descriptorCount) fits = false;
|
||||
}
|
||||
std::lock_guard lock(mutex);
|
||||
const auto allocate = context.Resolved(&DeviceFunctions::allocateDescriptorSets, "vkAllocateDescriptorSets");
|
||||
VkDescriptorSetAllocateInfo allocation{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO};
|
||||
allocation.descriptorSetCount = 1;
|
||||
allocation.pSetLayouts = &layout;
|
||||
if (!fits) {
|
||||
dedicated.reserve(dedicated.size() + 1);
|
||||
VkDescriptorPoolCreateInfo poolInfo{VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO};
|
||||
poolInfo.maxSets = 1;
|
||||
poolInfo.poolSizeCount = static_cast<std::uint32_t>(sizes.size());
|
||||
poolInfo.pPoolSizes = sizes.data();
|
||||
VkDescriptorPool pool = VK_NULL_HANDLE;
|
||||
Check(context.Function<PFN_vkCreateDescriptorPool>("vkCreateDescriptorPool")(context.device, &poolInfo, nullptr, &pool), "vkCreateDescriptorPool");
|
||||
allocation.descriptorPool = pool;
|
||||
VkDescriptorSet set = VK_NULL_HANDLE;
|
||||
const auto result = allocate(context.device, &allocation, &set);
|
||||
if (result != VK_SUCCESS) {
|
||||
destroyPool(context.device, pool, nullptr);
|
||||
Check(result, "vkAllocateDescriptorSets");
|
||||
}
|
||||
dedicated.push_back(pool);
|
||||
++stats.dedicatedPools;
|
||||
++stats.sets;
|
||||
return {set, pool};
|
||||
}
|
||||
// Newest pool first: it has the most room; a full or fragmented pool is left for its sets to
|
||||
// drain and tried again later.
|
||||
for (auto it = pools.rbegin(); it != pools.rend(); ++it) {
|
||||
|
|
@ -2145,8 +2285,8 @@ DescriptorCache::SetAllocation DescriptorCache::Allocate(VkDescriptorSetLayout l
|
|||
VkDescriptorPoolCreateInfo poolInfo{VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO};
|
||||
poolInfo.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT;
|
||||
poolInfo.maxSets = ChainPoolSets;
|
||||
poolInfo.poolSizeCount = static_cast<std::uint32_t>(ChainPoolSizes.size());
|
||||
poolInfo.pPoolSizes = ChainPoolSizes.data();
|
||||
poolInfo.poolSizeCount = static_cast<std::uint32_t>(ChainPoolCreateSizes.size());
|
||||
poolInfo.pPoolSizes = ChainPoolCreateSizes.data();
|
||||
VkDescriptorPool pool = VK_NULL_HANDLE;
|
||||
Check(context.Function<PFN_vkCreateDescriptorPool>("vkCreateDescriptorPool")(context.device, &poolInfo, nullptr, &pool), "vkCreateDescriptorPool");
|
||||
pools.push_back(pool);
|
||||
|
|
@ -2161,6 +2301,11 @@ DescriptorCache::SetAllocation DescriptorCache::Allocate(VkDescriptorSetLayout l
|
|||
void DescriptorCache::Free(const SetAllocation& allocation) noexcept {
|
||||
if (allocation.set == VK_NULL_HANDLE || allocation.pool == VK_NULL_HANDLE) return;
|
||||
std::lock_guard lock(mutex);
|
||||
if (const auto found = std::find(dedicated.begin(), dedicated.end(), allocation.pool); found != dedicated.end()) {
|
||||
dedicated.erase(found);
|
||||
destroyPool(context.device, allocation.pool, nullptr);
|
||||
return;
|
||||
}
|
||||
static_cast<void>(freeSets(context.device, allocation.pool, 1, &allocation.set));
|
||||
}
|
||||
|
||||
|
|
@ -2307,6 +2452,12 @@ bool ShaderResources::DataWordsDiffer(const CompiledShader& shader) const {
|
|||
}
|
||||
|
||||
bool ShaderResources::RefreshData(VkCommandBuffer commands, const CompiledShader& shader, Recorder* recorder) {
|
||||
Require(shader.program != nullptr, "missing compiled shader");
|
||||
Require(!refreshResourceKey.empty() && MatchesContentKey(shader, refreshResourceKey), "runtime data refresh cannot replace bound resources");
|
||||
return refreshData(commands, shader, recorder);
|
||||
}
|
||||
|
||||
bool ShaderResources::refreshData(VkCommandBuffer commands, const CompiledShader& shader, Recorder* recorder) {
|
||||
Require(shader.program != nullptr, "missing compiled shader");
|
||||
const auto& program = *shader.program;
|
||||
Require(program.bindings.size() == bindings.size(), "template bindings disagree with the shader");
|
||||
|
|
@ -2323,6 +2474,7 @@ bool ShaderResources::RefreshData(VkCommandBuffer commands, const CompiledShader
|
|||
const auto size = binding.guestDescriptor.size() * sizeof(std::uint32_t);
|
||||
Require(allocation.buffer != nullptr && !allocation.guest && allocation.size == size && size <= MaxRefreshBytes, "template data buffer cannot take the dispatch's words");
|
||||
if (allocation.dataWords.size() == binding.guestDescriptor.size() && std::equal(allocation.dataWords.begin(), allocation.dataWords.end(), binding.guestDescriptor.begin())) continue;
|
||||
if (binding.role == ShaderRecompiler::DescriptorRole::ShaderData) ValidateRuntimeResources(shader, binding.guestDescriptor);
|
||||
if (recorder != nullptr && timing == Recorder::NoTiming) timing = recorder->BeginGpuTiming(Recorder::CommandClass::TemplateDataRefresh);
|
||||
writeDataWords(commands, bindings[index].allocations.front(), binding.guestDescriptor);
|
||||
allocation.dataWords.assign(binding.guestDescriptor.begin(), binding.guestDescriptor.end());
|
||||
|
|
@ -2355,13 +2507,21 @@ void ShaderResources::PrecollectSurfaces() const {
|
|||
|
||||
void ShaderResources::addImageBinding(const ShaderRecompiler::DescriptorBinding& binding, VkShaderStageFlags flags) {
|
||||
Require(binding.count != 0, "empty descriptor binding");
|
||||
const auto heap = static_cast<ShaderRecompiler::RuntimeAbi::Binding>(binding.binding % static_cast<std::uint32_t>(ShaderRecompiler::RuntimeAbi::Binding::Count));
|
||||
const auto capacity = binding.count;
|
||||
Require(binding.count <= ShaderRecompiler::RuntimeAbi::HeapCapacity(heap), "typed descriptor heap capacity exceeded");
|
||||
const auto localBinding = static_cast<std::uint32_t>(heap);
|
||||
const bool sampledHeap = localBinding >= ShaderRecompiler::RuntimeAbi::FirstImageBinding && localBinding < ShaderRecompiler::RuntimeAbi::FirstStorageImageBinding;
|
||||
const bool storageHeap = localBinding >= ShaderRecompiler::RuntimeAbi::FirstStorageImageBinding && localBinding < static_cast<std::uint32_t>(ShaderRecompiler::RuntimeAbi::Binding::Samplers);
|
||||
Require((binding.kind == ShaderRecompiler::DescriptorKind::SampledImage && sampledHeap) || (binding.kind == ShaderRecompiler::DescriptorKind::StorageImage && storageHeap) || (binding.kind == ShaderRecompiler::DescriptorKind::Sampler && heap == ShaderRecompiler::RuntimeAbi::Binding::Samplers), "resource class disagrees with typed descriptor heap");
|
||||
Require(binding.kind == ShaderRecompiler::DescriptorKind::Sampler || context.nullDescriptors, "typed image heaps require enabled nullDescriptor");
|
||||
if (binding.kind == ShaderRecompiler::DescriptorKind::StorageImage) {
|
||||
// Storage images are guest textures the shader writes (looked up in stage B).
|
||||
Require(binding.role == ShaderRecompiler::DescriptorRole::GuestImages, "storage image binding has a non-image role");
|
||||
Require(binding.guestDescriptor.size() == static_cast<std::size_t>(binding.count) * 8u, "guest storage image descriptors must contain 8 dwords each");
|
||||
Require(context.detiler != nullptr, "device texture detiler is unavailable");
|
||||
Require(binding.count <= context.limits.maxPerStageDescriptorStorageImages, "shader storage-image descriptors exceed per-stage limits");
|
||||
bindings.push_back({{binding.binding, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, binding.count, flags, nullptr}, {}, {}});
|
||||
Require(capacity <= context.limits.maxPerStageDescriptorStorageImages, "shader storage-image descriptors exceed per-stage limits");
|
||||
bindings.push_back({{binding.binding, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, capacity, flags, nullptr}, {}, {}});
|
||||
plannedStorageImages += binding.count;
|
||||
deferredImages.push_back({&binding, bindings.size() - 1});
|
||||
return;
|
||||
|
|
@ -2373,19 +2533,19 @@ void ShaderResources::addImageBinding(const ShaderRecompiler::DescriptorBinding&
|
|||
Require(binding.guestDescriptor.size() % binding.count == 0, "guest image descriptor size is not a multiple of the binding count");
|
||||
const auto elementWords = binding.guestDescriptor.size() / binding.count;
|
||||
|
||||
Binding item{{binding.binding, sampledImage ? VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE : VK_DESCRIPTOR_TYPE_SAMPLER, binding.count, flags, nullptr}, {}, {}};
|
||||
Binding item{{binding.binding, sampledImage ? VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE : VK_DESCRIPTOR_TYPE_SAMPLER, capacity, flags, nullptr}, {}, {}};
|
||||
|
||||
if (sampledImage) {
|
||||
Require(elementWords == 8, "guest texture descriptor must contain 8 dwords");
|
||||
Require(binding.imageShape.has_value(), "guest image binding is missing an image shape");
|
||||
Require(context.detiler != nullptr, "device texture detiler is unavailable");
|
||||
Require(context.textureCache != nullptr, "device texture cache is unavailable");
|
||||
Require(binding.count <= context.limits.maxPerStageDescriptorSampledImages, "shader sampled-image descriptors exceed per-stage limits");
|
||||
Require(capacity <= context.limits.maxPerStageDescriptorSampledImages, "shader sampled-image descriptors exceed per-stage limits");
|
||||
plannedSampledImages += binding.count;
|
||||
Require(plannedSampledImages <= context.limits.maxDescriptorSetSampledImages, "pipeline sampled-image descriptors exceed device limits");
|
||||
} else {
|
||||
Require(elementWords == 4, "guest sampler descriptor must contain 4 dwords");
|
||||
Require(binding.count <= context.limits.maxPerStageDescriptorSamplers, "shader sampler descriptors exceed per-stage limits");
|
||||
Require(capacity <= context.limits.maxPerStageDescriptorSamplers, "shader sampler descriptors exceed per-stage limits");
|
||||
Require(binding.samplerDepthCompare.size() == binding.count, "guest sampler binding is missing depth comparison metadata");
|
||||
Require(binding.samplerUnnormalized.empty() || binding.samplerUnnormalized.size() == binding.count, "guest sampler binding has unnormalized coordinate metadata of another size");
|
||||
for (std::uint32_t element = 0; element < binding.count; ++element) {
|
||||
|
|
@ -2452,7 +2612,7 @@ bool ShaderResources::precollectImages() {
|
|||
const auto words = std::span<const std::uint32_t>(binding.guestDescriptor).subspan(static_cast<std::size_t>(element) * elementWords, elementWords);
|
||||
ImageRecord record;
|
||||
record.sampled = binding.kind == ShaderRecompiler::DescriptorKind::SampledImage;
|
||||
try {
|
||||
if (words[0] != 0u || (words[1] & 0xffu) != 0u) {
|
||||
record.resource = DecodeTextureResource(words);
|
||||
record.guestBytes = DescribeSurface(record.resource).guestBytes;
|
||||
record.generation = GuestMemory::CollectWrites(record.resource.baseAddress, static_cast<std::size_t>(record.guestBytes));
|
||||
|
|
@ -2471,8 +2631,6 @@ bool ShaderResources::precollectImages() {
|
|||
counters.records.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
} catch (const std::exception&) {
|
||||
record.decoded = false;
|
||||
}
|
||||
imageRecords.push_back(std::move(record));
|
||||
}
|
||||
|
|
@ -2545,6 +2703,11 @@ void ShaderResources::resolveImageBinding(const ShaderRecompiler::DescriptorBind
|
|||
for (std::uint32_t element = 0; element < binding.count; ++element) {
|
||||
const auto words = std::span<const std::uint32_t>(binding.guestDescriptor).subspan(static_cast<std::size_t>(element) * elementWords, elementWords);
|
||||
const auto* record = nextRecord();
|
||||
if (words[0] == 0u && (words[1] & 0xffu) == 0u) {
|
||||
item.imageAllocations.push_back(std::numeric_limits<std::size_t>::max());
|
||||
--plannedSampledImages;
|
||||
continue;
|
||||
}
|
||||
const auto resource = record != nullptr && record->decoded ? record->resource : DecodeTextureResource(words);
|
||||
const bool firstLayer = binding.imageShape == ShaderRecompiler::DescriptorImageShape::Image2D && resource.dimension == TextureDimension::k2DArray;
|
||||
if (!firstLayer && !MatchesGuestDimension(*binding.imageShape, resource.dimension)) throw std::runtime_error("AGC graphics: guest texture dimension disagrees with the shader's declared image shape (shape " + std::to_string(static_cast<int>(*binding.imageShape)) + ", dimension " + std::to_string(static_cast<int>(resource.dimension)) + ")");
|
||||
|
|
@ -2580,6 +2743,11 @@ void ShaderResources::resolveImageBinding(const ShaderRecompiler::DescriptorBind
|
|||
if (sameAsPrevious) ++mipOffset;
|
||||
else mipOffset = 0;
|
||||
const auto* record = nextRecord();
|
||||
if (words[0] == 0u && (words[1] & 0xffu) == 0u) {
|
||||
item.imageAllocations.push_back(std::numeric_limits<std::size_t>::max());
|
||||
--plannedStorageImages;
|
||||
continue;
|
||||
}
|
||||
const auto resource = record != nullptr && record->decoded ? record->resource : DecodeTextureResource(words);
|
||||
const bool firstLayer = binding.imageShape == ShaderRecompiler::DescriptorImageShape::Image2D && resource.dimension == TextureDimension::k2DArray;
|
||||
if (binding.imageShape.has_value() && !firstLayer && !MatchesGuestDimension(*binding.imageShape, resource.dimension)) throw std::runtime_error("AGC graphics: guest storage texture dimension disagrees with the shader's declared image shape (shape " + std::to_string(static_cast<int>(*binding.imageShape)) + ", dimension " + std::to_string(static_cast<int>(resource.dimension)) + ")");
|
||||
|
|
@ -2643,12 +2811,9 @@ std::vector<std::pair<std::uint64_t, std::uint64_t>> ShaderResources::PresyncSur
|
|||
// Stage A has not run (an address-based build, or the pass is off): decoded from the shader.
|
||||
if (deferredCompute.program == nullptr) return surfaces;
|
||||
forEachImageElement(*deferredCompute.program, [&](const ShaderRecompiler::DescriptorBinding& binding, std::uint32_t, std::span<const std::uint32_t> words) {
|
||||
try {
|
||||
const auto resource = DecodeTextureResource(words);
|
||||
consider(resource.format, resource.baseAddress, DescribeSurface(resource).guestBytes, binding.kind == ShaderRecompiler::DescriptorKind::SampledImage);
|
||||
} catch (const std::exception&) {
|
||||
// Stage B reports the bad descriptor.
|
||||
}
|
||||
if (words[0] == 0u && (words[1] & 0xffu) == 0u) return;
|
||||
const auto resource = DecodeTextureResource(words);
|
||||
consider(resource.format, resource.baseAddress, DescribeSurface(resource).guestBytes, binding.kind == ShaderRecompiler::DescriptorKind::SampledImage);
|
||||
});
|
||||
return surfaces;
|
||||
}
|
||||
|
|
@ -2714,7 +2879,7 @@ std::optional<std::vector<ShaderResources::MovedBuffer>> ShaderResources::MovedR
|
|||
const bool written = element >= binding.bufferWritten.size() || binding.bufferWritten[element];
|
||||
if (written || empty || item.written || size > context.limits.maxStorageBufferRange) return std::nullopt;
|
||||
const auto begin = address - item.adjustment;
|
||||
const auto bytes = static_cast<std::size_t>(size) + item.adjustment;
|
||||
const auto bytes = static_cast<std::size_t>(GuestBufferMemory::ViewBytes(size, item.adjustment));
|
||||
if (!GuestMemory::Accessible(reinterpret_cast<const void*>(begin), bytes)) return std::nullopt;
|
||||
if (guestMemory.WritesOverlap(begin, bytes) || recorder.PendingWriteOverlaps(begin, bytes) || PendingStorageOverlaps(begin, bytes, nullptr) || AnyShadowedOverlaps(begin, bytes)) return std::nullopt;
|
||||
moved.push_back({index, address, static_cast<std::size_t>(size)});
|
||||
|
|
@ -2753,7 +2918,7 @@ std::shared_ptr<ShaderResources::DrawBindings> ShaderResources::PrepareDrawBindi
|
|||
if (!direct || recorder.PendingWriteOverlaps(item.address, item.size)) continue;
|
||||
}
|
||||
const auto begin = address - item.adjustment;
|
||||
const auto bytes = size + item.adjustment;
|
||||
const auto bytes = static_cast<std::size_t>(GuestBufferMemory::ViewBytes(size, item.adjustment));
|
||||
const auto registryGeneration = GuestAllocations::GuestAllocationsGeneration_nid_postfix();
|
||||
const auto generation = GuestMemory::CollectWrites(begin, bytes);
|
||||
auto buffer = recorder.ReusableDrawSnapshot(begin, bytes);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#include "BdaAbi.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Pipeline.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/PipelineSpecialization.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/VertexInput.hpp"
|
||||
#include <spirv/unified1/spirv.hpp>
|
||||
#include <algorithm>
|
||||
|
|
@ -132,6 +133,7 @@ Module Inspect(const CompiledShader& compiled, const State& state, const VkPhysi
|
|||
using Stage = ShaderRecompiler::ShaderStage;
|
||||
Require(compiled.program != nullptr, "missing compiled shader");
|
||||
const auto& shader = *compiled.program;
|
||||
PipelineSpecialization::Validate(shader);
|
||||
const auto stage = compiled.stage;
|
||||
const bool vertex = stage == Stage::Vertex || stage == Stage::Local;
|
||||
const bool fragment = stage == Stage::Fragment;
|
||||
|
|
@ -181,6 +183,7 @@ Module Inspect(const CompiledShader& compiled, const State& state, const VkPhysi
|
|||
case spv::CapabilityGroupNonUniform: subgroupOperations = VK_SUBGROUP_FEATURE_BASIC_BIT; break;
|
||||
case spv::CapabilityGroupNonUniformBallot: subgroupOperations = VK_SUBGROUP_FEATURE_BASIC_BIT | VK_SUBGROUP_FEATURE_BALLOT_BIT; break;
|
||||
case spv::CapabilityGroupNonUniformShuffle: subgroupOperations = VK_SUBGROUP_FEATURE_BASIC_BIT | VK_SUBGROUP_FEATURE_SHUFFLE_BIT; break;
|
||||
case spv::CapabilityGroupNonUniformArithmetic: subgroupOperations = VK_SUBGROUP_FEATURE_BASIC_BIT | VK_SUBGROUP_FEATURE_ARITHMETIC_BIT; break;
|
||||
default: break;
|
||||
}
|
||||
const bool isSubgroupCapability = subgroupOperations != 0;
|
||||
|
|
@ -207,6 +210,10 @@ Module Inspect(const CompiledShader& compiled, const State& state, const VkPhysi
|
|||
mesh &&
|
||||
capability == spv::CapabilityMeshShadingEXT;
|
||||
|
||||
const bool isInterlockCapability =
|
||||
fragment &&
|
||||
capability == spv::CapabilityFragmentShaderPixelInterlockEXT;
|
||||
|
||||
// Enabled unconditionally or by the device setup in VulkanDevice.
|
||||
const bool isFeatureCapability =
|
||||
capability == spv::CapabilitySampled1D ||
|
||||
|
|
@ -215,6 +222,8 @@ Module Inspect(const CompiledShader& compiled, const State& state, const VkPhysi
|
|||
capability == spv::CapabilityImageQuery ||
|
||||
(fragment && geometryShader && capability == spv::CapabilityGeometry) ||
|
||||
(fragment && sampleRateShading && capability == spv::CapabilitySampleRateShading) ||
|
||||
capability == spv::CapabilityImageMSArray ||
|
||||
capability == spv::CapabilityStorageImageMultisample ||
|
||||
capability == spv::CapabilityStorageImageWriteWithoutFormat ||
|
||||
capability == spv::CapabilityStorageImageReadWithoutFormat ||
|
||||
capability == spv::CapabilityInt64 ||
|
||||
|
|
@ -241,6 +250,7 @@ Module Inspect(const CompiledShader& compiled, const State& state, const VkPhysi
|
|||
isBdaCapability ||
|
||||
isTessellationCapability ||
|
||||
isMeshCapability ||
|
||||
isInterlockCapability ||
|
||||
isFeatureCapability ||
|
||||
isDescriptorIndexingCapability ||
|
||||
(imageInt64Atomics && (capability == spv::CapabilityInt64Atomics || capability == spv::CapabilityInt64ImageEXT)),
|
||||
|
|
@ -256,6 +266,10 @@ Module Inspect(const CompiledShader& compiled, const State& state, const VkPhysi
|
|||
const auto end = std::find(text, text + bytes.size(), '\0');
|
||||
Require(end != text + bytes.size(), "unterminated SPIR-V extension");
|
||||
const std::string_view extension(text, static_cast<std::size_t>(end - text));
|
||||
if (extension == "SPV_EXT_fragment_shader_interlock") {
|
||||
Require(fragment, "SPV_EXT_fragment_shader_interlock requires a fragment shader");
|
||||
break;
|
||||
}
|
||||
if (extension == "SPV_KHR_fragment_shader_barycentric") {
|
||||
Require(fragment && fragmentShaderBarycentric, "SPV_KHR_fragment_shader_barycentric requires enabled fragmentShaderBarycentric in a fragment shader");
|
||||
break;
|
||||
|
|
@ -267,7 +281,6 @@ Module Inspect(const CompiledShader& compiled, const State& state, const VkPhysi
|
|||
case spv::OpDecorationGroup:
|
||||
case spv::OpGroupDecorate:
|
||||
case spv::OpGroupMemberDecorate:
|
||||
case spv::OpSpecConstant:
|
||||
case spv::OpSpecConstantTrue:
|
||||
case spv::OpSpecConstantFalse:
|
||||
case spv::OpSpecConstantComposite:
|
||||
|
|
@ -345,6 +358,8 @@ Module Inspect(const CompiledShader& compiled, const State& state, const VkPhysi
|
|||
case spv::OpConstant:
|
||||
if (count == 4) module.constants.emplace(instruction[2], instruction[3]);
|
||||
break;
|
||||
case spv::OpSpecConstant:
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
cursor += count;
|
||||
|
|
@ -376,7 +391,7 @@ Module Inspect(const CompiledShader& compiled, const State& state, const VkPhysi
|
|||
mode(spv::ExecutionModeVertexOrderCw, {});
|
||||
Require(module.modes.size() == 3, "unsupported tessellation-evaluation execution mode");
|
||||
} else if (fragment) {
|
||||
for (const auto& [name, operands] : module.modes) Require(operands.empty() && (name == spv::ExecutionModeOriginUpperLeft || name == spv::ExecutionModeEarlyFragmentTests || name == spv::ExecutionModeDepthReplacing || name == spv::ExecutionModeDepthLess || name == spv::ExecutionModeDepthGreater), "unsupported fragment execution mode");
|
||||
for (const auto& [name, operands] : module.modes) Require(operands.empty() && (name == spv::ExecutionModeOriginUpperLeft || name == spv::ExecutionModeEarlyFragmentTests || name == spv::ExecutionModeDepthReplacing || name == spv::ExecutionModeDepthLess || name == spv::ExecutionModeDepthGreater || name == spv::ExecutionModePixelInterlockOrderedEXT), "unsupported fragment execution mode");
|
||||
} else Require(module.modes.empty(), "unsupported vertex execution mode");
|
||||
std::set<std::pair<std::uint32_t, std::uint32_t>> descriptors;
|
||||
bool push = false;
|
||||
|
|
@ -451,6 +466,11 @@ Module Inspect(const CompiledShader& compiled, const State& state, const VkPhysi
|
|||
const auto binding = std::find_if(shader.bindings.begin(), shader.bindings.end(), [&](const auto& item) { return item.descriptorSet == key.first && item.binding == key.second; });
|
||||
Require(binding != shader.bindings.end(), "SPIR-V resource is absent from recompiler binding metadata");
|
||||
Require(binding->kind == ShaderRecompiler::DescriptorKind::Sampler || binding->kind == ShaderRecompiler::DescriptorKind::SampledImage || binding->kind == ShaderRecompiler::DescriptorKind::StorageImage, "SPIR-V descriptor type disagrees with recompiler binding metadata");
|
||||
Require(type.size() == 4 && (type[0] & 0xffffu) == spv::OpTypeArray, "typed heap must be declared as a descriptor array");
|
||||
const auto heap = static_cast<ShaderRecompiler::RuntimeAbi::Binding>(binding->binding % static_cast<std::uint32_t>(ShaderRecompiler::RuntimeAbi::Binding::Count));
|
||||
const auto capacity = ShaderRecompiler::RuntimeAbi::HeapCapacity(heap);
|
||||
const auto count = module.constants.find(type[3]);
|
||||
Require(binding->count != 0u && binding->count <= capacity && count != module.constants.end() && count->second == binding->count, "typed heap array length disagrees with compact binding");
|
||||
} else {
|
||||
Require(variable.storage == spv::StorageClassStorageBuffer && decoration.set && decoration.binding, "unsupported or unbound shader resource");
|
||||
const auto key = std::make_pair(*decoration.set, *decoration.binding);
|
||||
|
|
@ -460,7 +480,7 @@ Module Inspect(const CompiledShader& compiled, const State& state, const VkPhysi
|
|||
Require(binding->kind == ShaderRecompiler::DescriptorKind::StorageBuffer, "SPIR-V descriptor type disagrees with recompiler binding metadata");
|
||||
Require(!binding->readOnly, "read-only descriptor metadata is unsupported because the recompiler emits no NonWritable decoration");
|
||||
const bool array = binding->role == ShaderRecompiler::DescriptorRole::GuestBuffers;
|
||||
Require(array || (shader.bdaAbiVersion == ShaderRecompiler::BdaAbi::Version && (binding->role == ShaderRecompiler::DescriptorRole::BdaPagetable || binding->role == ShaderRecompiler::DescriptorRole::FaultBuffer)) || binding->role == ShaderRecompiler::DescriptorRole::ShaderData || binding->role == ShaderRecompiler::DescriptorRole::FlattenedSrt, "SPIR-V descriptor role is unsupported");
|
||||
Require(array || (shader.bdaAbiVersion == ShaderRecompiler::BdaAbi::Version && (binding->role == ShaderRecompiler::DescriptorRole::BdaPagetable || binding->role == ShaderRecompiler::DescriptorRole::FaultBuffer)) || binding->role == ShaderRecompiler::DescriptorRole::ShaderData || binding->role == ShaderRecompiler::DescriptorRole::FlattenedSrt || binding->role == ShaderRecompiler::DescriptorRole::Gds, "SPIR-V descriptor role is unsupported");
|
||||
auto blockId = typeId;
|
||||
if (array) {
|
||||
Require(type.size() == 4 && (type[0] & 0xffffu) == spv::OpTypeArray, "guest buffer descriptors must be declared as a descriptor array");
|
||||
|
|
@ -519,7 +539,12 @@ std::set<std::uint32_t> ValidateShaders(std::span<const CompiledShader> shaders,
|
|||
if (i != 0) {
|
||||
for (const auto& [location, signature] : current.inputs) {
|
||||
const auto output = previous.outputs.find(location);
|
||||
Require(output != previous.outputs.end() && output->second == signature, "graphics interfaces disagree at location " + std::to_string(location));
|
||||
if (output == previous.outputs.end() || output->second != signature) {
|
||||
Require(false, "graphics interfaces disagree at location " + std::to_string(location) +
|
||||
" (stage " + std::to_string(i - 1) + " output " +
|
||||
(output == previous.outputs.end() ? "missing" : output->second) +
|
||||
", stage " + std::to_string(i) + " input " + signature + ")");
|
||||
}
|
||||
}
|
||||
}
|
||||
previous = current;
|
||||
|
|
|
|||
|
|
@ -56,6 +56,43 @@ std::string vteMessage(std::uint32_t viewportControl) {
|
|||
return message.str();
|
||||
}
|
||||
|
||||
std::string conservativeMessage(std::uint32_t control) {
|
||||
std::ostringstream message;
|
||||
message << "AGC graphics: PA_SC_CONSERVATIVE_RASTERIZATION_CNTL=0x" << std::hex << control << ": only 0x6000 (off) and 0x6001 (overestimation) are supported";
|
||||
return message.str();
|
||||
}
|
||||
|
||||
void requireConservativeTriangles(bool triangles, const char* primitive, const char* source, std::uint32_t value) {
|
||||
if (triangles) return;
|
||||
std::ostringstream message;
|
||||
message << "AGC graphics: conservative rasterization of " << primitive << " is unsupported (" << source << "=0x" << std::hex << value << "): only triangles are overestimated";
|
||||
throw std::runtime_error(message.str());
|
||||
}
|
||||
|
||||
VkConservativeRasterizationModeEXT decodeConservativeRasterization(const QueueState& queue) {
|
||||
const auto& cx = queue.context;
|
||||
const auto control = read(cx, 0x313);
|
||||
if (control == 0x6000u) return VK_CONSERVATIVE_RASTERIZATION_MODE_DISABLED_EXT;
|
||||
if (control != 0x6001u) throw std::runtime_error(conservativeMessage(control));
|
||||
const auto stages = read(cx, 0x2d5);
|
||||
if ((stages & 0x20u) != 0) {
|
||||
const auto output = read(cx, 0x29b);
|
||||
const auto type = output & 0x3fu;
|
||||
const bool perStream = (output & 0x80000000u) != 0;
|
||||
requireConservativeTriangles(!perStream && type == 2u, perStream ? "per-stream primitive types" : type == 0u ? "points" : type == 1u ? "lines" : "other primitive types", "VGT_GS_OUT_PRIM_TYPE", output);
|
||||
} else if ((stages & 4u) != 0) {
|
||||
const auto parameters = read(cx, 0x2db);
|
||||
const auto domain = parameters & 3u;
|
||||
const auto topology = (parameters >> 5u) & 7u;
|
||||
requireConservativeTriangles((domain == 1u || domain == 2u) && (topology == 2u || topology == 3u), topology == 0u ? "points" : topology == 1u || domain == 0u ? "lines" : "other primitive types", "VGT_TF_PARAM", parameters);
|
||||
} else {
|
||||
const auto primitive = read(queue.userConfig, 0x242, RegisterBank::UserConfig);
|
||||
requireConservativeTriangles(primitive == 4u || primitive == 5u || primitive == 6u, primitive == 1u ? "points" : primitive == 2u || primitive == 3u ? "lines" : primitive == 7u || primitive == 17u ? "rectangles" : "other primitive types", "VGT_PRIMITIVE_TYPE", primitive);
|
||||
}
|
||||
Require((read(cx, 0x1b3) & 0x44u) == 0, "conservative rasterization with centroid interpolation is unsupported");
|
||||
return VK_CONSERVATIVE_RASTERIZATION_MODE_OVERESTIMATE_EXT;
|
||||
}
|
||||
|
||||
// The register rules DecodeState and DrawRejection share (the precheck must reject exactly what
|
||||
// DecodeState would): the masks whose set bits are unsupported, and the depth-control verdict.
|
||||
// Render target index, viewport index and the misc export vector that carries them are accepted but
|
||||
|
|
@ -64,9 +101,10 @@ constexpr std::uint32_t LayerExports = (1u << 18u) | (1u << 19u) | (1u << 21u) |
|
|||
constexpr std::uint32_t DepthControlMask = ~0x007007f0u;
|
||||
// EXEC_ON_HIER_FAIL / EXEC_ON_NOOP / EXEC_IF_OVERLAPPED (bits 9, 10, 17) only force the pixel shader
|
||||
// to run, which it always does here.
|
||||
constexpr std::uint32_t ShaderControlMask = ~(0x0000f870u | 0x00020600u);
|
||||
constexpr std::uint32_t ShaderControlMask = ~(0x0000f870u | 0x00020600u | 0x00010000u);
|
||||
constexpr std::uint32_t PixelStageRunsMask = 0x00020747u;
|
||||
constexpr std::uint32_t AlphaToCoverageMask = ~0x0001ff00u;
|
||||
constexpr std::uint32_t ScanModeMask = ~2u;
|
||||
constexpr std::uint32_t ScanModeMask = ~0x22u;
|
||||
constexpr std::uint32_t ScanControlMask = ~0x06023fffu;
|
||||
constexpr std::uint32_t ScreenOffsetMask = ~0x01ff01ffu;
|
||||
// Bits 26/27 (ZCLIP_NEAR/FAR_DISABLE) become depth clamping; bit 19 selects the [0, 1] clip space.
|
||||
|
|
@ -234,7 +272,8 @@ std::uint32_t effectiveDepthControl(std::uint32_t depthControl) {
|
|||
}
|
||||
|
||||
bool colorControlSupported(std::uint32_t colorControl, bool hasColorTarget) {
|
||||
return colorControl == 0xcc0010u || (!hasColorTarget && (colorControl & ~0x70u) == 0xcc0000u);
|
||||
colorControl &= ~1u;
|
||||
return colorControl == 0xcc0010u || ((colorControl >> 4u) & 7u) == 0u || (!hasColorTarget && (colorControl & ~0x70u) == 0xcc0000u);
|
||||
}
|
||||
|
||||
std::string colorControlMessage(std::uint32_t colorControl) {
|
||||
|
|
@ -292,12 +331,13 @@ DecodedColorFormat DecodeColorFormat(std::uint32_t format, std::uint32_t number,
|
|||
};
|
||||
const bool alternate = swap == 1;
|
||||
const auto single = [&](VkFormat vkFormat, std::uint32_t bytes) { return DecodedColorFormat{vkFormat, bytes, static_cast<std::uint8_t>((0xe4u & ~3u) | swap)}; };
|
||||
if (swap > 1 && format != 1 && format != 2 && format != 4) return fail();
|
||||
if (swap > 1 && format != 1 && format != 2 && format != 4 && format != 10 && format != 12) return fail();
|
||||
switch (format) {
|
||||
case 1:
|
||||
if (number == unorm) return single(VK_FORMAT_R8_UNORM, 1);
|
||||
if (number == snorm) return single(VK_FORMAT_R8_SNORM, 1);
|
||||
if (number == uint) return single(VK_FORMAT_R8_UINT, 1);
|
||||
if (number == srgb && swap == 0) return single(VK_FORMAT_R8_SRGB, 1);
|
||||
return fail();
|
||||
case 2:
|
||||
if (number == unorm) return single(VK_FORMAT_R16_UNORM, 2);
|
||||
|
|
@ -330,23 +370,27 @@ DecodedColorFormat DecodeColorFormat(std::uint32_t format, std::uint32_t number,
|
|||
// COLOR_2_10_10_10 keeps red in the low bits, the Vulkan A2B10G10R10 packing.
|
||||
if (number != unorm) return fail();
|
||||
return {alternate ? VK_FORMAT_A2R10G10B10_UNORM_PACK32 : VK_FORMAT_A2B10G10R10_UNORM_PACK32, 4};
|
||||
case 10:
|
||||
if (number == unorm) return {alternate ? VK_FORMAT_B8G8R8A8_UNORM : VK_FORMAT_R8G8B8A8_UNORM, 4};
|
||||
if (number == snorm) return {alternate ? VK_FORMAT_B8G8R8A8_SNORM : VK_FORMAT_R8G8B8A8_SNORM, 4};
|
||||
if (number == srgb) return {alternate ? VK_FORMAT_B8G8R8A8_SRGB : VK_FORMAT_R8G8B8A8_SRGB, 4};
|
||||
case 10: {
|
||||
const auto reversed = static_cast<std::uint8_t>(swap == 2 ? 0x1bu : swap == 3 ? 0x93u : 0xe4u);
|
||||
if (number == unorm) return {alternate ? VK_FORMAT_B8G8R8A8_UNORM : VK_FORMAT_R8G8B8A8_UNORM, 4, reversed};
|
||||
if (number == snorm) return {alternate ? VK_FORMAT_B8G8R8A8_SNORM : VK_FORMAT_R8G8B8A8_SNORM, 4, reversed};
|
||||
if (number == srgb) return {alternate ? VK_FORMAT_B8G8R8A8_SRGB : VK_FORMAT_R8G8B8A8_SRGB, 4, reversed};
|
||||
return fail();
|
||||
}
|
||||
case 11:
|
||||
if (swap != 0) return fail();
|
||||
if (number == floating) return {VK_FORMAT_R32G32_SFLOAT, 8};
|
||||
if (number == uint) return {VK_FORMAT_R32G32_UINT, 8};
|
||||
return fail();
|
||||
case 12:
|
||||
if (swap != 0) return fail();
|
||||
if (number == floating) return {VK_FORMAT_R16G16B16A16_SFLOAT, 8};
|
||||
if (number == unorm) return {VK_FORMAT_R16G16B16A16_UNORM, 8};
|
||||
if (number == snorm) return {VK_FORMAT_R16G16B16A16_SNORM, 8};
|
||||
if (number == uint) return {VK_FORMAT_R16G16B16A16_UINT, 8};
|
||||
case 12: {
|
||||
if (swap == 1) return fail();
|
||||
const auto reversed = static_cast<std::uint8_t>(swap == 2 ? 0x1bu : swap == 3 ? 0x93u : 0xe4u);
|
||||
if (number == floating) return {VK_FORMAT_R16G16B16A16_SFLOAT, 8, reversed};
|
||||
if (number == unorm) return {VK_FORMAT_R16G16B16A16_UNORM, 8, reversed};
|
||||
if (number == snorm) return {VK_FORMAT_R16G16B16A16_SNORM, 8, reversed};
|
||||
if (number == uint) return {VK_FORMAT_R16G16B16A16_UINT, 8, reversed};
|
||||
return fail();
|
||||
}
|
||||
case 14:
|
||||
if (swap != 0) return fail();
|
||||
if (number == floating) return {VK_FORMAT_R32G32B32A32_SFLOAT, 16};
|
||||
|
|
@ -396,7 +440,7 @@ ShaderStages DecodeShaderStages(const QueueState& queue) {
|
|||
APS5_LOG_OUT_DEBUG("DecodeShaderStages value=0x%x primitive=%u path=%u vertexWave=%u", value, primitive, static_cast<unsigned>(result.path), result.vertexWaveSize);
|
||||
if (path == ShaderPath::Vertex) {
|
||||
validate((value & 0x2000u) != 0, "legacy vertex routing without PRIMGEN_EN is unsupported");
|
||||
validate((value & ~0x02402010u) == 0, "unsupported vertex routing, scheduling or wave-ID state");
|
||||
validate((value & ~0x0247a010u) == 0, "unsupported vertex routing, scheduling or wave-ID state");
|
||||
} else if (path == ShaderPath::Tessellation) {
|
||||
validate((value & 0x00600020u) == 0, "wave32 tessellation or geometry amplification is unsupported");
|
||||
validate((value & ~0x0007ed0du) == 0 && (value & 3u) == 1u && ((value >> 3u) & 3u) == 1u, "unsupported tessellation routing");
|
||||
|
|
@ -429,9 +473,17 @@ ShaderStages DecodeShaderStages(const QueueState& queue) {
|
|||
return result;
|
||||
}
|
||||
|
||||
std::string DepthMaintenanceRejection(const QueueState& queue) {
|
||||
const auto control = find(queue.context, 0x000);
|
||||
if (control == queue.context.end() || (control->second & ~0x2063u) == 0) return {};
|
||||
return zeroMessage(0x000, control->second, "DB_RENDER_CONTROL depth copy, resummarize or decompress");
|
||||
}
|
||||
|
||||
State DecodeState(const QueueState& queue) {
|
||||
if (auto reason = DepthMaintenanceRejection(queue); !reason.empty()) throw std::runtime_error(reason);
|
||||
const auto& cx = queue.context;
|
||||
State result{};
|
||||
result.conservativeRasterization = decodeConservativeRasterization(queue);
|
||||
result.stages = DecodeShaderStages(queue);
|
||||
const auto primitive = read(queue.userConfig, 0x242, RegisterBank::UserConfig);
|
||||
APS5_LOG_OUT_DEBUG("DecodeState primitive=%u path=%u vertexWave=%u", primitive, static_cast<unsigned>(result.stages.path), result.stages.vertexWaveSize);
|
||||
|
|
@ -492,7 +544,6 @@ State DecodeState(const QueueState& queue) {
|
|||
Require(read(cx, 0x83) == 0xffffu, "clip rectangles are unsupported");
|
||||
Require((read(cx, 0x8c) & 0xfu) == 0xau, "nonstandard triangle edge rules are unsupported");
|
||||
Require(read(cx, 0x2f9) == 0x2du, "nonstandard pixel center or vertex quantization is unsupported");
|
||||
Require(read(cx, 0x313) == 0x6000u, "conservative rasterization is unsupported");
|
||||
Require(read(cx, 0x30e) == 0xffffffffu && read(cx, 0x30f) == 0xffffffffu, "sample masks are unsupported");
|
||||
const auto viewportControl = read(cx, 0x206);
|
||||
if (viewportControl != 0x43fu) throw std::runtime_error(vteMessage(viewportControl));
|
||||
|
|
@ -512,7 +563,7 @@ State DecodeState(const QueueState& queue) {
|
|||
const auto shaderMask = read(cx, 0x8f);
|
||||
// Channels of targets the pixel shader does not export are never written, so the target mask only
|
||||
// matters where the shader exports.
|
||||
const auto targetMask = read(cx, 0x8e) & shaderMask;
|
||||
const auto targetMask = ColorWriteMask(cx);
|
||||
APS5_LOG_OUT_DEBUG("CB_TARGET_MASK=0x%x CB_SHADER_MASK=0x%x", targetMask, shaderMask);
|
||||
std::vector<std::uint32_t> exportSlots;
|
||||
for (std::uint32_t slot = 0; slot < 8; ++slot) {
|
||||
|
|
@ -523,6 +574,7 @@ State DecodeState(const QueueState& queue) {
|
|||
for (std::uint32_t index = 0; index < exportSlots.size(); ++index) {
|
||||
if (written(exportSlots[index])) exportCount = index + 1;
|
||||
}
|
||||
if (((read(cx, 0x202) >> 4u) & 7u) == 0u) exportCount = 0;
|
||||
result.hasColorTarget = exportCount != 0;
|
||||
APS5_LOG_OUT_DEBUG("hasColorTarget=%u exports=%u", result.hasColorTarget ? 1u : 0u, exportCount);
|
||||
|
||||
|
|
@ -598,6 +650,7 @@ State DecodeState(const QueueState& queue) {
|
|||
if (((exportedMask >> ((mapping >> (2u * component)) & 3u)) & 1u) != 0) state.colorWriteMask |= 1u << component;
|
||||
}
|
||||
state.blendEnable = (blend >> 30u) & 1u;
|
||||
if (state.blendEnable && (mapping == 0x1bu || mapping == 0x93u)) throw std::runtime_error("AGC graphics: blending into a color target with a reversed component order is not implemented");
|
||||
if (state.blendEnable) {
|
||||
Require((read(cx, 0x31c + slot * 0xfu) & 0x10000u) == 0, "blend bypass conflicts with enabled blending");
|
||||
state.srcColorBlendFactor = blendFactor(blend & 0x1fu);
|
||||
|
|
@ -630,6 +683,24 @@ std::array<std::uint8_t, 8> ExportMappings(const State& state) {
|
|||
return mappings;
|
||||
}
|
||||
|
||||
std::size_t CmaskBytes(std::uint32_t width, std::uint32_t height) {
|
||||
constexpr std::size_t metablockWidth = 1024;
|
||||
constexpr std::size_t metablockHeight = 512;
|
||||
constexpr std::size_t metablockBytes = 4096;
|
||||
return ((width + metablockWidth - 1) / metablockWidth) * ((height + metablockHeight - 1) / metablockHeight) * metablockBytes;
|
||||
}
|
||||
|
||||
std::uint32_t ColorWriteMask(const Registers& context) {
|
||||
auto mask = read(context, 0x8e) & read(context, 0x8f);
|
||||
for (std::uint32_t slot = 0; slot < 8; ++slot) {
|
||||
const auto channels = 0xfu << (slot * 4u);
|
||||
if ((mask & channels) == 0) continue;
|
||||
const auto info = find(context, 0x31c + slot * 0xfu);
|
||||
if (info != context.end() && ((info->second >> 2u) & 0x1fu) == 0) mask &= ~channels;
|
||||
}
|
||||
return mask;
|
||||
}
|
||||
|
||||
ColorTarget DecodeColorBuffer(const Registers& cx, std::uint32_t slot) {
|
||||
const auto stride = slot * 0xfu;
|
||||
ColorTarget color{};
|
||||
|
|
@ -641,7 +712,7 @@ ColorTarget DecodeColorBuffer(const Registers& cx, std::uint32_t slot) {
|
|||
const auto format = (info >> 2u) & 0x1fu;
|
||||
const auto decoded = DecodeColorFormat(format, number, swap);
|
||||
// ROUND_MODE (bit 18) only affects unorm rounding. With DCC_ENABLE (bit 28) the target is written
|
||||
if ((info & ~(0x00039f7cu | 0x00040000u | 0x10000000u)) != 0) throw std::runtime_error("AGC graphics: color compression, DCC, endian conversion, nonstandard rounding or color optimization is unsupported (CB_COLOR_INFO 0x" + [&] { char text[16]; std::snprintf(text, sizeof(text), "%08x", info); return std::string(text); }() + ")");
|
||||
if ((info & ~(0x00039f7cu | 0x00040000u | 0x10000000u | 0x00002000u)) != 0) throw std::runtime_error("AGC graphics: color compression, DCC, endian conversion, nonstandard rounding or color optimization is unsupported (CB_COLOR_INFO 0x" + [&] { char text[16]; std::snprintf(text, sizeof(text), "%08x", info); return std::string(text); }() + ")");
|
||||
Require((info & 0x8000u) != 0 || number == 7 || number == 4 || number == 5, "unclamped normalized color is unsupported");
|
||||
const auto view = read(cx, 0x31b + stride);
|
||||
Require((view & ~0x3fffffffu) == 0, "reserved CB_COLOR_VIEW bits are set");
|
||||
|
|
@ -689,11 +760,19 @@ ColorTarget DecodeColorBuffer(const Registers& cx, std::uint32_t slot) {
|
|||
const auto clear = find(cx, 0x323 + word + stride);
|
||||
color.clearWords[word] = clear == cx.end() ? 0u : clear->second;
|
||||
}
|
||||
if ((info & 0x2000u) != 0) {
|
||||
Require(maxMip == 0 && !volume && slice == 0, "CMASK fast clears of a mipmapped, 3D or array color target are unsupported");
|
||||
const auto cmaskHigh = find(cx, 0x398 + slot);
|
||||
color.cmaskAddress = ((cmaskHigh == cx.end() ? 0ull : static_cast<std::uint64_t>(cmaskHigh->second & 0xffu)) << 40u) | (static_cast<std::uint64_t>(read(cx, 0x31f + stride)) << 8u);
|
||||
Require(color.cmaskAddress != 0, "CMASK fast clears without a CMASK address are unsupported");
|
||||
color.cmaskBytes = CmaskBytes(color.extent.width, color.extent.height);
|
||||
}
|
||||
if ((info & 0x10000000u) != 0) {
|
||||
if (maxMip == 0) {
|
||||
const auto dccHigh = find(cx, 0x3a8 + slot);
|
||||
color.dccAddress = ((dccHigh == cx.end() ? 0ull : static_cast<std::uint64_t>(dccHigh->second & 0xffu)) << 40u) | (static_cast<std::uint64_t>(read(cx, 0x325 + stride)) << 8u);
|
||||
color.dccAlphaOnMsb = DccAlphaOnMsb(color.format, swap);
|
||||
color.dccPipeAligned = ((attrib3 >> 30u) & 1u) != 0;
|
||||
} else {
|
||||
static bool reported = false;
|
||||
if (!reported) {
|
||||
|
|
@ -743,6 +822,7 @@ std::optional<ColorMetadataPass> DecodeColorMetadataPass(const QueueState& queue
|
|||
}
|
||||
|
||||
std::string DrawRejection(const QueueState& queue, bool indexed) {
|
||||
if (auto reason = DepthMaintenanceRejection(queue); !reason.empty()) return reason;
|
||||
const auto& cx = queue.context;
|
||||
// A register a rule needs that is absent gives no verdict here: DecodeState reports it.
|
||||
const auto value = [&](const Registers& registers, std::uint32_t offset, std::uint32_t& out) {
|
||||
|
|
@ -783,15 +863,15 @@ std::string DrawRejection(const QueueState& queue, bool indexed) {
|
|||
if (value(cx, 0x83, word) && word != 0xffffu) return require(false, "clip rectangles are unsupported");
|
||||
if (value(cx, 0x8c, word) && (word & 0xfu) != 0xau) return require(false, "nonstandard triangle edge rules are unsupported");
|
||||
if (value(cx, 0x2f9, word) && word != 0x2du) return require(false, "nonstandard pixel center or vertex quantization is unsupported");
|
||||
if (value(cx, 0x313, word) && word != 0x6000u) return require(false, "conservative rasterization is unsupported");
|
||||
if (value(cx, 0x313, word) && word != 0x6000u && word != 0x6001u) return conservativeMessage(word);
|
||||
std::uint32_t other = 0;
|
||||
if (value(cx, 0x30e, word) && value(cx, 0x30f, other) && (word != 0xffffffffu || other != 0xffffffffu)) return require(false, "sample masks are unsupported");
|
||||
if (value(cx, 0x206, word) && word != 0x43fu) return vteMessage(word);
|
||||
if (auto reason = nonzero(cx, 0x204, ClipControlMask, "unsupported PA_CL_CLIP_CNTL flags"); !reason.empty()) return reason;
|
||||
std::uint32_t targetMask = 0, shaderMask = 0;
|
||||
if (value(cx, 0x8e, targetMask) && value(cx, 0x8f, shaderMask) && value(cx, 0x202, word) && !colorControlSupported(word, (targetMask & shaderMask) != 0)) return colorControlMessage(word);
|
||||
if (value(cx, 0x8e, targetMask) && value(cx, 0x8f, shaderMask) && value(cx, 0x202, word) && !colorControlSupported(word, ColorWriteMask(cx) != 0)) return colorControlMessage(word);
|
||||
if (auto reason = nonzero(cx, 0x1c4, zFormatSupported(zFormat) ? 0u : ~0u, "depth or sample-mask export"); !reason.empty()) return reason;
|
||||
if (PixelProgramUnset(queue)) return NullPixelProgramRejection(queue);
|
||||
if (PixelProgramSkipped(queue)) return NullPixelProgramRejection(queue);
|
||||
for (const auto offset : {0x1b3u, 0x1b4u, 0x1c5u}) {
|
||||
if (find(cx, offset) != cx.end()) continue;
|
||||
char text[64];
|
||||
|
|
@ -801,17 +881,24 @@ std::string DrawRejection(const QueueState& queue, bool indexed) {
|
|||
return {};
|
||||
}
|
||||
|
||||
bool PixelProgramUnset(const QueueState& queue) {
|
||||
bool PixelProgramSkipped(const QueueState& queue) {
|
||||
const auto low = find(queue.shader, 0x008, RegisterBank::Shader);
|
||||
const auto high = find(queue.shader, 0x009, RegisterBank::Shader);
|
||||
return low != queue.shader.end() && high != queue.shader.end() && low->second == 0 && high->second == 0;
|
||||
if (low != queue.shader.end() && high != queue.shader.end() && low->second == 0 && high->second == 0) return true;
|
||||
const auto& cx = queue.context;
|
||||
const auto targetMask = find(cx, 0x8e);
|
||||
const auto shaderMask = find(cx, 0x8f);
|
||||
const auto zFormat = find(cx, 0x1c4);
|
||||
const auto shaderControl = find(cx, 0x203);
|
||||
if (targetMask == cx.end() || shaderMask == cx.end() || zFormat == cx.end() || shaderControl == cx.end()) return false;
|
||||
return (targetMask->second & shaderMask->second) == 0 && zFormat->second == 0 && (shaderControl->second & PixelStageRunsMask) == 0;
|
||||
}
|
||||
|
||||
std::string NullPixelProgramRejection(const QueueState& queue) {
|
||||
const auto targetMask = find(queue.context, 0x8e);
|
||||
const auto shaderMask = find(queue.context, 0x8f);
|
||||
if (targetMask == queue.context.end() || shaderMask == queue.context.end()) return "AGC graphics: a draw without a pixel program needs CB_TARGET_MASK and CB_SHADER_MASK";
|
||||
if ((targetMask->second & shaderMask->second) != 0) return "AGC graphics: a draw without a pixel program writes color";
|
||||
if (ColorWriteMask(queue.context) != 0) return "AGC graphics: a draw without a pixel program writes color";
|
||||
return {};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -312,9 +312,9 @@ Texture::Texture(const Context& context, TextureDetiler& detiler, const GuestTex
|
|||
VkCommandBuffer commands = VK_NULL_HANDLE;
|
||||
if (recorder != nullptr) {
|
||||
commands = recorder->Commands();
|
||||
recorder->Keep(staging);
|
||||
recorder->Keep(tiled);
|
||||
recorder->Keep(linear);
|
||||
recorder->Keep(staging, static_cast<std::size_t>(guestBytes));
|
||||
recorder->Keep(tiled, static_cast<std::size_t>(guestBytes));
|
||||
recorder->Keep(linear, static_cast<std::size_t>(linearBytes));
|
||||
recorder->Keep(owned);
|
||||
} else {
|
||||
batch = std::make_unique<CommandBatch>(context);
|
||||
|
|
@ -655,6 +655,28 @@ bool StorageClearAvailable(const Context& context, std::uint32_t guestFormat, Dc
|
|||
return IsDccClear(keys) && StorageFormatAvailable(context, guestFormat) && ClearColorFor(StorageFormatForGuest(context, guestFormat), keys, clear);
|
||||
}
|
||||
|
||||
VkFormat AttachmentProxyFormat(const Context& context, VkFormat format) {
|
||||
if (format != VK_FORMAT_R8_SRGB) return VK_FORMAT_UNDEFINED;
|
||||
struct Table {
|
||||
std::mutex mutex;
|
||||
std::unordered_map<VkPhysicalDevice, VkFormat> formats;
|
||||
};
|
||||
static Table table;
|
||||
{
|
||||
std::lock_guard lock(table.mutex);
|
||||
if (const auto found = table.formats.find(context.physical); found != table.formats.end()) return found->second;
|
||||
}
|
||||
const char* forced = std::getenv("APS5_SRGB_ATTACHMENT_PROXY");
|
||||
VkFormatProperties properties{};
|
||||
context.formatProperties(context.physical, format, &properties);
|
||||
constexpr VkFormatFeatureFlags attachment = VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT;
|
||||
const bool proxied = (forced != nullptr && std::strcmp(forced, "1") == 0) || (properties.optimalTilingFeatures & attachment) != attachment;
|
||||
const auto proxy = proxied ? VK_FORMAT_R8G8B8A8_SRGB : VK_FORMAT_UNDEFINED;
|
||||
std::lock_guard lock(table.mutex);
|
||||
table.formats.emplace(context.physical, proxy);
|
||||
return proxy;
|
||||
}
|
||||
|
||||
StorageTexture::StorageTexture(const Context& context, TextureDetiler& detiler, const GuestTextureResource& descriptor, std::uint32_t mipLevel) : context(context), detiler(detiler), descriptor(descriptor) {
|
||||
static const bool profile = std::getenv("APS5_PROFILE_DRAW") != nullptr;
|
||||
PhaseTimer timer;
|
||||
|
|
@ -1006,6 +1028,95 @@ VkImageView StorageTexture::AttachmentView(VkFormat format, std::uint32_t mip, s
|
|||
return created;
|
||||
}
|
||||
|
||||
VkImageView StorageTexture::AttachmentProxyView() {
|
||||
if (proxyView != VK_NULL_HANDLE) return proxyView;
|
||||
Require(storageFormat == VK_FORMAT_R8_UNORM && descriptor.dimension == TextureDimension::k2D && descriptor.mipCount == 1 && geometry.imageLayers == 1, "an RGBA8_SRGB attachment proxy is only modeled for a single-mip 2D R8 surface");
|
||||
try {
|
||||
VkImageCreateInfo imageInfo{VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO};
|
||||
imageInfo.flags = VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT;
|
||||
imageInfo.imageType = VK_IMAGE_TYPE_2D;
|
||||
imageInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
|
||||
imageInfo.extent = {descriptor.width, descriptor.height, 1};
|
||||
imageInfo.mipLevels = 1;
|
||||
imageInfo.arrayLayers = 1;
|
||||
imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
|
||||
imageInfo.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
||||
imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
Check(context.Function<PFN_vkCreateImage>("vkCreateImage")(context.device, &imageInfo, nullptr, &proxyImage), "vkCreateImage attachment proxy");
|
||||
VkMemoryRequirements requirements{};
|
||||
context.Function<PFN_vkGetImageMemoryRequirements>("vkGetImageMemoryRequirements")(context.device, proxyImage, &requirements);
|
||||
VkMemoryAllocateInfo allocation{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO};
|
||||
allocation.allocationSize = requirements.size;
|
||||
allocation.memoryTypeIndex = context.MemoryType(requirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
||||
Check(context.Function<PFN_vkAllocateMemory>("vkAllocateMemory")(context.device, &allocation, nullptr, &proxyMemory), "vkAllocateMemory attachment proxy");
|
||||
Check(context.Function<PFN_vkBindImageMemory>("vkBindImageMemory")(context.device, proxyImage, proxyMemory, 0), "vkBindImageMemory attachment proxy");
|
||||
VkImageViewCreateInfo viewInfo{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO};
|
||||
viewInfo.image = proxyImage;
|
||||
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||||
viewInfo.format = VK_FORMAT_R8G8B8A8_SRGB;
|
||||
viewInfo.components = {VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY};
|
||||
viewInfo.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
|
||||
Check(context.Function<PFN_vkCreateImageView>("vkCreateImageView")(context.device, &viewInfo, nullptr, &proxyView), "vkCreateImageView attachment proxy");
|
||||
} catch (...) {
|
||||
if (proxyImage) context.Function<PFN_vkDestroyImage>("vkDestroyImage")(context.device, proxyImage, nullptr);
|
||||
if (proxyMemory) context.Function<PFN_vkFreeMemory>("vkFreeMemory")(context.device, proxyMemory, nullptr);
|
||||
proxyImage = VK_NULL_HANDLE;
|
||||
proxyMemory = VK_NULL_HANDLE;
|
||||
proxyView = VK_NULL_HANDLE;
|
||||
throw;
|
||||
}
|
||||
return proxyView;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
void blitWhole(const Context& context, VkCommandBuffer commands, VkImage source, VkImage destination, std::uint32_t width, std::uint32_t height) {
|
||||
VkImageBlit region{};
|
||||
region.srcSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1};
|
||||
region.srcOffsets[1] = {static_cast<std::int32_t>(width), static_cast<std::int32_t>(height), 1};
|
||||
region.dstSubresource = region.srcSubresource;
|
||||
region.dstOffsets[1] = region.srcOffsets[1];
|
||||
context.Function<PFN_vkCmdBlitImage>("vkCmdBlitImage")(commands, source, VK_IMAGE_LAYOUT_GENERAL, destination, VK_IMAGE_LAYOUT_GENERAL, 1, ®ion, VK_FILTER_NEAREST);
|
||||
}
|
||||
|
||||
VkImageMemoryBarrier proxyBarrier(VkImage image, VkAccessFlags source, VkAccessFlags destination, VkImageLayout from, VkImageLayout to) {
|
||||
VkImageMemoryBarrier barrier{VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER};
|
||||
barrier.srcAccessMask = source;
|
||||
barrier.dstAccessMask = destination;
|
||||
barrier.oldLayout = from;
|
||||
barrier.newLayout = to;
|
||||
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barrier.image = image;
|
||||
barrier.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
|
||||
return barrier;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void StorageTexture::RecordAttachmentProxyLoad(VkCommandBuffer commands, VkImageLayout attachmentLayout) const {
|
||||
Require(proxyView != VK_NULL_HANDLE, "the storage image has no attachment proxy");
|
||||
const auto barrier = context.Resolved(&DeviceFunctions::cmdPipelineBarrier, "vkCmdPipelineBarrier");
|
||||
const VkMemoryBarrier written{VK_STRUCTURE_TYPE_MEMORY_BARRIER, nullptr, VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT};
|
||||
const auto discard = proxyBarrier(proxyImage, 0, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL);
|
||||
barrier(commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 1, &written, 0, nullptr, 1, &discard);
|
||||
blitWhole(context, commands, image, proxyImage, descriptor.width, descriptor.height);
|
||||
const auto attach = proxyBarrier(proxyImage, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_IMAGE_LAYOUT_GENERAL, attachmentLayout);
|
||||
barrier(commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, 0, 0, nullptr, 0, nullptr, 1, &attach);
|
||||
}
|
||||
|
||||
void StorageTexture::RecordAttachmentProxyStore(VkCommandBuffer commands, VkImageLayout attachmentLayout) const {
|
||||
Require(proxyView != VK_NULL_HANDLE, "the storage image has no attachment proxy");
|
||||
const auto barrier = context.Resolved(&DeviceFunctions::cmdPipelineBarrier, "vkCmdPipelineBarrier");
|
||||
const auto detach = proxyBarrier(proxyImage, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT, attachmentLayout, VK_IMAGE_LAYOUT_GENERAL);
|
||||
barrier(commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &detach);
|
||||
blitWhole(context, commands, proxyImage, image, descriptor.width, descriptor.height);
|
||||
const VkMemoryBarrier stored{VK_STRUCTURE_TYPE_MEMORY_BARRIER, nullptr, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT};
|
||||
barrier(commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 1, &stored, 0, nullptr, 0, nullptr);
|
||||
}
|
||||
|
||||
VkImageView StorageTexture::View(std::uint32_t mip) {
|
||||
if (mip == defaultMip) return view;
|
||||
const auto found = extraViews.find(mip);
|
||||
|
|
@ -1145,7 +1256,7 @@ bool StorageTexture::Refresh() {
|
|||
tracked = GuestMemory::ChangedBlocks(descriptor.baseAddress, static_cast<std::size_t>(guestBytes), generations, stampedBlocks, cpuBlocks);
|
||||
};
|
||||
const auto keysStart = profile ? std::chrono::steady_clock::now() : std::chrono::steady_clock::time_point{};
|
||||
keys = ProvedClearKeys(descriptor, guestBytes, keyProof);
|
||||
keys = ProvedKeys();
|
||||
if (profile && descriptor.dccAddress != 0) LookupOutcomes::Add(LookupOutcomes::DccScan, keysStart);
|
||||
if (keys != uploadedKeys) {
|
||||
changed.assign(trackedLayers, true);
|
||||
|
|
@ -1323,6 +1434,15 @@ bool StorageTexture::Refresh() {
|
|||
return false;
|
||||
}
|
||||
|
||||
DccKeys StorageTexture::ProvedKeys() const {
|
||||
std::optional<DccKeys> keys;
|
||||
if (descriptor.dccAddress != 0 && uploadedKeys == DccKeys::Uncompressed) keys = WaitForKeyWriters(descriptor, guestBytes);
|
||||
if (keys.has_value()) keyProof = {};
|
||||
else keys = ProvedClearKeys(descriptor, guestBytes, keyProof);
|
||||
if (IsDccClear(filledKeys) && *keys != filledKeys && !anyLayerPending()) filledKeys = DccKeys::Uncompressed;
|
||||
return *keys;
|
||||
}
|
||||
|
||||
bool StorageTexture::ServesKeysAt(std::uint64_t dccAddress) const {
|
||||
const bool locked = GuestMemory::GpuMutex().HeldByThisThread();
|
||||
const auto readFollowed = [&] {
|
||||
|
|
@ -1352,7 +1472,7 @@ void traceKeyStore(const char* path, const GuestTextureResource& descriptor, std
|
|||
static const bool trace = std::getenv("APS5_TRACE_DCC_KEYS") != nullptr;
|
||||
if (!trace || descriptor.dccAddress == 0) return;
|
||||
const auto packet = GuestMemory::CurrentPacket();
|
||||
std::fprintf(stderr, "[dcc-keys] uncompressed store by %s (%s) for surface 0x%llx+0x%llx: keys 0x%llx+0x%llx (packet 0x%x queue 0x%x)\n", path, flushReason != nullptr ? flushReason : "?", static_cast<unsigned long long>(descriptor.baseAddress), static_cast<unsigned long long>(guestBytes), static_cast<unsigned long long>(descriptor.dccAddress), static_cast<unsigned long long>(guestBytes / 256), packet.opcode, packet.queue);
|
||||
std::fprintf(stderr, "[dcc-keys] uncompressed store by %s (%s) for surface 0x%llx+0x%llx: keys 0x%llx+0x%llx (packet 0x%x queue 0x%x)\n", path, flushReason != nullptr ? flushReason : "?", static_cast<unsigned long long>(descriptor.baseAddress), static_cast<unsigned long long>(guestBytes), static_cast<unsigned long long>(descriptor.dccAddress), static_cast<unsigned long long>(DccKeyCount(descriptor, guestBytes)), packet.opcode, packet.queue);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1479,7 +1599,7 @@ void StorageTexture::upload(const std::vector<bool>* layers) {
|
|||
recorder->FlushStoresOverlapping(descriptor.baseAddress, static_cast<std::size_t>(guestBytes));
|
||||
commands = recorder->Commands();
|
||||
timing = recorder->BeginGpuTiming(Recorder::CommandClass::StorageUpload);
|
||||
recorder->Keep(linear);
|
||||
recorder->Keep(linear, linear->Size());
|
||||
// The image itself must outlive the recorded copy: the cache may evict it right after.
|
||||
if (auto self = weak_from_this().lock()) recorder->Keep(std::move(self));
|
||||
// The detile reads the tiled bytes from the import when the batch runs.
|
||||
|
|
@ -1808,7 +1928,7 @@ std::uint64_t StorageTexture::uploadWindows(const HostImport& import, std::span<
|
|||
for (const auto& [begin, end] : reads) recorder->FlushStoresOverlapping(begin, static_cast<std::size_t>(end - begin));
|
||||
commands = recorder->Commands();
|
||||
timing = recorder->BeginGpuTiming(Recorder::CommandClass::StorageUpload);
|
||||
recorder->Keep(linear);
|
||||
recorder->Keep(linear, linear->Size());
|
||||
for (const auto& source : sources) {
|
||||
if (source.slab != nullptr) recorder->Keep(source.slab);
|
||||
}
|
||||
|
|
@ -1974,8 +2094,8 @@ std::uint64_t StorageTexture::writeBackWindows(const HostImport& import, std::sp
|
|||
recorder->FlushStoresOverlapping(flushBegin, static_cast<std::size_t>(flushEnd - flushBegin));
|
||||
commands = recorder->Commands(&covered);
|
||||
timing = recorder->BeginGpuTiming(Recorder::CommandClass::StorageWriteBack);
|
||||
recorder->Keep(linear);
|
||||
recorder->Keep(tiledScratch);
|
||||
recorder->Keep(linear, linear->Size());
|
||||
recorder->Keep(tiledScratch, tiledScratch->Size());
|
||||
for (const auto& pieces : slabPieces) recorder->Keep(pieces.slab);
|
||||
for (const auto& slab : padding.slabs) recorder->Keep(slab);
|
||||
if (auto self = weak_from_this().lock()) recorder->Keep(std::move(self));
|
||||
|
|
@ -2093,6 +2213,11 @@ bool StorageTexture::overlaps(std::uint64_t address, std::size_t bytes) const {
|
|||
}
|
||||
|
||||
void StorageTexture::MarkDirty() {
|
||||
if (descriptor.dccAddress != 0 && IsDccClear(uploadedKeys) && !IsDccClear(filledKeys)) {
|
||||
traceKeyStore("first write", descriptor, guestBytes);
|
||||
MarkDccUncompressed(context, descriptor.dccAddress, guestBytes, DccKeyCount(descriptor, guestBytes));
|
||||
uploadedKeys = DccKeys::Uncompressed;
|
||||
}
|
||||
markLayersPending(0, trackedLayers);
|
||||
}
|
||||
|
||||
|
|
@ -3360,7 +3485,7 @@ void StorageTexture::writeBackLayers(const std::vector<bool>& layers) {
|
|||
if (profile) Profile().storageGpu += timer.lap();
|
||||
originalValid = false;
|
||||
traceKeyStore("block write-back", descriptor, guestBytes);
|
||||
if (!IsDccClear(filledKeys)) MarkDccUncompressed(context, descriptor.dccAddress, guestBytes);
|
||||
if (!IsDccClear(filledKeys)) MarkDccUncompressed(context, descriptor.dccAddress, guestBytes, DccKeyCount(descriptor, guestBytes));
|
||||
uploadedKeys = DccKeys::Uncompressed;
|
||||
for (const auto& [from, to] : keep) GuestMemory::MarkWritten(from, static_cast<std::size_t>(to - from));
|
||||
settle(true);
|
||||
|
|
@ -3391,8 +3516,8 @@ void StorageTexture::writeBackLayers(const std::vector<bool>& layers) {
|
|||
recorder->FlushStoresOverlapping(firstStored, static_cast<std::size_t>(lastStored - firstStored));
|
||||
commands = recorder->Commands();
|
||||
timing = recorder->BeginGpuTiming(Recorder::CommandClass::StorageWriteBack);
|
||||
recorder->Keep(linear);
|
||||
recorder->Keep(tiledScratch);
|
||||
recorder->Keep(linear, linear->Size());
|
||||
recorder->Keep(tiledScratch, tiledScratch->Size());
|
||||
for (const auto& slab : padding.slabs) recorder->Keep(slab);
|
||||
// The image itself must outlive the recorded retile: the cache may evict it right after.
|
||||
if (auto self = weak_from_this().lock()) recorder->Keep(std::move(self));
|
||||
|
|
@ -3468,7 +3593,7 @@ void StorageTexture::writeBackLayers(const std::vector<bool>& layers) {
|
|||
// metadata is host-imported (no CPU wait for the title's key-writing kernels), else a CPU
|
||||
// store (APS5_CPU_DCC_KEYS=1 keeps the CPU store; see DccMetadata.hpp).
|
||||
traceKeyStore("layer write-back", descriptor, guestBytes);
|
||||
if (!IsDccClear(filledKeys)) MarkDccUncompressed(context, descriptor.dccAddress, guestBytes);
|
||||
if (!IsDccClear(filledKeys)) MarkDccUncompressed(context, descriptor.dccAddress, guestBytes, DccKeyCount(descriptor, guestBytes));
|
||||
uploadedKeys = DccKeys::Uncompressed;
|
||||
for (const auto& [from, to] : keep) GuestMemory::MarkWritten(from, static_cast<std::size_t>(to - from));
|
||||
// The walk covers this surface's pages only: an adjacent image's later CPU write is stamped
|
||||
|
|
@ -3578,7 +3703,7 @@ void StorageTexture::writeBackLayers(const std::vector<bool>& layers) {
|
|||
// The texels now hold the whole image, so later reads must see them rather than a fast clear
|
||||
// (the keys may be host-imported although the texels were not: then a recorded fill, else a CPU store).
|
||||
traceKeyStore("cpu write-back", descriptor, guestBytes);
|
||||
if (!IsDccClear(filledKeys)) MarkDccUncompressed(context, descriptor.dccAddress, guestBytes);
|
||||
if (!IsDccClear(filledKeys)) MarkDccUncompressed(context, descriptor.dccAddress, guestBytes, DccKeyCount(descriptor, guestBytes));
|
||||
uploadedKeys = DccKeys::Uncompressed;
|
||||
// The store above is the only write to these pages, so `original` is current at a fresh
|
||||
// generation, unless blocks were kept for the CPU: then the image is stale there.
|
||||
|
|
@ -3623,6 +3748,9 @@ void StorageTexture::release() noexcept {
|
|||
uintViews.clear();
|
||||
for (const auto& [format, attachment] : attachmentViews) context.Function<PFN_vkDestroyImageView>("vkDestroyImageView")(context.device, attachment, nullptr);
|
||||
attachmentViews.clear();
|
||||
if (proxyView) context.Function<PFN_vkDestroyImageView>("vkDestroyImageView")(context.device, proxyView, nullptr);
|
||||
if (proxyImage) context.Function<PFN_vkDestroyImage>("vkDestroyImage")(context.device, proxyImage, nullptr);
|
||||
if (proxyMemory) context.Function<PFN_vkFreeMemory>("vkFreeMemory")(context.device, proxyMemory, nullptr);
|
||||
if (view) context.Function<PFN_vkDestroyImageView>("vkDestroyImageView")(context.device, view, nullptr);
|
||||
if (image) context.Function<PFN_vkDestroyImage>("vkDestroyImage")(context.device, image, nullptr);
|
||||
if (memory) context.Function<PFN_vkFreeMemory>("vkFreeMemory")(context.device, memory, nullptr);
|
||||
|
|
|
|||
|
|
@ -36,9 +36,16 @@ struct Push {
|
|||
std::uint32_t BlockBytesFor(TextureTileMode tileMode) {
|
||||
switch (tileMode) {
|
||||
case TextureTileMode::kLinear: return 0u;
|
||||
case TextureTileMode::kStandard256B: return 256u;
|
||||
case TextureTileMode::kStandard4KB: return 4096u;
|
||||
case TextureTileMode::kStandard256B:
|
||||
case TextureTileMode::kD256B: return 256u;
|
||||
case TextureTileMode::kStandard4KB:
|
||||
case TextureTileMode::kD4KB:
|
||||
case TextureTileMode::kS4KBX:
|
||||
case TextureTileMode::kD4KBX: return 4096u;
|
||||
case TextureTileMode::kStandard64KB:
|
||||
case TextureTileMode::kD64KB:
|
||||
case TextureTileMode::kS64KBT:
|
||||
case TextureTileMode::kD64KBT:
|
||||
case TextureTileMode::kZ64KBX:
|
||||
case TextureTileMode::kS64KBX:
|
||||
case TextureTileMode::kD64KBX:
|
||||
|
|
@ -108,8 +115,8 @@ VkPipeline TextureDetiler::pipeline(TextureTileMode tileMode, std::uint32_t elem
|
|||
// per-bit XOR equation for the equation family (2); 20-21 override the block extent in elements.
|
||||
std::array<std::uint32_t, 22> values{elementBytes, BlockBytesFor(tileMode), tileMode == TextureTileMode::kLinear ? 0u : 1u, retile ? 1u : 0u};
|
||||
if (thick) {
|
||||
const auto thickMode = tileMode == TextureTileMode::kStandard4KB ? 0x105u : tileMode == TextureTileMode::kStandard64KB ? 0x109u : 0u;
|
||||
Require(thickMode != 0, "3D textures are only detiled from SW_4KB_S or SW_64KB_S");
|
||||
const auto thickMode = tileMode == TextureTileMode::kStandard4KB ? 0x105u : tileMode == TextureTileMode::kStandard64KB ? 0x109u : tileMode == TextureTileMode::kS64KBX ? 0x119u : 0u;
|
||||
Require(thickMode != 0, "3D textures are only detiled thick from SW_4KB_S, SW_64KB_S or SW_64KB_S_X");
|
||||
const auto* equation = FindTextureSwizzleEquation(thickMode, elementBytes);
|
||||
Require(equation != nullptr, "no thick swizzle equation for the element size");
|
||||
values[2] = 2u;
|
||||
|
|
@ -117,7 +124,7 @@ VkPipeline TextureDetiler::pipeline(TextureTileMode tileMode, std::uint32_t elem
|
|||
const auto extent = ThickBlockExtent(tileMode, elementBytes);
|
||||
values[20] = extent[0];
|
||||
values[21] = extent[1];
|
||||
} else if (const auto mode = XorSwizzleMode(tileMode); mode != 0) {
|
||||
} else if (const auto mode = EquationSwizzleMode(tileMode); mode != 0) {
|
||||
const auto* equation = FindTextureSwizzleEquation(mode, elementBytes);
|
||||
if (equation == nullptr) Require(false, "no swizzle equation for tile mode " + std::to_string(mode) + " at " + std::to_string(elementBytes) + " bytes per element");
|
||||
values[2] = 2u;
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ constexpr auto MakeFormatLookupTable() {
|
|||
constexpr auto kFormatLookupTable = MakeFormatLookupTable();
|
||||
|
||||
std::uint32_t remapGuestFormat(std::uint32_t guestFormat) {
|
||||
return guestFormat == 30 || guestFormat == 34 ? 20 : guestFormat;
|
||||
return guestFormat == 30 || guestFormat == 34 || guestFormat == 43 ? 20 : guestFormat;
|
||||
}
|
||||
|
||||
const FormatEntry& findFormatEntry(std::uint32_t guestFormat) {
|
||||
|
|
|
|||
|
|
@ -44,9 +44,16 @@ BlockLayout GetBlockLayout(TextureTileMode tileMode, std::uint32_t bytesPerEleme
|
|||
const auto index = static_cast<std::size_t>(std::countr_zero(bytesPerElement));
|
||||
switch (tileMode) {
|
||||
case TextureTileMode::kLinear: throw std::runtime_error("AGC graphics: GetBlockLayout does not apply to linear tiling");
|
||||
case TextureTileMode::kStandard256B: return {256u, 1u << kLog2BlockThin256B[index].width, 1u << kLog2BlockThin256B[index].height};
|
||||
case TextureTileMode::kStandard4KB: return {4096u, 1u << kLog2BlockThin4KB[index].width, 1u << kLog2BlockThin4KB[index].height};
|
||||
case TextureTileMode::kStandard256B:
|
||||
case TextureTileMode::kD256B: return {256u, 1u << kLog2BlockThin256B[index].width, 1u << kLog2BlockThin256B[index].height};
|
||||
case TextureTileMode::kStandard4KB:
|
||||
case TextureTileMode::kD4KB:
|
||||
case TextureTileMode::kS4KBX:
|
||||
case TextureTileMode::kD4KBX: return {4096u, 1u << kLog2BlockThin4KB[index].width, 1u << kLog2BlockThin4KB[index].height};
|
||||
case TextureTileMode::kStandard64KB:
|
||||
case TextureTileMode::kD64KB:
|
||||
case TextureTileMode::kS64KBT:
|
||||
case TextureTileMode::kD64KBT:
|
||||
case TextureTileMode::kZ64KBX:
|
||||
case TextureTileMode::kS64KBX:
|
||||
case TextureTileMode::kD64KBX:
|
||||
|
|
@ -92,11 +99,18 @@ bool GetMipTailLayout(TextureTileMode tileMode, const BlockLayout& block, std::u
|
|||
const auto index = static_cast<std::size_t>(std::countr_zero(bytesPerElement));
|
||||
switch (tileMode) {
|
||||
case TextureTileMode::kLinear:
|
||||
case TextureTileMode::kStandard256B: return false;
|
||||
case TextureTileMode::kStandard256B:
|
||||
case TextureTileMode::kD256B: return false;
|
||||
case TextureTileMode::kStandard4KB:
|
||||
case TextureTileMode::kD4KB:
|
||||
case TextureTileMode::kS4KBX:
|
||||
case TextureTileMode::kD4KBX:
|
||||
out = MakeMipTailLayout(kMipTailThin4KB[index], block.blockWidth >> 1u, block.blockHeight);
|
||||
return true;
|
||||
case TextureTileMode::kStandard64KB:
|
||||
case TextureTileMode::kD64KB:
|
||||
case TextureTileMode::kS64KBT:
|
||||
case TextureTileMode::kD64KBT:
|
||||
case TextureTileMode::kZ64KBX:
|
||||
case TextureTileMode::kS64KBX:
|
||||
case TextureTileMode::kD64KBX:
|
||||
|
|
@ -245,8 +259,9 @@ std::array<std::uint32_t, 3> ThickBlockExtent(TextureTileMode tileMode, std::uin
|
|||
const auto index = static_cast<std::size_t>(std::countr_zero(bytesPerElement));
|
||||
switch (tileMode) {
|
||||
case TextureTileMode::kStandard4KB: return {1u << thick4KB[index][0], 1u << thick4KB[index][1], 1u << thick4KB[index][2]};
|
||||
case TextureTileMode::kStandard64KB: return {1u << thick64KB[index][0], 1u << thick64KB[index][1], 1u << thick64KB[index][2]};
|
||||
default: throw std::runtime_error("AGC graphics: 3D textures are only supported linear or in SW_4KB_S / SW_64KB_S, not in XOR swizzle " + std::to_string(XorSwizzleMode(tileMode)) + " at " + std::to_string(bytesPerElement) + " bytes per element");
|
||||
case TextureTileMode::kStandard64KB:
|
||||
case TextureTileMode::kS64KBX: return {1u << thick64KB[index][0], 1u << thick64KB[index][1], 1u << thick64KB[index][2]};
|
||||
default: throw std::runtime_error("AGC graphics: thick 3D textures are only supported in SW_4KB_S, SW_64KB_S and SW_64KB_S_X, not in swizzle mode " + std::to_string(EquationSwizzleMode(tileMode)) + " at " + std::to_string(bytesPerElement) + " bytes per element");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -283,7 +298,10 @@ ThickLayout ComputeThickLayout(TextureTileMode tileMode, std::uint32_t format, s
|
|||
Require(width != 0 && height != 0 && depth != 0, "cannot compute layout for a zero-sized 3D texture");
|
||||
Require(mipCount != 0 && mipCount <= 16u, "3D texture mip count is out of range");
|
||||
const auto bytesPerElement = BytesPerElement(format);
|
||||
Require(BlockWidth(format) == 1u && BlockHeight(format) == 1u, "block-compressed 3D textures are not implemented");
|
||||
const auto texelWidth = BlockWidth(format);
|
||||
const auto texelHeight = BlockHeight(format);
|
||||
const auto elementsWidth0 = (width + texelWidth - 1u) / texelWidth;
|
||||
const auto elementsHeight0 = (height + texelHeight - 1u) / texelHeight;
|
||||
ThickLayout result{};
|
||||
result.depth = depth;
|
||||
result.mips.resize(mipCount);
|
||||
|
|
@ -300,7 +318,7 @@ ThickLayout ComputeThickLayout(TextureTileMode tileMode, std::uint32_t format, s
|
|||
const auto effectiveLog2 = log2BlockBytes - (log2BlockBytes - 8u) / 3u;
|
||||
maxTailLevels = effectiveLog2 <= 11u ? 1u + (1u << (effectiveLog2 - 9u)) : effectiveLog2 - 4u;
|
||||
for (std::uint32_t level = 0; mipCount > 1 && level < mipCount; ++level) {
|
||||
if (ShiftCeil(width, level) <= tailWidth && ShiftCeil(height, level) <= tailHeight && mipCount - level <= maxTailLevels) {
|
||||
if (ShiftCeil(elementsWidth0, level) <= tailWidth && ShiftCeil(elementsHeight0, level) <= tailHeight && mipCount - level <= maxTailLevels) {
|
||||
firstTailLevel = level;
|
||||
break;
|
||||
}
|
||||
|
|
@ -311,14 +329,14 @@ ThickLayout ComputeThickLayout(TextureTileMode tileMode, std::uint32_t format, s
|
|||
std::uint64_t linearOffset = 0;
|
||||
for (auto level = mipCount; level-- > 0;) {
|
||||
auto& mip = result.mips[level];
|
||||
mip.width = std::max(width >> level, 1u);
|
||||
mip.height = std::max(height >> level, 1u);
|
||||
mip.width = TexelLevelDimension(width, level, texelWidth);
|
||||
mip.height = TexelLevelDimension(height, level, texelHeight);
|
||||
mip.tail = level >= firstTailLevel;
|
||||
mip.tailX = 0;
|
||||
mip.tailY = 0;
|
||||
const bool tiled = tileMode != TextureTileMode::kLinear;
|
||||
const auto levelWidth = tiled ? std::max((width + (1u << level) - 1u) >> level, 1u) : mip.width;
|
||||
const auto levelHeight = tiled ? std::max((height + (1u << level) - 1u) >> level, 1u) : mip.height;
|
||||
const auto levelWidth = tiled ? std::max(ShiftCeil(elementsWidth0, level), 1u) : mip.width;
|
||||
const auto levelHeight = tiled ? std::max(ShiftCeil(elementsHeight0, level), 1u) : mip.height;
|
||||
const auto paddedWidth = AlignUp(levelWidth, block[0]);
|
||||
mip.pitchBytes = paddedWidth * bytesPerElement;
|
||||
if (mip.tail) {
|
||||
|
|
@ -359,7 +377,7 @@ ThickLayout ComputeThickLayout(TextureTileMode tileMode, std::uint32_t format, s
|
|||
|
||||
SurfaceGeometry DescribeSurface(const GuestTextureResource& descriptor) {
|
||||
SurfaceGeometry geometry;
|
||||
if (descriptor.dimension == TextureDimension::k3D && (descriptor.tileMode == TextureTileMode::kD64KBX || descriptor.tileMode == TextureTileMode::kR64KBX)) {
|
||||
if (descriptor.dimension == TextureDimension::k3D && (descriptor.tileMode == TextureTileMode::kZ64KBX || descriptor.tileMode == TextureTileMode::kD64KBX || descriptor.tileMode == TextureTileMode::kR64KBX)) {
|
||||
const auto depth = descriptor.depthOrLastArray + 1u;
|
||||
geometry.mips = ComputeMipLayout(descriptor.tileMode, descriptor.format, descriptor.width, descriptor.height, descriptor.mipCount);
|
||||
geometry.layers = depth;
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue