Compare commits

...
Author SHA1 Message Date
Saadiandnmzik b9a0bd2791 loader: wrap elapsed time in Timer::GetTime (#679)
loader: format elapsed log timestamps beyond 24 hours

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-09-24 22:02:19 +04:00
6d4m 96874909ab cmake: pass -Map as discrete link option on non-clang-cl builds (#806)
LINK_FLAGS is a space-delimited string, so when the build path contains
spaces the linker map option is split into bogus arguments and the final
link fails:

  clang++: error: no such file or directory:
  'pro/KytyPS5/_Build/linux/kyty_emulator_clang_lld.map'

Move -Wl,-Map=<path> out of LINK_FLAGS into target_link_options, which
CMake passes to the linker as a single argument. This completes #26 for
the WIN32 OR LINUX branch (clang/gcc builds); the clang-cl branch was
already converted there.
2026-09-24 21:23:13 +04:00
Romain HedouinandClaude Sonnet 5 d646fd92d1 graphics: hoist loop-invariant checks out of draw/attrib hot paths (#807)
* graphics: hoist loop-invariant checks out of draw/attrib hot paths

DrawIndirectMulti recomputed the index_type_and_size switch on every
sub-draw of a multi-draw-indirect call, and ShaderApplyAttribSemantics
re-checked the debug-dump config flag on every vertex attribute. Both
values are invariant across their respective loops, so compute them
once before iterating instead.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* graphics: cache vertex buffer descriptor size in AcquireVertexBuffers

VertexBufferDescriptorSize was computed twice per vertex-buffer slot
on every draw call: once while collecting guest ranges, and again
while rebuilding slot bindings. Both calls use identical inputs, so
cache the first pass's result and reuse it in the second.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-24 21:02:23 +04:00
nmzikandMac d72a79af2d graphics: skip compute dispatches with BVH instructions
Co-authored-by: Mac <me@itsmemac.com>
2026-09-24 20:46:04 +04:00
nmzikandMac dc9280d75d shader: add GLC/DLC buffer cache policy support
Co-authored-by: Mac <me@itsmemac.com>
2026-09-24 20:46:04 +04:00
nmzik 6a9ae4ae43 shader: add SDWA support for V_MAX_I32 2026-09-24 20:46:04 +04:00
nmzik 392f39e2f6 shader: support indirect BUFFER_LOAD_DWORDX3 2026-09-24 20:46:04 +04:00
nmzikandMac cce0cd950c videoout: add RGB10A2 PQ buffer support
Co-authored-by: Mac <me@itsmemac.com>
2026-09-24 20:46:04 +04:00
e43c242683 shader: add S_MEMREALTIME opcode
Co-authored-by: Maxime Allanic <maxime@allanic.me>
Co-authored-by: Mac <me@itsmemac.com>
2026-09-24 20:46:04 +04:00
nmzikandMac 732057b072 videoout: fix VRR status import signature
Co-authored-by: Mac <me@itsmemac.com>
2026-09-24 20:46:04 +04:00
nmzik 2aaf0c9ba3 graphics: clamp raster depth after polygon offset
Enable native Vulkan depth clamping independently of guest depth clipping
2026-09-24 14:09:02 +04:00
nmzik db7745ef41 update README 2026-09-24 12:30:27 +04:00
nmzik 02578f3c6b docs: update screenshots 2026-09-24 12:18:55 +04:00
fcf0f3b67e shader: fold the lane test after a pixel shader's entry s_wqm (#801)
Preserve live lane predicates through RDNA2 whole-quad expansion

Keep the source-live predicate when WQM expands a scalar mask, so entry masks remain known live without an instruction pre-scan. Preserve scalar mask values, SCC, half writes, and raw-source behavior.

Cover entry pixel masks, cross-half copies, and scalar/high-word sources with existing shader regression suites.

Co-authored-by: nmzik <Nmzik@mail.ru>
Co-authored-by: Douglas <douglasproducoes21@gmail.com>
2026-09-24 12:02:19 +04:00
nmzik 84d015120c Remove duplicate image scratch setup from push constant test 2026-09-24 11:13:33 +04:00
nmzikandTarkusR a6248c7c7c Convert formatted half-float buffer stores from F32
Co-authored-by: TarkusR <90155059+TarkusR@users.noreply.github.com>
2026-09-24 11:13:33 +04:00
nmzik 3bd5de646d Reuse prepared shader bindings across draws and dispatches 2026-09-24 11:13:33 +04:00
nmzik 0382d0ca58 Publish reserved guest mappings atomically after backing commit 2026-09-24 11:13:33 +04:00
nmzik a670dc84b3 Test RDNA2 wave32 ReadLane material key selection 2026-09-24 11:13:33 +04:00
nmzikandTarkusR 963a0817dc Bound uniformized image keys by guarded material mask
Co-authored-by: TarkusR <90155059+TarkusR@users.noreply.github.com>
2026-09-24 11:13:33 +04:00
nmzik 1617ee77e5 Share CFG reachability for descriptor bounds analysis 2026-09-24 11:13:33 +04:00
nmzikandTarkusR 66cc2fe26d Track depth and stencil attachment feedback across graphics stages
Co-authored-by: TarkusR <90155059+TarkusR@users.noreply.github.com>
2026-09-24 11:13:33 +04:00
nmzik 51c34d1a24 Test zero scalar shift folding on RDNA2 compute shader 2026-09-24 11:13:33 +04:00
nmzikandTarkusR 3b89e36d1e Track bounded indirect image tables from signed shader loops
Co-authored-by: TarkusR <90155059+TarkusR@users.noreply.github.com>
2026-09-24 11:13:33 +04:00
João Lobo 3dc3fd0197 shader: implement DS_INC_U32 and DS_DEC_U32 (#790) 2026-09-24 00:45:12 +04:00
Deniz ŞAHİN fc86509aac shader: support DS_WRITE_B8_D16_HI (#794)
Decode GFX10 DS opcode 0xa0 as DS_WRITE_B8_D16_HI (next to
DS_WRITE_B16_D16_HI at 0xa1) and lower it through the existing 8-bit
shared-memory write. The stored byte is bits 23:16 of the data VGPR,
selected with SDWA BYTE_2 on src1.

Add captured-instruction decode coverage (from GTA V, PPSA04264) and a
runtime regression that writes one byte of a distinctive source value
into a pre-filled LDS dword.
2026-09-24 00:44:39 +04:00
569ec04d1a Fix save metadata and timestamps
Co-authored-by: Ali Almohaya <AliAlmohaya@gmail.com>
Co-authored-by: CheesyPoofs346 <jjs777406@gmail.com>
2026-09-23 22:50:51 +04:00
nmzik 7d7267cc56 Translate NGG mesh prolog with guest wave32 width 2026-09-23 22:50:51 +04:00
nmzik 2aa0a64f63 materialize compressed video-out DCC clears before flip
PPSA27624
2026-09-23 22:50:51 +04:00
nmzik fc04db7596 NGS2: preserve ATRAC9 config word byte order 2026-09-23 22:50:51 +04:00
nmzik 1275d65bb3 Audio: split ACM, Audio3D, and NGS2 implementations 2026-09-23 22:50:51 +04:00
nmzik 3742ad9980 NGS2: warn and skip unsupported custom audio modules 2026-09-23 22:50:51 +04:00
nmzik c437de9721 Presenter: avoid lock 2026-09-23 22:50:51 +04:00
Daniil Shushakov ba579088d7 loader: refresh imports after dynamic module loads (#511)
#307: loader: refresh imports after dynamic module loads
2026-09-23 21:04:10 +04:00
Fayaz Shaikhandnmzik 019929587c Allow resizing by default and recreate swapchain on resize (#763)
* Add `stretch to window` support

* Address comment

* Allow window resizing by default and recreate swapchain on resize

---------

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-09-23 17:49:38 +04:00
nmzik f68c77d785 SDL3 update (#787)
* Update to SDL3

* Add XTest development dependency for SDL3

* Preserve native entry points with SDL3

* Document SDL3 XTest dependency
2026-09-23 17:22:15 +04:00
Louisandnmzik b5ba482fc2 Implement missing kernel and startup library imports (#772)
* Implement missing kernel and startup library imports

* Remove chmod and test changes; guard vectored I/O size

---------

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-09-23 15:57:42 +04:00
nmzikandDaniil Shushakov 9860c2bbf8 Associate stencil when depth is acquired
Co-authored-by: Daniil Shushakov <4shushakov@gmail.com>
2026-09-23 15:41:02 +04:00
nmzik ee3749d9b6 Cover 64 MiB download ring staging 2026-09-23 15:41:02 +04:00
nmzik 3fad77fd8b Increase GPU readback staging to 64 MiB 2026-09-23 15:41:02 +04:00
nmzik d6b2abe26f Remove redundant command buffer checks 2026-09-23 15:41:02 +04:00
nmzikandMac 79530b4c5e Read indirect dispatch counts from GPU buffers
Co-authored-by: Mac <me@itsmemac.com>
2026-09-23 15:41:02 +04:00
nmzikandDaniil Shushakov d8a4412498 Refresh stencil backing through the depth image
Co-authored-by: Daniil Shushakov <4shushakov@gmail.com>
2026-09-23 15:41:02 +04:00
nmzik 5ed5fdd36e Preserve resource level bits in texture descriptors 2026-09-23 15:41:02 +04:00
nmzik ef83ba65ad Validate texture descriptor fields 2026-09-23 15:41:02 +04:00
nmzik d30dca78be Read image descriptors in one bounded span 2026-09-23 15:41:02 +04:00
nmzik 66f1a82365 Resolve scalar image tables during resource planning 2026-09-23 15:41:02 +04:00
nmzikandTechx3 eb8a5504a3 Emit coordinates for each indirect image candidate
Co-authored-by: Techx3 <56268269+Techx3@users.noreply.github.com>
2026-09-23 15:41:02 +04:00
nmzik 8a5e5c5476 Fold buffer address identities and remove empty predicates 2026-09-23 15:41:02 +04:00
nmzik 03c55e5712 Keep shader user data inline for cached programs 2026-09-23 15:41:02 +04:00
5d1ad31c49 Resolve guarded descriptor Phis at their use edge
Co-authored-by: Techx3 <56268269+Techx3@users.noreply.github.com>
Co-authored-by: GodsWill <GodsWill@users.noreply.github.com>
2026-09-23 15:41:02 +04:00
nmzik 13fcb3a133 Refresh cached resources in one SRT walk 2026-09-23 15:41:02 +04:00
d6dac92da1 Reuse scalar evaluation storage
Co-authored-by: MehmetCambaz <mcambaz18@gmail.com>
Co-authored-by: Leclowndu93150 <xdr.a123454321@gmail.com>
2026-09-23 15:41:02 +04:00
nmzik 90471836f7 Resolve storage mip views before layout lookup 2026-09-23 15:34:44 +04:00
nmzik 8252caee04 Quiet draw and audio logs 2026-09-23 15:32:30 +04:00
crevant 8b095caefe perf(files): move directory-entry names instead of copying them (#758) 2026-09-22 12:26:06 +04:00
nmzik 8dc9b9d532 Persist save allocations for search and mount capacity queries 2026-09-22 11:38:50 +04:00
e40438f95e Decode ATRAC9 streams after complete RIFF headers
Co-authored-by: Techx3 <56268269+Techx3@users.noreply.github.com>
Co-authored-by: Ali Almohaya <17254073+Almo7aya@users.noreply.github.com>
2026-09-22 11:38:50 +04:00
nmzik a6a56c8a44 Remove obsolete partial indirect draw warnings 2026-09-22 11:38:50 +04:00
nmzik 4b074cf9e9 Avoid heap allocation when compacting materialized shader images 2026-09-22 11:38:50 +04:00
nmzikandAli Almohaya 7c9e18e73b Ignore inactive metadata address bits in sampled depth descriptors
Co-authored-by: Ali Almohaya <17254073+Almo7aya@users.noreply.github.com>
2026-09-22 11:38:50 +04:00
nmzik d2df2cad74 Resolve draw-uniform image descriptors through scalar buffer chains 2026-09-22 11:38:50 +04:00
nmzikandBipin b2a80a3a96 Resolve GPU-selected raw buffer descriptors through shared RDNA2 addressing
Co-authored-by: Bipin <87369440+bipinkrish@users.noreply.github.com>
2026-09-22 11:38:50 +04:00
nmzik cfb8f7b246 Decode unsigned byte sources for SDWA half-float conversion 2026-09-22 11:11:36 +04:00
nmzik d792dfa885 Reject AvPlayer pause and seek before playback 2026-09-22 11:11:36 +04:00
nmzik 12ff571609 Preserve AvPlayer sync mode before source creation 2026-09-22 11:11:36 +04:00
nmzik d3c182f385 Centralize attachment sample validation before acquisition 2026-09-22 11:11:36 +04:00
nmzik bf4dcdce52 Accept resolved FMASK metadata with native MSAA 2026-09-22 11:11:36 +04:00
nmzik 476204cd4b Initialize native NGG vertex launch state and wave size 2026-09-22 11:11:36 +04:00
nmzik f9e56ef8a0 Scope native barriers to shaders with shared execution 2026-09-22 11:11:36 +04:00
nmzik 61761b65f2 Honor PS5 title output-resolution detection permission 2026-09-22 11:11:36 +04:00
nmzik ba55ba5dc5 Allow storage access to PS5 cubemap faces 2026-09-21 00:49:54 +04:00
nmzik c997cdb837 Skip zero-valued indirect CX 0x24a 2026-09-21 00:49:54 +04:00
Kody Schram fe4f9423e3 Significantly Reduce Linux Release Size (#738)
Added symbol stripping for linux build, drops fs from 120 mb to 20mb
2026-09-20 21:26:24 +04:00
nmzik 81af130147 Avoid game list rescans on settings changes/Remove Space pause shortcut 2026-09-20 21:07:36 +04:00
Saadiandnmzik ada0514084 launcher: add a game list refresh action (#683)
* launcher: add a game list refresh action

Dropping new games into an already-configured game folder was only
picked up after restarting the launcher or re-saving the global
settings dialog. Add a refresh toolbar button, an F5 shortcut, and a
"Refresh game list" context-menu entry, all triggering a rescan of the
game folders.

* launcher: do not rescan the game list while a game is running

Clearing cfgs_list destroys the running ConfigurationItem: the running
state is lost and the replacement items allow editing or deleting a
game whose process is still active. Skip the rescan while any item is
running (CodeRabbit review finding for the refresh action).

* launcher: preserve game state when refreshing the list

Share one refresh action across the toolbar, keyboard shortcut, and context menu. Retain running items during rescans, update their settings for the next launch, and preserve selection, filtering, and sort order.

Commit active inline editors before rebuilding rows to avoid losing comments or accessing destroyed configuration data. Tie editor callbacks to item lifetime and guard the context-menu item reference.

Validation: launcher build and startup check passed; 32 Qt interaction checks passed.

* launcher: remove refresh from the game context menu

---------

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-09-20 20:44:40 +04:00
Ilyasandnmzik d7da244475 Add automatic sccache/ccache detection to CMake and remove deprecated Ninja setup action (#734)
* remove outdated ninja setup action

* Remove deprecated ninja setup action

The seanmiddleditch/gha-setup-ninja action has been archived by its
maintainer, who explicitly states that Ninja is now pre-installed on
all GitHub-hosted runners. This removes the action from all three
build jobs (Windows, macOS, Linux) and removes ninja from the package
install lists on macOS and Linux.

* Add automatic ccache/sccache detection to CMakeLists.txt

Detects sccache (preferred on Windows/MSVC) or ccache at configure time
and sets CMAKE_C_COMPILER_LAUNCHER / CMAKE_CXX_COMPILER_LAUNCHER
automatically. Works for local builds without manual flags.

Priority: sccache > ccache (sccache handles MSVC PDBs better).

* Delete github-actions-version-research.md

* Respect user-specified CMAKE_C_COMPILER_LAUNCHER / CMAKE_CXX_COMPILER_LAUNCHER

Only auto-detect sccache/ccache if no launcher was explicitly provided
via -DCMAKE_C_COMPILER_LAUNCHER=... or -DCMAKE_CXX_COMPILER_LAUNCHER=...

* Preserve explicit compiler launcher settings per language

---------

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-09-20 19:49:59 +04:00
Elijaandnmzik 549495575c shader: add S_BITCMP0_B64 and S_BITCMP1_B64 (#688)
* shader: add S_BITCMP0_B64 and S_BITCMP1_B64

* test: move S_BITCMP_B64 coverage to compute suite

* test: use the existing compute CTest entry for bit comparisons

* test: remove the dedicated S_BITCMP_B64 command-line selector

---------

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-09-20 19:36:12 +04:00
Maayan080andnmzik c3cc8454b9 common: remove unused recursive file enumeration (#681)
common: don't follow reparse points in the Windows recursive file walk

SysFileFindFiles recurses into every entry carrying
FILE_ATTRIBUTE_DIRECTORY, which on Windows includes directory symlinks
and junctions. A junction pointing at one of its own ancestors makes the
walk recurse until the stack runs out.

The Linux implementation already guards against this: it uses lstat
rather than stat specifically so a symlink is never followed into a
cycle. Skip reparse-point entries on Windows for the same reason.

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-09-20 18:50:57 +04:00
Saadi b8ad1d8c4b common: return real Win32 cache flags from GetCacheAccessType (#673)
GetCacheAccessType returned the sys_file_cache_type_t enum values as
CreateFileW flags: SYS_FILE_CACHE_SEQUENTIAL_SCAN (2) became
FILE_ATTRIBUTE_HIDDEN and SYS_FILE_CACHE_AUTO (0) lost the normal
attributes, and no FILE_FLAG_SEQUENTIAL_SCAN hint was ever applied.
Return FILE_FLAG_SEQUENTIAL_SCAN / FILE_ATTRIBUTE_NORMAL instead,
mirroring the POSIX fadvise handling on Linux.
2026-09-20 18:47:25 +04:00
Elija cfb110e0aa shader: allow partial SDWA destinations for V_NOT_B32 (#684) 2026-09-20 18:21:40 +04:00
124 changed files with 9761 additions and 5529 deletions

No files matched your search

+3 -6
View File
@@ -43,9 +43,6 @@ jobs:
- name: Setup Visual Studio environment
uses: ilammy/msvc-dev-cmd@v1
- name: Setup Ninja
uses: seanmiddleditch/gha-setup-ninja@v5
- name: Locate vcpkg
id: vcpkg
shell: pwsh
@@ -155,7 +152,7 @@ jobs:
- name: Install build dependencies
shell: bash
run: brew install glslang ninja
run: brew install glslang
- name: Install Qt 6.10.3
uses: jurplel/install-qt-action@v4
@@ -317,8 +314,8 @@ jobs:
libxkbcommon-dev \
libxrandr-dev \
libxss-dev \
libxtst-dev \
lld \
ninja-build \
pkg-config \
wayland-protocols
@@ -363,7 +360,7 @@ jobs:
-DCMAKE_PREFIX_PATH="$Qt6_DIR" 2>&1 | tee _Build/configure.log
exit "${PIPESTATUS[0]}"
- name: Verify SDL2 backends
- name: Verify SDL3 backends
shell: bash
run: |
status=0
+3 -3
View File
@@ -1,7 +1,7 @@
[submodule "3rdparty/SDL2"]
path = 3rdparty/SDL2
[submodule "3rdparty/SDL3"]
path = 3rdparty/SDL3
url = https://github.com/libsdl-org/SDL.git
branch = SDL2
branch = release-3.4.x
[submodule "3rdparty/magic_enum"]
path = 3rdparty/magic_enum
url = https://github.com/Neargye/magic_enum.git
+1 -1
View File
@@ -29,7 +29,7 @@ FetchContent_Declare(
FetchContent_MakeAvailable(xbyak zydis)
set(SDL_SHARED OFF CACHE BOOL "" FORCE)
add_subdirectory(SDL2)
add_subdirectory(SDL3)
add_library(Vulkan-Headers INTERFACE)
add_library(Vulkan::Headers ALIAS Vulkan-Headers)
-1
Submodule 3rdparty/SDL2 deleted from 4b69833bc5.
Vendored Submodule
+1
Submodule 3rdparty/SDL3 added at fa2c02bb6e.
+28 -4
View File
@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.12...4.3)
cmake_minimum_required(VERSION 3.16...4.3)
if(CMAKE_VERSION VERSION_GREATER_EQUAL 4.0)
set(CMAKE_POLICY_VERSION_MINIMUM 3.5 CACHE STRING "Minimum policy version for bundled dependencies")
@@ -12,6 +12,22 @@ endif()
project(Kyty)
find_program(SCCACHE_PROGRAM sccache)
find_program(CCACHE_PROGRAM ccache)
foreach(lang IN ITEMS C CXX)
# An explicitly empty launcher disables caching for this language.
if(NOT DEFINED CMAKE_${lang}_COMPILER_LAUNCHER)
if(SCCACHE_PROGRAM)
set(CMAKE_${lang}_COMPILER_LAUNCHER "${SCCACHE_PROGRAM}")
message(STATUS "sccache enabled for ${lang}: ${SCCACHE_PROGRAM}")
elseif(CCACHE_PROGRAM)
set(CMAKE_${lang}_COMPILER_LAUNCHER "${CCACHE_PROGRAM}")
message(STATUS "ccache enabled for ${lang}: ${CCACHE_PROGRAM}")
endif()
endif()
endforeach()
set(KYTY_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/src")
set(KYTY_TESTS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/tests")
set(KYTY_THIRD_PARTY_DIR "${CMAKE_CURRENT_SOURCE_DIR}/3rdparty")
@@ -315,7 +331,7 @@ if (KYTY_CLANG_CL)
set_source_files_properties(${kyty_emulator_src} PROPERTIES COMPILE_FLAGS "-Wno-pragma-pack -Wno-deprecated-declarations -D_TIMESPEC_DEFINED")
endif()
set(kyty_emulator_link_libraries common Vulkan::Headers spirv-tools-opt spirv-tools SDL2-static imgui xxhash FFmpeg::ffmpeg fmt::fmt nlohmann_json::nlohmann_json LibAtrac9 kyty_git_version Zydis::Zydis xbyak::xbyak)
set(kyty_emulator_link_libraries common Vulkan::Headers spirv-tools-opt spirv-tools SDL3::SDL3-static imgui xxhash FFmpeg::ffmpeg fmt::fmt nlohmann_json::nlohmann_json LibAtrac9 kyty_git_version Zydis::Zydis xbyak::xbyak)
# Linux system libraries.
if(LINUX)
@@ -325,7 +341,6 @@ endif()
set(inc_headers
${KYTY_SOURCE_DIR}
${KYTY_THIRD_PARTY_DIR}/SDL2/include
${KYTY_THIRD_PARTY_DIR}/VulkanMemoryAllocator/include
${KYTY_THIRD_PARTY_DIR}/SPIRV-Tools/include
${KYTY_THIRD_PARTY_DIR}/SPIRV-Headers/include
@@ -662,7 +677,16 @@ if(KYTY_CLANG_CL)
)
add_custom_command(TARGET kyty_emulator POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different "${KYTY_THIRD_PARTY_DIR}/winpthread/bin/libwinpthread-1.dll" $<TARGET_FILE_DIR:kyty_emulator>/libwinpthread-1.dll)
elseif(WIN32 OR LINUX)
set_target_properties(kyty_emulator PROPERTIES LINK_FLAGS "${KYTY_LD_OPTIONS} -Wl,-Map=${KYTY_EMULATOR_MAP_LINK_PATH}")
set_target_properties(kyty_emulator PROPERTIES LINK_FLAGS "${KYTY_LD_OPTIONS}")
# LINK_FLAGS is a space-delimited string: a -Map path containing spaces would
# be split into bogus arguments, so pass it as a discrete link option instead.
target_link_options(kyty_emulator PRIVATE "-Wl,-Map=${KYTY_EMULATOR_MAP_LINK_PATH}")
endif()
if(LINUX)
target_link_options(kyty_emulator PRIVATE
$<$<CONFIG:Release>:-Wl,--strip-all>
)
endif()
if(APPLE)
+22 -21
View File
@@ -4,13 +4,13 @@
[![Build KytyPS5 (Linux)](https://img.shields.io/github/actions/workflow/status/KytyPS5/KytyPS5/build.yml?branch=main&event=push&label=Build%20KytyPS5%20%28Linux%29)](https://github.com/KytyPS5/KytyPS5/actions/workflows/build.yml)
[![Build KytyPS5 (macOS)](https://img.shields.io/github/actions/workflow/status/KytyPS5/KytyPS5/build.yml?branch=main&event=push&label=Build%20KytyPS5%20%28macOS%29)](https://github.com/KytyPS5/KytyPS5/actions/workflows/build.yml)
[![Platform](https://img.shields.io/badge/platform-Windows%20x64%20%7C%20Linux%20x64%20%7C%20macOS%20x86__64-0078D4.svg)](#system-requirements)
[![Status](https://img.shields.io/badge/status-early%20development-orange.svg)](#current-status)
[![Status](https://img.shields.io/badge/status-active%20development-orange.svg)](#current-status)
[![License](https://img.shields.io/badge/license-GPL--2.0-blue.svg)](LICENSE)
KytyPS5 is a free and open-source PlayStation 5 emulator written in C++ for Windows and Linux,
with experimental macOS support. It is based on a heavily modified version of
[Kyty](https://github.com/InoriRus/Kyty). The project is in an early stage of development, so
compatibility is limited and behavior may change significantly between builds.
[Kyty](https://github.com/InoriRus/Kyty). The project is in active development, and behavior
can change significantly between builds.
> [!IMPORTANT]
> KytyPS5 is not affiliated with Sony Interactive Entertainment or PlayStation. The project does
@@ -20,12 +20,12 @@ compatibility is limited and behavior may change significantly between builds.
## Current Status
KytyPS5 can boot 2D games and a selection of 3D games, including titles built with Unreal Engine
4/5, Unity, and custom engines. No external low-level emulation modules are currently required.
4/5, Unity, and custom engines. External low-level emulation modules are neither required nor
planned.
Development is focused on compatibility and boot reliability.
Development is currently focused on expanding game compatibility and improving boot reliability.
Windows is the primary platform and receives the most testing. Linux builds and runs; see
[Building on Linux](#building-on-linux).
Windows and Linux are the primary platforms and receive the most testing.
macOS support is experimental. The emulator is built for x86-64 and runs on Apple Silicon under
Rosetta 2, with Vulkan provided by MoltenVK. A small number of titles have been verified in-game
@@ -36,16 +36,16 @@ Community game test results are available in the
## Bugs and Issues
The project is in an early stage, so please be mindful when opening new issues. Expect crashes,
graphical glitches, low compatibility, and poor performance.
Compatibility, stability, and performance can vary between versions. You may encounter crashes
or graphical glitches, so please include the version you tested when reporting an issue.
## Screenshots
<table align="center">
<tr>
<td align="center">
<strong>Disgaea 6</strong><br>
<img src="docs/screenshots/ps5-01.png" width="300" alt="Disgaea 6 running in KytyPS5">
<strong>Astro Bot</strong><br>
<img src="docs/screenshots/ps5-01.png" width="300" alt="Astro Bot running in KytyPS5">
</td>
<td align="center">
<strong>Dreaming Sarah</strong><br>
@@ -64,12 +64,12 @@ graphical glitches, low compatibility, and poor performance.
</tr>
<tr>
<td align="center">
<strong>Hellboy</strong><br>
<img src="docs/screenshots/ps5-02.png" width="300" alt="Hellboy running in KytyPS5">
<strong>Demon's Souls</strong><br>
<img src="docs/screenshots/ps5-02.png" width="300" alt="Demon's Souls running in KytyPS5">
</td>
<td align="center">
<strong>Paleo Pines</strong><br>
<img src="docs/screenshots/ps5-06.png" width="300" alt="Paleo Pines running in KytyPS5">
<strong>Hellboy</strong><br>
<img src="docs/screenshots/ps5-06.png" width="300" alt="Hellboy running in KytyPS5">
</td>
</tr>
</table>
@@ -166,15 +166,15 @@ The finished application and its runtime dependencies will be placed in
### Building on Linux
Install the toolchain and the libraries the bundled SDL2 needs. Without the audio, Wayland and
udev development packages SDL2 quietly configures itself without those backends, and the resulting
Install the toolchain and the libraries the bundled SDL3 needs. Without the audio, Wayland and
udev development packages SDL3 quietly configures itself without those backends, and the resulting
build has no working sound and no gamepad hotplug:
```bash
sudo apt-get install --no-install-recommends \
clang lld ninja-build cmake git glslang-tools pkg-config \
libgl1-mesa-dev libx11-dev libxcursor-dev libxext-dev libxfixes-dev \
libxi-dev libxrandr-dev libxss-dev libxkbcommon-dev \
libxi-dev libxrandr-dev libxss-dev libxtst-dev libxkbcommon-dev \
libasound2-dev libpulse-dev libudev-dev libdbus-1-dev libwayland-dev wayland-protocols
```
@@ -204,7 +204,7 @@ The CMake source root is the repository root.
### Building on NixOS
A development shell provides Clang, CMake, Ninja, Qt 6, the Vulkan headers, and the SDL2 backend
A development shell provides Clang, CMake, Ninja, Qt 6, the Vulkan headers, and the SDL3 backend
libraries. Enter it and configure exactly as on other Linux distributions; the shell exports
`CMAKE_PREFIX_PATH` and `QT_PLUGIN_PATH`, so the `-DCMAKE_PREFIX_PATH="$Qt6_DIR"` argument is not
needed:
@@ -370,5 +370,6 @@ licenses included with those components.
- [InoriRus/Kyty](https://github.com/InoriRus/Kyty) — KytyPS5 is based on a heavily modified version
of the original Kyty project.
- [shadps4-emu/shadPS4](https://github.com/shadps4-emu/shadPS4) — reference for memory-model
understanding and the AVPlayer implementation.
- [shadps4-emu/shadPS4](https://github.com/shadps4-emu/shadPS4) — reference for understanding PS4
memory behavior, GPU resource aliasing and cache coherency,
and the AVPlayer implementation.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 MiB

After

Width:  |  Height:  |  Size: 1.6 MiB

+3 -2
View File
@@ -1,8 +1,7 @@
{ pkgs }:
let
# Build-time dependencies. Every -dev package the bundled SDL2 probes for must
# be listed here, otherwise SDL2 quietly drops the audio/Wayland/udev backends.
# Build-time dependencies for the bundled SDL3 audio, video and input backends.
buildDeps = with pkgs; [
qt6.qtbase # Concurrent, Network, Widgets (launcher)
vulkan-headers
@@ -18,6 +17,7 @@ let
libXi
libXrandr
libXScrnSaver
libXtst
libxkbcommon
# Wayland
wayland
@@ -46,6 +46,7 @@ let
libXi
libXrandr
libXScrnSaver
libXtst
libxkbcommon
wayland
libdecor
+2 -46
View File
@@ -445,45 +445,6 @@ bool File::SetLastAccessAndWriteTimeUTC(const std::filesystem::path& name, const
return SysFileSetLastAccessAndWriteTimeUtc(name, af, wf);
}
std::vector<File::FindInfo> File::FindFiles(const std::filesystem::path& path) {
std::vector<sys_file_find_t> files;
SysFileFindFiles(path, files);
const auto normalized_path = path.lexically_normal();
std::vector<File::FindInfo> ret;
ret.reserve(files.size());
for (const auto& f: files) {
File::FindInfo r {};
r.path_with_name = f.path_with_name;
r.rel_path_with_name = f.path_with_name.lexically_normal().lexically_relative(normalized_path);
r.size = f.size;
SysTimeStruct at {};
SysTimeStruct wt {};
SysFileToSystemTimeUtc(f.last_access_time, at);
SysFileToSystemTimeUtc(f.last_write_time, wt);
if (!at.is_invalid && !wt.is_invalid) {
r.last_access_time = DateTime(Date(at.Year, at.Month, at.Day),
Time(at.Hour, at.Minute, at.Second, at.Milliseconds));
r.last_write_time = DateTime(Date(wt.Year, wt.Month, wt.Day),
Time(wt.Hour, wt.Minute, wt.Second, wt.Milliseconds));
}
// printf("%s, %s, %" PRIu64", %s, %s\n", r.path_with_name.c_str(),
// r.rel_path_with_name.c_str(), r.size, r.last_access_time.ToString().c_str(),
// r.last_write_time.ToString().c_str());
ret.push_back(std::move(r));
}
return ret;
}
std::vector<File::DirEntry> File::GetDirEntries(const std::filesystem::path& path) {
std::vector<sys_dir_entry_t> files;
@@ -492,13 +453,8 @@ std::vector<File::DirEntry> File::GetDirEntries(const std::filesystem::path& pat
std::vector<File::DirEntry> ret;
ret.reserve(files.size());
for (const auto& f: files) {
File::DirEntry r {};
r.name = f.name;
r.is_file = f.is_file;
ret.push_back(std::move(r));
for (auto& f: files) {
ret.emplace_back(std::move(f.name), f.is_file);
}
return ret;
-9
View File
@@ -30,14 +30,6 @@ class File {
public:
enum class Mode { Read, Write, ReadWrite, WriteRead };
struct FindInfo {
std::filesystem::path path_with_name;
std::filesystem::path rel_path_with_name;
DateTime last_access_time;
DateTime last_write_time;
uint64_t size;
};
struct DirEntry {
std::string name;
bool is_file;
@@ -98,7 +90,6 @@ public:
static bool SetLastAccessAndWriteTimeUTC(const std::filesystem::path& name,
const DateTime& access, const DateTime& write);
static std::vector<FindInfo> FindFiles(const std::filesystem::path& path);
static std::vector<DirEntry> GetDirEntries(const std::filesystem::path& path);
static bool CopyFile(const std::filesystem::path& src, const std::filesystem::path& dst);
static bool RenameFile(const std::filesystem::path& src, const std::filesystem::path& dst);
-10
View File
@@ -18,14 +18,6 @@ enum sys_file_cache_type_t {
SYS_FILE_CACHE_SEQUENTIAL_SCAN = 2 // NOLINT(readability-identifier-naming)
};
// NOLINTNEXTLINE(readability-identifier-naming)
struct sys_file_find_t {
std::filesystem::path path_with_name;
SysFileTimeStruct last_access_time;
SysFileTimeStruct last_write_time;
uint64_t size;
};
// NOLINTNEXTLINE(readability-identifier-naming)
struct sys_dir_entry_t {
std::string name;
@@ -75,8 +67,6 @@ bool SysFileSetLastWriteTimeUtc(const std::filesystem::path& name, SysFileTimeSt
bool SysFileSetLastAccessAndWriteTimeUtc(const std::filesystem::path& name,
SysFileTimeStruct& access, SysFileTimeStruct& write);
// NOLINTNEXTLINE(google-runtime-references)
void SysFileFindFiles(const std::filesystem::path& path, std::vector<sys_file_find_t>& out);
// NOLINTNEXTLINE(google-runtime-references)
void SysFileGetDents(const std::filesystem::path& path, std::vector<sys_dir_entry_t>& out);
bool SysFileCopyFile(const std::filesystem::path& src, const std::filesystem::path& dst);
bool SysFileRenameFile(const std::filesystem::path& src, const std::filesystem::path& dst);
-45
View File
@@ -610,51 +610,6 @@ bool SysFileSetLastAccessAndWriteTimeUtc(const std::filesystem::path& name,
// }
}
// Recursively collect regular files.
void SysFileFindFiles(const std::filesystem::path& path, std::vector<sys_file_find_t>& out) {
auto real_path = get_internal_name(path);
DIR* dir = opendir(real_path.string().c_str());
if (dir == nullptr) {
return;
}
for (const dirent* entry = readdir(dir); entry != nullptr; entry = readdir(dir)) {
const std::string file_name(entry->d_name);
if (file_name == "." || file_name == "..") {
continue;
}
auto child = real_path / file_name;
struct stat s {};
// lstat, so a symlink is never followed into a cycle during the recursive walk.
if (0 != lstat(child.string().c_str(), &s)) {
continue;
}
if (S_ISDIR(s.st_mode)) {
SysFileFindFiles(child, out);
} else if (S_ISREG(s.st_mode)) {
sys_file_find_t r {};
r.path_with_name = child;
r.size = static_cast<uint64_t>(s.st_size);
r.last_access_time.is_invalid = false;
r.last_access_time.time = s.st_atime;
r.last_access_time.nanos = KYTY_STAT_ATIME_NS(s);
r.last_write_time.is_invalid = false;
r.last_write_time.time = s.st_mtime;
r.last_write_time.nanos = KYTY_STAT_MTIME_NS(s);
out.push_back(r);
}
}
closedir(dir);
}
// Keep "." and ".." to match FindFirstFileW.
void SysFileGetDents(const std::filesystem::path& path, std::vector<sys_dir_entry_t>& out) {
auto real_path = get_internal_name(path);
+2 -42
View File
@@ -57,10 +57,10 @@ static DWORD GetCacheAccessType(sys_file_cache_type_t t) {
}
if (t == SYS_FILE_CACHE_SEQUENTIAL_SCAN) {
return SYS_FILE_CACHE_SEQUENTIAL_SCAN;
return FILE_FLAG_SEQUENTIAL_SCAN;
}
return SYS_FILE_CACHE_AUTO;
return FILE_ATTRIBUTE_NORMAL;
}
void SysFileRead(void* data, uint32_t size, sys_file_t& f, uint32_t* bytes_read) {
@@ -476,46 +476,6 @@ bool SysFileSetLastAccessAndWriteTimeUtc(const std::filesystem::path& name,
return ok;
}
void SysFileFindFiles(const std::filesystem::path& path, std::vector<sys_file_find_t>& out) {
const auto pattern = path / L"*";
WIN32_FIND_DATAW data {};
HANDLE h = FindFirstFileW(pattern.c_str(), &data);
if (h == INVALID_HANDLE_VALUE) {
return;
}
do {
std::filesystem::path file_name(data.cFileName);
if (file_name == L"." || file_name == L"..") {
continue;
}
const auto full_path = path / file_name;
if ((data.dwFileAttributes & static_cast<DWORD>(FILE_ATTRIBUTE_DIRECTORY)) != 0u) {
SysFileFindFiles(full_path, out);
} else {
sys_file_find_t r {};
r.path_with_name = full_path;
r.size = (static_cast<uint64_t>(data.nFileSizeHigh) << 32u) +
static_cast<uint64_t>(data.nFileSizeLow);
r.last_access_time.is_invalid = false;
r.last_access_time.time = data.ftLastAccessTime;
r.last_write_time.is_invalid = false;
r.last_write_time.time = data.ftLastWriteTime;
out.push_back(std::move(r));
}
} while (FindNextFileW(h, &data) != 0);
FindClose(h);
}
void SysFileGetDents(const std::filesystem::path& path, std::vector<sys_dir_entry_t>& out) {
const auto pattern = path / L"*";
+1 -10
View File
@@ -38,16 +38,7 @@ bool Timer::IsPaused() const {
// return time in milliseconds
double Timer::GetTimeMs() const {
if (m_is_paused) {
return 1000.0 * (static_cast<double>(m_PauseTime - m_StartTime)) /
static_cast<double>(m_Frequency);
}
uint64_t current_time = 0;
SysQueryPerformanceCounter(&current_time);
return 1000.0 * (static_cast<double>(current_time - m_StartTime)) /
static_cast<double>(m_Frequency);
return GetTimeS() * 1000.0;
}
// return time in seconds
@@ -73,6 +73,9 @@ public:
void SetIndexBufferSize(uint32_t index_buffer_size);
void SetDrawIndirectArgsBaseAddress(uint64_t draw_indirect_args_base_addr);
void SetDispatchIndirectArgsBaseAddress(uint64_t dispatch_indirect_args_base_addr);
[[nodiscard]] uint64_t GetDispatchIndirectArgsBaseAddress() const {
return m_dispatch_indirect_args_base_addr;
}
void SetNumInstances(uint32_t num_instances);
void DrawIndex(DrawIndexArgs args);
void DrawIndexOffset(uint32_t index_offset, uint32_t index_count);
@@ -99,7 +102,7 @@ public:
void TriggerEopEventAtEndOfPipe(uint32_t interrupt_context_id);
void DispatchDirect(uint32_t thread_group_x, uint32_t thread_group_y, uint32_t thread_group_z,
uint32_t mode);
void DispatchIndirect(uint32_t data_offset, uint32_t mode);
void DispatchIndirect(uint64_t args_addr, uint32_t mode);
void WaitFlipDone(uint32_t video_out_handle, uint32_t display_buffer_index);
void TriggerEvent(uint32_t event_type, uint32_t event_index, uint64_t event_address = 0);
@@ -148,7 +151,6 @@ private:
void SuspendPm4();
CommandScheduler& GetScheduler() const { return m_renderer.GetCommandScheduler(); }
CommandBuffer& CurrentBuffer() { return GetScheduler().Current(); }
void CheckBuffer() const { GetScheduler().CheckActive(); }
RenderContext& m_renderer;
HW::Context m_ctx;
@@ -1331,26 +1331,13 @@ KYTY_CP_OP_PARSER(CpOpDispatchIndirect) {
EXIT_NOT_IMPLEMENTED(cmd_id != 0xc0011600 && cmd_id != 0xc0021600);
if (cmd_id == 0xc0021600) {
struct DispatchIndirectArgs {
uint32_t thread_group_x;
uint32_t thread_group_y;
uint32_t thread_group_z;
};
auto* args = reinterpret_cast<const DispatchIndirectArgs*>(
buffer[0] | (static_cast<uint64_t>(buffer[1]) << 32u));
uint32_t mode = buffer[2];
EXIT_NOT_IMPLEMENTED(args == nullptr);
cp.DispatchDirect(args->thread_group_x, args->thread_group_y, args->thread_group_z, mode);
cp.DispatchIndirect(buffer[0] | (static_cast<uint64_t>(buffer[1]) << 32u), buffer[2]);
return 3;
}
uint32_t data_offset = buffer[0];
uint32_t mode = buffer[1];
cp.DispatchIndirect(data_offset, mode);
const auto base_addr = cp.GetDispatchIndirectArgsBaseAddress();
EXIT_NOT_IMPLEMENTED(base_addr == 0);
cp.DispatchIndirect(base_addr + buffer[0], buffer[1]);
return 2;
}
@@ -2017,6 +2004,14 @@ KYTY_CP_OP_PARSER(CpOpIndirectCxRegs) {
auto pfunc = g_hw_ctx_indirect_func[cmd_offset & (Pm4::CX_NUM - 1)];
if (pfunc == nullptr) {
if (raw_cmd_offset == 0x24au && value == 0u) {
static std::atomic_flag logged = ATOMIC_FLAG_INIT;
if (!logged.test_and_set(std::memory_order_relaxed)) {
Log::WriteToConsoleAndLog(
"\t diagnostic: ignoring indirect CX {0x24a, 0}; hardware effect unresolved\n");
}
continue;
}
EXIT("unknown cx reg at %05" PRIx32 ": 0x%" PRIx32 "\n", num_dw - dw, cmd_offset);
}
+39 -97
View File
@@ -874,13 +874,11 @@ void CommandProcessor::SetPredication(uint32_t condition, uint32_t op, uint32_t
}
void CommandProcessor::DrawIndex(DrawIndexArgs args) {
CheckBuffer();
args.index_type_and_size = m_index_type_and_size;
if (args.instance_count == 0) {
args.instance_count = m_num_instances;
}
if (args.base_vertex != 0 || args.first_instance != 0) {
if (GraphicsRunDebugDumpEnabled() && (args.base_vertex != 0 || args.first_instance != 0)) {
LOGF("\t draw indexed offsets: base_vertex = %" PRId32 ", first_instance = %" PRIu32 "\n",
args.base_vertex, args.first_instance);
}
@@ -912,17 +910,6 @@ void CommandProcessor::DrawIndirect(uint32_t data_offset, uint32_t draw_initiato
if (!indexed) {
DrawIndirectArgs args {};
std::memcpy(&args, args_addr, sizeof(args));
if (args.instance_count != 1u || args.start_vertex_location != 0u ||
args.start_instance_location != 0u) {
static std::atomic<uint32_t> log_count {0};
if (log_count.fetch_add(1) < 64) {
LOGF("\t warning: partial DrawIndirect args: vertex_count=%" PRIu32
", instance_count=%" PRIu32 ", start_vertex=%" PRIu32
", start_instance=%" PRIu32 "\n",
args.vertex_count_per_instance, args.instance_count,
args.start_vertex_location, args.start_instance_location);
}
}
m_num_instances = args.instance_count;
DrawIndexAuto({.vertex_count = args.vertex_count_per_instance,
.instance_count = args.instance_count,
@@ -934,16 +921,6 @@ void CommandProcessor::DrawIndirect(uint32_t data_offset, uint32_t draw_initiato
DrawIndexedIndirectArgs args {};
std::memcpy(&args, args_addr, sizeof(args));
if (args.base_vertex_location != 0u || args.start_instance_location != 0u) {
static std::atomic<uint32_t> log_count {0};
if (log_count.fetch_add(1) < 64) {
LOGF("\t warning: partial DrawIndexIndirect args: index_count=%" PRIu32
", instance_count=%" PRIu32 ", start_index=%" PRIu32 ", base_vertex=%" PRIu32
", start_instance=%" PRIu32 "\n",
args.index_count_per_instance, args.instance_count, args.start_index_location,
args.base_vertex_location, args.start_instance_location);
}
}
uint64_t index_size = 0;
switch (m_index_type_and_size) {
@@ -999,23 +976,22 @@ void CommandProcessor::DrawIndirectMulti(uint32_t data_offset, uint32_t max_coun
const auto args_size = indexed ? sizeof(DrawIndexedIndirectArgs) : sizeof(DrawIndirectArgs);
EXIT_NOT_IMPLEMENTED(stride_in_bytes < args_size);
uint64_t index_size = 0;
if (indexed) {
switch (m_index_type_and_size) {
case 0: index_size = 2; break;
case 1: index_size = 4; break;
case 2: index_size = 1; break;
default: EXIT("unknown index_type_and_size: %u\n", m_index_type_and_size);
}
}
for (uint32_t i = 0; i < draw_count; i++) {
const auto args_addr = m_draw_indirect_args_base_addr + data_offset +
static_cast<uint64_t>(i) * stride_in_bytes;
if (!indexed) {
auto* args = reinterpret_cast<const DrawIndirectArgs*>(args_addr);
if (args->instance_count != 1u || args->start_vertex_location != 0u ||
args->start_instance_location != 0u) {
static std::atomic<uint32_t> log_count {0};
if (log_count.fetch_add(1) < 64) {
LOGF("\t warning: partial DrawIndirectMulti args[%u]: vertex_count=%" PRIu32
", instance_count=%" PRIu32 ", start_vertex=%" PRIu32
", start_instance=%" PRIu32 "\n",
i, args->vertex_count_per_instance, args->instance_count,
args->start_vertex_location, args->start_instance_location);
}
}
m_num_instances = args->instance_count;
DrawIndexAuto({.vertex_count = args->vertex_count_per_instance,
.instance_count = args->instance_count,
@@ -1026,25 +1002,6 @@ void CommandProcessor::DrawIndirectMulti(uint32_t data_offset, uint32_t max_coun
}
auto* args = reinterpret_cast<const DrawIndexedIndirectArgs*>(args_addr);
if (args->base_vertex_location != 0u || args->start_instance_location != 0u) {
static std::atomic<uint32_t> log_count {0};
if (log_count.fetch_add(1) < 64) {
LOGF("\t warning: partial DrawIndexIndirectMulti args[%u]: index_count=%" PRIu32
", instance_count=%" PRIu32 ", start_index=%" PRIu32 ", base_vertex=%" PRIu32
", start_instance=%" PRIu32 "\n",
i, args->index_count_per_instance, args->instance_count,
args->start_index_location, args->base_vertex_location,
args->start_instance_location);
}
}
uint64_t index_size = 0;
switch (m_index_type_and_size) {
case 0: index_size = 2; break;
case 1: index_size = 4; break;
case 2: index_size = 1; break;
default: EXIT("unknown index_type_and_size: %u\n", m_index_type_and_size);
}
auto* index_addr = reinterpret_cast<const void*>(
m_index_base_addr + static_cast<uint64_t>(args->start_index_location) * index_size);
@@ -1082,7 +1039,6 @@ void CommandProcessor::DispatchDirect(uint32_t thread_group_x, uint32_t thread_g
// uint32_t local_z = 1;
{
CheckBuffer();
frame_num = m_renderer.GetGpu().GetFrameNum();
if (GraphicsRunDebugDumpEnabled()) {
static std::atomic<uint32_t> log_count {0};
@@ -1133,24 +1089,18 @@ void CommandProcessor::DispatchDirect(uint32_t thread_group_x, uint32_t thread_g
}*/
}
void CommandProcessor::DispatchIndirect(uint32_t data_offset, uint32_t mode) {
struct DispatchIndirectArgs {
uint32_t thread_group_x;
uint32_t thread_group_y;
uint32_t thread_group_z;
};
EXIT_NOT_IMPLEMENTED(m_dispatch_indirect_args_base_addr == 0);
const auto args_addr = m_dispatch_indirect_args_base_addr + data_offset;
auto* args = reinterpret_cast<const DispatchIndirectArgs*>(args_addr);
DispatchDirect(args->thread_group_x, args->thread_group_y, args->thread_group_z, mode);
void CommandProcessor::DispatchIndirect(uint64_t args_addr, uint32_t mode) {
EXIT_NOT_IMPLEMENTED(args_addr == 0 || (args_addr & 3u) != 0);
if ((mode & Pm4::COMPUTE_DISPATCH_INITIATOR_USE_THREAD_DIMENSIONS) != 0) {
const auto* args = reinterpret_cast<const vk::DispatchIndirectCommand*>(args_addr);
DispatchDirect(args->x, args->y, args->z, mode);
return;
}
m_sh_ctx.SetCsWaveSize(Pm4::ComputeWaveSize(mode));
m_renderer.GetRenderExecutor().DispatchIndirect(m_submit_id, CurrentBuffer(), args_addr, mode);
}
void CommandProcessor::DrawIndexAuto(DrawAutoArgs args) {
CheckBuffer();
if (args.instance_count == 0) {
args.instance_count = m_num_instances;
}
@@ -1172,7 +1122,7 @@ void CommandProcessor::WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_wr
uint32_t interrupt_context_id) {
static_assert(sizeof(T) == sizeof(uint32_t) || sizeof(T) == sizeof(uint64_t));
CheckBuffer();
auto& command = CurrentBuffer();
if (GraphicsRunDebugDumpEnabled()) {
const auto bits = static_cast<unsigned>(sizeof(T) * 8u);
@@ -1203,7 +1153,7 @@ void CommandProcessor::WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_wr
case 0x03: with_interrupt = false; break;
case 0x01:
if (!IsAsyncComputeQueue()) {
Sync::TriggerEopEventAtEndOfPipe(CurrentBuffer(), m_interrupt_event_id,
Sync::TriggerEopEventAtEndOfPipe(command, m_interrupt_event_id,
interrupt_context_id);
return;
}
@@ -1220,17 +1170,17 @@ void CommandProcessor::WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_wr
if (with_interrupt) {
if (with_writeback) {
Sync::WriteAtEndOfPipeWithInterruptWriteBack32(m_submit_id, CurrentBuffer(), dst,
Sync::WriteAtEndOfPipeWithInterruptWriteBack32(m_submit_id, command, dst,
data, m_interrupt_event_id,
interrupt_context_id);
} else {
Sync::WriteAtEndOfPipeWithInterrupt32(m_submit_id, CurrentBuffer(), dst, data,
Sync::WriteAtEndOfPipeWithInterrupt32(m_submit_id, command, dst, data,
m_interrupt_event_id, interrupt_context_id);
}
} else if (with_writeback) {
Sync::WriteAtEndOfPipeWithWriteBack32(m_submit_id, CurrentBuffer(), dst, data);
Sync::WriteAtEndOfPipeWithWriteBack32(m_submit_id, command, dst, data);
} else {
Sync::WriteAtEndOfPipe32(m_submit_id, CurrentBuffer(), dst, data);
Sync::WriteAtEndOfPipe32(m_submit_id, command, dst, data);
}
};
@@ -1242,7 +1192,7 @@ void CommandProcessor::WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_wr
SynchronizeGpu();
Sync::ReadGds(*m_renderer.GetBufferCache().GetGdsBuffer(), dst, value & 0xffffu,
value >> 16u);
Sync::WriteAtEndOfPipeGds32(m_submit_id, CurrentBuffer(), dst, value & 0xffffu,
Sync::WriteAtEndOfPipeGds32(m_submit_id, command, dst, value & 0xffffu,
value >> 16u);
if (with_interrupt) {
m_renderer.TriggerInterrupt(m_interrupt_event_id, interrupt_context_id);
@@ -1271,18 +1221,18 @@ void CommandProcessor::WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_wr
if (with_interrupt) {
if (with_writeback) {
Sync::WriteAtEndOfPipeWithInterruptWriteBack64(
m_submit_id, CurrentBuffer(), dst, value, m_interrupt_event_id,
m_submit_id, command, dst, value, m_interrupt_event_id,
interrupt_context_id);
} else {
Sync::WriteAtEndOfPipeWithInterrupt64(m_submit_id, CurrentBuffer(), dst,
Sync::WriteAtEndOfPipeWithInterrupt64(m_submit_id, command, dst,
value, m_interrupt_event_id,
interrupt_context_id);
}
} else if (with_writeback) {
Sync::WriteAtEndOfPipeWithWriteBack64(m_submit_id, CurrentBuffer(), dst,
Sync::WriteAtEndOfPipeWithWriteBack64(m_submit_id, command, dst,
value);
} else {
Sync::WriteAtEndOfPipe64(m_submit_id, CurrentBuffer(), dst, value);
Sync::WriteAtEndOfPipe64(m_submit_id, command, dst, value);
}
};
@@ -1363,10 +1313,10 @@ void CommandProcessor::WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_wr
(eop_event_type == 0x28 && event_index == 0x00)) {
if (with_interrupt) {
Sync::WriteAtEndOfPipeWithInterrupt64(
m_submit_id, CurrentBuffer(), dst, clock, m_interrupt_event_id,
m_submit_id, command, dst, clock, m_interrupt_event_id,
interrupt_context_id);
} else {
Sync::WriteAtEndOfPipeClockCounter(m_submit_id, CurrentBuffer(),
Sync::WriteAtEndOfPipeClockCounter(m_submit_id, command,
dst, clock);
}
return;
@@ -1378,11 +1328,11 @@ void CommandProcessor::WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_wr
(eop_event_type == 0x28 && event_index == 0x00)) {
if (with_interrupt) {
Sync::WriteAtEndOfPipeWithInterruptWriteBack64(
m_submit_id, CurrentBuffer(), dst, clock, m_interrupt_event_id,
m_submit_id, command, dst, clock, m_interrupt_event_id,
interrupt_context_id);
} else {
Sync::WriteAtEndOfPipeClockCounterWithWriteBack(
m_submit_id, CurrentBuffer(), dst, clock);
m_submit_id, command, dst, clock);
}
return;
}
@@ -1420,8 +1370,6 @@ void CommandProcessor::WriteAtEndOfPipe64(uint32_t cache_policy, uint32_t event_
}
void CommandProcessor::EmitGlobalBarrier() {
CheckBuffer();
Common::LockGuard lock(m_renderer.GetMutex());
vk::MemoryBarrier2 barrier {};
@@ -1438,8 +1386,6 @@ void CommandProcessor::EmitGlobalBarrier() {
}
void CommandProcessor::TriggerEopEventAtEndOfPipe(uint32_t interrupt_context_id) {
CheckBuffer();
Sync::TriggerEopEventAtEndOfPipe(CurrentBuffer(), m_interrupt_event_id, interrupt_context_id);
}
@@ -1523,8 +1469,6 @@ void CommandProcessor::TriggerEvent(uint32_t event_type, uint32_t event_index,
}
void CommandProcessor::Flip() {
CheckBuffer();
if (GraphicsRunDebugDumpEnabled()) {
LOGF("CommandProcessor::Flip()\n");
}
@@ -1538,7 +1482,7 @@ void CommandProcessor::Flip() {
}
void CommandProcessor::Flip(void* dst_gpu_addr, uint32_t value) {
CheckBuffer();
auto& command = CurrentBuffer();
if (GraphicsRunDebugDumpEnabled()) {
LOGF("CommandProcessor::Flip()\n"
@@ -1548,7 +1492,6 @@ void CommandProcessor::Flip(void* dst_gpu_addr, uint32_t value) {
}
std::memcpy(dst_gpu_addr, &value, sizeof(value));
auto& command = CurrentBuffer();
auto request = Sync::PrepareVideoOutFlip(command, m_flip.handle, m_flip.index, m_flip.flip_mode,
m_flip.flip_arg);
Sync::WriteAtEndOfPipeWithFlip32(m_submit_id, command, static_cast<uint32_t*>(dst_gpu_addr),
@@ -1559,7 +1502,7 @@ void CommandProcessor::Flip(void* dst_gpu_addr, uint32_t value) {
void CommandProcessor::FlipWithInterrupt(uint32_t eop_event_type, uint32_t cache_action,
void* dst_gpu_addr, uint32_t value) {
CheckBuffer();
auto& command = CurrentBuffer();
if (GraphicsRunDebugDumpEnabled()) {
LOGF("CommandProcessor::FlipWithInterrupt()\n"
@@ -1574,7 +1517,6 @@ void CommandProcessor::FlipWithInterrupt(uint32_t eop_event_type, uint32_t cache
EXIT("unknown event type\n");
}
std::memcpy(dst_gpu_addr, &value, sizeof(value));
auto& command = CurrentBuffer();
auto request = Sync::PrepareVideoOutFlip(command, m_flip.handle, m_flip.index, m_flip.flip_mode,
m_flip.flip_arg);
Sync::WriteAtEndOfPipeWithInterruptWriteBackFlip32(
@@ -1584,7 +1526,7 @@ void CommandProcessor::FlipWithInterrupt(uint32_t eop_event_type, uint32_t cache
}
void CommandProcessor::PrepareCpuFlip(uint64_t request_id) {
CheckBuffer();
auto& command = CurrentBuffer();
if (g_current_processor != nullptr) {
EXIT("invalid graphics-thread CPU flip preparation\n");
}
@@ -1594,7 +1536,7 @@ void CommandProcessor::PrepareCpuFlip(uint64_t request_id) {
};
ProcessorScope processor_scope(*this);
m_renderer.GetVideoOut().PrepareFlip(request_id, CurrentBuffer());
m_renderer.GetVideoOut().PrepareFlip(request_id, command);
GetScheduler().Flush();
m_renderer.GetVideoOut().CompleteFlip(request_id);
}
+1
View File
@@ -910,6 +910,7 @@ constexpr uint32_t COMPUTE_PGM_RSRC1_WGP_MODE_MASK = 0x1;
constexpr uint32_t COMPUTE_PGM_RSRC1_FWD_PROGRESS_SHIFT = 31;
constexpr uint32_t COMPUTE_PGM_RSRC1_FWD_PROGRESS_MASK = 0x1;
constexpr uint32_t COMPUTE_DISPATCH_INITIATOR_USE_THREAD_DIMENSIONS = 1u << 5u;
constexpr uint32_t COMPUTE_DISPATCH_INITIATOR_CS_W32_EN_SHIFT = 15;
constexpr uint32_t COMPUTE_DISPATCH_INITIATOR_CS_W32_EN_MASK = 0x1;
+2 -2
View File
@@ -129,7 +129,7 @@ bool BufferCache::DownloadBufferMemory(Buffer& buffer, uint64_t vaddr, uint64_t
const auto [mapped, offset] = m_download_buffer.Map(total_size, 64);
if (mapped == nullptr) {
EXIT("BufferCache: download exceeds 32 MiB staging buffer capacity\n");
EXIT("BufferCache: download exceeds 64 MiB staging buffer capacity\n");
}
m_download_buffer.Commit();
for (auto& copy: copies) {
@@ -183,7 +183,7 @@ BufferCache::BufferCache(GraphicContext& graphics, CommandScheduler& scheduler,
m_memory_tracker(page_manager),
m_staging_buffer(graphics, scheduler, MemoryUsage::Upload, 512 * MiB),
m_stream_buffer(graphics, scheduler, MemoryUsage::Stream, 64 * MiB),
m_download_buffer(graphics, scheduler, MemoryUsage::Download, 32 * MiB),
m_download_buffer(graphics, scheduler, MemoryUsage::Download, 64 * MiB),
m_device_buffer(graphics, scheduler, MemoryUsage::DeviceLocal, 128 * MiB),
m_texture_cache(texture_cache) {
std::memset(m_gds_buffer.Mapped().data(), 0, static_cast<size_t>(m_gds_buffer.Size()));
+83 -31
View File
@@ -138,14 +138,15 @@ void NameImageBinding(GraphicContext& graphics, Image& image, vk::ImageView view
}
[[nodiscard]] std::vector<vk::BufferImageCopy> BuildDepthCopies(const ImageInfo& info,
uint64_t slice_stride) {
uint64_t slice_stride,
vk::ImageAspectFlags aspect) {
std::vector<vk::BufferImageCopy> copies(info.resources.layers);
for (uint32_t layer = 0; layer < info.resources.layers; ++layer) {
auto& copy = copies[layer];
copy.bufferOffset = slice_stride * layer;
copy.bufferRowLength = info.pitch;
copy.bufferImageHeight = info.extent.height;
copy.imageSubresource = {vk::ImageAspectFlagBits::eDepth, 0, layer, 1};
copy.imageSubresource = {aspect, 0, layer, 1};
copy.imageExtent = {info.extent.width, info.extent.height, 1};
}
return copies;
@@ -1031,16 +1032,17 @@ TextureCache::ImageDownload TextureCache::BuildDownload(const Image& image) cons
}
void TextureCache::UploadImage(Image& image, Buffer& source, uint64_t source_offset) {
const auto& info = image.info;
const auto binding = UploadBinding(image);
auto& destination = image.depth_id ? m_slot_images[image.depth_id] : image;
const auto binding = image.depth_id ? BindingType::DepthTarget : UploadBinding(image);
const auto upload = [&](std::vector<vk::BufferImageCopy>& copies, TileManager::Result linear) {
for (auto& copy: copies) {
copy.bufferOffset += linear.offset;
}
image.Upload(copies, linear.buffer, linear.offset, linear.size);
destination.Upload(copies, linear.buffer, linear.offset, linear.size);
};
if (binding != BindingType::DepthTarget) {
const auto& info = image.info;
auto transfer = BuildTextureTransfer(image, binding, TransferDirection::Upload);
if (!transfer.valid) {
EXIT("TextureCache: invalid texture upload: binding=%u addr=0x%016" PRIx64
@@ -1064,21 +1066,32 @@ void TextureCache::UploadImage(Image& image, Buffer& source, uint64_t source_off
return;
}
if (info.samples != 1 || image.backing.samples != 1 ||
// The stencil plane has its own row pitch and shares the native image
// with the depth plane.
auto info = destination.info;
if (image.depth_id) {
info.data = image.info.data;
info.guest_format = Prospero::BufferFormat::k8UInt;
info.bytes_per_block = 1;
if (info.IsTiled()) info.pitch = TileGetDepthPitch(info.extent.width, 1, 0);
}
if (info.samples != 1 || destination.backing.samples != 1 ||
info.resources.layers == 0 || info.data.size % info.resources.layers != 0 ||
Prospero::NumBytesPerElement(info.guest_format) != info.bytes_per_block) {
EXIT("TextureCache: invalid depth upload\n");
}
const auto layers = info.resources.layers;
const auto full_slice_size = info.data.size / layers;
auto copies = BuildDepthCopies(info, full_slice_size);
auto copies = BuildDepthCopies(info, full_slice_size, image.depth_id
? vk::ImageAspectFlagBits::eStencil
: vk::ImageAspectFlagBits::eDepth);
TileManager::Result linear {source.Handle(), source_offset, source.Size() - source_offset};
if (info.IsTiled()) {
const auto tiles = BuildDepthTiles(info);
linear =
m_tiler.Detile(source.Handle(), source_offset, info.data.size, info.data.size, tiles);
}
const auto transfer_bytes = DepthAspectTransferBytes(info.pixel_format);
const auto transfer_bytes = image.depth_id ? 1u : DepthAspectTransferBytes(info.pixel_format);
if (transfer_bytes != info.bytes_per_block) {
const uint64_t texels_per_slice = static_cast<uint64_t>(info.pitch) * info.extent.height;
EXIT_NOT_IMPLEMENTED(info.bytes_per_block != sizeof(uint16_t) ||
@@ -1197,13 +1210,20 @@ void TextureCache::MaterializeDccClear(ImageId id, const ImageDesc& desc,
}
// Native expanded keys own consumption. Existing buffer tracking publishes this CPU
// write to future GPU readers; FillBuffer can fault and must run outside the texture lock.
m_buffer_cache.FillBuffer(address, slice_size, UINT32_MAX, false);
if (desc.type != BindingType::VideoOut) {
m_buffer_cache.FillBuffer(address, slice_size, UINT32_MAX, false);
}
}
}
void TextureCache::RefreshImage(ImageId id) {
TrackImage(id);
auto& image = m_slot_images[id];
if (image.depth_id &&
(m_slot_images[image.depth_id].info.metadata.stencil_compressed ||
m_slot_images[image.depth_id].info.samples != 1)) {
return;
}
TrackImage(id);
if (image.IsMaybeCpuDirty()) {
const auto hash = image.HashGuestEdges();
if (image.NeedsMaybeCpuHash()) {
@@ -1225,7 +1245,7 @@ void TextureCache::RefreshImage(ImageId id) {
InitializeImage(id);
}
void TextureCache::AssociateStencil(ImageId depth_id, GuestRange stencil) {
ImageId TextureCache::AssociateStencil(ImageId depth_id, GuestRange stencil) {
if (!stencil.Valid()) {
EXIT("TextureCache: invalid stencil association range\n");
}
@@ -1251,6 +1271,7 @@ void TextureCache::AssociateStencil(ImageId depth_id, GuestRange stencil) {
auto& record = m_slot_images[association];
TouchImage(record);
record.depth_id = depth_id;
return association;
}
ImageId TextureCache::FindImage(ImageDesc& desc, bool exact_format) {
@@ -1312,16 +1333,6 @@ ImageId TextureCache::FindImage(ImageDesc& desc, bool exact_format) {
}
}
auto& image = m_slot_images[result];
if (desc.type == BindingType::VideoOut &&
desc.info.metadata.compression != VideoOutCompression::Uncompressed) {
const bool guest_dirty = image.IsBufferModified() || image.IsCpuDirty();
const bool native_current =
(image.usage.render_target || image.IsGpuModified()) && !guest_dirty;
if (!native_current) {
EXIT("TextureCache: compressed video-out read requires clean native GPU "
"contents\n");
}
}
if (view_mip >= 0) {
desc.view_info.base_level = static_cast<uint32_t>(view_mip);
}
@@ -1332,6 +1343,18 @@ ImageId TextureCache::FindImage(ImageDesc& desc, bool exact_format) {
TouchImage(image);
}
MaterializeDccClear(result, desc, metadata_base_layer);
if (desc.type == BindingType::VideoOut &&
desc.info.metadata.compression != VideoOutCompression::Uncompressed) {
std::scoped_lock lock {m_lock};
const auto& image = m_slot_images[result];
const bool guest_dirty = image.IsBufferModified() || image.IsCpuDirty();
const bool native_current =
(image.usage.render_target || image.IsGpuModified()) && !guest_dirty;
if (!native_current) {
EXIT("TextureCache: compressed video-out read requires clean native GPU "
"contents\n");
}
}
return result;
}
@@ -1396,6 +1419,19 @@ vk::ImageView TextureCache::FindTexture(ImageId id, const ImageDesc& desc) {
}
if (!image.info.data.Empty()) {
RefreshImage(id);
if (image.info.HasStencil() &&
desc.info.data.address >= image.info.stencil.address &&
desc.info.data.End() <= image.info.stencil.End()) {
for (const auto stencil_id:
FindImagesInRegion(image.info.stencil.address, image.info.stencil.size, false)) {
const auto* stencil = m_slot_images.try_get(stencil_id);
if (stencil != nullptr && stencil->depth_id == id &&
stencil->info.data == image.info.stencil) {
RefreshImage(stencil_id);
break;
}
}
}
}
switch (desc.type) {
case BindingType::Texture: break;
@@ -1444,16 +1480,17 @@ vk::ImageView TextureCache::FindDepthTarget(ImageId id, const ImageDesc& desc) {
TouchImage(image);
image.MarkGpuModified();
image.usage.depth_target = true;
RefreshImage(id);
image.info.stencil = desc.info.stencil;
image.info.metadata = desc.info.metadata;
if (desc.info.HasMetadata()) {
image.info.metadata = desc.info.metadata;
m_surface_metas.emplace(desc.info.metadata.range.address,
MetaDataInfo {.type = MetaDataInfo::Type::HTile,
.clear_mask = image.info.htile_clear_mask});
}
RefreshImage(id);
CommitGpuWrite(image);
if (desc.info.HasStencil()) {
AssociateStencil(id, desc.info.stencil);
RefreshImage(AssociateStencil(id, desc.info.stencil));
}
const auto view = image.FindView(desc.view_info);
NameImageBinding(m_graphics, image, view, desc.type, desc.view_info);
@@ -1468,11 +1505,16 @@ void TextureCache::MarkGpuWritten(ImageId id) {
}
TrackImage(id);
CommitGpuWrite(image);
if (image.info.HasStencil()) {
const auto stencil_id = AssociateStencil(id, image.info.stencil);
TrackImage(stencil_id);
CommitGpuWrite(m_slot_images[stencil_id]);
}
}
void TextureCache::CommitGpuWrite(Image& image) {
if (image.depth_id || image.backing.image == nullptr) {
EXIT("TextureCache: stencil association cannot own image contents\n");
if (!image.depth_id && image.backing.image == nullptr) {
EXIT("TextureCache: GPU writes require a native image or stencil association\n");
}
image.ClearBufferModified();
if (image.IsCpuDirty()) {
@@ -1556,9 +1598,10 @@ void TextureCache::ClearImage(CommandBuffer& command, ImageId id, vk::Format for
range.layerCount == 0 || range.baseArrayLayer >= layers ||
range.layerCount > layers - range.baseArrayLayer ||
(range.aspectMask & aspects) != range.aspectMask);
const bool full_image = range.aspectMask == aspects && range.baseMipLevel == 0 &&
range.levelCount == image.info.resources.levels &&
range.baseArrayLayer == 0 && range.layerCount == layers;
const bool full_subresources = range.baseMipLevel == 0 &&
range.levelCount == image.info.resources.levels &&
range.baseArrayLayer == 0 && range.layerCount == layers;
const bool full_image = range.aspectMask == aspects && full_subresources;
TrackImage(id);
if (!full_image && (image.IsBufferModified() || image.IsCpuDirty())) {
InitializeImage(id);
@@ -1566,6 +1609,15 @@ void TextureCache::ClearImage(CommandBuffer& command, ImageId id, vk::Format for
EXIT("TextureCache: image clear retained guest ownership\n");
}
}
if (image.info.HasStencil() && (range.aspectMask & vk::ImageAspectFlagBits::eStencil)) {
const auto stencil_id = AssociateStencil(id, image.info.stencil);
if (!full_subresources) {
RefreshImage(stencil_id);
} else {
TrackImage(stencil_id);
CommitGpuWrite(m_slot_images[stencil_id]);
}
}
command.EndRendering();
// Transfer clears use the backing format; aliased clears must encode through their view.
if (format != image.backing.format || (image.info.IsVolume() && !full_image)) {
@@ -1638,7 +1690,7 @@ void TextureCache::DownloadDepth(Image& image, Buffer& destination, uint64_t des
EXIT_NOT_IMPLEMENTED(transfer_slice > UINT64_MAX / layers);
const uint64_t transfer_size = transfer_slice * layers;
EXIT_NOT_IMPLEMENTED(guest_slice > full_slice_size);
auto copies = BuildDepthCopies(info, full_slice_size);
auto copies = BuildDepthCopies(info, full_slice_size, vk::ImageAspectFlagBits::eDepth);
if (transfer_bytes == info.bytes_per_block) {
if (!info.IsTiled()) {
for (auto& copy: copies) {
@@ -1831,7 +1883,7 @@ void TextureCache::InvalidateMemoryFromGPU(uint64_t address, uint64_t size) {
std::scoped_lock lock {m_lock};
for (const auto id: FindImagesInRegion(address, size, true)) {
auto& image = m_slot_images[id];
if (image.depth_id || !image.Overlaps(address, size)) {
if (!image.Overlaps(address, size)) {
continue;
}
if (image.IsGpuModified()) {
+1 -1
View File
@@ -157,7 +157,7 @@ private:
void RefreshCopySource(ImageId id);
[[nodiscard]] bool CopyD16(Image& destination, Image& source);
void CopyImage(ImageId destination, ImageId source);
void AssociateStencil(ImageId depth, GuestRange stencil);
[[nodiscard]] ImageId AssociateStencil(ImageId depth, GuestRange stencil);
void CopyImageMip(ImageId destination, ImageId source, uint32_t mip, uint32_t layer);
void ValidateImageDesc(const ImageDesc& desc) const;
+2 -3
View File
@@ -209,17 +209,16 @@ static void RtCheck(const HW::RenderTarget& rt) {
}
if (rt.info.fmask_compression_enable) {
EXIT_NOT_IMPLEMENTED(rt.attrib.num_samples == 0 && rt.attrib.num_fragments == 0);
// Native MSAA stores expanded samples, independent of FMASK metadata compression.
static bool logged = false;
if (!logged) {
LOGF("RenderTarget: using native Vulkan MSAA without guest FMASK metadata, "
LOGF("RenderTarget: using expanded native Vulkan MSAA samples, "
"fmask=0x%016" PRIx64 "\n",
rt.fmask.addr);
logged = true;
}
}
EXIT_NOT_IMPLEMENTED(rt.info.fmask_data_compression_disable != false);
if (rt.info.cmask_fast_clear_enable || rt.info.dcc_compression_enable) {
static bool logged = false;
if (!logged) {
@@ -41,13 +41,42 @@ struct RenderDepthInfo {
[[nodiscard]] vk::ImageAspectFlags AttachmentWriteAspects() const;
};
inline vk::ImageAspectFlags DepthFeedbackAspects(vk::ImageAspectFlags draw_writes,
const ImageViewInfo& target,
const ImageViewInfo& sampled) {
if (!ImageRangeOverlaps(target.base_level, target.level_count, sampled.base_level,
sampled.level_count) ||
!ImageRangeOverlaps(target.base_layer, target.layer_count, sampled.base_layer,
sampled.layer_count)) {
return {};
}
return draw_writes & sampled.aspect;
}
inline vk::ImageAspectFlags DepthReadableAspects(vk::ImageLayout layout) {
switch (layout) {
case vk::ImageLayout::eDepthReadOnlyOptimal:
case vk::ImageLayout::eDepthReadOnlyStencilAttachmentOptimal:
return vk::ImageAspectFlagBits::eDepth;
case vk::ImageLayout::eStencilReadOnlyOptimal:
case vk::ImageLayout::eDepthAttachmentStencilReadOnlyOptimal:
return vk::ImageAspectFlagBits::eStencil;
case vk::ImageLayout::eDepthStencilReadOnlyOptimal:
case vk::ImageLayout::eAttachmentFeedbackLoopOptimalEXT:
case vk::ImageLayout::eGeneral:
return vk::ImageAspectFlagBits::eDepth | vk::ImageAspectFlagBits::eStencil;
default:
return {};
}
}
inline vk::ImageLayout depth_attachment_layout(const RenderDepthInfo& depth) {
const auto available = ImageViewOps::DepthAspectMask(depth.desc.view_info.format);
const auto writes = depth.AttachmentWriteAspects();
const bool has_depth = static_cast<bool>(available & vk::ImageAspectFlagBits::eDepth);
const bool has_stencil = static_cast<bool>(available & vk::ImageAspectFlagBits::eStencil);
// LoadOp clears are separate from guest depth writes.
const bool depth_write = depth.depth_write_enable;
// The attachment layout must permit load clears as well as draw writes.
const bool depth_write = static_cast<bool>(writes & vk::ImageAspectFlagBits::eDepth);
const bool stencil_write = static_cast<bool>(writes & vk::ImageAspectFlagBits::eStencil);
if (!has_stencil) {
return depth_write ? vk::ImageLayout::eDepthAttachmentOptimal
@@ -329,7 +329,9 @@ struct VideoOutFormatPolicy {
VideoOutPixelFormatInfo info;
};
inline constexpr std::array<VideoOutFormatPolicy, 6> VIDEO_OUT_FORMAT_POLICIES {{
inline constexpr uint64_t VIDEO_OUT_PIXEL_FORMAT_R10_G10_B10_A2_BT2100_PQ = 0x8100070422000000ull;
inline constexpr std::array<VideoOutFormatPolicy, 7> VIDEO_OUT_FORMAT_POLICIES {{
{0x8000000022000000ull,
{vk::Format::eR8G8B8A8Srgb, Prospero::BufferFormat::k8_8_8_8Srgb, 4, false}},
{0x8000000000000000ull,
@@ -338,6 +340,8 @@ inline constexpr std::array<VideoOutFormatPolicy, 6> VIDEO_OUT_FORMAT_POLICIES {
{vk::Format::eA2B10G10R10UnormPack32, Prospero::BufferFormat::k10_10_10_2UNorm, 4, false}},
{0x8100000000000000ull,
{vk::Format::eA2R10G10B10UnormPack32, Prospero::BufferFormat::k10_10_10_2UNorm, 4, false}},
{VIDEO_OUT_PIXEL_FORMAT_R10_G10_B10_A2_BT2100_PQ,
{vk::Format::eA2B10G10R10UnormPack32, Prospero::BufferFormat::k10_10_10_2UNorm, 4, false}},
{0xc001000622000000ull,
{vk::Format::eR16G16B16A16Sfloat, Prospero::BufferFormat::k16_16_16_16Float, 8, false}},
{0xc001000600000000ull,
@@ -17,6 +17,7 @@
#include "graphics/host_gpu/hostMemory.h"
#include "graphics/host_gpu/renderer/colorRenderTarget.h"
#include "graphics/host_gpu/renderer/debug.h"
#include "graphics/host_gpu/renderer/depthRenderTarget.h"
#include "graphics/host_gpu/renderer/image/imageView.h"
#include "graphics/host_gpu/renderer/image/textureCommon.h"
#include "graphics/host_gpu/renderer/pipeline/shaderResourceBarrier.h"
@@ -184,17 +185,18 @@ bool IsSupportedDepthTextureEncoding(const ShaderTextureResource& descriptor, bo
}
const bool full = common && descriptor.fields[4] == field4_expected &&
descriptor.fields[5] == field5_expected;
if (!full || (descriptor.fields[6] == 0 && descriptor.fields[7] != 0) ||
if (!full ||
(descriptor.MsaaDepth() && !IsMultisampledTexture(descriptor.Type()))) {
return false;
}
if (descriptor.fields[6] == 0) {
const auto metadata_control = descriptor.fields[6] & 0x00ffffffu;
if (metadata_control == 0) {
return true;
}
constexpr uint32_t htile_control = 0x00280000u;
const uint32_t expected_control = htile_control | (descriptor.MsaaDepth() ? (1u << 10u) : 0u);
const auto metadata_addr = descriptor.MetaAddr() << 8u;
return (descriptor.fields[6] & 0x00ffffffu) == expected_control && metadata_addr != 0 &&
return metadata_control == expected_control && metadata_addr != 0 &&
metadata_addr < TRACKER_ADDRESS_SIZE && (metadata_addr & 0x7fffu) == 0 &&
descriptor.TileMode() == Prospero::TileMode::kDepth;
}
@@ -252,10 +254,10 @@ static bool IsSupportedStorageTextureDescriptor(const ShaderRecompiler::IR::Imag
(is_color_2d_array && descriptor.BaseArray5() <= descriptor.Depth());
const bool is_2d =
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2D && valid_2d_slice;
// Storage cube coordinates address individual faces, including partial cube views.
const bool is_cube = resource.cube && descriptor.Type() == Prospero::ImageType::kCube &&
descriptor.Width5() == descriptor.Height5() &&
descriptor.BaseArray5() <= descriptor.Depth() &&
(descriptor.Depth() - descriptor.BaseArray5() + 1u) % 6u == 0;
descriptor.BaseArray5() <= descriptor.Depth();
const bool is_2d_array =
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2DArray &&
((!resource.cube && is_color_2d_array && descriptor.BaseArray5() <= descriptor.Depth()) ||
@@ -550,8 +552,14 @@ TextureBinding RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageR
const bool multisampled = IsMultisampledTexture(type);
const auto max_mip = resource.r128 ? last_level : descriptor.MaxMip();
const auto physical_levels = multisampled ? 1u : static_cast<uint32_t>(max_mip) + 1u;
// IMAGE_STORE addresses BASE_LEVEL; only IMAGE_STORE_MIP selects other view mips.
const bool single_storage_mip =
storage && resource.mip_mode != ShaderRecompiler::IR::ImageMipMode::DynamicStorage;
const auto view_levels = multisampled || single_storage_mip
? 1u
: static_cast<uint32_t>(last_level - base_level) + 1u;
const auto levels =
multisampled ? 1u : std::max(physical_levels, static_cast<uint32_t>(last_level) + 1u);
multisampled ? 1u : std::max(physical_levels, base_level + view_levels);
const auto tile = descriptor.TileMode();
const bool depth_tile = tile == Prospero::TileMode::kDepth;
const bool msaa_tile = depth_tile || tile == Prospero::TileMode::kRenderTarget;
@@ -574,8 +582,6 @@ TextureBinding RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageR
descriptor.fields[6], descriptor.fields[7]);
}
const auto samples = multisampled ? 1u << last_level : 1u;
const auto view_levels =
multisampled ? 1u : static_cast<uint32_t>(last_level - base_level) + 1u;
const auto depth = static_cast<uint32_t>(descriptor.Depth()) + 1u;
const auto format = descriptor.Format();
const auto surface_format = TextureGetSurfaceFormatInfo(format);
@@ -744,18 +750,27 @@ void RenderExecutor::ResetBindings() {
m_bound_images.clear();
}
PreparedBindings RenderExecutor::PrepareBindings(const ShaderStageRuntime& runtime) {
void RenderExecutor::PrepareBindings(const ShaderStageRuntime& runtime,
PreparedBindings& prepared) {
KYTY_PROFILER_FUNCTION();
EXIT_IF(!runtime);
const auto& program = *runtime.program;
const auto& snapshot = runtime.resources;
PreparedBindings prepared;
const auto& snapshot = *runtime.resources;
prepared.runtime = &runtime;
prepared.images.reserve(program.info.images.size());
prepared.gds = {nullptr, 0, VK_WHOLE_SIZE};
prepared.flattened_srt = {};
prepared.shader_data_buffer = {};
prepared.buffer_sources.clear();
prepared.buffers.clear();
prepared.images.resize(program.info.images.size());
prepared.samplers.clear();
prepared.shader_data.clear();
for (uint32_t i = 0; i < program.info.images.size(); i++) {
auto binding = ResolveTexture(program.info.images[i], snapshot.images[i]);
BindImage(binding.image_id, binding.desc.type == TextureCache::BindingType::Storage);
prepared.images.push_back(std::move(binding));
binding.mip_views.swap(prepared.images[i].mip_views);
binding.mip_views.clear();
prepared.images[i] = std::move(binding);
}
prepared.samplers.reserve(program.info.samplers.size());
for (uint32_t i = 0; i < program.info.samplers.size(); i++) {
@@ -770,14 +785,13 @@ PreparedBindings RenderExecutor::PrepareBindings(const ShaderStageRuntime& runti
program.bindings, ShaderRecompiler::IR::DescriptorBindingKind::Gds) != nullptr) {
prepared.gds.buffer = m_context.GetBufferCache().GetGdsBuffer()->Handle();
}
return prepared;
}
void RenderExecutor::FindBuffers(PreparedBindings& prepared) {
KYTY_PROFILER_FUNCTION();
EXIT_IF(prepared.runtime == nullptr || !*prepared.runtime);
const auto& program = *prepared.runtime->program;
const auto& snapshot = prepared.runtime->resources;
const auto& snapshot = *prepared.runtime->resources;
auto& cache = m_context.GetBufferCache();
prepared.buffer_sources.clear();
@@ -799,7 +813,7 @@ void RenderExecutor::RebindBuffers(PreparedBindings& prepared) {
KYTY_PROFILER_FUNCTION();
EXIT_IF(prepared.runtime == nullptr || !*prepared.runtime);
const auto& program = *prepared.runtime->program;
const auto& snapshot = prepared.runtime->resources;
const auto& snapshot = *prepared.runtime->resources;
const auto& layout = program.bindings;
EXIT_IF(prepared.buffer_sources.size() != program.info.buffers.size());
@@ -834,7 +848,7 @@ void RenderExecutor::RebindImages(PreparedBindings& prepared) {
KYTY_PROFILER_FUNCTION();
EXIT_IF(prepared.runtime == nullptr || !*prepared.runtime);
const auto& program = *prepared.runtime->program;
const auto& snapshot = prepared.runtime->resources;
const auto& snapshot = *prepared.runtime->resources;
auto& images = prepared.images;
EXIT_IF(images.size() != program.info.images.size());
auto& texture_cache = m_context.GetTextureCache();
@@ -866,11 +880,7 @@ void RenderExecutor::RebindImages(PreparedBindings& prepared) {
}
binding.image_view = binding.mip_views.front();
} else {
auto desc = binding.desc;
if (desc.type == TextureCache::BindingType::Storage) {
desc.view_info.level_count = 1;
}
binding.image_view = texture_cache.FindTexture(binding.image_id, desc);
binding.image_view = texture_cache.FindTexture(binding.image_id, binding.desc);
}
auto& image = texture_cache.GetImage(binding.image_id);
const bool storage = binding.desc.type == TextureCache::BindingType::Storage;
@@ -988,19 +998,7 @@ void RenderExecutor::CommitBindings(CommandBuffer& buffer,
std::ranges::find(image.views, binding.image_view, &CachedImageView::view);
EXIT_IF(storage || host_view == image.views.end());
const auto aspect = host_view->info.aspect;
const bool depth_feedback =
layout == vk::ImageLayout::eAttachmentFeedbackLoopOptimalEXT &&
program.stage == ShaderType::Pixel;
const bool depth_read =
depth_feedback || layout == vk::ImageLayout::eDepthReadOnlyOptimal ||
layout == vk::ImageLayout::eDepthStencilReadOnlyOptimal ||
layout == vk::ImageLayout::eDepthReadOnlyStencilAttachmentOptimal;
const bool stencil_read =
layout == vk::ImageLayout::eStencilReadOnlyOptimal ||
layout == vk::ImageLayout::eDepthStencilReadOnlyOptimal ||
layout == vk::ImageLayout::eDepthAttachmentStencilReadOnlyOptimal;
if ((aspect & vk::ImageAspectFlagBits::eDepth && !depth_read) ||
(aspect & vk::ImageAspectFlagBits::eStencil && !stencil_read)) {
if (aspect & ~DepthReadableAspects(layout)) {
EXIT("sampling a writable depth/stencil attachment aspect\n");
}
}
@@ -92,9 +92,9 @@ void PipelineCacheLog(fmt::format_string<Args...> format, Args&&... args) {
Log::WriteToConsoleAndLog(message);
}
bool ReadShaderGuestMemory(void*, uint64_t address, uint32_t* value) {
return value != nullptr &&
Libs::LibKernel::Memory::TryReadGpuCleanBacking(address, value, sizeof(*value));
bool ReadShaderGuestMemory(void*, uint64_t address, std::span<uint32_t> values) {
return !values.empty() &&
Libs::LibKernel::Memory::TryReadGpuCleanBacking(address, values.data(), values.size_bytes());
}
void DumpShaderSpirv(const char* stage_name, uint64_t shader_hash,
@@ -200,8 +200,11 @@ struct PipelineCache::ProgramCache {
permutations.reserve(8);
}
ShaderRecompiler::IR::ResourcePlan resource_plan;
std::vector<Permutation> permutations;
ShaderRecompiler::IR::ResourcePlan resource_plan;
ShaderRecompiler::IR::ResourceSnapshot resources;
ShaderRecompiler::IR::ResourceSpecialization specialization;
std::vector<Permutation> permutations;
bool skip_dispatch = false;
};
struct ProgramKeyHash {
@@ -274,33 +277,36 @@ struct PipelineCache::ProgramCache {
stage = ShaderType::Compute;
}
const auto user_data = std::span(params.user_data).first(params.user_data_count);
lookup_key.stage = stage;
lookup_key.hash = params.hash;
lookup_key.user_data_count = static_cast<uint32_t>(params.user_data.size());
lookup_key.user_data_count = params.user_data_count;
lookup_key.code_size = static_cast<uint32_t>(params.code.size());
BuildStageStaticKey(input_info, lookup_key.static_state);
auto entry = programs.find(lookup_key);
ShaderRecompiler::IR::ResourceSnapshot resources;
ShaderRecompiler::IR::ResourceSpecialization specialization;
if (entry != programs.end() && entry->second.skip_dispatch) {
return {};
}
const ShaderRecompiler::IR::SrtRuntime runtime {
.user_data = params.user_data,
.user_data = user_data,
.shader_base = params.Base(),
.read_specialization_memory = ReadShaderGuestMemory,
};
if (entry != programs.end()) {
EXIT_IF(!ShaderRecompiler::IR::MaterializeResources(
entry->second.resource_plan, runtime, resources, specialization));
entry->second.resource_plan, runtime, entry->second.resources,
entry->second.specialization));
if (const auto permutation = std::ranges::find_if(
entry->second.permutations, [&](const Permutation& candidate) {
const auto& layout = candidate.program.bindings;
return layout.push_data_start_dword ==
ShaderRecompiler::IR::PushData::StartFor(
push_data_cursor, layout.ShaderDataDwords()) &&
candidate.specialization == specialization;
candidate.specialization == entry->second.specialization;
});
permutation != entry->second.permutations.end()) {
input_info.stage = {.program = &permutation->program,
.resources = std::move(resources)};
.resources = &entry->second.resources};
permutation->program.bindings.AdvancePushData(push_data_cursor);
return permutation->handle;
}
@@ -328,7 +334,7 @@ struct PipelineCache::ProgramCache {
ShaderRecompiler::CompileOptions options;
options.stage = stage;
options.shader_hash = params.hash;
options.user_data = params.user_data;
options.user_data = user_data;
options.back_code = params.back_code;
options.dump_ir = Config::GetShaderLogDirection() != Config::LogDirection::Silent;
options.early_dump = options.dump_ir;
@@ -337,6 +343,7 @@ struct PipelineCache::ProgramCache {
if constexpr (std::is_same_v<InputInfo, ShaderVertexInputInfo>) {
options.user_data_base = 8;
options.wave_size = input_info.wave_size;
if (stage == ShaderType::Mesh || stage == ShaderType::TessellationControl) {
options.user_data_base = 0;
options.wave_size = stage == ShaderType::Mesh ? input_info.mesh.wave_size : 64u;
@@ -345,16 +352,22 @@ struct PipelineCache::ProgramCache {
options.wave_size = input_info.wave_size;
}
auto translated = ShaderRecompiler::TranslateProgram(params.code, options);
if (translated.skip_dispatch) {
entry = programs.try_emplace(lookup_key, ShaderRecompiler::IR::ResourcePlan {}).first;
entry->second.skip_dispatch = true;
return {};
}
if (entry == programs.end()) {
auto resource_plan = ShaderRecompiler::IR::ExtractResourcePlan(translated.program);
EXIT_IF(!ShaderRecompiler::IR::MaterializeResources(resource_plan, runtime, resources,
specialization));
entry = programs.try_emplace(lookup_key, std::move(resource_plan)).first;
entry = programs.try_emplace(lookup_key,
ShaderRecompiler::IR::ExtractResourcePlan(translated.program)).first;
EXIT_IF(!ShaderRecompiler::IR::MaterializeResources(
entry->second.resource_plan, runtime, entry->second.resources,
entry->second.specialization));
}
entry->second.permutations.push_back(CompilePermutation(
params, options, std::move(translated), std::move(specialization), push_data_cursor));
params, options, std::move(translated), entry->second.specialization, push_data_cursor));
const auto& permutation = entry->second.permutations.back();
input_info.stage = {.program = &permutation.program, .resources = std::move(resources)};
input_info.stage = {.program = &permutation.program, .resources = &entry->second.resources};
permutation.program.bindings.AdvancePushData(push_data_cursor);
std::array<size_t, static_cast<size_t>(ShaderType::TessellationEvaluation) + 1> counts {};
@@ -90,7 +90,7 @@ vk::BufferMemoryBarrier MakeGdsDependency(vk::Buffer buffer) {
bool HasShaderBufferWrites(const ShaderStageRuntime& runtime) {
EXIT_IF(!runtime);
const auto& program = *runtime.program;
const auto& resources = runtime.resources;
const auto& resources = *runtime.resources;
EXIT_IF(resources.buffers.size() != program.info.buffers.size());
bool has_writes = false;
for (uint32_t i = 0; i < program.info.buffers.size(); i++) {
@@ -368,6 +368,8 @@ void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::Pipeline& p
// MoltenVK lacks VK_EXT_depth_clip_enable; omit the depth-clip struct on macOS and accept
// Vulkan's default depth clipping (enabled) instead of the PS5's clamp behavior.
#if !defined(__APPLE__)
// The DB clamps depth to the viewport range after polygon offset is applied.
rasterizer.depthClampEnable = VK_TRUE;
rasterizer.pNext = &clip_ext;
#endif
vk::PipelineRasterizationProvokingVertexStateCreateInfoEXT provoking_vertex {};
+8 -2
View File
@@ -45,6 +45,7 @@ enum class CommandBufferDebugOp : uint32_t {
EopFlip,
EopWriteBackFlip,
EopOnlyFlip,
DispatchIndirect,
Unknown,
};
@@ -156,8 +157,10 @@ public:
void DispatchDirect(uint64_t submit_id, CommandBuffer& buffer, uint32_t thread_group_x,
uint32_t thread_group_y, uint32_t thread_group_z, uint32_t mode);
void DispatchIndirect(uint64_t submit_id, CommandBuffer& buffer, uint64_t args_addr,
uint32_t mode);
[[nodiscard]] PreparedBindings PrepareBindings(const ShaderStageRuntime& runtime);
void PrepareBindings(const ShaderStageRuntime& runtime, PreparedBindings& prepared);
void FindBuffers(PreparedBindings& bindings);
void RebindBuffers(PreparedBindings& bindings);
void RebindImages(PreparedBindings& bindings);
@@ -193,7 +196,8 @@ private:
bool primitive_restart_enable);
[[nodiscard]] RenderState AcquireRenderTargets(CommandBuffer& buffer, RenderColorInfo* colors,
uint32_t color_count, RenderDepthInfo& depth,
const std::optional<PreparedBindings>& pixel = std::nullopt);
vk::ImageAspectFlags& feedback_aspects,
std::span<PreparedBindings* const> stages = {});
[[nodiscard]] bool ResolveColorTargets(CommandBuffer& buffer,
uint32_t render_target_slice_offset);
void BindImage(ImageId id, bool storage);
@@ -206,6 +210,8 @@ private:
uint32_t group_y, uint32_t group_z, uint32_t mode);
RenderContext& m_context;
GraphicsBindings m_graphics_bindings;
PreparedBindings m_compute_bindings;
std::vector<ImageId> m_bound_images;
std::vector<vk::DescriptorBufferInfo> m_descriptor_buffers;
std::vector<vk::DescriptorImageInfo> m_descriptor_images;
@@ -9,6 +9,7 @@
#include "graphics/guest_gpu/gpu_defs.h"
#include "graphics/guest_gpu/graphicsRun.h"
#include "graphics/guest_gpu/hardwareContext.h"
#include "graphics/guest_gpu/pm4.h"
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/renderer/image/imageInfo.h"
#include "graphics/host_gpu/renderer/pipeline/descriptors.h"
@@ -51,7 +52,7 @@ static bool FillSourcesDisjoint(std::span<const ShaderRecompiler::IR::Descriptor
bool RenderExecutor::TryConsumeComputeMetaClear(const ShaderComputeInputInfo& input,
const CommandBuffer& buffer) {
const auto& program = *input.stage.program;
const auto& resources = input.stage.resources;
const auto& resources = *input.stage.resources;
if (resources.buffers.size() != program.info.buffers.size()) {
EXIT("compute runtime buffer count does not match shader metadata\n");
}
@@ -85,7 +86,7 @@ bool ResolveComputeBufferFill(const ShaderComputeInputInfo& input, uint32_t grou
uint32_t group_y, uint32_t group_z, uint32_t mode,
ShaderBufferResource& resolved_descriptor, uint32_t& resolved_clear,
uint64_t& resolved_size) {
const auto& resources = input.stage.resources;
const auto& resources = *input.stage.resources;
const auto& fill = resources.uniform_fill;
if (fill.kind != ShaderRecompiler::IR::UniformFillKind::Buffer) {
return false;
@@ -128,7 +129,7 @@ bool RenderExecutor::TryConsumeComputeImageClear(const ShaderComputeInputInfo& i
CommandBuffer& command, uint32_t group_x,
uint32_t group_y, uint32_t group_z, uint32_t mode) {
const auto& program = *input.stage.program;
const auto& resources = input.stage.resources;
const auto& resources = *input.stage.resources;
const auto& fill = resources.uniform_fill;
auto& cache = command.GetContext().GetTextureCache();
if (fill.kind == ShaderRecompiler::IR::UniformFillKind::Image) {
@@ -255,6 +256,10 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, CommandBuffer& buffer,
input_info.dispatch_thread_dimensions = use_thread_dimensions;
const auto compute_program =
m_context.GetPipelineCache().GetComputeProgram(cs_regs, sh_regs, input_info);
if (!compute_program) {
// Temporary until RT is implemented.
return;
}
if (use_thread_dimensions) {
input_info.dispatch_threads_num[0] = thread_group_x;
input_info.dispatch_threads_num[1] = thread_group_y;
@@ -262,7 +267,7 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, CommandBuffer& buffer,
}
const auto& program = *input_info.stage.program;
const auto& resources = input_info.stage.resources;
const auto& resources = *input_info.stage.resources;
if (TryConsumeComputeMetaClear(input_info, buffer)) {
ResetBindings();
return;
@@ -361,7 +366,8 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, CommandBuffer& buffer,
buffer.EndRendering();
auto& pipeline =
m_context.GetPipelineCache().GetComputePipeline(input_info, compute_program);
auto bindings = PrepareBindings(input_info.stage);
auto& bindings = m_compute_bindings;
PrepareBindings(input_info.stage, bindings);
FindBuffers(bindings);
if (program.info.uses_dma) {
m_context.PrepareBda();
@@ -395,4 +401,65 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, CommandBuffer& buffer,
ResetBindings();
}
void RenderExecutor::DispatchIndirect(uint64_t submit_id, CommandBuffer& buffer,
uint64_t args_addr, uint32_t mode) {
EXIT_IF(buffer.IsInvalid() || args_addr == 0 || (args_addr & 3u) != 0 ||
(mode & Pm4::COMPUTE_DISPATCH_INITIATOR_USE_THREAD_DIMENSIONS) != 0);
m_context.GetCommandScheduler().PopPendingOperations();
buffer.SetDebugInfo(static_cast<uint32_t>(CommandBufferDebugOp::DispatchIndirect), submit_id,
static_cast<uint32_t>(args_addr), static_cast<uint32_t>(args_addr >> 32u),
0, mode, buffer.GetShaders().GetCs().cs_regs.data_addr);
Common::LockGuard lock(m_context.GetMutex());
const auto& cs_regs = buffer.GetShaders().GetCs();
if (cs_regs.cs_regs.data_addr == 0) {
return;
}
ShaderComputeInputInfo input_info {};
const auto compute_program = m_context.GetPipelineCache().GetComputeProgram(
cs_regs, buffer.GetRegisters().GetShaderRegisters(), input_info);
if (!compute_program) {
// Temporary until RT is implemented.
return;
}
buffer.EndRendering();
auto& pipeline = m_context.GetPipelineCache().GetComputePipeline(input_info, compute_program);
auto& bindings = m_compute_bindings;
PrepareBindings(input_info.stage, bindings);
FindBuffers(bindings);
const auto& program = *input_info.stage.program;
if (program.info.uses_dma) {
m_context.PrepareBda();
}
RebindImages(bindings);
// Acquiring arguments can merge cache buffers; finalize shader bindings afterward.
const auto [args_buffer, args_offset] = m_context.GetBufferCache().ObtainBuffer(
args_addr, sizeof(vk::DispatchIndirectCommand), false);
EXIT_IF(args_buffer == nullptr || (args_offset & 3u) != 0);
RebindBuffers(bindings);
PreparedBindings* descriptor_stage = &bindings;
CommitBindings(buffer, vk::PipelineBindPoint::eCompute, pipeline,
std::span {&descriptor_stage, 1u});
const auto vk_buffer = buffer.Handle();
const bool has_storage_writes = HasShaderBufferWrites(input_info.stage) ||
std::any_of(program.info.images.begin(), program.info.images.end(), [](const auto& image) {
return image.written && image.resource_class ==
ShaderRecompiler::IR::ImageResourceClass::Storage;
});
if (has_storage_writes) {
ShaderWriteHazardBarrier(vk_buffer, vk::PipelineStageFlagBits::eComputeShader);
}
vk::MemoryBarrier barrier {};
barrier.srcAccessMask = vk::AccessFlagBits::eShaderWrite | vk::AccessFlagBits::eTransferWrite;
barrier.dstAccessMask = vk::AccessFlagBits::eIndirectCommandRead;
vk_buffer.pipelineBarrier(vk::PipelineStageFlagBits::eAllGraphics |
vk::PipelineStageFlagBits::eComputeShader |
vk::PipelineStageFlagBits::eTransfer,
vk::PipelineStageFlagBits::eDrawIndirect, {},
1, &barrier, 0, nullptr, 0, nullptr);
vk_buffer.bindPipeline(vk::PipelineBindPoint::eCompute, pipeline.pipeline);
vk_buffer.dispatchIndirect(args_buffer->Handle(), args_offset);
ShaderAccessBarrier(vk_buffer, vk::PipelineStageFlagBits::eComputeShader);
ResetBindings();
}
} // namespace Libs::Graphics
+37 -51
View File
@@ -58,7 +58,7 @@ std::pair<int32_t, uint32_t> ResolveDrawOffsets(uint32_t index_offset,
EXIT_IF(!vs_input_info.stage);
const auto& program = *vs_input_info.stage.program;
const auto& resources = vs_input_info.stage.resources;
const auto& resources = *vs_input_info.stage.resources;
if (index_offset == 0 &&
program.info.vertex_offset_sgpr >= static_cast<int32_t>(program.user_data_base)) {
const auto index =
@@ -453,15 +453,16 @@ struct DrawCallInfo {
RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderColorInfo* colors,
uint32_t color_count, RenderDepthInfo& depth,
const std::optional<PreparedBindings>& pixel) {
vk::ImageAspectFlags& feedback_aspects,
std::span<PreparedBindings* const> stages) {
EXIT_IF(colors == nullptr || color_count > RENDER_COLOR_ATTACHMENTS_MAX);
feedback_aspects = {};
auto& cache = m_context.GetTextureCache();
RenderState state {};
state.width = std::numeric_limits<uint32_t>::max();
state.height = std::numeric_limits<uint32_t>::max();
state.num_layers = std::numeric_limits<uint32_t>::max();
state.num_color_attachments = 0;
uint32_t attachment_samples = 0;
for (uint32_t i = 0; i < color_count; i++) {
auto& target = colors[i];
EXIT_IF(!target.image_id);
@@ -482,12 +483,6 @@ RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderCo
target.desc.view_info.base_level, target.desc.view_info.base_layer,
target.desc.view_info.layer_count);
EXIT_IF(image.backing.samples != target.desc.info.samples || image_view == nullptr);
if (attachment_samples == 0) {
attachment_samples = target.desc.info.samples;
} else if (attachment_samples != target.desc.info.samples) {
EXIT("mixed color attachment sample counts are unsupported: %u and %u\n",
attachment_samples, target.desc.info.samples);
}
const auto& view = target.desc.view_info;
const auto layout = image.binding.is_bound ? vk::ImageLayout::eGeneral
: vk::ImageLayout::eColorAttachmentOptimal;
@@ -536,34 +531,29 @@ RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderCo
image.info.data.address, depth.desc.view_info.base_layer,
depth.desc.view_info.layer_count);
EXIT_IF(image_view == nullptr || image.backing.samples != depth.desc.info.samples);
if (attachment_samples == 0) {
attachment_samples = depth.desc.info.samples;
} else if (attachment_samples != depth.desc.info.samples) {
EXIT("mixed color/depth sample counts are unsupported: %u and %u\n", attachment_samples,
depth.desc.info.samples);
const auto draw_writes = depth.AttachmentWriteAspects();
vk::ImageAspectFlags sampled_aspects;
for (const auto* stage: stages) {
for (const auto& binding: stage->images) {
if (binding.image_id != depth.image_id ||
binding.desc.type != TextureCache::BindingType::Texture) continue;
const auto native =
std::ranges::find(image.views, binding.image_view, &CachedImageView::view);
EXIT_IF(native == image.views.end());
sampled_aspects |= native->info.aspect;
feedback_aspects |= DepthFeedbackAspects(draw_writes, depth.desc.view_info,
native->info);
}
}
const bool feedback = depth.depth_write_enable && pixel &&
std::ranges::any_of(pixel->images, [&](const TextureBinding& binding) {
if (binding.image_id != depth.image_id ||
binding.desc.type != TextureCache::BindingType::Texture) {
return false;
}
const auto native =
std::ranges::find(image.views, binding.image_view, &CachedImageView::view);
EXIT_IF(native == image.views.end());
const auto& sampled = native->info;
const auto& target = depth.desc.view_info;
return (sampled.aspect & vk::ImageAspectFlagBits::eDepth) &&
ImageRangeOverlaps(sampled.base_level, sampled.level_count,
target.base_level, target.level_count) &&
ImageRangeOverlaps(sampled.base_layer, sampled.layer_count,
target.base_layer, target.layer_count);
});
if (feedback && !m_context.GetGraphics().attachment_feedback_loop_enabled) {
if (feedback_aspects && !m_context.GetGraphics().attachment_feedback_loop_enabled) {
EXIT("depth attachment feedback loop is not supported by the host\n");
}
const auto layout = feedback ? vk::ImageLayout::eAttachmentFeedbackLoopOptimalEXT
: depth_attachment_layout(depth);
auto layout = depth_attachment_layout(depth);
if (sampled_aspects & ~DepthReadableAspects(layout)) {
layout = m_context.GetGraphics().attachment_feedback_loop_enabled
? vk::ImageLayout::eAttachmentFeedbackLoopOptimalEXT
: vk::ImageLayout::eGeneral;
}
// The attachment store writes even when guest depth/stencil tests do not.
const auto access = vk::AccessFlagBits2::eDepthStencilAttachmentRead |
vk::AccessFlagBits2::eDepthStencilAttachmentWrite;
@@ -592,9 +582,6 @@ RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderCo
const auto& limits = buffer.GetGraphics().GetPhysicalDeviceProperties().limits;
state.width = limits.maxFramebufferWidth;
state.height = limits.maxFramebufferHeight;
} else if (attachment_samples == 0 ||
vulkan_sample_count(attachment_samples) == vk::SampleCountFlagBits {}) {
EXIT("render state has no valid attachments\n");
}
if (state.num_layers == std::numeric_limits<uint32_t>::max()) {
state.num_layers = 1;
@@ -699,11 +686,13 @@ static PreparedVertexBuffers AcquireVertexBuffers(CommandBuffer& b
vs_input_info.buffers_num > ShaderVertexInputInfo::RES_MAX);
// Collect the non-empty guest vertex ranges.
std::array<uint64_t, ShaderVertexInputInfo::RES_MAX> sizes {};
std::array<VertexBufferRange, ShaderVertexInputInfo::RES_MAX> ranges {};
uint32_t range_count = 0;
for (int i = 0; i < vs_input_info.buffers_num; i++) {
const auto& vertex = vs_input_info.buffers[i];
const auto size = VertexBufferDescriptorSize(vertex, vs_input_info);
sizes[i] = size;
if (size == 0) {
continue;
}
@@ -752,7 +741,7 @@ static PreparedVertexBuffers AcquireVertexBuffers(CommandBuffer& b
vk::Buffer null_buffer = nullptr;
for (int i = 0; i < vs_input_info.buffers_num; i++) {
const auto& vertex = vs_input_info.buffers[i];
const auto size = VertexBufferDescriptorSize(vertex, vs_input_info);
const auto size = sizes[i];
if (size == 0) {
if (null_buffer == nullptr) {
null_buffer = cache.GetBuffer(NULL_BUFFER_ID).Handle();
@@ -1089,15 +1078,16 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
index_source.guest_element_size);
}
LogDrawPhase(draw.Name(), "PrepareBindings");
GraphicsBindings bindings;
auto& bindings = m_graphics_bindings;
std::array<PreparedBindings*, 4> descriptor_stages {};
uint32_t stage_count = 0;
for (uint32_t i = 0; i < vertex_stages.size(); i++) {
bindings.vertex[i] = PrepareBindings(state.vertex_info[i].stage);
PrepareBindings(state.vertex_info[i].stage, bindings.vertex[i]);
descriptor_stages[stage_count++] = &bindings.vertex[i];
}
if (state.ps_active) {
bindings.pixel.emplace(PrepareBindings(state.ps_input_info.stage));
if (!bindings.pixel) bindings.pixel.emplace();
PrepareBindings(state.ps_input_info.stage, *bindings.pixel);
descriptor_stages[stage_count++] = &*bindings.pixel;
}
const auto stages = std::span {descriptor_stages.data(), stage_count};
@@ -1109,10 +1099,6 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
vertex_bindings = AcquireVertexBuffers(buffer, state.vertex_info[0]);
index_binding = PrepareIndexBuffer(buffer, index_source);
}
const auto rendering =
AcquireRenderTargets(buffer, state.color_info, state.color_count, state.depth_info,
bindings.pixel);
if (draw.IsIndexed()) {
LogDrawPhase(draw.Name(), "CreatePipeline");
}
@@ -1120,6 +1106,10 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
std::span {state.color_info, state.color_count}, state.depth_info, vertex_stages, buffer,
state.ps_active ? &state.ps_input_info : nullptr, topology, primitive_restart_enable,
state.programs);
vk::ImageAspectFlags feedback_aspects;
const auto rendering =
AcquireRenderTargets(buffer, state.color_info, state.color_count, state.depth_info,
feedback_aspects, stages);
// Resource preparation above may synchronously finish and restart the scheduler. From this
// point onward, every operation targets the current command buffer and cannot touch guest
@@ -1129,7 +1119,7 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
if (!mesh_active) {
CommitVertexBuffers(vk_buffer, vertex_bindings);
}
if (bindings.pixel && !draw.IsIndexed()) {
if (state.ps_active && !draw.IsIndexed()) {
SetDrawDebugPhase(buffer, submit_id, draw, 0x300u);
}
CommitBindings(buffer, vk::PipelineBindPoint::eGraphics, pipeline, stages);
@@ -1151,11 +1141,7 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
SetGraphicsDynamicParams(buffer, vk_buffer, vertex_stages.back(), state.depth_info, rendering);
if (m_context.GetGraphics().attachment_feedback_loop_enabled) {
vk_buffer.setAttachmentFeedbackLoopEnableEXT(
rendering.depth_stencil_attachment.image_layout ==
vk::ImageLayout::eAttachmentFeedbackLoopOptimalEXT
? vk::ImageAspectFlags {vk::ImageAspectFlagBits::eDepth}
: vk::ImageAspectFlags {});
vk_buffer.setAttachmentFeedbackLoopEnableEXT(feedback_aspects);
}
LogDrawPhase(draw.Name(), "BeginRendering");
+70 -67
View File
@@ -1,6 +1,7 @@
#include "graphics/presentation/systemOverlay.h"
#include "SDL.h"
#include <SDL3/SDL.h>
#include "common/assert.h"
#include "common/stringUtils.h"
#include "graphics/host_gpu/graphicContext.h"
@@ -176,6 +177,7 @@ bool g_input_multiline = false;
bool g_input_lifecycle_active = false;
bool g_controller_captured = false;
OverlaySession g_session;
SDL_Window* g_input_window = nullptr;
void ClearInputEvents() {
std::scoped_lock lock(g_input_mutex);
@@ -212,7 +214,7 @@ void RetryVisibilityWakeup() {
}
SDL_Event event {};
event.type = type;
if (SDL_PushEvent(&event) > 0) {
if (SDL_PushEvent(&event)) {
g_missing_visibility_wakeups--;
}
}
@@ -252,7 +254,7 @@ void RefreshVisibility() {
std::scoped_lock input_lock(g_input_mutex);
g_visibility_updates.push_back({snapshot.session, visible, capture_controller,
capture_keyboard, text_input, multiline});
if (g_missing_visibility_wakeups != 0 || SDL_PushEvent(&event) <= 0) {
if (g_missing_visibility_wakeups != 0 || !SDL_PushEvent(&event)) {
g_missing_visibility_wakeups++;
}
}
@@ -268,16 +270,16 @@ void OnDialogVisibilityChanged(bool, uint64_t) {
uint32_t ExternalKeyStatus(SDL_Keymod modifiers, bool character_valid) {
uint32_t status = 0x00000001 | (character_valid ? 0x00000002 : 0);
if ((modifiers & KMOD_LCTRL) != 0) status |= 0x00000100;
if ((modifiers & KMOD_LSHIFT) != 0) status |= 0x00000200;
if ((modifiers & KMOD_LALT) != 0) status |= 0x00000400;
if ((modifiers & KMOD_LGUI) != 0) status |= 0x00000800;
if ((modifiers & KMOD_RCTRL) != 0) status |= 0x00001000;
if ((modifiers & KMOD_RSHIFT) != 0) status |= 0x00002000;
if ((modifiers & KMOD_RALT) != 0) status |= 0x00004000;
if ((modifiers & KMOD_RGUI) != 0) status |= 0x00008000;
if ((modifiers & KMOD_NUM) != 0) status |= 0x00010000;
if ((modifiers & KMOD_CAPS) != 0) status |= 0x00020000;
if ((modifiers & SDL_KMOD_LCTRL) != 0) status |= 0x00000100;
if ((modifiers & SDL_KMOD_LSHIFT) != 0) status |= 0x00000200;
if ((modifiers & SDL_KMOD_LALT) != 0) status |= 0x00000400;
if ((modifiers & SDL_KMOD_LGUI) != 0) status |= 0x00000800;
if ((modifiers & SDL_KMOD_RCTRL) != 0) status |= 0x00001000;
if ((modifiers & SDL_KMOD_RSHIFT) != 0) status |= 0x00002000;
if ((modifiers & SDL_KMOD_RALT) != 0) status |= 0x00004000;
if ((modifiers & SDL_KMOD_RGUI) != 0) status |= 0x00008000;
if ((modifiers & SDL_KMOD_NUM) != 0) status |= 0x00010000;
if ((modifiers & SDL_KMOD_CAPS) != 0) status |= 0x00020000;
return status;
}
@@ -392,14 +394,14 @@ float AlignmentPivot(Ime::Alignment alignment) {
ImGuiKey ControllerButtonToKey(int button) {
switch (button) {
case SDL_CONTROLLER_BUTTON_A: return ImGuiKey_GamepadFaceDown;
case SDL_CONTROLLER_BUTTON_B: return ImGuiKey_GamepadFaceRight;
case SDL_CONTROLLER_BUTTON_X: return ImGuiKey_GamepadFaceLeft;
case SDL_CONTROLLER_BUTTON_Y: return ImGuiKey_GamepadFaceUp;
case SDL_CONTROLLER_BUTTON_DPAD_LEFT: return ImGuiKey_GamepadDpadLeft;
case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: return ImGuiKey_GamepadDpadRight;
case SDL_CONTROLLER_BUTTON_DPAD_UP: return ImGuiKey_GamepadDpadUp;
case SDL_CONTROLLER_BUTTON_DPAD_DOWN: return ImGuiKey_GamepadDpadDown;
case SDL_GAMEPAD_BUTTON_SOUTH: return ImGuiKey_GamepadFaceDown;
case SDL_GAMEPAD_BUTTON_EAST: return ImGuiKey_GamepadFaceRight;
case SDL_GAMEPAD_BUTTON_WEST: return ImGuiKey_GamepadFaceLeft;
case SDL_GAMEPAD_BUTTON_NORTH: return ImGuiKey_GamepadFaceUp;
case SDL_GAMEPAD_BUTTON_DPAD_LEFT: return ImGuiKey_GamepadDpadLeft;
case SDL_GAMEPAD_BUTTON_DPAD_RIGHT: return ImGuiKey_GamepadDpadRight;
case SDL_GAMEPAD_BUTTON_DPAD_UP: return ImGuiKey_GamepadDpadUp;
case SDL_GAMEPAD_BUTTON_DPAD_DOWN: return ImGuiKey_GamepadDpadDown;
default: return ImGuiKey_None;
}
}
@@ -415,9 +417,11 @@ void CheckVulkanResult(VkResult result) {
} // namespace
void InitializeSystemOverlayInput() {
void InitializeSystemOverlayInput(SDL_Window* window) {
EXIT_IF(window == nullptr);
const Uint32 type = SDL_RegisterEvents(1);
EXIT_IF(type == static_cast<Uint32>(-1));
g_input_window = window;
{
std::scoped_lock lock(g_visibility_mutex);
g_input_lifecycle_active = true;
@@ -453,9 +457,10 @@ void ShutdownSystemOverlayInput() {
g_input_controller = false;
g_input_keyboard = false;
g_input_multiline = false;
if (SDL_IsTextInputActive() == SDL_TRUE) {
SDL_StopTextInput();
if (g_input_window != nullptr && SDL_TextInputActive(g_input_window)) {
SDL_StopTextInput(g_input_window);
}
g_input_window = nullptr;
}
SystemOverlayVisualState GetSystemOverlayVisualState() noexcept {
@@ -487,13 +492,13 @@ bool ProcessSystemOverlayInput(const SDL_Event& event) {
g_input_keyboard = update.capture_keyboard;
g_input_multiline = update.multiline;
if (update.text_input) {
SDL_StartTextInput();
} else if (SDL_IsTextInputActive() == SDL_TRUE) {
SDL_StopTextInput();
SDL_StartTextInput(g_input_window);
} else if (SDL_TextInputActive(g_input_window)) {
SDL_StopTextInput(g_input_window);
}
return true;
}
if (event.type == SDL_CONTROLLERDEVICEREMOVED) {
if (event.type == SDL_EVENT_GAMEPAD_REMOVED) {
if (g_input_active && g_input_controller) {
QueueInput({InputKind::ResetController, g_input_session, 0, 0.0f, 0.0f});
}
@@ -505,26 +510,26 @@ bool ProcessSystemOverlayInput(const SDL_Event& event) {
const auto session = g_input_session;
const uint64_t generation = session.generation;
const bool controller_event = event.type == SDL_CONTROLLERBUTTONDOWN ||
event.type == SDL_CONTROLLERBUTTONUP ||
event.type == SDL_CONTROLLERAXISMOTION;
const bool controller_event = event.type == SDL_EVENT_GAMEPAD_BUTTON_DOWN ||
event.type == SDL_EVENT_GAMEPAD_BUTTON_UP ||
event.type == SDL_EVENT_GAMEPAD_AXIS_MOTION;
if (controller_event && !g_input_controller) {
return false;
}
const bool keyboard_event = event.type == SDL_TEXTINPUT || event.type == SDL_TEXTEDITING ||
event.type == SDL_KEYDOWN || event.type == SDL_KEYUP;
const bool keyboard_event = event.type == SDL_EVENT_TEXT_INPUT || event.type == SDL_EVENT_TEXT_EDITING ||
event.type == SDL_EVENT_KEY_DOWN || event.type == SDL_EVENT_KEY_UP;
if (keyboard_event && !g_input_keyboard) {
return false;
}
if (keyboard_event && session.kind == OverlayKind::Error) {
if (event.type == SDL_KEYDOWN && event.key.repeat == 0 &&
(event.key.keysym.sym == SDLK_RETURN || event.key.keysym.sym == SDLK_KP_ENTER)) {
if (event.type == SDL_EVENT_KEY_DOWN && !event.key.repeat &&
(event.key.key == SDLK_RETURN || event.key.key == SDLK_KP_ENTER)) {
ErrorDialog::HostAccept(generation);
}
return true;
}
switch (event.type) {
case SDL_TEXTINPUT: {
case SDL_EVENT_TEXT_INPUT: {
const auto text = Utf8ToUtf16(event.text.text);
if (!text.empty()) {
auto input = MakeExternalInput(Ime::ExternalAction::Text, g_last_external_keycode,
@@ -535,26 +540,24 @@ bool ProcessSystemOverlayInput(const SDL_Event& event) {
}
return true;
}
case SDL_TEXTEDITING: return true;
case SDL_KEYDOWN: {
g_last_external_keycode = static_cast<uint16_t>(event.key.keysym.scancode);
g_last_external_status =
ExternalKeyStatus(static_cast<SDL_Keymod>(event.key.keysym.mod), false);
case SDL_EVENT_TEXT_EDITING: return true;
case SDL_EVENT_KEY_DOWN: {
g_last_external_keycode = static_cast<uint16_t>(event.key.scancode);
g_last_external_status = ExternalKeyStatus(event.key.mod, false);
auto action = Ime::ExternalAction::Text;
bool queue = true;
if (event.key.keysym.sym == SDLK_BACKSPACE) {
if (event.key.key == SDLK_BACKSPACE) {
action = Ime::ExternalAction::Backspace;
} else if (event.key.keysym.sym == SDLK_LEFT) {
} else if (event.key.key == SDLK_LEFT) {
action = Ime::ExternalAction::MoveLeft;
} else if (event.key.keysym.sym == SDLK_RIGHT) {
} else if (event.key.key == SDLK_RIGHT) {
action = Ime::ExternalAction::MoveRight;
} else if (event.key.keysym.sym == SDLK_ESCAPE) {
} else if (event.key.key == SDLK_ESCAPE) {
action = Ime::ExternalAction::Cancel;
} else if (event.key.keysym.sym == SDLK_RETURN ||
event.key.keysym.sym == SDLK_KP_ENTER) {
} else if (event.key.key == SDLK_RETURN || event.key.key == SDLK_KP_ENTER) {
action =
g_input_multiline ? Ime::ExternalAction::Newline : Ime::ExternalAction::Accept;
} else if (event.key.keysym.sym == SDLK_TAB) {
} else if (event.key.key == SDLK_TAB) {
action = Ime::ExternalAction::None;
} else {
queue = false;
@@ -566,28 +569,28 @@ bool ProcessSystemOverlayInput(const SDL_Event& event) {
}
return true;
}
case SDL_KEYUP: return true;
case SDL_CONTROLLERBUTTONDOWN:
case SDL_CONTROLLERBUTTONUP:
QueueInput({InputKind::Button, session, event.cbutton.button,
event.type == SDL_CONTROLLERBUTTONDOWN ? 1.0f : 0.0f, 0.0f});
case SDL_EVENT_KEY_UP: return true;
case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
case SDL_EVENT_GAMEPAD_BUTTON_UP:
QueueInput({InputKind::Button, session, event.gbutton.button,
event.type == SDL_EVENT_GAMEPAD_BUTTON_DOWN ? 1.0f : 0.0f, 0.0f});
return true;
case SDL_CONTROLLERAXISMOTION:
QueueInput({InputKind::Axis, session, event.caxis.axis,
static_cast<float>(event.caxis.value), 0.0f});
case SDL_EVENT_GAMEPAD_AXIS_MOTION:
QueueInput({InputKind::Axis, session, event.gaxis.axis,
static_cast<float>(event.gaxis.value), 0.0f});
return true;
case SDL_MOUSEMOTION:
case SDL_EVENT_MOUSE_MOTION:
QueueInput({InputKind::MousePosition, session, 0, static_cast<float>(event.motion.x),
static_cast<float>(event.motion.y)});
return true;
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP:
case SDL_EVENT_MOUSE_BUTTON_DOWN:
case SDL_EVENT_MOUSE_BUTTON_UP:
QueueInput({InputKind::MousePosition, session, 0, static_cast<float>(event.button.x),
static_cast<float>(event.button.y)});
QueueInput({InputKind::MouseButton, session, event.button.button,
event.type == SDL_MOUSEBUTTONDOWN ? 1.0f : 0.0f, 0.0f});
event.type == SDL_EVENT_MOUSE_BUTTON_DOWN ? 1.0f : 0.0f, 0.0f});
return true;
case SDL_MOUSEWHEEL:
case SDL_EVENT_MOUSE_WHEEL:
QueueInput({InputKind::MouseWheel, session, 0, static_cast<float>(event.wheel.x),
static_cast<float>(event.wheel.y)});
return true;
@@ -681,9 +684,9 @@ struct SystemOverlay::Impl {
case InputKind::Button: {
const bool down = event.x != 0.0f;
if (session.kind == OverlayKind::Ime && down) {
if (event.id == SDL_CONTROLLER_BUTTON_B) {
if (event.id == SDL_GAMEPAD_BUTTON_EAST) {
Ime::HostCancel(session.generation);
} else if (event.id == SDL_CONTROLLER_BUTTON_Y) {
} else if (event.id == SDL_GAMEPAD_BUTTON_NORTH) {
Ime::HostBackspace(session.generation);
}
}
@@ -695,19 +698,19 @@ struct SystemOverlay::Impl {
}
case InputKind::Axis: {
const float value = event.x / (event.x < 0.0f ? 32768.0f : 32767.0f);
if (event.id == SDL_CONTROLLER_AXIS_LEFTX) {
if (event.id == SDL_GAMEPAD_AXIS_LEFTX) {
io.AddKeyAnalogEvent(ImGuiKey_GamepadLStickLeft, value < -0.25f,
std::max(-value, 0.0f));
io.AddKeyAnalogEvent(ImGuiKey_GamepadLStickRight, value > 0.25f,
std::max(value, 0.0f));
} else if (event.id == SDL_CONTROLLER_AXIS_LEFTY) {
} else if (event.id == SDL_GAMEPAD_AXIS_LEFTY) {
io.AddKeyAnalogEvent(ImGuiKey_GamepadLStickUp, value < -0.25f,
std::max(-value, 0.0f));
io.AddKeyAnalogEvent(ImGuiKey_GamepadLStickDown, value > 0.25f,
std::max(value, 0.0f));
} else if (event.id == SDL_CONTROLLER_AXIS_RIGHTX) {
} else if (event.id == SDL_GAMEPAD_AXIS_RIGHTX) {
right_stick.x = std::abs(value) > 0.2f ? value : 0.0f;
} else if (event.id == SDL_CONTROLLER_AXIS_RIGHTY) {
} else if (event.id == SDL_GAMEPAD_AXIS_RIGHTY) {
right_stick.y = std::abs(value) > 0.2f ? value : 0.0f;
}
break;
+2 -1
View File
@@ -7,6 +7,7 @@
#include <memory>
union SDL_Event;
struct SDL_Window;
namespace Libs::Graphics {
@@ -17,7 +18,7 @@ struct SystemOverlayVisualState {
uint64_t revision;
};
void InitializeSystemOverlayInput();
void InitializeSystemOverlayInput(SDL_Window* window);
void ShutdownSystemOverlayInput();
bool ProcessSystemOverlayInput(const SDL_Event& event);
SystemOverlayVisualState GetSystemOverlayVisualState() noexcept;
+22 -2
View File
@@ -20,9 +20,12 @@
#include "kernel/pthread.h"
#include "libs/errno.h"
#include "libs/libs.h"
#include "loader/systemContent.h"
#include <algorithm>
#include <array>
#include <atomic>
#include <fmt/format.h>
#include <list>
#include <thread>
#include <vector>
@@ -540,6 +543,14 @@ Graphics::ImageInfo BufferAttributeGroup::ImageInfo(const VideoOutBuffer& buffer
if (!Graphics::DecodeVideoOutPixelFormat(attribute.pixel_format, pixel_format)) {
EXIT("unsupported video-out pixel format: 0x%016" PRIx64 "\n", attribute.pixel_format);
}
if (attribute.pixel_format == Graphics::VIDEO_OUT_PIXEL_FORMAT_R10_G10_B10_A2_BT2100_PQ) {
static std::atomic_flag warned = ATOMIC_FLAG_INIT;
if (!warned.test_and_set(std::memory_order_relaxed)) {
Log::WriteToConsoleAndLog(fmt::format(
"Warning: HDR-to-SDR conversion is not implemented; displaying PQ video-out "
"pixels unchanged (format=0x{:016x}).\n", attribute.pixel_format));
}
}
const auto tile_mode = Graphics::Prospero::TileMode::kRenderTarget;
const auto pitch =
Graphics::TileGetTexturePitch(pixel_format.guest_format, attribute.width, tile_mode);
@@ -564,7 +575,12 @@ Graphics::ImageInfo BufferAttributeGroup::ImageInfo(const VideoOutBuffer& buffer
info.bgra16 = pixel_format.bgra16;
info.mip_layout[0] = {0, total.size, pitch, attribute.height};
if (compression != Graphics::VideoOutCompression::Uncompressed) {
info.metadata.range = {buffer.metadata_address, 0};
Graphics::TileSizeAlign dcc_size {};
if (!Graphics::TileGetDccSize(attribute.width, attribute.height, 1,
pixel_format.bytes_per_element, 1, tile_mode, dcc_size)) {
EXIT("invalid video-out DCC footprint\n");
}
info.metadata.range = {buffer.metadata_address, dcc_size.size};
info.metadata.kind = Graphics::ImageMetadataKind::Dcc;
info.metadata.control = attribute.dcc_control;
info.metadata.compression = compression;
@@ -1654,8 +1670,12 @@ KYTY_SYSV_ABI int VideoOutGetOutputStatus(int handle, VideoOutOutputStatus* stat
return VIDEO_OUT_ERROR_INVALID_HANDLE;
}
int32_t attribute3 = 0;
Loader::SystemContentParamSfoGetInt("ATTRIBUTE3", &attribute3);
ctx->mutex.Lock();
status->resolution = (ctx->width >= 3840 || ctx->height >= 2160 ? 2u : 1u);
// Primary output reports 4K unless param.json Video-out Info enables resolution detection.
status->resolution =
((attribute3 & 4) != 0 && ctx->width < 3840 && ctx->height < 2160 ? 1u : 2u);
status->dynamicRange = 1;
status->refreshRate =
(ctx->output_mode == VIDEO_OUT_OUTPUT_MODE_119_88HZ || Config::GetVblankFrequency() >= 119
+71 -65
View File
@@ -1,12 +1,7 @@
#include "graphics/presentation/window/hostInput.h"
#include "SDL_error.h"
#include "SDL_events.h"
#include "SDL_keyboard.h"
#include "SDL_keycode.h"
#include "SDL_mouse.h"
#include "SDL_timer.h"
#include "SDL_video.h"
#include <SDL3/SDL.h>
#include "common/assert.h"
#include "common/emulatorConfig.h"
#include "common/logging/log.h"
@@ -80,6 +75,7 @@ struct MouseJoystickState {
};
MouseJoystickState g_mouse;
SDL_Window* g_mouse_window = nullptr;
std::size_t ControlFromName(std::string_view name) {
const auto info = std::find_if(CONTROL_INFO.begin(), CONTROL_INFO.end(),
@@ -141,9 +137,8 @@ public:
}
}
const bool reserved = binding.key == SDLK_ESCAPE || binding.key == SDLK_SPACE ||
binding.key == SDLK_F1 || binding.key == SDLK_F7 ||
binding.key == SDLK_F11;
const bool reserved = binding.key == SDLK_ESCAPE || binding.key == SDLK_F1 ||
binding.key == SDLK_F7 || binding.key == SDLK_F11;
if (binding.control == INVALID_CONTROL || reserved ||
(binding.key == SDLK_UNKNOWN && binding.mouse_button == 0)) {
EXIT("Invalid input mapping: %s\n", value.c_str());
@@ -214,14 +209,14 @@ uint32_t DefaultKeyboardButton(int key_code) {
case SDLK_LEFT: return Controller::PAD_BUTTON_LEFT;
case SDLK_DOWN: return Controller::PAD_BUTTON_DOWN;
case SDLK_RIGHT: return Controller::PAD_BUTTON_RIGHT;
case SDLK_j: return Controller::PAD_BUTTON_CROSS;
case SDLK_i: return Controller::PAD_BUTTON_TRIANGLE;
case SDLK_k: return Controller::PAD_BUTTON_SQUARE;
case SDLK_l: return Controller::PAD_BUTTON_CIRCLE;
case SDLK_q: return Controller::PAD_BUTTON_L1;
case SDLK_e: return Controller::PAD_BUTTON_R1;
case SDLK_z: return Controller::PAD_BUTTON_L2;
case SDLK_c: return Controller::PAD_BUTTON_R2;
case SDLK_J: return Controller::PAD_BUTTON_CROSS;
case SDLK_I: return Controller::PAD_BUTTON_TRIANGLE;
case SDLK_K: return Controller::PAD_BUTTON_SQUARE;
case SDLK_L: return Controller::PAD_BUTTON_CIRCLE;
case SDLK_Q: return Controller::PAD_BUTTON_L1;
case SDLK_E: return Controller::PAD_BUTTON_R1;
case SDLK_Z: return Controller::PAD_BUTTON_L2;
case SDLK_C: return Controller::PAD_BUTTON_R2;
case SDLK_LSHIFT: return Controller::PAD_BUTTON_L3;
case SDLK_LCTRL: return Controller::PAD_BUTTON_R3;
case SDLK_RETURN:
@@ -285,35 +280,35 @@ void DefaultKeyboardInput(int key_code, bool down) {
switch (NormalizeKey(static_cast<SDL_Keycode>(key_code))) {
case SDLK_BACKSPACE: SetTouchPad(0.25f, down); return;
case SDLK_TAB: SetTouchPad(0.75f, down); return;
case SDLK_a:
case SDLK_A:
left.left = down;
SetStickAxis(Controller::Axis::LeftX, left.left, left.right);
return;
case SDLK_d:
case SDLK_D:
left.right = down;
SetStickAxis(Controller::Axis::LeftX, left.left, left.right);
return;
case SDLK_w:
case SDLK_W:
left.up = down;
SetStickAxis(Controller::Axis::LeftY, left.up, left.down);
return;
case SDLK_s:
case SDLK_S:
left.down = down;
SetStickAxis(Controller::Axis::LeftY, left.up, left.down);
return;
case SDLK_f:
case SDLK_F:
right.left = down;
SetStickAxis(Controller::Axis::RightX, right.left, right.right);
return;
case SDLK_h:
case SDLK_H:
right.right = down;
SetStickAxis(Controller::Axis::RightX, right.left, right.right);
return;
case SDLK_t:
case SDLK_T:
right.up = down;
SetStickAxis(Controller::Axis::RightY, right.up, right.down);
return;
case SDLK_g:
case SDLK_G:
right.down = down;
SetStickAxis(Controller::Axis::RightY, right.up, right.down);
return;
@@ -321,11 +316,11 @@ void DefaultKeyboardInput(int key_code, bool down) {
}
}
void MouseToJoystick(int delta_x, int delta_y) {
void MouseToJoystick(float delta_x, float delta_y) {
const double distance = std::hypot(delta_x, delta_y);
const double scale =
std::clamp(distance * GetInputMap().MouseSensitivity() + 16.0, 64.0, 128.0) / distance;
const auto map_axis = [scale](int delta) {
const auto map_axis = [scale](float delta) {
return std::clamp(128 + static_cast<int>(std::lround(delta * scale)), 0, 255);
};
Controller::SetRightStick(Controller::HOST_INPUT_CONTROLLER_ID, map_axis(delta_x),
@@ -341,16 +336,45 @@ void CenterMouseStick() {
}
bool SetRelativeMouseMode(bool enabled) {
if (SDL_SetRelativeMouseMode(enabled ? SDL_TRUE : SDL_FALSE) == 0) {
if (SDL_SetWindowRelativeMouseMode(g_mouse_window, enabled)) {
return true;
}
LOGF("Mouse-to-joystick relative mode failed: %s\n", SDL_GetError());
return false;
}
int PollMouse(uint64_t now_ms) {
if (now_ms < g_mouse.next_poll) {
return static_cast<int>(g_mouse.next_poll - now_ms);
}
g_mouse.next_poll = now_ms + MOUSE_POLL_INTERVAL_MS;
float delta_x = 0;
float delta_y = 0;
SDL_GetRelativeMouseState(&delta_x, &delta_y);
if (delta_x == 0 && delta_y == 0) {
CenterMouseStick();
} else {
MouseToJoystick(delta_x, delta_y);
g_mouse.output = true;
}
return MOUSE_POLL_INTERVAL_MS;
}
} // namespace
void HostInputInit() {
void HostInputInit(SDL_Window* window) {
GetInputMap();
g_mouse_window = window;
}
void HostInputShutdown() {
if (g_mouse.enabled) {
SetRelativeMouseMode(false);
CenterMouseStick();
g_mouse = {};
}
g_mouse_window = nullptr;
}
void HostInputKey(int key_code, bool down) {
@@ -381,51 +405,33 @@ void HostInputToggleMouseToJoystick() {
if (!SetRelativeMouseMode(true)) {
return;
}
int ignored_x = 0;
int ignored_y = 0;
SDL_GetRelativeMouseState(&ignored_x, &ignored_y);
g_mouse.enabled = true;
g_mouse.next_poll = SDL_GetTicks64() + MOUSE_POLL_INTERVAL_MS;
g_mouse.enabled = true;
LOGF("Mouse to right stick: enabled (F7 to release)\n");
}
int PollMouse(uint64_t now_ms) {
if (now_ms < g_mouse.next_poll) {
return static_cast<int>(g_mouse.next_poll - now_ms);
}
g_mouse.next_poll = now_ms + MOUSE_POLL_INTERVAL_MS;
int delta_x = 0;
int delta_y = 0;
SDL_GetRelativeMouseState(&delta_x, &delta_y);
if (delta_x == 0 && delta_y == 0) {
CenterMouseStick();
return MOUSE_POLL_INTERVAL_MS;
}
MouseToJoystick(delta_x, delta_y);
g_mouse.output = true;
return MOUSE_POLL_INTERVAL_MS;
}
bool HostInputWaitEvent(SDL_Event* event) {
if (!g_mouse.enabled || SDL_GetKeyboardFocus() == nullptr) {
bool has_event;
if (!g_mouse.enabled || SDL_GetKeyboardFocus() != g_mouse_window) {
g_mouse.next_poll = 0;
CenterMouseStick();
if (SDL_WaitEvent(event) == 0) {
has_event = SDL_WaitEvent(event);
if (!has_event) {
EXIT("%s\n", SDL_GetError());
}
return true;
} else {
if (g_mouse.next_poll == 0) {
SDL_GetRelativeMouseState(nullptr, nullptr);
g_mouse.next_poll = SDL_GetTicks() + MOUSE_POLL_INTERVAL_MS;
}
has_event = SDL_WaitEventTimeout(event, PollMouse(SDL_GetTicks()));
}
const int timeout_ms = PollMouse(SDL_GetTicks64());
SDL_ClearError();
if (SDL_WaitEventTimeout(event, timeout_ms) != 0) {
return true;
if (has_event && event->type == SDL_EVENT_WINDOW_FOCUS_LOST &&
event->window.windowID == SDL_GetWindowID(g_mouse_window)) {
g_mouse.next_poll = 0;
CenterMouseStick();
}
if (SDL_GetError()[0] != '\0') {
EXIT("%s\n", SDL_GetError());
}
return false;
return has_event;
}
} // namespace Libs::Graphics
+3 -1
View File
@@ -3,11 +3,13 @@
#include <cstdint>
struct SDL_Window;
union SDL_Event;
namespace Libs::Graphics {
void HostInputInit();
void HostInputInit(SDL_Window* window);
void HostInputShutdown();
void HostInputKey(int key_code, bool down);
void HostInputMouseButton(uint8_t mouse_button, bool down);
void HostInputToggleMouseToJoystick();
+42 -20
View File
@@ -171,6 +171,13 @@ void Presenter::Frame::Configure(GraphicContext& graphics, vk::Extent2D extent,
EXIT("unsupported prepared frame, extent=%ux%u format=%d\n", extent.width, extent.height,
static_cast<int>(format));
}
auto& dst = image;
const bool compatible = dst.image != nullptr && dst.extent.width == extent.width &&
dst.extent.height == extent.height && dst.format == format;
if (compatible) {
return;
}
const auto features = graphics.GetFormatProperties(format).optimalTilingFeatures;
const auto required =
vk::FormatFeatureFlagBits::eBlitSrc | vk::FormatFeatureFlagBits::eSampledImageFilterLinear |
@@ -180,12 +187,6 @@ void Presenter::Frame::Configure(GraphicContext& graphics, vk::Extent2D extent,
static_cast<int>(format), static_cast<vk::FormatFeatureFlags::MaskType>(features));
}
auto& dst = image;
const bool compatible = dst.image != nullptr && dst.extent.width == extent.width &&
dst.extent.height == extent.height && dst.format == format;
if (compatible) {
return;
}
if (dst.image != nullptr) {
graphics.DeleteImage(dst);
}
@@ -279,6 +280,7 @@ public:
void Create();
void Recreate(bool surface_lost = false);
[[nodiscard]] bool NeedsResize() const;
[[nodiscard]] Status AcquireNextImage();
[[nodiscard]] bool PrepareSystemOverlay();
void RecordPresentCommands(CommandBuffer& command, VulkanImage& source,
@@ -298,6 +300,8 @@ private:
vk::SwapchainKHR m_handle = nullptr;
vk::Format m_format = vk::Format::eUndefined;
vk::Extent2D m_extent {};
// Drawable pixel size observed when this swapchain was created.
vk::Extent2D m_window_extent {};
std::vector<vk::Image> m_images;
std::vector<vk::ImageView> m_image_views;
std::vector<vk::Semaphore> m_image_acquired;
@@ -357,19 +361,25 @@ void Swapchain::Create() {
EXIT_IF(graphics.device == nullptr);
EXIT_IF(m_window.surface == nullptr);
Common::LockGuard lock(m_window.mutex);
EXIT_IF(graphics.screen_width == 0);
EXIT_IF(graphics.screen_height == 0);
// Capture the size before querying surface capabilities. If the window changes
// during creation, the next presentation will detect the newer size.
{
Common::LockGuard lock(m_window.mutex);
m_window_extent = {graphics.screen_width, graphics.screen_height};
}
EXIT_IF(m_window_extent.width == 0);
EXIT_IF(m_window_extent.height == 0);
m_window.RefreshSurfaceCapabilities();
const auto& surface = m_window.surface_capabilities;
EXIT_NOT_IMPLEMENTED(surface.formats.empty());
m_extent = surface.capabilities.currentExtent;
if (m_extent.width == std::numeric_limits<uint32_t>::max()) {
m_extent.width =
std::clamp(graphics.screen_width, surface.capabilities.minImageExtent.width,
std::clamp(m_window_extent.width, surface.capabilities.minImageExtent.width,
surface.capabilities.maxImageExtent.width);
m_extent.height =
std::clamp(graphics.screen_height, surface.capabilities.minImageExtent.height,
std::clamp(m_window_extent.height, surface.capabilities.minImageExtent.height,
surface.capabilities.maxImageExtent.height);
}
uint32_t image_count = surface.capabilities.minImageCount + 1;
@@ -473,8 +483,8 @@ void Swapchain::Create() {
graphics.device.createSemaphore(&semaphore_info, nullptr, &m_render_complete[i]),
"create swapchain render-complete semaphore");
}
m_image_index = static_cast<uint32_t>(-1);
m_frame_index = 0;
m_image_index = static_cast<uint32_t>(-1);
m_frame_index = 0;
}
Swapchain::~Swapchain() {
@@ -515,11 +525,12 @@ void Swapchain::Destroy() {
graphics.device.destroySwapchainKHR(m_handle, nullptr);
}
m_handle = nullptr;
m_format = vk::Format::eUndefined;
m_extent = {};
m_image_index = static_cast<uint32_t>(-1);
m_frame_index = 0;
m_handle = nullptr;
m_format = vk::Format::eUndefined;
m_extent = {};
m_window_extent = {};
m_image_index = static_cast<uint32_t>(-1);
m_frame_index = 0;
m_images.clear();
m_image_views.clear();
m_image_acquired.clear();
@@ -532,15 +543,22 @@ void Swapchain::Recreate(bool surface_lost) {
#if defined(__APPLE__)
// Surface recreation goes through SDL_Vulkan_CreateSurface, which touches the
// window's view/layer and must run on the main thread on macOS.
m_window.RunOnMainThread([this] { m_window.RecreateSurface(); });
EXIT_IF(!SDL_RunOnMainThread(
[](void* window) { static_cast<WindowContext*>(window)->RecreateSurface(); },
&m_window, true));
#else
m_window.RecreateSurface();
#endif
}
m_window.RefreshSurfaceCapabilities();
Create();
}
bool Swapchain::NeedsResize() const {
Common::LockGuard lock(m_window.mutex);
return m_window_extent.width != m_window.graphic_ctx.screen_width ||
m_window_extent.height != m_window.graphic_ctx.screen_height;
}
Swapchain::Status Swapchain::AcquireNextImage() {
EXIT_IF(m_handle == nullptr || m_frame_index >= m_image_acquired.size());
m_image_index = static_cast<uint32_t>(-1);
@@ -766,6 +784,10 @@ void Presenter::Present(Frame& frame, bool reuse) {
const auto overlay_visual = GetSystemOverlayVisualState();
auto& swapchain = m_impl->swapchain;
// Some window systems keep presenting an old swapchain after a resize.
if (swapchain.NeedsResize()) {
m_impl->RecoverSwapchain(Swapchain::Status::Recreate);
}
for (uint32_t attempt = 0; attempt < 2; attempt++) {
auto status = swapchain.AcquireNextImage();
if (status != Swapchain::Status::Success) {
@@ -1,20 +1,6 @@
#include "SDL.h"
#include "SDL_error.h"
#include "SDL_events.h"
#include "SDL_gamecontroller.h"
#include "SDL_hints.h"
#include "SDL_joystick.h"
#include "SDL_keyboard.h"
#include "SDL_keycode.h"
#include "SDL_mouse.h"
#include "SDL_pixels.h"
#include "SDL_rwops.h"
#include "SDL_stdinc.h"
#include "SDL_surface.h"
#include "SDL_thread.h"
#include "SDL_touch.h"
#include "SDL_video.h"
#include "SDL_vulkan.h"
#include <SDL3/SDL.h>
#include <SDL3/SDL_vulkan.h>
#include "common/assert.h"
#include "common/common.h"
#include "common/emulatorConfig.h"
@@ -252,6 +238,10 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa
#endif
}
#if !defined(__APPLE__)
if (device_features2.features.depthClamp != VK_TRUE) {
LOGF("depthClamp is not supported\n");
skip_device = true;
}
if (fragment_barycentric.fragmentShaderBarycentric != VK_TRUE) {
LOGF("fragmentShaderBarycentric is not supported\n");
skip_device = true;
@@ -621,6 +611,7 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics,
device_features.robustBufferAccess = VK_TRUE;
#if !defined(__APPLE__)
device_features.depthBounds = VK_TRUE; // unsupported by MoltenVK
device_features.depthClamp = VK_TRUE;
#endif
device_features.shaderStorageImageWriteWithoutFormat = VK_TRUE;
device_features.shaderImageGatherExtended = VK_TRUE;
@@ -700,25 +691,13 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics,
return device;
}
static void VulkanGetExtensions(SDL_Window* window, VulkanExtensions& r) {
EXIT_IF(window == nullptr);
static void VulkanGetExtensions(VulkanExtensions& r) {
uint32_t required_extensions_count = 0;
auto sdl_result = SDL_Vulkan_GetInstanceExtensions(window, &required_extensions_count, nullptr);
EXIT_NOT_IMPLEMENTED(sdl_result == SDL_FALSE);
const char* const* extensions = SDL_Vulkan_GetInstanceExtensions(&required_extensions_count);
EXIT_NOT_IMPLEMENTED(extensions == nullptr);
EXIT_NOT_IMPLEMENTED(required_extensions_count == 0);
r.required_extensions =
std::vector<const char*>(required_extensions_count); // @suppress("Ambiguous problem")
sdl_result = SDL_Vulkan_GetInstanceExtensions(window, &required_extensions_count,
r.required_extensions.data());
EXIT_NOT_IMPLEMENTED(sdl_result == SDL_FALSE);
EXIT_NOT_IMPLEMENTED(required_extensions_count == 0);
EXIT_NOT_IMPLEMENTED(required_extensions_count != r.required_extensions.size());
r.required_extensions.assign(extensions, extensions + required_extensions_count);
r.available_extensions =
EnumerateVulkan<vk::ExtensionProperties>( // @suppress("Ambiguous problem")
@@ -899,7 +878,7 @@ void WindowContext::CreateVulkan() {
VULKAN_HPP_DEFAULT_DISPATCHER.init(get_instance_proc_addr);
VulkanExtensions r;
VulkanGetExtensions(window, r);
VulkanGetExtensions(r);
VulkanCheckInstanceVersion();
vk::ApplicationInfo app_info {};
@@ -986,8 +965,8 @@ void WindowContext::CreateVulkan() {
}
vk::SurfaceKHR::CType native_surface = VK_NULL_HANDLE;
if (SDL_Vulkan_CreateSurface(window, static_cast<vk::Instance::CType>(graphic_ctx.instance),
&native_surface) == SDL_FALSE) {
if (!SDL_Vulkan_CreateSurface(window, static_cast<vk::Instance::CType>(graphic_ctx.instance),
nullptr, &native_surface)) {
EXIT("Could not create a Vulkan surface");
}
surface = native_surface;
@@ -1100,8 +1079,8 @@ void WindowContext::RecreateSurface() {
surface = nullptr;
}
vk::SurfaceKHR::CType native_surface = VK_NULL_HANDLE;
if (SDL_Vulkan_CreateSurface(window, static_cast<vk::Instance::CType>(graphic_ctx.instance),
&native_surface) == SDL_FALSE) {
if (!SDL_Vulkan_CreateSurface(window, static_cast<vk::Instance::CType>(graphic_ctx.instance),
nullptr, &native_surface)) {
EXIT("Could not recreate the Vulkan surface: %s\n", SDL_GetError());
}
surface = native_surface;
@@ -1137,7 +1116,7 @@ WindowContext::~WindowContext() {
SDL_DestroyWindow(window);
window = nullptr;
}
SDL_QuitSubSystem(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER);
SDL_QuitSubSystem(SDL_INIT_VIDEO | SDL_INIT_GAMEPAD);
}
} // namespace Libs::Graphics
+161 -236
View File
@@ -1,23 +1,7 @@
#include "graphics/presentation/window.h"
#include "SDL.h"
#include "SDL_error.h"
#include "SDL_events.h"
#include "SDL_filesystem.h"
#include "SDL_gamecontroller.h"
#include "SDL_hints.h"
#include "SDL_joystick.h"
#include "SDL_keyboard.h"
#include "SDL_keycode.h"
#include "SDL_mouse.h"
#include "SDL_pixels.h"
#include "SDL_rwops.h"
#include "SDL_stdinc.h"
#include "SDL_surface.h"
#include "SDL_thread.h"
#include "SDL_touch.h"
#include "SDL_video.h"
#include "SDL_vulkan.h"
#include <SDL3/SDL.h>
#include "common/assert.h"
#include "common/common.h"
#include "common/emulatorConfig.h"
@@ -72,39 +56,39 @@ struct EventKeyboard {
static uint32_t ControllerButtonToPadButton(int button) {
switch (button) {
case SDL_CONTROLLER_BUTTON_A: return Controller::PAD_BUTTON_CROSS;
case SDL_CONTROLLER_BUTTON_B: return Controller::PAD_BUTTON_CIRCLE;
case SDL_CONTROLLER_BUTTON_X: return Controller::PAD_BUTTON_SQUARE;
case SDL_CONTROLLER_BUTTON_Y: return Controller::PAD_BUTTON_TRIANGLE;
case SDL_CONTROLLER_BUTTON_START: return Controller::PAD_BUTTON_OPTIONS;
case SDL_CONTROLLER_BUTTON_LEFTSTICK: return Controller::PAD_BUTTON_L3;
case SDL_CONTROLLER_BUTTON_RIGHTSTICK: return Controller::PAD_BUTTON_R3;
case SDL_CONTROLLER_BUTTON_LEFTSHOULDER: return Controller::PAD_BUTTON_L1;
case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER: return Controller::PAD_BUTTON_R1;
case SDL_CONTROLLER_BUTTON_DPAD_UP: return Controller::PAD_BUTTON_UP;
case SDL_CONTROLLER_BUTTON_DPAD_DOWN: return Controller::PAD_BUTTON_DOWN;
case SDL_CONTROLLER_BUTTON_DPAD_LEFT: return Controller::PAD_BUTTON_LEFT;
case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: return Controller::PAD_BUTTON_RIGHT;
case SDL_CONTROLLER_BUTTON_TOUCHPAD: return Controller::PAD_BUTTON_TOUCH_PAD;
case SDL_GAMEPAD_BUTTON_SOUTH: return Controller::PAD_BUTTON_CROSS;
case SDL_GAMEPAD_BUTTON_EAST: return Controller::PAD_BUTTON_CIRCLE;
case SDL_GAMEPAD_BUTTON_WEST: return Controller::PAD_BUTTON_SQUARE;
case SDL_GAMEPAD_BUTTON_NORTH: return Controller::PAD_BUTTON_TRIANGLE;
case SDL_GAMEPAD_BUTTON_START: return Controller::PAD_BUTTON_OPTIONS;
case SDL_GAMEPAD_BUTTON_LEFT_STICK: return Controller::PAD_BUTTON_L3;
case SDL_GAMEPAD_BUTTON_RIGHT_STICK: return Controller::PAD_BUTTON_R3;
case SDL_GAMEPAD_BUTTON_LEFT_SHOULDER: return Controller::PAD_BUTTON_L1;
case SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER: return Controller::PAD_BUTTON_R1;
case SDL_GAMEPAD_BUTTON_DPAD_UP: return Controller::PAD_BUTTON_UP;
case SDL_GAMEPAD_BUTTON_DPAD_DOWN: return Controller::PAD_BUTTON_DOWN;
case SDL_GAMEPAD_BUTTON_DPAD_LEFT: return Controller::PAD_BUTTON_LEFT;
case SDL_GAMEPAD_BUTTON_DPAD_RIGHT: return Controller::PAD_BUTTON_RIGHT;
case SDL_GAMEPAD_BUTTON_TOUCHPAD: return Controller::PAD_BUTTON_TOUCH_PAD;
default: return 0;
}
}
static Controller::Axis ControllerAxisFromSdl(int axis_id) {
switch (axis_id) {
case SDL_CONTROLLER_AXIS_LEFTX: return Controller::Axis::LeftX;
case SDL_CONTROLLER_AXIS_LEFTY: return Controller::Axis::LeftY;
case SDL_CONTROLLER_AXIS_RIGHTX: return Controller::Axis::RightX;
case SDL_CONTROLLER_AXIS_RIGHTY: return Controller::Axis::RightY;
case SDL_CONTROLLER_AXIS_TRIGGERLEFT: return Controller::Axis::TriggerLeft;
case SDL_CONTROLLER_AXIS_TRIGGERRIGHT: return Controller::Axis::TriggerRight;
case SDL_GAMEPAD_AXIS_LEFTX: return Controller::Axis::LeftX;
case SDL_GAMEPAD_AXIS_LEFTY: return Controller::Axis::LeftY;
case SDL_GAMEPAD_AXIS_RIGHTX: return Controller::Axis::RightX;
case SDL_GAMEPAD_AXIS_RIGHTY: return Controller::Axis::RightY;
case SDL_GAMEPAD_AXIS_LEFT_TRIGGER: return Controller::Axis::TriggerLeft;
case SDL_GAMEPAD_AXIS_RIGHT_TRIGGER: return Controller::Axis::TriggerRight;
default: return Controller::Axis::AxisMax;
}
}
static bool ControllerAxisIsTrigger(int axis_id) {
return axis_id == SDL_CONTROLLER_AXIS_TRIGGERLEFT ||
axis_id == SDL_CONTROLLER_AXIS_TRIGGERRIGHT;
return axis_id == SDL_GAMEPAD_AXIS_LEFT_TRIGGER ||
axis_id == SDL_GAMEPAD_AXIS_RIGHT_TRIGGER;
}
static int ControllerAxisValueFromSdl(int axis_id, int axis_value) {
@@ -165,22 +149,6 @@ struct EventController {
double timestamp_seconds;
};
enum class DisplayOrientation {
Unknown, /* The display orientation can't be determined */
Landscape, /* The display is in landscape mode, with the right side up, relative to portrait
mode */
LandscapeFlipped, /* The display is in landscape mode, with the left side up, relative to
portrait mode */
Portrait, /* The display is in portrait mode */
PortraitFlipped, /* The display is in portrait mode, upside down */
DisplayEventOrientation = 0xF0
};
struct EventDisplay {
DisplayOrientation orientation;
};
namespace {
std::unique_ptr<WindowContext> g_window;
@@ -213,16 +181,13 @@ static void ToggleDesktopFullscreen() {
}
const auto flags = static_cast<uint32_t>(SDL_GetWindowFlags(g_window->window));
const bool fullscreen =
(flags & static_cast<uint32_t>(SDL_WINDOW_FULLSCREEN_DESKTOP)) != 0u;
const auto mode =
fullscreen ? 0u : static_cast<uint32_t>(SDL_WINDOW_FULLSCREEN_DESKTOP);
if (SDL_SetWindowFullscreen(g_window->window, mode) != 0) {
const bool fullscreen = (flags & SDL_WINDOW_FULLSCREEN) != 0u;
if (!SDL_SetWindowFullscreen(g_window->window, !fullscreen)) {
LOGF("Toggle fullscreen failed: %s\n", SDL_GetError());
}
}
static void GameEventKeyboard(WindowLoopState& game, const EventKeyboard& key) {
static void GameEventKeyboard(const EventKeyboard& key) {
static SDL_Keycode fullscreen_key = SDLK_UNKNOWN;
#ifdef KYTY_DBG_INPUT
@@ -235,7 +200,6 @@ static void GameEventKeyboard(WindowLoopState& game, const EventKeyboard& key) {
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS || KYTY_PLATFORM == KYTY_PLATFORM_LINUX
if (key.down) {
switch (key.key_code) {
case SDLK_SPACE: SetPause(game, !game.paused.load(std::memory_order_acquire)); break;
case SDLK_F1:
if (!key.repeat) {
RenderDocRequestCapture();
@@ -248,7 +212,7 @@ static void GameEventKeyboard(WindowLoopState& game, const EventKeyboard& key) {
break;
case SDLK_RETURN:
case SDLK_KP_ENTER:
if (!key.repeat && (key.mod & KMOD_ALT) != 0) {
if (!key.repeat && (key.mod & SDL_KMOD_ALT) != 0) {
ToggleDesktopFullscreen();
fullscreen_key = key.key_code;
}
@@ -341,15 +305,15 @@ static void GameEventController([[maybe_unused]] const EventController& f) {
#endif
if (f.added) {
auto* pad = SDL_GameControllerOpen(f.id);
auto* pad = SDL_OpenGamepad(f.id);
EXIT_NOT_IMPLEMENTED(pad == nullptr);
int id = SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(pad));
int id = SDL_GetJoystickID(SDL_GetGamepadJoystick(pad));
Controller::Connect(id);
}
if (f.removed) {
Controller::Disconnect(f.id);
SDL_GameControllerClose(SDL_GameControllerFromInstanceID(f.id));
SDL_CloseGamepad(SDL_GetGamepadFromID(f.id));
}
if (f.down || f.up) {
@@ -401,81 +365,73 @@ void WindowContext::Resize(uint32_t new_width, uint32_t new_height) {
void WindowContext::ProcessWindowEvent(const SDL_WindowEvent& event) {
const auto& window_event = event;
switch (window_event.event) {
case SDL_WINDOWEVENT_SHOWN:
switch (window_event.type) {
case SDL_EVENT_WINDOW_SHOWN:
LOGF("Window %" PRIu32 " shown\n", window_event.windowID);
break;
case SDL_WINDOWEVENT_HIDDEN:
case SDL_EVENT_WINDOW_HIDDEN:
LOGF("Window %" PRIu32 " hidden\n", window_event.windowID);
break;
case SDL_WINDOWEVENT_EXPOSED:
case SDL_EVENT_WINDOW_EXPOSED:
LOGF("Window %" PRIu32 " exposed\n", window_event.windowID);
break;
case SDL_WINDOWEVENT_MOVED:
case SDL_EVENT_WINDOW_MOVED:
LOGF("Window %" PRIu32 " moved to %" PRId32 ",%" PRId32 "\n", window_event.windowID,
window_event.data1, window_event.data2);
break;
case SDL_WINDOWEVENT_RESIZED:
case SDL_EVENT_WINDOW_RESIZED:
LOGF("Window %" PRIu32 " resized to %" PRId32 "x%" PRId32 "\n", window_event.windowID,
window_event.data1, window_event.data2);
LOGF("m: %d\n", static_cast<int>(SDL_ThreadID()));
Resize(window_event.data1, window_event.data2);
break;
case SDL_WINDOWEVENT_SIZE_CHANGED:
case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
LOGF("Window %" PRIu32 " size changed to %" PRId32 "x%" PRId32 "\n",
window_event.windowID, window_event.data1, window_event.data2);
LOGF("m: %d\n", static_cast<int>(SDL_ThreadID()));
Resize(window_event.data1, window_event.data2);
break;
case SDL_WINDOWEVENT_MINIMIZED:
case SDL_EVENT_WINDOW_MINIMIZED:
LOGF("Window %" PRIu32 " minimized\n", window_event.windowID);
break;
case SDL_WINDOWEVENT_MAXIMIZED:
case SDL_EVENT_WINDOW_MAXIMIZED:
LOGF("Window %" PRIu32 " maximized\n", window_event.windowID);
break;
case SDL_WINDOWEVENT_RESTORED:
case SDL_EVENT_WINDOW_RESTORED:
LOGF("Window %" PRIu32 " restored\n", window_event.windowID);
break;
case SDL_WINDOWEVENT_ENTER:
case SDL_EVENT_WINDOW_MOUSE_ENTER:
LOGF("Mouse entered window %" PRIu32 "\n", window_event.windowID);
break;
case SDL_WINDOWEVENT_LEAVE:
case SDL_EVENT_WINDOW_MOUSE_LEAVE:
LOGF("Mouse left window %" PRIu32 "\n", window_event.windowID);
break;
case SDL_WINDOWEVENT_FOCUS_GAINED:
case SDL_EVENT_WINDOW_FOCUS_GAINED:
LOGF("Window %" PRIu32 " gained keyboard focus\n", window_event.windowID);
break;
case SDL_WINDOWEVENT_FOCUS_LOST:
case SDL_EVENT_WINDOW_FOCUS_LOST:
LOGF("Window %" PRIu32 " lost keyboard focus\n", window_event.windowID);
break;
case SDL_WINDOWEVENT_CLOSE:
case SDL_EVENT_WINDOW_CLOSE_REQUESTED:
LOGF("Window %" PRIu32 " closed\n", window_event.windowID);
break;
default:
LOGF("Window %" PRIu32 " got unknown event %" PRIu8 "\n", window_event.windowID,
window_event.event);
LOGF("Window %" PRIu32 " got unknown event %" PRIu32 "\n", window_event.windowID,
static_cast<uint32_t>(window_event.type));
break;
}
}
void WindowContext::ProcessDisplayEvent(const SDL_DisplayEvent& display) {
bool sdl = false;
switch (display.event) {
case SDL_DISPLAYEVENT_ORIENTATION: sdl = true; [[fallthrough]];
case static_cast<Uint8>(DisplayOrientation::DisplayEventOrientation): {
LOGF("Display %" PRIu32 "[%s] changed orientation to %d - ", display.display,
sdl ? "SDL" : "Kyty", static_cast<int>(display.data1));
switch (display.type) {
case SDL_EVENT_DISPLAY_ORIENTATION: {
LOGF("Display %" PRIu32 " changed orientation to %d - ", display.displayID,
static_cast<int>(display.data1));
switch (display.data1) {
case SDL_ORIENTATION_UNKNOWN: LOGF("UNKNOWN\n"); break;
@@ -489,8 +445,8 @@ void WindowContext::ProcessDisplayEvent(const SDL_DisplayEvent& display) {
break;
}
default:
LOGF("Display %" PRIu32 " got unknown event 0x%" PRIx8 "\n", display.display,
display.event);
LOGF("Display %" PRIu32 " got unknown event 0x%" PRIx32 "\n", display.displayID,
static_cast<uint32_t>(display.type));
break;
}
}
@@ -498,10 +454,9 @@ void WindowContext::ProcessDisplayEvent(const SDL_DisplayEvent& display) {
void WindowContext::ProcessEvent(double time_s) {
auto& game = loop;
auto* event = &game.event;
EXIT_IF(SDL_GetEventState(SDL_DISPLAYEVENT) != SDL_ENABLE);
if ((event->type == SDL_KEYDOWN || event->type == SDL_KEYUP) &&
event->key.keysym.sym == SDLK_F7) {
if (event->type == SDL_KEYDOWN && event->key.repeat == 0) {
if ((event->type == SDL_EVENT_KEY_DOWN || event->type == SDL_EVENT_KEY_UP) &&
event->key.key == SDLK_F7) {
if (event->type == SDL_EVENT_KEY_DOWN && event->key.repeat == 0) {
HostInputToggleMouseToJoystick();
}
return;
@@ -509,59 +464,63 @@ void WindowContext::ProcessEvent(double time_s) {
if (ProcessSystemOverlayInput(*event)) {
return;
}
if (event->type >= SDL_EVENT_WINDOW_FIRST && event->type <= SDL_EVENT_WINDOW_LAST) {
ProcessWindowEvent(event->window);
return;
}
if (event->type >= SDL_EVENT_DISPLAY_FIRST && event->type <= SDL_EVENT_DISPLAY_LAST) {
ProcessDisplayEvent(event->display);
return;
}
switch (event->type) {
case SDL_QUIT: GameEventQuit(game); break;
case SDL_EVENT_QUIT: GameEventQuit(game); break;
case SDL_APP_TERMINATING: GameEventTerminate(game); break;
case SDL_EVENT_TERMINATING: GameEventTerminate(game); break;
case SDL_APP_LOWMEMORY: GameEventLowMemory(); break;
case SDL_EVENT_LOW_MEMORY: GameEventLowMemory(); break;
case SDL_APP_WILLENTERBACKGROUND: GameEventWillEnterBackground(game); break;
case SDL_EVENT_WILL_ENTER_BACKGROUND: GameEventWillEnterBackground(game); break;
case SDL_APP_DIDENTERBACKGROUND: GameEventDidEnterBackground(); break;
case SDL_EVENT_DID_ENTER_BACKGROUND: GameEventDidEnterBackground(); break;
case SDL_APP_WILLENTERFOREGROUND: GameEventWillEnterForeground(); break;
case SDL_EVENT_WILL_ENTER_FOREGROUND: GameEventWillEnterForeground(); break;
case SDL_APP_DIDENTERFOREGROUND: GameEventDidEnterForeground(game); break;
case SDL_EVENT_DID_ENTER_FOREGROUND: GameEventDidEnterForeground(game); break;
case SDL_KEYDOWN:
case SDL_KEYUP: {
case SDL_EVENT_KEY_DOWN:
case SDL_EVENT_KEY_UP: {
EventKeyboard key {};
key.down = (event->type == SDL_KEYDOWN);
key.up = (event->type == SDL_KEYUP);
key.pressed = (event->key.state == SDL_PRESSED);
key.released = (event->key.state == SDL_RELEASED);
key.down = (event->type == SDL_EVENT_KEY_DOWN);
key.up = (event->type == SDL_EVENT_KEY_UP);
key.pressed = event->key.down;
key.released = !event->key.down;
key.repeat = (event->key.repeat != 0u);
key.scan_code = event->key.keysym.scancode;
key.key_code = event->key.keysym.sym;
key.mod = event->key.keysym.mod;
key.scan_code = event->key.scancode;
key.key_code = event->key.key;
key.mod = event->key.mod;
key.timestamp_seconds = time_s;
GameEventKeyboard(game, key);
GameEventKeyboard(key);
break;
}
case SDL_WINDOWEVENT: ProcessWindowEvent(event->window); break;
case SDL_DISPLAYEVENT: ProcessDisplayEvent(event->display); break;
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP: {
case SDL_EVENT_MOUSE_BUTTON_DOWN:
case SDL_EVENT_MOUSE_BUTTON_UP: {
EventMouse mb {};
mb.down = (event->button.type == SDL_MOUSEBUTTONDOWN);
mb.up = (event->button.type == SDL_MOUSEBUTTONUP);
mb.down = (event->button.type == SDL_EVENT_MOUSE_BUTTON_DOWN);
mb.up = (event->button.type == SDL_EVENT_MOUSE_BUTTON_UP);
mb.left = (event->button.button == SDL_BUTTON_LEFT);
mb.middle = (event->button.button == SDL_BUTTON_MIDDLE);
mb.right = (event->button.button == SDL_BUTTON_RIGHT);
mb.x1 = (event->button.button == SDL_BUTTON_X1);
mb.x2 = (event->button.button == SDL_BUTTON_X2);
mb.touch = (event->button.which == SDL_TOUCH_MOUSEID);
mb.pressed = (event->button.state == SDL_PRESSED);
mb.released = (event->button.state == SDL_RELEASED);
mb.pressed = event->button.down;
mb.released = !event->button.down;
mb.num_of_clicks = event->button.clicks;
mb.wheel = false;
mb.x = event->button.x;
@@ -576,7 +535,7 @@ void WindowContext::ProcessEvent(double time_s) {
break;
}
case SDL_MOUSEWHEEL: {
case SDL_EVENT_MOUSE_WHEEL: {
EventMouse mb {};
mb.down = false;
@@ -603,7 +562,7 @@ void WindowContext::ProcessEvent(double time_s) {
break;
}
case SDL_MOUSEMOTION: {
case SDL_EVENT_MOUSE_MOTION: {
EventMouse mb {};
mb.down = false;
@@ -630,16 +589,16 @@ void WindowContext::ProcessEvent(double time_s) {
break;
}
case SDL_FINGERMOTION:
case SDL_FINGERDOWN:
case SDL_FINGERUP: {
case SDL_EVENT_FINGER_MOTION:
case SDL_EVENT_FINGER_DOWN:
case SDL_EVENT_FINGER_UP: {
EventFinger f {};
f.down = (event->tfinger.type == SDL_FINGERDOWN);
f.up = (event->tfinger.type == SDL_FINGERUP);
f.motion = (event->tfinger.type == SDL_FINGERMOTION);
f.finger_id = static_cast<int>(event->tfinger.fingerId);
f.touch_id = static_cast<int>(event->tfinger.touchId);
f.down = (event->tfinger.type == SDL_EVENT_FINGER_DOWN);
f.up = (event->tfinger.type == SDL_EVENT_FINGER_UP);
f.motion = (event->tfinger.type == SDL_EVENT_FINGER_MOTION);
f.finger_id = static_cast<int>(event->tfinger.fingerID);
f.touch_id = static_cast<int>(event->tfinger.touchID);
f.x = event->tfinger.x;
f.y = event->tfinger.y;
f.dx = event->tfinger.dx;
@@ -652,13 +611,13 @@ void WindowContext::ProcessEvent(double time_s) {
break;
}
case SDL_CONTROLLERAXISMOTION: {
case SDL_EVENT_GAMEPAD_AXIS_MOTION: {
EventController c {};
c.id = event->caxis.which;
c.button = SDL_CONTROLLER_BUTTON_INVALID;
c.axis_id = event->caxis.axis;
c.axis_value = event->caxis.value;
c.id = event->gaxis.which;
c.button = SDL_GAMEPAD_BUTTON_INVALID;
c.axis_id = event->gaxis.axis;
c.axis_value = event->gaxis.value;
c.down = false;
c.up = false;
c.added = false;
@@ -674,22 +633,22 @@ void WindowContext::ProcessEvent(double time_s) {
break;
}
case SDL_CONTROLLERBUTTONDOWN:
case SDL_CONTROLLERBUTTONUP: {
case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
case SDL_EVENT_GAMEPAD_BUTTON_UP: {
EventController c {};
c.id = event->cbutton.which;
c.button = event->cbutton.button;
c.axis_id = SDL_CONTROLLER_AXIS_INVALID;
c.id = event->gbutton.which;
c.button = event->gbutton.button;
c.axis_id = SDL_GAMEPAD_AXIS_INVALID;
c.axis_value = 0;
c.down = (event->cbutton.type == SDL_CONTROLLERBUTTONDOWN);
c.up = (event->cbutton.type == SDL_CONTROLLERBUTTONUP);
c.down = event->gbutton.down;
c.up = !event->gbutton.down;
c.added = false;
c.removed = false;
c.remapped = false;
c.axis = false;
c.pressed = (event->cbutton.state == SDL_PRESSED);
c.released = (event->cbutton.state == SDL_RELEASED);
c.pressed = event->gbutton.down;
c.released = !event->gbutton.down;
c.timestamp_seconds = time_s;
GameEventController(c);
@@ -697,44 +656,44 @@ void WindowContext::ProcessEvent(double time_s) {
break;
}
case SDL_CONTROLLERTOUCHPADDOWN:
case SDL_CONTROLLERTOUCHPADMOTION:
case SDL_CONTROLLERTOUCHPADUP:
if (event->ctouchpad.touchpad == 0) {
Controller::SetTouchPad(event->ctouchpad.which, event->ctouchpad.finger,
event->ctouchpad.type != SDL_CONTROLLERTOUCHPADUP,
event->ctouchpad.x, event->ctouchpad.y);
case SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN:
case SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION:
case SDL_EVENT_GAMEPAD_TOUCHPAD_UP:
if (event->gtouchpad.touchpad == 0) {
Controller::SetTouchPad(event->gtouchpad.which, event->gtouchpad.finger,
event->gtouchpad.type != SDL_EVENT_GAMEPAD_TOUCHPAD_UP,
event->gtouchpad.x, event->gtouchpad.y);
}
break;
case SDL_CONTROLLERSENSORUPDATE: {
const auto& sensor = event->csensor;
case SDL_EVENT_GAMEPAD_SENSOR_UPDATE: {
const auto& sensor = event->gsensor;
if (sensor.sensor == SDL_SENSOR_ACCEL || sensor.sensor == SDL_SENSOR_GYRO) {
Controller::SetSensor(sensor.which,
sensor.sensor == SDL_SENSOR_ACCEL ? Controller::Sensor::Accel
: Controller::Sensor::Gyro,
sensor.data,
sensor.timestamp_us != 0
? sensor.timestamp_us
: static_cast<uint64_t>(sensor.timestamp) * 1000);
sensor.sensor_timestamp != 0
? sensor.sensor_timestamp / 1000
: sensor.timestamp / 1000);
}
break;
}
case SDL_CONTROLLERDEVICEADDED:
case SDL_CONTROLLERDEVICEREMOVED:
case SDL_CONTROLLERDEVICEREMAPPED: {
case SDL_EVENT_GAMEPAD_ADDED:
case SDL_EVENT_GAMEPAD_REMOVED:
case SDL_EVENT_GAMEPAD_REMAPPED: {
EventController c {};
c.id = event->cdevice.which;
c.button = SDL_CONTROLLER_BUTTON_INVALID;
c.axis_id = SDL_CONTROLLER_AXIS_INVALID;
c.id = event->gdevice.which;
c.button = SDL_GAMEPAD_BUTTON_INVALID;
c.axis_id = SDL_GAMEPAD_AXIS_INVALID;
c.axis_value = 0;
c.down = false;
c.up = false;
c.added = (event->cdevice.type == SDL_CONTROLLERDEVICEADDED);
c.removed = (event->cdevice.type == SDL_CONTROLLERDEVICEREMOVED);
c.remapped = (event->cdevice.type == SDL_CONTROLLERDEVICEREMAPPED);
c.added = (event->gdevice.type == SDL_EVENT_GAMEPAD_ADDED);
c.removed = (event->gdevice.type == SDL_EVENT_GAMEPAD_REMOVED);
c.remapped = (event->gdevice.type == SDL_EVENT_GAMEPAD_REMAPPED);
c.axis = false;
c.pressed = false;
c.released = false;
@@ -747,47 +706,6 @@ void WindowContext::ProcessEvent(double time_s) {
}
}
void WindowContext::RunOnMainThread(std::function<void()> task) {
if (Common::Thread::IsMainThread()) {
task();
return;
}
uint64_t ticket = 0;
{
Common::LockGuard lock(main_task_mutex);
main_tasks.push_back(std::move(task));
ticket = ++main_tasks_queued;
}
// Wake the main loop in case it is blocked in SDL_WaitEvent.
SDL_Event event {};
event.type = SDL_USEREVENT;
SDL_PushEvent(&event);
Common::LockGuard lock(main_task_mutex);
while (main_tasks_run < ticket) {
main_task_done.Wait(&main_task_mutex);
}
}
void WindowContext::DrainMainThreadTasks() {
std::vector<std::function<void()>> tasks;
{
Common::LockGuard lock(main_task_mutex);
tasks.swap(main_tasks);
}
if (tasks.empty()) {
return;
}
for (auto& task: tasks) {
task();
}
Common::LockGuard lock(main_task_mutex);
main_tasks_run += tasks.size();
main_task_done.SignalAll();
}
void WindowContext::Run() {
Common::Timer timer;
timer.Start();
@@ -797,7 +715,6 @@ void WindowContext::Run() {
loop.paused.store(false, std::memory_order_release);
while (!loop.need_exit) {
DrainMainThreadTasks();
if (loop.paused.load(std::memory_order_acquire)) {
if (!timer.IsPaused()) {
timer.Pause();
@@ -821,17 +738,15 @@ static void WindowCreate(WindowContext& context) {
int width = static_cast<int>(context.graphic_ctx.screen_width);
int height = static_cast<int>(context.graphic_ctx.screen_height);
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1");
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1");
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
SDL_SetHint(SDL_HINT_WINDOWS_DPI_SCALING, "0");
#if KYTY_PLATFORM == KYTY_PLATFORM_LINUX
// RenderDoc can hide Wayland Vulkan surface support; prefer X11 for captures.
if (Config::RenderDocEnabled()) {
SDL_SetHint(SDL_HINT_VIDEO_DRIVER, "x11,wayland");
}
#endif
if (SDL_InitSubSystem(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER) < 0) {
if (!SDL_InitSubSystem(SDL_INIT_VIDEO | SDL_INIT_GAMEPAD)) {
EXIT("%s\n", SDL_GetError());
}
HostInputInit();
InitializeSystemOverlayInput();
LOGF("WindowCreate(): width = %d, height = %d\n", width, height);
@@ -841,15 +756,14 @@ static void WindowCreate(WindowContext& context) {
// MoltenVK loader before calling SDL_CreateWindow. Keep an explicit user
// override, and fall back to SDL's normal loader search when no bundle is present.
if (std::getenv("SDL_VULKAN_LIBRARY") == nullptr) {
if (char* base_path = SDL_GetBasePath(); base_path != nullptr) {
if (const char* base_path = SDL_GetBasePath(); base_path != nullptr) {
const std::string base_path_str = base_path;
SDL_free(base_path);
std::string moltenvk_path = base_path_str + "libMoltenVK.dylib";
if (!Common::File::IsFileExisting(moltenvk_path)) {
moltenvk_path = base_path_str + "../Frameworks/libMoltenVK.dylib";
}
if (Common::File::IsFileExisting(moltenvk_path) &&
SDL_setenv("SDL_VULKAN_LIBRARY", moltenvk_path.c_str(), 0) == 0) {
SDL_setenv_unsafe("SDL_VULKAN_LIBRARY", moltenvk_path.c_str(), 0) == 0) {
LOGF("Vulkan loader: %s\n", moltenvk_path.c_str());
}
}
@@ -862,21 +776,22 @@ static void WindowCreate(WindowContext& context) {
window_flags |= static_cast<uint32_t>(SDL_WINDOW_BORDERLESS);
}
#endif
context.window = SDL_CreateWindow(KYTY_SDL_WINDOW_CAPTION, SDL_WINDOWPOS_CENTERED,
SDL_WINDOWPOS_CENTERED, width, height, window_flags);
context.window = SDL_CreateWindow(KYTY_SDL_WINDOW_CAPTION, width, height, window_flags);
if (context.window == nullptr) {
EXIT("%s\n", SDL_GetError());
}
SDL_SetWindowResizable(context.window, SDL_FALSE);
SDL_SetWindowPosition(context.window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
HostInputInit(context.window);
InitializeSystemOverlayInput(context.window);
context.UpdateIcon();
}
uint32_t WindowContext::InitialWindowFlags(bool fullscreen) noexcept {
auto flags = static_cast<uint32_t>(SDL_WINDOW_VULKAN);
auto flags = static_cast<uint32_t>(SDL_WINDOW_VULKAN | SDL_WINDOW_RESIZABLE);
if (fullscreen) {
flags |= static_cast<uint32_t>(SDL_WINDOW_FULLSCREEN_DESKTOP);
flags |= static_cast<uint32_t>(SDL_WINDOW_FULLSCREEN);
}
return flags;
}
@@ -907,6 +822,7 @@ void WindowRun() {
void WindowShutdown() {
if (g_window != nullptr) {
HostInputShutdown();
Controller::EmergencyShutdown();
g_window.reset();
}
@@ -942,7 +858,7 @@ struct WindowIcon {
void* pixels = nullptr;
~WindowIcon() {
SDL_FreeSurface(surface);
SDL_DestroySurface(surface);
stbi_image_free(pixels);
}
};
@@ -966,8 +882,8 @@ static void WindowLoadPngIcon(const std::filesystem::path& path, WindowIcon* ico
EXIT_IF(icon->pixels == nullptr);
icon->surface = SDL_CreateRGBSurfaceWithFormatFrom(icon->pixels, width, height, 32, width * 4,
SDL_PIXELFORMAT_RGBA32);
icon->surface = SDL_CreateSurfaceFrom(width, height, SDL_PIXELFORMAT_RGBA32, icon->pixels,
width * 4);
EXIT_NOT_IMPLEMENTED(icon->surface == nullptr);
}
@@ -1029,7 +945,16 @@ void WindowContext::UpdateTitle() {
(has_title_id ? ", " : ""), (has_app_ver ? app_ver : ""), (has_app_ver ? " " : ""),
device_name, processor_name, frame_num, current_fps);
RunOnMainThread([this, text = std::move(text)] { SDL_SetWindowTitle(window, text.c_str()); });
struct TitleUpdate {
SDL_Window* window;
std::string* text;
} update {window, &text};
EXIT_IF(!SDL_RunOnMainThread(
[](void* data) {
auto& title = *static_cast<TitleUpdate*>(data);
SDL_SetWindowTitle(title.window, title.text->c_str());
},
&update, true));
}
} // namespace Libs::Graphics
@@ -1,15 +1,14 @@
#ifndef EMULATOR_SRC_GRAPHICS_PRESENTATION_WINDOW_WINDOWINTERNAL_H_
#define EMULATOR_SRC_GRAPHICS_PRESENTATION_WINDOW_WINDOWINTERNAL_H_
#include "SDL_events.h"
#include "SDL_video.h"
#include <SDL3/SDL.h>
#include "common/threads.h"
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include <atomic>
#include <cstdint>
#include <functional>
#include <memory>
#include <vector>
@@ -48,9 +47,6 @@ struct WindowContext {
void ProcessDisplayEvent(const SDL_DisplayEvent& event);
void ProcessEvent(double time_seconds);
void Run();
// SDL window operations must complete on the main thread.
void RunOnMainThread(std::function<void()> task);
void DrainMainThreadTasks();
GraphicContext graphic_ctx;
SDL_Window* window = nullptr;
@@ -61,12 +57,6 @@ struct WindowContext {
WindowLoopState loop;
Common::Mutex mutex;
Common::Mutex main_task_mutex;
Common::CondVar main_task_done;
std::vector<std::function<void()>> main_tasks; // guarded by main_task_mutex
uint64_t main_tasks_queued = 0; // guarded by main_task_mutex
uint64_t main_tasks_run = 0; // guarded by main_task_mutex
};
} // namespace Libs::Graphics
@@ -20,6 +20,7 @@
#include <algorithm>
#include <array>
#include <atomic>
#include <chrono>
#include <fmt/format.h>
#include <map>
@@ -523,6 +524,20 @@ TranslateResult TranslateProgram(std::span<const uint32_t> code, const CompileOp
GetDumpLabel(options), StageName(options.stage), options.shader_hash,
static_cast<uint64_t>(decoded.instructions.size()), phase_ms());
// Temporary workaround for games that compile ray-tracing shaders before
// the player can select a mode without ray tracing.
if (options.stage == ShaderType::Compute && decoded.has_bvh) {
static std::atomic_flag warned = ATOMIC_FLAG_INIT;
if (!warned.test_and_set(std::memory_order_relaxed)) {
const auto& bvh = decoded.instructions.back();
Log::WriteToConsoleAndLog(fmt::format(
"Warning: ray tracing is not implemented; skipping compute dispatches containing "
"BVH intersection instructions (shader=0x{:016x}, pc=0x{:08x}, opcode=0x{:02x}).\n",
options.shader_hash, bvh.pc, bvh.opcode_id));
}
return {.skip_dispatch = true};
}
std::string decoded_dump;
if (options.dump_ir) {
decoded_dump = Decoder::ProgramToString(decoded);
@@ -615,6 +630,7 @@ TranslateResult TranslateProgram(std::span<const uint32_t> code, const CompileOp
CompileResult CompileProgram(TranslateResult translated, const CompileOptions& options,
const IR::ResourceSpecialization& specialization,
uint32_t push_data_start_dword) {
EXIT_IF(translated.skip_dispatch);
const auto emit_begin = std::chrono::steady_clock::now();
auto& ir = translated.program;
IR::ApplyResourceSpecialization(ir, specialization);
@@ -28,6 +28,7 @@ struct TranslateResult {
IR::Program program;
std::string decoded_dump;
std::string cfg_dump;
bool skip_dispatch = false;
};
struct CompileResult {
@@ -132,12 +132,23 @@ void ValidateNativeProgram(const IR::Program& program) {
program.memory_info[index].planning_only;
});
};
const auto indirect_buffer_handle = [&](const IR::Inst& handle) {
return program.info.uses_dma && handle.NumArgs() == 4u && !handle.Uses().empty() &&
std::ranges::all_of(handle.Uses(), [&](const IR::Use& use) {
if (IR::BufferAccessOf(use.user->GetOpcode()) != IR::BufferAccess::Read) {
return false;
}
const auto index = use.user->Flags<IR::MemoryFlags>().index;
return index < program.memory_info.size() &&
program.memory_info[index].kind == IR::ResourceKind::IndirectBuffer;
});
};
for (const auto* block: program.blocks) {
for (const auto& inst: *block) {
const auto dense = inst.Flags<uint32_t>();
switch (inst.GetOpcode()) {
case IR::ValueOpcode::GetBufferResource:
if (planning_only_handle(inst)) {
if (planning_only_handle(inst) || indirect_buffer_handle(inst)) {
break;
}
if (dense >= program.info.buffers.size()) {
@@ -212,7 +223,11 @@ Emitter::SpirvRequirements Emitter::AnalyzeProgramRequirements(const IR::Program
Fail(program, "buffer operation has invalid memory metadata");
}
const auto& memory = program.memory_info[memory_index];
if (memory.kind == IR::ResourceKind::IndirectBuffer) {
requirements.subgroup_local_invocation_id = true;
}
if (memory.kind == IR::ResourceKind::Buffer) {
requirements.coherent_buffers |= memory.coherent;
if (memory.resource >= program.info.buffers.size()) {
Fail(program, "buffer operation has invalid resource metadata");
}
@@ -559,6 +559,10 @@ void EmitVoid(ValueEmitContext&) {}
void EmitBarrier(EmitterState& state) {
const auto tessellation = state.program.stage == ShaderType::TessellationControl;
if (!tessellation && ShaderWorkgroupInput(state.program.stage, state.input_info) == nullptr) {
// Independent graphics invocations have no native workgroup left to synchronize.
return;
}
const auto memory_scope = tessellation ? spv::ScopeInvocation : spv::ScopeWorkgroup;
const auto semantics = tessellation ? spv::MemorySemanticsMaskNone
: spv::MemorySemanticsAcquireReleaseMask |
@@ -63,10 +63,10 @@ uint32_t AddressF32(ValueEmitContext& ctx, const IR::MemoryInfo& mem, const IR::
: Unary(ctx.state, spv::OpBitcast, TypeF32(ctx.state), value);
}
ImageSampleLayout Layout(const IR::MemoryInfo& mem, ImageDimension dimension) {
ImageSampleLayout Layout(const IR::MemoryInfo& mem) {
ImageSampleLayout layout;
uint32_t cursor = 0;
const auto& info = ImageDimensionInfoFor(dimension);
const auto& info = ImageDimensionInfoFor(mem.image_dimension);
if (HasFlag(mem, Decoder::ImageSampleFlagOffset)) layout.offset = cursor++;
if (HasFlag(mem, Decoder::ImageSampleFlagBias)) layout.bias = cursor++;
if (HasFlag(mem, Decoder::ImageSampleFlagCompare)) layout.dref = cursor++;
@@ -104,8 +104,7 @@ uint32_t CubeLayer(EmitterState& state, uint32_t value) {
}
uint32_t CoordF32(ValueEmitContext& ctx, const IR::MemoryInfo& mem, const IR::Inst& address,
uint32_t first, uint32_t components) {
const bool cube = ctx.state.program.info.images.at(mem.resource).cube;
uint32_t first, uint32_t components, bool cube = false) {
auto x = AddressF32(ctx, mem, address, first);
if (components == 1u) return x;
auto y = mem.image_address_components > first + 1u ? AddressF32(ctx, mem, address, first + 1u)
@@ -576,7 +575,8 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
const auto lod = state.builder.AllocateId();
state.builder.AddFunction(
spv::OpImageQueryLod, TypeF32Vector(state, 2), lod, sampled,
CoordF32(ctx, mem, *address, 0, ImageDimensionInfoFor(dimension).spatial_components));
CoordF32(ctx, mem, *address, 0, ImageDimensionInfoFor(dimension).spatial_components,
image.cube));
uint32_t values[4] = {ConstantU32(state, 0), ConstantU32(state, 0), ConstantU32(state, 0),
ConstantU32(state, 0)};
for (uint32_t index = 0; index < 2u; index++) {
@@ -637,7 +637,7 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
if (op == IR::ValueOpcode::ImageSampleRaw || op == IR::ValueOpcode::ImageGatherRaw) {
const auto dimension = image.dimension;
const auto& dimension_info = ImageDimensionInfoFor(dimension);
const auto layout = Layout(mem, dimension);
const auto layout = Layout(mem);
const auto numeric_class = image.numeric_class;
const bool dref = HasFlag(mem, Decoder::ImageSampleFlagCompare);
if (dref && state.program.info.images[mem.resource].conversion_format !=
@@ -645,9 +645,9 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
ctx.Fail(inst, "uses depth comparison with a packed integer image");
return;
}
const auto coord =
CoordF32(ctx, mem, *address, layout.coord, dimension_info.coordinate_components);
if (op == IR::ValueOpcode::ImageGatherRaw) {
const auto coord = CoordF32(ctx, mem, *address, layout.coord,
dimension_info.coordinate_components, image.cube);
if (HasFlag(mem, Decoder::ImageSampleFlagLod)) {
static std::atomic_flag warned = ATOMIC_FLAG_INIT;
if (!warned.test_and_set(std::memory_order_relaxed)) {
@@ -752,6 +752,11 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
operands.push_back(AddressF32(ctx, mem, *address, layout.bias));
}
const auto EmitSample = [&](uint32_t resource) {
const auto& candidate = state.program.info.images[resource];
const auto coord =
CoordF32(ctx, mem, *address, layout.coord,
ImageDimensionInfoFor(candidate.dimension).coordinate_components,
candidate.cube);
const auto sampled = MakeSampledImage(state, resource, mem.sampler);
const auto sample = state.builder.AllocateId();
std::vector<uint32_t> sample_operands {result_type, sample, sampled, coord};
@@ -779,11 +784,11 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
? &state.program.descriptor_sources[image.source]
: nullptr;
if (handle == nullptr || source == nullptr || !source->indirect_image.has_value() ||
source->indirect_image->key_arg >= handle->NumArgs()) {
handle->NumArgs() == 0u) {
ctx.Fail(inst, "has invalid indirect image key provenance");
return;
}
const auto key = ctx.Def(handle->Arg(source->indirect_image->key_arg));
const auto key = ctx.Def(handle->Arg(0));
if (state.flattened_srt_variable == 0 || image.indirect_search_iterations == 0u ||
image.indirect_resources.size() < 2u) {
ctx.Fail(inst, "has no indirect image runtime mapping");
@@ -67,6 +67,7 @@ struct SpirvRequirements {
bool function_scratch = false;
bool pixel_valid_mask = false;
bool buffer_int64_atomics = false;
bool coherent_buffers = false;
};
SpirvRequirements AnalyzeProgramRequirements(const IR::Program& program);
@@ -371,12 +372,13 @@ void EmitMemoryOffsets(EmitterState& state);
uint32_t LdsDwordCount(const EmitterState& state);
struct MemoryResourceAccess {
IR::ResourceKind kind = IR::ResourceKind::None;
uint32_t object_pointer = 0;
uint32_t length = 0;
uint32_t index_offset = 0;
uint32_t byte_offset = 0;
bool add_index_offset = false;
IR::ResourceKind kind = IR::ResourceKind::None;
uint32_t object_pointer = 0;
uint32_t length = 0;
uint32_t index_offset = 0;
uint32_t byte_offset = 0;
bool add_index_offset = false;
spv::MemoryAccessMask memory_access = spv::MemoryAccessMaskNone;
};
MemoryResourceAccess PrepareMemoryResourceAccess(EmitterState& state, const IR::MemoryInfo& mem);
@@ -494,22 +496,18 @@ template <typename Fn>
uint32_t EmitValueOrDefaultIfCondition(EmitterState& state, uint32_t condition, uint32_t type,
uint32_t default_value, Fn&& fn) {
const auto then_label = state.builder.AllocateId();
const auto then_exit = state.builder.AllocateId();
const auto else_label = state.builder.AllocateId();
const auto header = state.current_label;
const auto merge_label = state.builder.AllocateId();
state.builder.AddFunction(spv::OpSelectionMerge, merge_label, spv::SelectionControlMaskNone);
state.builder.AddFunction(spv::OpBranchConditional, condition, then_label, else_label);
state.builder.AddFunction(spv::OpBranchConditional, condition, then_label, merge_label);
EmitLabel(state, then_label);
const auto then_value = fn();
state.builder.AddFunction(spv::OpBranch, then_exit);
EmitLabel(state, then_exit);
state.builder.AddFunction(spv::OpBranch, merge_label);
EmitLabel(state, else_label);
const auto then_exit = state.current_label;
state.builder.AddFunction(spv::OpBranch, merge_label);
EmitLabel(state, merge_label);
const auto value = state.builder.AllocateId();
state.builder.AddFunction(spv::OpPhi, type, value, then_value, then_exit, default_value,
else_label);
header);
return value;
}
@@ -27,61 +27,71 @@ uint32_t EmitDsMaskedLaneRead(EmitterState& state, uint32_t source, uint32_t tar
return Select(state, TypeU32(state), source_active, shuffled, ConstantU32(state, 0));
}
uint32_t BufferByteAddress(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem,
uint32_t index, uint32_t offset, uint32_t soffset) {
auto& state = ctx.state;
const uint32_t packed = StorageBufferPackedStride(state, mem);
const uint32_t stride = packed & 0x3fffu;
const bool swizzle = stride != 0u && ((packed >> 14u) & 1u) != 0u;
if (((packed >> 20u) & 1u) != 0u) {
const auto lane = Binary(state, spv::OpBitwiseAnd, TypeU32(state),
EmitSubgroupLocalInvocationId(state), ConstantU32(state, 63));
index = Binary(state, spv::OpIAdd, TypeU32(state), index, lane);
}
if (mem.offset != 0u) {
offset = Binary(state, spv::OpIAdd, TypeU32(state), offset, ConstantU32(state, mem.offset));
}
struct BufferAddress {
uint32_t offset;
uint32_t byte;
};
uint32_t address = 0;
if (!swizzle) {
if (stride == 0u) {
address = offset;
} else {
const auto indexed = stride == 1u ? index
: Binary(state, spv::OpIMul, TypeU32(state), index,
ConstantU32(state, stride));
address = Binary(state, spv::OpIAdd, TypeU32(state), indexed, offset);
}
} else {
const uint32_t stride_enum = (packed >> 16u) & 3u;
const uint32_t index_stride = 8u << stride_enum;
const auto index_msb = Binary(state, spv::OpShiftRightLogical, TypeU32(state), index,
ConstantU32(state, stride_enum + 3u));
const auto index_lsb = Binary(state, spv::OpBitwiseAnd, TypeU32(state), index,
ConstantU32(state, index_stride - 1u));
const auto offset_msb =
BufferAddress CalculateBufferAddress(EmitterState& state, uint32_t index, uint32_t offset,
uint32_t soffset, uint32_t immediate, uint32_t stride,
uint32_t swizzle, uint32_t index_stride) {
const auto zero = ConstantU32(state, 0);
const auto one = ConstantU32(state, 1);
const auto add = [&](uint32_t lhs, uint32_t rhs) {
return lhs == zero ? rhs : rhs == zero ? lhs
: Binary(state, spv::OpIAdd, TypeU32(state), lhs, rhs);
};
const auto mul = [&](uint32_t lhs, uint32_t rhs) {
return lhs == zero || rhs == zero ? zero
: lhs == one ? rhs
: rhs == one ? lhs
: Binary(state, spv::OpIMul, TypeU32(state), lhs, rhs);
};
if (immediate != 0u) {
offset = add(offset, ConstantU32(state, immediate));
}
auto address = add(mul(index, stride), offset);
if (swizzle != 0u) {
const auto index_shift =
Binary(state, spv::OpIAdd, TypeU32(state), index_stride, ConstantU32(state, 3));
const auto indices = Binary(state, spv::OpShiftLeftLogical, TypeU32(state),
ConstantU32(state, 1), index_shift);
const auto index_msb =
Binary(state, spv::OpShiftRightLogical, TypeU32(state), index, index_shift);
const auto index_lsb =
Binary(state, spv::OpBitwiseAnd, TypeU32(state), index,
Binary(state, spv::OpISub, TypeU32(state), indices, ConstantU32(state, 1)));
const auto offset_msb =
Binary(state, spv::OpBitwiseAnd, TypeU32(state), offset, ConstantU32(state, ~3u));
const auto offset_lsb =
Binary(state, spv::OpBitwiseAnd, TypeU32(state), offset, ConstantU32(state, 3u));
const auto indexed_msb = stride == 1u ? index_msb
: Binary(state, spv::OpIMul, TypeU32(state),
index_msb, ConstantU32(state, stride));
const auto msb = Binary(state, spv::OpIMul, TypeU32(state),
Binary(state, spv::OpIAdd, TypeU32(state), indexed_msb, offset_msb),
ConstantU32(state, index_stride));
const auto lsb = Binary(state, spv::OpIAdd, TypeU32(state),
Binary(state, spv::OpShiftLeftLogical, TypeU32(state), index_lsb,
ConstantU32(state, 2u)),
offset_lsb);
address = Binary(state, spv::OpIAdd, TypeU32(state), msb, lsb);
const auto msb = mul(add(mul(index_msb, stride), offset_msb), indices);
const auto lsb = add(Binary(state, spv::OpShiftLeftLogical, TypeU32(state), index_lsb,
ConstantU32(state, 2u)), offset_lsb);
address = Select(state, TypeU32(state), swizzle, add(msb, lsb), address);
}
return {offset, add(address, soffset)};
}
const auto soffset_value = inst.Arg(3).Resolve();
if (soffset_value.IsImmediate() && soffset_value.GetType() == IR::Type::U32 &&
soffset_value.U32() == 0u) {
return address;
uint32_t BufferLane(EmitterState& state) {
return Binary(state, spv::OpBitwiseAnd, TypeU32(state), EmitSubgroupLocalInvocationId(state),
ConstantU32(state, 63));
}
uint32_t BufferByteAddress(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem) {
auto& state = ctx.state;
const auto packed = StorageBufferPackedStride(state, mem);
const auto stride = packed & 0x3fffu;
auto index = ctx.Arg(inst, 1);
if ((packed & (1u << 20u)) != 0u) {
index = Binary(state, spv::OpIAdd, TypeU32(state), index, BufferLane(state));
}
return Binary(state, spv::OpIAdd, TypeU32(state), address, soffset);
const bool swizzle = stride != 0u && (packed & (1u << 14u)) != 0u;
return CalculateBufferAddress(state, index, ctx.Arg(inst, 2), ctx.Arg(inst, 3), mem.offset,
ConstantU32(state, stride),
swizzle ? ConstantBool(state, true) : 0u,
ConstantU32(state, (packed >> 16u) & 3u))
.byte;
}
uint32_t AddU64Low(EmitterState& state, uint32_t low, uint32_t high, uint32_t add_low,
@@ -196,11 +206,8 @@ uint32_t LoadBdaDword(ValueEmitContext& ctx, uint32_t address) {
});
}
uint32_t LoadBda(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem,
uint32_t bits) {
auto& state = ctx.state;
const auto address = GuestAddress(ctx, inst, mem);
const auto active = ctx.Arg(inst, inst.NumArgs() - 1);
uint32_t LoadBda(ValueEmitContext& ctx, uint32_t address, uint32_t active, uint32_t bits) {
auto& state = ctx.state;
return EmitValueOrZeroIfCondition(state, active, [&]() {
const auto aligned = Binary(state, spv::OpBitwiseAnd, TypeScalarU64(state), address,
ConstantDeviceAddress(state, ~uint64_t {3}));
@@ -236,8 +243,7 @@ uint32_t LoadBda(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryIn
uint32_t ByteAddress(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem) {
if (mem.kind == IR::ResourceKind::Buffer) {
return BufferByteAddress(ctx, inst, mem, ctx.Arg(inst, 1), ctx.Arg(inst, 2),
ctx.Arg(inst, 3));
return BufferByteAddress(ctx, inst, mem);
}
if (mem.kind == IR::ResourceKind::Lds || mem.kind == IR::ResourceKind::Gds) {
if (mem.offset == 0u) {
@@ -307,7 +313,8 @@ uint32_t LoadWordInBounds(ValueEmitContext& ctx, const MemoryResourceAccess& res
uint32_t index) {
const auto value = ctx.state.builder.AllocateId();
const auto pointer = EmitMemoryElementPointer(ctx.state, resource, index);
ctx.state.builder.AddFunction(spv::OpLoad, TypeU32(ctx.state), value, pointer);
ctx.state.builder.AddFunction(spv::OpLoad, TypeU32(ctx.state), value, pointer,
resource.memory_access);
return value;
}
@@ -492,21 +499,21 @@ void StoreSubword(ValueEmitContext& ctx, const IR::Inst& inst, IR::MemoryInfo me
});
}
void StoreWordInBounds(ValueEmitContext& ctx, const MemoryResourceAccess& resource, uint32_t index,
uint32_t data) {
ctx.state.builder.AddFunction(spv::OpStore,
EmitMemoryElementPointer(ctx.state, resource, index), data,
resource.memory_access);
}
void StoreWordPrepared(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem,
const MemoryResourceAccess& resource, uint32_t data) {
const auto index = EmitMemoryElementIndex(ctx.state, resource, DwordIndex(ctx, inst, mem));
EmitIfCondition(ctx.state, EmitMemoryElementInBounds(ctx.state, resource, index), [&]() {
ctx.state.builder.AddFunction(spv::OpStore,
EmitMemoryElementPointer(ctx.state, resource, index), data);
StoreWordInBounds(ctx, resource, index, data);
});
}
void StoreWordInBounds(ValueEmitContext& ctx, const MemoryResourceAccess& resource, uint32_t index,
uint32_t data) {
ctx.state.builder.AddFunction(spv::OpStore,
EmitMemoryElementPointer(ctx.state, resource, index), data);
}
void StoreWord(ValueEmitContext& ctx, const IR::Inst& inst, IR::MemoryInfo mem) {
EmitIfCondition(ctx.state, ctx.Arg(inst, inst.NumArgs() - 1), [&]() {
const auto resource = PrepareMemoryResourceAccess(ctx.state, mem);
@@ -704,10 +711,14 @@ void StoreFormattedInBounds(ValueEmitContext& ctx, const IR::MemoryInfo& mem,
uint32_t data) {
if (component >= plan.info.component_count) return;
const auto bits = plan.info.component_bits[component];
if (plan.info.type == Format::ComponentType::Snorm && bits == 16u) {
if (bits == 16u && (plan.info.type == Format::ComponentType::Snorm ||
plan.info.type == Format::ComponentType::Float)) {
const auto value = EmitBitCastF32U32(ctx.state, data);
data = EmitPackSnorm2x16(
ctx.state, EmitCompositeConstructF32x2(ctx.state, value, ConstantF32Value(ctx.state, 0.0f)));
const auto pair = EmitCompositeConstructF32x2(ctx.state, value,
ConstantF32Value(ctx.state, 0.0f));
data = plan.info.type == Format::ComponentType::Float
? EmitPackHalf2x16(ctx.state, pair)
: EmitPackSnorm2x16(ctx.state, pair);
}
if (bits == 8u || bits == 16u) {
StoreSubwordInBounds(ctx, mem, plan.resource, plan.addresses[component],
@@ -734,12 +745,86 @@ void FormattedStore(ValueEmitContext& ctx, const IR::Inst& inst, const IR::Memor
});
}
uint32_t LoadIndirectBuffer(ValueEmitContext& ctx, const IR::Inst& inst, uint32_t components) {
auto& state = ctx.state;
const auto& handle = *inst.Arg(0).ResolveInstruction();
const auto word1 = ctx.Arg(handle, 1);
const auto records = ctx.Arg(handle, 2);
const auto word3 = ctx.Arg(handle, 3);
const auto field = [&](uint32_t word, uint32_t first, uint32_t count) {
return EmitBitFieldUExtract(state, word, ConstantU32(state, first),
ConstantU32(state, count));
};
const auto nonzero = [&](uint32_t value) {
return Binary(state, spv::OpINotEqual, TypeBool(state), value, ConstantU32(state, 0));
};
const auto has_dword = [&](uint32_t offset, uint32_t size) {
return AndCondition(
state,
Binary(state, spv::OpUGreaterThanEqual, TypeBool(state), size, ConstantU32(state, 4)),
Binary(state, spv::OpULessThanEqual, TypeBool(state), offset,
Binary(state, spv::OpISub, TypeU32(state), size, ConstantU32(state, 4))));
};
const auto stride = field(word1, 16, 14);
const auto swizzle = AndCondition(state, nonzero(stride), nonzero(field(word1, 31, 1)));
const auto index_stride = field(word3, 21, 2);
const auto add_tid = nonzero(field(word3, 23, 1));
const auto index =
Binary(state, spv::OpIAdd, TypeU32(state), ctx.Arg(inst, 1),
Select(state, TypeU32(state), add_tid, BufferLane(state), ConstantU32(state, 0)));
const auto soffset = ctx.Arg(inst, 3);
const auto base = DeviceAddressFromWords(state, ctx.Arg(handle, 0), field(word1, 0, 16));
const auto valid_format = nonzero(field(word3, 12, 7));
const auto mode = field(word3, 28, 2);
const auto index_in_bounds = Binary(state, spv::OpULessThan, TypeBool(state), index, records);
const auto scalar_in_bounds =
Binary(state, spv::OpULessThanEqual, TypeBool(state), soffset, records);
const auto raw_records = Binary(state, spv::OpISub, TypeU32(state), records, soffset);
const auto raw_index_in_bounds =
Binary(state, spv::OpULessThan, TypeBool(state), index, raw_records);
std::array<uint32_t, 4> values {};
for (uint32_t component = 0; component < components; component++) {
const auto address = CalculateBufferAddress(state, index, ctx.Arg(inst, 2), soffset,
ctx.Memory(inst).offset + component * 4u,
stride, swizzle, index_stride);
// RDNA2 raw DWORD vectors check each component, unlike formatted accesses.
const auto structured_bounds =
AndCondition(state, index_in_bounds,
Binary(state, spv::OpULessThan, TypeBool(state), address.offset, stride));
// OOB_SELECT=3 reduces NUM_RECORDS by SOFFSET before the offset/index check.
const auto raw_bounds = AndCondition(
state, scalar_in_bounds,
Select(state, TypeBool(state), swizzle,
AndCondition(state, raw_index_in_bounds, has_dword(address.offset, stride)),
has_dword(address.offset, raw_records)));
auto in_bounds =
Select(state, TypeBool(state),
Binary(state, spv::OpIEqual, TypeBool(state), mode, ConstantU32(state, 0)),
structured_bounds, index_in_bounds);
in_bounds = Select(
state, TypeBool(state),
Binary(state, spv::OpULessThan, TypeBool(state), mode, ConstantU32(state, 2)),
in_bounds,
Select(state, TypeBool(state),
Binary(state, spv::OpIEqual, TypeBool(state), mode, ConstantU32(state, 2)),
nonzero(records), raw_bounds));
const auto guest =
Binary(state, spv::OpIAdd, TypeScalarU64(state), base,
Unary(state, spv::OpUConvert, TypeScalarU64(state), address.byte));
values[component] = LoadBda(ctx, guest, AndCondition(state, valid_format, in_bounds), 32u);
}
return ConstructU32Composite(state, components, values);
}
uint32_t LoadWideBuffer(ValueEmitContext& ctx, const IR::Inst& inst, uint32_t components) {
auto& state = ctx.state;
return EmitValueOrDefaultIfCondition(
state, ctx.Arg(inst, inst.NumArgs() - 1), TypeU32Composite(state, components),
ConstantU32CompositeZero(state, components), [&]() {
const auto mem = ctx.Memory(inst);
if (mem.kind == IR::ResourceKind::IndirectBuffer) {
return LoadIndirectBuffer(ctx, inst, components);
}
const auto resource = PrepareMemoryResourceAccess(state, mem);
const auto info = Format::GetFormatInfo(
mem.formatted ? BufferFormat(ctx, mem) : Prospero::BufferFormat::kInvalid);
@@ -1088,7 +1173,8 @@ void EmitLoadMemory(ValueEmitContext& ctx, const IR::Inst& inst) {
value = LoadWideShared(ctx, inst, shared_components);
else if (address_info.access == IR::AddressAccess::Read &&
mem.kind != IR::ResourceKind::Scratch)
value = LoadBda(ctx, inst, mem, address_info.data_bits);
value = LoadBda(ctx, GuestAddress(ctx, inst, mem), ctx.Arg(inst, inst.NumArgs() - 1),
address_info.data_bits);
else if (op == IR::ValueOpcode::LoadBufferU32 && mem.formatted)
value = FormattedLoad(ctx, inst, mem);
else if (inst.GetType() == IR::Type::U8)
@@ -86,7 +86,9 @@ MemoryResourceAccess PrepareStorageBufferResourceAccess(EmitterState& state,
}
const auto array_index =
ResourceForDescriptor(state, IR::DescriptorBindingKind::Buffers, mem.resource);
MemoryResourceAccess access {.kind = mem.kind};
MemoryResourceAccess access {
.kind = mem.kind,
.memory_access = mem.coherent ? spv::MemoryAccessVolatileMask : spv::MemoryAccessMaskNone};
access.object_pointer = state.builder.AllocateId();
state.builder.AddFunction(spv::OpAccessChain, pointer_type, access.object_pointer, variable,
ConstantU32(state, array_index));
@@ -1,4 +1,4 @@
#include "graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h"
#include "graphics/shader/recompiler/backend/spirv/spirvEmitterInstructions.h"
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
namespace {
@@ -127,10 +127,7 @@ void EmitMeshEntryPoint(EmitterState& state) {
state.builder.AddFunction(spv::OpFunctionCall, TypeVoid(state), state.builder.AllocateId(),
state.mesh_guest_func);
// All guest waves finish before the uniform Vulkan allocation and output stores.
state.builder.AddFunction(spv::OpControlBarrier, ConstantU32(state, spv::ScopeWorkgroup),
ConstantU32(state, spv::ScopeWorkgroup),
ConstantU32(state, spv::MemorySemanticsAcquireReleaseMask |
spv::MemorySemanticsWorkgroupMemoryMask));
EmitBarrier(state);
const auto vertices = MeshLoad(state, state.mesh_allocation, spv::StorageClassWorkgroup,
TypeU32(state), ConstantU32(state, 0));
const auto primitives = MeshLoad(state, state.mesh_allocation, spv::StorageClassWorkgroup,
@@ -201,6 +201,16 @@ void DefineDescriptors(EmitterState& state) {
state.builder.AddAnnotation(spv::OpDecorate, state.storage_buffer_u64_variable,
spv::DecorationAliased);
}
if (state.requirements.coherent_buffers) {
// RDNA2 stores publish to L2 even without GLC; every alias of the buffer
// must participate in visibility for cache-bypassing polling loads.
state.builder.AddAnnotation(spv::OpDecorate, state.storage_buffer_variable,
spv::DecorationCoherent);
if (state.storage_buffer_u64_variable != 0) {
state.builder.AddAnnotation(spv::OpDecorate, state.storage_buffer_u64_variable,
spv::DecorationCoherent);
}
}
break;
case IR::DescriptorBindingKind::BdaPagetable:
state.bda_pagetable_variable = Define(StorageBufferU64Type(state), "bda_pagetable");
@@ -21,6 +21,7 @@ constexpr MemoryOpcodeInfo SMEM_OPCODE_LIST[] = {
{0x04u, Opcode::S_LOAD_DWORDX16, 16, 32}, {0x08u, Opcode::S_BUFFER_LOAD_DWORD, 1, 32},
{0x09u, Opcode::S_BUFFER_LOAD_DWORDX2, 2, 32}, {0x0au, Opcode::S_BUFFER_LOAD_DWORDX4, 4, 32},
{0x0bu, Opcode::S_BUFFER_LOAD_DWORDX8, 8, 32}, {0x0cu, Opcode::S_BUFFER_LOAD_DWORDX16, 16, 32},
{0x25u, Opcode::S_MEMREALTIME, 2, 32},
};
constexpr MemoryOpcodeInfo MUBUF_OPCODE_LIST[] = {
@@ -86,6 +87,7 @@ constexpr MemoryOpcodeInfo FLAT_OPCODE_LIST[] = {
constexpr MemoryOpcodeInfo DS_OPCODE_LIST[] = {
{0x00u, Opcode::DS_ADD_U32, 1, 32}, {0x01u, Opcode::DS_SUB_U32, 1, 32},
{0x03u, Opcode::DS_INC_U32, 1, 32}, {0x04u, Opcode::DS_DEC_U32, 1, 32},
{0x05u, Opcode::DS_MIN_I32, 1, 32}, {0x06u, Opcode::DS_MAX_I32, 1, 32},
{0x07u, Opcode::DS_MIN_U32, 1, 32}, {0x08u, Opcode::DS_MAX_U32, 1, 32},
{0x09u, Opcode::DS_AND_B32, 1, 32}, {0x0au, Opcode::DS_OR_B32, 1, 32},
@@ -107,6 +109,7 @@ constexpr MemoryOpcodeInfo DS_OPCODE_LIST[] = {
{0x4du, Opcode::DS_WRITE_B64, 2, 32}, {0x4eu, Opcode::DS_WRITE2_B64, 4, 32},
{0x4fu, Opcode::DS_WRITE2ST64_B64, 4, 32}, {0x76u, Opcode::DS_READ_B64, 2, 32},
{0x77u, Opcode::DS_READ2_B64, 4, 32}, {0x78u, Opcode::DS_READ2ST64_B64, 4, 32},
{0xa0u, Opcode::DS_WRITE_B8_D16_HI, 1, 8},
{0xa1u, Opcode::DS_WRITE_B16_D16_HI, 1, 16},
{0xa6u, Opcode::DS_READ_U16_D16, 1, 16},
{0xa7u, Opcode::DS_READ_U16_D16_HI, 1, 16},
@@ -147,6 +150,7 @@ bool IsDsWriteOpcode(Opcode opcode) {
switch (opcode) {
case Opcode::DS_WRITE_B8:
case Opcode::DS_WRITE_B16:
case Opcode::DS_WRITE_B8_D16_HI:
case Opcode::DS_WRITE_B16_D16_HI:
case Opcode::DS_WRITE2_B32:
case Opcode::DS_WRITE2ST64_B32:
@@ -166,7 +170,9 @@ bool IsDsAtomicOpcode(Opcode opcode) {
case Opcode::DS_ADD_RTN_U32:
case Opcode::DS_SUB_U32:
case Opcode::DS_SUB_RTN_U32:
case Opcode::DS_INC_U32:
case Opcode::DS_INC_RTN_U32:
case Opcode::DS_DEC_U32:
case Opcode::DS_DEC_RTN_U32:
case Opcode::DS_MIN_I32:
case Opcode::DS_MIN_RTN_I32:
@@ -240,6 +246,10 @@ void DecodeSmem(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
}
DecodeScalarDestination(sdst, pc, inst.dst);
if (inst.opcode == Opcode::S_MEMREALTIME) {
inst.src_count = 0;
return;
}
// SMEM encodes SBASE in SGPR pairs. Scalar-buffer loads still use the same
// pair index; their descriptor operand consumes four SGPRs from that base.
DecodeScalarSource(sbase * 2u, pc, inst.src0);
@@ -263,6 +273,7 @@ void DecodeMubuf(uint32_t pc, std::span<const uint32_t> code, uint32_t word_inde
inst.idxen = ((word0 >> 13u) & 1u) != 0;
inst.offen = ((word0 >> 12u) & 1u) != 0;
inst.glc = ((word0 >> 14u) & 1u) != 0;
inst.dlc = ((word0 >> 15u) & 1u) != 0;
inst.slc = ((word1 >> 22u) & 1u) != 0;
inst.family = Family::MUBUF;
inst.opcode_id = opcode;
@@ -298,6 +309,7 @@ void DecodeMtbuf(uint32_t pc, std::span<const uint32_t> code, uint32_t word_inde
inst.idxen = ((word0 >> 13u) & 1u) != 0;
inst.offen = ((word0 >> 12u) & 1u) != 0;
inst.glc = ((word0 >> 14u) & 1u) != 0;
inst.dlc = ((word0 >> 15u) & 1u) != 0;
inst.slc = ((word1 >> 22u) & 1u) != 0;
inst.family = Family::MTBUF;
inst.opcode_id = opcode;
@@ -429,7 +441,9 @@ void DecodeDs(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index,
}
DecodeVectorGpr(addr, inst.src0);
DecodeVectorGpr(data0, inst.src1);
if (inst.opcode == Opcode::DS_WRITE_B16_D16_HI) {
if (inst.opcode == Opcode::DS_WRITE_B8_D16_HI) {
inst.src1.sdwa_sel = 2u;
} else if (inst.opcode == Opcode::DS_WRITE_B16_D16_HI) {
inst.src1.sdwa_sel = 5u;
}
DecodeVectorGpr(data1, inst.src2);
@@ -74,7 +74,8 @@ constexpr OpcodeMap SOPC_OPCODE_LIST[] = {
{0x03u, Opcode::S_CMP_GE_I32}, {0x04u, Opcode::S_CMP_LT_I32}, {0x05u, Opcode::S_CMP_LE_I32},
{0x06u, Opcode::S_CMP_EQ_U32}, {0x07u, Opcode::S_CMP_LG_U32}, {0x08u, Opcode::S_CMP_GT_U32},
{0x09u, Opcode::S_CMP_GE_U32}, {0x0au, Opcode::S_CMP_LT_U32}, {0x0bu, Opcode::S_CMP_LE_U32},
{0x0cu, Opcode::S_BITCMP0_B32}, {0x0du, Opcode::S_BITCMP1_B32}, {0x12u, Opcode::S_CMP_EQ_U64},
{0x0cu, Opcode::S_BITCMP0_B32}, {0x0du, Opcode::S_BITCMP1_B32},
{0x0eu, Opcode::S_BITCMP0_B64}, {0x0fu, Opcode::S_BITCMP1_B64}, {0x12u, Opcode::S_CMP_EQ_U64},
{0x13u, Opcode::S_CMP_LG_U64},
};
@@ -73,11 +73,11 @@ std::string FormatMemory(const Instruction& inst) {
text += OperandToString(*sources[i]);
}
text += fmt::format(" ; offset={} offset2={} dwords={} bits={} dfmt={} nfmt={} signed={} "
"typed={} formatted={} segment={} glc={} slc={} idxen={} offen={}",
"typed={} formatted={} segment={} glc={} dlc={} slc={} idxen={} offen={}",
inst.offset, inst.secondary_offset, inst.data_dwords, inst.data_bits,
inst.data_format, inst.number_format, inst.data_signed ? 1u : 0u,
inst.typed ? 1u : 0u, inst.formatted ? 1u : 0u, inst.memory_segment,
inst.glc ? 1u : 0u, inst.slc ? 1u : 0u, inst.idxen ? 1u : 0u,
inst.glc ? 1u : 0u, inst.dlc ? 1u : 0u, inst.slc ? 1u : 0u, inst.idxen ? 1u : 0u,
inst.offen ? 1u : 0u);
return text;
}
@@ -403,6 +403,7 @@ void DecodeProgram(std::span<const uint32_t> code, Program& program) {
program.instructions.clear();
program.instructions.reserve(code.size());
program.code = code;
program.has_bvh = false;
std::vector<bool> branch_targets;
for (uint32_t word_index = 0; word_index < code.size();) {
@@ -411,6 +412,10 @@ void DecodeProgram(std::span<const uint32_t> code, Program& program) {
const auto& inst = program.instructions.back();
word_index += inst.word_count;
if (inst.family == Family::MIMG && (inst.opcode_id == 0xe6u || inst.opcode_id == 0xe7u)) {
program.has_bvh = true;
return;
}
if (IsDirectBranch(inst.opcode)) {
const auto target_index = inst.branch_target / sizeof(uint32_t);
@@ -659,7 +664,9 @@ std::string InstructionToString(const Instruction& inst) {
case Opcode::DS_ADD_RTN_U32:
case Opcode::DS_SUB_U32:
case Opcode::DS_SUB_RTN_U32:
case Opcode::DS_INC_U32:
case Opcode::DS_INC_RTN_U32:
case Opcode::DS_DEC_U32:
case Opcode::DS_DEC_RTN_U32:
case Opcode::DS_MIN_I32:
case Opcode::DS_MIN_RTN_I32:
@@ -694,6 +701,7 @@ std::string InstructionToString(const Instruction& inst) {
case Opcode::DS_READ_B128:
case Opcode::DS_WRITE_B8:
case Opcode::DS_WRITE_B16:
case Opcode::DS_WRITE_B8_D16_HI:
case Opcode::DS_WRITE_B16_D16_HI:
case Opcode::DS_WRITE2_B32:
case Opcode::DS_WRITE2ST64_B32:
@@ -73,6 +73,8 @@ enum class Opcode {
S_SUB_I32,
S_BITCMP0_B32,
S_BITCMP1_B32,
S_BITCMP0_B64,
S_BITCMP1_B64,
S_BITSET0_B32,
S_BITSET1_B32,
S_BITSET0_B64,
@@ -437,6 +439,7 @@ enum class Opcode {
S_BUFFER_LOAD_DWORDX4,
S_BUFFER_LOAD_DWORDX8,
S_BUFFER_LOAD_DWORDX16,
S_MEMREALTIME,
BUFFER_LOAD_FORMAT_X,
BUFFER_LOAD_FORMAT_XY,
BUFFER_LOAD_FORMAT_XYZ,
@@ -500,7 +503,9 @@ enum class Opcode {
DS_ADD_RTN_U32,
DS_SUB_U32,
DS_SUB_RTN_U32,
DS_INC_U32,
DS_INC_RTN_U32,
DS_DEC_U32,
DS_DEC_RTN_U32,
DS_MIN_I32,
DS_MIN_RTN_I32,
@@ -539,6 +544,7 @@ enum class Opcode {
DS_READ_B128,
DS_WRITE_B8,
DS_WRITE_B16,
DS_WRITE_B8_D16_HI,
DS_WRITE_B16_D16_HI,
DS_WRITE2_B32,
DS_WRITE2ST64_B32,
@@ -706,6 +712,7 @@ struct Instruction {
bool formatted = false;
bool gds = false;
bool glc = false;
bool dlc = false;
bool slc = false;
bool idxen = false;
bool offen = false;
@@ -724,6 +731,7 @@ struct Instruction {
struct Program {
std::span<const uint32_t> code;
std::vector<Instruction> instructions;
bool has_bvh = false;
};
// Code spans are trusted to contain complete instructions, valid branch targets, and 32-bit PCs.
@@ -40,7 +40,7 @@ constexpr Vop2OpcodeInfo VOP2_OPCODE_LIST[] = {
{0x0fu, Opcode::V_MIN_F32},
{0x10u, Opcode::V_MAX_F32},
{0x11u, Opcode::V_MIN_I32},
{0x12u, Opcode::V_MAX_I32},
{0x12u, Opcode::V_MAX_I32, Vop2SdwaProfile::IntegerFullDestination},
{0x13u, Opcode::V_MIN_U32, Vop2SdwaProfile::IntegerPartialDestination},
{0x14u, Opcode::V_MAX_U32, Vop2SdwaProfile::IntegerFullDestination},
{0x15u, Opcode::V_LSHR_B32},
@@ -521,13 +521,14 @@ constexpr Vop1SdwaRule VOP1_SDWA_RULES[] = {
{Opcode::V_CVT_F32_U32, SdwaSelBytes() | SdwaSelWords() | SdwaSelFull(), 0, 0, false},
{Opcode::V_CVT_F32_I32, SdwaSelBytes() | SdwaSelWords() | SdwaSelFull(), 0, 0, false},
{Opcode::V_CVT_F32_UBYTE0, SdwaSelBytes() | SdwaSelWords() | SdwaSelFull(), 0, 0, false},
{Opcode::V_NOT_B32, SdwaSelBytes() | SdwaSelWords() | SdwaSelFull(), 0, 0, false},
{Opcode::V_NOT_B32, SdwaSelBytes() | SdwaSelWords() | SdwaSelFull(), SdwaSelBytes() | SdwaSelWords(),
SdwaSelFull(), false},
{Opcode::V_FFBL_B32, SdwaSelBytes() | SdwaSelWords() | SdwaSelFull(), 0, 0, false},
{Opcode::V_CVT_F32_F16, SdwaSelWords() | SdwaSelFull(), 0, 0, true},
{Opcode::V_CVT_F16_F32, SdwaSelBytes() | SdwaSelWords() | SdwaSelFull(), SdwaSelWords(),
SdwaSelBytes() | SdwaSelWords() | SdwaSelFull(), true},
{Opcode::V_CVT_F16_U16, SdwaSelWords() | SdwaSelFull(), SdwaSelWords(),
SdwaSelWords() | SdwaSelFull(), false},
{Opcode::V_CVT_F16_U16, SdwaSelBytes() | SdwaSelWords() | SdwaSelFull(), SdwaSelWords(),
SdwaSelBytes() | SdwaSelWords() | SdwaSelFull(), false},
{Opcode::V_CVT_U16_F16, SdwaSelWords() | SdwaSelFull(), SdwaSelWords(),
SdwaSelWords() | SdwaSelFull(), true},
{Opcode::V_CVT_F16_I16, SdwaSelWords() | SdwaSelFull(), SdwaSelWords(),
@@ -617,13 +618,14 @@ bool SupportsVop1Clamp(Opcode opcode) {
}
bool ValidateVop1Sdwa(Instruction& inst, uint32_t opcode, uint32_t modifier) {
const auto dst_sel = (modifier >> 8u) & 0x7u;
const auto dst_u = (modifier >> 11u) & 0x3u;
const auto clamp = (modifier >> 13u) & 0x1u;
const auto omod = (modifier >> 14u) & 0x3u;
const auto src0_sel = (modifier >> 16u) & 0x7u;
const auto src0_neg = (modifier >> 20u) & 0x1u;
const auto src0_abs = (modifier >> 21u) & 0x1u;
const auto dst_sel = (modifier >> 8u) & 0x7u;
const auto dst_u = (modifier >> 11u) & 0x3u;
const auto clamp = (modifier >> 13u) & 0x1u;
const auto omod = (modifier >> 14u) & 0x3u;
const auto src0_sel = (modifier >> 16u) & 0x7u;
const auto src0_sext = (modifier >> 19u) & 0x1u;
const auto src0_neg = (modifier >> 20u) & 0x1u;
const auto src0_abs = (modifier >> 21u) & 0x1u;
if (src0_sel > 6u || dst_sel > 6u) {
SetUnsupported(inst, Family::VOP1, opcode, "VOP1 SDWA selector is invalid");
@@ -639,7 +641,8 @@ bool ValidateVop1Sdwa(Instruction& inst, uint32_t opcode, uint32_t modifier) {
"VOP1 SDWA destination selector is not supported");
return false;
}
if (!IsVop1SdwaSourceSupported(inst.opcode, src0_sel, src0_neg != 0u, src0_abs != 0u)) {
if (!IsVop1SdwaSourceSupported(inst.opcode, src0_sel, src0_neg != 0u, src0_abs != 0u) ||
(inst.opcode == Opcode::V_CVT_F16_U16 && src0_sel <= 3u && src0_sext != 0u)) {
SetUnsupported(inst, Family::VOP1, opcode, "VOP1 SDWA source selector is not supported");
return false;
}
@@ -302,23 +302,95 @@ void Translator::S_MOV_B64(const Decoder::Instruction& inst) {
}
void Translator::S_WQM(const Decoder::Instruction& inst, bool wide) {
const auto source = wide ? ReadU64(inst.src0)
: ir.ConstructU64(ReadU32(inst.src0), IR::U32(IR::Value(0u)));
const bool source_is_mask = inst.src0.kind == Decoder::OperandKind::Sgpr ||
inst.src0.kind == Decoder::OperandKind::ExecLo ||
inst.src0.kind == Decoder::OperandKind::VccLo ||
(!wide && IsExecOrVcc(inst.src0));
const auto source_valid = ReadMaskValid(inst.src0);
auto source_live = IR::U1(IR::Value(false));
if (source_is_mask) {
if (inst.src0.kind == Decoder::OperandKind::VccLo ||
inst.src0.kind == Decoder::OperandKind::VccHi) {
source_live = ir.GetVcc();
} else {
source_live = ReadMask(inst.src0);
}
}
const auto retained_live = ir.LogicalAnd(source_valid, source_live);
IR::U64 source;
if (wide) {
source = ReadU64(inst.src0);
} else {
source = ir.ConstructU64(ReadU32(inst.src0), IR::U32(IR::Value(0u)));
}
const auto result = IR::U64(ir.Emit(IR::ValueOpcode::WqmU64, {source}));
if (!wide) {
const auto low = ir.CompositeExtract(result, 0);
const bool destination_is_mask = IsExecOrVcc(inst.dst);
const bool destination_is_exec = inst.dst.kind == Decoder::OperandKind::ExecLo ||
inst.dst.kind == Decoder::OperandKind::ExecHi;
auto old_live = IR::U1(IR::Value(false));
if (destination_is_mask) {
if (destination_is_exec) {
old_live = ir.GetExec();
} else {
old_live = ir.GetVcc();
}
}
// A 32-bit write preserves the other EXEC/VCC half and invalidates any
// overlapping SGPR-pair mask provenance through the ordinary scalar path.
WriteRawU32(inst.dst, low);
if (destination_is_mask) {
const auto is_high_half = [](const Decoder::Operand& operand) {
return operand.kind == Decoder::OperandKind::ExecHi ||
operand.kind == Decoder::OperandKind::VccHi;
};
const bool destination_is_high = is_high_half(inst.dst);
auto affected_lanes = IR::U1(IR::Value(!destination_is_high));
if (program.wave_size == 64u) {
const auto lane = IR::U32(ir.Emit(IR::ValueOpcode::LaneId));
affected_lanes = ir.ULessThan(lane, IR::U32(IR::Value(32u)));
if (destination_is_high) {
affected_lanes = ir.LogicalNot(affected_lanes);
}
}
IR::U1 live;
if (destination_is_exec) {
live = ir.GetExec();
} else {
live = ir.GetVcc();
}
// A cross-half copy refers to different lanes, so only scalar bits apply.
if (is_high_half(inst.src0) == destination_is_high) {
live = ir.LogicalOr(retained_live, live);
}
live = IR::U1(ir.Emit(IR::ValueOpcode::SelectU1, {affected_lanes, live, old_live}));
if (destination_is_exec) {
ir.SetExec(live);
} else {
ir.SetVcc(live);
}
}
ir.SetScc(ir.INotEqual(low, IR::U32(IR::Value(0u))));
return;
}
const auto mask_valid = ReadMaskValid(inst.src0);
// WQM adds live lanes but never removes a source lane. Keep that fact separate
// from the scalar mask: reconstructing an entry-true predicate from a ballot
// otherwise introduces a spurious dependency into every vector write.
const auto live = ir.LogicalOr(retained_live, ThreadBit(ExtractU64(result)));
WriteOperand(DestinationOperand(inst), result);
if (inst.dst.kind == Decoder::OperandKind::Sgpr) {
if (inst.dst.kind == Decoder::OperandKind::ExecLo) {
ir.SetExec(live);
} else if (inst.dst.kind == Decoder::OperandKind::VccLo) {
ir.SetVcc(live);
} else if (inst.dst.kind == Decoder::OperandKind::Sgpr) {
const auto dst = static_cast<IR::ScalarReg>(inst.dst.reg);
ir.SetThreadBitScalarReg(dst, ThreadBit(ExtractU64(result)));
ir.SetScalarMaskTag(dst, mask_valid);
ir.SetThreadBitScalarReg(dst, live);
ir.SetScalarMaskTag(dst, source_valid);
}
ir.SetScc(IR::U1(ir.Emit(IR::ValueOpcode::INotEqual64, {result, IR::Value(uint64_t {0})})));
}
@@ -507,6 +507,18 @@ bool Translator::S_BITCMP_B32(const Decoder::Instruction& inst, bool expected) {
return true;
}
bool Translator::S_BITCMP_B64(const Decoder::Instruction& inst, bool expected) {
const auto value = ReadU32Pair(inst.src0);
const auto offset = ir.BitwiseAnd(ReadU32(inst.src1), IR::U32(IR::Value(63u)));
const auto word_bit = ir.BitwiseAnd(offset, IR::U32(IR::Value(31u)));
const auto word =
ir.Select(ir.ULessThan(offset, IR::U32(IR::Value(32u))), value[0], value[1]);
const auto bit =
IR::U32(ir.Emit(IR::ValueOpcode::BitFieldUExtract, {word, word_bit, IR::Value(1u)}));
WriteCompareResult(inst.dst, ir.IEqual(bit, IR::U32(IR::Value(expected ? 1u : 0u))));
return true;
}
bool Translator::V_ALIGNBIT_B32(const Decoder::Instruction& inst) {
const auto hi = ReadU32(inst.src0);
const auto lo = ReadU32(inst.src1);
@@ -1,8 +1,10 @@
#include "common/logging/log.h"
#include "graphics/shader/recompiler/frontend/translate/Translator.h"
#include "graphics/shader/recompiler/frontend/decode/ImageOps.h"
#include <algorithm>
#include <array>
#include <atomic>
namespace Libs::Graphics::ShaderRecompiler::Frontend {
@@ -123,6 +125,11 @@ IR::MemoryInfo MemoryInfoFromDecoded(const Decoder::Instruction& decoded) {
memory.image_r128 = decoded.image_r128;
memory.idxen = decoded.idxen;
memory.offen = decoded.offen;
// Vector loads use GLC/DLC to bypass L0/GL1; atomics use GLC only to return data.
const bool buffer_atomic = decoded.opcode >= Decoder::Opcode::BUFFER_ATOMIC_SWAP &&
decoded.opcode <= Decoder::Opcode::BUFFER_ATOMIC_FMAX;
memory.coherent = memory.kind == ResourceKind::Buffer && !buffer_atomic &&
(decoded.glc || decoded.dlc);
memory.resource = ResourceIndexFromOperand(decoded.src1);
memory.sampler = ResourceIndexFromOperand(decoded.src2);
if (memory.kind == ResourceKind::ScalarBuffer) {
@@ -235,6 +242,7 @@ Decoder::Operand MemorySourceAt(const Decoder::Instruction& decoded, uint32_t in
return index == 0u ? decoded.src1 : index == 1u ? decoded.src0 : decoded.src2;
case Decoder::Opcode::DS_WRITE_B8:
case Decoder::Opcode::DS_WRITE_B16:
case Decoder::Opcode::DS_WRITE_B8_D16_HI:
case Decoder::Opcode::DS_WRITE_B16_D16_HI:
case Decoder::Opcode::DS_WRITE_B32:
case Decoder::Opcode::DS_WRITE_B64:
@@ -890,6 +898,17 @@ bool Translator::EmitMemory(const Decoder::Instruction& inst) {
case Decoder::Opcode::S_BUFFER_LOAD_DWORDX4:
case Decoder::Opcode::S_BUFFER_LOAD_DWORDX8:
case Decoder::Opcode::S_BUFFER_LOAD_DWORDX16: return S_LOAD(inst, false);
case Decoder::Opcode::S_MEMREALTIME: {
static std::atomic_flag warned = ATOMIC_FLAG_INIT;
if (!warned.test_and_set(std::memory_order_relaxed)) {
Log::WriteToConsoleAndLog(
"Warning: S_MEMREALTIME uses placeholder UINT64_MAX; real-time clock not implemented.\n");
}
for (uint32_t component = 0; component < 2; component++) {
WriteOperand(ScalarDestinationOperand(inst.dst, component), IR::Value(UINT32_MAX));
}
return true;
}
case Decoder::Opcode::BUFFER_LOAD_UBYTE:
case Decoder::Opcode::BUFFER_LOAD_SBYTE:
@@ -962,8 +981,12 @@ bool Translator::EmitMemory(const Decoder::Instruction& inst) {
return DS_ATOMIC(inst, IR::ValueOpcode::SharedAtomicISub32, false);
case Decoder::Opcode::DS_SUB_RTN_U32:
return DS_ATOMIC(inst, IR::ValueOpcode::SharedAtomicISub32, true);
case Decoder::Opcode::DS_INC_U32:
return DS_ATOMIC(inst, IR::ValueOpcode::SharedAtomicInc32, false);
case Decoder::Opcode::DS_INC_RTN_U32:
return DS_ATOMIC(inst, IR::ValueOpcode::SharedAtomicInc32, true);
case Decoder::Opcode::DS_DEC_U32:
return DS_ATOMIC(inst, IR::ValueOpcode::SharedAtomicDec32, false);
case Decoder::Opcode::DS_DEC_RTN_U32:
return DS_ATOMIC(inst, IR::ValueOpcode::SharedAtomicDec32, true);
case Decoder::Opcode::DS_MIN_I32:
@@ -1076,6 +1099,7 @@ bool Translator::EmitMemory(const Decoder::Instruction& inst) {
case Decoder::Opcode::DS_WRITE2ST64_B64: return DS_WRITE2(inst);
case Decoder::Opcode::DS_WRITE_B8:
case Decoder::Opcode::DS_WRITE_B16:
case Decoder::Opcode::DS_WRITE_B8_D16_HI:
case Decoder::Opcode::DS_WRITE_B16_D16_HI:
case Decoder::Opcode::DS_WRITE_B32:
case Decoder::Opcode::DS_WRITE_B64:
@@ -208,6 +208,8 @@ bool Translator::EmitScalar(const Decoder::Instruction& inst) {
case O::S_BFE_U64: return S_BFE_U64(inst);
case O::S_BITCMP0_B32: return S_BITCMP_B32(inst, false);
case O::S_BITCMP1_B32: return S_BITCMP_B32(inst, true);
case O::S_BITCMP0_B64: return S_BITCMP_B64(inst, false);
case O::S_BITCMP1_B64: return S_BITCMP_B64(inst, true);
case O::S_PACK_LL_B32_B16: return PackB16(inst, false, false);
case O::S_PACK_LH_B32_B16: return PackB16(inst, false, true);
case O::S_PACK_HH_B32_B16: return PackB16(inst, true, true);
@@ -1063,8 +1063,8 @@ IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph&
}
} else if (options.stage == ShaderType::Mesh) {
const auto& mesh = options.input_info.vertex->mesh;
EXIT_NOT_IMPLEMENTED(options.wave_size != 64u || mesh.primitives_per_group == 0u ||
mesh.vertices_per_group > 64u || total_threads > 15u * 64u);
EXIT_NOT_IMPLEMENTED(mesh.primitives_per_group == 0u || mesh.vertices_per_group > 64u ||
total_threads > 15u * options.wave_size);
const auto u32 = [](uint32_t value) { return IR::U32(IR::Value(value)); };
const auto draw = [&](uint32_t index) {
return IR::U32(
@@ -1089,12 +1089,15 @@ IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph&
entry_ir.IAdd(IR::U32(entry_ir.Emit(IR::ValueOpcode::UDiv32,
{subtract_saturate(vertices, size), step})),
u32(1)));
const auto wave = entry_ir.ShiftRightLogical(local, u32(6));
const auto wave_base = entry_ir.BitwiseAnd(local, u32(~63u));
const auto vertex_count = minimum(subtract_saturate(vertices, wave_base), u32(64));
const auto primitive_count = minimum(subtract_saturate(primitives, wave_base), u32(64));
const auto wave = entry_ir.ShiftRightLogical(local, u32(options.wave_size == 32u ? 5u : 6u));
const auto wave_base = entry_ir.BitwiseAnd(local, u32(~(options.wave_size - 1u)));
const auto vertex_count =
minimum(subtract_saturate(vertices, wave_base), u32(options.wave_size));
const auto primitive_count =
minimum(subtract_saturate(primitives, wave_base), u32(options.wave_size));
const auto wave_info = entry_ir.BitwiseOr(entry_ir.ShiftLeftLogical(wave, u32(24)),
u32(((total_threads + 63u) / 64u) << 28u));
u32(((total_threads + options.wave_size - 1u) /
options.wave_size) << 28u));
entry_ir.SetScalarReg(
static_cast<IR::ScalarReg>(3),
entry_ir.BitwiseOr(wave_info, entry_ir.BitwiseOr(entry_ir.ShiftLeftLogical(
@@ -1225,6 +1228,12 @@ IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph&
builtin(IR::StageInputKind::PackedAncillary));
}
} else if (options.stage == ShaderType::Vertex) {
// Vulkan owns primitive assembly; each vertex subgroup is one NGG wave.
// Keep its full lane extent: mbcnt(-1) uses lane ordinals, not active counts.
entry_ir.SetScalarReg(static_cast<IR::ScalarReg>(2),
IR::U32(IR::Value(options.wave_size << 12u)));
entry_ir.SetScalarReg(static_cast<IR::ScalarReg>(3),
IR::U32(IR::Value((1u << 28u) | options.wave_size)));
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(5),
builtin(IR::StageInputKind::VertexIndex));
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(8),
@@ -205,6 +205,7 @@ private:
bool V_BFE_U32(const Decoder::Instruction& inst, bool sign);
bool V_BFI_B32(const Decoder::Instruction& inst);
bool S_BITCMP_B32(const Decoder::Instruction& inst, bool expected);
bool S_BITCMP_B64(const Decoder::Instruction& inst, bool expected);
bool V_ALIGNBIT_B32(const Decoder::Instruction& inst);
bool V_ALIGNBYTE_B32(const Decoder::Instruction& inst);
bool V_LSHL_ADD_U32(const Decoder::Instruction& inst);
@@ -447,13 +447,18 @@ void ValidateProgram(const Program& program, bool require_ssa) {
ValueOpcodeName(inst.GetOpcode())));
}
const auto& memory = program.memory_info[memory_index];
if (memory.kind != ResourceKind::Buffer &&
memory.kind != ResourceKind::ScalarBuffer) {
const bool vector_buffer = memory.kind == ResourceKind::Buffer ||
memory.kind == ResourceKind::IndirectBuffer;
if (!vector_buffer && memory.kind != ResourceKind::ScalarBuffer) {
return Fail(fmt::format("{} has a non-buffer resource kind",
ValueOpcodeName(inst.GetOpcode())));
}
if (memory.kind == ResourceKind::IndirectBuffer &&
!memory.SupportsIndirectBufferLoad(inst.GetOpcode())) {
return Fail("indirect buffer requires a raw DWORD x2/x3/x4 load");
}
if (buffer_components > 1u &&
(memory.kind != ResourceKind::Buffer || memory.data_bits != 32u ||
(!vector_buffer || memory.data_bits != 32u ||
memory.data_dwords != buffer_components || memory.component_index != 0u)) {
return Fail(fmt::format("{} has inconsistent native-wide metadata",
ValueOpcodeName(inst.GetOpcode())));
+36 -6
View File
@@ -14,6 +14,7 @@
#include <array>
#include <bit>
#include <deque>
#include <list>
#include <memory>
#include <optional>
@@ -27,6 +28,7 @@ enum class ResourceKind {
ScalarBuffer,
ScalarAddress,
Buffer,
IndirectBuffer,
Flat,
Global,
Scratch,
@@ -65,8 +67,15 @@ struct MemoryInfo {
bool image_r128 = false;
bool idxen = false;
bool offen = false;
bool coherent = false;
bool planning_only = false;
[[nodiscard]] bool SupportsIndirectBufferLoad(ValueOpcode opcode) const {
return !formatted && !typed && data_bits == 32u &&
(opcode == ValueOpcode::LoadBufferU32x2 || opcode == ValueOpcode::LoadBufferU32x3 ||
opcode == ValueOpcode::LoadBufferU32x4);
}
bool operator==(const MemoryInfo& other) const = default;
};
@@ -457,11 +466,13 @@ struct BlockInfo {
struct DescriptorSource {
struct IndirectImage {
uint32_t material_source = 0;
uint32_t heap_source = 0;
uint32_t material_source = UINT32_MAX;
uint32_t table_source = 0;
uint32_t selector_stride = 0;
uint32_t selector_offset = 0;
uint32_t key_arg = 0;
uint32_t table_offset = 0;
Value key_count;
Value selector_mask;
bool operator==(const IndirectImage& other) const = default;
};
@@ -505,9 +516,19 @@ struct UniformFillPlan {
std::array<Value, 4> values;
};
// Immutable runtime resource analysis retained by the shader cache. It owns descriptor/SRT,
// uniform condition and fill values without retaining translated blocks.
// Resource analysis retained by the shader cache. It owns immutable descriptor/SRT,
// condition and fill values without translated blocks, plus reusable evaluation scratch.
struct ResourcePlan {
struct EvaluationContext {
struct Entry {
uint64_t value = 0;
uint64_t generation = 0;
};
std::vector<Entry> values;
uint64_t generation = 0;
};
ResourcePlan() = default;
~ResourcePlan();
@@ -524,14 +545,23 @@ struct ResourcePlan {
std::vector<MemoryInfo> memory_info;
std::vector<DescriptorSource> descriptor_sources;
std::vector<ResourceBlock> control_flow;
std::vector<uint32_t> materialization_sources;
std::vector<SrtRead> srt_reads;
std::vector<uint8_t> clean_flat_slots;
bool requires_specialization_memory = false;
bool has_address_writes = false;
bool srt_plan_complete = false;
bool resource_tracking_complete = false;
ShaderInfo info;
UniformFillPlan uniform_fill;
// GPU-thread scratch for nested clean/EXEC memos, activity and material keys.
mutable std::deque<EvaluationContext> evaluation_contexts;
mutable uint32_t evaluation_value_count = 0;
mutable uint32_t evaluation_depth = 0;
mutable std::vector<uint8_t> active_sources;
mutable std::vector<uint8_t> visited_blocks;
mutable std::vector<uint32_t> pending_blocks;
mutable std::vector<uint32_t> material_keys;
mutable std::vector<std::pair<uint64_t, uint64_t>> specialization_reads;
};
struct Program: ResourcePlan {
@@ -121,6 +121,13 @@ public:
[[nodiscard]] Block* PhiBlock(size_t index) const;
[[nodiscard]] Block* Parent() const;
[[nodiscard]] const std::vector<Use>& Uses() const;
// Runtime indices belong to the resource plan that owns this instruction.
[[nodiscard]] uint32_t EvaluationIndex(uint32_t& count) const {
if (evaluation_index == UINT32_MAX) {
evaluation_index = count++;
}
return evaluation_index;
}
void SetParent(Block* block);
void SetArg(size_t index, Value value);
@@ -155,6 +162,7 @@ private:
std::vector<Value> args;
std::vector<Block*> phi_blocks;
std::vector<Use> uses;
mutable uint32_t evaluation_index = UINT32_MAX;
};
} // namespace Libs::Graphics::ShaderRecompiler::IR
@@ -200,6 +200,13 @@ void FoldInstruction(Block& block, Block::iterator instruction,
switch (inst.GetOpcode()) {
case ValueOpcode::Phi: FoldPhi(inst); return;
case ValueOpcode::SelectU1:
if (!FoldSelect(inst) && IsImmediate(Arg(inst, 2), Type::U1) &&
!Arg(inst, 2).U1()) {
auto result = block.PrependNewInst(instruction, ValueOpcode::LogicalAnd,
{Arg(inst, 0), Arg(inst, 1)});
Replace(inst, Value(&*result));
}
return;
case ValueOpcode::SelectU32:
case ValueOpcode::SelectF32: FoldSelect(inst); return;
case ValueOpcode::BitFieldInsert: {
@@ -431,6 +438,10 @@ void FoldInstruction(Block& block, Block::iterator instruction,
return;
case ValueOpcode::ShiftRightLogical32:
if (!FoldU32(inst, [](uint32_t a, uint32_t b) { return a >> (b & 31u); })) {
if (IsImmediate(Arg(inst, 0), Type::U32) && Arg(inst, 0).U32() == 0u) {
Replace(inst, Value(0u));
return;
}
const auto shift = Arg(inst, 1);
if (IsImmediate(shift, Type::U32) && (shift.U32() & 31u) == 0u) {
Replace(inst, Arg(inst, 0));
@@ -573,6 +584,23 @@ void FoldInstruction(Block& block, Block::iterator instruction,
if (!FoldLogical(inst, [](bool a, bool b) { return a && b; })) {
const auto lhs = Arg(inst, 0);
const auto rhs = Arg(inst, 1);
const auto simplify = [&](Value assumption, Value expression) {
const auto* disjunction = expression.TryInstruction();
if (disjunction == nullptr || disjunction->GetOpcode() != ValueOpcode::LogicalOr) {
return false;
}
for (uint32_t i = 0; i < 2u; ++i) {
const auto* inverse = disjunction->Arg(i).Resolve().TryInstruction();
if (inverse != nullptr && inverse->GetOpcode() == ValueOpcode::LogicalNot &&
inverse->Arg(0).Resolve() == assumption) {
inst.SetArg(assumption == lhs ? 1u : 0u,
disjunction->Arg(i ^ 1u));
return true;
}
}
return false;
};
if (simplify(lhs, rhs) || simplify(rhs, lhs)) return;
if (IsImmediate(lhs, Type::U1)) {
Replace(inst, lhs.U1() ? rhs : lhs);
} else if (IsImmediate(rhs, Type::U1)) {
@@ -21,18 +21,6 @@ namespace {
constexpr uint64_t AddressMask = 0x0000ffffffffffffull;
constexpr uint64_t MaxIndirectImageProbes = 65536u;
struct IndirectImage {
uint32_t resource = 0;
std::vector<uint32_t> keys;
std::vector<uint32_t> candidates;
std::vector<DescriptorValue> descriptors;
};
struct MaterializedSnapshot {
ResourceSnapshot resources;
std::vector<IndirectImage> indirect_images;
};
bool SpecializationFail(std::string_view message) {
std::fprintf(stderr, "shader resource specialization failed: %.*s\n",
static_cast<int>(message.size()), message.data());
@@ -74,6 +62,13 @@ bool NullImageDescriptor(const DescriptorValue& descriptor) {
}
bool ValidImageDescriptor(const DescriptorValue& descriptor, bool r128 = false) {
const auto& words = descriptor.dwords;
// Reject texture descriptors with nonzero reserved bits.
if ((words[1] & 0x20000000u) != 0u || (words[2] & 0x70003000u) != 0u ||
(!r128 && ((words[4] & 0xe000e000u) != 0u || (words[5] & 0xf9000000u) != 0u ||
(words[6] & 0x00007b00u) != 0u))) {
return false;
}
const auto type = static_cast<Prospero::ImageType>((descriptor.dwords[3] >> 28u) & 0xfu);
const auto format = static_cast<Prospero::BufferFormat>((descriptor.dwords[1] >> 20u) & 0x1ffu);
if (type < Prospero::ImageType::kColor1D || format == Prospero::BufferFormat::kInvalid ||
@@ -84,6 +79,13 @@ bool ValidImageDescriptor(const DescriptorValue& descriptor, bool r128 = false)
type != Prospero::ImageType::kColor2DMsaa) {
return false;
}
const bool array = type == Prospero::ImageType::kColor1DArray ||
type == Prospero::ImageType::kColor2DArray ||
type == Prospero::ImageType::kColor2DMsaaArray ||
type == Prospero::ImageType::kCube;
if (array && ((words[4] >> 16u) & 0x1fffu) > (words[4] & 0x1fffu)) {
return false;
}
if (type == Prospero::ImageType::kColor2DMsaa ||
type == Prospero::ImageType::kColor2DMsaaArray) {
const auto base_level = (descriptor.dwords[3] >> 12u) & 0xfu;
@@ -136,6 +138,50 @@ bool DecodeBufferDescriptor(const DescriptorValue& descriptor, ShaderBufferResou
return true;
}
struct ReadCapture {
SrtRuntime source;
std::vector<std::pair<uint64_t, uint64_t>>& ranges;
};
bool CaptureStrictRead(void* userdata, uint64_t address, std::span<uint32_t> values) {
auto& capture = *static_cast<ReadCapture*>(userdata);
if (!capture.source.read_specialization_memory(capture.source.userdata, address, values)) {
return false;
}
capture.ranges.emplace_back(address, values.size_bytes());
return true;
}
bool CaptureOrdinaryRead(void* userdata, uint64_t address, std::span<uint32_t> values) {
auto& capture = *static_cast<ReadCapture*>(userdata);
if (!capture.source.read_memory(capture.source.userdata, address, values)) {
return false;
}
capture.ranges.emplace_back(address, values.size_bytes());
return true;
}
bool WrittenBuffersDisjoint(const ResourcePlan& program, const ResourceSnapshot& snapshot,
std::span<const std::pair<uint64_t, uint64_t>> reads) {
for (uint32_t i = 0; i < program.info.buffers.size(); ++i) {
if (!program.info.buffers[i].written) continue;
ShaderBufferResource buffer;
if (!DecodeBufferDescriptor(snapshot.buffers[i], buffer)) return false;
const auto base = buffer.Base48();
const auto size = buffer.GetSize();
if (size == 0u) continue;
if (size - 1u > AddressMask - base) return false;
for (const auto [address, bytes]: reads) {
if (bytes == 0u) continue;
if (address > AddressMask || bytes - 1u > AddressMask - address ||
(address <= base ? base - address < bytes : address - base < size)) {
return false;
}
}
}
return true;
}
const DescriptorSource* Source(const ResourcePlan& program, uint32_t source) {
if (source >= program.descriptor_sources.size()) {
return nullptr;
@@ -144,12 +190,15 @@ const DescriptorSource* Source(const ResourcePlan& program, uint32_t source) {
}
void MarkCleanFlatSlots(const ResourcePlan& program, const DescriptorSource* source,
std::span<uint8_t> slots) {
if (source == nullptr) {
std::span<uint8_t> slots, Value extra = {}) {
if (source == nullptr && extra.IsEmpty()) {
return;
}
std::vector<Value> pending(source->dwords.begin(),
source->dwords.begin() + source->dword_count);
std::vector<Value> pending;
if (source != nullptr) {
pending.assign(source->dwords.begin(), source->dwords.begin() + source->dword_count);
}
if (!extra.IsEmpty()) pending.push_back(extra);
std::vector<const Inst*> visited;
while (!pending.empty()) {
auto value = pending.back().Resolve();
@@ -173,200 +222,185 @@ void MarkCleanFlatSlots(const ResourcePlan& program, const DescriptorSource* sou
}
}
bool ReadSpecializationWord(const SrtRuntime& runtime, uint64_t address, uint32_t& word) {
return runtime.read_specialization_memory != nullptr &&
runtime.read_specialization_memory(runtime.userdata, address, &word);
}
bool ReadScalarBufferWord(const ShaderBufferResource& descriptor, uint32_t dynamic_offset,
uint32_t immediate_offset, const SrtRuntime& runtime, uint32_t& word) {
const auto byte_offset = static_cast<uint64_t>(dynamic_offset) + immediate_offset;
const auto aligned = byte_offset & ~uint64_t {3};
const auto size = descriptor.GetSize();
if (aligned > size || size - aligned < sizeof(uint32_t)) {
word = 0;
bool ReadScalarTable(uint64_t base, uint64_t size, uint32_t dynamic_offset,
const SrtRuntime& runtime, std::span<uint32_t> words) {
const auto offset = static_cast<uint64_t>(dynamic_offset) & ~uint64_t {3};
const auto count = std::min<uint64_t>(words.size(), offset < size ? (size - offset) / 4u : 0u);
std::ranges::fill(words.subspan(count), 0u);
if (count == 0u) {
return true;
}
const auto base = descriptor.Base48() & ~uint64_t {3};
if (aligned > AddressMask - base) {
base &= AddressMask & ~uint64_t {3};
if (offset > AddressMask - base) {
return false;
}
const auto address = base + aligned;
if (!ReadSpecializationWord(runtime, address, word)) {
return false;
}
return true;
const auto address = base + offset;
const auto prefix = words.first(count);
return prefix.size_bytes() - 1u <= AddressMask - address &&
runtime.read_specialization_memory != nullptr &&
runtime.read_specialization_memory(runtime.userdata, address, prefix);
}
bool MaterializeIndirectImage(const DescriptorSource::IndirectImage& indirect,
const DescriptorValue& material_value,
const DescriptorValue& heap_value, bool r128,
const SrtRuntime& runtime, IndirectImage& result) {
ShaderBufferResource material;
ShaderBufferResource heap;
if (!DecodeBufferDescriptor(material_value, material) ||
!DecodeBufferDescriptor(heap_value, heap)) {
bool MaterializeIndirectImage(const ResourcePlan& program,
const DescriptorSource::IndirectImage& indirect,
const DescriptorValue& material_value,
const DescriptorValue& table_value, uint32_t image_index,
const SrtRuntime& runtime, SrtWalker& clean,
ResourceSnapshot& snapshot,
ResourceSpecialization& specialization) {
uint64_t table_base = 0;
uint64_t table_size = UINT64_MAX; // Scalar addresses have no buffer descriptor bounds.
ShaderBufferResource table;
if (table_value.dword_count == 2u) {
table_base = (static_cast<uint64_t>(table_value.dwords[1]) << 32u) | table_value.dwords[0];
} else if (DecodeBufferDescriptor(table_value, table)) {
table_base = table.Base48();
table_size = table.GetSize();
} else {
return false;
}
if (material.Stride() != indirect.selector_stride) {
return false;
}
// S_BUFFER_LOAD ignores vector-buffer swizzle/add-thread fields. The shader computes the
// record stride explicitly; enumerate every wrapped 32-bit offset that can pass bounds.
const auto period = uint64_t {1} << 32u;
const auto step = std::gcd<uint64_t>(indirect.selector_stride, period);
const auto residue = static_cast<uint64_t>(indirect.selector_offset) % step;
const auto size = material.GetSize();
const auto limit = std::min<uint64_t>(UINT32_MAX, size + 3u);
const auto probe_count = residue <= limit ? (limit - residue) / step + 1u : 0u;
if (probe_count > MaxIndirectImageProbes) {
return false;
}
std::vector<uint32_t> keys {0u};
std::unordered_set<uint32_t> seen {0u};
keys.reserve(static_cast<size_t>(probe_count) + 1u);
seen.reserve(static_cast<size_t>(probe_count) + 1u);
for (uint64_t offset = residue; offset <= limit && probe_count != 0u; offset += step) {
uint32_t key = 0;
if (!ReadScalarBufferWord(material, static_cast<uint32_t>(offset), 0u, runtime, key)) {
auto& keys = program.material_keys;
keys.clear();
if (indirect.material_source == UINT32_MAX) {
uint32_t key_count = 0;
const bool evaluated = clean.Evaluate(indirect.key_count, key_count);
if (std::bit_cast<int32_t>(key_count) <= 0) key_count = 0;
if (table_value.dword_count != 2u || !evaluated ||
key_count > MaxIndirectImageProbes ||
uint64_t {indirect.table_offset} + uint64_t {key_count} * 32u > UINT32_MAX + 1ull) {
return false;
}
if (seen.insert(key).second) {
keys.resize(key_count);
std::iota(keys.begin(), keys.end(), 0u);
} else if (!indirect.selector_mask.IsEmpty()) {
uint32_t mask = 0;
uint32_t count = 0;
if (material_value.dword_count != 2u || table_value.dword_count != 2u ||
!clean.Evaluate(indirect.selector_mask, mask) ||
!clean.Evaluate(indirect.key_count, count) || count == 0u || count > 32u) {
return false;
}
if (count < 32u) mask &= (1u << count) - 1u;
const auto material_base =
(static_cast<uint64_t>(material_value.dwords[1]) << 32u) | material_value.dwords[0];
keys.reserve(std::popcount(mask));
while (mask != 0u) {
const auto index = std::countr_zero(mask);
const auto offset = static_cast<uint64_t>(indirect.selector_offset) +
static_cast<uint64_t>(index) * indirect.selector_stride;
if (offset > UINT32_MAX) return false;
uint32_t key = 0;
if (!ReadScalarTable(material_base, UINT64_MAX, static_cast<uint32_t>(offset),
runtime, {&key, 1})) return false;
keys.push_back(key);
mask &= mask - 1u;
}
if (limit - offset < step) {
break;
std::ranges::sort(keys);
keys.erase(std::unique(keys.begin(), keys.end()), keys.end());
} else {
ShaderBufferResource material;
if (!DecodeBufferDescriptor(material_value, material) || table_value.dword_count != 4u ||
material.Stride() != indirect.selector_stride) {
return false;
}
// Enumerate every wrapped scalar-buffer offset that can pass the descriptor bounds.
const auto step = std::gcd<uint64_t>(indirect.selector_stride, uint64_t {1} << 32u);
const auto residue = static_cast<uint64_t>(indirect.selector_offset) % step;
const auto limit = std::min<uint64_t>(UINT32_MAX, material.GetSize() + 3u);
const auto probe_count = residue <= limit ? (limit - residue) / step + 1u : 0u;
if (probe_count > MaxIndirectImageProbes) {
return false;
}
keys.reserve(static_cast<size_t>(probe_count) + 1u);
keys.push_back(0u);
for (uint64_t offset = residue; offset <= limit && probe_count != 0u; offset += step) {
uint32_t key = 0;
if (!ReadScalarTable(material.Base48(), material.GetSize(),
static_cast<uint32_t>(offset), runtime, {&key, 1})) {
return false;
}
keys.push_back(key);
if (limit - offset < step) {
break;
}
}
std::ranges::sort(keys);
keys.erase(std::unique(keys.begin(), keys.end()), keys.end());
}
IndirectImage next;
next.keys = std::move(keys);
next.candidates.reserve(next.keys.size());
next.descriptors.reserve(
std::min(next.keys.size(), static_cast<size_t>(ShaderInfo::MaxImages)));
for (const auto key: next.keys) {
const auto children_begin = snapshot.images.size();
const auto mapping_offset = snapshot.flattened_srt.size();
const auto root_image = specialization.images[image_index];
snapshot.flattened_srt.resize(mapping_offset + 1u + keys.size() * 2u);
snapshot.flattened_srt[mapping_offset] = static_cast<uint32_t>(keys.size());
for (uint32_t entry = 0; entry < keys.size(); ++entry) {
const auto key = keys[entry];
DescriptorValue candidate;
candidate.dword_count = 8u;
const auto heap_offset = key << 5u;
for (uint32_t dword = 0; dword < candidate.dword_count; dword++) {
if (!ReadScalarBufferWord(heap, heap_offset, dword * sizeof(uint32_t), runtime,
candidate.dwords[dword])) {
return false;
}
candidate.dword_count = 8u;
const auto table_offset = (key << 5u) + indirect.table_offset;
if (!ReadScalarTable(table_base, table_size, table_offset, runtime, candidate.dwords)) {
return false;
}
if (NullImageDescriptor(candidate) || !ValidImageDescriptor(candidate, r128)) {
if (NullImageDescriptor(candidate) ||
!ValidImageDescriptor(candidate, program.info.images[image_index].r128)) {
candidate.dwords.fill(0);
}
const auto found = std::ranges::find(next.descriptors, candidate);
if (found == next.descriptors.end()) {
if (next.descriptors.size() >= ShaderInfo::MaxImages) {
return false;
uint32_t ordinal = 0;
if (entry == 0) {
snapshot.images[image_index] = candidate;
} else if (snapshot.images[image_index] != candidate) {
const auto found = std::find(snapshot.images.begin() + children_begin,
snapshot.images.end(), candidate);
ordinal = static_cast<uint32_t>(found - snapshot.images.begin() - children_begin + 1u);
if (found == snapshot.images.end()) {
if (snapshot.images.size() >= ShaderInfo::MaxImages) {
return false;
}
snapshot.images.push_back(candidate);
auto child = root_image;
child.indirect_root = image_index;
specialization.images.push_back(child);
}
next.descriptors.push_back(candidate);
next.candidates.push_back(static_cast<uint32_t>(next.descriptors.size() - 1u));
} else {
next.candidates.push_back(static_cast<uint32_t>(found - next.descriptors.begin()));
}
snapshot.flattened_srt[mapping_offset + 1u + entry * 2u] = key;
snapshot.flattened_srt[mapping_offset + 2u + entry * 2u] = ordinal;
}
if (snapshot.images.size() == children_begin) {
snapshot.flattened_srt.resize(mapping_offset);
} else {
auto& root = specialization.images[image_index];
root.indirect_root = image_index;
root.indirect_mapping_offset = static_cast<uint32_t>(mapping_offset);
root.indirect_search_iterations = std::bit_width(keys.size());
}
result = std::move(next);
return true;
}
} // namespace
static bool MaterializeSnapshot(const ResourcePlan& program, const SrtRuntime& runtime,
MaterializedSnapshot& snapshot) {
if (!program.resource_tracking_complete) {
return false;
}
if (program.requires_specialization_memory && runtime.read_specialization_memory == nullptr) {
return false;
}
std::vector<DescriptorValue> values;
std::vector<uint32_t> flattened_srt;
std::vector<uint8_t> active_sources;
if (!EvaluateRuntimeSources(program, program.materialization_sources, runtime, values,
flattened_srt, program.clean_flat_slots, active_sources)) {
return false;
}
auto& next = snapshot.resources;
const auto& fill = program.uniform_fill;
const auto words = fill.fill.words;
std::array<uint32_t, 4> stored {};
if (words != 0 &&
EvaluateUniformValues(program, std::span(fill.values).first(words), runtime,
std::span(stored).first(words)) &&
std::all_of(stored.begin(), stored.begin() + words,
[&](uint32_t value) { return value == stored[0]; })) {
next.uniform_fill = fill.fill;
next.uniform_fill.value = stored[0];
}
auto cursor = values.begin();
next.buffers.assign(cursor, cursor + program.info.buffers.size());
cursor += program.info.buffers.size();
next.flattened_srt = std::move(flattened_srt);
next.images.resize(program.info.images.size());
for (uint32_t image_index = 0; image_index < program.info.images.size(); image_index++) {
const auto& image = program.info.images[image_index];
const auto* source = Source(program, image.source);
if (source != nullptr && source->indirect_image.has_value()) {
if (!active_sources[image.source]) {
next.images[image_index].dword_count = 8u;
continue;
}
const std::array requests {source->indirect_image->material_source,
source->indirect_image->heap_source};
SrtRuntime clean_runtime = runtime;
clean_runtime.read_memory = runtime.read_specialization_memory;
std::vector<DescriptorValue> tables;
if (!EvaluateDescriptorSources(program, requests, clean_runtime, tables)) {
return false;
}
const auto& material = tables[0];
const auto& heap = tables[1];
IndirectImage table;
if (!MaterializeIndirectImage(*source->indirect_image, material, heap, image.r128,
runtime, table)) {
return false;
}
next.images[image_index] = table.descriptors[table.candidates[0]];
if (table.descriptors.size() > 1u) {
table.resource = image_index;
snapshot.indirect_images.push_back(std::move(table));
}
} else {
auto descriptor = *cursor++;
if (!ValidImageDescriptor(descriptor, image.r128)) {
descriptor.dwords.fill(0);
}
next.images[image_index] = descriptor;
}
}
next.samplers.assign(cursor, cursor + program.info.samplers.size());
next.user_data.assign(runtime.user_data.begin(), runtime.user_data.end());
return true;
}
struct SamplerPlan {
std::array<uint32_t, ShaderInfo::MaxSamplers> point_sampler {};
uint32_t sampler_count = 0;
};
struct ImageRemap {
explicit ImageRemap(const ResourceSpecialization& specialization) {
for (const auto& image: specialization.images) {
indices.push_back(image.fmask ? UINT32_MAX : count++);
explicit ImageRemap(const ResourceSpecialization& specialization)
: source_count(static_cast<uint32_t>(specialization.images.size())) {
EXIT_IF(specialization.images.size() > indices.size());
for (uint32_t index = 0; index < source_count; index++) {
indices[index] = specialization.images[index].fmask ? UINT32_MAX : count++;
}
}
uint32_t operator[](uint32_t index) const {
EXIT_IF(index >= source_count);
return indices[index];
}
template <typename T>
void Apply(std::vector<T>& images) const {
EXIT_IF(images.size() != indices.size());
for (uint32_t index = 0; index < indices.size(); index++) {
EXIT_IF(images.size() != source_count);
for (uint32_t index = 0; index < source_count; index++) {
if (indices[index] != UINT32_MAX && indices[index] != index) {
images[indices[index]] = std::move(images[index]);
}
@@ -374,75 +408,21 @@ struct ImageRemap {
images.resize(count);
}
std::vector<uint32_t> indices;
uint32_t count = 0;
private:
std::array<uint32_t, ShaderInfo::MaxImages> indices;
uint32_t source_count;
uint32_t count = 0;
};
template <typename Images>
bool BuildSamplerPlan(const ShaderInfo& base, const Images& images, SamplerPlan& plan);
static bool BuildResourceSpecialization(const ResourcePlan& program, MaterializedSnapshot snapshot,
ResourceSnapshot& specialized_snapshot,
static bool BuildResourceSpecialization(const ResourcePlan& program, ResourceSnapshot& snapshot,
ResourceSpecialization& specialization) {
auto next_snapshot = std::move(snapshot.resources);
ResourceSpecialization next_specialization;
next_specialization.buffers.reserve(program.info.buffers.size());
size_t image_count = program.info.images.size();
size_t mapping_words = 0;
for (const auto& table: snapshot.indirect_images) {
if (table.resource >= program.info.images.size() || table.descriptors.size() < 2u ||
image_count + table.descriptors.size() - 1u > ShaderInfo::MaxImages) {
return SpecializationFail(
"indirect image candidates exceed the dense image resource limit");
}
image_count += table.descriptors.size() - 1u;
mapping_words += 1u + table.keys.size() * 2u;
}
next_snapshot.images.reserve(image_count);
next_snapshot.flattened_srt.reserve(next_snapshot.flattened_srt.size() + mapping_words);
next_specialization.images.reserve(image_count);
for (const auto& image: program.info.images) {
next_specialization.images.push_back({
.numeric_class = image.numeric_class,
.dimension = image.dimension,
.mip_count = image.mip_count,
.conversion_format = image.conversion_format,
.shader_swizzle = image.shader_swizzle,
.indirect_root = image.indirect_root,
.indirect_mapping_offset = image.indirect_mapping_offset,
.indirect_search_iterations = image.indirect_search_iterations,
.cube = image.cube,
});
}
for (const auto& table: snapshot.indirect_images) {
const auto root_image = next_specialization.images[table.resource];
for (uint32_t candidate = 1; candidate < table.descriptors.size(); candidate++) {
auto image = root_image;
image.indirect_root = table.resource;
next_specialization.images.push_back(image);
next_snapshot.images.push_back(table.descriptors[candidate]);
}
auto& root = next_specialization.images[table.resource];
root.indirect_root = table.resource;
root.indirect_mapping_offset = static_cast<uint32_t>(next_snapshot.flattened_srt.size());
root.indirect_search_iterations = std::bit_width(table.keys.size());
next_snapshot.flattened_srt.resize(next_snapshot.flattened_srt.size() + 1u +
table.keys.size() * 2u);
std::vector<uint32_t> order(table.keys.size());
std::iota(order.begin(), order.end(), 0u);
std::ranges::sort(order, {}, [&](uint32_t index) { return table.keys[index]; });
next_snapshot.flattened_srt[root.indirect_mapping_offset] =
static_cast<uint32_t>(table.keys.size());
for (uint32_t entry = 0; entry < order.size(); entry++) {
const auto source = order[entry];
const auto offset = root.indirect_mapping_offset + 1u + entry * 2u;
next_snapshot.flattened_srt[offset] = table.keys[source];
next_snapshot.flattened_srt[offset + 1] = table.candidates[source];
}
next_snapshot.images[table.resource] = table.descriptors[0];
}
specialization.buffers.clear();
specialization.buffers.reserve(program.info.buffers.size());
for (uint32_t i = 0; i < program.info.buffers.size(); i++) {
auto& descriptor_value = next_snapshot.buffers[i];
auto& descriptor_value = snapshot.buffers[i];
ShaderBufferResource descriptor;
if (!DecodeBufferDescriptor(descriptor_value, descriptor)) {
return SpecializationFail(fmt::format("buffer descriptor {} has invalid width", i));
@@ -459,7 +439,7 @@ static bool BuildResourceSpecialization(const ResourcePlan& program, Materialize
} else if (!swizzle) {
packed_stride &= ~(3u << 16u);
}
next_specialization.buffers.push_back({
specialization.buffers.push_back({
.packed_stride = packed_stride,
.descriptor_format = program.info.buffers[i].formatted
? descriptor.Format()
@@ -468,9 +448,9 @@ static bool BuildResourceSpecialization(const ResourcePlan& program, Materialize
program.info.buffers[i].formatted ? descriptor.DstSelXYZW() : DstSel(4, 5, 6, 7),
});
}
for (uint32_t i = 0; i < next_specialization.images.size(); i++) {
const auto& descriptor = next_snapshot.images[i];
auto& image = next_specialization.images[i];
for (uint32_t i = 0; i < specialization.images.size(); i++) {
const auto& descriptor = snapshot.images[i];
auto& image = specialization.images[i];
const auto base_index = i < program.info.images.size() ? i : image.indirect_root;
if (base_index >= program.info.images.size()) {
return SpecializationFail(fmt::format("image resource {} has an invalid root", i));
@@ -545,42 +525,46 @@ static bool BuildResourceSpecialization(const ResourcePlan& program, Materialize
static_cast<uint32_t>(format)));
}
}
for (uint32_t root_index = 0; root_index < next_specialization.images.size(); root_index++) {
auto& root = next_specialization.images[root_index];
for (uint32_t root_index = 0; root_index < specialization.images.size(); root_index++) {
auto& root = specialization.images[root_index];
if (root.indirect_root != root_index) {
continue;
}
const auto key_count = root.indirect_mapping_offset < next_snapshot.flattened_srt.size()
? next_snapshot.flattened_srt[root.indirect_mapping_offset]
const auto key_count = root.indirect_mapping_offset < snapshot.flattened_srt.size()
? snapshot.flattened_srt[root.indirect_mapping_offset]
: 0u;
if (root.indirect_search_iterations == 0u || key_count < 2u ||
static_cast<size_t>(root.indirect_mapping_offset) + 1u +
static_cast<size_t>(key_count) * 2u >
next_snapshot.flattened_srt.size()) {
snapshot.flattened_srt.size()) {
return SpecializationFail("indirect image specialization has an invalid key mapping");
}
uint32_t exemplar = ImageResource::NoIndirectImage;
uint32_t resource_count = 0;
for (uint32_t resource = 0; resource < next_specialization.images.size(); resource++) {
if (next_specialization.images[resource].indirect_root != root_index) {
for (uint32_t resource = 0; resource < specialization.images.size(); resource++) {
if (specialization.images[resource].indirect_root != root_index) {
continue;
}
resource_count++;
if (exemplar == ImageResource::NoIndirectImage &&
!NullImageDescriptor(next_snapshot.images[resource])) {
!NullImageDescriptor(snapshot.images[resource])) {
exemplar = resource;
}
}
if (resource_count < 2u || exemplar == ImageResource::NoIndirectImage) {
return SpecializationFail("indirect image specialization has no typed candidate");
}
const auto& image_class = next_specialization.images[exemplar];
for (uint32_t candidate = 0; candidate < next_specialization.images.size(); candidate++) {
auto& image = next_specialization.images[candidate];
const auto& image_class = specialization.images[exemplar];
const auto is_2d = [](Decoder::ImageDimension dimension) {
return dimension == Decoder::ImageDimension::Dim2D ||
dimension == Decoder::ImageDimension::Dim2DArray;
};
for (uint32_t candidate = 0; candidate < specialization.images.size(); candidate++) {
auto& image = specialization.images[candidate];
if (image.indirect_root != root_index) {
continue;
}
if (NullImageDescriptor(next_snapshot.images[candidate])) {
if (NullImageDescriptor(snapshot.images[candidate])) {
image.numeric_class = image_class.numeric_class;
image.dimension = image_class.dimension;
image.mip_count = image_class.mip_count;
@@ -588,12 +572,13 @@ static bool BuildResourceSpecialization(const ResourcePlan& program, Materialize
image.shader_swizzle = image_class.shader_swizzle;
image.cube = image_class.cube;
}
const bool same_coordinates = image.dimension == image_class.dimension &&
image.cube == image_class.cube;
if (image.numeric_class != image_class.numeric_class ||
image.dimension != image_class.dimension ||
(!same_coordinates && !(is_2d(image.dimension) && is_2d(image_class.dimension))) ||
image.mip_count != image_class.mip_count ||
image.conversion_format != image_class.conversion_format ||
image.shader_swizzle != image_class.shader_swizzle ||
image.cube != image_class.cube) {
image.shader_swizzle != image_class.shader_swizzle) {
return SpecializationFail(
fmt::format("indirect image table at pc 0x{:08x} has incompatible candidates",
program.info.images[root_index].first_use_pc));
@@ -601,18 +586,16 @@ static bool BuildResourceSpecialization(const ResourcePlan& program, Materialize
}
}
SamplerPlan sampler_plan;
if (!BuildSamplerPlan(program.info, next_specialization.images, sampler_plan)) {
if (!BuildSamplerPlan(program.info, specialization.images, sampler_plan)) {
return SpecializationFail("specialized sampler layout exceeds its resource limit");
}
for (uint32_t index = 0; index < program.info.samplers.size(); index++) {
const auto target = sampler_plan.point_sampler[index];
if (target != UINT32_MAX && target >= program.info.samplers.size()) {
next_snapshot.samplers.push_back(next_snapshot.samplers[index]);
snapshot.samplers.push_back(snapshot.samplers[index]);
}
}
ImageRemap(next_specialization).Apply(next_snapshot.images);
specialization = std::move(next_specialization);
specialized_snapshot = std::move(next_snapshot);
ImageRemap(specialization).Apply(snapshot.images);
return true;
}
@@ -873,6 +856,7 @@ ResourcePlan ExtractResourcePlan(const Program& program) {
plan.memory_info = program.memory_info;
plan.srt_plan_complete = program.srt_plan_complete;
plan.resource_tracking_complete = program.resource_tracking_complete;
plan.has_address_writes = program.has_address_writes;
std::unordered_map<const Inst*, Inst*> cloned;
std::function<Value(Value)> Clone = [&](Value value) -> Value {
@@ -910,6 +894,10 @@ ResourcePlan ExtractResourcePlan(const Program& program) {
auto& target = plan.descriptor_sources.emplace_back();
target.dword_count = source.dword_count;
target.indirect_image = source.indirect_image;
if (target.indirect_image.has_value()) {
target.indirect_image->key_count = Clone(target.indirect_image->key_count);
target.indirect_image->selector_mask = Clone(target.indirect_image->selector_mask);
}
for (uint32_t dword = 0; dword < source.dword_count; dword++) {
target.dwords[dword] = Clone(source.dwords[dword]);
}
@@ -926,31 +914,16 @@ ResourcePlan ExtractResourcePlan(const Program& program) {
for (uint32_t i = 0; i < plan.uniform_fill.fill.words; ++i) {
plan.uniform_fill.values[i] = Clone(plan.uniform_fill.values[i]);
}
plan.materialization_sources.reserve(plan.info.buffers.size() + plan.info.images.size() +
plan.info.samplers.size());
for (const auto& buffer: plan.info.buffers) {
plan.materialization_sources.push_back(buffer.source);
}
for (const auto& image: plan.info.images) {
const auto* source = Source(plan, image.source);
if (source != nullptr && source->indirect_image.has_value()) {
plan.requires_specialization_memory = true;
} else {
plan.materialization_sources.push_back(image.source);
}
}
for (const auto& sampler: plan.info.samplers) {
plan.materialization_sources.push_back(sampler.source);
}
plan.clean_flat_slots.resize(plan.srt_reads.size());
for (const auto& image: plan.info.images) {
const auto* source = Source(plan, image.source);
if (source == nullptr || !source->indirect_image.has_value()) {
continue;
}
plan.requires_specialization_memory = true;
MarkCleanFlatSlots(plan, Source(plan, source->indirect_image->material_source),
plan.clean_flat_slots);
MarkCleanFlatSlots(plan, Source(plan, source->indirect_image->heap_source),
plan.clean_flat_slots, source->indirect_image->selector_mask);
MarkCleanFlatSlots(plan, Source(plan, source->indirect_image->table_source),
plan.clean_flat_slots);
}
return plan;
@@ -958,11 +931,130 @@ ResourcePlan ExtractResourcePlan(const Program& program) {
bool MaterializeResources(const ResourcePlan& program, const SrtRuntime& runtime,
ResourceSnapshot& snapshot, ResourceSpecialization& specialization) {
MaterializedSnapshot materialized;
if (!MaterializeSnapshot(program, runtime, materialized)) {
if (!program.resource_tracking_complete ||
(program.requires_specialization_memory && runtime.read_specialization_memory == nullptr)) {
return false;
}
return BuildResourceSpecialization(program, std::move(materialized), snapshot, specialization);
const bool masked_image = std::ranges::any_of(program.info.images, [&](const auto& image) {
const auto* source = Source(program, image.source);
return source != nullptr && source->indirect_image.has_value() &&
!source->indirect_image->selector_mask.IsEmpty();
});
if (masked_image &&
(program.has_address_writes ||
std::ranges::any_of(program.info.images, &ImageResource::written))) {
return false;
}
const bool capture_reads = masked_image &&
std::ranges::any_of(program.info.buffers, &BufferResource::written);
auto& reads = program.specialization_reads;
ReadCapture capture {runtime, reads};
SrtRuntime observed = runtime;
if (capture_reads) {
reads.clear();
observed.userdata = &capture;
observed.read_specialization_memory = CaptureStrictRead;
if (observed.read_memory != nullptr) observed.read_memory = CaptureOrdinaryRead;
}
SrtWalker clean(program, CleanRuntime(observed));
SrtWalker walker(program, observed, program.clean_flat_slots, &clean);
const auto active = clean.FindActiveSources();
if (!walker.RefreshFlatBuffer(snapshot.flattened_srt)) {
return false;
}
snapshot.uniform_fill = {};
const auto& fill = program.uniform_fill;
const auto words = fill.fill.words;
std::array<uint32_t, 4> stored {};
bool uniform_fill = words != 0;
for (uint32_t i = 0; i < words && uniform_fill; ++i) {
uniform_fill = clean.Evaluate(fill.values[i], stored[i]) && stored[i] == stored[0];
}
if (uniform_fill) {
snapshot.uniform_fill = fill.fill;
snapshot.uniform_fill.value = stored[0];
}
const auto evaluate = [&](uint32_t source, DescriptorValue& value) {
if (source >= program.descriptor_sources.size()) {
return false;
}
if (active.empty() || active[source]) {
return walker.EvaluateDescriptor(source, value);
}
value = {};
value.dword_count = program.descriptor_sources[source].dword_count;
return true;
};
snapshot.buffers.resize(program.info.buffers.size());
for (uint32_t i = 0; i < program.info.buffers.size(); ++i) {
if (!evaluate(program.info.buffers[i].source, snapshot.buffers[i])) {
return false;
}
}
if (capture_reads) {
for (uint32_t i = 0; i < program.info.buffers.size(); ++i) {
const auto& buffer = program.info.buffers[i];
if (!buffer.written || (!active.empty() && !active[buffer.source])) continue;
DescriptorValue strict;
if (!clean.EvaluateDescriptor(buffer.source, strict) ||
strict != snapshot.buffers[i]) return false;
}
}
snapshot.images.resize(program.info.images.size());
specialization.images.clear();
specialization.images.reserve(program.info.images.size());
for (const auto& image: program.info.images) {
specialization.images.push_back({
.numeric_class = image.numeric_class,
.dimension = image.dimension,
.mip_count = image.mip_count,
.conversion_format = image.conversion_format,
.shader_swizzle = image.shader_swizzle,
.indirect_root = image.indirect_root,
.indirect_mapping_offset = image.indirect_mapping_offset,
.indirect_search_iterations = image.indirect_search_iterations,
.cube = image.cube,
});
}
for (uint32_t i = 0; i < program.info.images.size(); ++i) {
const auto& image = program.info.images[i];
const auto* source = Source(program, image.source);
if (source == nullptr) {
return false;
}
if (source->indirect_image.has_value()) {
snapshot.images[i] = {.dword_count = 8u};
if (!active.empty() && !active[image.source]) {
continue;
}
const auto& indirect = *source->indirect_image;
DescriptorValue material;
DescriptorValue table;
if ((indirect.material_source != UINT32_MAX &&
!clean.EvaluateDescriptor(indirect.material_source, material)) ||
!clean.EvaluateDescriptor(indirect.table_source, table) ||
!MaterializeIndirectImage(program, indirect, material, table, i, observed, clean, snapshot,
specialization)) {
return false;
}
} else {
if (!evaluate(image.source, snapshot.images[i])) {
return false;
}
if (!ValidImageDescriptor(snapshot.images[i], image.r128)) {
snapshot.images[i].dwords.fill(0);
}
}
}
snapshot.samplers.resize(program.info.samplers.size());
for (uint32_t i = 0; i < program.info.samplers.size(); ++i) {
if (!evaluate(program.info.samplers[i].source, snapshot.samplers[i])) {
return false;
}
}
if (capture_reads && !WrittenBuffersDisjoint(program, snapshot, reads)) return false;
snapshot.user_data.assign(runtime.user_data.begin(), runtime.user_data.end());
return BuildResourceSpecialization(program, snapshot, specialization);
}
void ApplyResourceSpecialization(Program& program, const ResourceSpecialization& specialization) {
@@ -1075,29 +1167,29 @@ void ApplyResourceSpecialization(Program& program, const ResourceSpecialization&
for (auto* block: program.blocks) {
for (auto& inst: *block) {
if (inst.GetOpcode() == ValueOpcode::GetImageResource) {
inst.SetFlags(image_remap.indices.at(inst.Flags<uint32_t>()));
inst.SetFlags(image_remap[inst.Flags<uint32_t>()]);
}
}
}
for (auto& memory: memory_info) {
if (memory.kind == ResourceKind::Image && !memory.planning_only) {
memory.resource = image_remap.indices.at(memory.resource);
memory.resource = image_remap[memory.resource];
}
}
for (auto& buffer: buffers) {
if (buffer.image_alias != BufferResource::NoImageAlias) {
buffer.image_alias = image_remap.indices.at(buffer.image_alias);
buffer.image_alias = image_remap[buffer.image_alias];
}
}
for (auto& pair: sampled_pairs) {
pair.image = image_remap.indices.at(pair.image);
pair.image = image_remap[pair.image];
}
for (auto& image: images) {
if (image.indirect_root != ImageResource::NoIndirectImage) {
image.indirect_root = image_remap.indices.at(image.indirect_root);
image.indirect_root = image_remap[image.indirect_root];
}
for (auto& resource: image.indirect_resources) {
resource = image_remap.indices.at(resource);
resource = image_remap[resource];
}
}
image_remap.Apply(images);
@@ -39,8 +39,7 @@ struct ResourceSpecialization {
// its values and is independent of the translated shader CFG.
ResourcePlan ExtractResourcePlan(const Program& program);
// Resolves and specializes the immutable resource plan in one transaction. On failure both
// destinations are unchanged.
// Refreshes cached resources and specialization in place. A failed refresh must not be used.
bool MaterializeResources(const ResourcePlan& program, const SrtRuntime& runtime,
ResourceSnapshot& snapshot, ResourceSpecialization& specialization);
@@ -169,7 +169,7 @@ private:
std::abort();
}
Value LowerDescriptorPhi(Value value) {
Value LowerDescriptorPhi(Value value, const Block* use) {
value = value.Resolve();
const auto* phi = value.TryInstruction();
if (m_shader_writes || phi == nullptr || phi->GetOpcode() != ValueOpcode::Phi ||
@@ -177,17 +177,53 @@ private:
m_program.blocks.size() != m_program.block_info.size()) {
return value;
}
for (const auto& [original, selected]: m_descriptor_selections) {
if (original == phi) {
return selected;
}
}
const auto* merge = phi->Parent();
const auto* merge = phi->Parent();
const auto* branch = phi->PhiBlock(0);
if (merge == nullptr || branch == nullptr || phi->PhiBlock(1) == nullptr ||
branch == phi->PhiBlock(1)) {
return value;
}
// Structurization can merge the descriptor and its use predicate in parallel Phis.
// Match their incoming blocks to exclude only edges that cannot reach this use.
if (use != nullptr && use->ImmPredecessors().size() == 1u &&
use->ImmPredecessors()[0] == merge) {
const auto merge_it = std::ranges::find(m_program.blocks, merge);
const auto use_it = std::ranges::find(m_program.blocks, use);
if (merge_it != m_program.blocks.end() && use_it != m_program.blocks.end()) {
const auto& info = m_program.block_info[merge_it - m_program.blocks.begin()];
const auto& term = info.terminator;
const auto id = m_program.block_info[use_it - m_program.blocks.begin()].id;
const auto* condition = info.condition.Resolve().TryInstruction();
if (term.kind == CFG::TerminatorKind::ConditionalBranch &&
term.true_block != term.false_block &&
(id == term.true_block || id == term.false_block) && condition != nullptr &&
condition->GetOpcode() == ValueOpcode::Phi && condition->GetType() == Type::U1 &&
condition->Parent() == merge && condition->NumArgs() == 2u &&
condition->NumPhiBlocks() == 2u) {
const bool taken = id == term.true_block;
for (uint32_t skipped = 0; skipped < 2u; skipped++) {
const auto excluded = condition->Arg(skipped).Resolve();
const auto included = condition->Arg(skipped ^ 1u).Resolve();
if (!excluded.IsImmediate() || excluded.GetType() != Type::U1 ||
excluded.U1() == taken ||
(included.IsImmediate() && included.U1() != taken)) {
continue;
}
for (uint32_t selected = 0; selected < 2u; selected++) {
if (phi->PhiBlock(selected) == condition->PhiBlock(skipped ^ 1u) &&
phi->PhiBlock(selected ^ 1u) == condition->PhiBlock(skipped)) {
return phi->Arg(selected);
}
}
}
}
}
}
for (const auto& [original, selected]: m_descriptor_selections) {
if (original == phi) {
return selected;
}
}
if (branch->ImmSuccessors().size() != 2u) {
if (branch->ImmPredecessors().size() != 1u) {
return value;
@@ -247,7 +283,7 @@ private:
}
descriptor.dword_count = width;
for (uint32_t i = 0; i < width; i++) {
descriptor.dwords[i] = LowerDescriptorPhi(handle.Arg(i));
descriptor.dwords[i] = LowerDescriptorPhi(handle.Arg(i), handle.Parent());
}
if (sample_adjust) {
descriptor.dwords[3] = CanonicalizeSampleAdjustDword3(descriptor.dwords[3]);
@@ -277,9 +313,20 @@ private:
for (uint32_t candidate = 0; candidate < m_sources.size(); candidate++) {
const auto& current = m_sources[candidate];
if (current.dword_count != descriptor.dword_count ||
current.indirect_image != descriptor.indirect_image) {
current.indirect_image.has_value() != descriptor.indirect_image.has_value()) {
continue;
}
if (current.indirect_image.has_value()) {
const auto& a = *current.indirect_image;
const auto& b = *descriptor.indirect_image;
if (a.material_source != b.material_source || a.table_source != b.table_source ||
a.selector_stride != b.selector_stride || a.selector_offset != b.selector_offset ||
a.table_offset != b.table_offset ||
!EquivalentValue(m_program, a.key_count, b.key_count) ||
a.selector_mask.IsEmpty() != b.selector_mask.IsEmpty() ||
(!a.selector_mask.IsEmpty() &&
!EquivalentValue(m_program, a.selector_mask, b.selector_mask))) continue;
}
bool same = true;
for (uint32_t i = 0; i < descriptor.dword_count; i++) {
same = same && EquivalentValue(m_program, current.dwords[i], descriptor.dwords[i]);
@@ -308,16 +355,26 @@ private:
}
const MemoryInfo* ScalarReadMemory(const Inst& read, uint32_t& index) const {
if (read.GetOpcode() != ValueOpcode::ReadConstBuffer || read.NumArgs() != 2u) {
const bool address = read.GetOpcode() == ValueOpcode::LoadAddressU32;
if (!(address ? read.NumArgs() == 4u
: read.GetOpcode() == ValueOpcode::ReadConstBuffer && read.NumArgs() == 2u)) {
return nullptr;
}
if (address) {
const auto high = read.Arg(2).Resolve();
const auto enabled = read.Arg(3).Resolve();
if (!high.IsImmediate() || high.GetType() != Type::U32 || high.U32() != 0u ||
!enabled.IsImmediate() || enabled.GetType() != Type::U1 || !enabled.U1()) {
return nullptr;
}
}
index = read.Flags<MemoryFlags>().index;
if (index >= m_program.memory_info.size()) {
return nullptr;
}
const auto& memory = m_program.memory_info[index];
return memory.kind == ResourceKind::ScalarBuffer && memory.data_bits == 32u &&
memory.data_dwords == 1u
return memory.kind == (address ? ResourceKind::ScalarAddress : ResourceKind::ScalarBuffer) &&
memory.data_bits == 32u && memory.data_dwords == 1u
? &memory
: nullptr;
}
@@ -340,18 +397,16 @@ private:
return true;
}
bool MakeRuntimeBufferSource(const Inst& handle, uint32_t pc, uint32_t& source,
DescriptorSource& descriptor) {
if (handle.GetOpcode() != ValueOpcode::GetBufferResource) {
bool MakeRuntimeTableSource(const Inst& handle, uint32_t pc,
DescriptorSource& descriptor) {
const auto width = handle.GetOpcode() == ValueOpcode::GetBufferResource ? 4u
: handle.GetOpcode() == ValueOpcode::GetAddressResource ? 2u : 0u;
if (width == 0u) {
return false;
}
MakeSource(handle, 4u, false, false, descriptor, pc);
MakeSource(handle, width, false, false, descriptor, pc);
uint32_t bad_dword = 0;
if (!ValidateSource(descriptor, bad_dword)) {
return false;
}
source = InternSource(descriptor);
return true;
return ValidateSource(descriptor, bad_dword);
}
bool MatchMaterialOffset(Value value, Value& selector, uint32_t& stride,
@@ -388,106 +443,539 @@ private:
selector_inst->GetOpcode() == ValueOpcode::ReadFirstLane;
}
bool NonzeroOnEntry(Value value, const Block* block) const {
if (m_program.blocks.size() != m_program.block_info.size()) {
return false;
}
// Each unique predecessor must execute before this use. Stop at joins: an
// unrelated comparison is not a bound on FindILsb's zero-input sentinel.
for (size_t depth = 0; block != nullptr && depth < m_program.blocks.size(); ++depth) {
if (block->ImmPredecessors().size() != 1u) {
return false;
}
const auto* previous = block->ImmPredecessors()[0];
const auto current_it = std::ranges::find(m_program.blocks, block);
const auto previous_it = std::ranges::find(m_program.blocks, previous);
if (current_it == m_program.blocks.end() || previous_it == m_program.blocks.end()) {
return false;
}
const auto& info = m_program.block_info[previous_it - m_program.blocks.begin()];
const auto id = m_program.block_info[current_it - m_program.blocks.begin()].id;
const auto& term = info.terminator;
if (term.kind == CFG::TerminatorKind::ConditionalBranch &&
(term.true_block == id) != (term.false_block == id)) {
bool positive = term.true_block == id;
const auto* test = info.condition.Resolve().TryInstruction();
while (test != nullptr && test->GetOpcode() == ValueOpcode::LogicalNot) {
positive = !positive;
test = test->Arg(0).Resolve().TryInstruction();
}
if (test != nullptr && test->NumArgs() == 2u &&
((test->GetOpcode() == ValueOpcode::INotEqual32 && positive) ||
(test->GetOpcode() == ValueOpcode::IEqual32 && !positive))) {
for (uint32_t arg = 0; arg < 2u; ++arg) {
uint32_t immediate;
if (ImmediateU32(test->Arg(arg), immediate) && immediate == 0u &&
EquivalentValue(m_program, test->Arg(arg ^ 1u), value)) {
return true;
}
}
}
}
block = previous;
}
return false;
}
bool MatchTableOffset(Value value, Value& key, uint32_t& offset) const {
offset = 0;
for (;;) {
const auto* inst = value.Resolve().TryInstruction();
if (inst == nullptr || inst->NumArgs() != 2u) {
return false;
}
uint32_t immediate;
if (inst->GetOpcode() == ValueOpcode::ShiftLeftLogical32 &&
ImmediateU32(inst->Arg(1), immediate) && immediate == 5u) {
key = inst->Arg(0).Resolve();
return key.GetType() == Type::U32;
}
if (inst->GetOpcode() != ValueOpcode::IAdd32) {
return false;
}
if (ImmediateU32(inst->Arg(0), immediate)) {
value = inst->Arg(1);
} else if (ImmediateU32(inst->Arg(1), immediate)) {
value = inst->Arg(0);
} else {
return false;
}
// These additions are shader U32 arithmetic, before the scalar memory offset.
offset += immediate;
}
}
const Block* FindBlock(uint32_t id) const {
const auto info = std::ranges::find(m_program.block_info, id, &BlockInfo::id);
return info == m_program.block_info.end()
? nullptr
: m_program.blocks[info - m_program.block_info.begin()];
}
bool CanReach(const Block* start, const Block* target, const Block* avoid) const {
std::vector<const Block*> pending {start};
std::vector<const Block*> visited;
while (!pending.empty()) {
const auto* block = pending.back();
pending.pop_back();
if (block == nullptr || block == avoid ||
std::ranges::find(visited, block) != visited.end()) continue;
if (block == target) return true;
visited.push_back(block);
for (const auto* next: block->ImmSuccessors()) pending.push_back(next);
}
return false;
}
bool ConditionalEdge(const Block* from, const Block* to, Value& condition,
bool& positive) const {
const auto position = std::ranges::find(m_program.blocks, from);
const auto target = std::ranges::find(m_program.blocks, to);
if (position == m_program.blocks.end() || target == m_program.blocks.end()) return false;
const auto& info = m_program.block_info[position - m_program.blocks.begin()];
const auto& term = info.terminator;
const auto id = m_program.block_info[target - m_program.blocks.begin()].id;
if (term.kind != CFG::TerminatorKind::ConditionalBranch ||
(term.true_block == id) == (term.false_block == id)) return false;
condition = info.condition;
positive = term.true_block == id;
while (const auto* inst = condition.Resolve().TryInstruction()) {
if (inst->GetOpcode() != ValueOpcode::LogicalNot) break;
condition = inst->Arg(0);
positive = !positive;
}
condition = condition.Resolve();
return true;
}
Value PositiveUseGuard(const Block* use) const {
if (use == nullptr || use->ImmPredecessors().size() != 1u) return {};
Value condition;
bool positive = false;
return ConditionalEdge(use->ImmPredecessors()[0], use, condition, positive) && positive
? condition : Value {};
}
Value SimplifyGuard(Value guard) const {
const auto invariant = ResolveInvariantPhi(m_program, guard);
return (invariant.IsEmpty() ? guard : invariant).Resolve();
}
bool Implies(Value guard, Value required) const {
guard = SimplifyGuard(guard);
required = required.Resolve();
if (EquivalentValue(m_program, guard, required)) return true;
const auto* inst = guard.TryInstruction();
return inst != nullptr && inst->GetOpcode() == ValueOpcode::LogicalAnd &&
(Implies(inst->Arg(0), required) || Implies(inst->Arg(1), required));
}
Value EqualLocalKey(Value guard, Value key) const {
guard = SimplifyGuard(guard);
const auto* inst = guard.TryInstruction();
if (inst == nullptr) return {};
if (inst->GetOpcode() == ValueOpcode::LogicalAnd) {
const auto left = EqualLocalKey(inst->Arg(0), key);
return left.IsEmpty() ? EqualLocalKey(inst->Arg(1), key) : left;
}
if (inst->GetOpcode() != ValueOpcode::IEqual32 || inst->NumArgs() != 2u) return {};
if (EquivalentValue(m_program, inst->Arg(0), key)) return inst->Arg(1).Resolve();
if (EquivalentValue(m_program, inst->Arg(1), key)) return inst->Arg(0).Resolve();
return {};
}
struct AffineOffset {
Value index;
uint64_t stride = 0;
uint64_t offset = 0;
};
bool MatchAffineOffset(Value value, Value guard, AffineOffset& out,
uint32_t depth = 0) const {
if (depth > 16u) return false;
value = value.Resolve();
uint32_t immediate = 0;
if (ImmediateU32(value, immediate)) {
out.offset = immediate;
return true;
}
const auto* inst = value.TryInstruction();
if (inst == nullptr) return false;
if (inst->GetOpcode() == ValueOpcode::SelectU32 && inst->NumArgs() == 3u &&
Implies(guard, inst->Arg(0))) {
return MatchAffineOffset(inst->Arg(1), guard, out, depth + 1u);
}
if (inst->GetOpcode() == ValueOpcode::IAdd32 && inst->NumArgs() == 2u) {
AffineOffset left, right;
if (!MatchAffineOffset(inst->Arg(0), guard, left, depth + 1u) ||
!MatchAffineOffset(inst->Arg(1), guard, right, depth + 1u) ||
(!left.index.IsEmpty() && !right.index.IsEmpty() &&
!EquivalentValue(m_program, left.index, right.index))) return false;
out.index = left.index.IsEmpty() ? right.index : left.index;
out.stride = left.stride + right.stride;
out.offset = left.offset + right.offset;
return out.stride <= UINT32_MAX && out.offset <= UINT32_MAX;
}
if (inst->GetOpcode() == ValueOpcode::ShiftLeftLogical32 && inst->NumArgs() == 2u &&
ImmediateU32(inst->Arg(1), immediate) && immediate < 32u) {
if (!MatchAffineOffset(inst->Arg(0), guard, out, depth + 1u)) return false;
out.stride <<= immediate;
out.offset <<= immediate;
return out.stride <= UINT32_MAX && out.offset <= UINT32_MAX;
}
out.index = value;
out.stride = 1u;
return value.GetType() == Type::U32;
}
bool ImpliesNonzero(Value guard, Value value) const {
guard = SimplifyGuard(guard);
const auto* inst = guard.TryInstruction();
if (inst == nullptr) return false;
if (inst->GetOpcode() == ValueOpcode::LogicalAnd) {
return ImpliesNonzero(inst->Arg(0), value) ||
ImpliesNonzero(inst->Arg(1), value);
}
if (inst->GetOpcode() != ValueOpcode::INotEqual32 || inst->NumArgs() != 2u)
return false;
uint32_t zero = 1u;
return (ImmediateU32(inst->Arg(0), zero) && zero == 0u &&
EquivalentValue(m_program, inst->Arg(1), value)) ||
(ImmediateU32(inst->Arg(1), zero) && zero == 0u &&
EquivalentValue(m_program, inst->Arg(0), value));
}
bool ImpliesIndexBelow32(Value guard, Value index) const {
guard = SimplifyGuard(guard);
const auto* inst = guard.TryInstruction();
if (inst == nullptr) return false;
if (inst->GetOpcode() == ValueOpcode::LogicalAnd) {
return ImpliesIndexBelow32(inst->Arg(0), index) ||
ImpliesIndexBelow32(inst->Arg(1), index);
}
if (inst->NumArgs() != 2u) return false;
uint32_t limit = 0;
return (inst->GetOpcode() == ValueOpcode::SLessThan32 &&
EquivalentValue(m_program, inst->Arg(0), index) &&
ImmediateU32(inst->Arg(1), limit) && limit == 32u) ||
(inst->GetOpcode() == ValueOpcode::SGreaterThan32 &&
ImmediateU32(inst->Arg(0), limit) && limit == 32u &&
EquivalentValue(m_program, inst->Arg(1), index));
}
bool ClearsFirstSetBit(Value value, Value mask) const {
const auto* update = value.Resolve().TryInstruction();
if (update == nullptr || update->GetOpcode() != ValueOpcode::BitwiseXor32 ||
update->NumArgs() != 2u) return false;
Value bit;
if (EquivalentValue(m_program, update->Arg(0), mask)) bit = update->Arg(1);
else if (EquivalentValue(m_program, update->Arg(1), mask)) bit = update->Arg(0);
else return false;
const auto* shift = bit.Resolve().TryInstruction();
uint32_t one = 0;
if (shift == nullptr || shift->GetOpcode() != ValueOpcode::ShiftLeftLogical32 ||
shift->NumArgs() != 2u || !ImmediateU32(shift->Arg(0), one) || one != 1u)
return false;
Value position = shift->Arg(1).Resolve();
const auto* masked = position.TryInstruction();
uint32_t lane_mask = 0;
if (masked != nullptr && masked->GetOpcode() == ValueOpcode::BitwiseAnd32 &&
masked->NumArgs() == 2u) {
if (ImmediateU32(masked->Arg(0), lane_mask) && lane_mask == 31u)
position = masked->Arg(1);
else if (ImmediateU32(masked->Arg(1), lane_mask) && lane_mask == 31u)
position = masked->Arg(0);
}
const auto* first = position.Resolve().TryInstruction();
return first != nullptr && first->GetOpcode() == ValueOpcode::FindILsb32 &&
first->NumArgs() == 1u && EquivalentValue(m_program, first->Arg(0), mask);
}
Value InitialClearingMask(Value value, const Block* update_block) const {
const auto* phi = value.Resolve().TryInstruction();
if (phi == nullptr || phi->GetOpcode() != ValueOpcode::Phi ||
phi->NumArgs() != 2u || phi->GetType() != Type::U32) return {};
for (uint32_t back = 0; back < 2u; ++back) {
if (phi->PhiBlock(back) != update_block ||
!ClearsFirstSetBit(phi->Arg(back), value)) continue;
const auto initial = phi->Arg(back ^ 1u).Resolve();
return ValidateRuntimeValue(m_program, initial, RuntimeValueType::Integer)
? initial : Value {};
}
return {};
}
bool EdgeOn(const Block* from, const Block* to, Value predicate, bool positive) const {
Value condition;
bool taken = false;
return ConditionalEdge(from, to, condition, taken) && taken == positive &&
EquivalentValue(m_program, condition, predicate);
}
Value BoundedSetBitMask(Value index, Value guard) const {
const auto* phi = index.Resolve().TryInstruction();
if (phi == nullptr || phi->GetOpcode() != ValueOpcode::Phi ||
phi->NumArgs() != 3u || phi->GetType() != Type::U32 ||
!ImpliesIndexBelow32(guard, index)) return {};
for (uint32_t bit_arm = 0; bit_arm < 3u; ++bit_arm) {
const auto bit_guard = PositiveUseGuard(phi->PhiBlock(bit_arm));
const auto* selected = phi->Arg(bit_arm).Resolve().TryInstruction();
if (selected == nullptr || selected->GetOpcode() != ValueOpcode::SelectU32 ||
selected->NumArgs() != 3u ||
!Implies(bit_guard, selected->Arg(0))) continue;
const auto* first = selected->Arg(1).Resolve().TryInstruction();
if (first == nullptr || first->GetOpcode() != ValueOpcode::FindILsb32 ||
first->NumArgs() != 1u || !ImpliesNonzero(bit_guard, first->Arg(0)))
continue;
const auto initial_mask = InitialClearingMask(first->Arg(0), phi->PhiBlock(bit_arm));
if (initial_mask.IsEmpty()) continue;
for (uint32_t sentinel_arm = 0; sentinel_arm < 3u; ++sentinel_arm) {
if (sentinel_arm == bit_arm) continue;
const auto sentinel_guard = PositiveUseGuard(phi->PhiBlock(sentinel_arm));
const auto* sentinel = phi->Arg(sentinel_arm).Resolve().TryInstruction();
uint32_t bound = 0;
if (sentinel == nullptr || sentinel->GetOpcode() != ValueOpcode::SelectU32 ||
sentinel->NumArgs() != 3u ||
!Implies(sentinel_guard, sentinel->Arg(0)) ||
!ImmediateU32(sentinel->Arg(1), bound) || bound != 32u) continue;
const auto loop_active = sentinel->Arg(0).Resolve();
const auto* active_phi = loop_active.TryInstruction();
if (active_phi == nullptr || active_phi->GetOpcode() != ValueOpcode::Phi ||
active_phi->NumArgs() != 2u) continue;
bool invariant = false;
for (uint32_t initial = 0; initial < 2u; ++initial) {
invariant = Implies(guard, active_phi->Arg(initial)) &&
EdgeOn(active_phi->PhiBlock(initial ^ 1u), active_phi->Parent(),
active_phi->Arg(initial ^ 1u), true);
if (invariant) break;
}
if (!invariant) continue;
const auto other_arm = 3u - bit_arm - sentinel_arm;
if (EdgeOn(phi->PhiBlock(other_arm), phi->Parent(), loop_active, false))
return initial_mask;
}
}
return {};
}
bool MatchUniformizedMaterialKey(Value key, const Inst& image,
DescriptorSource::IndirectImage& indirect,
DescriptorSource& material_source, uint32_t pc) {
const auto guard = PositiveUseGuard(image.Parent());
if (guard.IsEmpty()) return false;
const auto local = EqualLocalKey(guard, key);
const auto* selected = local.Resolve().TryInstruction();
if (selected == nullptr || selected->GetOpcode() != ValueOpcode::SelectU32 ||
selected->NumArgs() != 3u || !Implies(guard, selected->Arg(0))) return false;
const auto active = selected->Arg(0).Resolve();
const auto* read = selected->Arg(1).Resolve().TryInstruction();
if (read == nullptr || read->GetOpcode() != ValueOpcode::LoadAddressU32 ||
read->NumArgs() != 4u || !EquivalentValue(m_program, read->Arg(3), active))
return false;
uint32_t high = 1u;
if (!ImmediateU32(read->Arg(2), high) || high != 0u) return false;
const auto memory_index = read->Flags<MemoryFlags>().index;
if (memory_index >= m_program.memory_info.size()) return false;
const auto& memory = m_program.memory_info[memory_index];
if (memory.kind != ResourceKind::Global || memory.data_bits != 32u ||
memory.data_dwords != 1u || !MemoryIndexBelongsTo(memory_index, *read)) return false;
const auto* material_handle = read->Arg(0).Resolve().TryInstruction();
if (material_handle == nullptr ||
material_handle->GetOpcode() != ValueOpcode::GetAddressResource ||
!MakeRuntimeTableSource(*material_handle, pc, material_source)) return false;
AffineOffset offset;
if (!MatchAffineOffset(read->Arg(1), active, offset) || offset.index.IsEmpty() ||
offset.stride == 0u || offset.offset + memory.offset > UINT32_MAX ||
offset.offset + memory.offset + 31u * offset.stride + 4u >
static_cast<uint64_t>(UINT32_MAX) + 1u) return false;
const auto mask = BoundedSetBitMask(offset.index, active);
if (mask.IsEmpty()) return false;
indirect.material_source = InternSource(material_source);
indirect.selector_stride = static_cast<uint32_t>(offset.stride);
indirect.selector_offset = static_cast<uint32_t>(offset.offset + memory.offset);
indirect.key_count = Value(32u);
indirect.selector_mask = mask;
return true;
}
Value BoundedLoopCount(Value key, const Block* use) const {
const auto* phi = key.Resolve().TryInstruction();
if (m_shader_writes || phi == nullptr || phi->GetOpcode() != ValueOpcode::Phi ||
phi->GetType() != Type::U32 || phi->NumArgs() != 2u ||
m_program.blocks.size() != m_program.block_info.size()) {
return {};
}
bool induction = false;
for (uint32_t initial = 0; initial < 2u; ++initial) {
const auto zero = phi->Arg(initial).Resolve();
const auto* step = phi->Arg(initial ^ 1u).Resolve().TryInstruction();
if (!zero.IsImmediate() || zero.GetType() != Type::U32 || zero.U32() != 0u ||
step == nullptr || step->GetOpcode() != ValueOpcode::IAdd32 ||
step->Parent() != phi->PhiBlock(initial ^ 1u)) {
continue;
}
uint32_t increment = 0;
induction = (step->Arg(0).Resolve() == key &&
ImmediateU32(step->Arg(1), increment) && increment == 1u) ||
(step->Arg(1).Resolve() == key &&
ImmediateU32(step->Arg(0), increment) && increment == 1u);
if (induction) break;
}
if (!induction) return {};
const auto contains = [&](auto&& self, Value value, const Inst* comparison) -> bool {
value = value.Resolve();
if (value.TryInstruction() == comparison) return true;
const auto* inst = value.TryInstruction();
return inst != nullptr && inst->GetOpcode() == ValueOpcode::LogicalAnd &&
(self(self, inst->Arg(0), comparison) || self(self, inst->Arg(1), comparison));
};
for (const auto& use_of_key: phi->Uses()) {
const auto* compare = use_of_key.user;
if (compare->GetOpcode() != ValueOpcode::SLessThan32 || use_of_key.operand != 0u ||
!ValidateRuntimeValue(m_program, compare->Arg(1))) continue;
for (uint32_t i = 0; i < m_program.block_info.size(); ++i) {
const auto& info = m_program.block_info[i];
const auto* negated = info.condition.Resolve().TryInstruction();
if (info.terminator.kind != CFG::TerminatorKind::ConditionalBranch ||
compare->Parent() != m_program.blocks[i] || negated == nullptr ||
negated->GetOpcode() != ValueOpcode::LogicalNot ||
!contains(contains, negated->Arg(0), compare) ||
use == m_program.blocks[i] ||
CanReach(m_program.blocks.front(), use, m_program.blocks[i]) ||
CanReach(phi->Parent(), use, m_program.blocks[i]) ||
CanReach(FindBlock(info.terminator.true_block), use, phi->Parent()) ||
!CanReach(FindBlock(info.terminator.false_block), use, phi->Parent())) {
continue;
}
return compare->Arg(1);
}
}
return {};
}
bool TryMakeIndirectImage(Inst& handle, uint32_t pc, IndirectImagePlan& plan) {
if (handle.GetOpcode() != ValueOpcode::GetImageResource || handle.NumArgs() != 8u) {
return false;
}
std::array<Inst*, 8> heap_reads {};
Inst* heap_handle = nullptr;
Value heap_offset;
for (uint32_t dword = 0; dword < heap_reads.size(); dword++) {
heap_reads[dword] = handle.Arg(dword).Resolve().TryInstruction();
if (heap_reads[dword] == nullptr) {
Inst* table_handle = nullptr;
Value key;
uint32_t table_offset = 0;
for (uint32_t dword = 0; dword < plan.reads.size(); ++dword) {
auto* read = handle.Arg(dword).Resolve().TryInstruction();
if (read == nullptr) {
return false;
}
uint32_t memory_index = 0;
const auto* memory = ScalarReadMemory(*heap_reads[dword], memory_index);
if (memory == nullptr || memory->offset != dword * sizeof(uint32_t) ||
!MemoryIndexBelongsTo(memory_index, *heap_reads[dword])) {
uint32_t memory_index = 0;
const auto* memory = ScalarReadMemory(*read, memory_index);
if (memory == nullptr || memory->offset > INT32_MAX || (memory->offset & 3u) != 0u ||
!MemoryIndexBelongsTo(memory_index, *read)) {
return false;
}
auto* current_handle = heap_reads[dword]->Arg(0).Resolve().TryInstruction();
auto* current_handle = read->Arg(0).Resolve().TryInstruction();
Value current_key;
uint32_t offset = 0;
if (current_handle == nullptr ||
(heap_handle != nullptr && current_handle != heap_handle)) {
current_handle->GetOpcode() != (memory->kind == ResourceKind::ScalarAddress
? ValueOpcode::GetAddressResource
: ValueOpcode::GetBufferResource) ||
(memory->kind == ResourceKind::ScalarAddress && read->Parent() != handle.Parent()) ||
(table_handle != nullptr &&
!EquivalentValue(m_program, Value(table_handle), Value(current_handle))) ||
!MatchTableOffset(read->Arg(1), current_key, offset) ||
memory->offset > UINT32_MAX - offset) {
return false;
}
heap_handle = current_handle;
offset += memory->offset;
if (dword == 0u) {
heap_offset = heap_reads[dword]->Arg(1).Resolve();
} else if (!EquivalentValue(m_program, heap_offset, heap_reads[dword]->Arg(1))) {
key = current_key;
table_offset = offset;
} else if (!EquivalentValue(m_program, key, current_key) ||
static_cast<uint64_t>(table_offset) + dword * sizeof(uint32_t) != offset) {
return false;
}
plan.memory[dword] = memory_index;
plan.reads[dword] = heap_reads[dword];
}
const auto* shift = heap_offset.TryInstruction();
uint32_t shift_amount = 0;
if (shift == nullptr || shift->GetOpcode() != ValueOpcode::ShiftLeftLogical32 ||
shift->NumArgs() != 2u || !ImmediateU32(shift->Arg(1), shift_amount) ||
shift_amount != 5u) {
return false;
}
auto* material_read = shift->Arg(0).Resolve().TryInstruction();
if (material_read == nullptr) {
return false;
}
uint32_t material_memory_index = 0;
const auto* material_memory = ScalarReadMemory(*material_read, material_memory_index);
if (material_memory == nullptr || material_memory->offset != 0u ||
!MemoryIndexBelongsTo(material_memory_index, *material_read)) {
return false;
}
auto* material_handle = material_read->Arg(0).Resolve().TryInstruction();
if (material_handle == nullptr) {
return false;
}
Value selector;
uint32_t selector_stride = 0;
uint32_t selector_offset = 0;
if (!MatchMaterialOffset(material_read->Arg(1), selector, selector_stride,
selector_offset)) {
return false;
}
const std::array<const Inst*, 1> material_users {shift};
std::array<const Inst*, 8> heap_users {};
std::copy(heap_reads.begin(), heap_reads.end(), heap_users.begin());
const std::array<const Inst*, 1> image_users {&handle};
if (!UsesOnly(*material_read, material_users) || !UsesOnly(*shift, heap_users)) {
return false;
}
for (const auto* read: heap_reads) {
table_handle = current_handle;
const std::array<const Inst*, 1> image_users {&handle};
if (!UsesOnly(*read, image_users)) {
return false;
}
plan.memory[dword] = memory_index;
plan.reads[dword] = read;
}
DescriptorSource material_source;
DescriptorSource heap_source;
uint32_t material_source_index = 0;
uint32_t heap_source_index = 0;
if (!MakeRuntimeBufferSource(*material_handle, pc, material_source_index,
material_source) ||
!MakeRuntimeBufferSource(*heap_handle, pc, heap_source_index, heap_source)) {
DescriptorSource table_source;
if (!MakeRuntimeTableSource(*table_handle, pc, table_source)) {
return false;
}
DescriptorSource material_source;
DescriptorSource::IndirectImage indirect;
indirect.table_offset = table_offset;
if (table_source.dword_count == 2u) {
const auto* selector = key.Resolve().TryInstruction();
const bool bitscan = selector != nullptr && selector->GetOpcode() == ValueOpcode::FindILsb32 &&
selector->NumArgs() == 1u && !m_shader_writes &&
NonzeroOnEntry(selector->Arg(0), handle.Parent());
if (bitscan) {
indirect.key_count = Value(32u);
} else {
indirect.key_count = BoundedLoopCount(key, handle.Parent());
}
if (indirect.key_count.IsEmpty() &&
!MatchUniformizedMaterialKey(key, handle, indirect, material_source, pc)) {
return false;
}
if ((table_offset & 3u) != 0u ||
(bitscan && table_offset > UINT32_MAX - (32u * 32u - 1u))) return false;
} else {
auto* material_read = key.Resolve().TryInstruction();
uint32_t material_memory_index = 0;
const auto* memory = material_read != nullptr
? ScalarReadMemory(*material_read, material_memory_index) : nullptr;
if (table_offset != 0u || memory == nullptr || memory->kind != ResourceKind::ScalarBuffer ||
memory->offset != 0u || !MemoryIndexBelongsTo(material_memory_index, *material_read)) {
return false;
}
Value selector;
if (!MatchMaterialOffset(material_read->Arg(1), selector, indirect.selector_stride,
indirect.selector_offset)) {
return false;
}
const auto* shift = plan.reads[0]->Arg(1).Resolve().TryInstruction();
const std::array<const Inst*, 1> material_users {shift};
if (!UsesOnly(*material_read, material_users) || !UsesOnly(*shift, plan.reads)) {
return false;
}
const auto* material_handle = material_read->Arg(0).Resolve().TryInstruction();
if (material_handle == nullptr ||
material_handle->GetOpcode() != ValueOpcode::GetBufferResource ||
!MakeRuntimeTableSource(*material_handle, pc, material_source)) {
return false;
}
indirect.material_source = InternSource(material_source);
}
indirect.table_source = InternSource(table_source);
DescriptorSource image_source;
image_source.dword_count = 8u;
std::copy(material_source.dwords.begin(), material_source.dwords.begin() + 4u,
image_source.dwords.begin());
std::copy(heap_source.dwords.begin(), heap_source.dwords.begin() + 4u,
image_source.dwords.begin() + 4u);
image_source.indirect_image = DescriptorSource::IndirectImage {
material_source_index, heap_source_index, selector_stride, selector_offset, 0u};
image_source.dwords.fill(Value(0u));
std::copy_n(material_source.dwords.begin(), material_source.dword_count,
image_source.dwords.begin());
std::copy_n(table_source.dwords.begin(), table_source.dword_count,
image_source.dwords.begin() + 4u);
image_source.indirect_image = indirect;
plan.handle = &handle;
plan.source = InternSource(image_source);
plan.key = Value(material_read);
plan.roots = image_source.dwords;
plan.key = key;
plan.roots = image_source.dwords;
return true;
}
@@ -526,7 +1014,7 @@ private:
}
}
void GetHandle(Value value, ValueOpcode expected, uint32_t width, uint32_t pc, Inst*& handle,
bool GetHandle(Value value, ValueOpcode expected, uint32_t width, uint32_t pc, Inst*& handle,
uint32_t& source, bool sampler = false, bool sample_adjust = false) {
handle = value.Resolve().TryInstruction();
if (handle == nullptr || handle->GetOpcode() != expected) {
@@ -535,21 +1023,17 @@ private:
DescriptorSource descriptor;
MakeSource(*handle, width, sampler, sample_adjust, descriptor, pc);
uint32_t bad_dword = 0;
if (expected == ValueOpcode::GetImageResource) {
for (; bad_dword < descriptor.dword_count; bad_dword++) {
const auto* value = descriptor.dwords[bad_dword].Resolve().TryInstruction();
if (value != nullptr && value->GetOpcode() == ValueOpcode::ReadConstBuffer) {
Fail(pc, fmt::format("{} dword {} is not a valid runtime value",
ValueOpcodeName(expected), bad_dword));
}
}
bad_dword = 0;
}
if (!ValidateSource(descriptor, bad_dword)) {
if (expected == ValueOpcode::GetBufferResource &&
std::all_of(descriptor.dwords.begin(), descriptor.dwords.begin() + width,
[](Value word) { return word.Resolve().GetType() == Type::U32; })) {
return false;
}
Fail(pc, fmt::format("{} dword {} is not a valid runtime value",
ValueOpcodeName(expected), bad_dword));
}
source = InternSource(descriptor);
return true;
}
void ValidateAddressHandle(Value value, uint32_t pc) const {
@@ -720,7 +1204,17 @@ private:
uint32_t resource = 0;
if (buffer != BufferAccess::None) {
GetHandle(inst.Arg(0), ValueOpcode::GetBufferResource, 4, flags.pc, handle, source);
if (!GetHandle(inst.Arg(0), ValueOpcode::GetBufferResource, 4, flags.pc, handle,
source)) {
if (memory.kind != ResourceKind::Buffer || !memory.SupportsIndirectBufferLoad(op)) {
Fail(flags.pc,
"buffer descriptor is not a valid runtime value; GPU-selected access "
"requires a raw DWORD x2/x3/x4 load");
}
m_program.memory_info[flags.index].kind = ResourceKind::IndirectBuffer;
m_info.uses_dma = true;
return;
}
resource = AddBuffer(source, memory, op, flags.pc);
if (resource == UINT32_MAX) {
Fail(flags.pc, "buffer resource limit exceeded");
@@ -745,6 +1239,9 @@ private:
return;
}
ValidateAddressHandle(inst.Arg(0), flags.pc);
if (address_info.access == AddressAccess::Write) {
m_program.has_address_writes = true;
}
m_info.uses_dma = true;
return;
}
File diff suppressed because it is too large. Load diff
@@ -9,7 +9,7 @@ namespace Libs::Graphics::ShaderRecompiler::IR {
class Value;
using SrtMemoryReader = bool (*)(void* userdata, uint64_t address, uint32_t* value);
using SrtMemoryReader = bool (*)(void* userdata, uint64_t address, std::span<uint32_t> values);
struct SrtRuntime {
std::span<const uint32_t> user_data;
@@ -26,26 +26,42 @@ enum class RuntimeValueType { Any, Integer };
void BuildSrtPlan(Program& program);
bool ValidateRuntimeValue(const ResourcePlan& program, Value value,
RuntimeValueType type = RuntimeValueType::Any);
bool EvaluateUniformValues(const ResourcePlan& program, std::span<const Value> values,
const SrtRuntime& runtime, std::span<uint32_t> results);
// Uses the strict reader for values that affect shader specialization.
SrtRuntime CleanRuntime(SrtRuntime runtime);
bool EvaluateDescriptorSource(const ResourcePlan& program, uint32_t source,
const SrtRuntime& runtime, DescriptorValue& result);
// One memoized evaluation session shared by the entire shader resource refresh.
class SrtWalker {
public:
SrtWalker(const ResourcePlan& program, const SrtRuntime& runtime,
std::span<const uint8_t> clean_flat_slots = {}, SrtWalker* clean_evaluator = nullptr,
Value active_mask = {});
~SrtWalker();
SrtWalker(const SrtWalker&) = delete;
SrtWalker& operator=(const SrtWalker&) = delete;
// Evaluates one runtime snapshot transactionally. Scalar values and ReadConst results shared by
// several descriptors are memoized once across the batch.
bool EvaluateDescriptorSources(const ResourcePlan& program, std::span<const uint32_t> sources,
const SrtRuntime& runtime, std::vector<DescriptorValue>& results);
bool Evaluate(Value value, uint32_t& result);
bool EvaluateDescriptor(uint32_t source, DescriptorValue& result);
// An empty span means that all sources are active.
std::span<const uint8_t> FindActiveSources();
bool RefreshFlatBuffer(std::vector<uint32_t>& flat);
// Evaluates potentially reachable descriptor sources and the flattened immediate SRT with one
// memoized scalar walk. Inactive descriptors are zero; on failure no destination is changed.
bool EvaluateRuntimeSources(const ResourcePlan& program, std::span<const uint32_t> sources,
const SrtRuntime& runtime, std::vector<DescriptorValue>& results,
std::vector<uint32_t>& flat, std::span<const uint8_t> clean_flat_slots,
std::vector<uint8_t>& active_sources);
private:
static ResourcePlan::EvaluationContext& AcquireContext(const ResourcePlan& program);
static float Float32(uint64_t bits);
bool EvaluateWide(Value value, uint64_t& result);
bool Arg(const Inst& inst, size_t index, uint64_t& result);
bool EvaluatePhi(const Inst& inst, uint64_t& result);
bool EvaluateExtract(const Inst& inst, uint64_t& result);
bool EvaluateRawRead(const Inst& inst, uint64_t& result);
bool EvaluateInst(const Inst& inst, uint64_t& result);
bool WalkSrt(const ResourcePlan& program, const SrtRuntime& runtime,
std::vector<uint32_t>& flat);
const ResourcePlan& m_program;
SrtRuntime m_runtime;
std::span<const uint8_t> m_clean_flat_slots;
SrtWalker* m_clean_evaluator = nullptr;
Value m_active_mask;
ResourcePlan::EvaluationContext& m_context;
};
} // namespace Libs::Graphics::ShaderRecompiler::IR
+20 -12
View File
@@ -108,7 +108,7 @@ static uint64_t GetDeclaredShaderHash(uint64_t shader_addr) {
static ShaderParams GetShaderParams(uint64_t shader_addr, const char* label, uint64_t declared_hash,
std::span<const uint32_t> user_data,
const ShaderMappedData& data) {
const ShaderMappedData& data, uint32_t user_data_base = 0) {
if (data.code_size_bytes == 0 || data.code_size_bytes % sizeof(uint32_t) != 0) {
EXIT("%s hash=0x%016" PRIx64 " shader=0x%016" PRIx64
" has invalid AGC shader_size=0x%08" PRIx32 "\n",
@@ -116,12 +116,16 @@ static ShaderParams GetShaderParams(uint64_t shader_addr, const char* label, uin
}
const auto code_words = data.code_size_bytes / sizeof(uint32_t);
const auto code = std::span {reinterpret_cast<const uint32_t*>(shader_addr), code_words};
return {
.code = code,
.user_data = std::vector<uint32_t>(user_data.begin(), user_data.end()),
.hash = declared_hash != 0 ? declared_hash
: XXH3_64bits(code.data(), code.size_bytes()),
ShaderParams params {
.code = code,
.user_data_count = static_cast<uint32_t>(user_data.size()) + user_data_base,
.hash = declared_hash != 0 ? declared_hash
: XXH3_64bits(code.data(), code.size_bytes()),
};
EXIT_IF(user_data.size() > HW::UserSgprInfo::SGPRS_MAX ||
params.user_data_count > params.user_data.size());
std::copy(user_data.begin(), user_data.end(), params.user_data.begin() + user_data_base);
return params;
}
#if 0
@@ -384,6 +388,8 @@ static void ShaderApplyAttribSemantics(ShaderVertexInputInfo& info,
EXIT_IF(attrib == nullptr || buffer == nullptr);
const bool debug_dump = Config::GraphicsDebugDumpEnabled();
for (uint32_t i = 0; i < num_input_semantics; i++) {
const auto& in = input_semantics[i];
@@ -392,7 +398,7 @@ static void ShaderApplyAttribSemantics(ShaderVertexInputInfo& info,
uint32_t reg = in.hardware_mapping;
uint32_t size = in.size_in_elements;
if (Config::GraphicsDebugDumpEnabled()) {
if (debug_dump) {
LOGF("reg = %u, size = %u, va[%u] = 0x%08" PRIx32 "\n", reg, size, i,
attrib[in.semantic]);
}
@@ -651,6 +657,7 @@ void BuildStageStaticKey(const ShaderVertexInputInfo& info, std::vector<uint32_t
key.push_back(static_cast<uint32_t>(info.fetch_attrib_reg));
key.push_back(static_cast<uint32_t>(info.fetch_buffer_reg));
key.push_back(info.resources_num);
key.push_back(info.wave_size);
key.push_back(info.scratch_size_dwords);
key.push_back(info.pa_cl_vs_out_cntl);
key.push_back(static_cast<uint32_t>(info.clip_space.enabled));
@@ -755,20 +762,22 @@ ShaderParams PrepareProgram(const HW::VertexShaderInfo& regs, const HW::Context&
const HW::UserConfig& user_config, ShaderVertexInputInfo& info) {
const auto& sh = context.GetShaderRegisters();
const auto data = ShaderGetMappedData(regs.es_regs.data_addr, "ShaderGetInputInfoVS():");
const bool merged = (context.GetShaderStages() & 0x20u) != 0;
auto params = GetShaderParams(
regs.es_regs.data_addr, "ShaderRecompiler VS",
GetDeclaredShaderHash(regs.es_regs.data_addr),
std::span<const uint32_t>(regs.gs_user_sgpr.value, regs.gs_regs.rsrc2.user_sgpr), data);
if ((context.GetShaderStages() & 0x20u) == 0) {
std::span<const uint32_t>(regs.gs_user_sgpr.value, regs.gs_regs.rsrc2.user_sgpr), data,
merged ? 8u : 0u);
if (!merged) {
if (!ShaderGetStaticVertexInputInfo(regs.es_regs.data_addr, regs.gs_user_sgpr,
regs.gs_regs.rsrc2.user_sgpr, sh, data, info)) {
EXIT("failed to prepare vertex shader program\n");
}
info.wave_size = (context.GetShaderStages() & 0x00400000u) != 0 ? 32u : 64u;
return params;
}
// NGG user SGPRs start at s8; a separately compiled GS back half also receives
// its user-data pointer in s0:s1.
params.user_data.insert(params.user_data.begin(), 8u, 0u);
info = {};
info.logical_stage = ShaderType::Mesh;
info.pa_cl_vs_out_cntl = sh.m_paClVsOutCntl;
@@ -839,14 +848,13 @@ PrepareTessellationPrograms(const HW::VertexShaderInfo& regs, const HW::Context&
GetShaderParams(regs.ls_regs.data_addr, "ShaderRecompiler LS",
GetDeclaredShaderHash(regs.ls_regs.data_addr), local_users, local),
GetShaderParams(regs.hs_regs.data_addr, "ShaderRecompiler HS",
GetDeclaredShaderHash(regs.hs_regs.data_addr), local_users, control),
GetDeclaredShaderHash(regs.hs_regs.data_addr), local_users, control, 8u),
GetShaderParams(regs.es_regs.data_addr, "ShaderRecompiler TES",
GetDeclaredShaderHash(regs.es_regs.data_addr), evaluation_users,
evaluation),
};
// The fused HS back half receives its separate user-data address in s0:s1.
// RDNA2 reserves s0:s7 before the native HS user SGPRs.
params[1].user_data.insert(params[1].user_data.begin(), 8u, 0u);
params[1].user_data[0] = static_cast<uint32_t>(regs.hs_regs.user_data_addr);
params[1].user_data[1] = static_cast<uint32_t>(regs.hs_regs.user_data_addr >> 32u);
+3 -2
View File
@@ -51,10 +51,10 @@ struct CompiledShaderInfo;
struct ShaderStageRuntime {
const ShaderRecompiler::IR::CompiledShaderInfo* program = nullptr;
ShaderRecompiler::IR::ResourceSnapshot resources;
const ShaderRecompiler::IR::ResourceSnapshot* resources = nullptr;
[[nodiscard]] explicit operator bool() const {
return program != nullptr;
return program != nullptr && resources != nullptr;
}
};
@@ -134,6 +134,7 @@ struct ShaderVertexInputInfo {
int fetch_attrib_reg = 0;
int fetch_buffer_reg = 0;
int buffers_num = 0;
uint32_t wave_size = 64;
uint32_t scratch_size_dwords = 0;
uint32_t pa_cl_vs_out_cntl = 0;
ShaderClipSpaceTransform clip_space;
+3 -1
View File
@@ -3,6 +3,7 @@
#include "graphics/shader/shader.h"
#include <array>
#include <span>
#include <vector>
@@ -15,7 +16,8 @@ class UserConfig;
struct ShaderParams {
std::span<const uint32_t> code;
std::vector<uint32_t> user_data;
std::array<uint32_t, 40> user_data {}; // 32 user SGPRs plus the merged-stage s0:s7 prefix.
uint32_t user_data_count = 0;
uint64_t hash = 0;
std::span<const uint32_t> back_code;
+169
View File
@@ -64,6 +64,7 @@ struct File {
std::filesystem::path real_name;
std::atomic_bool opened;
std::atomic_bool directory;
std::atomic_bool readable;
std::atomic_bool writable;
std::atomic_bool append;
std::atomic_bool sync_writes;
@@ -175,6 +176,7 @@ int FileDescriptors::CreateDescriptor() {
auto* file = new File {};
file->opened = false;
file->directory = false;
file->readable = false;
file->writable = false;
file->append = false;
file->sync_writes = false;
@@ -442,6 +444,7 @@ int KYTY_SYSV_ABI KernelOpen(const char* path, int flags, uint16_t mode) {
EXIT_IF(file == nullptr || file->opened || file->directory);
file->name = path;
file->readable = rw_mode != Common::File::Mode::Write;
file->writable = rw_mode != Common::File::Mode::Read;
file->append = append;
file->sync_writes = fsync || sync || dsync;
@@ -761,6 +764,106 @@ int64_t KYTY_SYSV_ABI KernelPread(int d, void* buf, size_t nbytes, int64_t offse
return bytes_read;
}
static int ValidateIovecs(const KernelIovec* iov, int iovcnt, int64_t offset,
std::vector<KernelIovec>* buffers, size_t* total) {
constexpr int MaxIov = 1024;
if (iovcnt < 0 || iovcnt > MaxIov || offset < 0) {
return KERNEL_ERROR_EINVAL;
}
if (iov == nullptr && iovcnt != 0) {
return KERNEL_ERROR_EFAULT;
}
if (iovcnt != 0) {
buffers->assign(iov, iov + iovcnt);
}
*total = 0;
for (const auto& buffer: *buffers) {
if (buffer.iov_len > INT_MAX - *total) {
return KERNEL_ERROR_EINVAL;
}
if (buffer.iov_base == nullptr && buffer.iov_len != 0) {
return KERNEL_ERROR_EFAULT;
}
*total += buffer.iov_len;
}
return OK;
}
int64_t KYTY_SYSV_ABI KernelPreadv(int d, const KernelIovec* iov, int iovcnt, int64_t offset) {
PRINT_NAME();
std::vector<KernelIovec> buffers;
size_t total = 0;
const int error = ValidateIovecs(iov, iovcnt, offset, &buffers, &total);
if (error != OK) {
return error;
}
if (d < DESCRIPTOR_MIN) {
return KERNEL_ERROR_EBADF;
}
if (::Libs::Network::Net::IsSocket(d)) {
return KERNEL_ERROR_ESPIPE;
}
auto* file = g_files->GetFile(d);
if (file == nullptr || !file->opened || !file->readable) {
return KERNEL_ERROR_EBADF;
}
Common::LockGuard lock(file->mutex);
if (file->directory) {
return KERNEL_ERROR_EISDIR;
}
if (total == 0) {
return 0;
}
if (file->special == SpecialFile::Random) {
for (const auto& buffer: buffers) {
if (buffer.iov_len != 0) {
Memory::InvalidateMemory(reinterpret_cast<uint64_t>(buffer.iov_base),
buffer.iov_len);
FillRandomBuffer(buffer.iov_base, buffer.iov_len);
}
}
return static_cast<int64_t>(total);
}
if (file->f.IsInvalid()) {
return KERNEL_ERROR_EIO;
}
const auto position = file->f.Tell();
const auto file_size = file->f.Size();
if (!file->f.Seek(static_cast<uint64_t>(offset))) {
return KERNEL_ERROR_EIO;
}
auto remaining = static_cast<uint64_t>(offset) < file_size ? file_size - offset : 0;
int64_t bytes_read = 0;
for (const auto& buffer: buffers) {
if (remaining == 0) {
break;
}
if (buffer.iov_len == 0) {
continue;
}
const auto count = static_cast<uint32_t>(std::min<uint64_t>(buffer.iov_len, remaining));
Memory::InvalidateMemory(reinterpret_cast<uint64_t>(buffer.iov_base), count);
uint32_t bytes = 0;
file->f.Read(buffer.iov_base, count, &bytes);
bytes_read += bytes;
remaining -= bytes;
if (bytes < count) {
break;
}
}
if (!file->f.Seek(position)) {
return KERNEL_ERROR_EIO;
}
LOGF("\tReadv %" PRId64 " bytes (pos = %" PRId64 ", iovcnt = %d) from: %s\n", bytes_read,
offset, iovcnt, Common::PathToString(file->real_name).c_str());
return bytes_read;
}
int64_t KYTY_SYSV_ABI KernelPwrite(int d, const void* buf, size_t nbytes, int64_t offset) {
PRINT_NAME();
@@ -814,6 +917,72 @@ int64_t KYTY_SYSV_ABI KernelPwrite(int d, const void* buf, size_t nbytes, int64_
return bytes_written;
}
int64_t KYTY_SYSV_ABI KernelPwritev(int d, const KernelIovec* iov, int iovcnt, int64_t offset) {
PRINT_NAME();
std::vector<KernelIovec> buffers;
size_t total = 0;
const int error = ValidateIovecs(iov, iovcnt, offset, &buffers, &total);
if (error != OK) {
return error;
}
if (d < DESCRIPTOR_MIN) {
return KERNEL_ERROR_EBADF;
}
if (::Libs::Network::Net::IsSocket(d)) {
return KERNEL_ERROR_ESPIPE;
}
auto* file = g_files->GetFile(d);
if (file == nullptr || !file->opened || !file->writable) {
return KERNEL_ERROR_EBADF;
}
Common::LockGuard lock(file->mutex);
if (file->directory) {
return KERNEL_ERROR_EISDIR;
}
if (file->special != SpecialFile::None) {
return KERNEL_ERROR_EINVAL;
}
if (total == 0) {
return 0;
}
if (file->f.IsInvalid()) {
return KERNEL_ERROR_EIO;
}
const auto position = file->f.Tell();
const auto target = (file->append ? file->f.Size() : static_cast<uint64_t>(offset));
if (target > static_cast<uint64_t>(INT64_MAX) - total) {
return KERNEL_ERROR_EFBIG;
}
if (!file->f.Seek(target)) {
return KERNEL_ERROR_EIO;
}
int64_t bytes_written = 0;
for (const auto& buffer: buffers) {
if (buffer.iov_len == 0) {
continue;
}
uint32_t bytes = 0;
file->f.Write(buffer.iov_base, static_cast<uint32_t>(buffer.iov_len), &bytes);
bytes_written += bytes;
if (bytes < buffer.iov_len) {
break;
}
}
const bool flushed = !file->sync_writes || file->f.Flush();
const bool restored = file->f.Seek(position);
if (!flushed || !restored) {
return KERNEL_ERROR_EIO;
}
LOGF("\tWritev %" PRId64 " bytes (pos = %" PRId64 ", iovcnt = %d) to: %s\n", bytes_written,
offset, iovcnt, Common::PathToString(file->real_name).c_str());
return bytes_written;
}
int64_t KYTY_SYSV_ABI KernelLseek(int d, int64_t offset, int whence) {
PRINT_NAME();
+11
View File
@@ -6,6 +6,7 @@
#include "common/stringUtils.h"
#include "kernel/pthread.h"
#include <cstddef>
#include <filesystem>
namespace Libs::LibKernel::FileSystem {
@@ -32,6 +33,14 @@ struct FileStat {
unsigned int: (8 / 2) * (16 - static_cast<int>(sizeof(KernelTimespec)));
};
struct KernelIovec {
void* iov_base;
size_t iov_len;
};
static_assert(sizeof(KernelIovec) == 16);
static_assert(offsetof(KernelIovec, iov_len) == 8);
void Initialize();
void Shutdown();
void EmergencyShutdown();
@@ -51,8 +60,10 @@ int KYTY_SYSV_ABI KernelOpen(const char* path, int flags, uint16_t mode);
int KYTY_SYSV_ABI KernelClose(int d);
int64_t KYTY_SYSV_ABI KernelRead(int d, void* buf, size_t nbytes);
int64_t KYTY_SYSV_ABI KernelPread(int d, void* buf, size_t nbytes, int64_t offset);
int64_t KYTY_SYSV_ABI KernelPreadv(int d, const KernelIovec* iov, int iovcnt, int64_t offset);
int64_t KYTY_SYSV_ABI KernelWrite(int d, const void* buf, size_t nbytes);
int64_t KYTY_SYSV_ABI KernelPwrite(int d, const void* buf, size_t nbytes, int64_t offset);
int64_t KYTY_SYSV_ABI KernelPwritev(int d, const KernelIovec* iov, int iovcnt, int64_t offset);
int64_t KYTY_SYSV_ABI KernelLseek(int d, int64_t offset, int whence);
int KYTY_SYSV_ABI KernelStat(const char* path, FileStat* sb);
int KYTY_SYSV_ABI KernelFstat(int d, FileStat* sb);
+83 -112
View File
@@ -200,6 +200,10 @@ static bool IsPrivateCommittedRangeType(VirtualRangeType type) {
type == VirtualRangeType::Runtime;
}
#if defined(KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS)
static bool g_test_fail_next_range_replace = false;
#endif
class VirtualRanges {
public:
struct Range {
@@ -288,36 +292,22 @@ public:
return true;
}
bool ConsumeReserved(uint64_t start, uint64_t size,
VirtualRangeType type = VirtualRangeType::Reserved) {
bool ReplaceSpan(uint64_t start, uint64_t size, VirtualRangeType expected_type,
uint64_t offset, int protection, int memory_type, VirtualRangeType type,
const char* name, bool disallow_merge = false) {
Common::LockGuard lock(m_mutex);
auto end = End(start, size);
for (const auto& r: m_ranges) {
if (r.type == type && start >= r.start && end <= End(r.start, r.size)) {
RemoveUnlocked(start, size);
MergeUnlocked();
return true;
}
}
return false;
}
bool ConsumeReservedSpan(uint64_t start, uint64_t size, Range* first_range = nullptr,
VirtualRangeType type = VirtualRangeType::Reserved) {
Common::LockGuard lock(m_mutex);
if (size == 0) {
if (start == 0 || size == 0 || size > UINT64_MAX - start) {
return false;
}
auto current = start;
auto end = End(start, size);
const auto end = start + size;
while (current < end) {
const Range* candidate = nullptr;
for (const auto& r: m_ranges) {
if (r.type == type && current >= r.start && current < End(r.start, r.size)) {
if (r.type == expected_type && current >= r.start &&
current < End(r.start, r.size)) {
candidate = &r;
break;
}
@@ -325,14 +315,29 @@ public:
if (candidate == nullptr) {
return false;
}
if (current == start && first_range != nullptr) {
*first_range = *candidate;
}
current = std::min(end, End(candidate->start, candidate->size));
}
#if defined(KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS)
if (g_test_fail_next_range_replace) {
g_test_fail_next_range_replace = false;
return false;
}
#endif
Range replacement {};
replacement.start = start;
replacement.size = size;
replacement.offset = offset;
replacement.protection = protection;
replacement.memory_type = memory_type;
replacement.type = type;
replacement.disallow_merge = disallow_merge;
CopyVirtualRangeName(replacement.name, name);
RemoveUnlocked(start, size);
MergeUnlocked();
auto position = LowerBound(start);
const auto index = static_cast<size_t>(position - m_ranges.begin());
m_ranges.insert(position, replacement);
MergeAroundUnlocked(index);
return true;
}
@@ -2250,10 +2255,9 @@ int32_t KYTY_SYSV_ABI KernelMapNamedFlexibleMemory(void** addr_in_out, size_t le
EXIT("unknown prot: %d\n", prot);
}
auto in_addr = reinterpret_cast<uint64_t>(*addr_in_out);
uint64_t out_addr = 0;
bool consumed_reservation = false;
VirtualRanges::Range consumed_range {};
auto in_addr = reinterpret_cast<uint64_t>(*addr_in_out);
uint64_t out_addr = 0;
bool reserved_target = false;
if ((flags & GUEST_MAP_FIXED) != 0) {
if (in_addr == 0 || (in_addr & (PAGE_SIZE - 1)) != 0 ||
@@ -2269,16 +2273,12 @@ int32_t KYTY_SYSV_ABI KernelMapNamedFlexibleMemory(void** addr_in_out, size_t le
return range.type == VirtualRangeType::Reserved;
})) {
UnmapGpuRange(in_addr, len);
consumed_range = reserved_ranges.front();
if (g_virtual_ranges->ConsumeReservedSpan(in_addr, len)) {
consumed_reservation = true;
out_addr = in_addr;
}
reserved_target = true;
out_addr = in_addr;
}
if (!consumed_reservation && ReplaceFixedRangeWithReserved(in_addr, len) &&
g_virtual_ranges->ConsumeReservedSpan(in_addr, len, &consumed_range)) {
consumed_reservation = true;
out_addr = in_addr;
if (!reserved_target && ReplaceFixedRangeWithReserved(in_addr, len)) {
reserved_target = true;
out_addr = in_addr;
}
} else {
const auto search_addr = (in_addr != 0 ? in_addr : DEFAULT_PS5_BASE);
@@ -2291,30 +2291,25 @@ int32_t KYTY_SYSV_ABI KernelMapNamedFlexibleMemory(void** addr_in_out, size_t le
*addr_in_out = reinterpret_cast<void*>(out_addr);
if (out_addr == 0) {
if (consumed_reservation) {
g_virtual_ranges->Add(in_addr, len, 0, 0, 0, VirtualRangeType::Reserved,
consumed_range.name);
}
return KERNEL_ERROR_ENOMEM;
}
if (!g_flexible_memory->Map(out_addr, len, prot, mode, gpu_mode, name)) {
LOGF_COLOR(Log::Color::Red, "\t [Fail]\n");
if (consumed_reservation) {
EXIT_IF(!g_virtual_ranges->Add(out_addr, len, 0, 0, 0, VirtualRangeType::Reserved,
consumed_range.name));
}
return KERNEL_ERROR_ENOMEM;
}
if (!g_virtual_ranges->Add(out_addr, len, 0, prot, 0, VirtualRangeType::Flexible, name,
(map_flags & GUEST_MAP_NO_COALESCE) != 0)) {
const bool published = reserved_target
? g_virtual_ranges->ReplaceSpan(
out_addr, len, VirtualRangeType::Reserved, 0, prot, 0,
VirtualRangeType::Flexible, name,
(map_flags & GUEST_MAP_NO_COALESCE) != 0)
: g_virtual_ranges->Add(out_addr, len, 0, prot, 0,
VirtualRangeType::Flexible, name,
(map_flags & GUEST_MAP_NO_COALESCE) != 0);
if (!published) {
GpuAccessMode rollback_gpu_mode = GpuAccessMode::NoAccess;
EXIT_IF(!g_flexible_memory->Unmap(out_addr, len, &rollback_gpu_mode));
if (consumed_reservation) {
EXIT_IF(!g_virtual_ranges->Add(out_addr, len, 0, 0, 0, VirtualRangeType::Reserved,
consumed_range.name));
}
return KERNEL_ERROR_EBUSY;
}
@@ -2946,25 +2941,17 @@ int KYTY_SYSV_ABI KernelMapDirectMemory(void** addr, size_t len, int prot, int f
return KERNEL_ERROR_ENOMEM;
}
auto in_addr = reinterpret_cast<uint64_t>(*addr);
uint64_t out_addr = 0;
bool shared_backing = false;
bool consumed_reservation = false;
VirtualRanges::Range consumed_range {};
auto shared_failure = GuestBackingStore::FailureReason::None;
auto in_addr = reinterpret_cast<uint64_t>(*addr);
uint64_t out_addr = 0;
bool shared_backing = false;
bool reserved_target = false;
auto shared_failure = GuestBackingStore::FailureReason::None;
// Direct mappings must remain views of the single backing object. Anonymous fallbacks break
// aliasing and lose direct-memory contents when a range is unmapped and mapped again.
auto map_shared_fixed = [&](uint64_t target_addr) -> bool {
return g_guest_address_space->MapBacking(target_addr, len, direct_memory_start, mode,
&shared_failure);
};
auto map_consumed_reserved_fixed = [&]() {
if (map_shared_fixed(in_addr)) {
out_addr = in_addr;
shared_backing = true;
}
};
if (fixed) {
if (in_addr == 0 || (in_addr & (PAGE_SIZE - 1u)) != 0 ||
(alignment != 0 && in_addr % alignment != 0)) {
@@ -2980,20 +2967,18 @@ int KYTY_SYSV_ABI KernelMapDirectMemory(void** addr, size_t len, int prot, int f
return range.type == VirtualRangeType::Reserved;
})) {
UnmapGpuRange(in_addr, len);
consumed_range = reserved_ranges.front();
if (g_virtual_ranges->ConsumeReservedSpan(in_addr, len)) {
consumed_reservation = true;
map_consumed_reserved_fixed();
}
reserved_target = true;
}
if (!consumed_reservation && ReplaceFixedRangeWithReserved(in_addr, len) &&
g_virtual_ranges->ConsumeReservedSpan(in_addr, len, &consumed_range)) {
consumed_reservation = true;
map_consumed_reserved_fixed();
if (!reserved_target && ReplaceFixedRangeWithReserved(in_addr, len)) {
reserved_target = true;
}
if (!consumed_reservation) {
if (!reserved_target) {
return KERNEL_ERROR_ENOMEM;
}
if (map_shared_fixed(in_addr)) {
out_addr = in_addr;
shared_backing = true;
}
} else {
constexpr size_t DEFAULT_ALIGNMENT = 0x4000;
alignment = (alignment != 0 ? alignment : DEFAULT_ALIGNMENT);
@@ -3003,16 +2988,13 @@ int KYTY_SYSV_ABI KernelMapDirectMemory(void** addr, size_t len, int prot, int f
return range.type == VirtualRangeType::Reserved;
})) {
UnmapGpuRange(in_addr, len);
consumed_range = reserved_ranges.front();
if (g_virtual_ranges->ConsumeReservedSpan(in_addr, len)) {
consumed_reservation = true;
if (map_shared_fixed(in_addr)) {
out_addr = in_addr;
shared_backing = true;
}
reserved_target = true;
if (map_shared_fixed(in_addr)) {
out_addr = in_addr;
shared_backing = true;
}
}
if (!consumed_reservation) {
if (!reserved_target) {
out_addr = FindGuestFreeRange(in_addr, len, alignment);
if (out_addr != 0) {
UnmapGpuRange(out_addr, len);
@@ -3046,34 +3028,28 @@ int KYTY_SYSV_ABI KernelMapDirectMemory(void** addr, size_t len, int prot, int f
magic_enum::enum_name(gpu_mode), shared_backing ? "yes" : "no", shared_reason);
if (out_addr == 0) {
if (consumed_reservation) {
g_virtual_ranges->Add(in_addr, len, 0, 0, 0, VirtualRangeType::Reserved,
consumed_range.name);
}
return KERNEL_ERROR_ENOMEM;
}
if (!g_physical_memory->Map(out_addr, direct_memory_start, len, prot, mode, gpu_mode)) {
LOGF_COLOR(Log::Color::Red, "\t [Fail]\n");
EXIT_IF(!g_guest_address_space->UnmapBacking(out_addr, len));
if (consumed_reservation) {
EXIT_IF(!g_virtual_ranges->Add(in_addr, len, 0, 0, 0, VirtualRangeType::Reserved,
consumed_range.name));
}
return KERNEL_ERROR_EBUSY;
}
PhysicalMemory::AllocatedBlock mapped_block {};
g_physical_memory->Find(direct_memory_start, false, &mapped_block);
if (!g_virtual_ranges->Add(out_addr, len, direct_memory_start, prot, mapped_block.memory_type,
VirtualRangeType::Direct, "")) {
const bool published = reserved_target
? g_virtual_ranges->ReplaceSpan(
out_addr, len, VirtualRangeType::Reserved, direct_memory_start,
prot, mapped_block.memory_type, VirtualRangeType::Direct, "")
: g_virtual_ranges->Add(out_addr, len, direct_memory_start, prot,
mapped_block.memory_type,
VirtualRangeType::Direct, "");
if (!published) {
GpuAccessMode rollback_gpu_mode = GpuAccessMode::NoAccess;
EXIT_IF(!g_physical_memory->Unmap(out_addr, len, &rollback_gpu_mode));
EXIT_IF(!g_guest_address_space->UnmapBacking(out_addr, len));
if (consumed_reservation) {
EXIT_IF(!g_virtual_ranges->Add(in_addr, len, 0, 0, 0, VirtualRangeType::Reserved,
consumed_range.name));
}
return KERNEL_ERROR_EBUSY;
}
@@ -3442,6 +3418,10 @@ void TestFailNextFixedReserveRangeRegistration() {
g_test_fail_next_fixed_reserve_range_add = true;
}
void TestFailNextVirtualRangeReplacement() {
g_test_fail_next_range_replace = true;
}
bool TestPlaceholderRangeIsFree(uint64_t vaddr, uint64_t size) {
return g_guest_address_space->TestContainsFree(vaddr, size);
}
@@ -3965,12 +3945,8 @@ int KYTY_SYSV_ABI KernelMemoryPoolReserve(void* addr_in, size_t len, size_t alig
!IsReservedRangeType(reserved_range.type)) {
return KERNEL_ERROR_EBUSY;
}
g_virtual_ranges->Remove(out_vaddr, len);
if (!g_virtual_ranges->Add(out_vaddr, len, 0, 0, 0, VirtualRangeType::PoolReserved,
reserved_range.name)) {
g_virtual_ranges->Add(out_vaddr, len, reserved_range.offset, reserved_range.protection,
reserved_range.memory_type, reserved_range.type,
reserved_range.name);
if (!g_virtual_ranges->ReplaceSpan(out_vaddr, len, VirtualRangeType::Reserved, 0, 0, 0,
VirtualRangeType::PoolReserved, reserved_range.name)) {
return KERNEL_ERROR_EBUSY;
}
*addr_out = out_addr;
@@ -4011,17 +3987,13 @@ int KYTY_SYSV_ABI KernelMemoryPoolCommit(void* addr, size_t len, int type, int p
VirtualRanges::Range old_range {};
if (!g_virtual_ranges->Query(vaddr, 0, &old_range) ||
old_range.type != VirtualRangeType::PoolReserved) {
return KERNEL_ERROR_EACCES;
}
if (!g_virtual_ranges->ConsumeReserved(vaddr, len, VirtualRangeType::PoolReserved)) {
old_range.type != VirtualRangeType::PoolReserved ||
len > old_range.size - (vaddr - old_range.start)) {
return KERNEL_ERROR_EACCES;
}
std::vector<PooledMemory::Mapping> mappings;
if (!g_pooled_memory->Allocate(vaddr, len, gpu_mode, &mappings)) {
g_virtual_ranges->Add(vaddr, len, 0, 0, 0, VirtualRangeType::PoolReserved, old_range.name);
return KERNEL_ERROR_ENOMEM;
}
@@ -4034,7 +4006,6 @@ int KYTY_SYSV_ABI KernelMemoryPoolCommit(void* addr, size_t len, int type, int p
if (!g_pooled_memory->Release(vaddr, len, &rollback_gpu_mode)) {
EXIT("failed to release pooled-memory rollback allocation\n");
}
g_virtual_ranges->Add(vaddr, len, 0, 0, 0, VirtualRangeType::PoolReserved, old_range.name);
};
for (const auto& mapping: mappings) {
@@ -4050,8 +4021,8 @@ int KYTY_SYSV_ABI KernelMemoryPoolCommit(void* addr, size_t len, int type, int p
mapped.push_back(mapping);
}
if (!g_virtual_ranges->Add(vaddr, len, 0, prot, type, VirtualRangeType::Pooled,
old_range.name)) {
if (!g_virtual_ranges->ReplaceSpan(vaddr, len, VirtualRangeType::PoolReserved, 0, prot, type,
VirtualRangeType::Pooled, old_range.name)) {
rollback();
return KERNEL_ERROR_EBUSY;
}
+1
View File
@@ -190,6 +190,7 @@ void TestFailNextPhysicalMemoryUnmap();
void TestFailPhysicalMemoryUnmapAfter(uint32_t successful_unmaps);
void TestFailGuestBackingStoreUnmapAfter(uint32_t successful_unmaps);
void TestFailNextFixedReserveRangeRegistration();
void TestFailNextVirtualRangeReplacement();
bool TestPlaceholderRangeIsFree(uint64_t vaddr, uint64_t size);
bool TestGuestAddressRangeIsOwned(uint64_t vaddr, uint64_t size);
bool TestGuestBackingOutsideAddressSpace();
+1 -1
View File
@@ -20,7 +20,7 @@ add_executable(launcher WIN32
)
target_link_libraries(launcher Qt6::Concurrent Qt6::Network Qt6::Widgets Vulkan::Headers SDL2-static kyty_git_version)
target_link_libraries(launcher Qt6::Concurrent Qt6::Network Qt6::Widgets Vulkan::Headers SDL3::SDL3-static kyty_git_version)
if(WIN32)
target_link_libraries(launcher setupapi)
endif()
@@ -44,6 +44,28 @@
<property name="spacing">
<number>4</number>
</property>
<item>
<widget class="QToolButton" name="refresh_button">
<property name="minimumSize">
<size>
<width>36</width>
<height>36</height>
</size>
</property>
<property name="text">
<string/>
</property>
<property name="iconSize">
<size>
<width>32</width>
<height>32</height>
</size>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="global_settings_button">
<property name="minimumSize">
@@ -245,6 +267,14 @@
</layout>
</item>
</layout>
<action name="refresh_action">
<property name="text">
<string>Refresh game list</string>
</property>
<property name="toolTip">
<string>Rescan game folders for new games</string>
</property>
</action>
</widget>
<customwidgets>
<customwidget>
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="#000" d="M17.65 6.35A7.96 7.96 0 0 0 12 4a8 8 0 1 0 7.73 10h-2.08A6 6 0 1 1 12 6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"/></svg>

After

Width:  |  Height:  |  Size: 232 B

+1 -3
View File
@@ -144,7 +144,7 @@ public:
host_input_mapping = other.host_input_mapping;
}
void CopyFrom(const Configuration& other) {
void CopyGameInfoFrom(const Configuration& other) {
name = other.name;
title_id = other.title_id;
gameVersion = other.gameVersion;
@@ -154,8 +154,6 @@ public:
custom_settings = other.custom_settings;
game_status = other.game_status;
game_comment = other.game_comment;
CopyEmulatorSettingsFrom(other);
elf = other.elf;
}
void WriteSettings(QSettings* s) const {
@@ -8,6 +8,8 @@
#include <QStringList>
#include <QWidget>
#include <memory>
class ConfigurationItem;
class CompatibilityDatabase;
class QEvent;
@@ -30,10 +32,9 @@ public:
[[nodiscard]] const ConfigurationItem* GetSelectedItem() const { return m_selected_item; }
ConfigurationItem* GetSelectedItem() { return m_selected_item; }
[[nodiscard]] const QString& GetSettingsFile() const { return m_settings_file; }
[[nodiscard]] const QStringList& GetHostInputMapping() const {
return m_global_info.host_input_mapping;
}
[[nodiscard]] const QString& GetSettingsFile() const { return m_settings_file; }
[[nodiscard]] std::unique_ptr<Configuration>
CreateConfiguration(const ConfigurationItem& item) const;
bool EnsureGameDirectory();
void ScanGameDirectory();
@@ -64,7 +65,6 @@ protected slots:
void filter_configurations(const QString& text);
private:
void ClearCustomSettings(ConfigurationItem* item);
void SelectItem(QTreeWidgetItem* witem);
void ApplyCompatibility();
void UpdateToolbarIcons();
+1
View File
@@ -3,6 +3,7 @@
<file alias="edit-configuration.svg">icons/edit-configuration.svg</file>
<file alias="global-settings.svg">icons/global-settings.svg</file>
<file alias="input-mapping.svg">icons/input-mapping.svg</file>
<file alias="refresh.svg">icons/refresh.svg</file>
<file alias="remove-configuration.svg">icons/remove-configuration.svg</file>
</qresource>
<qresource prefix="/styles">
+6 -5
View File
@@ -3,7 +3,7 @@
#include "common/emulatorConfig.h"
#include "configuration.h"
#include "mandatoryLineEdit.h"
#include "SDL.h"
#include <SDL3/SDL.h>
#include <QAbstractItemView>
#include <QCheckBox>
@@ -173,17 +173,18 @@ void ConfigurationEditDialog::Init(const Configuration& info) {
microphone->clear();
microphone->addItem(tr("None"), QString {});
microphone->setToolTip(tr("Microphone used by games. None supplies silence."));
SDL_SetMainReady();
if (SDL_InitSubSystem(SDL_INIT_AUDIO) == 0) {
const int device_count = SDL_GetNumAudioDevices(SDL_TRUE);
if (SDL_InitSubSystem(SDL_INIT_AUDIO)) {
int device_count = 0;
SDL_AudioDeviceID* devices = SDL_GetAudioRecordingDevices(&device_count);
for (int i = 0; i < device_count; i++) {
if (const auto* device = SDL_GetAudioDeviceName(i, SDL_TRUE); device != nullptr) {
if (const auto* device = SDL_GetAudioDeviceName(devices[i]); device != nullptr) {
const auto name = QString::fromUtf8(device);
if (microphone->findData(name) < 0) {
microphone->addItem(name, name);
}
}
}
SDL_free(devices);
SDL_QuitSubSystem(SDL_INIT_AUDIO);
} else {
microphone->setToolTip(tr("Microphones could not be listed: %1")
+102 -42
View File
@@ -13,6 +13,7 @@
#include <QAbstractItemModel>
#include <QAbstractItemView>
#include <QAction>
#include <QApplication>
#include <QComboBox>
#include <QCoreApplication>
#include <QCursor>
@@ -26,11 +27,13 @@
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonParseError>
#include <QKeySequence>
#include <QLineEdit>
#include <QMenu>
#include <QMessageBox>
#include <QPainter>
#include <QPalette>
#include <QPointer>
#include <QPushButton>
#include <QRegularExpression>
#include <QSet>
@@ -201,6 +204,10 @@ ConfigurationListWidget::ConfigurationListWidget(QWidget* parent)
m_ui->setupUi(this);
ConfigureGameList(m_ui);
m_ui->refresh_action->setShortcuts(QKeySequence::Refresh);
m_ui->refresh_button->setDefaultAction(m_ui->refresh_action);
addAction(m_ui->refresh_action);
UpdateToolbarIcons();
m_ui->global_settings_button->setToolTip(tr("Edit global settings and game folders"));
m_ui->input_mapping_button->setToolTip(tr("Edit global input mapping"));
@@ -221,7 +228,10 @@ ConfigurationListWidget::ConfigurationListWidget(QWidget* parent)
m_ui->cfgs_list->setColumnWidth(GAME_PATH_COLUMN, 320);
m_ui->cfgs_list->setColumnWidth(GAME_STATUS_COLUMN, 150);
m_ui->cfgs_list->setColumnWidth(GAME_COMMENT_COLUMN, 240);
m_ui->cfgs_list->sortItems(GAME_NAME_COLUMN, Qt::AscendingOrder);
connect(m_ui->refresh_action, &QAction::triggered, this,
&ConfigurationListWidget::ScanGameDirectory);
connect(m_ui->global_settings_button, &QToolButton::clicked, this,
&ConfigurationListWidget::edit_global_settings);
connect(m_ui->input_mapping_button, &QToolButton::clicked, this,
@@ -276,6 +286,8 @@ void ConfigurationListWidget::UpdateToolbarIcons() {
button->setIcon(QIcon(pixmap));
};
set_icon(m_ui->refresh_button, QStringLiteral(":/icons/refresh.svg"));
m_ui->refresh_action->setIcon(m_ui->refresh_button->icon());
set_icon(m_ui->global_settings_button, QStringLiteral(":/icons/global-settings.svg"));
set_icon(m_ui->input_mapping_button, QStringLiteral(":/icons/input-mapping.svg"));
set_icon(m_ui->edit_button, QStringLiteral(":/icons/edit-configuration.svg"));
@@ -398,10 +410,17 @@ void ConfigurationListWidget::ApplyCompatibility() {
}
}
static Configuration* CloneConfiguration(const Configuration& source) {
auto* ret = new Configuration;
ret->CopyFrom(source);
return ret;
std::unique_ptr<Configuration>
ConfigurationListWidget::CreateConfiguration(const ConfigurationItem& item) const {
auto info = std::make_unique<Configuration>();
const auto* custom = m_custom_infos.value(item.GetInfo().game_path);
info->CopyGameInfoFrom(item.GetInfo());
info->CopyEmulatorSettingsFrom(custom != nullptr ? *custom : m_global_info);
if (custom != nullptr && !custom->elf.isEmpty()) {
info->elf = custom->elf;
}
info->host_input_mapping = m_global_info.host_input_mapping;
return info;
}
struct GameMetadata {
@@ -516,9 +535,6 @@ static void SetGameFiles(Configuration& info, const QString& game_dir, const QSt
if (info.name.isEmpty()) {
info.name = game.dirName();
}
if (info.elf.isEmpty()) {
info.elf = QStringLiteral("eboot.bin");
}
}
static Configuration* FindCustomInfo(QMap<QString, Configuration*>* custom_infos,
@@ -564,11 +580,32 @@ bool ConfigurationListWidget::HasValidGameDirectory() const {
}
void ConfigurationListWidget::ScanGameDirectory() {
// Commit an active inline editor before destroying its row or replacing its data.
if (auto* focused = QApplication::focusWidget();
focused != nullptr && m_ui->cfgs_list->isAncestorOf(focused)) {
m_ui->cfgs_list->setFocus();
}
const auto selected_path =
m_selected_item != nullptr ? m_selected_item->GetInfo().game_path : QString();
const bool sorting_enabled = m_ui->cfgs_list->isSortingEnabled();
const int sort_column = m_ui->cfgs_list->sortColumn();
const auto sort_order = m_ui->cfgs_list->header()->sortIndicatorOrder();
const QSignalBlocker blocker(m_ui->cfgs_list);
m_ui->cfgs_list->setSortingEnabled(false);
m_selected_item = nullptr;
m_ui->cfgs_list->clear();
m_ui->cfgs_list->SetBackgroundImage({});
m_ui->edit_button->setEnabled(false);
m_ui->delete_button->setEnabled(false);
// MainDialog tracks the running item, so keep its identity even if its
// directory is no longer present. Other rows can be rebuilt from disk.
QMap<QString, ConfigurationItem*> running_items;
for (int index = m_ui->cfgs_list->topLevelItemCount() - 1; index >= 0; index--) {
auto* item = static_cast<ConfigurationItem*>(m_ui->cfgs_list->topLevelItem(index));
if (item->IsRunning()) {
running_items.insert(PathKey(item->GetInfo().game_path), item);
} else {
delete item;
}
}
const QString eboot_name = QStringLiteral("eboot.bin");
QSet<QString> found_games;
@@ -601,16 +638,9 @@ void ConfigurationListWidget::ScanGameDirectory() {
auto metadata = GetGameMetadata(
game_dir.filePath(QStringLiteral("sce_sys/param.json")), game_dir.dirName());
auto info = std::make_unique<Configuration>();
auto* custom = FindCustomInfo(&m_custom_infos, game_path, legacy_game_path);
if (custom != nullptr) {
info->CopyFrom(*custom);
info->custom_settings = true;
} else {
info->CopyEmulatorSettingsFrom(m_global_info);
info->custom_settings = false;
}
auto info = std::make_unique<Configuration>();
info->custom_settings =
FindCustomInfo(&m_custom_infos, game_path, legacy_game_path) != nullptr;
SetGameFiles(*info, game_dir.absolutePath(), game_path, metadata);
const auto* compatibility = m_compatibility->Find(info->title_id);
if (compatibility != nullptr) {
@@ -618,10 +648,20 @@ void ConfigurationListWidget::ScanGameDirectory() {
info->game_comment = compatibility->comment;
}
if (auto* item = running_items.value(game_key); item != nullptr) {
// Refresh metadata without losing the running row's identity.
const QSignalBlocker status_blocker(item->GetStatusCombo());
item->GetInfo().CopyGameInfoFrom(*info);
item->Update();
item->SetCompatibilityEditable(m_compatibility->IsLocal() &&
!item->GetInfo().title_id.trimmed().isEmpty());
continue;
}
auto* item = new ConfigurationItem(std::move(info), m_ui->cfgs_list);
item->SetCompatibilityEditable(m_compatibility->IsLocal() &&
!item->GetInfo().title_id.trimmed().isEmpty());
connect(item->GetStatusCombo(), &QComboBox::currentIndexChanged, this,
connect(item->GetStatusCombo(), &QComboBox::currentIndexChanged, item,
[this, item](int /*index*/) {
if (!m_compatibility->IsLocal()) {
return;
@@ -638,7 +678,7 @@ void ConfigurationListWidget::ScanGameDirectory() {
m_ui->cfgs_list->setCurrentItem(item);
SelectItem(item);
});
connect(item->GetCommentEdit(), &QLineEdit::editingFinished, this, [this, item]() {
connect(item->GetCommentEdit(), &QLineEdit::editingFinished, item, [this, item]() {
if (!m_compatibility->IsLocal()) {
return;
}
@@ -664,8 +704,22 @@ void ConfigurationListWidget::ScanGameDirectory() {
}
}
m_ui->cfgs_list->sortItems(GAME_NAME_COLUMN, Qt::AscendingOrder);
m_ui->cfgs_list->setSortingEnabled(sorting_enabled);
if (sorting_enabled) {
m_ui->cfgs_list->sortItems(sort_column, sort_order);
}
filter_configurations(m_ui->search_line_edit->text());
ConfigurationItem* selected_item = nullptr;
for (int index = 0; index < m_ui->cfgs_list->topLevelItemCount(); index++) {
auto* item = static_cast<ConfigurationItem*>(m_ui->cfgs_list->topLevelItem(index));
if (!item->isHidden() && item->GetInfo().game_path == selected_path) {
selected_item = item;
break;
}
}
m_ui->cfgs_list->setCurrentItem(selected_item);
SelectItem(selected_item);
}
void ConfigurationListWidget::edit_configuration() {
@@ -674,16 +728,19 @@ void ConfigurationListWidget::edit_configuration() {
return;
}
ConfigurationEditDialog dlg(item->GetInfo(), this);
auto info = CreateConfiguration(*item);
ConfigurationEditDialog dlg(*info, this);
dlg.setWindowTitle(tr("Edit game settings"));
if (dlg.exec() == QDialog::Accepted) {
info->custom_settings = true;
item->GetInfo().custom_settings = true;
auto game_path = item->GetInfo().game_path;
auto game_path = info->game_path;
delete m_custom_infos.take(game_path);
m_custom_infos.insert(game_path, CloneConfiguration(item->GetInfo()));
m_custom_infos.insert(game_path, info.release());
WriteSettings();
ScanGameDirectory();
item->Update();
SelectItem(item);
}
}
@@ -695,9 +752,11 @@ void ConfigurationListWidget::delete_configuartion() {
if (QMessageBox::Yes == QMessageBox::question(this, tr("Clear custom settings"),
tr("Do you want to clear custom settings?"))) {
ClearCustomSettings(item);
delete m_custom_infos.take(item->GetInfo().game_path);
item->GetInfo().custom_settings = false;
WriteSettings();
ScanGameDirectory();
item->Update();
SelectItem(item);
}
}
@@ -712,9 +771,13 @@ void ConfigurationListWidget::edit_global_settings() {
if (dlg.exec() == QDialog::Accepted) {
m_global_info.CopyEmulatorSettingsFrom(info);
m_game_dirs = NormalizeGameDirectories(dlg.GetGameDirectories());
const auto game_dirs = NormalizeGameDirectories(dlg.GetGameDirectories());
const bool game_dirs_changed = game_dirs != m_game_dirs;
m_game_dirs = game_dirs;
WriteSettings();
ScanGameDirectory();
if (game_dirs_changed) {
ScanGameDirectory();
}
}
}
@@ -726,10 +789,6 @@ void ConfigurationListWidget::edit_input_mapping() {
}
}
void ConfigurationListWidget::ClearCustomSettings(ConfigurationItem* item) {
delete m_custom_infos.take(item->GetInfo().game_path);
}
void ConfigurationListWidget::ViewTrophies() {
auto* current = static_cast<ConfigurationItem*>(m_ui->cfgs_list->currentItem());
auto* item = current != nullptr ? current : m_selected_item;
@@ -871,12 +930,13 @@ void ConfigurationListWidget::show_context_menu(const QPoint& pos) {
connect(action_view_trophies, &QAction::triggered, this,
&ConfigurationListWidget::ViewTrophies);
QAction* action_patches = menu.addAction(tr("Cheats (experimental)..."));
connect(action_patches, &QAction::triggered, this, [this, item]() {
if (item != nullptr) {
auto* dialog = new PatchesDialog(item->GetInfo(), this);
dialog->show();
}
});
connect(action_patches, &QAction::triggered, this,
[this, item = QPointer<ConfigurationItem>(item)]() {
if (item != nullptr) {
auto* dialog = new PatchesDialog(item->GetInfo(), this);
dialog->show();
}
});
action_patches->setVisible(item != nullptr &&
PatchesDialog::IsSupportedTitleId(item->GetInfo().title_id));
QAction* action_remove_save_data =
+4 -4
View File
@@ -90,6 +90,7 @@ QString KeyName(const QKeyEvent& event) {
}
switch (key) {
case Qt::Key_Space: return QStringLiteral("Space");
case Qt::Key_Return:
case Qt::Key_Enter: return QStringLiteral("Return");
case Qt::Key_Backspace: return QStringLiteral("Backspace");
@@ -132,8 +133,7 @@ public:
auto* layout = new QVBoxLayout(this);
m_label = new QLabel(
tr("Press a key or mouse button.\nSpace, F1, F7, and F11 are reserved; Esc cancels."),
this);
tr("Press a key or mouse button.\nF1, F7, and F11 are reserved; Esc cancels."), this);
m_label->setAlignment(Qt::AlignCenter);
layout->addWidget(m_label);
}
@@ -149,8 +149,8 @@ protected:
reject();
return;
}
if (event->key() == Qt::Key_Space || event->key() == Qt::Key_F1 ||
event->key() == Qt::Key_F7 || event->key() == Qt::Key_F11) {
if (event->key() == Qt::Key_F1 || event->key() == Qt::Key_F7 ||
event->key() == Qt::Key_F11) {
m_label->setText(tr("That key is reserved by the emulator."));
return;
}
+2 -4
View File
@@ -493,10 +493,8 @@ void MainDialogPrivate::Run() {
m_running_item->SetRunning(true);
Configuration info;
info.CopyFrom(m_running_item->GetInfo());
info.host_input_mapping = m_ui->widget->GetHostInputMapping();
m_main_dialog->RunInterpreter(&m_process, info);
auto info = m_ui->widget->CreateConfiguration(*m_running_item);
m_main_dialog->RunInterpreter(&m_process, *info);
Update();
}
+177
View File
@@ -0,0 +1,177 @@
#include "libs/acm.h"
#include "common/assert.h"
#include "libs/errno.h"
#include "libs/libs.h"
#include <algorithm>
#include <atomic>
#include <cstring>
namespace Libs::Audio {
namespace Acm {
LIB_NAME("Acm", "Acm");
struct AcmBatchInfo {
void* buffer;
size_t offset;
size_t buffer_size;
};
struct AcmBatchError {
uint32_t reserved[8];
};
static std::atomic_uint32_t g_acm_next_context {1};
static std::atomic_uint32_t g_acm_next_batch {1};
static void acm_advance_batch(AcmBatchInfo* info, size_t bytes) {
if (info == nullptr || info->buffer == nullptr || info->buffer_size == 0) {
return;
}
info->offset = std::min(info->buffer_size, info->offset + bytes);
}
int KYTY_SYSV_ABI AcmContextCreate(AcmContextId* context) {
PRINT_NAME();
EXIT_NOT_IMPLEMENTED(context == nullptr);
*context = g_acm_next_context.fetch_add(1, std::memory_order_relaxed);
LOGF("\t context = %" PRIu32 "\n", *context);
return OK;
}
int KYTY_SYSV_ABI AcmContextDestroy(AcmContextId context) {
PRINT_NAME();
LOGF("\t context = %" PRIu32 "\n", context);
return OK;
}
int KYTY_SYSV_ABI AcmBatchStartBuffer(AcmContextId context, const void* batch_commands,
size_t batch_size, AcmBatchError* batch_error,
AcmBatchId* batch) {
PRINT_NAME();
EXIT_NOT_IMPLEMENTED(batch == nullptr);
if (batch_error != nullptr) {
std::memset(batch_error, 0, sizeof(AcmBatchError));
}
*batch = g_acm_next_batch.fetch_add(1, std::memory_order_relaxed);
return OK;
}
int KYTY_SYSV_ABI AcmBatchStartBuffers(AcmContextId context, uint32_t batch_info_count,
const AcmBatchInfo* const batch_info[],
AcmBatchError* batch_error, AcmBatchId* batch) {
PRINT_NAME();
EXIT_NOT_IMPLEMENTED(batch_info_count != 0 && batch_info == nullptr);
EXIT_NOT_IMPLEMENTED(batch == nullptr);
if (batch_error != nullptr) {
std::memset(batch_error, 0, sizeof(AcmBatchError));
}
*batch = g_acm_next_batch.fetch_add(1, std::memory_order_relaxed);
return OK;
}
int KYTY_SYSV_ABI AcmBatchWait(AcmContextId context, AcmBatchId batch, uint32_t timeout) {
return OK;
}
int KYTY_SYSV_ABI AcmBatchJobNotification(AcmBatchInfo* batch_info) {
PRINT_NAME();
acm_advance_batch(batch_info, 2 * 16);
return OK;
}
int KYTY_SYSV_ABI AcmConvReverbSharedInput(AcmBatchInfo* batch_info, uint32_t block_count, void* in,
uint32_t count, const void* const ir[],
const float* gain, void* const out[]) {
PRINT_NAME();
(void)block_count;
(void)in;
(void)count;
(void)ir;
(void)gain;
(void)out;
acm_advance_batch(batch_info, 1024);
return OK;
}
int KYTY_SYSV_ABI AcmConvReverbSharedIr(AcmBatchInfo* batch_info, uint32_t block_count,
const void* ir, uint32_t count, void* const in[],
const float* gain, void* const out[]) {
PRINT_NAME();
(void)block_count;
(void)ir;
(void)count;
(void)in;
(void)gain;
(void)out;
acm_advance_batch(batch_info, 1024);
return OK;
}
int KYTY_SYSV_ABI AcmFft(AcmBatchInfo* batch_info, int size, int count, int input_format,
const void* const input[], int output_format, void* const output[],
uint32_t flags) {
PRINT_NAME();
(void)size;
(void)count;
(void)input_format;
(void)input;
(void)output_format;
(void)output;
(void)flags;
acm_advance_batch(batch_info, 256);
return OK;
}
int KYTY_SYSV_ABI AcmIfft(AcmBatchInfo* batch_info, int size, int count, int input_format,
const void* const input[], int output_format, void* const output[],
uint32_t flags) {
PRINT_NAME();
(void)size;
(void)count;
(void)input_format;
(void)input;
(void)output_format;
(void)output;
(void)flags;
acm_advance_batch(batch_info, 256);
return OK;
}
int KYTY_SYSV_ABI AcmPanner(AcmBatchInfo* batch_info, uint32_t in_count, const float* const in[],
uint32_t biquad_count, uint32_t biquad_update_count, uint32_t out_count,
const void* const parameter[], void* const state[],
const float* const out_init[], float* const out[]) {
PRINT_NAME();
(void)in_count;
(void)in;
(void)biquad_count;
(void)biquad_update_count;
(void)out_count;
(void)parameter;
(void)state;
(void)out_init;
(void)out;
acm_advance_batch(batch_info, 512);
return OK;
}
} // namespace Acm
} // namespace Libs::Audio
+48
View File
@@ -0,0 +1,48 @@
#ifndef EMULATOR_INCLUDE_EMULATOR_LIBS_ACM_H_
#define EMULATOR_INCLUDE_EMULATOR_LIBS_ACM_H_
#include "common/abi.h"
#include "common/common.h"
namespace Libs::Audio {
namespace Acm {
struct AcmBatchInfo;
struct AcmBatchError;
using AcmContextId = uint32_t;
using AcmBatchId = uint32_t;
int KYTY_SYSV_ABI AcmContextCreate(AcmContextId* context);
int KYTY_SYSV_ABI AcmContextDestroy(AcmContextId context);
int KYTY_SYSV_ABI AcmBatchStartBuffer(AcmContextId context, const void* batch_commands,
size_t batch_size, AcmBatchError* batch_error,
AcmBatchId* batch);
int KYTY_SYSV_ABI AcmBatchStartBuffers(AcmContextId context, uint32_t batch_info_count,
const AcmBatchInfo* const batch_info[],
AcmBatchError* batch_error, AcmBatchId* batch);
int KYTY_SYSV_ABI AcmBatchWait(AcmContextId context, AcmBatchId batch, uint32_t timeout);
int KYTY_SYSV_ABI AcmBatchJobNotification(AcmBatchInfo* batch_info);
int KYTY_SYSV_ABI AcmConvReverbSharedInput(AcmBatchInfo* batch_info, uint32_t block_count, void* in,
uint32_t count, const void* const ir[],
const float* gain, void* const out[]);
int KYTY_SYSV_ABI AcmConvReverbSharedIr(AcmBatchInfo* batch_info, uint32_t block_count,
const void* ir, uint32_t count, void* const in[],
const float* gain, void* const out[]);
int KYTY_SYSV_ABI AcmFft(AcmBatchInfo* batch_info, int size, int count, int input_format,
const void* const input[], int output_format, void* const output[],
uint32_t flags);
int KYTY_SYSV_ABI AcmIfft(AcmBatchInfo* batch_info, int size, int count, int input_format,
const void* const input[], int output_format, void* const output[],
uint32_t flags);
int KYTY_SYSV_ABI AcmPanner(AcmBatchInfo* batch_info, uint32_t in_count, const float* const in[],
uint32_t biquad_count, uint32_t biquad_update_count, uint32_t out_count,
const void* const parameter[], void* const state[],
const float* const out_init[], float* const out[]);
} // namespace Acm
} // namespace Libs::Audio
#endif /* EMULATOR_INCLUDE_EMULATOR_LIBS_ACM_H_ */
-6
View File
@@ -1196,12 +1196,6 @@ int KYTY_SYSV_ABI AjmBatchJobRunSplit(AjmBatchInfo* info, uint32_t instance, uin
(void)AjmGetInstanceFormat(instance, nullptr, &decoder, &gapless);
AjmWriteSideband(flags, sideband_output, sideband_output_size, decoder, gapless, decode_result);
LOGF("\t instance = 0x%08" PRIx32 ", flags = 0x%016" PRIx64 ", input_buffers = 0x%016" PRIx64
", input_num = 0x%016" PRIx64 ", output_buffers = 0x%016" PRIx64
", output_num = 0x%016" PRIx64 "\n",
instance, flags, reinterpret_cast<uint64_t>(input_buffers),
static_cast<uint64_t>(input_buffers_num), reinterpret_cast<uint64_t>(output_buffers),
static_cast<uint64_t>(output_buffers_num));
return AjmAppendJob(info, 16 * (input_buffers_num + output_buffers_num) + 32, "run-split");
}
+8 -6
View File
@@ -464,6 +464,14 @@ private:
const auto* chunk = input + offset;
const auto chunk_size = static_cast<size_t>(AjmReadLe32(chunk + 4));
const auto payload = offset + 8;
// The data chunk describes the whole stream; this job may contain only its prefix.
if (AjmFourCcEquals(chunk, 'd', 'a', 't', 'a')) {
*data_offset = payload;
result->input_consumed = payload;
result->format = GetFormat();
result->total_decoded_samples = m_total_decoded_samples;
return true;
}
if (payload > input_size || chunk_size > input_size - payload) {
result->result = AJM_RESULT_PARTIAL_INPUT;
return false;
@@ -496,12 +504,6 @@ private:
LOGF("AJM ATRAC9 gapless: total=%" PRIu32 ", skip=%" PRIu16 "\n",
gapless_decode.total_samples, gapless_decode.skip_samples);
}
} else if (AjmFourCcEquals(chunk, 'd', 'a', 't', 'a')) {
*data_offset = payload;
result->input_consumed = payload;
result->format = GetFormat();
result->total_decoded_samples = m_total_decoded_samples;
return true;
}
const auto padded_size = chunk_size + (chunk_size & 1u);
+101 -2618
View File
File diff suppressed because it is too large. Load diff
-126
View File
@@ -190,43 +190,6 @@ const char* KYTY_SYSV_ABI AjmStrError(int error);
} // namespace Ajm
namespace Acm {
struct AcmBatchInfo;
struct AcmBatchError;
using AcmContextId = uint32_t;
using AcmBatchId = uint32_t;
int KYTY_SYSV_ABI AcmContextCreate(AcmContextId* context);
int KYTY_SYSV_ABI AcmContextDestroy(AcmContextId context);
int KYTY_SYSV_ABI AcmBatchStartBuffer(AcmContextId context, const void* batch_commands,
size_t batch_size, AcmBatchError* batch_error,
AcmBatchId* batch);
int KYTY_SYSV_ABI AcmBatchStartBuffers(AcmContextId context, uint32_t batch_info_count,
const AcmBatchInfo* const batch_info[],
AcmBatchError* batch_error, AcmBatchId* batch);
int KYTY_SYSV_ABI AcmBatchWait(AcmContextId context, AcmBatchId batch, uint32_t timeout);
int KYTY_SYSV_ABI AcmBatchJobNotification(AcmBatchInfo* batch_info);
int KYTY_SYSV_ABI AcmConvReverbSharedInput(AcmBatchInfo* batch_info, uint32_t block_count, void* in,
uint32_t count, const void* const ir[],
const float* gain, void* const out[]);
int KYTY_SYSV_ABI AcmConvReverbSharedIr(AcmBatchInfo* batch_info, uint32_t block_count,
const void* ir, uint32_t count, void* const in[],
const float* gain, void* const out[]);
int KYTY_SYSV_ABI AcmFft(AcmBatchInfo* batch_info, int size, int count, int input_format,
const void* const input[], int output_format, void* const output[],
uint32_t flags);
int KYTY_SYSV_ABI AcmIfft(AcmBatchInfo* batch_info, int size, int count, int input_format,
const void* const input[], int output_format, void* const output[],
uint32_t flags);
int KYTY_SYSV_ABI AcmPanner(AcmBatchInfo* batch_info, uint32_t in_count, const float* const in[],
uint32_t biquad_count, uint32_t biquad_update_count, uint32_t out_count,
const void* const parameter[], void* const state[],
const float* const out_init[], float* const out[]);
} // namespace Acm
namespace AvPlayer {
struct AvPlayerInitData;
@@ -271,95 +234,6 @@ int KYTY_SYSV_ABI AvPlayerSetLogCallback(void* callback, void* user_data);
} // namespace AvPlayer
namespace Audio3d {
struct Audio3dOpenParameters;
int KYTY_SYSV_ABI Audio3dInitialize(int64_t reserved);
void KYTY_SYSV_ABI Audio3dGetDefaultOpenParameters(Audio3dOpenParameters* p);
int KYTY_SYSV_ABI Audio3dPortOpen(int user_id, const Audio3dOpenParameters* parameters,
uint32_t* id);
int KYTY_SYSV_ABI Audio3dPortSetAttribute(uint32_t port_id, uint32_t attribute_id,
const void* attribute, size_t attribute_size);
int KYTY_SYSV_ABI Audio3dPortGetQueueLevel(uint32_t port_id, uint32_t* queue_level,
uint32_t* queue_available);
int KYTY_SYSV_ABI Audio3dPortAdvance(uint32_t port_id);
int KYTY_SYSV_ABI Audio3dPortPush(uint32_t port_id, uint32_t blocking);
} // namespace Audio3d
namespace Ngs2 {
struct Ngs2SystemOption;
struct Ngs2SystemInfo;
struct Ngs2RackOption;
struct Ngs2BufferAllocator;
struct Ngs2VoiceParamHeader;
struct Ngs2RenderBufferInfo;
struct Ngs2ContextBufferInfo;
struct Ngs2VoiceState;
struct Ngs2WaveformFormat;
struct Ngs2WaveformBlock;
struct Ngs2WaveformInfo;
struct Ngs2PanWork;
struct Ngs2PanParam;
struct Ngs2GeomListenerParam;
struct Ngs2GeomListenerWork;
struct Ngs2GeomSourceParam;
struct Ngs2GeomAttribute;
int KYTY_SYSV_ABI Ngs2SystemResetOption(Ngs2SystemOption* option);
int KYTY_SYSV_ABI Ngs2SystemQueryBufferSize(const Ngs2SystemOption* option,
Ngs2ContextBufferInfo* buffer_info);
int KYTY_SYSV_ABI Ngs2SystemCreate(const Ngs2SystemOption* option,
const Ngs2ContextBufferInfo* buffer_info, uintptr_t* handle);
int KYTY_SYSV_ABI Ngs2SystemGetInfo(uintptr_t system_handle, Ngs2SystemInfo* info,
size_t info_size);
int KYTY_SYSV_ABI Ngs2SystemSetGrainSamples(uintptr_t system_handle, uint32_t num_samples);
int KYTY_SYSV_ABI Ngs2RackQueryBufferSize(uint32_t rack_id, const Ngs2RackOption* option,
Ngs2ContextBufferInfo* buffer_info);
int KYTY_SYSV_ABI Ngs2RackCreate(uintptr_t system_handle, uint32_t rack_id,
const Ngs2RackOption* option,
const Ngs2ContextBufferInfo* buffer_info, uintptr_t* handle);
int KYTY_SYSV_ABI Ngs2SystemCreateWithAllocator(const Ngs2SystemOption* option,
const Ngs2BufferAllocator* allocator,
uintptr_t* handle);
int KYTY_SYSV_ABI Ngs2SystemDestroy(uintptr_t system_handle, Ngs2ContextBufferInfo* buffer_info);
int KYTY_SYSV_ABI Ngs2RackCreateWithAllocator(uintptr_t system_handle, uint32_t rack_id,
const Ngs2RackOption* option,
const Ngs2BufferAllocator* allocator,
uintptr_t* handle);
int KYTY_SYSV_ABI Ngs2RackDestroy(uintptr_t rack_handle, Ngs2ContextBufferInfo* buffer_info);
int KYTY_SYSV_ABI Ngs2RackLock(uintptr_t rack_handle);
int KYTY_SYSV_ABI Ngs2RackUnlock(uintptr_t rack_handle);
int KYTY_SYSV_ABI Ngs2RackGetVoiceHandle(uintptr_t rack_handle, uint32_t voice_id,
uintptr_t* handle);
int KYTY_SYSV_ABI Ngs2VoiceControl(uintptr_t voice_handle, const Ngs2VoiceParamHeader* param_list);
int KYTY_SYSV_ABI Ngs2VoiceRunCommands(uintptr_t voice_handle, const void* commands,
size_t num_commands);
int KYTY_SYSV_ABI Ngs2VoiceGetState(uintptr_t voice_handle, Ngs2VoiceState* state,
size_t state_size);
int KYTY_SYSV_ABI Ngs2VoiceGetStateFlags(uintptr_t voice_handle, uint32_t* state_flags);
int KYTY_SYSV_ABI Ngs2SystemRender(uintptr_t system_handle, const Ngs2RenderBufferInfo* buffer_info,
uint32_t num_buffer_info);
int KYTY_SYSV_ABI Ngs2ParseWaveformData(const void* data, size_t data_size, Ngs2WaveformInfo* info);
int KYTY_SYSV_ABI Ngs2CalcWaveformBlock(const Ngs2WaveformFormat* format, uint32_t sample_pos,
uint32_t num_samples, Ngs2WaveformBlock* block);
int KYTY_SYSV_ABI Ngs2PanInit(Ngs2PanWork* work, const float* speaker_angles, float unit_angle,
uint32_t num_speakers);
int KYTY_SYSV_ABI Ngs2PanGetVolumeMatrix(Ngs2PanWork* work, const Ngs2PanParam* params,
uint32_t num_params, uint32_t matrix_format,
float* out_volume_matrix);
int KYTY_SYSV_ABI Ngs2GeomResetListenerParam(Ngs2GeomListenerParam* out_listener_param);
int KYTY_SYSV_ABI Ngs2GeomResetSourceParam(Ngs2GeomSourceParam* out_source_param);
int KYTY_SYSV_ABI Ngs2GeomCalcListener(const Ngs2GeomListenerParam* param,
Ngs2GeomListenerWork* out_work, uint32_t flags);
int KYTY_SYSV_ABI Ngs2GeomApply(const Ngs2GeomListenerWork* listener,
const Ngs2GeomSourceParam* source, Ngs2GeomAttribute* out_attrib,
uint32_t flags);
} // namespace Ngs2
} // namespace Libs::Audio
#endif /* EMULATOR_INCLUDE_EMULATOR_AUDIO_H_ */
+319
View File
@@ -0,0 +1,319 @@
#include "libs/audio3d.h"
#include "common/assert.h"
#include "common/threads.h"
#include "kernel/semaphore.h"
#include "libs/errno.h"
#include "libs/libs.h"
#include <atomic>
namespace Libs::Audio {
namespace Audio3d {
LIB_NAME("Audio3d", "Audio3d");
namespace Semaphore = LibKernel::Semaphore;
struct Audio3dOpenParameters {
size_t size = 0x20;
uint32_t granularity = 256;
uint32_t rate = 0;
uint32_t max_objects = 512;
uint32_t queue_depth = 2;
uint32_t buffer_mode = 2;
uint32_t pad = 0;
// uint32_t num_beds;
};
struct Audio3dData {
enum class State { Empty, Ready, Play };
std::atomic<State> state = State::Empty;
};
struct Audio3dInternal {
Audio3dData* data = nullptr;
Common::Mutex* data_mutex = nullptr;
uint64_t data_delay = 0;
Semaphore::KernelSema playback_sema = nullptr;
Audio3dOpenParameters params = {};
int user_id = 0;
float late_reverb_level = 0.0f;
float downmix_spread_radius = 2.0f;
int downmix_spread_height_aware = 0;
uint32_t data_index = 0;
bool used = false;
std::atomic_bool playback_finished = false;
};
constexpr uint32_t MAX_PORTS = 4;
static Audio3dInternal g_ports[MAX_PORTS] = {};
static void playback_simulate(void* arg) {
auto* port = static_cast<Audio3dInternal*>(arg);
EXIT_IF(port == nullptr);
EXIT_IF(port->data_mutex == nullptr);
EXIT_IF(port->data == nullptr);
for (;;) {
int result = Semaphore::KernelWaitSema(port->playback_sema, 1, nullptr);
if (result != OK) {
break;
}
Audio3dData* play_data = nullptr;
port->data_mutex->Lock();
{
for (uint32_t i = 0; i < port->params.queue_depth; i++) {
uint32_t index = (port->data_index + i) % port->params.queue_depth;
if (port->data[index].state == Audio3dData::State::Play) {
play_data = &port->data[index];
break;
}
}
}
port->data_mutex->Unlock();
EXIT_IF(play_data == nullptr);
if (play_data != nullptr) {
// TODO(): Audio output is not yet implemented, so simulate audio delay
Common::Thread::SleepMicro(port->data_delay);
play_data->state = Audio3dData::State::Empty;
}
}
port->playback_finished = true;
}
int KYTY_SYSV_ABI Audio3dInitialize(int64_t reserved) {
PRINT_NAME();
EXIT_NOT_IMPLEMENTED(reserved != 0);
return OK;
}
void KYTY_SYSV_ABI Audio3dGetDefaultOpenParameters(Audio3dOpenParameters* p) {
PRINT_NAME();
EXIT_NOT_IMPLEMENTED(sizeof(Audio3dOpenParameters) != 0x20);
*p = Audio3dOpenParameters();
}
int KYTY_SYSV_ABI Audio3dPortOpen(int user_id, const Audio3dOpenParameters* parameters,
uint32_t* id) {
PRINT_NAME();
EXIT_NOT_IMPLEMENTED(parameters == nullptr);
EXIT_NOT_IMPLEMENTED(id == nullptr);
EXIT_NOT_IMPLEMENTED(parameters->size != 0x20);
LOGF("\t user_id = %d\n"
"\t granularity = %u\n"
"\t rate = %u\n"
"\t max_objects = %u\n"
"\t queue_depth = %u\n"
"\t buffer_mode = %u\n",
user_id, parameters->granularity, parameters->rate, parameters->max_objects,
parameters->queue_depth, parameters->buffer_mode);
EXIT_NOT_IMPLEMENTED(parameters->buffer_mode != 2);
EXIT_NOT_IMPLEMENTED(user_id != 255 && user_id != 1);
uint32_t port = 0;
for (; port < MAX_PORTS; port++) {
if (!g_ports[port].used) {
break;
}
}
EXIT_NOT_IMPLEMENTED(port >= MAX_PORTS);
g_ports[port].user_id = user_id;
g_ports[port].params = *parameters;
g_ports[port].used = true;
EXIT_IF(g_ports[port].data != nullptr);
EXIT_IF(g_ports[port].data_mutex != nullptr);
EXIT_IF(g_ports[port].playback_sema != nullptr);
g_ports[port].data = new Audio3dData[parameters->queue_depth];
g_ports[port].data_index = 0;
g_ports[port].data_mutex = new Common::Mutex;
g_ports[port].data_delay = (1000000 * static_cast<uint64_t>(parameters->granularity)) / 48000;
for (uint32_t d = 0; d < parameters->queue_depth; d++) {
g_ports[port].data[d].state = Audio3dData::State::Empty;
}
int result = Semaphore::KernelCreateSema(&g_ports[port].playback_sema, "audio3d_play", 0x01, 0,
static_cast<int>(parameters->queue_depth), nullptr);
EXIT_NOT_IMPLEMENTED(result != OK);
g_ports[port].playback_finished = false;
Common::Thread playback_thread(playback_simulate, &g_ports[port]);
playback_thread.Detach();
*id = port;
return OK;
}
int KYTY_SYSV_ABI Audio3dPortSetAttribute(uint32_t port_id, uint32_t attribute_id,
const void* attribute, size_t attribute_size) {
PRINT_NAME();
EXIT_NOT_IMPLEMENTED(port_id >= MAX_PORTS);
EXIT_NOT_IMPLEMENTED(!g_ports[port_id].used);
EXIT_NOT_IMPLEMENTED(attribute == nullptr);
LOGF("\t attribute_id = 0x%" PRIx32 "\n", attribute_id);
switch (attribute_id) {
case 0x10001:
EXIT_NOT_IMPLEMENTED(attribute_size != 4);
g_ports[port_id].late_reverb_level = *static_cast<const float*>(attribute);
LOGF("\t late_reverb_level = %f\n", g_ports[port_id].late_reverb_level);
break;
case 0x10002:
EXIT_NOT_IMPLEMENTED(attribute_size != 4);
g_ports[port_id].downmix_spread_radius = *static_cast<const float*>(attribute);
LOGF("\t downmix_spread_radius = %f\n", g_ports[port_id].downmix_spread_radius);
break;
case 0x10003:
EXIT_NOT_IMPLEMENTED(attribute_size != 4);
g_ports[port_id].downmix_spread_height_aware = *static_cast<const int*>(attribute);
LOGF("\t downmix_spread_height_aware = %d\n",
g_ports[port_id].downmix_spread_height_aware);
break;
default: EXIT("unknown attribute: 0x%" PRIx32 "\n", attribute_id);
}
return OK;
}
int KYTY_SYSV_ABI Audio3dPortGetQueueLevel(uint32_t port_id, uint32_t* queue_level,
uint32_t* queue_available) {
PRINT_NAME();
EXIT_NOT_IMPLEMENTED(port_id >= MAX_PORTS);
EXIT_NOT_IMPLEMENTED(!g_ports[port_id].used);
EXIT_NOT_IMPLEMENTED(queue_level == nullptr && queue_available == nullptr);
auto* port = &g_ports[port_id];
uint32_t empty_num = 0;
port->data_mutex->Lock();
{
for (uint32_t i = 0; i < port->params.queue_depth; i++) {
uint32_t index = (port->data_index + i) % port->params.queue_depth;
if (port->data[index].state == Audio3dData::State::Empty) {
empty_num++;
} else {
break;
}
}
}
port->data_mutex->Unlock();
EXIT_IF(empty_num > port->params.queue_depth);
LOGF("\t queue_available = %u\n", empty_num);
if (queue_level != nullptr) {
*queue_level = port->params.queue_depth - empty_num;
}
if (queue_available != nullptr) {
*queue_available = empty_num;
}
return OK;
}
int KYTY_SYSV_ABI Audio3dPortAdvance(uint32_t port_id) {
PRINT_NAME();
EXIT_NOT_IMPLEMENTED(port_id >= MAX_PORTS);
EXIT_NOT_IMPLEMENTED(!g_ports[port_id].used);
auto* port = &g_ports[port_id];
port->data_mutex->Lock();
{
uint32_t current_index = port->data_index;
uint32_t next_index = (current_index + 1) % port->params.queue_depth;
if (port->data[current_index].state == Audio3dData::State::Empty) {
port->data[current_index].state = Audio3dData::State::Ready;
}
EXIT_NOT_IMPLEMENTED(port->data[current_index].state != Audio3dData::State::Ready);
port->data_index = next_index;
LOGF("\t %u -> %u\n", current_index, next_index);
}
port->data_mutex->Unlock();
return OK;
}
int KYTY_SYSV_ABI Audio3dPortPush(uint32_t port_id, uint32_t blocking) {
PRINT_NAME();
EXIT_NOT_IMPLEMENTED(port_id >= MAX_PORTS);
EXIT_NOT_IMPLEMENTED(!g_ports[port_id].used);
auto* port = &g_ports[port_id];
EXIT_NOT_IMPLEMENTED(blocking != 1);
LOGF("\t blocking = %u\n", blocking);
int data_num = 0;
Audio3dData* first_data = nullptr;
port->data_mutex->Lock();
{
first_data = port->data + port->data_index;
for (uint32_t i = 0; i < port->params.queue_depth; i++) {
uint32_t index = (port->data_index + i) % port->params.queue_depth;
if (port->data[index].state == Audio3dData::State::Ready) {
port->data[index].state = Audio3dData::State::Play;
data_num++;
}
}
}
port->data_mutex->Unlock();
LOGF("\t push num = %d\n", data_num);
if (data_num > 0) {
Semaphore::KernelSignalSema(port->playback_sema, data_num);
if (blocking == 1) {
auto wait_time = port->data_delay / 8;
while (first_data->state != Audio3dData::State::Empty) {
Common::Thread::SleepMicro(wait_time);
}
}
}
return OK;
}
} // namespace Audio3d
} // namespace Libs::Audio
+28
View File
@@ -0,0 +1,28 @@
#ifndef EMULATOR_INCLUDE_EMULATOR_LIBS_AUDIO3D_H_
#define EMULATOR_INCLUDE_EMULATOR_LIBS_AUDIO3D_H_
#include "common/abi.h"
#include "common/common.h"
namespace Libs::Audio {
namespace Audio3d {
struct Audio3dOpenParameters;
int KYTY_SYSV_ABI Audio3dInitialize(int64_t reserved);
void KYTY_SYSV_ABI Audio3dGetDefaultOpenParameters(Audio3dOpenParameters* p);
int KYTY_SYSV_ABI Audio3dPortOpen(int user_id, const Audio3dOpenParameters* parameters,
uint32_t* id);
int KYTY_SYSV_ABI Audio3dPortSetAttribute(uint32_t port_id, uint32_t attribute_id,
const void* attribute, size_t attribute_size);
int KYTY_SYSV_ABI Audio3dPortGetQueueLevel(uint32_t port_id, uint32_t* queue_level,
uint32_t* queue_available);
int KYTY_SYSV_ABI Audio3dPortAdvance(uint32_t port_id);
int KYTY_SYSV_ABI Audio3dPortPush(uint32_t port_id, uint32_t blocking);
} // namespace Audio3d
} // namespace Libs::Audio
#endif /* EMULATOR_INCLUDE_EMULATOR_LIBS_AUDIO3D_H_ */
+20 -6
View File
@@ -852,11 +852,15 @@ public:
emit_event(stop_event, AVPLAYER_EVENT_STATE_STOP);
return 0;
}
void Pause() {
int Pause() {
std::scoped_lock lifecycle_lock(lifecycle_mutex);
std::lock_guard lock(mutex);
if (state != State::Playing) {
return AVPLAYER_ERROR_OPERATION_FAILED;
}
paused = true;
pause_time = std::chrono::steady_clock::now();
return 0;
}
void Resume() {
std::scoped_lock lifecycle_lock(lifecycle_mutex);
@@ -890,6 +894,9 @@ public:
bool was_paused = false;
{
std::lock_guard lock(mutex);
if (state != State::Playing) {
return AVPLAYER_ERROR_OPERATION_FAILED;
}
was_paused = paused;
}
return StartImpl(ms, was_paused, true);
@@ -1682,6 +1689,7 @@ struct AvPlayerInternal {
AvPlayerPostInitData post_init {};
bool auto_start = false;
int32_t video_buffers = 2;
uint32_t sync_mode = 0;
uint32_t start_bandwidth = 0;
uint32_t minimum_bandwidth = 0;
uint32_t maximum_bandwidth = 0;
@@ -1730,6 +1738,7 @@ static int add_source(AvPlayerInternal* h, const std::string& filename, AvPlayer
if (auto rc = s->Init(filename, type); rc < 0) {
return rc;
}
s->SetSync(h->sync_mode);
h->source = std::move(s);
emit_event(h->event, AVPLAYER_EVENT_STATE_READY);
if (h->auto_start) {
@@ -1915,9 +1924,11 @@ int KYTY_SYSV_ABI AvPlayerPause(AvPlayerInternal* h) {
if (h == nullptr || h->source == nullptr) {
return AVPLAYER_ERROR_INVALID_PARAMS;
}
h->source->Pause();
emit_event(h->event, AVPLAYER_EVENT_STATE_PAUSE);
return 0;
auto rc = h->source->Pause();
if (rc == 0) {
emit_event(h->event, AVPLAYER_EVENT_STATE_PAUSE);
}
return rc;
}
int KYTY_SYSV_ABI AvPlayerResume(AvPlayerInternal* h) {
PRINT_NAME();
@@ -1951,13 +1962,16 @@ int KYTY_SYSV_ABI AvPlayerSetTrickSpeed(AvPlayerInternal* h, int32_t trick_speed
}
int KYTY_SYSV_ABI AvPlayerSetAvSyncMode(AvPlayerInternal* h, uint32_t sync_mode) {
PRINT_NAME();
if (h == nullptr || h->source == nullptr) {
if (h == nullptr) {
return AVPLAYER_ERROR_INVALID_PARAMS;
}
if (sync_mode > 1) {
return AVPLAYER_ERROR_NOT_SUPPORTED;
}
h->source->SetSync(sync_mode);
h->sync_mode = sync_mode;
if (h->source != nullptr) {
h->source->SetSync(sync_mode);
}
return 0;
}
int KYTY_SYSV_ABI AvPlayerSetAvailableBandwidth(AvPlayerInternal* h, uint32_t start_bandwidth,
+18 -18
View File
@@ -1,6 +1,6 @@
#include "libs/controller.h"
#include "SDL.h"
#include <SDL3/SDL.h>
#include "common/assert.h"
#include "common/common.h"
#include "common/emulatorConfig.h"
@@ -293,11 +293,11 @@ void GameController::Connect(int id) {
m_connected_ids.push_back(id);
if (id != HOST_INPUT_CONTROLLER_ID) {
if (auto* pad = SDL_GameControllerFromInstanceID(static_cast<SDL_JoystickID>(id));
if (auto* pad = SDL_GetGamepadFromID(static_cast<SDL_JoystickID>(id));
pad != nullptr) {
for (auto sensor: {SDL_SENSOR_ACCEL, SDL_SENSOR_GYRO}) {
if (SDL_GameControllerHasSensor(pad, sensor) &&
SDL_GameControllerSetSensorEnabled(pad, sensor, SDL_TRUE) != 0) {
if (SDL_GamepadHasSensor(pad, sensor) &&
!SDL_SetGamepadSensorEnabled(pad, sensor, true)) {
LOGF("\t enabling controller sensor failed: %s\n", SDL_GetError());
}
}
@@ -519,22 +519,22 @@ void GameController::ResetInputState() {
void GameController::ReleaseHostPads() {
Common::LockGuard lock(m_mutex);
std::vector<SDL_GameController*> pads;
std::vector<SDL_Gamepad*> pads;
for (const auto id: m_connected_ids) {
if (id == HOST_INPUT_CONTROLLER_ID) {
continue;
}
if (auto* pad = SDL_GameControllerFromInstanceID(static_cast<SDL_JoystickID>(id));
if (auto* pad = SDL_GetGamepadFromID(static_cast<SDL_JoystickID>(id));
pad != nullptr) {
if (SDL_GameControllerGetType(pad) == SDL_CONTROLLER_TYPE_PS5) {
if (SDL_GetGamepadType(pad) == SDL_GAMEPAD_TYPE_PS5) {
DualSenseEffects effect {};
effect.enable_bits = 0x0c;
effect.right_trigger[0] = 0x05;
effect.left_trigger[0] = 0x05;
(void)SDL_GameControllerSendEffect(pad, &effect, sizeof(effect));
(void)SDL_SendGamepadEffect(pad, &effect, sizeof(effect));
}
(void)SDL_GameControllerRumble(pad, 0, 0, 0);
(void)SDL_GameControllerSetLED(pad, 0, 0, 0);
(void)SDL_RumbleGamepad(pad, 0, 0, 0);
(void)SDL_SetGamepadLED(pad, 0, 0, 0);
pads.push_back(pad);
}
}
@@ -542,7 +542,7 @@ void GameController::ReleaseHostPads() {
if (!pads.empty()) {
SDL_Delay(RELEASE_FLUSH_MS);
for (auto* pad: pads) {
SDL_GameControllerClose(pad);
SDL_CloseGamepad(pad);
}
}
@@ -556,23 +556,23 @@ void GameController::SetVibration(uint8_t large_motor, uint8_t small_motor) {
return;
}
auto* pad = SDL_GameControllerFromInstanceID(static_cast<SDL_JoystickID>(m_active_id));
auto* pad = SDL_GetGamepadFromID(static_cast<SDL_JoystickID>(m_active_id));
if (pad == nullptr) {
return;
}
const auto large = static_cast<uint16_t>(large_motor * 0x101U);
const auto small = static_cast<uint16_t>(small_motor * 0x101U);
if (SDL_GameControllerRumble(pad, large, small, RUMBLE_DURATION_MS) != 0) {
if (!SDL_RumbleGamepad(pad, large, small, RUMBLE_DURATION_MS)) {
LOGF("\t rumble failed: %s\n", SDL_GetError());
}
}
void GameController::SetLightBar(uint8_t r, uint8_t g, uint8_t b) {
Common::LockGuard lock(m_mutex);
if (auto* pad = SDL_GameControllerFromInstanceID(static_cast<SDL_JoystickID>(m_active_id));
if (auto* pad = SDL_GetGamepadFromID(static_cast<SDL_JoystickID>(m_active_id));
pad != nullptr) {
(void)SDL_GameControllerSetLED(pad, r, g, b);
(void)SDL_SetGamepadLED(pad, r, g, b);
}
}
@@ -599,9 +599,9 @@ bool GameController::SetTriggerEffect(const PadTriggerEffectParam& param) {
}
Common::LockGuard lock(m_mutex);
auto* pad = SDL_GameControllerFromInstanceID(static_cast<SDL_JoystickID>(m_active_id));
if (pad != nullptr && SDL_GameControllerGetType(pad) == SDL_CONTROLLER_TYPE_PS5) {
(void)SDL_GameControllerSendEffect(pad, &effect, sizeof(effect));
auto* pad = SDL_GetGamepadFromID(static_cast<SDL_JoystickID>(m_active_id));
if (pad != nullptr && SDL_GetGamepadType(pad) == SDL_GAMEPAD_TYPE_PS5) {
(void)SDL_SendGamepadEffect(pad, &effect, sizeof(effect));
}
return true;
}
Loaded 100 of 124 files, more files were not shown because too many files have changed in this diff. Show more