Compare commits

...
Author SHA1 Message Date
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
nmzik e930657bf6 tesselation support in GUI 2026-09-20 17:16:19 +04:00
nmzik 0ad9149c90 Fix three-vertex rectangle rendering 2026-09-20 17:16:19 +04:00
nmzik 25f2699523 Fix video aspect ratio 2026-09-20 17:16:19 +04:00
nmzik a849f25376 Make tessellation optional 2026-09-20 17:16:19 +04:00
nmzik fa51da4d76 Fix AvPlayer end-of-file handling 2026-09-20 17:16:19 +04:00
nmzik 6b79b7a003 Fix AvPlayer startup state 2026-09-20 17:16:19 +04:00
nmzik 102a10f0fe Fix signed sampler comparisons 2026-09-20 17:16:19 +04:00
nmzik 8d66c4ef6d user service: return logged-out error for unavailable user names 2026-09-20 14:03:08 +04:00
nmzikandAugusto Domingos 8cecaf4e30 Merge PR #716: Fix Unicode paths on Windows
Preserve native Windows paths for filesystem operations, command-line options,
and game icons. Complete Unicode handling in recursive file listings and file
logging, restore unmounting by host directory, and extend regression coverage.

Original implementation by Augusto Domingos (@AghastyGD); retain all five of
his PR commits and include the reviewed follow-up fixes.

Closes #714

Co-authored-by: Augusto Domingos <augustodomingosvasco@gmail.com>
2026-09-20 02:14:22 +04:00
nmzik 82b2711cdf audio: play ATRAC9 sampler blocks 2026-09-20 01:47:43 +04:00
nmzik 35a275e97d avplayer: signal when playback ends 2026-09-20 01:47:43 +04:00
nmzik 303a0203b6 audio: play NGS2 PCM buffers 2026-09-20 01:47:43 +04:00
cfbaee07f0 Persist SaveDataMemory2 save data to disk (fixes Astro Bot progress not saving) (#659)
* Persist save-data memory across restarts

* Fix SetSaveDataMemory2 data loss, split saves per user, add atomic writes everywhere

- SaveDataSetSaveDataMemory2 treated data_num=0 as "no entries" even when
  a valid data pointer was supplied. Astro Bot calls this with data_num=0
  meaning "one entry, at data[0]" (an implicit-count convention), so every
  real save write from it was silently dropped as a no-op. Default
  data_num to 1 when data is non-null, matching that convention.
- SaveDataSetupSaveDataMemory2/Get/SetSaveDataMemory2 rejected any call
  that included a non-null icon pointer. Icon persistence isn't
  implemented, but titles are allowed to pass one anyway; rejecting the
  whole call over it made every title that does so fail outright. Ignore
  icon instead of rejecting.
- SaveDataMount3/SaveDataTransferringMount/SaveDataDelete built their
  save-data path from title+dir_name only, dropping the user_id PS5
  local users are keyed by (unlike the newer SetSaveDataMemory2 layout,
  which already nests by user_id). Nest these under user_id too so saves
  from different local users can no longer collide. No migration for the
  old flat layout - the project is still pre-release/experimental.
- Classic SaveDataMount3-mounted titles (Blasphemous, Dead Cells, ...)
  write their save files directly through the guest's normal file I/O
  once mounted, bypassing SaveData's own write path entirely, so they
  had none of the crash-safety SetSaveDataMemory2 already had. Added the
  same protection at the file-system layer: a guest file opened under a
  /savedataN mount that recreates the file from scratch (O_CREAT and/or
  O_TRUNC) is now written to a .tmp shadow file and atomically replaces
  the real one on close, instead of being truncated in place.
- Both write paths now back up the file they're about to replace into a
  sibling "backups" folder (same filename, no added suffix) before the
  atomic replace, so a manual recovery copy of the previous save always
  survives a write - even one that "succeeds" but wasn't wanted.

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

* Fix save-memory compatibility and preserve existing saves

---------

Co-authored-by: nmzik <Nmzik@mail.ru>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-20 00:51:22 +04:00
Augusto Domingos fe45265fcd preserve native paths when creating directories 2026-09-19 17:53:15 +02:00
Augusto Domingos 8b294eb5b8 Add unicode path regression coverage 2026-09-19 15:48:30 +02:00
Augusto Domingos 07c9978ac6 keep game icon paths native in windows 2026-09-19 14:45:24 +02:00
Augusto Domingos b61b3f2a40 handle UTF-8 command-line paths 2026-09-19 14:12:35 +02:00
Augusto Domingos 001dfea501 Preserve native windows paths in filesystem operations 2026-09-19 13:11:55 +02:00
nmzik f100f785da libs: fill controller and broadcast status outputs 2026-09-19 05:16:16 +04:00
nmzik ba1854bbdc net: start pool IDs at 1 2026-09-19 04:59:28 +04:00
nmzik ea6aae8577 shader: V_MAD_I16 2026-09-19 04:43:48 +04:00
nmzik 5b7d3343cf shader: V_CMP_NLT_F16 2026-09-18 12:25:13 +04:00
nmzik 394e638905 shader: V_MUL_LO_U16 2026-09-18 11:21:57 +04:00
nmzik c2b7b0b58c Revert "BC7-RGBA32_UINT views (Vulkan validation)"
This reverts commit 165234b159.
2026-09-18 11:12:36 +04:00
nmzik 1d2f59dfa4 ajm: reject invalid batch storage 2026-09-18 09:11:55 +04:00
nmzikandBlackCAT 007d7ea2eb audio: infer stereo RAW AAC layout from channel-pair packets
Co-authored-by: BlackCAT <52720489+psnwd@users.noreply.github.com>
2026-09-18 09:11:55 +04:00
nmzikandshadowbeat070 95d3d9d3ad shader: fix packed integer negation
Co-authored-by: shadowbeat070 <70843316+shadowbeat070@users.noreply.github.com>
2026-09-18 09:11:55 +04:00
bfc8cd0e0b shader: DPP8 moves
Co-authored-by: foufouadi <attar.foued@gmail.com>
Co-authored-by: fxpw <fxpw@outlook.com>
2026-09-18 09:11:55 +04:00
165234b159 BC7-RGBA32_UINT views (Vulkan validation)
Co-authored-by: Techx3 <56268269+Techx3@users.noreply.github.com>
Co-authored-by: shadowbeat070 <70843316+shadowbeat070@users.noreply.github.com>
Co-authored-by: SueryRD <the.ai.tester2@gmail.com>
Co-authored-by: BlackCAT <52720489+psnwd@users.noreply.github.com>
2026-09-18 09:11:55 +04:00
nmzik 6493674726 shader: dual-source blending (Vulkan) 2026-09-18 09:11:55 +04:00
nmzik cbedf5707f UDP P2P sockets 2026-09-18 09:11:55 +04:00
nmzik 57c5b206ca fix network errno 2026-09-18 09:11:55 +04:00
nmzik 87b4417aca fix initial NP sign-in callback 2026-09-18 09:11:55 +04:00
nmzik 104530eafc fix Windows build (#680)
fix Windows Vulkan barrier macro
2026-09-17 21:32:54 +04:00
nmzik 6cfde37e98 avoid extra allocations 2026-09-17 20:35:36 +04:00
nmzik c5cebc1d19 remove unused helpers and the -64 suffix 2026-09-17 20:35:36 +04:00
nmzik 697b18b366 replace ByteBuffer with std::vector 2026-09-17 20:35:36 +04:00
nmzik 5b2935e36c remove the extra virtual memory layer 2026-09-17 20:35:36 +04:00
nmzik 5dc644edb4 clean up graphics helpers 2026-09-17 20:35:36 +04:00
nmzik 1211958b4e clean up runtime helpers 2026-09-17 20:35:36 +04:00
nmzik 218e7200bb clean up launcher helpers 2026-09-17 20:35:36 +04:00
nmzik 2bf78a94e0 remove string wrappers 2026-09-17 20:35:36 +04:00
nmzikandSaadi cc8e02380f remove magic_enum wrappers
Co-authored-by: Saadi <cosxmos@gmail.com>
2026-09-17 20:35:36 +04:00
nmzik c66d977c1d shader: encode SNORM16 formatted stores for skinning buffers 2026-09-17 17:40:05 +04:00
nmzik 4b77686566 shader: V_CMP_NGT_F16 2026-09-17 16:56:32 +04:00
c0sxm0s a6144f794b launcher: report cheat file save failures (#669)
PatchesDialog::Save silently ignored QSaveFile failures, leaving the
status label showing the old "Loaded N cheat(s)" text so the user could
not tell that their selection was not saved. Report the failure in the
status label and the log instead.
2026-09-17 16:13:35 +04:00
Mininaut bab1c3614c libc: treat negative dynamic printf precision as omitted (#625) 2026-09-17 15:18:04 +04:00
c0sxm0s de1545e19a launcher: wire cancel->reject in C++; document glslang for Windows (#663)
uic from Qt 6.12.0 crashes (0xC0000005) on any non-empty <connections>
block in .ui files. Move the cancel_button -> reject connection from
configuration_edit_dialog.ui into configurationEditDialog.cpp, matching
how the other buttons are already connected there.

Also document the glslang (glslangValidator) requirement for Windows
builds; CMake requires it via find_program(... REQUIRED).
2026-09-17 15:13:20 +04:00
Komaryandnmzik ce629e0391 input: add gyroscope and motion sensor support (#634)
input: add controller motion sensor support

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-09-17 12:28:12 +04:00
BlackCAT 6c03b4aaa5 vscode: point IntelliSense at clang instead of MSVC (#564)
* vscode: point IntelliSense at clang instead of MSVC

KYTY_SYSV_ABI expands to __attribute__((sysv_abi)) with no MSVC fallback,
which is correct for a project that rejects cl.exe at configure time. Nothing
told the C/C++ extension that, so it fell back to windows-msvc-x64 and
flagged every guest-facing function in the tree with "__attribute__ is not a
type name".

Set the IntelliSense mode, compiler path and C++ standard to match the
compiler the build actually uses, and export a compile database for it to
read.

* vscode: resolve the clang toolchain through the VS developer environment

The IntelliSense settings named a specific Visual Studio install, so the
compiler and Ninja only resolved on machines with VS 18 Community under
Program Files. Let CMake Tools source the developer environment instead,
which locates the toolchain through vswhere, and drop the explicit
compilerPath so cpptools takes the compiler from compile_commands.json.

Also point C_Cpp.default.compileCommands at the build directory this
workspace actually configures, and drop CMAKE_EXPORT_COMPILE_COMMANDS
because CMake Tools already exports the database by default.
2026-09-17 11:38:40 +04:00
nmzik 0627e23e92 coderabbit: make it quieter 2026-09-17 11:26:44 +04:00
4e6f4f99d3 loader: emulate immediate SSE4a instructions on macOS (#635)
* loader: emulate immediate SSE4a instructions on macOS

* tests: simplify macOS SSE4a test integration

Keep the Darwin target, CTest registration, and aggregate dependency in
one conditional block without a separate target list. Preserve the
standalone target when BUILD_TESTING is disabled.

Lowercase the test filename and diagnostic prefix, and drop unrelated
formatting changes from the instruction emulator.

Credit Brandon Strong's related SSE4a work in
https://github.com/KytyPS5/KytyPS5/pull/467.

Co-authored-by: Brandon Strong <brandostrong@gmail.com>

---------

Co-authored-by: nmzik <Nmzik@mail.ru>
Co-authored-by: Brandon Strong <brandostrong@gmail.com>
2026-09-17 11:23:50 +04:00
Elijaandnmzik f30afc184e shader: support S_ASHR_I64 (#658)
* shader: implement S_ASHR_I64 with compute coverage

* tests: use existing compute suite for S_ASHR_I64

---------

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-09-17 10:48:05 +04:00
228cf3fbf4 Build the launcher on Qt below 6.6 and cap the reference-clock log (#653)
* Fix launcher build on Qt versions below 6.6

QVulkanInstance::NoPortabilityDrivers is only available since Qt 6.6, so
guard its use the same way as the Apple-specific case.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Stop logging every reference-clock copy

CopyData of the clock counter runs on every GPU wait, so the handler wrote
one line per wait: a single SpongeBob boot that never reaches a frame logs
1312 of them, and the count grows with run time. Keep the first 64 lines
for diagnostics; the copy itself is unchanged.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Remove reference-clock logging comment

---------

Co-authored-by: YuhiAida <21134775+YuhiAida@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: nmzik <Nmzik@mail.ru>
2026-09-17 10:30:47 +04:00
nmzik 47957faa16 Match stencil associations by guest range and extent
PPSA30279
2026-09-17 09:01:36 +04:00
nmzik 235e24d44f Recognize null-destination SWAPPC shader handoffs
Credits:
BlackCAT (@psnwd):
https://github.com/KytyPS5/KytyPS5/pull/613
YuhiAida (@YuhiAida):
https://github.com/KytyPS5/KytyPS5/pull/655
2026-09-17 08:45:21 +04:00
nmzik e0ecffed70 Approximate explicit-LOD texture gathers at mip zero
Credits:
YuhiAida (@YuhiAida):
https://github.com/KytyPS5/KytyPS5/pull/588
https://github.com/KytyPS5/KytyPS5/pull/589
https://github.com/KytyPS5/KytyPS5/pull/655
frans (@francoisatt):
https://github.com/KytyPS5/KytyPS5/pull/590
BlackCAT (@psnwd):
https://github.com/KytyPS5/KytyPS5/pull/613
shadowbeat070 (@shadowbeat070):
https://github.com/KytyPS5/KytyPS5/pull/654
Pouare514 (@Pouare514):
https://github.com/KytyPS5/KytyPS5/pull/29
https://github.com/KytyPS5/KytyPS5/pull/32
2026-09-17 08:39:57 +04:00
nmzik fa913ec1a7 Accept EXPCLEAR acceleration on expanded depth attachments 2026-09-17 08:25:55 +04:00
nmzik 49b2eedd1c Fix command buffer capacity calculation
Credits:
YuhiAida (@YuhiAida):
https://github.com/KytyPS5/KytyPS5/pull/588
https://github.com/KytyPS5/KytyPS5/pull/589
https://github.com/KytyPS5/KytyPS5/pull/652
Crimson (@Crimson3076):
https://github.com/KytyPS5/KytyPS5/pull/598
2026-09-17 08:16:59 +04:00
nmzik 63fb822f8a Warn and continue mesh draws with primitive restart enabled 2026-09-16 22:33:19 +04:00
nmzikandÇağatay Güçlü f38d738ee2 Honor PS5 texture minimum LOD for streamed mip chains
Fix broken ground textures in ASTRO BOT.

Related work: https://github.com/KytyPS5/KytyPS5/pull/558

Co-authored-by: Çağatay Güçlü <33038942+defektu@users.noreply.github.com>
2026-09-16 22:33:19 +04:00
nmzikandÇağatay Güçlü eda6ba37ec Json2: new ABIs
Related work: https://github.com/KytyPS5/KytyPS5/pull/558

Co-authored-by: Çağatay Güçlü <33038942+defektu@users.noreply.github.com>
2026-09-16 22:33:19 +04:00
nmzikandÇağatay Güçlü b594f8c834 Bind only exported color targets and preserve MRT slots
Related work: https://github.com/KytyPS5/KytyPS5/pull/558

Co-authored-by: Çağatay Güçlü <33038942+defektu@users.noreply.github.com>
2026-09-16 22:33:19 +04:00
nmzikandÇağatay Güçlü bc45aaf604 Track uniform sampler choices across both conditional arms
Related work: https://github.com/KytyPS5/KytyPS5/pull/558

Co-authored-by: Çağatay Güçlü <33038942+defektu@users.noreply.github.com>
2026-09-16 22:33:19 +04:00
nmzik fb94a819c7 Structure early returns with shared loop continuations 2026-09-16 22:33:19 +04:00
nmzikandÇağatay Güçlü 8589731a27 Preserve PS5 mip-tail views and GPU data during image expansion
Related work: https://github.com/KytyPS5/KytyPS5/pull/558

Co-authored-by: Çağatay Güçlü <33038942+defektu@users.noreply.github.com>
2026-09-16 22:33:19 +04:00
nmzik c1dccd7db1 Skip zero-sized compute dispatches before shader preparation 2026-09-16 22:33:19 +04:00
nmzikandÇağatay Güçlü af3a3f4bf6 Preserve uniform conditional sampler choices in resource plans
Related work: https://github.com/KytyPS5/KytyPS5/pull/558

Co-authored-by: Çağatay Güçlü <33038942+defektu@users.noreply.github.com>
2026-09-16 22:33:19 +04:00
nmzik 349fae00d9 Emulate register-form SSE4a EXTRQ 2026-09-16 22:33:19 +04:00
Fag Fag Fag Sahur 10cae50b34 Add Nix flake and development shell (#645) 2026-09-16 22:19:41 +04:00
nmzik 5b541375a3 shader: implement S_CBRANCH_CDBGSYS opcode 2026-09-16 17:04:48 +04:00
nmzik ded6964953 Use native TLS for fiber context across thread migration 2026-09-16 11:22:05 +02:00
nmzik 9b2111dc01 Bundle FFmpeg 2026-09-16 10:13:03 +02:00
nmzik 71f52f9d90 Simplify fiber ownership and preserve small guest stacks 2026-09-16 08:53:56 +02:00
nmzik 86e5e01403 Protect Windows guest red zones when emulating VRSQRTPS 2026-09-16 06:52:36 +02:00
nmzik 77680ce9d2 Make AMD CPU patch optional 2026-09-16 09:48:50 +04:00
nmzik 32e6a433e8 Fix Json2 integer reads 2026-09-16 09:21:44 +04:00
nmzik 7c9148dd47 Support triangle fans in PS5 geometry shaders
Keep the fan center across subgroups and reuse the existing index fetch.
2026-09-16 08:42:47 +04:00
nmzik 957452d6f9 Emulate VRSQRTPS instruction 2026-09-16 08:28:36 +04:00
nmzikandAdrian Endlicher e6156bef05 Cache CI builds with sccache
Co-authored-by: Adrian Endlicher <godlike93@gmail.com>
2026-09-15 22:16:54 +04:00
nmzik 441367b1a1 Fix pixel shader input aliases 2026-09-15 20:46:51 +04:00
nmzik 629ae1ac05 Handle Z-pass predication and query waits 2026-09-15 20:15:39 +04:00
nmzik 0d9e95d96a Fix centroid interpolation weights
PPSA03792
2026-09-15 19:51:30 +04:00
nmzik 9cf7963f44 Use system FFmpeg for VP9 playback on Linux 2026-09-15 19:30:28 +04:00
nmzik acd7435d31 Support byte destinations in V_MOV_B32 2026-09-15 18:25:20 +04:00
nmzik 7af28c267d Map Z/C to L2/R2 2026-09-15 15:20:53 +04:00
nmzik 79120d53ec Handle chained PM4 buffers without recursion 2026-09-15 14:43:15 +04:00
nmzik 6a2987a7b2 Log the game title id 2026-09-15 14:09:24 +04:00
nmzik d1372abf46 Add launcher microphone selection and SDL AudioIn capture 2026-09-15 11:24:05 +04:00
nmzik 98b5d88d67 Implement AudioIn HQ open and close with paced silent input 2026-09-15 11:24:05 +04:00
nmzik 86414760e6 Cull zero homogeneous vertex positions before rasterization
Fix Playroom top right artifact on Nvidia by clipping zero-position vertices.
2026-09-15 11:24:05 +04:00
nmzik a1fbc41146 Preserve shared scalar loads during embedded vertex fetch replacement 2026-09-15 11:24:05 +04:00
nmzik 5702cfc935 Fix transfer-save mounts to honor the source title and missing data 2026-09-15 11:24:05 +04:00
nmzik 7b5a33f878 Fix PS5 front-face VGPR encoding to restore scene lighting 2026-09-15 07:01:10 +04:00
nmzik ea092a9472 Fix DCC clears through aliased image formats
Clear DCC images using the requested view format.
UNORM alpha 0xffff reads as NaN in a FLOAT view.
2026-09-15 07:01:10 +04:00
nmzikandBipin 18a1e0fadc Remove global condition waiter registry that deadlocks exception delivery
Co-authored-by: Bipin <bipinkrish4@gmail.com>
2026-09-14 21:34:01 +04:00
nmzik 437e69ef1b Remove extra DCC tracking 2026-09-14 20:29:20 +04:00
nmzik dd408ffd00 Resolve image aliases before buffer uploads 2026-09-14 20:29:20 +04:00
nmzik bce8924dd6 Use dynamic stencil state 2026-09-14 20:29:20 +04:00
nmzik fe26c1de17 Share SPIR-V module creation 2026-09-14 20:29:20 +04:00
nmzik 01df42a5f3 Fix DCC clears after metadata writes 2026-09-14 20:29:20 +04:00
nmzik 968cf3c03f Use the correct pixel shader wave size 2026-09-14 20:29:20 +04:00
nmzik 586cbd4a0d Fix separate stencil test and write values 2026-09-14 20:29:20 +04:00
nmzik 940e42f130 Share stencil setup for both faces 2026-09-14 20:29:20 +04:00
nmzik 3f80151a6f Respect vertex buffer sizes 2026-09-14 20:29:20 +04:00
nmzik 7e89b97daf Handle front shader allocation state 2026-09-14 20:29:20 +04:00
nmzik d63fb62367 Fix async compute completion interrupts 2026-09-14 20:29:20 +04:00
nmzik 29e9ea6344 Fix masked stencil replacements 2026-09-14 20:29:20 +04:00
nmzik 9a74ef15c3 Move stencil checks into op conversion 2026-09-14 20:29:20 +04:00
nmzikandBlackCAT a9ae04c152 graphics: decode observed FSR setup registers
Inspired by https://github.com/KytyPS5/KytyPS5/pull/607

Co-authored-by: BlackCAT <52720489+psnwd@users.noreply.github.com>
2026-09-14 10:30:28 +04:00
nmzikandBlackCAT 8e35e1233b hmd2: initialize reprojection and virtual headset field of view
Inspired by https://github.com/KytyPS5/KytyPS5/pull/607

Co-authored-by: BlackCAT <52720489+psnwd@users.noreply.github.com>
2026-09-14 10:30:28 +04:00
nmzikandBlackCAT 5610976720 videoout: map buses to dense port slots before opening
Inspired by https://github.com/KytyPS5/KytyPS5/pull/607

Co-authored-by: BlackCAT <52720489+psnwd@users.noreply.github.com>
2026-09-14 10:30:28 +04:00
nmzikandBlackCAT 1133272668 hmd2: initialize a virtual headset with --vr
Inspired by https://github.com/KytyPS5/KytyPS5/pull/607

Co-authored-by: BlackCAT <52720489+psnwd@users.noreply.github.com>
2026-09-14 10:30:28 +04:00
nmzik bbebb6419b graphics: preserve PS5 array mip contents during cache promotion
PPSA05975
2026-09-14 06:58:32 +04:00
nmzik b67cb81359 avplayer: report loop boundaries when decoded output is delivered 2026-09-14 06:58:32 +04:00
nmzikandBlackCAT f3c82a317a kernel: support timed umtx waits through shared address deadlines
Co-authored-by: BlackCAT <52720489+psnwd@users.noreply.github.com>
2026-09-14 06:58:32 +04:00
nmzik f426d60ff1 net: use the shared host resolver for Linux name lookups 2026-09-14 06:58:32 +04:00
nmzikandAli Almohaya b2fdad9e47 memory: preserve live POSIX mappings during partial unmap
Co-authored-by: Ali Almohaya <AliAlmohaya@gmail.com>
2026-09-14 06:58:32 +04:00
nmzik 87971a28f5 gpu: isolate tessellation analysis and SPIR-V emission 2026-09-14 06:58:32 +04:00
nmzik 96d62b3367 gpu: reflect tessellation strides from memory address dataflow 2026-09-14 06:58:32 +04:00
nmzik 7516068662 gpu: compile RDNA2 tessellation through native LS HS and TES stages 2026-09-14 06:58:32 +04:00
nmzik 6d1ba5870a renderer: share graphics resource preparation across shader stages 2026-09-14 06:58:32 +04:00
nmzik 00a16f3120 gpu: retain fused hull shader user-data addresses 2026-09-14 06:58:32 +04:00
nmzik d3d7bd33f8 launcher: Add game size column 2026-09-12 17:56:44 +04:00
Mehmet Cambaz 069f928edc net: return copied bytes for truncated datagram receives (#510)
Normalize Winsock WSAEMSGSIZE on receive to the copied byte count before converting the peer address. Preserve other receive errors and oversized send failures.
2026-09-12 17:51:43 +04:00
Mininautandnmzik 0f3674f531 kernel: release descriptors when rejecting read-only truncation (#504)
Co-authored-by: nmzik <Nmzik@mail.ru>
2026-09-12 17:44:45 +04:00
ADBHXNLOLandnmzik a21ffde34e shader: decode and translate V_FRACT_F16 (VOP1 0x5f) and V_CMPX_LT_U16 (VOPC 0xb9) (#582)
shader: support V_FRACT_F16 and V_CMPX_LT_U16

Decode compact, SDWA, and VOP3 forms using the existing arithmetic and comparison paths. Preserve EXEC masking and VCC for the integer compare, and reject unsupported DPP encoding.

Add captured-instruction, operand-selection, floating-point edge, and execution-mask regression coverage.

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-09-12 17:21:32 +04:00
nmzik 1208d22547 Fix Windows Clang SPIR-V serialization with typed operands 2026-09-12 13:08:23 +02:00
nmzik 3a27f8a76a build: fix SPIR-V enum word conversions 2026-09-12 15:43:47 +04:00
ADBHXNLOL dff2b19c56 json2: export Value::clear (#584)
Export the existing clear implementation while preserving tree ownership and root handler metadata.
2026-09-12 15:15:26 +04:00
Vandvmexe a58db4f023 docs: fix pre-commit setup instructions (#556)
Co-authored-by: vmexe <vmexe@users.noreply.github.com>
2026-09-12 14:58:34 +04:00
Frans 7bcd43dbee shader: support S_MUL_HI_I32 (SOP2 0x36) (#576) 2026-09-12 14:56:14 +04:00
nmzik 7c5473400e config: simplify logging and profiler options. Change to Mailbox 2026-09-12 14:41:34 +04:00
nmzik 23c8a43e37 Simplify and batch buffer cache downloads 2026-09-12 14:17:00 +04:00
nmzik 2d37634ee9 shader: construct SPIR-V module variables in one place 2026-09-12 13:42:15 +04:00
nmzik e026bfc9a2 shader: let structurization own rollback on failure 2026-09-12 13:42:15 +04:00
nmzik 5a31de7955 shader: keep derived frontend metadata with its owner 2026-09-12 13:42:15 +04:00
nmzik 20e57994cf shader: reuse native emitters and simplify memory dispatch 2026-09-12 13:42:15 +04:00
nmzik 59ef88c959 shader: generate SPIR-V dispatch and marshal handler arguments 2026-09-12 13:42:15 +04:00
nmzik 1d1f00bdef shader: use Khronos definitions and simplify compiler ownership 2026-09-12 13:42:15 +04:00
nmzik 47b26e497f pthread: fix per-thread wakeups 2026-09-12 13:40:10 +04:00
nmzik 733842cad4 kernel: index pthread key values per thread 2026-09-12 13:40:10 +04:00
nmzik 73363f1272 memory: remove per-page host protection bookkeeping 2026-09-12 13:40:10 +04:00
nmzik 0cd7682012 Rename texture transfer types and download APIs 2026-09-12 13:40:10 +04:00
nmzik 25a44371e3 refactor(graphics) 2026-09-12 13:40:10 +04:00
nmzik f2455c875e update tracy to 0.14.1 2026-09-10 19:34:37 +04:00
nmzik 2e315a3c62 ajm: parse gapless control data before codec initialization 2026-09-10 18:38:24 +04:00
nmzik d2fa86579b kernel: share directory streams across seeks and reads 2026-09-10 14:36:15 +04:00
nmzik 23e7df21b1 graphics: simplify texture upload and component mapping paths 2026-09-10 14:36:15 +04:00
nmzik a305a6c1c6 graphics: share packed texture and render-target channel layouts
Resolve PS5 5551 textures with alpha in bit 15 and compose host channel order with guest selectors. Share the existing render-target mapping with sampled textures so packed layouts cannot diverge.

Add native GPU readback coverage for 5551 and 4444, arbitrary selectors, constants, and existing 1555 render-target mapping. Verified PPSA24515 through launch.json with Vulkan validation, advancing scenes and over 12,700 guest flips. Independent correctness and cleanliness audits passed.
2026-09-10 14:36:15 +04:00
nmzik 635fb8882b systemservice: use configured language for PS5 parameter 400 2026-09-10 14:11:46 +04:00
nmzik fe6f49632c font: accept supported OpenType CFF faces 2026-09-10 14:11:46 +04:00
nmzikandBrandon Strong c3546577b6 shader: implement S_WQM_B32
Co-authored-by: Brandon Strong <brandostrong@gmail.com>
2026-09-10 14:11:46 +04:00
nmzik c913951f7b shader: implement RDNA2 subvector loop mask and branch semantics 2026-09-10 14:11:46 +04:00
nmzik 3902b42096 shader: share direct and conditional branch classification 2026-09-10 14:11:46 +04:00
nmzik ae573f941a graphics: share vertex format mapping and support packed float colors 2026-09-10 14:11:46 +04:00
nmzik 20ea54cf0c Json2: new ABI 2026-09-10 14:11:46 +04:00
nmzik 0b4e78c0b2 graphics: execute DB_RENDER_OVERRIDE depth and stencil copies 2026-09-09 15:26:52 +04:00
nmzik 57844a07e2 buffer cache: grow stream reserves toward incoming requests 2026-09-09 15:26:52 +04:00
nmzik ba18551d6e ces: implement CP932 string conversion 2026-09-09 15:26:52 +04:00
nmzikandAli Almohaya c7b0d77970 loader: port guest fault diagnostics and remove host stack traces
Co-authored-by: Ali Almohaya <AliAlmohaya@gmail.com>
2026-09-09 13:55:53 +04:00
nmzik 1af19eabf0 shader: fix incorrect per-lane VCCZ/EXECZ reads 2026-09-09 13:22:09 +04:00
nmzik 32ea0869e5 shader: implement S_ORN2_SAVEEXEC_B32 2026-09-09 13:11:48 +04:00
nmzik e04007a7c6 shader: implement DS_INC_RTN_U32 and DS_DEC_RTN_U32 2026-09-09 12:56:20 +04:00
frangametvandnmzik 1a3a15e4cf Fix mount root opens and empty APR prefixes (#439)
* Fix opening mount root directories

* Allow empty APR path prefixes

---------

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-09-09 12:31:23 +04:00
nmzik 679adbfa5e graphics: fix linear render-target pitch alignment 2026-09-09 02:45:59 +02:00
nmzikandEttore Sartori d285efe3b5 graphics: allow absent programmable restart indices
Co-authored-by: Ettore Sartori <85788613+Ekt0re@users.noreply.github.com>
2026-09-08 21:26:59 +04:00
nmzik a64c8d3205 kernel: implement umtx wait/wake and reject malformed timeouts 2026-09-08 21:26:59 +04:00
nmzik 82c52c3fab window: round FPS in the title 2026-09-08 21:26:59 +04:00
nmzik dd1e90fcd7 video: keep decoding after Flush 2026-09-08 21:26:59 +04:00
nmzik ef0155636a shader: support single and split NGG shaders for PPSA11142 2026-09-08 21:26:59 +04:00
nmzik 9d550fbffa graphics: fix last provoking vertex on PS5 2026-09-08 21:26:59 +04:00
nmzik fcef691e0a shader: drop EmitTrueBool 2026-09-08 21:26:59 +04:00
nmzik 839c172612 shader: reuse operands and remove duplicate helpers 2026-09-08 21:26:59 +04:00
nmzik 9f49ac8249 shader: reuse SPIR-V helpers and memory resources 2026-09-08 21:26:59 +04:00
nmzik fde550ef66 graphics: simplify texture cleanup and swizzle decoding 2026-09-08 21:26:59 +04:00
nmzik 2b3993a595 shader: share emitter helpers 2026-09-08 21:26:59 +04:00
nmzik de3e86791c shader: reuse CFG and validation lookups 2026-09-08 21:26:59 +04:00
nmzik 6f66f77b09 shader: drop unused decoder fields 2026-09-08 21:26:59 +04:00
nmzik d853aba955 graphics: skip temporary cache lookup vectors 2026-09-08 21:26:59 +04:00
nmzik cd62693792 shader: share control flow labels 2026-09-08 21:26:59 +04:00
nmzik cb4c8f3878 shader: decode buffer formats once 2026-09-08 21:26:59 +04:00
nmzik 4d371d5f5f shader: reuse initial SSA types 2026-09-08 21:26:59 +04:00
nmzik 72963b01e1 shader: remove redundant decoder and translator state 2026-09-08 21:26:59 +04:00
nmzik 80a18e82b7 graphics: simplify image allocation 2026-09-08 21:26:58 +04:00
nmzik 574e687734 graphics: drop unused completion overload 2026-09-08 21:26:58 +04:00
nmzik c725ec50ff graphics: clean up draw setup 2026-09-08 21:26:58 +04:00
Brandon Strong c6f6e306e0 shader: raise the dense image resource limit to 64 (#457) 2026-09-08 19:37:41 +04:00
nmzik b847135321 launcher: guard optional Qt Vulkan support on macOS 2026-09-08 04:29:01 +04:00
nmzik ca3dc8792a libs: implement NpCommerce lifecycle 2026-09-08 04:25:35 +04:00
nmzik ac3156df3a graphics: fix Vulkan BC image compatibility checks` 2026-09-07 23:41:19 +02:00
nmzik 6aa6e390ee renderer: skip unsupported primitives (print log) 2026-09-07 15:20:09 +04:00
nmzik e4c10de8b6 launcher: add GPU selection 2026-09-07 15:12:35 +04:00
nmzik b3b3aeef73 graphics: simplify EOP recording 2026-09-07 15:00:52 +04:00
nmzik 056dcee70c graphics: simplify buffer allocations 2026-09-07 15:00:52 +04:00
nmzik 13c5bbe916 graphics: simplify pipeline keys 2026-09-07 15:00:52 +04:00
bbe3bd0304 Audio: support 12-channel AudioOut2 output with SDL (#498)
* Audio: support 12-channel AudioOut2 output via stereo downmix

* Audio: document 7.1.4 downmix matrix and align s16 coefficients

* audio: use shared 12-to-8 channel conversion for SDL

---------

Co-authored-by: Airsoft Project <airsoft@example.com>
Co-authored-by: nmzik <Nmzik@mail.ru>
2026-09-07 12:58:23 +04:00
Tarunandnmzik f9b279c49d launcher: verify updates against GitHub and check release dates (#482)
* launcher: verify with GitHub when website feed differs and compare tag dates

* launcher: restore kyty_emulator dependency for CI build and install

* launcher: simplify GitHub update verification

---------

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-09-07 12:25:54 +04:00
nmzik b61360a0c0 fix: share thread signal stacks with macOS 2026-09-07 12:17:30 +04:00
Ali Almohaya 27ee6f02d6 fix: windows build failure in UTF-16 conversion (#499) 2026-09-07 11:52:22 +04:00
nmzik 2097ebb414 render: support overlapping fragment depth feedback 2026-09-07 11:15:47 +04:00
nmzik 587df9145f render: validate sampled depth descriptors independently of target history
Comparison textures can carry valid HTILE descriptors before any depth-target binding. Keep encoding checks independent of cached image metadata, matching SDK texture classification and shadPS4 depth promotion. Preserve HTILE address and control validation, and replace cache-history test fixtures with captured first-use coverage.
2026-09-07 11:12:48 +04:00
nmzik 2c5060e14f shader: remove masked-value pass and normalize ancillary fields 2026-09-07 11:12:48 +04:00
nmzik 75aaa963d2 update screenshot 2026-09-07 09:31:15 +04:00
nmzik 34ebfe40c3 shader: report compiled counts by guest stage 2026-09-07 07:34:01 +04:00
nmzikandMehmetCambaz 2a54310876 render: fix depth write state before choosing layouts
Co-authored-by: MehmetCambaz <mcambaz18@gmail.com>
2026-09-07 07:12:58 +04:00
nmzik 8bccd325ec shader: keep mask predicates across 64-bit moves 2026-09-07 07:04:55 +04:00
nmzik ad935311f0 Revert "render: add depth attachment feedback support" 2026-09-07 07:01:53 +04:00
nmzik 19fc0274de shader: validate shared terminal paths in selections 2026-09-07 06:57:55 +04:00
nmzikandBrandon Strong 6cc9e5de11 shader: fix wave32 VCC halves and scalar mask moves
Co-authored-by: Brandon Strong <brandostrong@gmail.com>
2026-09-07 06:04:40 +04:00
nmzik bf7ca29487 shader: decode VOP3 FMAC source modifiers 2026-09-07 05:25:26 +04:00
nmzik 3cf426662e audio: fix microphone input format decoding 2026-09-07 05:04:15 +04:00
nmzik 1fd07c2c83 shader: assemble point inputs for merged geometry shaders 2026-09-07 05:04:15 +04:00
nmzikandDeathWhispers f880c58ccc render: add depth attachment feedback support
Co-authored-by: DeathWhispers <56944463+Death-Whispers-U@users.noreply.github.com>
2026-09-07 04:25:10 +04:00
49e59e572e shader: separate depth comparison and regular image descriptors
Co-authored-by: Lordix <lordix253634@gmail.com>
Co-authored-by: fxpw <fxpw@outlook.com>
Co-authored-by: Mac <me@itsmemac.com>
2026-09-07 04:06:14 +04:00
nmzikandfxpw 9d1fc8328f render: allow supported unrestricted float depth uploads
Co-authored-by: fxpw <fxpw@outlook.com>
2026-09-06 22:00:40 +04:00
c9ff09c7c2 textures: keep native formats unless depth promotion is known
Co-authored-by: Ettore Sartori <85788613+Ekt0re@users.noreply.github.com>
Co-authored-by: Mac <me@itsmemac.com>
2026-09-06 21:40:58 +04:00
nmzik ceb2b664e0 render: reset rasterization defaults on CLEAR_STATE 2026-09-06 21:25:12 +04:00
nmzik b5b4c06179 textures: share depth comparison view geometry 2026-09-06 20:18:31 +04:00
nmzik d70f9c0b2b launcher: add readback option 2026-09-06 19:48:21 +04:00
nmzik e847f82fab shader: initialize pixel position W as a reciprocal 2026-09-06 19:47:43 +04:00
nmzik 807a594c2d textures: keep atlas data across compressed block views 2026-09-06 18:57:26 +04:00
nmzik ced73c56cc shader: fix spilled mesh shader data loads 2026-09-06 17:17:51 +04:00
nmzik 9c6f6fa624 json2: add array and object value lifecycles 2026-09-06 14:48:49 +04:00
nmzik 533176f9ff shader: drop inactive pixel values under whole-quad masks 2026-09-06 14:38:32 +04:00
nmzik 66c1663495 audio: use the shared AAC decoder for Audiodec 2026-09-06 14:16:27 +04:00
nmzik 30c7715d3f audio: separate the decoder interface from AJM jobs 2026-09-06 14:07:12 +04:00
nmzik 093911a1dc audio: check packet buffers only during decode 2026-09-06 14:04:27 +04:00
nmzikandCheesyPoofs346 d83f1f8235 memory: handle GPU faults at the mapped boundary
Co-authored-by: CheesyPoofs346 <jjs777406@gmail.com>
2026-09-06 11:08:05 +04:00
nmzik 50e78d717d render: use guest polygon modes 2026-09-06 10:56:59 +04:00
nmzik 6ca8201982 textures: handle DCC memory reused as HTile 2026-09-06 10:34:02 +04:00
nmzik 99a87bd65b shader: keep loop branches during selection routing 2026-09-06 10:23:44 +04:00
nmzikandEttore Sartori f9f8e12c35 shader: add viewport-index exports and indexed raster state
Co-authored-by: Ettore Sartori <85788613+Ekt0re@users.noreply.github.com>
2026-09-06 10:16:38 +04:00
nmzik dcaf4205cb render: track pages for DMA fills and copies 2026-09-06 10:16:27 +04:00
nmzik 4735359466 textures: invalidate stencil links on CPU writes 2026-09-06 09:38:22 +04:00
nmzik 211242452b kernel: use per-thread signal stacks for guest stack faults 2026-09-06 09:28:19 +04:00
nmzik 6ae5cb7b64 system: keep controller focus in overlays 2026-09-06 09:15:29 +04:00
nmzik 997ad60c50 system: keep error dialogs open until dismissed 2026-09-06 08:34:58 +04:00
44f982bca7 render: apply DCC clears before binding images
Co-authored-by: Techx3 <56268269+Techx3@users.noreply.github.com>
Co-authored-by: Brandon Strong <brandostrong@gmail.com>
2026-09-06 08:00:59 +04:00
nmzik 0456f5a06b libc: fix UTF-16 printf strings and precision limits 2026-09-06 07:28:19 +04:00
nmzik b346f58b73 shader: read sample IDs from pixel ancillary input 2026-09-06 06:54:38 +04:00
nmzik ace877ce28 shader: lower FMASK loads before binding images 2026-09-06 06:42:23 +04:00
nmzik cf0cbee02d shader: fix vertex attribute identity swizzles 2026-09-06 05:47:20 +04:00
nmzik 98e7eeb72a textures: keep dirty pages during image-to-buffer sync 2026-09-06 05:36:41 +04:00
nmzik 803c124136 textures: reuse range lookups for buffer sync 2026-09-06 05:07:44 +04:00
nmzik d9d17737c3 net: return native SO_ERROR 2026-09-06 05:07:44 +04:00
nmzik 46850f9574 net: fix socket wakeups and readiness checks 2026-09-06 05:00:12 +04:00
nmzik 314d37155a net: add TCP_NODELAY and translate socket errors 2026-09-06 04:51:07 +04:00
nmzik ddcdb654ad net: share native connect and accept 2026-09-06 04:47:15 +04:00
nmzik cdec5c515b net: use native listen on POSIX 2026-09-06 04:45:53 +04:00
nmzik 1eb13dfa5e render: call Vulkan diagnostics and capability checks directly 2026-09-06 04:11:46 +04:00
nmzik dda628ca81 render: simplify command and resource ownership 2026-09-06 04:05:35 +04:00
nmzik 13ad9f510d render: clean up cache and image internals 2026-09-06 03:55:13 +04:00
nmzik 4c769dfbf9 render: simplify buffer and memory tracking ownership 2026-09-06 03:46:34 +04:00
nmzikandDeathWhispers ccd19b3e56 render: choose depth layouts independently of load clears
Co-authored-by: DeathWhispers <56944463+Death-Whispers-U@users.noreply.github.com>
2026-09-06 03:35:56 +04:00
nmzik 81d0ea5697 render: ignore stale export formats with no pixel shader 2026-09-05 23:36:42 +04:00
nmzik 43de8bfaac shader: cache integer dependency checks 2026-09-05 20:35:18 +04:00
nmzik 86b539a9ea tests: clear BDA page tables before transfer updates 2026-09-05 16:04:58 +04:00
nmzik 81a5dd612f shader: fix embedded vertex fetch swizzles 2026-09-05 16:00:46 +04:00
nmzik 9c00c7077b shader: keep per-invocation mesh exports out of shared memory 2026-09-05 15:48:29 +04:00
nmzikandshadowbeat070 49ff962f69 shader: add conditional 64-bit scalar moves
Co-authored-by: shadowbeat070 <70843316+shadowbeat070@users.noreply.github.com>
2026-09-05 15:38:51 +04:00
nmzik c83aac966e textures: keep incompatible mip layouts separate 2026-09-05 15:24:07 +04:00
nmzikandshadowbeat070 9918dc2591 render: set merged GS user data from shader registers
Co-authored-by: shadowbeat070 <70843316+shadowbeat070@users.noreply.github.com>
2026-09-05 15:08:20 +04:00
nmzikandshadowbeat070 3ab372ccec render: build indexed triangles for merged ES/GS shaders
Co-authored-by: shadowbeat070 <70843316+shadowbeat070@users.noreply.github.com>
2026-09-05 14:59:28 +04:00
nmzik 843c0c59be render: use Vulkan-Hpp setup defaults 2026-09-05 14:42:14 +04:00
nmzik d93812b0bd render: simplify pool wrapping and scissor clipping 2026-09-05 14:37:11 +04:00
nmzik 1c68c94c05 render: remove redundant cache setup 2026-09-05 14:37:04 +04:00
nmzik c66060be10 render: simplify pipeline setup 2026-09-05 14:36:52 +04:00
nmzik e7b269a9f6 render: clean up image setup and tiler recording 2026-09-05 14:36:42 +04:00
nmzik d5b19850fb render: clean up texture transfers and compute fills 2026-09-05 13:20:52 +04:00
nmzikandVyas Devgna e7fe8192bb tests: wait for interrupt callbacks before the next queue
Co-authored-by: Vyas Devgna <vyasdevgna@gmail.com>
2026-09-05 13:20:16 +04:00
nmzik ebe0979563 shader: share runtime value checks 2026-09-05 13:16:54 +04:00
nmzik a379304362 present: wait for image acquisition before transitioning 2026-09-05 12:40:54 +04:00
nmzik bec7c510c2 render: use native clears for stencil fill shaders 2026-09-05 12:36:13 +04:00
nmzikandBrandon Strong 6a187ac67e render: upload the full stride-zero vertex attribute
Co-authored-by: Brandon Strong <brandostrong@gmail.com>
2026-09-05 12:17:39 +04:00
9641b51925 render: fix tiled depth comparison textures
Co-authored-by: Ettore Sartori <85788613+Ekt0re@users.noreply.github.com>
Co-authored-by: Mac <me@itsmemac.com>
2026-09-05 11:44:11 +04:00
nmzikandshadowbeat070 b96a30ed88 shader: keep fragment layer values under lane masks
Co-authored-by: shadowbeat070 <70843316+shadowbeat070@users.noreply.github.com>
2026-09-05 11:43:49 +04:00
nmzik 2ec0e9e8f3 shader: fix packed image address reads 2026-09-05 11:32:32 +04:00
nmzik 51afce3a97 shader: zero-extend unsigned SOPK comparisons 2026-09-05 10:38:29 +04:00
nmzikandTechx3 dea80f34ca shader: detect full buffer fills from uniform stores
Co-authored-by: Techx3 <56268269+Techx3@users.noreply.github.com>
2026-09-05 10:34:30 +04:00
nmzik 994271e2f6 renderdoc: start and stop captures on the present thread 2026-09-05 09:54:42 +04:00
59902d1fd8 shader: skip resources in untaken uniform branches
Co-authored-by: GodsWill <GodsWill@users.noreply.github.com>
Co-authored-by: Techx3 <56268269+Techx3@users.noreply.github.com>
2026-09-05 09:38:50 +04:00
nmzikandDeathWhispers b92f0a5458 render: share layouts with sampled attachments
Co-authored-by: DeathWhispers <56944463+Death-Whispers-U@users.noreply.github.com>
2026-09-05 08:54:41 +04:00
nmzik 251f6d6121 shader: fix returns and descriptor placement 2026-09-05 08:54:35 +04:00
nmzik 0d8c83a5cb tests: enable Vulkan gather and derivative features 2026-09-05 08:54:14 +04:00
nmzik 3624d96c78 render: fix depth mapping for -1 to 1 viewports 2026-09-05 08:40:05 +04:00
3213934d42 shader: run merged geometry stages as mesh shaders
Co-authored-by: Brandon Strong <brandostrong@gmail.com>
Co-authored-by: fxpw <fxpw@outlook.com>
Co-authored-by: shadowbeat070 <70843316+shadowbeat070@users.noreply.github.com>
2026-09-05 08:32:23 +04:00
nmzik 246212789f agc: merge resources from both shader stages 2026-09-05 07:39:30 +04:00
nmzikandLordix c7f7fd974b textures: use the backing format for alias uploads
Co-authored-by: Lordix <lordix253634@gmail.com>
2026-09-05 07:30:05 +04:00
5cf2f5767a render: fix volume attachment slice bounds
Co-authored-by: SueryRD <the.ai.tester2@gmail.com>
Co-authored-by: Techx3 <56268269+Techx3@users.noreply.github.com>
2026-09-05 07:11:46 +04:00
nmzik e8e580cf6c fix ge 2026-09-05 06:53:59 +04:00
nmzik 74a78f37d0 renderer: grow repeatedly joined buffer ranges (perf) 2026-09-05 06:30:31 +04:00
nmzikandAli Almohaya 8d3ebb5a55 renderer: widen buffer readback windows
Co-authored-by: Ali Almohaya <17254073+Almo7aya@users.noreply.github.com>
2026-09-05 05:44:42 +04:00
nmzik 3628d2b4c1 renderer: remove dynamic draw state from pipeline keys 2026-09-05 04:41:41 +04:00
nmzik d96d0eeb18 libPad: ABIs 2026-09-04 07:42:23 +02:00
nmzik bd401a1407 shader: ignore inactive auxiliary position exports 2026-09-04 07:42:23 +02:00
nmzik 070730be0a kernel: report PS5 Base operation mode 2026-09-04 07:42:23 +02:00
nmzik 94e7d0a189 renderer: accept inactive coarse HTILE; drop dead MRT filter 2026-09-04 07:42:23 +02:00
nmzik c28b71f8e0 gpu: centralize depth shading-rate handling 2026-09-04 05:55:28 +02:00
nmzik 27f015ecd1 Revert "graphics: preserve unrestricted viewport depth ranges"
This reverts commit eccd6f6cac.
2026-09-04 05:22:17 +02:00
nmzik 8596627ced shader: share SPIR-V atomic update lowering 2026-09-04 05:10:31 +02:00
nmzik d4cca82a22 libs: stub WebBrowserDialogGetStatus 2026-09-04 05:07:58 +02:00
nmzik dd9ecd5f65 Revert "shader: separate comparison image bindings"
This reverts commit d762d24f73.
2026-09-04 03:09:00 +02:00
nmzik 81b0c1302d Revert "graphics: create depth images for comparison textures"
This reverts commit 0ce19357f6.
2026-09-04 03:08:56 +02:00
Tarunandnmzik b9b0b87545 Add update checker to launcher (#478)
launcher: add release update checker

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-09-04 02:38:02 +02:00
nmzik 040b2b9653 tests: align GPU fixtures with binding architecture 2026-09-04 02:06:06 +02:00
nmzik eccd6f6cac graphics: preserve unrestricted viewport depth ranges 2026-09-04 02:06:06 +02:00
nmzik 0ce19357f6 graphics: create depth images for comparison textures 2026-09-04 02:06:06 +02:00
nmzik d762d24f73 shader: separate comparison image bindings 2026-09-04 02:06:06 +02:00
nmzik eb917463ad fix File abstraction destructor 2026-09-04 02:06:06 +02:00
Mehmet Cambaz c2189de88f controller: give the DualShock 4 the DualSense pad path and release p… (#453)
controller: give the DualShock 4 the DualSense pad path and release pads on exit
2026-09-04 00:10:57 +02:00
GodsWill Devandnmzik 2fe5c150bc shader: implement BUFFER_ATOMIC_CMPSWAP (#357)
shader: implement RDNA2 buffer atomic compare swap

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-09-03 23:34:34 +02:00
nmzik 5b8e3b51b0 cross-vendor TLS fix: 0x66 prefixes are forced into 64bit call on AMD 2026-09-03 23:03:48 +02:00
nmzik f27a136430 Refactor(simplify) PM4 draw submission arguments 2026-09-03 22:23:13 +02:00
nmzik fb4d97e8a9 graphics: fix PS5 NGG vertex and instance draw offsets
Refs: https://github.com/KytyPS5/KytyPS5/issues/130
2026-09-03 21:52:02 +02:00
nmzik 5ffcd28ca4 launcher: fix rare use after free bug 2026-09-03 11:32:32 +02:00
nmzik b3c8f91a84 graphics: implement RDNA2 buffer atomic swap x2 2026-09-03 10:17:04 +02:00
nmzik c78a5a9fdf graphics: unify atomic feature and width handling 2026-09-03 05:45:45 +02:00
nmzik 858c18468b graphics: implement RDNA2 buffer atomic OR x2 2026-09-03 05:39:05 +02:00
nmzik cd74809086 launcher: fix light mode, group settings into resizable sections 2026-09-03 04:13:34 +02:00
Brandon Strongandnmzik e21c5f37d0 graphics: honour DCC fixed clear codes on 16-bit and float colour targets (#471)
* Honour DCC fixed clear codes on 16-bit and float colour targets

* tests: DCC fixed clear codes materialise on an RGBA16F target

* graphics: remove fixed clear format comment

---------

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-09-03 03:41:28 +02:00
Brandon Strong 2d5023fda2 shader: support IMAGE_ATOMIC_SWAP (#456) 2026-09-03 03:37:55 +02:00
nmzik 98200fed19 shader: rework resource specialization and optimize pipeline caching 2026-09-03 03:04:17 +02:00
nmzik c48a8bd6ae shader: evaluate uniform first-lane sampler descriptors 2026-09-03 03:04:17 +02:00
Brandon Strongandnmzik 16ea36eb4b libkernel: register scePthreadRwlockTryrdlock and the timed rwlock variants (#472)
* libkernel: register scePthreadRwlockTryrdlock and the timed rwlock variants

* fix: initialize static rwlocks in newly registered APIs

---------

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-09-03 01:58:38 +02:00
Brandon Strong 8f6f65f4c9 libs: implement Json Value::referValue by key and Value::toString (#455) 2026-09-03 01:58:23 +02:00
Brandon Strongandnmzik b81f1fc774 libs: implement sceConvertKeycodeGetImeKeyboardType (#469)
* libs: implement sceConvertKeycodeGetImeKeyboardType

* fix: correct ConvertKeycode error codes

---------

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-09-03 01:57:10 +02:00
Brandon Strong c85bd45604 kernel: implement sceKernelClearVirtualRangeName (#466) 2026-09-03 01:50:14 +02:00
nmzik 5c86f894be shader: simplify typed image bindings 2026-09-02 15:45:16 +02:00
nmzik 86e3c90e32 shader: simplify image resource semantics 2026-09-02 15:45:15 +02:00
nmzik 93be8c201b shader: unify SPIR-V image dimension 2026-09-02 15:44:39 +02:00
nmzik 894bc7de3c shader: support signed sampled images 2026-09-02 15:39:30 +02:00
nmzik fc91fd2b1a libs: simplify ABI registration facades 2026-09-02 02:06:14 +04:00
nmzik a06a005124 controller: simplify input facade API 2026-09-02 02:06:14 +04:00
nmzikandPouare514 e9425e101d shader: add missing RDNA2 ALU opcodes
Add missing scalar and vector opcodes with compute tests.

Original work by @Pouare514 in #176.

Co-authored-by: Pouare514 <julienaulycee@gmail.com>
2026-09-02 01:56:55 +04:00
CheesyPoofs3460andnmzik 04b404f3c9 controller: enable the DualSense HIDAPI driver, light bar and adaptive triggers (#437)
* controller: enable the DualSense HIDAPI driver, light bar and adaptive triggers

Three additions. SDL was opening a DualSense through the generic gamepad path, so SDL_GameControllerHasLED and SDL_GameControllerGetNumTouchpads both reported nothing and no touchpad events ever arrived - enabling the PS5 HIDAPI driver before SDL_InitSubSystem is what makes the light bar and the existing touchpad support reachable. PadSetLightBar validated its arguments and returned OK without touching the pad; it now drives the LED. PadSetTriggerEffect did the same; it now translates the mode and parameters into a DualSense trigger block and sends it.

* controller: fix DualSense effects

---------

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-09-02 00:53:19 +04:00
nmzik 80225015c5 renderer: enable storage for color images 2026-09-01 23:54:17 +04:00
nmzik 0c4dfd565e renderer: emulate clip-disabled screen-space positions 2026-09-01 21:39:11 +04:00
nmzik 0c8d7a1e49 shader: DPP row-xmask 2026-09-01 21:02:19 +04:00
nmzik a2150c70bb network: implement Linux getsockname 2026-09-01 20:50:57 +04:00
nmzik 160cf5352c http: new abi 2026-09-01 20:50:57 +04:00
nmzik 3b6b3c2040 pm4: handle reserved depth control fields 2026-09-01 20:50:57 +04:00
nmzik 63dd3917e2 reduce runtime logging 2026-09-01 20:50:57 +04:00
nmzik 2333c235f1 loader: TLS patch fix 2026-09-01 20:50:57 +04:00
nmzik dd062ceeeb prevent Escape from exiting 2026-09-01 07:50:37 +04:00
nmzik e591a66b67 Revert "shader: preserve raw VCC bits in S_MOV_B64 (#418)"
This reverts commit 6758d67942.

An EXEC pair loses its mask provenance and falls into the raw high-word path. The saved all-ones high word then keeps waterfall branches true and wedges GPU work.
2026-09-01 07:50:37 +04:00
Brandon Strongandnmzik b8a459e83d graphics: do not arm a fast clear from a read-modify-write dispatch (#428)
* graphics: do not arm a fast clear from a read-modify-write dispatch

* tests: cover compute metadata clear classification

---------

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-09-01 05:48:06 +04:00
nmzik 7684c343d6 guest_gpu: fix private AGC register regression 2026-09-01 04:24:47 +04:00
329 changed files with 34479 additions and 22082 deletions

No files matched your search

+37
View File
@@ -0,0 +1,37 @@
# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json
# Keep inherited review profiles, automatic reviews, tools, and pre-merge checks.
inheritance: true
reviews:
# Trim supplemental output and keep the remaining walkthrough collapsed.
high_level_summary: false
review_status: false
review_details: false
collapse_walkthrough: true
changed_files_summary: false
sequence_diagrams: false
estimate_code_review_effort: false
related_issues: false
related_prs: false
in_progress_fortune: false
poem: false
enable_prompt_for_ai_agents: false
# Hide standard finishing-touch controls; this also disables their commands.
finishing_touches:
docstrings:
enabled: false
unit_tests:
enabled: false
simplify:
enabled: false
autofix:
enabled: false
fix_ci:
enabled: false
resolve_merge_conflict:
enabled: false
chat:
art: false
+21
View File
@@ -6,7 +6,12 @@ on:
branches: [ main, master ]
pull_request:
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
env:
SCCACHE_GHA_ENABLED: "true"
KYTY_BUILD_ORIGIN: ${{ github.repository != 'KytyPS5/KytyPS5' && 'Fork' || github.event_name == 'push' && 'Official' || 'Source' }}
KYTY_BUILD_REPOSITORY: ${{ github.repository }}
@@ -78,6 +83,9 @@ jobs:
arch: win64_msvc2022_64
cache: true
- name: Setup sccache
uses: mozilla-actions/sccache-action@v0.0.11
- name: Verify toolchain
shell: cmd
run: |
@@ -100,6 +108,8 @@ jobs:
-DKYTY_RELEASE_TAG=${{ needs.metadata.outputs.release_tag }} ^
-DCMAKE_C_COMPILER=clang-cl ^
-DCMAKE_CXX_COMPILER=clang-cl ^
-DCMAKE_C_COMPILER_LAUNCHER=sccache ^
-DCMAKE_CXX_COMPILER_LAUNCHER=sccache ^
-DCMAKE_PREFIX_PATH="%Qt6_DIR%"
- name: Build
@@ -156,6 +166,9 @@ jobs:
arch: clang_64
cache: true
- name: Setup sccache
uses: mozilla-actions/sccache-action@v0.0.11
- name: Verify toolchain
shell: bash
run: |
@@ -180,6 +193,8 @@ jobs:
-DCMAKE_OSX_ARCHITECTURES=x86_64 \
-DCMAKE_C_COMPILER=clang \
-DCMAKE_CXX_COMPILER=clang++ \
-DCMAKE_C_COMPILER_LAUNCHER=sccache \
-DCMAKE_CXX_COMPILER_LAUNCHER=sccache \
-DCMAKE_PREFIX_PATH="$QT_ROOT_DIR"
- name: Build
@@ -304,6 +319,7 @@ jobs:
libxss-dev \
lld \
ninja-build \
pkg-config \
wayland-protocols
- name: Install Qt 6.10.3
@@ -315,6 +331,9 @@ jobs:
arch: linux_gcc_64
cache: true
- name: Setup sccache
uses: mozilla-actions/sccache-action@v0.0.11
- name: Verify toolchain
shell: bash
run: |
@@ -339,6 +358,8 @@ jobs:
-DKYTY_RELEASE_TAG="${{ needs.metadata.outputs.release_tag }}" \
-DCMAKE_C_COMPILER=clang \
-DCMAKE_CXX_COMPILER=clang++ \
-DCMAKE_C_COMPILER_LAUNCHER=sccache \
-DCMAKE_CXX_COMPILER_LAUNCHER=sccache \
-DCMAKE_PREFIX_PATH="$Qt6_DIR" 2>&1 | tee _Build/configure.log
exit "${PIPESTATUS[0]}"
+1 -1
View File
@@ -22,7 +22,7 @@
url = https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git
[submodule "3rdparty/ffmpeg-core"]
path = 3rdparty/ffmpeg-core
url = https://github.com/shadps4-emu/ext-ffmpeg-core.git
url = https://github.com/KytyPS5/ext-ffmpeg-core.git
shallow = true
[submodule "3rdparty/fmt"]
path = 3rdparty/fmt
+6 -4
View File
@@ -2,15 +2,17 @@
"cmake.sourceDirectory": "${workspaceFolder}",
"cmake.buildDirectory": "${workspaceFolder}/_Build/vscode-clang",
"cmake.generator": "Ninja",
"cmake.useVsDeveloperEnvironment": "always",
"cmake.environment": {
"CC": "clang-cl",
"CXX": "clang-cl",
"PATH": "${env:ProgramFiles}/Microsoft Visual Studio/18/Community/VC/Tools/Llvm/x64/bin;${env:ProgramFiles}/Microsoft Visual Studio/18/Community/Common7/IDE/CommonExtensions/Microsoft/CMake/Ninja;${env:PATH}"
"CXX": "clang-cl"
},
"cmake.configureSettings": {
"CMAKE_C_COMPILER": "clang-cl",
"CMAKE_CXX_COMPILER": "clang-cl",
"CMAKE_PREFIX_PATH": "C:/Qt/6.11.0/msvc2022_64"
}
},
"C_Cpp.default.intelliSenseMode": "windows-clang-x64",
"C_Cpp.default.compileCommands": "${workspaceFolder}/_Build/vscode-clang/compile_commands.json",
"C_Cpp.default.cppStandard": "c++20"
}
+3
View File
@@ -86,6 +86,9 @@ set(SPDLOG_BUILD_WARNINGS OFF CACHE BOOL "" FORCE)
set(SPDLOG_INSTALL OFF CACHE BOOL "" FORCE)
set(SPDLOG_FMT_EXTERNAL ON CACHE BOOL "" FORCE)
set(SPDLOG_NO_EXCEPTIONS ON CACHE BOOL "" FORCE)
if(WIN32)
set(SPDLOG_WCHAR_FILENAMES ON CACHE BOOL "" FORCE)
endif()
add_subdirectory(spdlog EXCLUDE_FROM_ALL)
if (NOT TARGET FFmpeg::ffmpeg)
+1 -1
+39 -23
View File
@@ -126,7 +126,7 @@ if (KYTY_LINKER STREQUAL LD AND NOT LINUX)
set(KYTY_LD_OPTIONS "-Wl,--image-base=0x100000000000")
endif()
project(Kyty${KYTY_PROJECT_NAME}${CMAKE_BUILD_TYPE}${KYTY_COMPILER} VERSION 0.2.2)
project(Kyty${KYTY_PROJECT_NAME}${CMAKE_BUILD_TYPE}${KYTY_COMPILER} VERSION 0.3.0)
include_directories(
${KYTY_THIRD_PARTY_DIR}/gtest/include
@@ -317,24 +317,10 @@ 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)
# Linux system libraries required by the static FFmpeg archive.
# Linux system libraries.
if(LINUX)
find_package(Threads REQUIRED)
list(APPEND kyty_emulator_link_libraries m ${CMAKE_DL_LIBS} Threads::Threads)
# Optional FFmpeg dependencies.
find_package(ZLIB)
if(ZLIB_FOUND)
list(APPEND kyty_emulator_link_libraries ZLIB::ZLIB)
endif()
find_library(KYTY_BZ2_LIBRARY bz2)
if(KYTY_BZ2_LIBRARY)
list(APPEND kyty_emulator_link_libraries ${KYTY_BZ2_LIBRARY})
endif()
find_library(KYTY_LZMA_LIBRARY lzma)
if(KYTY_LZMA_LIBRARY)
list(APPEND kyty_emulator_link_libraries ${KYTY_LZMA_LIBRARY})
endif()
endif()
set(inc_headers
@@ -342,6 +328,7 @@ set(inc_headers
${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
${KYTY_THIRD_PARTY_DIR}/fmt/include
${KYTY_THIRD_PARTY_DIR}/xxhash
${KYTY_THIRD_PARTY_DIR}/cpuinfo/include
@@ -431,16 +418,15 @@ add_executable(shader_vertex_metadata_tests EXCLUDE_FROM_ALL
)
target_include_directories(shader_vertex_metadata_tests PRIVATE ${inc_headers})
add_executable(shader_stage_runtime_tests EXCLUDE_FROM_ALL
"${KYTY_TESTS_DIR}/ShaderStageRuntimeTests.cpp"
add_executable(resource_materialization_tests EXCLUDE_FROM_ALL
"${KYTY_TESTS_DIR}/ResourceMaterializationTests.cpp"
"${KYTY_SOURCE_DIR}/graphics/guest_gpu/gpu_format.cpp"
"${KYTY_SOURCE_DIR}/graphics/host_gpu/hostMemory.cpp"
"${KYTY_SOURCE_DIR}/graphics/shader/shaderStageRuntime.cpp"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/ResourceMaterialization.cpp"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/SrtWalker.cpp"
)
target_link_libraries(shader_stage_runtime_tests fmt::fmt)
target_include_directories(shader_stage_runtime_tests PRIVATE ${inc_headers})
target_link_libraries(resource_materialization_tests fmt::fmt)
target_include_directories(resource_materialization_tests PRIVATE ${inc_headers})
add_executable(resource_tracking_tests EXCLUDE_FROM_ALL
"${KYTY_TESTS_DIR}/ResourceTrackingTests.cpp"
@@ -464,6 +450,12 @@ add_executable(audio_out2_port_tests EXCLUDE_FROM_ALL
target_link_libraries(audio_out2_port_tests common fmt::fmt)
target_include_directories(audio_out2_port_tests PRIVATE ${inc_headers})
add_executable(save_data_memory_tests EXCLUDE_FROM_ALL
"${KYTY_TESTS_DIR}/SaveDataMemoryTests.cpp"
)
target_link_libraries(save_data_memory_tests common fmt::fmt)
target_include_directories(save_data_memory_tests PRIVATE ${inc_headers})
add_executable(event_queue_lifetime_tests EXCLUDE_FROM_ALL
"${KYTY_TESTS_DIR}/EventQueueLifetimeTests.cpp"
"${KYTY_SOURCE_DIR}/kernel/eventQueue.cpp"
@@ -542,15 +534,20 @@ if(BUILD_TESTING)
add_test(NAME page_manager COMMAND $<TARGET_FILE:page_manager_tests>)
add_test(NAME bit_array COMMAND $<TARGET_FILE:bit_array_tests>)
add_test(NAME shader_vertex_metadata COMMAND $<TARGET_FILE:shader_vertex_metadata_tests>)
add_test(NAME shader_stage_runtime COMMAND $<TARGET_FILE:shader_stage_runtime_tests>)
add_test(NAME resource_materialization COMMAND $<TARGET_FILE:resource_materialization_tests>)
add_test(NAME resource_tracking COMMAND $<TARGET_FILE:resource_tracking_tests>)
add_test(NAME event_queue_lifetime COMMAND $<TARGET_FILE:event_queue_lifetime_tests>)
add_test(NAME kernel_file_system COMMAND $<TARGET_FILE:kernel_file_system_tests>)
add_test(NAME sync_on_address COMMAND $<TARGET_FILE:sync_on_address_tests>)
add_test(NAME audio_out2_port COMMAND $<TARGET_FILE:audio_out2_port_tests>)
add_test(NAME save_data_memory COMMAND $<TARGET_FILE:save_data_memory_tests>)
add_test(NAME shader_recompiler_compute COMMAND $<TARGET_FILE:shader_recompiler_compute_tests>)
add_test(NAME shader_recompiler_alignbyte
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --alignbyte-only)
add_test(NAME graphics_pipeline_rasterization
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --polygon-mode-only)
add_test(NAME graphics_draw_offsets
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --draw-offset-only)
add_test(NAME virtual_memory_allocation
COMMAND $<TARGET_FILE:virtual_memory_allocation_tests>)
add_test(NAME guest_red_zone_patcher
@@ -568,12 +565,16 @@ if(BUILD_TESTING)
add_test(NAME texture_cache_layered_image
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --layered-image-only)
add_test(NAME host_image_allocation
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --host-image-allocation-only)
add_test(NAME texture_cache_image_views
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --image-view-cache-only)
add_test(NAME texture_cache_storage_sampled
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --storage-sampled-only)
add_test(NAME texture_cache_depth_readback
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --depth-readback-only)
add_test(NAME compute_meta_clear_classification
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --compute-meta-clear-only)
add_test(NAME buffer_cache_dirty_gc
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --buffer-cache-gc-only)
@@ -596,7 +597,7 @@ if(BUILD_TESTING)
page_manager_tests
bit_array_tests
shader_vertex_metadata_tests
shader_stage_runtime_tests
resource_materialization_tests
resource_tracking_tests
event_queue_lifetime_tests
kernel_file_system_tests
@@ -607,6 +608,21 @@ if(BUILD_TESTING)
)
endif()
# Test Darwin signal-context restoration for SSE4a instructions.
if(APPLE AND (CMAKE_OSX_ARCHITECTURES STREQUAL "x86_64" OR
(NOT CMAKE_OSX_ARCHITECTURES AND CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|AMD64)$")))
add_executable(macos_sse4a_tests EXCLUDE_FROM_ALL
"${KYTY_TESTS_DIR}/macosSse4aTests.cpp"
"${KYTY_SOURCE_DIR}/loader/x64InstructionEmulator.cpp"
)
target_link_libraries(macos_sse4a_tests common fmt::fmt Zydis::Zydis)
target_include_directories(macos_sse4a_tests PRIVATE ${inc_headers})
if(BUILD_TESTING)
add_test(NAME macos_sse4a COMMAND $<TARGET_FILE:macos_sse4a_tests>)
set_tests_properties(macos_sse4a PROPERTIES TIMEOUT 15)
add_dependencies(kyty_tests macos_sse4a_tests)
endif()
endif()
add_executable(kyty_emulator "${KYTY_SOURCE_DIR}/main.cpp" ${kyty_emulator_src})
configure_macos_guest_address_space(kyty_emulator)
+35 -4
View File
@@ -90,8 +90,14 @@ starting a large change.
Set up the clang-format hook after cloning:
```powershell
python -m pip install pre-commit
Install `pre-commit` using the method appropriate for your platform:
- **Arch Linux / CachyOS:** `sudo pacman -S pre-commit`
- **Other Linux / macOS / Windows:** `python -m pip install pre-commit`
Then install the Git hook:
```bash
python -m pre_commit install --install-hooks
```
@@ -131,6 +137,7 @@ the Vulkan/SPIR-V validation rules.
- Visual Studio 2022 or Build Tools 2022 with the **Desktop development with C++** workload and
**C++ Clang tools for Windows** component
- Qt 6 for MSVC 2022 64-bit, including Concurrent, Network, and Widgets
- [glslang](https://github.com/KhronosGroup/glslang/releases) (`glslangValidator`) on `PATH`
The Microsoft C++ compiler (`cl.exe`) is not supported; use `clang-cl`.
@@ -165,7 +172,7 @@ 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 \
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 \
libasound2-dev libpulse-dev libudev-dev libdbus-1-dev libwayland-dev wayland-protocols
@@ -186,13 +193,37 @@ cmake --install _Build/linux --prefix _Build/linux/install
```
The install step copies the Qt libraries and plugins next to the binaries, so
`_Build/linux/install` runs without a matching system Qt.
`_Build/linux/install` runs without a matching system Qt. FFmpeg is linked statically
from the pinned [KytyPS5 FFmpeg core](https://github.com/KytyPS5/ext-ffmpeg-core)
release, including VP9 and WebM support. System FFmpeg packages are not required.
As on Windows, the MSVC compiler is not used; Clang is required. `cl.exe` is rejected at configure
time.
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
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:
```bash
nix-shell # or: nix develop
git submodule update --init --recursive
cmake -S . -B _Build/linux -G Ninja -DCMAKE_BUILD_TYPE=Release \
-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++
cmake --build _Build/linux --target launcher --parallel
cmake --install _Build/linux --prefix _Build/linux/install
```
The configure step downloads the FFmpeg prebuilts and the `xbyak`/`zydis` sources, so it needs
network access; a fully sandboxed `nix build` would require vendoring those inputs. A Vulkan 1.3
driver must be available at runtime (on NixOS, `hardware.graphics.enable = true`).
### Building on macOS
macOS builds target x86-64 and run under Rosetta 2 on Apple Silicon, so the PS5's x86-64 game
Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

After

Width:  |  Height:  |  Size: 2.2 MiB

Generated
+27
View File
@@ -0,0 +1,27 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1789426278,
"narHash": "sha256-GnotcvKtbTMnoVx6G4E7ZdUX6487Dky74dqq3pU7iRk=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "efe6f071ede9d21c37462d2d6682d5e670099684",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}
+20
View File
@@ -0,0 +1,20 @@
{
description = "KytyPS5 development shell";
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
outputs = { self, nixpkgs }:
let
systems = [ "x86_64-linux" ];
forAllSystems = nixpkgs.lib.genAttrs systems;
in
{
devShells = forAllSystems (system:
let
pkgs = import nixpkgs { inherit system; };
in
{
default = import ./nix/devshell.nix { inherit pkgs; };
});
};
}
+84
View File
@@ -0,0 +1,84 @@
{ 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.
buildDeps = with pkgs; [
qt6.qtbase # Concurrent, Network, Widgets (launcher)
vulkan-headers
vulkan-loader
vulkan-tools
vulkan-validation-layers
libglvnd
# X11 / input
libX11
libXcursor
libXext
libXfixes
libXi
libXrandr
libXScrnSaver
libxkbcommon
# Wayland
wayland
wayland-protocols
libdecor
# audio + device/dbus
alsa-lib
libpulseaudio
systemd # libudev
dbus
];
# Libraries the built binaries need at runtime.
runtimeLibs = with pkgs; [
stdenv.cc.cc.lib # libstdc++ / libgcc_s
libgcc.lib
qt6.qtbase # libQt6Concurrent/Network/Widgets
vulkan-loader
libglvnd
alsa-lib
libpulseaudio
libX11
libXcursor
libXext
libXfixes
libXi
libXrandr
libXScrnSaver
libxkbcommon
wayland
libdecor
systemd
dbus
];
in
pkgs.mkShell {
name = "kytyps5";
nativeBuildInputs = with pkgs; [
clang
lld
cmake
ninja
pkg-config
git
glslang # provides glslangValidator for the bundled shaders
];
buildInputs = buildDeps;
shellHook = ''
export CMAKE_PREFIX_PATH="${pkgs.qt6.qtbase}:''${CMAKE_PREFIX_PATH:-}"
export QT_PLUGIN_PATH="${pkgs.qt6.qtbase}/lib/qt-6/plugins:${pkgs.qt6.qtbase}/lib/qt6/plugins:''${QT_PLUGIN_PATH:-}"
export LD_LIBRARY_PATH="${pkgs.lib.makeLibraryPath runtimeLibs}:''${LD_LIBRARY_PATH:-}"
if [ -z "''${KYTY_SHELL_QUIET:-}" ]; then
echo "KytyPS5 dev shell"
echo " cmake -S . -B _Build/linux -G Ninja -DCMAKE_BUILD_TYPE=Release \\"
echo " -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++"
echo " cmake --build _Build/linux --target launcher --parallel"
echo " cmake --install _Build/linux --prefix _Build/linux/install"
fi
'';
}
+4
View File
@@ -0,0 +1,4 @@
let
pkgs = import <nixpkgs> { };
in
import ./nix/devshell.nix { inherit pkgs; }
+25
View File
@@ -0,0 +1,25 @@
// SPDX-FileCopyrightText: 2014 Jannik Vogel <email@jannikvogel.de>
// SPDX-License-Identifier: CC0-1.0
#pragma once
#include <cstddef>
#include <type_traits>
namespace Common {
template <typename T>
[[nodiscard]] constexpr T AlignUp(T value, std::size_t size) {
static_assert(std::is_unsigned_v<T>, "T must be an unsigned value.");
auto mod{static_cast<T>(value % size)};
value -= mod;
return static_cast<T>(mod == T{0} ? value : value + size);
}
template <typename T>
[[nodiscard]] constexpr T AlignDown(T value, std::size_t size) {
static_assert(std::is_unsigned_v<T>, "T must be an unsigned value.");
return static_cast<T>(value - value % size);
}
} // namespace Common
+3 -18
View File
@@ -1,6 +1,5 @@
#include "common/assert.h"
#include "common/debug.h"
#include "common/logging/log.h"
#include "common/subsystems.h"
#include "kytyGitVersion.h"
@@ -12,25 +11,10 @@
namespace Common {
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS && KYTY_BUILD == KYTY_BUILD_DEBUG && \
KYTY_COMPILER == KYTY_COMPILER_CLANG
constexpr int PRINT_STACK_FROM = 4;
#else
constexpr int PRINT_STACK_FROM = 2;
#endif
static std::string BuildFatalReport(const char* title, std::string_view text, const char* file,
int line) {
DebugStack stack;
DebugStack::Trace(&stack);
std::string report = "--- Build ---\n" KYTY_BUILD_LABEL "\n--- Stack Trace ---\n";
for (int i = PRINT_STACK_FROM; i < stack.depth; i++) {
report += fmt::format("[{}] {:016x}\n", i - PRINT_STACK_FROM,
static_cast<uint64_t>(stack.GetAddr(i)));
}
report += fmt::format("{}\n{} in {}:{}\n", title, text, file, line);
return report;
return fmt::format("--- Build ---\n{}\n{}\n{} in {}:{}\n", KYTY_BUILD_LABEL, title, text, file,
line);
}
static int DbgReport(const char* title, std::string_view text, const char* file, int line) {
@@ -59,6 +43,7 @@ int DbgExitHandler(const char* file, int line, fmt::text_style style, std::strin
}
void DbgExit(int status) {
Subsystems::EmergencyShutdownActive();
std::fflush(nullptr);
std::_Exit(status);
}
-4
View File
@@ -29,11 +29,7 @@ void DbgExit(int status);
} // namespace Common
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS || KYTY_PLATFORM == KYTY_PLATFORM_LINUX
#define EXIT_HALT() (Common::DbgExit(321), 1)
#else
#define EXIT_HALT() (std::_Exit(321), 1)
#endif
#ifndef KYTY_FINAL
#define EXIT_IF(x) \
-166
View File
@@ -1,166 +0,0 @@
#ifndef KYTY_COMMON_BYTEBUFFER_H_
#define KYTY_COMMON_BYTEBUFFER_H_
#include "common/assert.h"
#include "common/common.h"
#include <algorithm>
#include <cstddef> // IWYU pragma: export
#include <cstring>
#include <utility>
#include <vector>
namespace Common {
using Byte = std::byte;
class ByteBuffer {
public:
using DataType = std::vector<Byte>;
static constexpr uint32_t INVALID_INDEX = static_cast<uint32_t>(-1);
using iterator = DataType::iterator;
using const_iterator = DataType::const_iterator;
ByteBuffer() = default;
ByteBuffer(const ByteBuffer&) = default;
ByteBuffer(ByteBuffer&&) = default;
ByteBuffer& operator=(const ByteBuffer&) = default;
ByteBuffer& operator=(ByteBuffer&&) = default;
explicit ByteBuffer(uint32_t size): m_data(size) {}
ByteBuffer(std::initializer_list<uint8_t> list) {
for (const uint8_t& e: list) {
m_data.emplace_back(static_cast<Byte>(e));
}
}
explicit ByteBuffer(const void* buf, uint32_t size)
: ByteBuffer(size) // @suppress("Ambiguous problem")
{
EXIT_IF(buf == nullptr && size != 0);
if (size != 0) {
std::memcpy(m_data.data(), buf, size);
}
}
[[nodiscard]] uint32_t Size() const { return static_cast<uint32_t>(m_data.size()); }
[[nodiscard]] bool IsEmpty() const { return m_data.empty(); }
void Clear() { m_data.clear(); }
void Add(const Byte& val) { m_data.push_back(val); }
void Add(Byte&& val) { m_data.push_back(std::move(val)); }
void Add(const Byte* val, uint32_t num) {
EXIT_IF(val == nullptr && num != 0);
m_data.insert(m_data.end(), val, val + num);
}
void Add(const ByteBuffer& v) {
if (this == &v) {
const ByteBuffer copy(v);
Add(copy.GetDataConst(), copy.Size());
return;
}
Add(v.GetDataConst(), v.Size());
}
void InsertAt(uint32_t index, const Byte& val) {
EXIT_IF(index > Size());
m_data.insert(m_data.begin() + index, val);
}
void InsertAt(uint32_t index, Byte&& val) {
EXIT_IF(index > Size());
m_data.insert(m_data.begin() + index, std::move(val));
}
[[nodiscard]] uint32_t Find(const Byte& t) const {
const auto it = std::find(m_data.begin(), m_data.end(), t);
return (it == m_data.end() ? INVALID_INDEX
: static_cast<uint32_t>(std::distance(m_data.begin(), it)));
}
[[nodiscard]] bool Contains(const Byte& t) const { return Find(t) != INVALID_INDEX; }
bool Remove(const Byte& t) {
const auto it = std::find(m_data.begin(), m_data.end(), t);
if (it == m_data.end()) {
return false;
}
m_data.erase(it);
return true;
}
bool RemoveAt(uint32_t index, uint32_t count = 1) {
if (index >= Size() || count == 0) {
return false;
}
const uint32_t erase_count = std::min(count, Size() - index);
m_data.erase(m_data.begin() + index, m_data.begin() + index + erase_count);
return true;
}
[[nodiscard]] bool IndexValid(uint32_t index) const { return index < Size(); }
Byte& operator[](uint32_t index) {
EXIT_IF(index >= Size());
return m_data[index];
}
const Byte& operator[](uint32_t index) const {
EXIT_IF(index >= Size());
return m_data[index];
}
[[nodiscard]] const Byte& At(uint32_t index) const {
EXIT_IF(index >= Size());
return m_data[index];
}
Byte* GetData() { return m_data.data(); }
[[nodiscard]] const Byte* GetData() const { return m_data.data(); }
[[nodiscard]] const Byte* GetDataConst() const { return m_data.data(); }
void Sort() {
std::sort(m_data.begin(), m_data.end(), [](Byte a, Byte b) {
return std::to_integer<uint8_t>(a) < std::to_integer<uint8_t>(b);
});
}
template <typename OP>
void Sort(OP&& comp_func) {
std::sort(m_data.begin(), m_data.end(), std::forward<OP>(comp_func));
}
bool operator==(const ByteBuffer& s) const { return m_data == s.m_data; }
bool operator!=(const ByteBuffer& s) const { return !(*this == s); }
iterator begin() { return m_data.begin(); } // NOLINT(readability-identifier-naming)
iterator end() { return m_data.end(); } // NOLINT(readability-identifier-naming)
[[nodiscard]] const_iterator begin() const {
return m_data.begin();
} // NOLINT(readability-identifier-naming)
[[nodiscard]] const_iterator end() const {
return m_data.end();
} // NOLINT(readability-identifier-naming)
[[nodiscard]] const_iterator cbegin() const {
return m_data.cbegin();
} // NOLINT(readability-identifier-naming)
[[nodiscard]] const_iterator cend() const {
return m_data.cend();
} // NOLINT(readability-identifier-naming)
private:
DataType m_data;
};
} // namespace Common
#endif /* KYTY_COMMON_BYTEBUFFER_H_ */
-17
View File
@@ -1,20 +1,7 @@
#include "common/debug.h"
#include "common/platform/sysDbg.h"
namespace Common {
void DebugStack::Print(int from, [[maybe_unused]] bool with_name) const {
for (int i = from; i < depth; i++) {
printf("[%d] %016" PRIx64 "\n", i - from, static_cast<uint64_t>(GetAddr(i)));
}
}
void DebugStack::Trace(DebugStack* stack) {
stack->depth = DEBUG_MAX_STACK_DEPTH;
SysStackWalk(stack->stack.data(), &stack->depth);
}
std::string Debug::GetCompiler() {
#if KYTY_COMPILER == KYTY_COMPILER_CLANG
return "clang";
@@ -37,8 +24,4 @@ std::string Debug::GetLinker() {
#endif
}
std::string Debug::GetBitness() {
return "64";
}
} // namespace Common
+1 -27
View File
@@ -2,42 +2,16 @@
#define KYTY_COMMON_DEBUG_H_
#include "common/common.h"
#include "common/stringUtils.h"
#include <array>
#include <cstring>
#include <string>
namespace Common {
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS && KYTY_BUILD == KYTY_BUILD_DEBUG && \
KYTY_COMPILER == KYTY_COMPILER_CLANG
constexpr int DEBUG_MAX_STACK_DEPTH = 20;
#else
constexpr int DEBUG_MAX_STACK_DEPTH = 15;
#endif
namespace Debug {
std::string GetCompiler();
std::string GetLinker();
std::string GetBitness();
} // namespace Debug
struct DebugStack {
int depth {0};
std::array<void*, DEBUG_MAX_STACK_DEPTH> stack {};
[[nodiscard]] uintptr_t GetAddr(int i) const { return reinterpret_cast<uintptr_t>(stack[i]); }
void Print(int from, bool with_name = true) const;
void CopyTo(DebugStack* s) const {
std::memcpy(s->stack.data(), stack.data(), sizeof(void*) * depth);
s->depth = depth;
}
static void Trace(DebugStack* stack);
};
} // namespace Common
#endif /* KYTY_COMMON_DEBUG_H_ */
+20 -4
View File
@@ -43,6 +43,10 @@ int32_t GetUserId() {
return g_config->user_id;
}
const std::string& GetAudioInputDevice() {
return g_config->audio_input_device;
}
PresentMode GetPresentMode() {
return g_config->present_mode;
}
@@ -55,6 +59,14 @@ bool FullscreenEnabled() {
return g_config->fullscreen_enabled;
}
bool VrEnabled() {
return g_config->vr_enabled;
}
bool AmdCpuEnabled() {
return g_config->amd_cpu_enabled;
}
uint32_t GetVblankFrequency() {
return std::clamp(g_config->vblank_frequency, 30u, 360u);
}
@@ -75,7 +87,7 @@ ShaderOptimizationType GetShaderOptimizationType() {
return g_config->shader_optimization_type;
}
ShaderLogDirection GetShaderLogDirection() {
LogDirection GetShaderLogDirection() {
return g_config->shader_log_direction;
}
@@ -95,7 +107,7 @@ bool GraphicsDebugDumpEnabled() {
return g_config->graphics_debug_dump_enabled;
}
OutputDirection GetPrintfDirection() {
LogDirection GetPrintfDirection() {
return g_config->printf_direction;
}
@@ -103,8 +115,8 @@ std::filesystem::path GetPrintfOutputFile() {
return g_config->printf_output_file;
}
ProfilerDirection GetProfilerDirection() {
return g_config->profiler_direction;
bool ProfilerEnabled() {
return g_config->profiler_enabled;
}
bool SpirvDebugPrintfEnabled() {
@@ -123,6 +135,10 @@ bool ReadbackLinearImagesEnabled() {
return g_config->readback_linear_images;
}
bool TessellationEnabled() {
return g_config->tessellation_enabled;
}
bool PlayGoHackEnabled() {
return g_config->playgo_hack_enabled;
}
+16 -12
View File
@@ -21,11 +21,7 @@ struct Lifecycle {
enum class ShaderOptimizationType { None, Size, Performance };
enum class ShaderLogDirection { Silent, Console, File };
enum class ProfilerDirection { None, Network };
enum class OutputDirection { Silent, Console, File };
enum class LogDirection { Silent, Console, File };
enum class PresentMode { Fifo, Mailbox, Immediate };
@@ -47,26 +43,30 @@ struct ConfigOptions {
uint32_t screen_height = 720;
std::string user_name = "Kyty";
int32_t user_id = DEFAULT_USER_ID;
PresentMode present_mode = PresentMode::Fifo;
std::string audio_input_device;
PresentMode present_mode = PresentMode::Mailbox;
int32_t gpu_index = -1;
bool fullscreen_enabled = false;
bool vr_enabled = false;
bool amd_cpu_enabled = false;
uint32_t vblank_frequency = 60;
uint32_t console_language = DEFAULT_CONSOLE_LANGUAGE;
bool vulkan_validation_enabled = false;
bool shader_validation_enabled = false;
ShaderOptimizationType shader_optimization_type = ShaderOptimizationType::None;
ShaderLogDirection shader_log_direction = ShaderLogDirection::Silent;
LogDirection shader_log_direction = LogDirection::Silent;
std::filesystem::path shader_log_folder = "_Shaders";
bool command_buffer_dump_enabled = false;
std::filesystem::path command_buffer_dump_folder = "_Buffers";
bool graphics_debug_dump_enabled = false;
OutputDirection printf_direction = OutputDirection::Silent;
LogDirection printf_direction = LogDirection::Silent;
std::filesystem::path printf_output_file = "_kyty.txt";
ProfilerDirection profiler_direction = ProfilerDirection::None;
bool profiler_enabled = false;
bool spirv_debug_printf_enabled = false;
bool gpu_assisted_validation_enabled = false;
bool renderdoc_enabled = false;
bool readback_linear_images = false;
bool tessellation_enabled = false;
bool playgo_hack_enabled = false;
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
bool red_zone_protection_enabled = false;
@@ -80,16 +80,19 @@ uint32_t GetScreenWidth();
uint32_t GetScreenHeight();
const std::string& GetUserName();
int32_t GetUserId();
const std::string& GetAudioInputDevice();
PresentMode GetPresentMode();
int32_t GetGpuIndex();
bool FullscreenEnabled();
bool VrEnabled();
bool AmdCpuEnabled();
uint32_t GetVblankFrequency();
uint32_t GetConsoleLanguage();
bool VulkanValidationEnabled();
bool ShaderValidationEnabled();
ShaderOptimizationType GetShaderOptimizationType();
ShaderLogDirection GetShaderLogDirection();
LogDirection GetShaderLogDirection();
std::filesystem::path GetShaderLogFolder();
bool CommandBufferDumpEnabled();
@@ -97,10 +100,10 @@ std::filesystem::path GetCommandBufferDumpFolder();
bool GraphicsDebugDumpEnabled();
OutputDirection GetPrintfDirection();
LogDirection GetPrintfDirection();
std::filesystem::path GetPrintfOutputFile();
ProfilerDirection GetProfilerDirection();
bool ProfilerEnabled();
bool SpirvDebugPrintfEnabled();
@@ -108,6 +111,7 @@ bool GpuAssistedValidationEnabled();
bool RenderDocEnabled();
bool ReadbackLinearImagesEnabled();
bool TessellationEnabled();
bool PlayGoHackEnabled();
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
bool RedZoneProtectionEnabled();
+52 -122
View File
@@ -69,7 +69,7 @@ File::File(): m_p(std::make_unique<FilePrivate>()) {
}
File::~File() {
EXIT_IF(m_p->f != nullptr);
Close();
}
File::File(const std::filesystem::path& name): m_p(std::make_unique<FilePrivate>()) {
@@ -136,10 +136,6 @@ bool File::OpenInMem(void* buf, uint32_t buf_size) {
return true;
}
bool File::OpenInMem(ByteBuffer& buf) {
return OpenInMem(buf.GetData(), buf.Size());
}
bool File::CreateInMem() {
EXIT_IF(m_p->f != nullptr);
@@ -249,88 +245,78 @@ void File::Printf(const char* format, ...) {
Write(s.data(), static_cast<uint32_t>(s.size()));
}
static std::filesystem::path WithoutTrailingSeparator(
const std::filesystem::path& path) {
if (!path.empty() && !path.has_filename() &&
path != path.root_path()) {
return path.parent_path();
}
return path;
}
bool File::IsDirectoryExisting(const std::filesystem::path& path) {
auto path_str = PathToGenericString(path);
return SysFileIsDirectoryExisting(
Common::EndsWith(path_str, "/") ? Common::RemoveLast(path_str, 1) : path_str);
return SysFileIsDirectoryExisting(WithoutTrailingSeparator(path));
}
bool File::IsFileExisting(const std::filesystem::path& name) {
return SysFileIsFileExisting(name);
}
bool File::CreateDirectory(
const std::filesystem::path& path) // @suppress("Member declaration not found")
{
auto path_str = PathToGenericString(path);
return SysFileCreateDirectory(Common::EndsWith(path_str, "/") ? Common::RemoveLast(path_str, 1)
: path_str);
bool File::CreateDirectory(const std::filesystem::path& path) {
return SysFileCreateDirectory(
WithoutTrailingSeparator(path));
}
bool File::DeleteDirectory(const std::filesystem::path& path) {
auto path_str = PathToGenericString(path);
return SysFileDeleteDirectory(Common::EndsWith(path_str, "/") ? Common::RemoveLast(path_str, 1)
: path_str);
return SysFileDeleteDirectory(
WithoutTrailingSeparator(path));
}
bool File::CreateDirectories(const std::filesystem::path& path) {
std::string real_path = Common::ReplaceChar(PathToGenericString(path), '\\', '/');
const auto normalized_path =
WithoutTrailingSeparator(path);
std::vector<std::string> list = Common::Split(real_path, "/");
auto current_path = normalized_path.root_path();
std::string p;
for (const auto& component:
normalized_path.relative_path()) {
current_path /= component;
for (uint32_t si = 0; si < list.size(); si++) {
const std::string& s = list[si];
if (IsDirectoryExisting(current_path)) {
continue;
}
if (si != 0 || Common::StartsWith(real_path, "/")) {
p += "/";
}
if (!CreateDirectory(current_path)) {
return false;
}
}
p += s;
if (IsDirectoryExisting(p)) {
continue;
}
if (!CreateDirectory(p)) // @suppress("Invalid arguments")
{
return false;
}
}
return true;
return true;
}
bool File::DeleteDirectories(const std::filesystem::path& path) {
std::string real_path = Common::ReplaceChar(PathToGenericString(path), '\\', '/');
const auto normalized_path =
WithoutTrailingSeparator(path);
std::vector<std::string> list = Common::Split(real_path, "/");
std::vector<std::filesystem::path> directories;
auto current_path = normalized_path.root_path();
std::string p;
std::vector<std::string> list2;
for (const auto& component:
normalized_path.relative_path()) {
current_path /= component;
directories.push_back(current_path);
}
for (uint32_t si = 0; si < list.size(); si++) {
const std::string& s = list[si];
for (auto it = directories.rbegin();
it != directories.rend(); ++it) {
if (!DeleteDirectory(*it)) {
return false;
}
}
if (si != 0 || Common::StartsWith(real_path, "/")) {
p += "/";
}
p += s;
list2.push_back(p);
}
uint32_t num = list2.size();
for (uint32_t si = num - 1; si < num; si--) {
if (!DeleteDirectory(list2[si])) {
return false;
}
}
return true;
return true;
}
bool File::DeleteFile(
@@ -345,7 +331,7 @@ bool File::Flush() {
return SysFileFlush(*m_p->f);
}
ByteBuffer File::ReadWholeBuffer() {
std::vector<std::byte> File::ReadWholeBuffer() {
EXIT_IF(IsInvalid());
EXIT_IF(Tell() != 0);
@@ -353,25 +339,14 @@ ByteBuffer File::ReadWholeBuffer() {
EXIT_IF((s >> 32u) != 0);
ByteBuffer buf(s);
const auto read_size = static_cast<uint32_t>(s);
std::vector<std::byte> buf(read_size);
Read(buf.GetData(), s);
Read(buf.data(), read_size);
return buf;
}
ByteBuffer File::Read(uint32_t size) {
ByteBuffer buf(size);
uint32_t b = 0;
Read(buf.GetData(), size, &b);
buf.RemoveAt(b, size - b);
return buf;
}
void File::Write(const ByteBuffer& buf, uint32_t* bytes_written) {
Write(buf.GetDataConst(), buf.Size(), bytes_written);
}
DateTime File::GetLastAccessTimeUTC(const std::filesystem::path& name) {
SysTimeStruct t {};
SysFileToSystemTimeUtc(SysFileGetLastAccessTimeUtc(name), t);
@@ -470,51 +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);
auto path_str = PathToGenericString(path);
uint32_t len = static_cast<uint32_t>(path_str.size());
if (!Common::EndsWith(path_str, "/") && !Common::EndsWith(path_str, "\\")) {
len++;
}
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 = Common::Mid(PathToGenericString(f.path_with_name), len);
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;
+4 -16
View File
@@ -1,10 +1,10 @@
#ifndef KYTY_COMMON_FILE_H_
#define KYTY_COMMON_FILE_H_
#include "common/byteBuffer.h"
#include "common/common.h"
#include "common/dateTime.h"
#include <cstddef>
#include <filesystem>
#include <memory>
#include <string>
@@ -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;
@@ -51,7 +43,6 @@ public:
bool Create(const std::filesystem::path& name);
bool Open(const std::filesystem::path& name, Mode mode);
bool OpenInMem(void* buf, uint32_t buf_size);
bool OpenInMem(ByteBuffer& buf); // NOLINT(google-runtime-references)
bool CreateInMem();
void Close();
@@ -73,14 +64,12 @@ public:
void GetLastAccessAndWriteTimeUTC(DateTime* access, DateTime* write);
void Read(void* data, uint32_t size, uint32_t* bytes_read = nullptr);
ByteBuffer Read(uint32_t size);
void Write(const void* data, uint32_t size, uint32_t* bytes_written = nullptr);
void Write(const ByteBuffer& buf, uint32_t* bytes_written = nullptr);
void Read(void* data, uint32_t size, uint32_t* bytes_read = nullptr);
void Write(const void* data, uint32_t size, uint32_t* bytes_written = nullptr);
void Printf(const char* format, ...) KYTY_FORMAT_PRINTF(2, 3);
ByteBuffer ReadWholeBuffer();
std::vector<std::byte> ReadWholeBuffer();
static uint64_t Size(const std::filesystem::path& name);
@@ -101,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);
-97
View File
@@ -54,103 +54,6 @@ inline uint32_t hash(const void* key, uint32_t key_len) {
return hash;
}
inline uint32_t hash8(uint8_t key) {
uint32_t hash = 0;
uint8_t* ptr = &key;
hash += ptr[0];
hash += (hash << 10u);
hash ^= (hash >> 6u);
hash += (hash << 3u);
hash ^= (hash >> 11u);
hash += (hash << 15u);
return hash;
}
inline uint32_t hash16(uint16_t key) {
uint32_t hash = 0;
auto* ptr = reinterpret_cast<uint8_t*>(&key);
hash += ptr[0];
hash += (hash << 10u);
hash ^= (hash >> 6u);
hash += ptr[1];
hash += (hash << 10u);
hash ^= (hash >> 6u);
hash += (hash << 3u);
hash ^= (hash >> 11u);
hash += (hash << 15u);
return hash;
}
inline uint32_t hash32(uint32_t key) {
uint32_t hash = 0;
auto* ptr = reinterpret_cast<uint8_t*>(&key);
hash += ptr[0];
hash += (hash << 10u);
hash ^= (hash >> 6u);
hash += ptr[1];
hash += (hash << 10u);
hash ^= (hash >> 6u);
hash += ptr[2];
hash += (hash << 10u);
hash ^= (hash >> 6u);
hash += ptr[3];
hash += (hash << 10u);
hash ^= (hash >> 6u);
hash += (hash << 3u);
hash ^= (hash >> 11u);
hash += (hash << 15u);
return hash;
}
inline uint32_t hash64(uint64_t key) {
uint32_t hash = 0;
auto* ptr = reinterpret_cast<uint8_t*>(&key);
hash += ptr[0];
hash += (hash << 10u);
hash ^= (hash >> 6u);
hash += ptr[1];
hash += (hash << 10u);
hash ^= (hash >> 6u);
hash += ptr[2];
hash += (hash << 10u);
hash ^= (hash >> 6u);
hash += ptr[3];
hash += (hash << 10u);
hash ^= (hash >> 6u);
hash += ptr[4];
hash += (hash << 10u);
hash ^= (hash >> 6u);
hash += ptr[5];
hash += (hash << 10u);
hash ^= (hash >> 6u);
hash += ptr[6];
hash += (hash << 10u);
hash ^= (hash >> 6u);
hash += ptr[7];
hash += (hash << 10u);
hash ^= (hash >> 6u);
hash += (hash << 3u);
hash ^= (hash >> 11u);
hash += (hash << 15u);
return hash;
}
} // namespace Common
#endif /* KYTY_COMMON_HASH_H_ */
+54 -7
View File
@@ -5,14 +5,17 @@
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
#include <windows.h> // IWYU pragma: keep
#elif defined(__APPLE__)
#else
#include <algorithm>
#include <csignal>
#include <cstdlib>
#include <initializer_list>
#include <unistd.h>
#if defined(__APPLE__)
#include <sys/ucontext.h>
#else
#include <csignal>
#include <initializer_list>
#include <ucontext.h> // IWYU pragma: keep
#include <unistd.h>
#endif
#endif
// IWYU pragma: no_include <errhandlingapi.h>
@@ -32,6 +35,51 @@ static_assert(decltype(g_handler)::is_always_lock_free);
static_assert(decltype(g_install_state)::is_always_lock_free);
#endif
#if KYTY_PLATFORM == KYTY_PLATFORM_LINUX
// macOS uses the same POSIX platform setting and needs a signal stack too.
class ThreadSignalStack {
public:
ThreadSignalStack() {
const auto page_size = static_cast<size_t>(::getpagesize());
const auto stack_size =
(std::max<size_t>(64 * 1024, MINSIGSTKSZ) + page_size - 1) & ~(page_size - 1);
if (::posix_memalign(&m_memory, page_size, stack_size) != 0) {
return;
}
stack_t stack {};
stack.ss_sp = m_memory;
stack.ss_size = stack_size;
if (::sigaltstack(&stack, &m_previous) != 0) {
std::free(m_memory);
m_memory = nullptr;
}
}
~ThreadSignalStack() {
if (m_memory != nullptr && ::sigaltstack(&m_previous, nullptr) == 0) {
std::free(m_memory);
}
}
[[nodiscard]] bool IsInitialized() const { return m_memory != nullptr; }
KYTY_CLASS_NO_COPY(ThreadSignalStack)
private:
void* m_memory = nullptr;
stack_t m_previous {};
};
bool InitializeThreadSignalStack() {
// Keep fault handling off guest stacks, which GPU tracking can make read-only.
thread_local ThreadSignalStack signal_stack;
return signal_stack.IsInitialized();
}
#endif
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
static LONG WINAPI ExceptionFilter(PEXCEPTION_POINTERS exception) noexcept {
@@ -253,7 +301,7 @@ bool InstallHandler(Handler handler) {
#elif defined(__APPLE__)
struct sigaction sa {};
sa.sa_sigaction = SignalHandler;
sa.sa_flags = SA_SIGINFO;
sa.sa_flags = SA_SIGINFO | SA_ONSTACK;
sigemptyset(&sa.sa_mask);
// The guest signal-dispatch path (KernelRaiseException) interrupts threads with
// SIGUSR1; block it while a fault is being resolved so a stop-the-world request
@@ -274,8 +322,7 @@ bool InstallHandler(Handler handler) {
struct sigaction action {};
action.sa_sigaction = SignalHandler;
sigemptyset(&action.sa_mask);
// Fault resolution needs the normal thread stack.
action.sa_flags = SA_SIGINFO | SA_RESTART;
action.sa_flags = SA_SIGINFO | SA_RESTART | SA_ONSTACK;
for (const int signal_number: {SIGSEGV, SIGBUS, SIGILL}) {
if (::sigaction(signal_number, &action, nullptr) != 0) {
+4
View File
@@ -39,6 +39,10 @@ using Handler = bool (*)(const ExceptionInfo&);
bool InstallHandler(Handler handler);
#if KYTY_PLATFORM == KYTY_PLATFORM_LINUX
bool InitializeThreadSignalStack();
#endif
} // namespace Common::HostException
#endif /* KYTY_COMMON_HOST_EXCEPTION_H_ */
+12 -5
View File
@@ -3,7 +3,6 @@
#include "common/assert.h"
#include "common/emulatorConfig.h"
#include "common/stringUtils.h"
#include <cstdio>
#include <filesystem>
@@ -47,7 +46,7 @@ std::shared_ptr<spdlog::logger> MakeFileLogger(std::string name
}
auto sink =
std::make_shared<spdlog::sinks::basic_file_sink_mt>(Common::PathToString(path), true);
std::make_shared<spdlog::sinks::basic_file_sink_mt>(path.native(), true);
return MakeLogger(std::move(name), std::move(sink));
}
@@ -129,6 +128,14 @@ static void WriteImpl(std::string_view text, fmt::text_style style = {}) {
}
}
void WriteToConsoleAndLog(std::string_view text) {
WriteImpl(text);
if (g_initialized && g_direction != Direction::Console) {
WriteStdout(text);
}
Flush();
}
void WriteFatal(std::string_view text) {
if (g_direction == Direction::Silent || !g_initialized) {
WriteStdout(text);
@@ -152,9 +159,9 @@ void WriteFatal(fmt::text_style style, std::string_view text) {
void Initialize() {
g_initialized = true;
switch (Config::GetPrintfDirection()) {
case Config::OutputDirection::Silent: g_direction = Direction::Silent; break;
case Config::OutputDirection::Console: g_direction = Direction::Console; break;
case Config::OutputDirection::File: g_direction = Direction::File; break;
case Config::LogDirection::Silent: g_direction = Direction::Silent; break;
case Config::LogDirection::Console: g_direction = Direction::Console; break;
case Config::LogDirection::File: g_direction = Direction::File; break;
}
g_output_file =
(g_direction == Direction::File ? Config::GetPrintfOutputFile() : std::filesystem::path {});
+1
View File
@@ -25,6 +25,7 @@ Direction GetDirection();
bool IsSilent();
void Write(std::string_view text);
void Write(fmt::text_style style, std::string_view text);
void WriteToConsoleAndLog(std::string_view text);
void WriteFatal(std::string_view text);
void WriteFatal(fmt::text_style style, std::string_view text);
void Flush();
-42
View File
@@ -1,42 +0,0 @@
#ifndef KYTY_COMMON_MAGICENUM_H_
#define KYTY_COMMON_MAGICENUM_H_
#include "common/common.h"
#include "common/stringUtils.h"
#include "magic_enum.hpp" // IWYU pragma: export
namespace Common {
template <typename E>
inline std::string EnumName(E v) {
auto str = magic_enum::enum_name(v);
return std::string(str.data(), str.length());
}
template <typename E>
inline std::string EnumName8(E v) {
auto str = magic_enum::enum_name(v);
return std::string(str.data(), str.length());
}
template <typename E>
inline E EnumValue(const std::string& str, E default_value) {
auto v = magic_enum::enum_cast<E>(str.c_str());
if (v.has_value()) {
return v.value();
}
return default_value;
}
#define KYTY_ENUM_RANGE(e, mx, mn) \
namespace magic_enum::customize { \
template <> \
struct enum_range<e> { \
static constexpr int min = (mx); \
static constexpr int max = (mn); \
}; \
}
} // namespace Common
#endif /* KYTY_COMMON_MAGICENUM_H_ */
+1 -3
View File
@@ -30,8 +30,6 @@ struct sys_dbg_stack_info_t {
#endif
};
void SysStackWalk(void** stack, int* depth);
void SysStackUsage(sys_dbg_stack_info_t& s); // NOLINT(google-runtime-references)
void SysStackUsagePrint(sys_dbg_stack_info_t& stack); // NOLINT(google-runtime-references)
void SysStackUsage(sys_dbg_stack_info_t& s); // NOLINT(google-runtime-references)
#endif /* KYTY_COMMON_PLATFORM_SYSDBG_H_ */
-11
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;
@@ -52,7 +44,6 @@ uint64_t SysFileTell(sys_file_t& f); // NOLINT(google-runt
bool SysFileTruncate(sys_file_t& f, uint64_t size); // NOLINT(google-runtime-references)
bool SysFileUnlink(sys_file_t& f,
const std::filesystem::path& name); // NOLINT(google-runtime-references)
void SysFileWrite(uint32_t n, sys_file_t& f); // NOLINT(google-runtime-references)
uint64_t SysFileSize(const std::filesystem::path& file_name);
bool SysFileIsError(sys_file_t& f); // NOLINT(google-runtime-references)
bool SysFileIsDirectoryExisting(const std::filesystem::path& path);
@@ -76,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);
-51
View File
@@ -8,7 +8,6 @@
#include <cstdlib>
#include <cstring>
#include <execinfo.h>
#include <pthread.h>
#include <sys/param.h>
#include <sys/types.h>
@@ -17,56 +16,6 @@
#include <libgen.h> // POSIX basename() lives here on macOS, not in <cstring>
#endif
// Avoid unwinding a guest-owned stack.
static bool OnOwnStack() {
const char* probe = reinterpret_cast<const char*>(&probe);
pthread_attr_t attr {};
#if defined(__APPLE__)
const auto* top = static_cast<const char*>(pthread_get_stackaddr_np(pthread_self()));
const auto size = pthread_get_stacksize_np(pthread_self());
(void)attr;
return top != nullptr && size != 0 && probe < top && probe >= top - size;
#else
if (pthread_getattr_np(pthread_self(), &attr) != 0) {
return false;
}
void* base = nullptr;
size_t size = 0;
const bool ok = pthread_attr_getstack(&attr, &base, &size) == 0 && base != nullptr && size != 0;
pthread_attr_destroy(&attr);
if (!ok) {
return false;
}
const auto* low = static_cast<const char*>(base);
return probe >= low && probe < low + size;
#endif
}
void SysStackWalk(void** stack, int* depth) {
if (stack == nullptr || depth == nullptr || *depth <= 0) {
if (depth != nullptr) {
*depth = 0;
}
return;
}
if (!OnOwnStack()) {
*depth = 0;
return;
}
const int n = ::backtrace(stack, *depth);
*depth = (n < 0 ? 0 : n);
}
void SysStackUsagePrint(sys_dbg_stack_info_t& stack) {
printf("stack: (0x%" PRIx64 ", %" PRIu64 ")\n", static_cast<uint64_t>(stack.commited_addr),
static_cast<uint64_t>(stack.commited_size));
printf("code: (0x%" PRIx64 ", %" PRIu64 ")\n", static_cast<uint64_t>(stack.code_addr),
static_cast<uint64_t>(stack.code_size));
}
void SysStackUsage(sys_dbg_stack_info_t& s) {
pid_t pid = getpid();
+1 -49
View File
@@ -11,6 +11,7 @@
#include <cerrno>
#include <cstdlib>
#include <cstring>
#include <dirent.h>
#include <fcntl.h>
#include <filesystem>
@@ -151,10 +152,6 @@ void SysFileWrite(const void* data, uint32_t size, sys_file_t& f, uint32_t* byte
}
}
void SysFileWrite(uint32_t n, sys_file_t& f) {
SysFileWrite(&n, 4, f);
}
sys_file_t* SysFileCreate(const std::filesystem::path& file_name) {
auto* ret = new sys_file_t;
@@ -613,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);
+37 -106
View File
@@ -5,7 +5,6 @@
#else
#include "common/assert.h"
#include "common/platform/sysVirtual.h"
#include "common/virtualMemory.h"
#include <atomic>
@@ -28,13 +27,12 @@
#define KYTY_FIXED_NOREPLACE
#endif
namespace Common {
namespace Common::VirtualMemory {
static pthread_mutex_t g_virtual_mutex {};
static std::map<uintptr_t, size_t>* g_allocs = nullptr;
static std::map<uintptr_t, int>* g_protects = nullptr;
static std::map<uintptr_t, size_t>* g_allocs = nullptr;
void SysVirtualInit() {
void Init() {
pthread_mutexattr_t attr {};
pthread_mutexattr_init(&attr);
@@ -46,43 +44,27 @@ void SysVirtualInit() {
pthread_mutex_init(&g_virtual_mutex, &attr);
pthread_mutexattr_destroy(&attr);
g_allocs = new std::map<uintptr_t, size_t>;
g_protects = new std::map<uintptr_t, int>;
g_allocs = new std::map<uintptr_t, size_t>;
}
static int get_protection_flag(VirtualMemory::Mode mode) {
static int get_protection_flag(Mode mode) {
int protect = PROT_NONE;
switch (mode) {
case VirtualMemory::Mode::Read: protect = PROT_READ; break;
case VirtualMemory::Mode::Write:
case VirtualMemory::Mode::ReadWrite: protect = PROT_READ | PROT_WRITE; break; // NOLINT
case VirtualMemory::Mode::Execute: protect = PROT_EXEC; break;
case VirtualMemory::Mode::ExecuteRead: protect = PROT_EXEC | PROT_READ; break; // NOLINT
case VirtualMemory::Mode::ExecuteWrite:
case VirtualMemory::Mode::ExecuteReadWrite:
case Mode::Read: protect = PROT_READ; break;
case Mode::Write:
case Mode::ReadWrite: protect = PROT_READ | PROT_WRITE; break; // NOLINT
case Mode::Execute: protect = PROT_EXEC; break;
case Mode::ExecuteRead: protect = PROT_EXEC | PROT_READ; break; // NOLINT
case Mode::ExecuteWrite:
case Mode::ExecuteReadWrite:
protect = PROT_EXEC | PROT_WRITE | PROT_READ;
break; // NOLINT
case VirtualMemory::Mode::NoAccess:
case Mode::NoAccess:
default: protect = PROT_NONE; break;
}
return protect;
}
static VirtualMemory::Mode get_protection_flag(int mode) {
switch (mode) {
case PROT_NONE: return VirtualMemory::Mode::NoAccess;
case PROT_READ: return VirtualMemory::Mode::Read;
case PROT_WRITE: return VirtualMemory::Mode::Write;
case PROT_READ | PROT_WRITE: return VirtualMemory::Mode::ReadWrite; // NOLINT
case PROT_EXEC: return VirtualMemory::Mode::Execute;
case PROT_EXEC | PROT_WRITE: return VirtualMemory::Mode::ExecuteWrite; // NOLINT
case PROT_EXEC | PROT_READ: return VirtualMemory::Mode::ExecuteRead; // NOLINT
case PROT_EXEC | PROT_WRITE | PROT_READ:
return VirtualMemory::Mode::ExecuteReadWrite; // NOLINT
default: return VirtualMemory::Mode::NoAccess;
}
}
// Keep automatic mappings inside the guest and GPU-addressable low window.
#ifdef KYTY_FIXED_NOREPLACE
static constexpr uintptr_t LOW_ARENA_LIMIT = 0x000000FC00000000ULL; // libc mspace window ceiling
@@ -147,7 +129,7 @@ static void* map_anonymous(uintptr_t addr, size_t size, int protect, int flags)
return mmap(nullptr, size, protect, flags, -1, 0); // NOLINT
}
uint64_t SysVirtualAlloc(uint64_t address, uint64_t size, VirtualMemory::Mode mode) {
uint64_t Alloc(uint64_t address, uint64_t size, Mode mode) {
EXIT_IF(g_allocs == nullptr);
auto addr = static_cast<uintptr_t>(address);
@@ -161,11 +143,6 @@ uint64_t SysVirtualAlloc(uint64_t address, uint64_t size, VirtualMemory::Mode mo
if (ptr != MAP_FAILED) {
pthread_mutex_lock(&g_virtual_mutex);
record_alloc(ret_addr, size);
uintptr_t page_start = ret_addr >> 12u;
uintptr_t page_end = (ret_addr + size - 1) >> 12u;
for (uintptr_t page = page_start; page <= page_end; page++) {
(*g_protects)[page] = protect;
}
pthread_mutex_unlock(&g_virtual_mutex);
}
@@ -176,8 +153,7 @@ static uintptr_t align_up(uintptr_t addr, uint64_t alignment) {
return (addr + alignment - 1) & ~(alignment - 1);
}
uint64_t SysVirtualAllocAligned(uint64_t address, uint64_t size, VirtualMemory::Mode mode,
uint64_t alignment) {
uint64_t AllocAligned(uint64_t address, uint64_t size, Mode mode, uint64_t alignment) {
if (alignment == 0) {
return 0;
}
@@ -246,16 +222,11 @@ uint64_t SysVirtualAllocAligned(uint64_t address, uint64_t size, VirtualMemory::
}
if (ptr == MAP_FAILED) {
return SysVirtualAllocAligned(address, size, mode, alignment << 1u);
return AllocAligned(address, size, mode, alignment << 1u);
}
pthread_mutex_lock(&g_virtual_mutex);
record_alloc(ret_addr, size);
uintptr_t page_start = ret_addr >> 12u;
uintptr_t page_end = (ret_addr + size - 1) >> 12u;
for (uintptr_t page = page_start; page <= page_end; page++) {
(*g_protects)[page] = protect;
}
pthread_mutex_unlock(&g_virtual_mutex);
return ret_addr;
@@ -308,7 +279,7 @@ static bool is_mapped(void* ptr, size_t length) {
}
#endif
bool SysVirtualAllocFixed(uint64_t address, uint64_t size, VirtualMemory::Mode mode) {
bool AllocFixed(uint64_t address, uint64_t size, Mode mode) {
EXIT_IF(g_allocs == nullptr);
auto addr = static_cast<uintptr_t>(address);
@@ -337,11 +308,6 @@ bool SysVirtualAllocFixed(uint64_t address, uint64_t size, VirtualMemory::Mode m
if (ptr != MAP_FAILED) {
pthread_mutex_lock(&g_virtual_mutex);
record_alloc(ret_addr, size);
uintptr_t page_start = ret_addr >> 12u;
uintptr_t page_end = (ret_addr + size - 1) >> 12u;
for (uintptr_t page = page_start; page <= page_end; page++) {
(*g_protects)[page] = protect;
}
pthread_mutex_unlock(&g_virtual_mutex);
return true;
@@ -350,15 +316,15 @@ bool SysVirtualAllocFixed(uint64_t address, uint64_t size, VirtualMemory::Mode m
return false;
}
bool SysVirtualCommit(uint64_t address, uint64_t size, VirtualMemory::Mode mode) {
return SysVirtualProtect(address, size, mode);
bool Commit(uint64_t address, uint64_t size, Mode mode) {
return Protect(address, size, mode);
}
uint64_t SysVirtualReserve(uint64_t address, uint64_t size) {
return SysVirtualReserveAligned(address, size, 1);
uint64_t Reserve(uint64_t address, uint64_t size) {
return ReserveAligned(address, size, 1);
}
uint64_t SysVirtualReserveAligned(uint64_t address, uint64_t size, uint64_t alignment) {
uint64_t ReserveAligned(uint64_t address, uint64_t size, uint64_t alignment) {
if (alignment == 0) {
return 0;
}
@@ -427,7 +393,7 @@ uint64_t SysVirtualReserveAligned(uint64_t address, uint64_t size, uint64_t alig
}
if (ptr == MAP_FAILED) {
return SysVirtualReserveAligned(address, size, alignment << 1u);
return ReserveAligned(address, size, alignment << 1u);
}
pthread_mutex_lock(&g_virtual_mutex);
@@ -437,7 +403,7 @@ uint64_t SysVirtualReserveAligned(uint64_t address, uint64_t size, uint64_t alig
return ret_addr;
}
bool SysVirtualReserveFixed(uint64_t address, uint64_t size) {
bool ReserveFixed(uint64_t address, uint64_t size) {
EXIT_IF(g_allocs == nullptr);
auto addr = static_cast<uintptr_t>(address);
@@ -473,9 +439,9 @@ bool SysVirtualReserveFixed(uint64_t address, uint64_t size) {
return false;
}
bool SysVirtualDecommit(uint64_t address, uint64_t size) {
bool Decommit(uint64_t address, uint64_t size) {
// Drop physical pages while preserving the reservation.
if (!SysVirtualProtect(address, size, VirtualMemory::Mode::NoAccess)) {
if (!Protect(address, size, Mode::NoAccess)) {
return false;
}
@@ -499,7 +465,7 @@ bool SysVirtualDecommit(uint64_t address, uint64_t size) {
return true;
}
bool SysVirtualFree(uint64_t address) {
bool Free(uint64_t address) {
EXIT_IF(g_allocs == nullptr);
size_t size = 0;
@@ -516,21 +482,10 @@ bool SysVirtualFree(uint64_t address) {
return false;
}
if (munmap(reinterpret_cast<void*>(addr), size) == 0) {
uintptr_t page_start = addr >> 12u;
uintptr_t page_end = (addr + size - 1) >> 12u;
pthread_mutex_lock(&g_virtual_mutex);
for (uintptr_t page = page_start; page <= page_end; page++) {
g_protects->erase(page);
}
pthread_mutex_unlock(&g_virtual_mutex);
return true;
}
return false;
return munmap(reinterpret_cast<void*>(addr), size) == 0;
}
bool SysVirtualFreeRange(uint64_t address, uint64_t size) {
bool FreeRange(uint64_t address, uint64_t size) {
EXIT_IF(g_allocs == nullptr);
if (size == 0 || (address & 0xfffu) != 0 || (size & 0xfffu) != 0) {
return false;
@@ -582,46 +537,22 @@ bool SysVirtualFreeRange(uint64_t address, uint64_t size) {
if (end < alloc_end) {
(*g_allocs)[end] = alloc_end - end;
}
for (uintptr_t page = addr >> 12u; page <= (end - 1u) >> 12u; page++) {
g_protects->erase(page);
}
pthread_mutex_unlock(&g_virtual_mutex);
return true;
}
bool SysVirtualProtect(uint64_t address, uint64_t size, VirtualMemory::Mode mode,
VirtualMemory::Mode* old_mode) {
auto addr = static_cast<uintptr_t>(address);
pthread_mutex_lock(&g_virtual_mutex);
if (old_mode != nullptr) {
if (auto s = g_protects->find(addr >> 12u); s != g_protects->end()) {
*old_mode = get_protection_flag(s->second);
} else {
*old_mode = VirtualMemory::Mode::NoAccess;
}
}
pthread_mutex_unlock(&g_virtual_mutex);
uintptr_t page_start = addr >> 12u;
uintptr_t page_end = (addr + size - 1) >> 12u;
if (mprotect(reinterpret_cast<void*>(page_start << 12u), (page_end - page_start + 1) << 12u,
get_protection_flag(mode)) == 0) {
pthread_mutex_lock(&g_virtual_mutex);
for (uintptr_t page = page_start; page <= page_end; page++) {
(*g_protects)[page] = get_protection_flag(mode);
}
pthread_mutex_unlock(&g_virtual_mutex);
return true;
}
return false;
bool Protect(uint64_t address, uint64_t size, Mode mode) {
const auto addr = static_cast<uintptr_t>(address);
const auto page_start = addr >> 12u;
const auto page_end = (addr + size - 1) >> 12u;
return mprotect(reinterpret_cast<void*>(page_start << 12u), (page_end - page_start + 1) << 12u,
get_protection_flag(mode)) == 0;
}
bool SysVirtualFlushInstructionCache(uint64_t /*address*/, uint64_t /*size*/) {
bool FlushInstructionCache(uint64_t /*address*/, uint64_t /*size*/) {
return true;
}
} // namespace Common
} // namespace Common::VirtualMemory
#endif
-53
View File
@@ -1,53 +0,0 @@
#ifndef KYTY_COMMON_PLATFORM_SYSSWAPBYTEORDER_H_
#define KYTY_COMMON_PLATFORM_SYSSWAPBYTEORDER_H_
#include "common/common.h"
#include <type_traits>
inline uint16_t SwapByteOrder16(uint16_t value) {
uint16_t hi = value << 8u;
uint16_t lo = value >> 8u;
return hi | lo;
}
inline uint32_t SwapByteOrder32(uint32_t value) {
return __builtin_bswap32(value);
}
inline uint64_t SwapByteOrder64(uint64_t value) {
return __builtin_bswap64(value);
}
template <typename T>
inline void SwapByteOrder(T& x) {
if (sizeof(x) == 2) {
if (std::is_signed_v<T>) {
x = static_cast<std::make_signed_t<T>>(
SwapByteOrder16(static_cast<std::make_unsigned_t<uint16_t>>(x)));
} else {
x = SwapByteOrder16(x);
}
}
if (sizeof(x) == 4) {
if (std::is_signed_v<T>) {
x = static_cast<std::make_signed_t<T>>(
SwapByteOrder32(static_cast<std::make_unsigned_t<uint32_t>>(x)));
} else {
x = SwapByteOrder32(x);
}
}
if (sizeof(x) == 8) {
if (std::is_signed_v<T>) {
x = static_cast<std::make_signed_t<T>>(
SwapByteOrder64(static_cast<std::make_unsigned_t<uint64_t>>(x)));
} else {
x = SwapByteOrder64(x);
}
}
}
template <typename T>
inline void NoSwapByteOrder(T& x) {}
#endif /* KYTY_COMMON_PLATFORM_SYSSWAPBYTEORDER_H_ */
+1 -5
View File
@@ -151,10 +151,6 @@ inline void SysTimeTToSystem(time_t t, SysTimeStruct& s) {
SysFileToSystemTimeUtc(ft, s);
}
inline time_t SysTimegm(struct tm* tm) {
return timegm(tm);
}
inline void SysSystemToFileTimeUtc(const SysTimeStruct& f, SysFileTimeStruct& t) {
struct tm i {};
@@ -165,7 +161,7 @@ inline void SysSystemToFileTimeUtc(const SysTimeStruct& f, SysFileTimeStruct& t)
i.tm_min = f.Minute;
i.tm_sec = f.Second;
t.is_invalid = (f.is_invalid || (t.time = SysTimegm(&i)) == static_cast<time_t>(-1));
t.is_invalid = (f.is_invalid || (t.time = ::timegm(&i)) == static_cast<time_t>(-1));
}
// Retrieves the current local date and time.
-28
View File
@@ -1,28 +0,0 @@
#ifndef KYTY_COMMON_PLATFORM_SYSVIRTUAL_H_
#define KYTY_COMMON_PLATFORM_SYSVIRTUAL_H_
#include "common/common.h"
#include "common/virtualMemory.h"
namespace Common {
void SysVirtualInit();
uint64_t SysVirtualAlloc(uint64_t address, uint64_t size, VirtualMemory::Mode mode);
uint64_t SysVirtualAllocAligned(uint64_t address, uint64_t size, VirtualMemory::Mode mode,
uint64_t alignment);
bool SysVirtualAllocFixed(uint64_t address, uint64_t size, VirtualMemory::Mode mode);
bool SysVirtualCommit(uint64_t address, uint64_t size, VirtualMemory::Mode mode);
uint64_t SysVirtualReserve(uint64_t address, uint64_t size);
uint64_t SysVirtualReserveAligned(uint64_t address, uint64_t size, uint64_t alignment);
bool SysVirtualReserveFixed(uint64_t address, uint64_t size);
bool SysVirtualDecommit(uint64_t address, uint64_t size);
bool SysVirtualFree(uint64_t address);
bool SysVirtualFreeRange(uint64_t address, uint64_t size);
bool SysVirtualProtect(uint64_t address, uint64_t size, VirtualMemory::Mode mode,
VirtualMemory::Mode* old_mode = nullptr);
bool SysVirtualFlushInstructionCache(uint64_t address, uint64_t size);
} // namespace Common
#endif /* KYTY_COMMON_PLATFORM_SYSVIRTUAL_H_ */
-12
View File
@@ -15,18 +15,6 @@
#include "common/assert.h"
#include "common/platform/sysDbg.h"
void SysStackWalk(void** stack, int* depth) {
*depth = static_cast<int>(CaptureStackBackTrace(0, static_cast<DWORD>(*depth), stack, nullptr));
}
void SysStackUsagePrint(sys_dbg_stack_info_t& stack) {
printf("stack: (0x%" PRIx64 ", %" PRIu64 ") + (0x%" PRIx64 ", %" PRIu64 ") + (0x%" PRIx64
", %" PRIu64 ")\n",
static_cast<uint64_t>(stack.reserved_addr), static_cast<uint64_t>(stack.reserved_size),
static_cast<uint64_t>(stack.guard_addr), static_cast<uint64_t>(stack.guard_size),
static_cast<uint64_t>(stack.commited_addr), static_cast<uint64_t>(stack.commited_size));
}
void SysStackUsage(sys_dbg_stack_info_t& s) {
MEMORY_BASIC_INFORMATION mbi {};
[[maybe_unused]] size_t ss = VirtualQuery(&mbi, &mbi, sizeof(mbi));
+7 -65
View File
@@ -14,6 +14,7 @@
#include "common/stringUtils.h"
#include <cstdlib>
#include <cstring>
#include <vector>
// NOLINTNEXTLINE(readability-identifier-naming)
@@ -56,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) {
@@ -135,10 +136,6 @@ void SysFileWrite(const void* data, uint32_t size, sys_file_t& f, uint32_t* byte
}
}
void SysFileWrite(uint32_t n, sys_file_t& f) {
SysFileWrite(&n, 4, f);
}
sys_file_t* SysFileCreate(const std::filesystem::path& file_name) {
auto* ret = new sys_file_t;
@@ -479,66 +476,11 @@ bool SysFileSetLastAccessAndWriteTimeUtc(const std::filesystem::path& name,
return ok;
}
void SysFileFindFiles(const std::filesystem::path& path, std::vector<sys_file_find_t>& out) {
std::string real_path = Common::ReplaceChar(Common::PathToGenericString(path), '\\', '/');
if (!Common::EndsWith(real_path, "/")) {
real_path += "/";
}
std::string pattern = real_path + "*";
HANDLE h = nullptr;
WIN32_FIND_DATAW data;
auto wide_pattern = std::filesystem::path(pattern).wstring();
h = FindFirstFileW(wide_pattern.c_str(), &data);
if (h == INVALID_HANDLE_VALUE) {
return;
}
do {
std::filesystem::path file_name(data.cFileName);
auto file_name_str = Common::PathToString(file_name);
if (file_name_str == "." || file_name_str == "..") {
continue;
}
if ((data.dwFileAttributes & static_cast<DWORD>(FILE_ATTRIBUTE_DIRECTORY)) != 0u) {
SysFileFindFiles(std::filesystem::path(real_path) / file_name, out);
} else {
sys_file_find_t r {};
r.path_with_name = std::filesystem::path(real_path) / file_name;
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(r);
}
} while (FindNextFileW(h, &data) != 0);
FindClose(h);
}
void SysFileGetDents(const std::filesystem::path& path, std::vector<sys_dir_entry_t>& out) {
std::string real_path = Common::ReplaceChar(Common::PathToGenericString(path), '\\', '/');
if (!Common::EndsWith(real_path, "/")) {
real_path += "/";
}
const auto pattern = path / L"*";
std::string pattern = real_path + "*";
HANDLE h = nullptr;
WIN32_FIND_DATAW data;
auto wide_pattern = std::filesystem::path(pattern).wstring();
h = FindFirstFileW(wide_pattern.c_str(), &data);
WIN32_FIND_DATAW data {};
HANDLE h = FindFirstFileW(pattern.c_str(), &data);
if (h == INVALID_HANDLE_VALUE) {
return;
@@ -552,7 +494,7 @@ void SysFileGetDents(const std::filesystem::path& path, std::vector<sys_dir_entr
r.is_file = ((data.dwFileAttributes & static_cast<DWORD>(FILE_ATTRIBUTE_DIRECTORY)) == 0u);
r.name = Common::PathToString(file_name);
out.push_back(r);
out.push_back(std::move(r));
} while (FindNextFileW(h, &data) != 0);
+31 -49
View File
@@ -10,7 +10,6 @@
#include <windows.h> // IWYU pragma: keep
#include "common/assert.h"
#include "common/platform/sysVirtual.h"
#include "common/virtualMemory.h"
// IWYU pragma: no_include <basetsd.h>
@@ -22,45 +21,33 @@
// IWYU pragma: no_include <winerror.h>
// IWYU pragma: no_include <wtypes.h>
namespace Common {
namespace Common::VirtualMemory {
static DWORD GetProtectionFlag(VirtualMemory::Mode mode) {
static DWORD GetProtectionFlag(Mode mode) {
DWORD protect = PAGE_NOACCESS;
switch (mode) {
case VirtualMemory::Mode::Read: protect = PAGE_READONLY; break;
case Mode::Read: protect = PAGE_READONLY; break;
case VirtualMemory::Mode::Write:
case VirtualMemory::Mode::ReadWrite: protect = PAGE_READWRITE; break;
case Mode::Write:
case Mode::ReadWrite: protect = PAGE_READWRITE; break;
case VirtualMemory::Mode::Execute: protect = PAGE_EXECUTE; break;
case Mode::Execute: protect = PAGE_EXECUTE; break;
case VirtualMemory::Mode::ExecuteRead: protect = PAGE_EXECUTE_READ; break;
case Mode::ExecuteRead: protect = PAGE_EXECUTE_READ; break;
case VirtualMemory::Mode::ExecuteWrite:
case VirtualMemory::Mode::ExecuteReadWrite: protect = PAGE_EXECUTE_READWRITE; break;
case Mode::ExecuteWrite:
case Mode::ExecuteReadWrite: protect = PAGE_EXECUTE_READWRITE; break;
case VirtualMemory::Mode::NoAccess:
case Mode::NoAccess:
default: protect = PAGE_NOACCESS; break;
}
return protect;
}
static VirtualMemory::Mode GetProtectionFlag(DWORD mode) {
switch (mode) {
case PAGE_NOACCESS: return VirtualMemory::Mode::NoAccess;
case PAGE_READONLY: return VirtualMemory::Mode::Read;
case PAGE_READWRITE: return VirtualMemory::Mode::ReadWrite;
case PAGE_EXECUTE: return VirtualMemory::Mode::Execute;
case PAGE_EXECUTE_READ: return VirtualMemory::Mode::ExecuteRead;
case PAGE_EXECUTE_READWRITE: return VirtualMemory::Mode::ExecuteReadWrite;
default: return VirtualMemory::Mode::NoAccess;
}
}
void Init() {}
void SysVirtualInit() {}
uint64_t SysVirtualAlloc(uint64_t address, uint64_t size, VirtualMemory::Mode mode) {
auto ptr = (address == 0 ? SysVirtualAllocAligned(address, size, mode, 1)
uint64_t Alloc(uint64_t address, uint64_t size, Mode mode) {
auto ptr = (address == 0 ? AllocAligned(address, size, mode, 1)
: reinterpret_cast<uintptr_t>(VirtualAlloc(
reinterpret_cast<LPVOID>(static_cast<uintptr_t>(address)), size,
static_cast<DWORD>(MEM_COMMIT) | static_cast<DWORD>(MEM_RESERVE),
@@ -71,7 +58,7 @@ uint64_t SysVirtualAlloc(uint64_t address, uint64_t size, VirtualMemory::Mode mo
if (err != ERROR_INVALID_ADDRESS) {
printf("VirtualAlloc() failed: 0x%08" PRIx32 "\n", err);
} else {
return SysVirtualAllocAligned(address, size, mode, 1);
return AllocAligned(address, size, mode, 1);
}
}
return ptr;
@@ -92,8 +79,7 @@ static uint64_t AlignUp(uint64_t addr, uint64_t alignment) {
return (addr + alignment - 1) & ~(alignment - 1);
}
uint64_t SysVirtualAllocAligned(uint64_t address, uint64_t size, VirtualMemory::Mode mode,
uint64_t alignment) {
uint64_t AllocAligned(uint64_t address, uint64_t size, Mode mode, uint64_t alignment) {
if (alignment == 0) {
printf("VirtualAlloc2 failed: 0x%08" PRIx32 "\n", static_cast<uint32_t>(GetLastError()));
return 0;
@@ -147,13 +133,13 @@ uint64_t SysVirtualAllocAligned(uint64_t address, uint64_t size, VirtualMemory::
printf("VirtualAlloc2(alignment = 0x%016" PRIx64 ") failed: 0x%08" PRIx32 "\n",
alignment, err);
} else {
return SysVirtualAllocAligned(address, size, mode, alignment << 1u);
return AllocAligned(address, size, mode, alignment << 1u);
}
}
return ptr;
}
bool SysVirtualAllocFixed(uint64_t address, uint64_t size, VirtualMemory::Mode mode) {
bool AllocFixed(uint64_t address, uint64_t size, Mode mode) {
auto ptr = reinterpret_cast<uintptr_t>(VirtualAlloc(
reinterpret_cast<LPVOID>(static_cast<uintptr_t>(address)), size,
static_cast<DWORD>(MEM_COMMIT) | static_cast<DWORD>(MEM_RESERVE), GetProtectionFlag(mode)));
@@ -173,7 +159,7 @@ bool SysVirtualAllocFixed(uint64_t address, uint64_t size, VirtualMemory::Mode m
return true;
}
bool SysVirtualCommit(uint64_t address, uint64_t size, VirtualMemory::Mode mode) {
bool Commit(uint64_t address, uint64_t size, Mode mode) {
auto ptr = reinterpret_cast<uintptr_t>(
VirtualAlloc(reinterpret_cast<LPVOID>(static_cast<uintptr_t>(address)), size,
static_cast<DWORD>(MEM_COMMIT), GetProtectionFlag(mode)));
@@ -191,8 +177,8 @@ bool SysVirtualCommit(uint64_t address, uint64_t size, VirtualMemory::Mode mode)
return true;
}
uint64_t SysVirtualReserve(uint64_t address, uint64_t size) {
auto ptr = (address == 0 ? SysVirtualReserveAligned(address, size, 1)
uint64_t Reserve(uint64_t address, uint64_t size) {
auto ptr = (address == 0 ? ReserveAligned(address, size, 1)
: reinterpret_cast<uintptr_t>(VirtualAlloc(
reinterpret_cast<LPVOID>(static_cast<uintptr_t>(address)), size,
static_cast<DWORD>(MEM_RESERVE), PAGE_NOACCESS)));
@@ -202,13 +188,13 @@ uint64_t SysVirtualReserve(uint64_t address, uint64_t size) {
if (err != ERROR_INVALID_ADDRESS) {
printf("VirtualAlloc(MEM_RESERVE) failed: 0x%08" PRIx32 "\n", err);
} else {
return SysVirtualReserveAligned(address, size, 1);
return ReserveAligned(address, size, 1);
}
}
return ptr;
}
uint64_t SysVirtualReserveAligned(uint64_t address, uint64_t size, uint64_t alignment) {
uint64_t ReserveAligned(uint64_t address, uint64_t size, uint64_t alignment) {
if (alignment == 0) {
printf("VirtualAlloc2(MEM_RESERVE) failed: 0x%08" PRIx32 "\n",
static_cast<uint32_t>(GetLastError()));
@@ -262,13 +248,13 @@ uint64_t SysVirtualReserveAligned(uint64_t address, uint64_t size, uint64_t alig
"\n",
alignment, err);
} else {
return SysVirtualReserveAligned(address, size, alignment << 1u);
return ReserveAligned(address, size, alignment << 1u);
}
}
return ptr;
}
bool SysVirtualReserveFixed(uint64_t address, uint64_t size) {
bool ReserveFixed(uint64_t address, uint64_t size) {
auto ptr = reinterpret_cast<uintptr_t>(
VirtualAlloc(reinterpret_cast<LPVOID>(static_cast<uintptr_t>(address)), size,
static_cast<DWORD>(MEM_RESERVE), PAGE_NOACCESS));
@@ -288,7 +274,7 @@ bool SysVirtualReserveFixed(uint64_t address, uint64_t size) {
return true;
}
bool SysVirtualDecommit(uint64_t address, uint64_t size) {
bool Decommit(uint64_t address, uint64_t size) {
if (VirtualFree(reinterpret_cast<LPVOID>(static_cast<uintptr_t>(address)), size,
MEM_DECOMMIT) == 0) {
printf("VirtualFree(MEM_DECOMMIT) failed: 0x%08" PRIx32 "\n",
@@ -298,7 +284,7 @@ bool SysVirtualDecommit(uint64_t address, uint64_t size) {
return true;
}
bool SysVirtualFree(uint64_t address) {
bool Free(uint64_t address) {
if (VirtualFree(reinterpret_cast<LPVOID>(static_cast<uintptr_t>(address)), 0, MEM_RELEASE) ==
0) {
printf("VirtualFree() failed: 0x%08" PRIx32 "\n", static_cast<uint32_t>(GetLastError()));
@@ -307,7 +293,7 @@ bool SysVirtualFree(uint64_t address) {
return true;
}
bool SysVirtualFreeRange(uint64_t address, uint64_t size) {
bool FreeRange(uint64_t address, uint64_t size) {
if (address == 0 || size == 0) {
return false;
}
@@ -325,24 +311,20 @@ bool SysVirtualFreeRange(uint64_t address, uint64_t size) {
}
current = next;
}
return current - address == size && SysVirtualFree(address);
return current - address == size && Free(address);
}
bool SysVirtualProtect(uint64_t address, uint64_t size, VirtualMemory::Mode mode,
VirtualMemory::Mode* old_mode) {
bool Protect(uint64_t address, uint64_t size, Mode mode) {
DWORD old_protect = 0;
if (VirtualProtect(reinterpret_cast<LPVOID>(static_cast<uintptr_t>(address)), size,
GetProtectionFlag(mode), &old_protect) == 0) {
printf("VirtualProtect() failed: 0x%08" PRIx32 "\n", static_cast<uint32_t>(GetLastError()));
return false;
}
if (old_mode != nullptr) {
*old_mode = GetProtectionFlag(old_protect);
}
return true;
}
bool SysVirtualFlushInstructionCache(uint64_t address, uint64_t size) {
bool FlushInstructionCache(uint64_t address, uint64_t size) {
if (::FlushInstructionCache(GetCurrentProcess(),
reinterpret_cast<LPVOID>(static_cast<uintptr_t>(address)),
size) == 0) {
@@ -353,6 +335,6 @@ bool SysVirtualFlushInstructionCache(uint64_t address, uint64_t size) {
return true;
}
} // namespace Common
} // namespace Common::VirtualMemory
#endif
+7 -13
View File
@@ -55,19 +55,13 @@ void SetThreadName(const char* name) {
}
void Initialize() {
switch (Config::GetProfilerDirection()) {
case Config::ProfilerDirection::Network:
if (!tracy::ProfilerAvailable()) {
tracy::StartupProfiler();
TracySetProgramName("KytyPS5");
::printf("Tracy profiler enabled: client %d.%d.%d, protocol %u, "
"broadcast %u, connect to 127.0.0.1:8086\n",
tracy::Version::Major, tracy::Version::Minor, tracy::Version::Patch,
tracy::ProtocolVersion, tracy::BroadcastVersion);
}
break;
case Config::ProfilerDirection::None:
default: break;
if (Config::ProfilerEnabled() && !tracy::ProfilerAvailable()) {
tracy::StartupProfiler();
TracySetProgramName("KytyPS5");
::printf("Tracy profiler enabled: client %d.%d.%d, protocol %u, "
"broadcast %u, connect to 127.0.0.1:8086\n",
tracy::Version::Major, tracy::Version::Minor, tracy::Version::Patch,
tracy::ProtocolVersion, tracy::BroadcastVersion);
}
}
+12 -180
View File
@@ -1,7 +1,6 @@
#ifndef KYTY_COMMON_STRING_UTILS_H_
#define KYTY_COMMON_STRING_UTILS_H_
#include "common/byteBuffer.h"
#include "common/common.h"
#include <algorithm>
@@ -9,7 +8,6 @@
#include <charconv>
#include <codecvt>
#include <filesystem>
#include <fmt/format.h>
#include <locale>
#include <string>
#include <string_view>
@@ -18,8 +16,6 @@
namespace Common {
constexpr uint32_t FIND_INVALID_INDEX = static_cast<uint32_t>(-1);
inline std::string PathToString(const std::filesystem::path& path) {
#if defined(__cpp_char8_t)
auto u8 = path.u8string();
@@ -38,8 +34,12 @@ inline std::string PathToGenericString(const std::filesystem::path& path) {
#endif
}
inline bool IsSpace(char c) {
return std::isspace(static_cast<unsigned char>(c)) != 0;
inline std::filesystem::path PathFromUtf8(std::string_view text) {
#if defined(__cpp_char8_t)
return std::filesystem::path(std::u8string(text.begin(), text.end()));
#else
return std::filesystem::u8path(text.begin(), text.end());
#endif
}
inline bool EqualNoCase(std::string_view lhs, std::string_view rhs) {
@@ -63,65 +63,6 @@ inline std::string ToLower(std::string_view text) {
return ret;
}
inline bool ContainsStr(std::string_view text, std::string_view needle) {
return text.find(needle) != std::string_view::npos;
}
inline bool StartsWith(std::string_view text, std::string_view prefix) {
return text.starts_with(prefix);
}
inline bool StartsWith(std::string_view text, char prefix) {
return !text.empty() && text.front() == prefix;
}
inline bool EndsWith(std::string_view text, std::string_view suffix) {
return text.ends_with(suffix);
}
inline bool EndsWith(std::string_view text, char suffix) {
return !text.empty() && text.back() == suffix;
}
inline bool ContainsChar(std::string_view text, char needle) {
return text.find(needle) != std::string_view::npos;
}
inline bool IndexValid(std::string_view text, uint32_t index) {
return index < text.size();
}
inline uint32_t FindIndex(std::string_view text, std::string_view needle, size_t from = 0) {
const auto pos = text.find(needle, from);
return pos == std::string_view::npos ? FIND_INVALID_INDEX : static_cast<uint32_t>(pos);
}
inline uint32_t FindIndex(std::string_view text, char needle, size_t from = 0) {
const auto pos = text.find(needle, from);
return pos == std::string_view::npos ? FIND_INVALID_INDEX : static_cast<uint32_t>(pos);
}
inline uint32_t FindLastIndex(std::string_view text, std::string_view needle,
size_t from = FIND_INVALID_INDEX) {
if (needle.empty()) {
return 0;
}
if (from == FIND_INVALID_INDEX || from > text.size()) {
from = text.size();
}
const auto pos = text.rfind(needle, from);
return pos == std::string_view::npos ? FIND_INVALID_INDEX : static_cast<uint32_t>(pos);
}
inline uint32_t FindLastIndex(std::string_view text, char needle,
size_t from = FIND_INVALID_INDEX) {
if (from == FIND_INVALID_INDEX || from > text.size()) {
from = text.size();
}
const auto pos = text.rfind(needle, from);
return pos == std::string_view::npos ? FIND_INVALID_INDEX : static_cast<uint32_t>(pos);
}
inline std::string Mid(std::string_view text, size_t first, size_t count = std::string_view::npos) {
if (first >= text.size()) {
return {};
@@ -129,49 +70,18 @@ inline std::string Mid(std::string_view text, size_t first, size_t count = std::
return std::string(text.substr(first, count));
}
inline std::string Left(std::string_view text, size_t count) {
return Mid(text, 0, count);
}
inline std::string Right(std::string_view text, size_t count) {
if (count >= text.size()) {
return std::string(text);
}
return Mid(text, text.size() - count, count);
}
inline std::string RemoveLast(std::string_view text, size_t count) {
if (count >= text.size()) {
return {};
}
return Left(text, text.size() - count);
return std::string(text.substr(0, text.size() - count));
}
inline std::string RemoveFirst(std::string_view text, size_t count) {
if (count >= text.size()) {
return {};
}
return Right(text, text.size() - count);
}
inline std::string TrimRight(std::string_view text) {
size_t end = text.size();
while (end > 0 && IsSpace(text[end - 1])) {
end--;
}
return std::string(text.substr(0, end));
}
inline std::string TrimLeft(std::string_view text) {
size_t first = 0;
while (first < text.size() && IsSpace(text[first])) {
first++;
}
return std::string(text.substr(first));
}
inline std::string Trim(std::string_view text) {
return TrimRight(TrimLeft(text));
return std::string(text.substr(count));
}
inline std::string ReplaceChar(std::string_view text, char old_char, char new_char) {
@@ -204,16 +114,6 @@ inline std::string FilenameWithoutDirectory(std::string_view text) {
return pos == std::string_view::npos ? std::string(text) : std::string(text.substr(pos + 1));
}
inline std::string FilenameWithoutExtension(std::string_view text) {
const auto pos = text.find_last_of('.');
return pos == std::string_view::npos ? std::string(text) : std::string(text.substr(0, pos));
}
inline std::string ExtensionWithoutFilename(std::string_view text) {
const auto pos = text.find_last_of('.');
return pos == std::string_view::npos ? std::string() : std::string(text.substr(pos));
}
inline std::string FixFilenameSlash(std::string_view text) {
return ReplaceChar(text, '\\', '/');
}
@@ -257,86 +157,18 @@ inline std::vector<std::string> Split(std::string_view text, char sep, bool keep
return Split(text, std::string_view(&sep, 1), keep_empty);
}
inline std::string Concat(const std::vector<std::string>& list, std::string_view sep) {
std::string ret;
for (size_t i = 0; i < list.size(); i++) {
if (i != 0) {
ret += sep;
}
ret += list[i];
}
return ret;
}
inline std::string Concat(const std::vector<std::string>& list, char sep) {
return Concat(list, std::string_view(&sep, 1));
}
inline uint32_t ToUint32(std::string_view text, int base = 10) {
uint32_t value = 0;
std::from_chars(text.data(), text.data() + text.size(), value, base);
return value;
}
inline uint64_t ToUint64(std::string_view text, int base = 10) {
uint64_t value = 0;
std::from_chars(text.data(), text.data() + text.size(), value, base);
return value;
}
inline int32_t ToInt32(std::string_view text, int base = 10) {
int32_t value = 0;
std::from_chars(text.data(), text.data() + text.size(), value, base);
return value;
}
inline int64_t ToInt64(std::string_view text, int base = 10) {
int64_t value = 0;
std::from_chars(text.data(), text.data() + text.size(), value, base);
return value;
}
inline double ToDouble(std::string_view text) {
return std::stod(std::string(text));
}
inline float ToFloat(std::string_view text) {
return std::stof(std::string(text));
}
inline std::string SafeCsv(std::string_view text) {
const bool add_space =
!text.empty() && (text.front() == '+' || text.front() == '=' || text.front() == '-');
if (text.find('"') != std::string_view::npos || text.find(';') != std::string_view::npos ||
text.find('+') != std::string_view::npos || text.find('=') != std::string_view::npos ||
text.find('-') != std::string_view::npos) {
return "\"" + std::string(add_space ? " " : "") +
ReplaceStr(std::string(text), "\"", "\"\"") + "\"";
inline std::string Utf16ToUtf8(std::u16string_view utf16) {
if (utf16.empty()) {
return {};
}
return std::string(text);
}
inline std::string Utf16ToUtf8(const char16_t* utf16) {
std::u16string input(utf16);
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> convert;
return convert.to_bytes(input);
}
inline ByteBuffer HexToBin(std::string_view text) {
ByteBuffer ret;
for (size_t i = 0; i + 1 < text.size(); i += 2) {
ret.Add(static_cast<Byte>((ToUint32(text.substr(i, 2), 16) & 0xffu)));
}
return ret;
}
inline std::string HexFromBin(const ByteBuffer& bin) {
std::string ret;
ret.reserve(bin.Size() * 2);
for (uint32_t i = 0; i < bin.Size(); i++) {
ret += fmt::format("{:02X}", std::to_integer<uint8_t>(bin.At(i)));
}
return ret;
return convert.to_bytes(utf16.data(), utf16.data() + utf16.size());
}
} // namespace Common
+8 -51
View File
@@ -1,7 +1,6 @@
#include "common/threads.h"
#include "common/assert.h"
#include "common/debug.h"
#include <algorithm>
#include <atomic>
@@ -9,7 +8,6 @@
#include <chrono> // IWYU pragma: keep
#include <condition_variable> // IWYU pragma: keep
#include <mutex>
#include <vector>
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS && KYTY_COMPILER == KYTY_COMPILER_CLANG
#define KYTY_WIN_CS
@@ -198,37 +196,7 @@ struct CondVarPrivate {
#endif
};
static std::recursive_mutex g_cond_waiters_mutex;
static std::vector<std::pair<int, CondVarPrivate*>> g_cond_waiters;
static wait_poll_func_t g_cond_wait_poll_callback = nullptr;
static void WakeCondVar(CondVarPrivate* cond_var) {
#ifdef KYTY_WIN_CS
static auto func = ResolveWakeAllConditionVariable();
EXIT_NOT_IMPLEMENTED(func == nullptr);
func(&cond_var->m_cv);
#else
cond_var->m_cv.notify_all();
#endif
}
static void RegisterCondWaiter(CondVarPrivate* cond_var) {
std::lock_guard lock(g_cond_waiters_mutex);
g_cond_waiters.emplace_back(Thread::GetThreadIdUnique(), cond_var);
}
static void UnregisterCondWaiter(CondVarPrivate* cond_var) {
const auto thread_id = Thread::GetThreadIdUnique();
std::lock_guard lock(g_cond_waiters_mutex);
const auto it = std::find_if(g_cond_waiters.begin(), g_cond_waiters.end(),
[thread_id, cond_var](const auto& waiter) {
return waiter.first == thread_id && waiter.second == cond_var;
});
if (it != g_cond_waiters.end()) {
g_cond_waiters.erase(it);
}
}
static wait_poll_func_t g_cond_wait_poll_callback = nullptr;
struct ThreadPrivate {
ThreadPrivate(thread_func_t f, void* a): func(f), arg(a), m_thread(&Run, this) {}
@@ -285,10 +253,6 @@ void Thread::Detach() {
m_thread->m_thread.detach();
}
void Thread::Sleep(uint32_t millis) {
std::this_thread::sleep_for(std::chrono::milliseconds(millis));
}
void Thread::SleepMicro(uint32_t micros) {
#ifdef KYTY_WIN_CS
SleepHighResolution100ns(static_cast<uint64_t>(micros) * 10);
@@ -366,7 +330,6 @@ CondVar::~CondVar() {
}
void CondVar::Wait(Mutex* mutex) {
RegisterCondWaiter(m_cond_var.get());
#ifndef KYTY_WIN_CS
std::unique_lock<std::recursive_mutex> cpp_lock(mutex->m_mutex->m_mutex, std::adopt_lock_t());
#endif
@@ -407,7 +370,6 @@ void CondVar::Wait(Mutex* mutex) {
}
cpp_lock.release();
#endif
UnregisterCondWaiter(m_cond_var.get());
}
void CondVar::SetWaitPollCallback(wait_poll_func_t callback) {
@@ -416,7 +378,6 @@ void CondVar::SetWaitPollCallback(wait_poll_func_t callback) {
bool CondVar::WaitFor(Mutex* mutex, uint32_t micros) {
bool ok = false;
RegisterCondWaiter(m_cond_var.get());
#ifndef KYTY_WIN_CS
std::unique_lock<std::recursive_mutex> cpp_lock(mutex->m_mutex->m_mutex, std::adopt_lock_t());
#endif
@@ -431,7 +392,6 @@ bool CondVar::WaitFor(Mutex* mutex, uint32_t micros) {
std::cv_status::no_timeout);
cpp_lock.release();
#endif
UnregisterCondWaiter(m_cond_var.get());
return ok;
}
@@ -446,16 +406,13 @@ void CondVar::Signal() {
}
void CondVar::SignalAll() {
WakeCondVar(m_cond_var.get());
}
void CondVar::SignalThread(int thread_id) {
std::lock_guard lock(g_cond_waiters_mutex);
for (const auto& waiter: g_cond_waiters) {
if (waiter.first == thread_id) {
WakeCondVar(waiter.second);
}
}
#ifdef KYTY_WIN_CS
static auto func = ResolveWakeAllConditionVariable();
EXIT_NOT_IMPLEMENTED(func == nullptr);
func(&m_cond_var->m_cv);
#else
m_cond_var->m_cv.notify_all();
#endif
}
int Thread::GetThreadIdUnique() {
-2
View File
@@ -31,7 +31,6 @@ public:
// The id is unique and can't be reused by another thread.
[[nodiscard]] int GetUniqueId() const;
static void Sleep(uint32_t millis);
static void SleepMicro(uint32_t micros);
static void SleepNano(uint64_t nanos);
static bool IsMainThread();
@@ -77,7 +76,6 @@ public:
void Signal();
void SignalAll();
static void SignalThread(int thread_id);
static void SetWaitPollCallback(wait_poll_func_t callback);
KYTY_CLASS_NO_COPY(CondVar);
-17
View File
@@ -62,23 +62,6 @@ double Timer::GetTimeS() const {
return (static_cast<double>(current_time - m_StartTime)) / static_cast<double>(m_Frequency);
}
// return time in ticks
uint64_t Timer::GetTicks() const {
if (m_is_paused) {
return (m_PauseTime - m_StartTime);
}
uint64_t current_time = 0;
SysQueryPerformanceCounter(&current_time);
return (current_time - m_StartTime);
}
// return ticks frequency
uint64_t Timer::GetFrequency() const {
return m_Frequency;
}
uint64_t Timer::QueryPerformanceFrequency() {
uint64_t ret = 0;
SysQueryPerformanceFrequency(&ret);
-6
View File
@@ -24,12 +24,6 @@ public:
// return time in seconds
[[nodiscard]] double GetTimeS() const;
// return time in ticks
[[nodiscard]] uint64_t GetTicks() const;
// return ticks frequency
[[nodiscard]] uint64_t GetFrequency() const;
KYTY_CLASS_NO_COPY(Timer);
[[nodiscard]] static uint64_t QueryPerformanceFrequency();
-63
View File
@@ -1,63 +0,0 @@
#include "common/virtualMemory.h"
#include "common/platform/sysVirtual.h"
namespace Common {
namespace VirtualMemory {
void Init() {
SysVirtualInit();
}
uint64_t Alloc(uint64_t address, uint64_t size, Mode mode) {
return SysVirtualAlloc(address, size, mode);
}
uint64_t AllocAligned(uint64_t address, uint64_t size, Mode mode, uint64_t alignment) {
return SysVirtualAllocAligned(address, size, mode, alignment);
}
bool AllocFixed(uint64_t address, uint64_t size, Mode mode) {
return SysVirtualAllocFixed(address, size, mode);
}
bool Commit(uint64_t address, uint64_t size, Mode mode) {
return SysVirtualCommit(address, size, mode);
}
uint64_t Reserve(uint64_t address, uint64_t size) {
return SysVirtualReserve(address, size);
}
uint64_t ReserveAligned(uint64_t address, uint64_t size, uint64_t alignment) {
return SysVirtualReserveAligned(address, size, alignment);
}
bool ReserveFixed(uint64_t address, uint64_t size) {
return SysVirtualReserveFixed(address, size);
}
bool Decommit(uint64_t address, uint64_t size) {
return SysVirtualDecommit(address, size);
}
bool Free(uint64_t address) {
return SysVirtualFree(address);
}
bool FreeRange(uint64_t address, uint64_t size) {
return SysVirtualFreeRange(address, size);
}
bool Protect(uint64_t address, uint64_t size, Mode mode, Mode* old_mode) {
return SysVirtualProtect(address, size, mode, old_mode);
}
bool FlushInstructionCache(uint64_t address, uint64_t size) {
return SysVirtualFlushInstructionCache(address, size);
}
} // namespace VirtualMemory
} // namespace Common
+1 -1
View File
@@ -35,7 +35,7 @@ bool ReserveFixed(uint64_t address, uint64_t size);
bool Decommit(uint64_t address, uint64_t size);
bool Free(uint64_t address);
bool FreeRange(uint64_t address, uint64_t size);
bool Protect(uint64_t address, uint64_t size, Mode mode, Mode* old_mode = nullptr);
bool Protect(uint64_t address, uint64_t size, Mode mode);
bool FlushInstructionCache(uint64_t address, uint64_t size);
} // namespace VirtualMemory
+4
View File
@@ -196,6 +196,10 @@ void Run(const RunOptions& options) {
ClearDebugTextureFolder();
PrintSystemInfo();
std::string title_id;
if (Loader::SystemContentParamSfoGetString("TITLE_ID", &title_id) && !title_id.empty()) {
Log::WriteToConsoleAndLog(fmt::format("Title ID: {}\n", title_id));
}
int ok = atexit(KytyClose);
EXIT_NOT_IMPLEMENTED(ok != 0);
@@ -32,11 +32,12 @@ private:
struct BufferCursor {
std::span<const uint32_t> commands;
uint32_t offset_dw = 0;
uint32_t deferred_advance_dw = 0;
uint32_t offset_dw = 0;
};
std::vector<BufferCursor> m_buffer_stack;
std::span<const uint32_t> m_next_buffer;
bool m_chain = false;
bool m_suspended = false;
bool m_made_progress = false;
};
@@ -73,13 +74,9 @@ public:
void SetDrawIndirectArgsBaseAddress(uint64_t draw_indirect_args_base_addr);
void SetDispatchIndirectArgsBaseAddress(uint64_t dispatch_indirect_args_base_addr);
void SetNumInstances(uint32_t num_instances);
void DrawIndex(uint32_t index_count, const void* index_addr, uint32_t flags, uint32_t type,
uint32_t instance_count = 0, const void* object_ids = nullptr,
uint32_t render_target_slice_offset = 0, int32_t vertex_offset_add = 0,
uint32_t first_instance = 0);
void DrawIndexOffset(uint32_t index_offset, uint32_t index_count, uint32_t flags);
void DrawIndexAuto(uint32_t index_count, uint32_t flags,
uint32_t render_target_slice_offset = 0);
void DrawIndex(DrawIndexArgs args);
void DrawIndexOffset(uint32_t index_offset, uint32_t index_count);
void DrawIndexAuto(DrawAutoArgs args);
void DrawIndirect(uint32_t data_offset, uint32_t draw_initiator, bool indexed);
void DrawIndirectMulti(uint32_t data_offset, uint32_t max_count_or_count,
const volatile uint32_t* count_addr, uint32_t stride_in_bytes,
@@ -133,7 +130,7 @@ public:
[[nodiscard]] bool ShouldSkipPredicatedPackets() const { return m_predicate_skip; }
Pm4ProcessResult Process(Pm4Execution& execution, std::span<const uint32_t> commands);
void ProcessIndirectBuffer(std::span<const uint32_t> commands);
void ProcessIndirectBuffer(std::span<const uint32_t> commands, bool chain);
void SetFlip(const FlipInfo& flip) { m_flip = flip; }
@@ -147,16 +144,11 @@ private:
uint32_t cache_action, uint32_t event_index, uint32_t event_write_source,
void* dst_gpu_addr, T value, uint32_t interrupt_selector,
uint32_t interrupt_context_id);
void ProcessPm4(Pm4Execution& execution, size_t stop_depth);
void ProcessPm4(Pm4Execution& execution);
void SuspendPm4();
void SubmitNonIndexedDraw(uint32_t vertex_count, uint32_t flags,
uint32_t render_target_slice_offset, uint32_t first_vertex,
uint32_t first_instance);
CommandScheduler& GetScheduler() const { return m_renderer.GetCommandScheduler(); }
CommandBuffer& CurrentBuffer() { return GetScheduler().Current(); }
void CheckBuffer() const { GetScheduler().CheckActive(); }
GpuResourceManager& GetGpuResources() const { return m_renderer.GetGpuResources(); }
RenderContext& m_renderer;
HW::Context m_ctx;
@@ -24,7 +24,6 @@ constexpr auto MakeContextDispatchTable() {
g_hw_ctx_func[Pm4::DB_DEPTH_CLEAR] = HwCtxSetDepthClear;
g_hw_ctx_func[Pm4::PA_SC_SCREEN_SCISSOR_TL] = HwCtxSetScreenScissor;
g_hw_ctx_func[Pm4::DB_STENCIL_INFO] = HwCtxSetStencilInfo;
g_hw_ctx_func[Pm4::DB_SHADING_RATE_ENCODING] = HwCtxSetDepthShadingRateEncoding;
g_hw_ctx_func[Pm4::PA_SU_HARDWARE_SCREEN_OFFSET] = HwCtxSetHardwareScreenOffset;
g_hw_ctx_func[Pm4::PA_SC_WINDOW_OFFSET] = HwCtxSetWindowOffset;
g_hw_ctx_func[Pm4::PA_SC_WINDOW_SCISSOR_TL] = HwCtxSetWindowScissor;
@@ -140,13 +139,13 @@ constexpr auto MakeShaderDispatchTable() {
g_hw_sh_func[Pm4::COMPUTE_TMPRING_SIZE] = HwShSetCsRegisters;
g_hw_sh_func[Pm4::COMPUTE_PGM_RSRC3] = HwShSetCsRegisters;
g_hw_sh_func[Pm4::COMPUTE_PACE_ID] = HwShSetCsRegisters;
g_hw_sh_func[Pm4::SPI_GRAPHICS_SHADER_CONTROL_PS] = HwShIgnoreRegisters;
g_hw_sh_func[Pm4::SPI_GRAPHICS_SHADER_CONTROL_GS] = HwShIgnoreRegisters;
g_hw_sh_func[Pm4::SPI_SHADER_USER_DATA_ADDR_LO_GS] = HwShIgnoreRegisters;
g_hw_sh_func[Pm4::SPI_SHADER_USER_DATA_ADDR_HI_GS] = HwShIgnoreRegisters;
g_hw_sh_func[Pm4::SPI_GRAPHICS_SHADER_CONTROL_HS] = HwShIgnoreRegisters;
g_hw_sh_func[Pm4::SPI_SHADER_USER_DATA_ADDR_LO_HS] = HwShIgnoreRegisters;
g_hw_sh_func[Pm4::SPI_SHADER_USER_DATA_ADDR_HI_HS] = HwShIgnoreRegisters;
g_hw_sh_func[Pm4::SPI_GRAPHICS_SHADER_CONTROL_PS] = HwShSetRegisters;
g_hw_sh_func[Pm4::SPI_GRAPHICS_SHADER_CONTROL_GS] = HwShSetRegisters;
g_hw_sh_func[Pm4::SPI_SHADER_USER_DATA_ADDR_LO_GS] = HwShSetRegisters;
g_hw_sh_func[Pm4::SPI_SHADER_USER_DATA_ADDR_HI_GS] = HwShSetRegisters;
g_hw_sh_func[Pm4::SPI_GRAPHICS_SHADER_CONTROL_HS] = HwShSetRegisters;
g_hw_sh_func[Pm4::SPI_SHADER_USER_DATA_ADDR_LO_HS] = HwShSetRegisters;
g_hw_sh_func[Pm4::SPI_SHADER_USER_DATA_ADDR_HI_HS] = HwShSetRegisters;
return g_hw_sh_func;
}
@@ -45,8 +45,6 @@ uint32_t HwCtxSetStencilClear(CommandProcessor&, uint32_t, uint32_t, const uint3
uint32_t HwCtxSetDepthClear(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwCtxSetScreenScissor(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwCtxSetStencilInfo(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwCtxSetDepthShadingRateEncoding(CommandProcessor&, uint32_t, uint32_t,
const uint32_t*, uint32_t);
uint32_t HwCtxSetHardwareScreenOffset(CommandProcessor&, uint32_t, uint32_t, const uint32_t*,
uint32_t);
uint32_t HwCtxSetWindowOffset(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
@@ -100,7 +98,7 @@ uint32_t HwShSetCsUserSgpr(CommandProcessor&, uint32_t, uint32_t, const uint32_t
uint32_t HwShIgnoreUserAccumulator(CommandProcessor&, uint32_t, uint32_t, const uint32_t*,
uint32_t);
uint32_t HwShSetCsRegisters(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwShIgnoreRegisters(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwShSetRegisters(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwUcSetPrimitiveType(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwUcSetIndexType(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwUcSetObjectId(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
@@ -136,7 +136,6 @@ static HW::ColorControl DecodeColorControl(uint32_t value) {
static HW::DepthControl DecodeDepthControl(uint32_t value) {
HW::DepthControl r {};
EXIT_NOT_IMPLEMENTED((value & 0xc0000000u) != 0);
r.stencil_enable = KYTY_PM4_GET(value, DB_DEPTH_CONTROL, STENCIL_ENABLE) != 0;
r.z_enable = KYTY_PM4_GET(value, DB_DEPTH_CONTROL, Z_ENABLE) != 0;
@@ -316,8 +315,17 @@ static void HwCtxSetDepthBoundsRegister(CommandProcessor& cp, uint32_t cmd_offse
}
}
static void HwCtxIgnoreDepthMetadataRegister([[maybe_unused]] uint32_t cmd_offset,
[[maybe_unused]] uint32_t value) {}
static void HwCtxSetDepthMetadataRegister(CommandProcessor& cp, uint32_t cmd_offset,
uint32_t value) {
if (cmd_offset == Pm4::DB_RENDER_OVERRIDE) {
HW::DepthRenderOverride control;
control.force_z_valid = (value & 0x20000000u) != 0;
control.force_z_dirty = (value & 0x08000000u) != 0;
control.force_stencil_valid = (value & 0x40000000u) != 0;
control.force_stencil_dirty = (value & 0x10000000u) != 0;
cp.GetCtx().SetDepthRenderOverride(control);
}
}
static void HwCtxIgnoreFovWindow([[maybe_unused]] uint32_t value) {}
@@ -390,7 +398,7 @@ KYTY_HW_CTX_PARSER(HwCtxSetDepthMetadataRegisters) {
auto num_values = KYTY_PM4_LEN(cmd_id) - 2u;
for (uint32_t i = 0; i < num_values; i++) {
HwCtxIgnoreDepthMetadataRegister(cmd_offset + i, buffer[i]);
HwCtxSetDepthMetadataRegister(cp, cmd_offset + i, buffer[i]);
}
return num_values;
@@ -600,17 +608,6 @@ static void HwCtxSetDepthShadingRateEncodingRegister(CommandProcessor& cp, uint3
ctx.SetDepthRenderTarget(target);
}
KYTY_HW_CTX_PARSER(HwCtxSetDepthShadingRateEncoding) {
auto num_values = KYTY_PM4_LEN(cmd_id) - 2u;
EXIT_NOT_IMPLEMENTED(num_values != 1);
EXIT_NOT_IMPLEMENTED(cmd_offset != Pm4::DB_SHADING_RATE_ENCODING);
HwCtxSetDepthShadingRateEncodingRegister(cp, buffer[0]);
return num_values;
}
static HW::EqaaControl ParseEqaaControl(uint32_t value) {
HW::EqaaControl r;
@@ -992,7 +989,7 @@ static void HwShIgnoreComputeRegister([[maybe_unused]] uint32_t cmd_offset,
static void HwShIgnoreShaderRegister([[maybe_unused]] uint32_t cmd_offset,
[[maybe_unused]] uint32_t value) {}
KYTY_HW_SH_PARSER(HwShIgnoreRegisters) {
KYTY_HW_SH_PARSER(HwShSetRegisters) {
const auto reg_num = (cmd_id >> 16u) & 0x3fffu;
EXIT_NOT_IMPLEMENTED(reg_num == 0);
@@ -1498,10 +1495,10 @@ KYTY_CP_OP_PARSER(CpOpBranch) {
reinterpret_cast<uint64_t>(else_buffer), else_num_dw);
if (take_then) {
cp.ProcessIndirectBuffer({then_buffer, then_num_dw});
cp.ProcessIndirectBuffer({then_buffer, then_num_dw}, true);
} else if (mode == 2 && else_num_dw != 0) {
EXIT_NOT_IMPLEMENTED(else_buffer == nullptr);
cp.ProcessIndirectBuffer({else_buffer, else_num_dw});
cp.ProcessIndirectBuffer({else_buffer, else_num_dw}, true);
}
return payload_dw;
@@ -1619,15 +1616,15 @@ KYTY_CP_OP_PARSER(CpOpDrawIndex) {
auto* index_addr =
reinterpret_cast<void*>(buffer[1] | (static_cast<uint64_t>(buffer[2]) << 32u));
uint32_t max_instance_count = buffer[3];
auto* object_ids =
reinterpret_cast<void*>(buffer[4] | (static_cast<uint64_t>(buffer[5]) << 32u));
uint32_t instance_count = buffer[6];
uint32_t flags = buffer[7];
uint32_t instance_count = buffer[6];
uint32_t flags = buffer[7];
EXIT_NOT_IMPLEMENTED(instance_count > max_instance_count);
EXIT_NOT_IMPLEMENTED((flags & ~0xa0u) != 0);
cp.DrawIndex(index_count, index_addr, 0, 1, instance_count, object_ids);
cp.DrawIndex({.index_count = index_count,
.index_addr = index_addr,
.instance_count = instance_count});
return 8;
}
@@ -1642,7 +1639,7 @@ KYTY_CP_OP_PARSER(CpOpDrawIndex) {
EXIT_NOT_IMPLEMENTED(index_count > max_index_count);
EXIT_NOT_IMPLEMENTED((flags & ~0x20u) != 0);
cp.DrawIndex(index_count, index_addr, 0, 1);
cp.DrawIndex({.index_count = index_count, .index_addr = index_addr});
return 5;
}
@@ -1701,7 +1698,7 @@ KYTY_CP_OP_PARSER(CpOpDrawIndexOffset) {
EXIT_NOT_IMPLEMENTED(index_count > max_index_size);
EXIT_NOT_IMPLEMENTED((flags & ~0x20u) != 0);
cp.DrawIndexOffset(index_offset, index_count, 0);
cp.DrawIndexOffset(index_offset, index_count);
return 4;
}
@@ -1716,7 +1713,7 @@ KYTY_CP_OP_PARSER(CpOpDrawIndexAuto) {
EXIT_NOT_IMPLEMENTED((flags & ~0x22u) != 0);
cp.DrawIndexAuto(index_count, 0);
cp.DrawIndexAuto({.vertex_count = index_count});
return 2;
}
@@ -1962,7 +1959,7 @@ KYTY_CP_OP_PARSER(CpOpIndirectBuffer) {
GraphicsDbgDumpDcb("ci", indirect_num_dw, indirect_buffer);
cp.ProcessIndirectBuffer({indirect_buffer, indirect_num_dw});
cp.ProcessIndirectBuffer({indirect_buffer, indirect_num_dw}, (control & (1u << 20u)) != 0);
return 3;
}
@@ -3015,28 +3012,28 @@ void GraphicsInitJmpTablesCxIndirect() {
HwCtxIgnoreCbDccControl(value);
};
g_hw_ctx_indirect_func[Pm4::DB_COUNT_CONTROL] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
HwCtxIgnoreDepthMetadataRegister(cmd_offset, value);
HwCtxSetDepthMetadataRegister(cp, cmd_offset, value);
};
for (auto cmd_offset = Pm4::DB_SRESULTS_COMPARE_STATE0;
cmd_offset <= Pm4::DB_SRESULTS_COMPARE_STATE1; cmd_offset++) {
g_hw_ctx_indirect_func[cmd_offset] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
HwCtxIgnoreDepthMetadataRegister(cmd_offset, value);
HwCtxSetDepthMetadataRegister(cp, cmd_offset, value);
};
}
g_hw_ctx_indirect_func[Pm4::DB_RENDER_OVERRIDE] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
HwCtxIgnoreDepthMetadataRegister(cmd_offset, value);
HwCtxSetDepthMetadataRegister(cp, cmd_offset, value);
};
g_hw_ctx_indirect_func[Pm4::DB_RENDER_OVERRIDE2] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
HwCtxIgnoreDepthMetadataRegister(cmd_offset, value);
HwCtxSetDepthMetadataRegister(cp, cmd_offset, value);
};
g_hw_ctx_indirect_func[Pm4::DB_DFSM_CONTROL] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
HwCtxIgnoreDepthMetadataRegister(cmd_offset, value);
HwCtxSetDepthMetadataRegister(cp, cmd_offset, value);
};
g_hw_ctx_indirect_func[Pm4::DB_RMI_L2_CACHE_CONTROL] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
HwCtxIgnoreDepthMetadataRegister(cmd_offset, value);
HwCtxSetDepthMetadataRegister(cp, cmd_offset, value);
};
g_hw_ctx_indirect_func[Pm4::CB_RMI_GL2_CACHE_CONTROL] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
HwCtxIgnoreDepthMetadataRegister(cmd_offset, value);
HwCtxSetDepthMetadataRegister(cp, cmd_offset, value);
};
g_hw_ctx_indirect_func[Pm4::TA_BC_BASE_ADDR] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
HwCtxIgnoreBorderColorTableAddr(cmd_offset, value);
@@ -3635,24 +3632,40 @@ void GraphicsInitJmpTablesShIndirect() {
g_hw_sh_indirect_func[Pm4::SPI_SHADER_PACE_ID_GS] = [](KYTY_HW_SH_INDIRECT_ARGS) {
HwShIgnoreShaderRegister(cmd_offset, value);
};
g_hw_sh_indirect_func[Pm4::SPI_SHADER_PGM_RSRC4_GS] = [](KYTY_HW_SH_INDIRECT_ARGS) {
HwShIgnoreShaderRegister(cmd_offset, value);
};
// Toolkit state restoration emits the compiler's GS-front allocation
// metadata here. Native GS resource registers already carry that allocation.
g_hw_sh_indirect_func[0x0ca] = [](KYTY_HW_SH_INDIRECT_ARGS) {
HwShIgnoreShaderRegister(cmd_offset, value);
};
g_hw_sh_indirect_func[Pm4::SPI_GRAPHICS_SHADER_CONTROL_GS] = [](KYTY_HW_SH_INDIRECT_ARGS) {
HwShIgnoreShaderRegister(cmd_offset, value);
};
g_hw_sh_indirect_func[Pm4::SPI_SHADER_USER_DATA_ADDR_LO_GS] = [](KYTY_HW_SH_INDIRECT_ARGS) {
for (uint32_t offset = Pm4::SPI_SHADER_USER_DATA_ADDR_LO_GS;
offset <= Pm4::SPI_SHADER_USER_DATA_ADDR_HI_GS; offset++) {
g_hw_sh_indirect_func[offset] = [](KYTY_HW_SH_INDIRECT_ARGS) {
cp.GetShCtx().SetGsUserDataAddress(cmd_offset - Pm4::SPI_SHADER_USER_DATA_ADDR_LO_GS,
value);
};
}
g_hw_sh_indirect_func[Pm4::SPI_SHADER_PGM_CHKSUM_HS] = [](KYTY_HW_SH_INDIRECT_ARGS) {
HwShIgnoreShaderRegister(cmd_offset, value);
};
g_hw_sh_indirect_func[Pm4::SPI_SHADER_USER_DATA_ADDR_HI_GS] = [](KYTY_HW_SH_INDIRECT_ARGS) {
g_hw_sh_indirect_func[Pm4::SPI_SHADER_PGM_RSRC4_HS] = [](KYTY_HW_SH_INDIRECT_ARGS) {
HwShIgnoreShaderRegister(cmd_offset, value);
};
g_hw_sh_indirect_func[Pm4::SPI_GRAPHICS_SHADER_CONTROL_HS] = [](KYTY_HW_SH_INDIRECT_ARGS) {
HwShIgnoreShaderRegister(cmd_offset, value);
};
g_hw_sh_indirect_func[Pm4::SPI_SHADER_USER_DATA_ADDR_LO_HS] = [](KYTY_HW_SH_INDIRECT_ARGS) {
HwShIgnoreShaderRegister(cmd_offset, value);
};
g_hw_sh_indirect_func[Pm4::SPI_SHADER_USER_DATA_ADDR_HI_HS] = [](KYTY_HW_SH_INDIRECT_ARGS) {
HwShIgnoreShaderRegister(cmd_offset, value);
};
for (uint32_t offset = Pm4::SPI_SHADER_USER_DATA_ADDR_LO_HS;
offset <= Pm4::SPI_SHADER_USER_DATA_ADDR_HI_HS; offset++) {
g_hw_sh_indirect_func[offset] = [](KYTY_HW_SH_INDIRECT_ARGS) {
cp.GetShCtx().SetHsUserDataAddress(cmd_offset - Pm4::SPI_SHADER_USER_DATA_ADDR_LO_HS,
value);
};
}
g_hw_sh_indirect_func[Pm4::SPI_SHADER_PGM_LO_HS] = [](KYTY_HW_SH_INDIRECT_ARGS) {
auto base = cp.GetShCtx().GetVs().hs_regs.data_addr;
base &= 0xFFFFFF00000000FFull;
@@ -3811,6 +3824,25 @@ void GraphicsInitJmpTablesUcIndirect() {
for (auto& func: g_hw_uc_indirect_func) {
func = nullptr;
}
for (uint32_t i = 0; i < 4; ++i) {
g_hw_uc_indirect_func[Pm4::FSR_CONTROL_POINTS_LEFT_X + i] = [](KYTY_HW_UC_INDIRECT_ARGS) {
cp.GetUcfg().SetFsrControlPoint(0, cmd_offset - Pm4::FSR_CONTROL_POINTS_LEFT_X, value);
};
g_hw_uc_indirect_func[Pm4::FSR_CONTROL_POINTS_LEFT_Y + i] = [](KYTY_HW_UC_INDIRECT_ARGS) {
cp.GetUcfg().SetFsrControlPoint(1, cmd_offset - Pm4::FSR_CONTROL_POINTS_LEFT_Y, value);
};
}
for (uint32_t i = 0; i < 2; ++i) {
g_hw_uc_indirect_func[Pm4::FSR_ALPHA_LEFT_X + i] = [](KYTY_HW_UC_INDIRECT_ARGS) {
cp.GetUcfg().SetFsrAlpha(0, cmd_offset - Pm4::FSR_ALPHA_LEFT_X, value);
};
g_hw_uc_indirect_func[Pm4::FSR_ALPHA_LEFT_Y + i] = [](KYTY_HW_UC_INDIRECT_ARGS) {
cp.GetUcfg().SetFsrAlpha(1, cmd_offset - Pm4::FSR_ALPHA_LEFT_Y, value);
};
g_hw_uc_indirect_func[Pm4::FSR_WINDOW_LEFT + i] = [](KYTY_HW_UC_INDIRECT_ARGS) {
cp.GetUcfg().SetFsrWindow(cmd_offset - Pm4::FSR_WINDOW_LEFT, value);
};
}
g_hw_uc_indirect_func[Pm4::GE_CNTL] = [](KYTY_HW_UC_INDIRECT_ARGS) {
HW::GeControl r;
@@ -3818,6 +3850,11 @@ void GraphicsInitJmpTablesUcIndirect() {
r.vertex_group_size = KYTY_PM4_GET(value, GE_CNTL, VERT_GRP_SIZE);
cp.GetUcfg().SetGeControl(r);
};
g_hw_uc_indirect_func[Pm4::UC_PARAMETER_OVERSUBSCRIPTION] = [](KYTY_HW_UC_INDIRECT_ARGS) {
(void)cp;
(void)cmd_offset;
(void)value;
};
g_hw_uc_indirect_func[Pm4::GE_USER_VGPR_EN] = [](KYTY_HW_UC_INDIRECT_ARGS) {
HW::GeUserVgprEn r;
r.vgpr1 = KYTY_PM4_GET(value, GE_USER_VGPR_EN, EN_USER_VGPR1) != 0;
+4
View File
@@ -26,6 +26,10 @@ enum class PrimitiveType : uint32_t {
kPolygon = 21,
};
constexpr bool IsRectList(PrimitiveType type) {
return type == PrimitiveType::kRectList || type == PrimitiveType::kRectListLegacy;
}
enum class IndexType : uint32_t {
kIndex16 = 0,
kIndex32 = 1,
+18 -16
View File
@@ -15,6 +15,7 @@ struct FormatInfo {
uint32_t render_target_bytes_per_element;
bool sampled_texture;
bool uint_texture;
bool sint_texture = false;
};
constexpr FormatInfo kFormatInfo[] = {
@@ -24,19 +25,19 @@ constexpr FormatInfo kFormatInfo[] = {
{BufferFormat::k16UNorm, 2, 0, 2, true, false},
{BufferFormat::k16SNorm, 2, 0, 2, true, false},
{BufferFormat::k16UInt, 2, 0, 2, true, true},
{BufferFormat::k16SInt, 2, 0, 2, false, false},
{BufferFormat::k16SInt, 2, 0, 2, true, false, true},
{BufferFormat::k16Float, 2, 0, 2, true, false},
{BufferFormat::k8_8UNorm, 2, 0, 2, true, false},
{BufferFormat::k8_8SNorm, 2, 0, 2, true, false},
{BufferFormat::k8_8UInt, 2, 0, 2, true, true},
{BufferFormat::k8_8SInt, 2, 0, 2, false, false},
{BufferFormat::k8_8SInt, 2, 0, 2, true, false, true},
{BufferFormat::k32UInt, 4, 0, 4, true, true},
{BufferFormat::k32SInt, 4, 0, 4, false, false},
{BufferFormat::k32SInt, 4, 0, 4, true, false, true},
{BufferFormat::k32Float, 4, 0, 4, true, false},
{BufferFormat::k16_16UNorm, 4, 0, 4, true, false},
{BufferFormat::k16_16SNorm, 4, 0, 4, true, false},
{BufferFormat::k16_16UInt, 4, 0, 4, true, true},
{BufferFormat::k16_16SInt, 4, 0, 4, false, false},
{BufferFormat::k16_16SInt, 4, 0, 4, true, false, true},
{BufferFormat::k16_16Float, 4, 0, 4, true, false},
{BufferFormat::k11_11_10UInt, 4, 0, 4, true, true},
{BufferFormat::k11_11_10Float, 4, 0, 4, true, false},
@@ -45,20 +46,20 @@ constexpr FormatInfo kFormatInfo[] = {
{BufferFormat::k8_8_8_8UNorm, 4, 0, 4, true, false},
{BufferFormat::k8_8_8_8SNorm, 4, 0, 4, true, false},
{BufferFormat::k8_8_8_8UInt, 4, 0, 4, true, true},
{BufferFormat::k8_8_8_8SInt, 4, 0, 4, false, false},
{BufferFormat::k8_8_8_8SInt, 4, 0, 4, true, false, true},
{BufferFormat::k32_32UInt, 8, 0, 8, true, true},
{BufferFormat::k32_32SInt, 8, 0, 8, false, false},
{BufferFormat::k32_32SInt, 8, 0, 8, true, false, true},
{BufferFormat::k32_32Float, 8, 0, 8, true, false},
{BufferFormat::k16_16_16_16UNorm, 8, 0, 8, true, false},
{BufferFormat::k16_16_16_16SNorm, 8, 0, 8, true, false},
{BufferFormat::k16_16_16_16UInt, 8, 0, 8, true, true},
{BufferFormat::k16_16_16_16SInt, 8, 0, 8, false, false},
{BufferFormat::k16_16_16_16SInt, 8, 0, 8, true, false, true},
{BufferFormat::k16_16_16_16Float, 8, 0, 8, true, false},
{BufferFormat::k32_32_32UInt, 12, 0, 12, true, true},
{BufferFormat::k32_32_32SInt, 12, 0, 12, false, false},
{BufferFormat::k32_32_32SInt, 12, 0, 12, true, false, true},
{BufferFormat::k32_32_32Float, 12, 0, 12, true, false},
{BufferFormat::k32_32_32_32UInt, 16, 0, 16, true, true},
{BufferFormat::k32_32_32_32SInt, 16, 0, 16, false, false},
{BufferFormat::k32_32_32_32SInt, 16, 0, 16, true, false, true},
{BufferFormat::k32_32_32_32Float, 16, 0, 16, true, false},
{BufferFormat::k8Srgb, 1, 0, 0, true, false},
{BufferFormat::k8_8Srgb, 2, 0, 0, true, false},
@@ -218,14 +219,15 @@ bool IsFmaskTextureFormat(BufferFormat format) {
return format >= BufferFormat::kFmask8_S2_F1 && format <= BufferFormat::kFmask64_S16_F8;
}
bool IsSampledTextureFormat(BufferFormat format) {
TextureNumericClass SampledTextureNumericClass(BufferFormat format) {
const auto* info = FindFormatInfo(format);
return info != nullptr && info->sampled_texture;
}
bool IsUintTextureFormat(BufferFormat format) {
const auto* info = FindFormatInfo(format);
return info != nullptr && info->uint_texture;
if (info == nullptr || !info->sampled_texture) {
return TextureNumericClass::Unsupported;
}
if (info->sint_texture) {
return TextureNumericClass::Sint;
}
return info->uint_texture ? TextureNumericClass::Uint : TextureNumericClass::Float;
}
BufferFormat RemapTextureFormat(BufferFormat format) {
+3 -2
View File
@@ -37,13 +37,14 @@ struct RenderTargetFormatEncoding {
}
};
enum class TextureNumericClass { Unsupported, Float, Uint, Sint };
RenderTargetFormatEncoding ResolveRenderTargetFormat(ChannelLayout layout, ChannelType type);
uint32_t NumBytesPerElement(BufferFormat format);
uint32_t BlockCompressedBytesPerBlock(BufferFormat format);
uint32_t RenderTargetBytesPerElement(BufferFormat format);
bool IsFmaskTextureFormat(BufferFormat format);
bool IsSampledTextureFormat(BufferFormat format);
bool IsUintTextureFormat(BufferFormat format);
TextureNumericClass SampledTextureNumericClass(BufferFormat format);
BufferFormat RemapTextureFormat(BufferFormat format);
} // namespace Libs::Graphics::Prospero
+124 -145
View File
@@ -13,7 +13,6 @@
#include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/host_gpu/renderer/sync.h"
#include "graphics/presentation/renderDoc.h"
#include "graphics/presentation/videoOut.h"
#include "graphics/presentation/window.h"
#include "graphics/shader/shader.h"
@@ -40,6 +39,21 @@ static thread_local bool g_gpu_mutex_owned = false;
static thread_local bool g_gpu_thread = false;
static thread_local GuestGpu* g_gpu_state = nullptr;
struct DrawIndirectArgs {
uint32_t vertex_count_per_instance;
uint32_t instance_count;
uint32_t start_vertex_location;
uint32_t start_instance_location;
};
struct DrawIndexedIndirectArgs {
uint32_t index_count_per_instance;
uint32_t instance_count;
uint32_t start_index_location;
uint32_t base_vertex_location;
uint32_t start_instance_location;
};
class GpuMutexLock final {
public:
explicit GpuMutexLock(Common::Mutex& mutex): m_mutex(mutex) {
@@ -63,7 +77,7 @@ private:
static bool GraphicsRunDebugDumpEnabled() {
return Config::GraphicsDebugDumpEnabled() &&
Config::GetPrintfDirection() != Config::OutputDirection::Silent;
Config::GetPrintfDirection() != Config::LogDirection::Silent;
}
GuestGpu::GuestGpu(RenderContext& renderer): m_renderer(renderer) {
@@ -372,8 +386,12 @@ void CommandProcessor::WriteReferenceClock(uint64_t dst_address, uint32_t num_by
}
const auto value = Sync::ReadReferenceClock();
std::memcpy(reinterpret_cast<void*>(dst_address), &value, num_bytes);
LOGF("\t copy_data reference clock: dst=0x%016" PRIx64 " value=0x%016" PRIx64 " size=%u\n",
dst_address, value, num_bytes);
static std::atomic<uint32_t> clock_log_count {0};
if (clock_log_count.fetch_add(1) < 64) {
LOGF("\t copy_data reference clock: dst=0x%016" PRIx64 " value=0x%016" PRIx64
" size=%u\n",
dst_address, value, num_bytes);
}
}
void CommandProcessor::DmaData(uint8_t engine, uint8_t dst_sel, uint8_t dst_cache_policy,
@@ -413,7 +431,7 @@ void CommandProcessor::DmaData(uint8_t engine, uint8_t dst_sel, uint8_t dst_cach
if (!decode_gds(dst_sel, dst_gds)) {
EXIT("unsupported dmaData destination selector 0x%02" PRIx8 "\n", dst_sel);
}
auto& buffer_cache = GetGpuResources().GetBufferCache();
auto& buffer_cache = m_renderer.GetBufferCache();
if (src_sel == 2) {
buffer_cache.FillBuffer(
dst_address_or_offset, num_bytes,
@@ -546,10 +564,6 @@ void GuestGpu::ThreadRun(void* data) {
bool GuestGpu::Process(Submission& submission) {
const bool first_slice = !submission.started;
if (first_slice && RenderDocCaptureRequested()) {
Common::LockGuard render_lock(m_renderer.GetMutex());
RenderDocStartCapture();
}
auto& cp = GetProcessor(submission.queue_id);
if (first_slice && submission.reset_processor) {
@@ -593,11 +607,11 @@ bool GuestGpu::Process(Submission& submission) {
}
if (progressed) {
if (complete) {
m_renderer.GetGpuResources().RunGarbageCollector();
m_renderer.RunGarbageCollector();
}
cp.BufferFlush();
} else if (complete) {
m_renderer.GetGpuResources().RunGarbageCollector();
m_renderer.RunGarbageCollector();
}
break;
}
@@ -619,16 +633,16 @@ bool GuestGpu::Process(Submission& submission) {
Pm4ProcessResult::Complete;
if (submission.command_execution.MadeProgress()) {
if (complete) {
m_renderer.GetGpuResources().RunGarbageCollector();
m_renderer.RunGarbageCollector();
}
cp.BufferFlush();
} else if (complete) {
m_renderer.GetGpuResources().RunGarbageCollector();
m_renderer.RunGarbageCollector();
}
break;
}
case SubmissionType::FlipPreparation:
m_renderer.GetGpuResources().RunGarbageCollector();
m_renderer.RunGarbageCollector();
cp.PrepareCpuFlip(submission.flip_request_id);
break;
}
@@ -662,20 +676,16 @@ Pm4ProcessResult CommandProcessor::Process(Pm4Execution& execution,
Pm4Execution* previous_execution;
} execution_scope(*this, execution);
ProcessPm4(execution, 0);
ProcessPm4(execution);
return execution.m_buffer_stack.empty() ? Pm4ProcessResult::Complete
: Pm4ProcessResult::Blocked;
}
void CommandProcessor::ProcessIndirectBuffer(std::span<const uint32_t> commands) {
void CommandProcessor::ProcessIndirectBuffer(std::span<const uint32_t> commands, bool chain) {
EXIT_IF(g_current_execution == nullptr);
if (commands.empty()) {
return;
}
auto& execution = *g_current_execution;
const auto stop_depth = execution.m_buffer_stack.size();
execution.m_buffer_stack.push_back({commands});
ProcessPm4(execution, stop_depth);
EXIT_IF(!g_current_execution->m_next_buffer.empty());
g_current_execution->m_next_buffer = commands;
g_current_execution->m_chain = chain;
}
void CommandProcessor::SuspendPm4() {
@@ -683,21 +693,13 @@ void CommandProcessor::SuspendPm4() {
g_current_execution->m_suspended = true;
}
void CommandProcessor::ProcessPm4(Pm4Execution& execution, size_t stop_depth) {
while (execution.m_buffer_stack.size() > stop_depth) {
void CommandProcessor::ProcessPm4(Pm4Execution& execution) {
while (!execution.m_buffer_stack.empty()) {
if (g_gpu_state != nullptr) {
g_gpu_state->ProcessCommands();
}
const auto buffer_index = execution.m_buffer_stack.size() - 1;
auto& cursor = execution.m_buffer_stack[buffer_index];
auto& cursor = execution.m_buffer_stack.back();
EXIT_IF(cursor.offset_dw > cursor.commands.size());
if (cursor.deferred_advance_dw != 0) {
EXIT_IF(cursor.deferred_advance_dw > cursor.commands.size() - cursor.offset_dw);
cursor.offset_dw += cursor.deferred_advance_dw;
cursor.deferred_advance_dw = 0;
execution.m_made_progress = true;
continue;
}
if (cursor.offset_dw == cursor.commands.size()) {
execution.m_buffer_stack.pop_back();
continue;
@@ -773,14 +775,19 @@ void CommandProcessor::ProcessPm4(Pm4Execution& execution, size_t stop_depth) {
handler(*this, packet_header & ~1u, packet + 1, remaining_dw, total_dw) + 1;
EXIT_IF(packet_dw > remaining_dw);
if (execution.m_suspended) {
if (execution.m_buffer_stack.size() > buffer_index + 1) {
execution.m_buffer_stack[buffer_index].deferred_advance_dw = packet_dw;
}
return;
}
EXIT_IF(execution.m_buffer_stack.size() != buffer_index + 1);
execution.m_buffer_stack[buffer_index].offset_dw += packet_dw;
cursor.offset_dw += packet_dw;
execution.m_made_progress = true;
if (!execution.m_next_buffer.empty()) {
// Chains and taken branches reuse the fetcher; only calls retain a return cursor.
if (execution.m_chain) {
cursor = {execution.m_next_buffer};
} else {
execution.m_buffer_stack.push_back({execution.m_next_buffer});
}
execution.m_next_buffer = {};
}
}
}
@@ -815,68 +822,72 @@ void CommandProcessor::SetNumInstances(uint32_t num_instances) {
void CommandProcessor::SetPredication(uint32_t condition, uint32_t op, uint32_t wait_op,
const volatile void* address, uint32_t count_in_dwords) {
if (wait_op != 0) {
BufferFlushAndWait();
}
(void)count_in_dwords;
uint64_t value = 0;
switch (op) {
case 0x00: {
case 0x00:
m_predicate_skip = false;
} break;
case 0x03: {
return;
case 0x01: {
EXIT_NOT_IMPLEMENTED(address == nullptr);
auto value = *reinterpret_cast<const volatile uint64_t*>(address);
switch (condition) {
case 0x00: m_predicate_skip = (value != 0); break;
case 0x01: m_predicate_skip = (value == 0); break;
default: EXIT("unknown predication condition: 0x%08" PRIx32 "\n", condition);
}
static std::atomic<uint32_t> log_count {0};
if (log_count.fetch_add(1) < 128) {
LOGF("\t bool predication: addr=0x%016" PRIx64 ", value=0x%016" PRIx64
", condition=%" PRIu32 ", skip=%u, wait_op=%" PRIu32 "\n",
reinterpret_cast<uint64_t>(address), value, condition,
m_predicate_skip ? 1u : 0u, wait_op);
// One begin/end pair per DB; bit 63 marks each counter ready.
constexpr uint64_t ready_bit = 1ull << 63u;
const auto* results = reinterpret_cast<const volatile uint64_t*>(address);
for (uint32_t db = 0; db < 16u; db++) {
const auto begin = results[db * 2u];
const auto end = results[db * 2u + 1u];
if ((begin & end & ready_bit) == 0) {
if (wait_op == 0) {
SuspendPm4();
} else {
m_predicate_skip = false;
}
return;
}
value += end - begin;
}
} break;
case 0x03:
if (wait_op != 0) {
BufferFlushAndWait();
}
EXIT_NOT_IMPLEMENTED(address == nullptr);
value = *reinterpret_cast<const volatile uint64_t*>(address);
break;
default: EXIT("unknown predication op: 0x%08" PRIx32 "\n", op);
}
switch (condition) {
case 0x00: m_predicate_skip = (value != 0); break;
case 0x01: m_predicate_skip = (value == 0); break;
default: EXIT("unknown predication condition: 0x%08" PRIx32 "\n", condition);
}
if (op == 0x03) {
static std::atomic<uint32_t> log_count {0};
if (log_count.fetch_add(1) < 128) {
LOGF("\t bool predication: addr=0x%016" PRIx64 ", value=0x%016" PRIx64
", condition=%" PRIu32 ", skip=%u, wait_op=%" PRIu32 "\n",
reinterpret_cast<uint64_t>(address), value, condition,
m_predicate_skip ? 1u : 0u, wait_op);
}
}
}
void CommandProcessor::DrawIndex(uint32_t index_count, const void* index_addr, uint32_t flags,
uint32_t type, uint32_t instance_count, const void* object_ids,
uint32_t render_target_slice_offset, int32_t vertex_offset_add,
uint32_t first_instance) {
void CommandProcessor::DrawIndex(DrawIndexArgs args) {
CheckBuffer();
if (instance_count == 0) {
instance_count = m_num_instances;
args.index_type_and_size = m_index_type_and_size;
if (args.instance_count == 0) {
args.instance_count = m_num_instances;
}
if (object_ids != nullptr) {
LOGF("\t draw indexed multi-instanced objectIds = 0x%016" PRIx64 "\n",
reinterpret_cast<uint64_t>(object_ids));
if (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);
}
if (render_target_slice_offset != 0) {
LOGF("\t draw render target slice offset = %" PRIu32 "\n", render_target_slice_offset);
}
if (vertex_offset_add != 0 || first_instance != 0) {
LOGF("\t draw indexed offsets: vertex_offset_add = %" PRId32 ", first_instance = %" PRIu32
"\n",
vertex_offset_add, first_instance);
}
m_renderer.GetRenderExecutor().DrawIndex(
m_submit_id, CurrentBuffer(), m_index_type_and_size, index_count, index_addr, flags, type,
instance_count, render_target_slice_offset, vertex_offset_add, first_instance);
m_renderer.GetRenderExecutor().DrawIndex(m_submit_id, CurrentBuffer(), args);
}
void CommandProcessor::DrawIndexOffset(uint32_t index_offset, uint32_t index_count,
uint32_t flags) {
CheckBuffer();
void CommandProcessor::DrawIndexOffset(uint32_t index_offset, uint32_t index_count) {
uint64_t index_size = 0;
switch (m_index_type_and_size) {
case 0: index_size = 2; break;
@@ -888,25 +899,10 @@ void CommandProcessor::DrawIndexOffset(uint32_t index_offset, uint32_t index_cou
auto* index_addr = reinterpret_cast<const void*>(
m_index_base_addr + static_cast<uint64_t>(index_offset) * index_size);
m_renderer.GetRenderExecutor().DrawIndex(m_submit_id, CurrentBuffer(), m_index_type_and_size,
index_count, index_addr, flags, 1, m_num_instances);
DrawIndex({.index_count = index_count, .index_addr = index_addr});
}
void CommandProcessor::DrawIndirect(uint32_t data_offset, uint32_t draw_initiator, bool indexed) {
struct DrawIndirectArgs {
uint32_t vertex_count_per_instance;
uint32_t instance_count;
uint32_t start_vertex_location;
uint32_t start_instance_location;
};
struct DrawIndexedIndirectArgs {
uint32_t index_count_per_instance;
uint32_t instance_count;
uint32_t start_index_location;
uint32_t base_vertex_location;
uint32_t start_instance_location;
};
EXIT_NOT_IMPLEMENTED((draw_initiator & ~0x20u) != 2u);
EXIT_NOT_IMPLEMENTED(m_draw_indirect_args_base_addr == 0);
@@ -928,8 +924,11 @@ void CommandProcessor::DrawIndirect(uint32_t data_offset, uint32_t draw_initiato
}
}
m_num_instances = args.instance_count;
SubmitNonIndexedDraw(args.vertex_count_per_instance, 0, 0, args.start_vertex_location,
args.start_instance_location);
DrawIndexAuto({.vertex_count = args.vertex_count_per_instance,
.instance_count = args.instance_count,
.first_vertex = args.start_vertex_location,
.first_instance = args.start_instance_location,
.offset_source = DrawOffsetSource::IndirectArgs});
return;
}
@@ -970,28 +969,18 @@ void CommandProcessor::DrawIndirect(uint32_t data_offset, uint32_t draw_initiato
}
m_num_instances = args.instance_count;
DrawIndex(index_count, index_addr, 0, 1, args.instance_count, nullptr, 0,
static_cast<int32_t>(args.base_vertex_location), args.start_instance_location);
DrawIndex({.index_count = index_count,
.index_addr = index_addr,
.instance_count = args.instance_count,
.base_vertex = static_cast<int32_t>(args.base_vertex_location),
.first_instance = args.start_instance_location,
.offset_source = DrawOffsetSource::IndirectArgs});
}
void CommandProcessor::DrawIndirectMulti(uint32_t data_offset, uint32_t max_count_or_count,
const volatile uint32_t* count_addr,
uint32_t stride_in_bytes, uint32_t draw_initiator,
bool indexed) {
struct DrawIndirectArgs {
uint32_t vertex_count_per_instance;
uint32_t instance_count;
uint32_t start_vertex_location;
uint32_t start_instance_location;
};
struct DrawIndexedIndirectArgs {
uint32_t index_count_per_instance;
uint32_t instance_count;
uint32_t start_index_location;
uint32_t base_vertex_location;
uint32_t start_instance_location;
};
EXIT_NOT_IMPLEMENTED((draw_initiator & ~0x20u) != 2u);
EXIT_NOT_IMPLEMENTED(m_draw_indirect_args_base_addr == 0);
@@ -1028,8 +1017,11 @@ void CommandProcessor::DrawIndirectMulti(uint32_t data_offset, uint32_t max_coun
}
}
m_num_instances = args->instance_count;
SubmitNonIndexedDraw(args->vertex_count_per_instance, 0, 0, args->start_vertex_location,
args->start_instance_location);
DrawIndexAuto({.vertex_count = args->vertex_count_per_instance,
.instance_count = args->instance_count,
.first_vertex = args->start_vertex_location,
.first_instance = args->start_instance_location,
.offset_source = DrawOffsetSource::IndirectArgs});
continue;
}
@@ -1071,8 +1063,12 @@ void CommandProcessor::DrawIndirectMulti(uint32_t data_offset, uint32_t max_coun
}
m_num_instances = args->instance_count;
DrawIndex(index_count, index_addr, 0, 1, args->instance_count, nullptr, 0,
static_cast<int32_t>(args->base_vertex_location), args->start_instance_location);
DrawIndex({.index_count = index_count,
.index_addr = index_addr,
.instance_count = args->instance_count,
.base_vertex = static_cast<int32_t>(args->base_vertex_location),
.first_instance = args->start_instance_location,
.offset_source = DrawOffsetSource::IndirectArgs});
}
}
@@ -1110,17 +1106,6 @@ void CommandProcessor::DispatchDirect(uint32_t thread_group_x, uint32_t thread_g
// local_x = std::max(cs.num_thread_x, 1u);
// local_y = std::max(cs.num_thread_y, 1u);
// local_z = std::max(cs.num_thread_z, 1u);
if (cs.wave_size == 64u) {
static std::atomic_bool logged_wave64_shader {false};
if (!logged_wave64_shader.exchange(true, std::memory_order_relaxed)) {
LOGF("warning: executing wave64 compute shader cs=0x%016" PRIx64 "\n",
cs.data_addr);
std::printf("warning: executing wave64 compute shader cs=0x%016" PRIx64 "\n",
cs.data_addr);
std::fflush(stdout);
}
}
m_renderer.GetRenderExecutor().DispatchDirect(m_submit_id, CurrentBuffer(), thread_group_x,
thread_group_y, thread_group_z, mode);
}
@@ -1163,19 +1148,13 @@ void CommandProcessor::DispatchIndirect(uint32_t data_offset, uint32_t mode) {
DispatchDirect(args->thread_group_x, args->thread_group_y, args->thread_group_z, mode);
}
void CommandProcessor::DrawIndexAuto(uint32_t index_count, uint32_t flags,
uint32_t render_target_slice_offset) {
SubmitNonIndexedDraw(index_count, flags, render_target_slice_offset, 0, 0);
}
void CommandProcessor::SubmitNonIndexedDraw(uint32_t vertex_count, uint32_t flags,
uint32_t render_target_slice_offset,
uint32_t first_vertex, uint32_t first_instance) {
void CommandProcessor::DrawIndexAuto(DrawAutoArgs args) {
CheckBuffer();
m_renderer.GetRenderExecutor().DrawAuto(m_submit_id, CurrentBuffer(), vertex_count, flags,
render_target_slice_offset, m_num_instances,
first_vertex, first_instance);
if (args.instance_count == 0) {
args.instance_count = m_num_instances;
}
m_renderer.GetRenderExecutor().DrawAuto(m_submit_id, CurrentBuffer(), args);
}
void CommandProcessor::WaitFlipDone(uint32_t video_out_handle, uint32_t display_buffer_index) {
@@ -1318,6 +1297,7 @@ void CommandProcessor::WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_wr
break;
case 0x14:
case 0x28:
case 0x2f:
if (event_index == 0x00) {
write64(false);
return;
@@ -1325,7 +1305,6 @@ void CommandProcessor::WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_wr
break;
case 0x2b:
case 0x2d:
case 0x2f:
case 0x30:
if (event_index == 0x00 && !with_interrupt) {
write64(false);
+69 -26
View File
@@ -202,6 +202,13 @@ struct RenderControl {
uint8_t copy_sample = 0;
};
struct DepthRenderOverride {
bool force_z_valid = false;
bool force_z_dirty = false;
bool force_stencil_valid = false;
bool force_stencil_dirty = false;
};
struct GdsOaCounter {
uint32_t counter = 0;
uint32_t address = 0;
@@ -362,16 +369,16 @@ struct AaConfig {
struct Viewport {
float zmin = 0.0f;
float zmax = 0.0f;
float xscale = 0.0f;
float xscale = 1.0f;
float xoffset = 0.0f;
float yscale = 0.0f;
float yscale = 1.0f;
float yoffset = 0.0f;
float zscale = 0.0f;
float zscale = 1.0f;
float zoffset = 0.0f;
int viewport_scissor_left = 0;
int viewport_scissor_top = 0;
int viewport_scissor_right = 0;
int viewport_scissor_bottom = 0;
int viewport_scissor_right = 16384;
int viewport_scissor_bottom = 16384;
bool viewport_scissor_window_offset_enable = false;
};
@@ -380,26 +387,26 @@ struct ScreenViewport {
uint32_t transform_control = 1087;
int screen_scissor_left = 0;
int screen_scissor_top = 0;
int screen_scissor_right = 0;
int screen_scissor_bottom = 0;
int screen_scissor_right = 16384;
int screen_scissor_bottom = 16384;
int window_scissor_left = 0;
int window_scissor_top = 0;
int window_scissor_right = 0;
int window_scissor_bottom = 0;
int window_scissor_right = 16384;
int window_scissor_bottom = 16384;
bool window_scissor_window_offset_enable = false;
int generic_scissor_left = 0;
int generic_scissor_top = 0;
int generic_scissor_right = 0;
int generic_scissor_bottom = 0;
int generic_scissor_right = 16384;
int generic_scissor_bottom = 16384;
bool generic_scissor_window_offset_enable = false;
int window_offset_x = 0;
int window_offset_y = 0;
uint32_t hw_offset_x = 0;
uint32_t hw_offset_y = 0;
float guard_band_horz_clip = 0.0f;
float guard_band_vert_clip = 0.0f;
float guard_band_horz_discard = 0.0f;
float guard_band_vert_discard = 0.0f;
float guard_band_horz_clip = 1.0f;
float guard_band_vert_clip = 1.0f;
float guard_band_horz_discard = 1.0f;
float guard_band_vert_discard = 1.0f;
uint16_t clip_rect_rule = 0xffffu;
int clip_rect_left[4] = {};
int clip_rect_top[4] = {};
@@ -491,7 +498,8 @@ struct LsStageRegisters {
};
struct HsStageRegisters {
uint64_t data_addr = 0;
uint64_t data_addr = 0;
uint64_t user_data_addr = 0;
HsShaderResource1 rsrc1;
HsShaderResource2 rsrc2;
};
@@ -520,7 +528,8 @@ struct GsShaderResource2 {
};
struct GsStageRegisters {
uint64_t data_addr = 0;
uint64_t data_addr = 0;
uint64_t user_data_addr = 0;
GsShaderResource1 rsrc1;
GsShaderResource2 rsrc2;
};
@@ -808,6 +817,12 @@ public:
void SetClipControl(const ClipControl& control) { m_clip_control = control; }
[[nodiscard]] const RenderControl& GetRenderControl() const { return m_render_control; }
void SetRenderControl(const RenderControl& control) { m_render_control = control; }
[[nodiscard]] const DepthRenderOverride& GetDepthRenderOverride() const {
return m_depth_render_override;
}
void SetDepthRenderOverride(const DepthRenderOverride& control) {
m_depth_render_override = control;
}
[[nodiscard]] const DepthControl& GetDepthControl() const { return m_depth_control; }
void SetDepthControl(const DepthControl& control) { m_depth_control = control; }
[[nodiscard]] const ModeControl& GetModeControl() const { return m_mode_control; }
@@ -903,15 +918,16 @@ private:
uint32_t m_shader_stages = 0;
DepthRenderTarget m_depth_render_target;
RenderControl m_render_control;
DepthControl m_depth_control;
StencilControl m_stencil_control;
StencilMask m_stencil_mask;
float m_depth_clear_value = 0.0f;
float m_depth_bounds_min = 0.0f;
float m_depth_bounds_max = 1.0f;
uint8_t m_stencil_clear_value = 0;
DepthRenderTarget m_depth_render_target;
RenderControl m_render_control;
DepthRenderOverride m_depth_render_override;
DepthControl m_depth_control;
StencilControl m_stencil_control;
StencilMask m_stencil_mask;
float m_depth_clear_value = 0.0f;
float m_depth_bounds_min = 0.0f;
float m_depth_bounds_max = 1.0f;
uint8_t m_stencil_clear_value = 0;
ModeControl m_mode_control;
PolyOffset m_poly_offset;
@@ -920,6 +936,12 @@ private:
ShaderRegisters m_sh_regs;
};
struct FsrView {
uint32_t control_points[2][4] {};
uint32_t alphas[2][2] {};
uint32_t window[2] {};
};
class UserConfig {
public:
UserConfig() = default;
@@ -949,8 +971,17 @@ public:
void SetGdsOaCntl(uint32_t value) { m_gds_oa.cntl = value; }
void SetGdsOaCounter(uint32_t value) { m_gds_oa.counters[m_gds_oa.GetIndex()].counter = value; }
void SetGdsOaAddress(uint32_t value) { m_gds_oa.counters[m_gds_oa.GetIndex()].address = value; }
void SetFsrControlPoint(uint32_t axis, uint32_t index, uint32_t value) {
m_fsr_view.control_points[axis][index] = value;
}
void SetFsrAlpha(uint32_t axis, uint32_t index, uint32_t value) {
m_fsr_view.alphas[axis][index] = value;
}
void SetFsrWindow(uint32_t index, uint32_t value) { m_fsr_view.window[index] = value; }
[[nodiscard]] const FsrView& GetFsrView() const { return m_fsr_view; }
private:
FsrView m_fsr_view;
Prospero::PrimitiveType m_prim_type = Prospero::PrimitiveType::kNone;
uint32_t m_index_offset = 0;
uint32_t m_object_id = 0;
@@ -973,9 +1004,15 @@ public:
void SetEsShaderBase(uint64_t addr) { m_vs.es_regs.data_addr = addr; }
void SetLsShaderBase(uint64_t addr) { m_vs.ls_regs.data_addr = addr; }
void SetHsShaderBase(uint64_t addr) { m_vs.hs_regs.data_addr = addr; }
void SetHsUserDataAddress(uint32_t word, uint32_t value) {
SetUserDataAddressWord(m_vs.hs_regs.user_data_addr, word, value);
}
void SetHsShaderResource1(const HsShaderResource1& rsrc1) { m_vs.hs_regs.rsrc1 = rsrc1; }
void SetHsShaderResource2(const HsShaderResource2& rsrc2) { m_vs.hs_regs.rsrc2 = rsrc2; }
void SetGsShaderBase(uint64_t addr) { m_vs.gs_regs.data_addr = addr; }
void SetGsUserDataAddress(uint32_t word, uint32_t value) {
SetUserDataAddressWord(m_vs.gs_regs.user_data_addr, word, value);
}
void SetGsShaderResource1(const GsShaderResource1& rsrc1) { m_vs.gs_regs.rsrc1 = rsrc1; }
void SetGsShaderResource2(const GsShaderResource2& rsrc2) { m_vs.gs_regs.rsrc2 = rsrc2; }
@@ -1016,6 +1053,12 @@ public:
[[nodiscard]] const ComputeShaderInfo& GetCs() const { return m_cs; }
private:
static void SetUserDataAddressWord(uint64_t& address, uint32_t word, uint32_t value) {
const auto shift = word * 32u;
address = (address & ~(uint64_t {0xffffffffu} << shift)) |
(static_cast<uint64_t>(value) << shift);
}
VertexShaderInfo m_vs;
PixelShaderInfo m_ps;
ComputeShaderInfo m_cs;
+9 -2
View File
@@ -834,8 +834,9 @@ constexpr uint32_t SPI_SHADER_USER_ACCUM_ESGS_0 = 0xB2;
constexpr uint32_t SPI_SHADER_USER_ACCUM_ESGS_3 = 0xB5;
constexpr uint32_t SPI_SHADER_PGM_LO_ES = 0xC8;
constexpr uint32_t SPI_SHADER_PGM_HI_ES = 0xC9;
// Retained only as an opaque AGC compiler-record tag; 0x100 is not a PS5 SH register.
// Private AGC registers used by shader binaries submitted through SET_SH_REG_INDIRECT.
constexpr uint32_t SPI_SHADER_PGM_CHKSUM_HS = 0x100;
constexpr uint32_t SPI_SHADER_PGM_RSRC4_HS = 0x101;
constexpr uint32_t SPI_SHADER_USER_DATA_ADDR_LO_HS = 0x102;
constexpr uint32_t SPI_SHADER_USER_DATA_ADDR_HI_HS = 0x103;
constexpr uint32_t SPI_GRAPHICS_SHADER_CONTROL_HS = 0x107;
@@ -976,7 +977,7 @@ constexpr uint32_t GE_USER_VGPR1 = 0x25C;
constexpr uint32_t GE_USER_VGPR2 = 0x25D;
constexpr uint32_t GE_USER_VGPR3 = 0x25E;
constexpr uint32_t GE_STEREO_CNTL = 0x25F;
constexpr uint32_t GE_PC_ALLOC = 0x260;
constexpr uint32_t UC_PARAMETER_OVERSUBSCRIPTION = 0x260;
constexpr uint32_t GE_USER_VGPR_EN = 0x262;
constexpr uint32_t GE_USER_VGPR_EN_EN_USER_VGPR1_SHIFT = 0;
@@ -986,6 +987,12 @@ constexpr uint32_t GE_USER_VGPR_EN_EN_USER_VGPR2_MASK = 0x1;
constexpr uint32_t GE_USER_VGPR_EN_EN_USER_VGPR3_SHIFT = 2;
constexpr uint32_t GE_USER_VGPR_EN_EN_USER_VGPR3_MASK = 0x1;
constexpr uint32_t FSR_WINDOW_LEFT = 0x2BC;
constexpr uint32_t FSR_CONTROL_POINTS_LEFT_X = 0x2C4;
constexpr uint32_t FSR_CONTROL_POINTS_LEFT_Y = 0x2CC;
constexpr uint32_t FSR_ALPHA_LEFT_X = 0x2E4;
constexpr uint32_t FSR_ALPHA_LEFT_Y = 0x2E8;
constexpr uint32_t TA_CS_BC_BASE_ADDR = 0x380;
constexpr uint32_t TA_CS_BC_BASE_ADDR_HI = 0x381;
constexpr uint32_t TEXTURE_GRADIENT_FACTORS = 0x383;
+42 -30
View File
@@ -1,24 +1,18 @@
#include "graphics/guest_gpu/tile.h"
#include "common/alignment.h"
#include "common/assert.h"
#include "common/logging/log.h"
#include "common/profiler.h"
#include "common/stringUtils.h"
#include "graphics/guest_gpu/gpu_defs.h"
#include "graphics/guest_gpu/gpu_format.h"
#include <algorithm>
#include <array>
#include <bit>
#include <fmt/format.h>
#include <vector>
namespace Libs::Graphics {
static uint32_t AlignUp(uint32_t value, uint32_t alignment) {
return (value + alignment - 1u) & ~(alignment - 1u);
}
static uint32_t ShiftCeil(uint32_t value, uint32_t shift) {
return static_cast<uint32_t>((static_cast<uint64_t>(value) + (1ull << shift) - 1ull) >> shift);
}
@@ -33,7 +27,7 @@ static uint32_t CalcLinearAlignedLevelPitch(uint32_t base_width, uint32_t base_h
const uint32_t level_width = ShiftCeil(base_width, level);
const uint32_t level_height = ShiftCeil(base_height, level);
const uint32_t padded_width =
AlignUp(std::max(level_width, 1u), CalcLinearBlockWidth(bytes_per_element));
Common::AlignUp(std::max(level_width, 1u), CalcLinearBlockWidth(bytes_per_element));
const uint64_t size =
static_cast<uint64_t>(padded_width) * std::max(level_height, 1u) * bytes_per_element;
EXIT_NOT_IMPLEMENTED(size > 0xffffffffull);
@@ -61,7 +55,7 @@ static uint32_t SetLinearMipChainLayout(uint32_t levels, const uint32_t* mip_pit
offset += mip_size[level];
}
return AlignUp(offset, 256u);
return Common::AlignUp(offset, 256u);
}
struct Log2BlockDimensions {
@@ -366,8 +360,8 @@ bool TileGetTiledTextureLayout(const TileSurfaceDescription& description, TileSu
((description.width >> level) + texture.texel_width - 1u) / texture.texel_width, 1u);
mip.height = std::max(
((description.height >> level) + texture.texel_height - 1u) / texture.texel_height, 1u);
mip.padded_width = AlignUp(std::max(ShiftCeil(width0, level), 1u), block.block_width);
mip.padded_height = AlignUp(std::max(ShiftCeil(height0, level), 1u), block.block_height);
mip.padded_width = Common::AlignUp(std::max(ShiftCeil(width0, level), 1u), block.block_width);
mip.padded_height = Common::AlignUp(std::max(ShiftCeil(height0, level), 1u), block.block_height);
mip.size = static_cast<uint64_t>(block.block_depth) * mip.padded_width * mip.padded_height *
block.bytes_per_element;
result.block_slice_size += mip.size;
@@ -1121,8 +1115,8 @@ static bool TileGetHtileSize(uint32_t width, uint32_t height, TileSizeAlign& hti
}
// Prospero HTile stores one DWORD per depth tile. Its 32 KiB allocation blocks cover
// 1024x512 pixels, independently of the attachment's fragment count.
const uint64_t size = static_cast<uint64_t>(AlignUp(width, 1024u) / 1024u) *
(AlignUp(height, 512u) / 512u) * 32768u;
const uint64_t size = static_cast<uint64_t>(Common::AlignUp(width, 1024u) / 1024u) *
(Common::AlignUp(height, 512u) / 512u) * 32768u;
if (size == 0 || size > UINT32_MAX) {
return false;
}
@@ -1154,13 +1148,13 @@ bool TileGetDepthSize(uint32_t width, uint32_t height, uint32_t pitch,
&stencil_block_height));
const uint32_t fragments = 1u << num_fragments_log2;
const uint64_t depth_bytes_total =
valid_blocks ? static_cast<uint64_t>(AlignUp(width, depth_block_width)) *
AlignUp(height, depth_block_height) * depth_bytes * fragments
valid_blocks ? static_cast<uint64_t>(Common::AlignUp(width, depth_block_width)) *
Common::AlignUp(height, depth_block_height) * depth_bytes * fragments
: 0;
const uint64_t stencil_bytes_total =
stencil_format == Prospero::StencilFormat::k8UInt && valid_blocks
? static_cast<uint64_t>(AlignUp(width, stencil_block_width)) *
AlignUp(height, stencil_block_height) * fragments
? static_cast<uint64_t>(Common::AlignUp(width, stencil_block_width)) *
Common::AlignUp(height, stencil_block_height) * fragments
: 0;
TileSizeAlign calculated_htile {};
const bool htile_valid = !htile || TileGetHtileSize(width, height, calculated_htile);
@@ -1187,8 +1181,7 @@ uint32_t TileGetRenderTargetPitch(uint32_t width, uint32_t bytes_per_element,
&block_width, &block_height)) {
return 0;
}
const uint64_t pitch = (static_cast<uint64_t>(width) + block_width - 1u) &
~static_cast<uint64_t>(block_width - 1u);
const uint64_t pitch = Common::AlignUp(static_cast<uint64_t>(width), block_width);
return pitch <= UINT32_MAX ? static_cast<uint32_t>(pitch) : 0;
}
@@ -1209,8 +1202,7 @@ bool TileGetRenderTargetSize(uint32_t width, uint32_t height, uint32_t pitch,
pitch != TileGetRenderTargetPitch(width, bytes_per_element, num_fragments_log2)) {
return false;
}
const uint64_t padded_height = (static_cast<uint64_t>(height) + block_height - 1u) &
~static_cast<uint64_t>(block_height - 1u);
const uint64_t padded_height = Common::AlignUp(static_cast<uint64_t>(height), block_height);
const uint64_t size = static_cast<uint64_t>(pitch) * padded_height * bytes_per_element *
(1u << num_fragments_log2);
if (size == 0 || size > UINT32_MAX) {
@@ -1221,6 +1213,31 @@ bool TileGetRenderTargetSize(uint32_t width, uint32_t height, uint32_t pitch,
return true;
}
bool TileGetDccSize(uint32_t width, uint32_t height, uint32_t slices,
uint32_t bytes_per_element, uint32_t levels, Prospero::TileMode tile,
TileSizeAlign& total_size, uint32_t num_fragments_log2) {
total_size = {};
if (width == 0 || height == 0 || slices == 0 || levels != 1 || num_fragments_log2 != 0 ||
!std::has_single_bit(bytes_per_element) || bytes_per_element > 16 ||
(tile != Prospero::TileMode::kRenderTarget && tile != Prospero::TileMode::kDepth)) {
return false;
}
// Gen5 color metadata uses 4 KiB blocks, with one byte per 256 bytes of color data.
// Metadata stays thin for volume surfaces, so every depth slice has its own block raster.
const uint32_t coverage_bits = 20u - std::countr_zero(bytes_per_element);
const uint32_t block_width = 1u << ((coverage_bits + 1u) / 2u);
const uint32_t block_height = 1u << (coverage_bits / 2u);
const uint64_t blocks_x = (static_cast<uint64_t>(width) + block_width - 1u) / block_width;
const uint64_t blocks_y = (static_cast<uint64_t>(height) + block_height - 1u) / block_height;
const uint64_t blocks = blocks_x * blocks_y;
if (blocks > UINT32_MAX / 4096u / slices) {
return false;
}
total_size.size = static_cast<uint32_t>(blocks * slices * 4096u);
total_size.align = 4096;
return true;
}
bool TileGetRenderTargetMipLayout(uint32_t width, uint32_t height, uint32_t pitch,
uint32_t bytes_per_element, uint32_t levels,
TileSizeAlign& total_size, TileSizeOffset* level_sizes,
@@ -1302,13 +1319,8 @@ void TileGetTextureSize(Prospero::BufferFormat format, uint32_t width, uint32_t
return;
}
if (total_size != nullptr && total_size->size == 0) {
std::vector<std::string> list;
list.push_back(fmt::format("format = {}", static_cast<uint32_t>(format)));
list.push_back(fmt::format("width = {}", width));
list.push_back(fmt::format("height = {}", height));
list.push_back(fmt::format("levels = {}", levels));
list.push_back(fmt::format("tile = {}", static_cast<uint32_t>(tile)));
EXIT("unknown format:\n%s\n", Common::Concat(list, '\n').c_str());
EXIT("unknown format:\nformat = %u\nwidth = %u\nheight = %u\nlevels = %u\ntile = %u\n",
static_cast<uint32_t>(format), width, height, levels, static_cast<uint32_t>(tile));
}
}
@@ -1347,7 +1359,7 @@ uint32_t TileGetTexturePitch(Prospero::BufferFormat format, uint32_t width,
TileTextureElementLayout element {};
if (TileGetTextureElementLayout(format, element) && element.texel_width == 1 &&
element.texel_height == 1) {
pitch = AlignUp(pitch, CalcLinearBlockWidth(element.bytes));
pitch = Common::AlignUp(pitch, CalcLinearBlockWidth(element.bytes));
}
break;
}
@@ -1358,7 +1370,7 @@ uint32_t TileGetTexturePitch(Prospero::BufferFormat format, uint32_t width,
case Prospero::TileMode::kRenderTarget: {
TileTextureBlockLayout layout {};
if (TileGetTextureBlockLayout(format, tile, false, layout)) {
pitch = AlignUp(pitch, layout.block.block_width * layout.texel_width);
pitch = Common::AlignUp(pitch, layout.block.block_width * layout.texel_width);
}
break;
}
+4
View File
@@ -86,6 +86,7 @@ struct TileMipLayout {
uint32_t padded_height = 0;
uint32_t tail_x = 0;
uint32_t tail_y = 0;
bool operator==(const TileMipLayout&) const = default;
};
struct TileSurfaceLayout {
@@ -122,6 +123,9 @@ uint32_t TileGetDepthPitch(uint32_t width, uint32_t bytes_per_element,
bool TileGetRenderTargetSize(uint32_t width, uint32_t height, uint32_t pitch,
uint32_t bytes_per_element, TileSizeAlign& total_size,
uint32_t num_fragments_log2 = 0);
bool TileGetDccSize(uint32_t width, uint32_t height, uint32_t slices,
uint32_t bytes_per_element, uint32_t levels, Prospero::TileMode tile,
TileSizeAlign& total_size, uint32_t num_fragments_log2 = 0);
bool TileGetRenderTargetMipLayout(uint32_t width, uint32_t height, uint32_t pitch,
uint32_t bytes_per_element, uint32_t levels,
TileSizeAlign& total_size, TileSizeOffset* level_sizes,
+11 -31
View File
@@ -14,9 +14,7 @@
namespace Libs::Graphics {
struct VulkanBuffer;
struct VulkanImage;
struct VulkanMemory;
inline constexpr uint32_t VULKAN_TARGET_API_VERSION = VK_API_VERSION_1_3;
@@ -29,10 +27,13 @@ struct GraphicContext {
vk::Device device = nullptr;
VmaAllocator allocator = nullptr;
bool memory_budget_ext_enabled = false;
bool rt_extensions_enabled = false;
bool compute_subgroup_size_control_enabled = false;
bool compute_wave64_supported = false;
bool sample_rate_shading_enabled = false;
bool attachment_feedback_loop_enabled = false;
bool provoking_vertex_last_enabled = false;
bool supports_block_texel_view = false;
bool mesh_shader_enabled = false;
vk::PhysicalDeviceMeshShaderPropertiesEXT mesh_shader_properties = {};
uint32_t subgroup_size = 0;
uint32_t min_subgroup_size = 0;
uint32_t max_subgroup_size = 0;
@@ -80,6 +81,10 @@ struct GraphicContext {
return it->second.first;
}
[[nodiscard]] bool SupportsComputeWave64() const noexcept {
return subgroup_size == 64u || compute_subgroup_size_control_enabled;
}
[[nodiscard]] vk::DeviceSize StorageMinAlignment() const {
const auto alignment = physical_device_properties.limits.minStorageBufferOffsetAlignment;
return alignment != 0 ? alignment : 1;
@@ -91,15 +96,8 @@ struct GraphicContext {
[[nodiscard]] bool CanReportMemoryUsage() const noexcept { return memory_budget_ext_enabled; }
[[nodiscard]] uint64_t GetDeviceMemoryUsage() const;
[[nodiscard]] uint64_t GetTotalMemoryBudget() const;
void CreateBuffer(uint64_t size, VulkanBuffer& buffer);
[[nodiscard]] bool CreateImage(const vk::ImageCreateInfo& info, VulkanImage& image);
void DeleteImage(VulkanImage& image);
void MapMemory(VulkanMemory& memory, void*& data);
void UnmapMemory(VulkanMemory& memory);
void AppendHardwareRayTracingDeviceExtensions(
const std::vector<vk::ExtensionProperties>& available_extensions,
std::vector<const char*>& device_extensions);
void LoadHardwareRayTracingFunctions() const;
uint32_t screen_width = 0;
uint32_t screen_height = 0;
@@ -114,18 +112,6 @@ private:
m_image_format_properties;
};
struct VulkanMemory {
vk::MemoryRequirements requirements = {};
vk::MemoryPropertyFlags property = {};
vk::MemoryPropertyFlags preferred_property = {};
vk::DeviceMemory memory = nullptr;
VmaAllocation allocation = nullptr;
VmaAllocationInfo allocation_info = {};
vk::DeviceSize offset = 0;
uint32_t type = 0;
uint64_t unique_id = 0;
};
struct VulkanImageState {
vk::PipelineStageFlags2 pl_stage = vk::PipelineStageFlagBits2::eAllCommands;
vk::AccessFlags2 access_mask = vk::AccessFlagBits2::eNone;
@@ -139,7 +125,6 @@ struct VulkanImage {
vk::Format format = vk::Format::eUndefined;
vk::ImageType image_type = vk::ImageType::e2D;
vk::Extent3D extent = {1, 1, 1};
uint32_t guest_pitch = 0;
uint32_t layers = 1;
uint32_t mip_levels = 1;
uint32_t samples = 1;
@@ -148,15 +133,10 @@ struct VulkanImage {
vk::Image image = nullptr;
VulkanImageState state;
std::vector<VulkanImageState> subresource_states;
Graphics::VulkanMemory memory;
VmaAllocation allocation = nullptr;
};
struct VulkanBuffer {
vk::Buffer buffer = nullptr;
VulkanMemory memory;
vk::BufferUsageFlags usage = {};
uint64_t buffer_size = 0;
};
} // namespace Libs::Graphics
+7 -19
View File
@@ -1,5 +1,6 @@
#include "graphics/host_gpu/memoryTracker.h"
#include "common/alignment.h"
#include "common/assert.h"
namespace Libs::Graphics {
@@ -8,9 +9,6 @@ static_assert(std::atomic<void*>::is_always_lock_free);
MemoryTracker::MemoryTracker(PageManager& page_manager): m_page_manager(page_manager) {
m_regions = std::make_unique<std::atomic<RegionManager*>[]>(REGION_COUNT);
for (size_t i = 0; i < REGION_COUNT; i++) {
m_regions[i].store(nullptr, std::memory_order_relaxed);
}
}
MemoryTracker::~MemoryTracker() = default;
@@ -23,10 +21,7 @@ void MemoryTracker::ValidateGpuDirtyPages(const RangeSet& dirty, uint64_t vaddr,
EXIT("MemoryTracker: invalid dirty-page validation range\n");
}
for (auto page = vaddr; page < vaddr + size; page += TRACKER_PAGE_SIZE) {
bool found = false;
dirty.ForEachIntersection(page, TRACKER_PAGE_SIZE,
[&found](RangeSet::Range) { found = true; });
if (!found) {
if (!dirty.Intersects(page, TRACKER_PAGE_SIZE)) {
EXIT("MemoryTracker: GPU-dirty tracker page has no dirty bytes, operation=%s "
"addr=0x%016" PRIx64 "\n",
operation, page);
@@ -37,12 +32,10 @@ void MemoryTracker::ValidateGpuDirtyPages(const RangeSet& dirty, uint64_t vaddr,
void MemoryTracker::ValidateGpuDirtyOwnership(const RangeSet& dirty, uint64_t vaddr, uint64_t size,
const char* operation) {
ValidateRange(vaddr, size);
const auto begin = vaddr & ~(TRACKER_PAGE_SIZE - 1);
const auto end = (vaddr + size + TRACKER_PAGE_SIZE - 1) & ~(TRACKER_PAGE_SIZE - 1);
const auto begin = Common::AlignDown(vaddr, TRACKER_PAGE_SIZE);
const auto end = Common::AlignUp(vaddr + size, TRACKER_PAGE_SIZE);
for (auto page = begin; page < end; page += TRACKER_PAGE_SIZE) {
bool has_dirty_bytes = false;
dirty.ForEachIntersection(page, TRACKER_PAGE_SIZE,
[&has_dirty_bytes](RangeSet::Range) { has_dirty_bytes = true; });
const bool has_dirty_bytes = dirty.Intersects(page, TRACKER_PAGE_SIZE);
if (IsRegionGpuModified(page, TRACKER_PAGE_SIZE) != has_dirty_bytes) {
EXIT("MemoryTracker: tracker and byte ownership disagree, operation=%s "
"addr=0x%016" PRIx64 "\n",
@@ -113,7 +106,8 @@ void MemoryTracker::UnmarkRegionAsGpuModified(uint64_t vaddr, uint64_t size) {
});
}
void MemoryTracker::UntrackMemoryImpl(uint64_t vaddr, uint64_t size) {
void MemoryTracker::UntrackMemory(uint64_t vaddr, uint64_t size) {
CheckNotInUploadCallback();
std::vector<RegionManager*> managers;
managers.reserve((vaddr % TRACKER_REGION_SIZE + size + TRACKER_REGION_SIZE - 1) /
TRACKER_REGION_SIZE);
@@ -134,12 +128,6 @@ void MemoryTracker::UntrackMemoryImpl(uint64_t vaddr, uint64_t size) {
Iterate<false>(vaddr, size, [](RegionManager* manager, uint64_t offset, uint64_t bytes) {
manager->ChangeState<DirtySource::Cpu, true>(manager->GetCpuAddr() + offset, bytes);
});
locks.clear();
}
void MemoryTracker::UntrackMemory(uint64_t vaddr, uint64_t size) {
CheckNotInUploadCallback();
UntrackMemoryImpl(vaddr, size);
}
} // namespace Libs::Graphics
+10 -37
View File
@@ -34,7 +34,6 @@ public:
void InvalidateRegion(uint64_t vaddr, uint64_t size, Flush&& on_flush) noexcept {
static_assert(std::is_invocable_v<Flush&>);
CheckNotInUploadCallback();
ValidateRange(vaddr, size);
Iterate<false>(vaddr, size, [&](RegionManager* manager, uint64_t offset, uint64_t bytes) {
const bool should_flush = [&] {
@@ -63,43 +62,18 @@ public:
void ValidateGpuDirtyOwnership(const RangeSet&, uint64_t, uint64_t, const char*) {}
#endif
template <bool clear, typename Preflight, typename Func>
void ForEachDownloadRange(uint64_t vaddr, uint64_t size, Preflight&& preflight, Func&& func) {
static_assert(std::is_nothrow_invocable_v<Preflight&, uint64_t, uint64_t>);
static_assert(std::is_nothrow_invocable_v<Func&, uint64_t, uint64_t>);
CheckNotInUploadCallback();
std::vector<RegionManager*> managers;
Iterate<false>(vaddr, size, [&](RegionManager* manager, uint64_t, uint64_t) {
managers.push_back(manager);
});
std::vector<std::unique_lock<TrackingSpinLock>> locks;
locks.reserve(managers.size());
for (auto* manager: managers) {
locks.emplace_back(manager->lock);
}
Iterate<false>(vaddr, size, [&](RegionManager* manager, uint64_t offset, uint64_t bytes) {
const auto address = manager->GetCpuAddr() + offset;
manager->template ForEachModifiedRange<DirtySource::Gpu, false>(address, bytes,
preflight);
});
Iterate<false>(vaddr, size, [&](RegionManager* manager, uint64_t offset, uint64_t bytes) {
manager->template ForEachModifiedRange<DirtySource::Gpu, false>(
manager->GetCpuAddr() + offset, bytes, func);
});
if constexpr (clear) {
Iterate<false>(vaddr, size,
[&](RegionManager* manager, uint64_t offset, uint64_t bytes) {
const auto address = manager->GetCpuAddr() + offset;
manager->template ForEachModifiedRange<DirtySource::Gpu, true>(
address, bytes, [](uint64_t, uint64_t) noexcept {});
});
}
}
template <bool clear, typename Func>
void ForEachDownloadRange(uint64_t vaddr, uint64_t size, Func&& func) {
ForEachDownloadRange<clear>(
vaddr, size, [](uint64_t, uint64_t) noexcept {}, std::forward<Func>(func));
static_assert(std::is_nothrow_invocable_v<Func&, uint64_t, uint64_t>);
CheckNotInUploadCallback();
Iterate<false>(vaddr, size, [&](RegionManager* manager, uint64_t offset, uint64_t bytes) {
std::scoped_lock lock(manager->lock);
const auto address = manager->GetCpuAddr() + offset;
manager->template ForEachModifiedRange<DirtySource::Gpu, false>(address, bytes, func);
if constexpr (clear) {
manager->template ChangeState<DirtySource::Gpu, false>(address, bytes);
}
});
}
template <typename RangeFunc, typename UploadFunc>
@@ -171,7 +145,6 @@ private:
}
static void ValidateRange(uint64_t vaddr, uint64_t size);
void UntrackMemoryImpl(uint64_t vaddr, uint64_t size);
RegionManager* GetOrCreateRegion(uint64_t index);
std::unique_ptr<std::atomic<RegionManager*>[]> m_regions;
+15 -68
View File
@@ -1,6 +1,7 @@
#include "graphics/host_gpu/pageManager.h"
#include "common/platform/sysDbg.h"
#include "common/alignment.h"
#include "common/virtualMemory.h"
#include "graphics/host_gpu/regionDefinitions.h"
#include "kernel/memory.h"
@@ -21,10 +22,7 @@
#include <windows.h>
#undef min
#undef max
#elif defined(__APPLE__)
#include <unistd.h>
#else
#include <execinfo.h>
#include <unistd.h>
#endif
@@ -36,38 +34,12 @@ constexpr uint64_t REGION_SIZE = TRACKER_REGION_SIZE;
constexpr uint64_t ADDRESS_SIZE = TRACKER_ADDRESS_SIZE;
constexpr uint64_t REGION_COUNT = ADDRESS_SIZE / REGION_SIZE;
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
// The tracker reuses Win32 memory-protection tags as internal page-state values.
// Mirror their canonical numeric values so the shared state-machine logic is identical.
constexpr uint32_t PAGE_NOACCESS = 0x01;
constexpr uint32_t PAGE_READONLY = 0x02;
constexpr uint32_t PAGE_READWRITE = 0x04;
#endif
constexpr uint64_t REGION_PAGES = REGION_SIZE / PAGE_SIZE;
constexpr uint32_t NO_ACCESS_PROTECTION = PAGE_NOACCESS;
constexpr uint32_t READ_ONLY_PROTECTION = PAGE_READONLY;
constexpr uint32_t READ_WRITE_PROTECTION = PAGE_READWRITE;
[[noreturn]] void FailFast(const char* reason = nullptr) noexcept {
std::fputs("PageManager fail-fast: ", stderr);
std::fputs(reason != nullptr ? reason : "invalid page state", stderr);
std::fputc('\n', stderr);
#if !defined(__APPLE__)
void* frames[16] {};
int frame_count = static_cast<int>(std::size(frames));
SysStackWalk(frames, &frame_count);
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
const auto image_base = reinterpret_cast<uintptr_t>(GetModuleHandleW(nullptr));
for (int i = 0; i < frame_count; i++) {
const auto address = reinterpret_cast<uintptr_t>(frames[i]);
std::fprintf(stderr, " frame[%d]=0x%016" PRIxPTR " image_rva=0x%016" PRIxPTR "\n", i,
address, address >= image_base ? address - image_base : 0);
}
#else
::backtrace_symbols_fd(frames, frame_count, STDERR_FILENO);
#endif
#endif
std::fflush(stderr);
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
TerminateProcess(GetCurrentProcess(), static_cast<UINT>(EXCEPTION_NONCONTINUABLE_EXCEPTION));
@@ -86,15 +58,6 @@ constexpr uint32_t READ_WRITE_PROTECTION = PAGE_READWRITE;
std::_Exit(322);
}
Common::VirtualMemory::Mode ToMemoryMode(uint32_t protection) {
switch (protection) {
case NO_ACCESS_PROTECTION: return Common::VirtualMemory::Mode::NoAccess;
case READ_ONLY_PROTECTION: return Common::VirtualMemory::Mode::Read;
case READ_WRITE_PROTECTION: return Common::VirtualMemory::Mode::ReadWrite;
default: Fatal("unmappable protection 0x%08" PRIx32, protection);
}
}
class SpinGuard final {
public:
explicit SpinGuard(std::atomic_flag& lock): m_lock(lock) {
@@ -115,15 +78,6 @@ void ValidateRange(uint64_t vaddr, uint64_t size) {
}
}
uint64_t PageStart(uint64_t vaddr) {
return vaddr & ~(PAGE_SIZE - 1);
}
uint64_t PageEnd(uint64_t vaddr, uint64_t size) {
ValidateRange(vaddr, size);
return PageStart(vaddr + size - 1) + PAGE_SIZE;
}
} // namespace
struct PageManager::Impl {
@@ -131,14 +85,14 @@ struct PageManager::Impl {
uint8_t write_watchers : 7 = 0;
uint8_t access_watchers : 1 = 0;
[[nodiscard]] uint32_t Perms() const noexcept {
[[nodiscard]] Common::VirtualMemory::Mode Perms() const noexcept {
if (access_watchers != 0) {
return NO_ACCESS_PROTECTION;
return Common::VirtualMemory::Mode::NoAccess;
}
if (write_watchers != 0) {
return READ_ONLY_PROTECTION;
return Common::VirtualMemory::Mode::Read;
}
return READ_WRITE_PROTECTION;
return Common::VirtualMemory::Mode::ReadWrite;
}
template <int delta, bool is_read>
@@ -202,9 +156,6 @@ struct PageManager::Impl {
}
#endif
regions = std::make_unique<std::atomic<Region*>[]>(REGION_COUNT);
for (uint64_t i = 0; i < REGION_COUNT; i++) {
regions[i].store(nullptr, std::memory_order_relaxed);
}
}
~Impl() {
@@ -239,11 +190,10 @@ struct PageManager::Impl {
return ptr;
}
void Protect(uint64_t vaddr, uint64_t size, uint32_t protection) noexcept {
if (!Libs::LibKernel::Memory::ProtectGuestHostMemory(vaddr, size,
ToMemoryMode(protection))) {
Fatal("address-space protection failed at 0x%016" PRIx64 ", new=0x%08" PRIx32, vaddr,
protection);
void Protect(uint64_t vaddr, uint64_t size, Common::VirtualMemory::Mode mode) noexcept {
if (!Libs::LibKernel::Memory::ProtectGuestHostMemory(vaddr, size, mode)) {
Fatal("address-space protection failed at 0x%016" PRIx64 ", mode=0x%08" PRIx32, vaddr,
static_cast<uint32_t>(mode));
}
}
@@ -300,11 +250,12 @@ struct PageManager::Impl {
template <bool track, bool is_read>
void UpdatePageWatchers(uint64_t vaddr, uint64_t size) {
const auto begin = PageStart(vaddr);
const auto end = PageEnd(vaddr, size);
ValidateRange(vaddr, size);
const auto begin = Common::AlignDown(vaddr, PAGE_SIZE);
const auto end = Common::AlignUp(vaddr + size, PAGE_SIZE);
for (auto chunk_begin = begin; chunk_begin < end;) {
const auto chunk_end = std::min(end, (chunk_begin / REGION_SIZE + 1) * REGION_SIZE);
const auto region_base = chunk_begin / REGION_SIZE * REGION_SIZE;
const auto chunk_end = std::min(end, Common::AlignUp(chunk_begin + 1, REGION_SIZE));
const auto region_base = Common::AlignDown(chunk_begin, REGION_SIZE);
auto* region = track ? GetOrCreateRegion(chunk_begin) : FindRegion(chunk_begin);
if (region == nullptr) {
Fatal("untracking unknown page 0x%016" PRIx64, chunk_begin);
@@ -371,8 +322,4 @@ template void PageManager::UpdatePageWatchersForRegion<true, false>(uint64_t, Re
template void PageManager::UpdatePageWatchersForRegion<false, true>(uint64_t, RegionBits&);
template void PageManager::UpdatePageWatchersForRegion<false, false>(uint64_t, RegionBits&);
void PageManager::OnGpuMap(uint64_t, uint64_t) {}
void PageManager::OnGpuUnmap(uint64_t, uint64_t) {}
} // namespace Libs::Graphics
-2
View File
@@ -24,8 +24,6 @@ public:
void UpdatePageWatchers(uint64_t vaddr, uint64_t size);
template <bool track, bool is_read = false>
void UpdatePageWatchersForRegion(uint64_t base_addr, RegionBits& mask);
void OnGpuMap(uint64_t vaddr, uint64_t size);
void OnGpuUnmap(uint64_t vaddr, uint64_t size);
private:
struct Impl;
+2 -14
View File
@@ -6,17 +6,11 @@
#include <algorithm>
#include <cstdint>
#include <map>
#include <vector>
namespace Libs::Graphics {
class RangeSet final {
public:
struct Range {
uint64_t address = 0;
uint64_t size = 0;
};
void Add(uint64_t address, uint64_t size) {
const auto end = End(address, size);
auto it = m_ranges.lower_bound(address);
@@ -62,12 +56,6 @@ public:
}
}
[[nodiscard]] std::vector<Range> Intersections(uint64_t address, uint64_t size) const {
std::vector<Range> result;
ForEachIntersection(address, size, [&result](Range range) { result.push_back(range); });
return result;
}
[[nodiscard]] bool Intersects(uint64_t address, uint64_t size) const {
const auto end = End(address, size);
auto it = m_ranges.lower_bound(address);
@@ -88,7 +76,7 @@ public:
}
template <typename Func>
void ForEachIntersection(uint64_t address, uint64_t size, Func&& func) const {
void ForEachInRange(uint64_t address, uint64_t size, Func&& func) const {
const auto end = End(address, size);
auto it = m_ranges.upper_bound(address);
if (it != m_ranges.begin()) {
@@ -98,7 +86,7 @@ public:
const auto begin = std::max(address, it->first);
const auto last = std::min(end, it->second);
if (begin < last) {
func(Range {begin, last - begin});
func(begin, last);
}
}
}
+20 -39
View File
@@ -16,6 +16,7 @@
#include <windows.h>
#undef min
#undef max
#undef MemoryBarrier
#elif defined(__APPLE__)
#include <pthread.h>
#elif defined(__linux__)
@@ -111,56 +112,43 @@ public:
bits.UnsetRange(start, end);
}
if constexpr (source == DirtySource::Cpu) {
UpdateCpuProtection<!enable>();
UpdateProtection<!enable, false>();
} else {
UpdateGpuProtection<enable>();
UpdateProtection<enable, true>();
}
}
template <DirtySource source, bool clear, typename Func>
void ForEachModifiedRange(uint64_t vaddr, uint64_t size, Func&& func) {
const auto [start, end] = GetPageRange(vaddr, size);
RegionBits mask(GetBits<source>(), start, end);
auto& bits = GetBits<source>();
RegionBits mask(bits, start, end);
if constexpr (clear) {
GetBits<source>().UnsetRange(start, end);
bits.UnsetRange(start, end);
if constexpr (source == DirtySource::Cpu) {
UpdateProtection<true, false>();
} else {
UpdateProtection<false, true>();
}
}
if constexpr (source == DirtySource::Cpu && clear) {
UpdateCpuProtection<true>();
ForEachRange(mask, std::forward<Func>(func));
return;
for (const auto [first, last]: mask) {
func(m_cpu_addr + first * TRACKER_PAGE_SIZE, (last - first) * TRACKER_PAGE_SIZE);
}
if constexpr (source == DirtySource::Gpu && clear) {
UpdateGpuProtection<false>();
}
ForEachRange(mask, std::forward<Func>(func));
}
TrackingSpinLock lock;
private:
template <bool track>
void UpdateCpuProtection() {
auto mask = m_cpu_dirty ^ m_writable;
m_writable = m_cpu_dirty;
template <bool track, bool is_read>
void UpdateProtection() {
const auto protection = is_read ? ~m_gpu_dirty : m_cpu_dirty;
auto& previous = is_read ? m_readable : m_writable;
auto mask = protection ^ previous;
if (mask.None()) {
return;
}
m_page_manager.UpdatePageWatchersForRegion<track>(m_cpu_addr, mask);
}
template <bool track>
void UpdateGpuProtection() {
auto readable = ~m_gpu_dirty;
auto mask = readable ^ m_readable;
m_readable = readable;
if (mask.None()) {
return;
}
if constexpr (track) {
m_page_manager.UpdatePageWatchersForRegion<true, true>(m_cpu_addr, mask);
} else {
m_page_manager.UpdatePageWatchersForRegion<false, true>(m_cpu_addr, mask);
}
previous = protection;
m_page_manager.UpdatePageWatchersForRegion<track, is_read>(m_cpu_addr, mask);
}
template <DirtySource source>
@@ -191,13 +179,6 @@ private:
static_cast<size_t>((offset + size + TRACKER_PAGE_SIZE - 1) / TRACKER_PAGE_SIZE)};
}
template <typename Func>
void ForEachRange(const RegionBits& bits, Func&& func) const {
for (const auto [start, end]: bits) {
func(m_cpu_addr + start * TRACKER_PAGE_SIZE, (end - start) * TRACKER_PAGE_SIZE);
}
}
PageManager& m_page_manager;
uint64_t m_cpu_addr = 0;
RegionBits m_cpu_dirty;
+197 -313
View File
@@ -1,5 +1,6 @@
#include "graphics/host_gpu/renderer/cache/bufferCache.h"
#include "common/alignment.h"
#include "common/assert.h"
#include "common/logging/log.h"
#include "common/profiler.h"
@@ -13,7 +14,6 @@
#include "kernel/memory.h"
#include <algorithm>
#include <array>
#include <cinttypes>
#include <cstring>
#include <memory>
@@ -43,13 +43,6 @@ void BufferCache::WriteDataBuffer(Buffer& buffer, uint64_t address, const void*
}
}
struct BufferCache::DownloadCopy {
Buffer* buffer = nullptr;
uint64_t source_offset = 0;
uint64_t address = 0;
uint64_t size = 0;
};
void BufferCache::Register(BufferId id) {
ChangeRegister<true>(id);
}
@@ -104,8 +97,7 @@ void BufferCache::TouchBuffer(const Buffer& buffer) {
}
void BufferCache::DeleteBuffer(BufferId id) {
auto* buffer = m_slot_buffers.try_get(id);
if (buffer == nullptr || buffer->is_deleted) {
if (IsBufferInvalid(id)) {
return;
}
Unregister(id);
@@ -116,94 +108,84 @@ void BufferCache::DeleteBuffer(BufferId id) {
}
}
std::pair<uint64_t, uint64_t> BufferCache::DownloadEnvelope(const DownloadCopy& copy) {
if (copy.buffer == nullptr || copy.size == 0 || copy.source_offset > copy.buffer->Size() ||
copy.size > copy.buffer->Size() - copy.source_offset) {
EXIT("BufferCache: invalid download copy\n");
bool BufferCache::DownloadBufferMemory(Buffer& buffer, uint64_t vaddr, uint64_t size) {
std::vector<vk::BufferCopy> copies;
uint64_t total_size = 0;
const auto buffer_address = buffer.CpuAddress();
m_memory_tracker.ForEachDownloadRange<false>(
vaddr, size, [&](uint64_t address, uint64_t bytes) noexcept {
m_memory_tracker.ValidateGpuDirtyPages(m_gpu_modified_ranges, address, bytes,
"buffer download");
m_gpu_modified_ranges.ForEachInRange(address, bytes, [&](uint64_t start, uint64_t end) {
copies.emplace_back(start - buffer_address, total_size, end - start);
// Keep packed ranges on separate cache lines, as in shadPS4.
total_size += Common::AlignUp(end - start, 64);
});
m_gpu_modified_ranges.Subtract(address, bytes);
});
if (copies.empty()) {
return false;
}
const auto begin = copy.source_offset & ~uint64_t {3};
if (copy.source_offset > UINT64_MAX - copy.size ||
copy.source_offset + copy.size > UINT64_MAX - 3) {
EXIT("BufferCache: download copy alignment overflow\n");
}
const auto end = (copy.source_offset + copy.size + 3) & ~uint64_t {3};
if (end > copy.buffer->Size()) {
EXIT("BufferCache: aligned download copy exceeds its owner\n");
}
return {begin, end - begin};
}
void BufferCache::DownloadBufferMemory(std::span<const DownloadCopy> copies) {
std::vector<DownloadCopy> batch;
batch.reserve(copies.size());
uint64_t packed_size = 0;
auto& download = m_download_buffer;
const auto flush = [&] {
const auto [mapped, base_offset] = download.Map(packed_size, DOWNLOAD_ALIGNMENT);
EXIT_IF(mapped == nullptr);
uint64_t cursor = 0;
for (const auto& copy: batch) {
const auto [source_begin, envelope_size] = DownloadEnvelope(copy);
download.CopyFrom(m_scheduler.Current(), *copy.buffer, source_begin, base_offset + cursor,
envelope_size, vk::AccessFlagBits::eMemoryWrite, vk::AccessFlags {},
vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite,
vk::AccessFlagBits::eHostRead);
cursor += AlignDownload(envelope_size);
}
download.Commit();
const auto completion_tick = m_scheduler.CurrentTick();
m_scheduler.Finish();
m_scheduler.WaitPriorityOperations(completion_tick);
cursor = 0;
for (const auto& copy: batch) {
const auto [source_begin, envelope_size] = DownloadEnvelope(copy);
const auto offset = cursor + copy.source_offset - source_begin;
download.Invalidate(base_offset + offset, copy.size);
Libs::LibKernel::Memory::WriteBacking(copy.address, mapped + offset, copy.size);
cursor += AlignDownload(envelope_size);
}
batch.clear();
packed_size = 0;
};
for (auto copy: copies) {
while (copy.size != 0) {
const auto available = download.Size() - packed_size;
const auto prefix = copy.source_offset & 3u;
const auto bytes = std::min(copy.size, available - prefix);
DownloadCopy part {copy.buffer, copy.source_offset, copy.address, bytes};
const auto [source_begin, envelope_size] = DownloadEnvelope(part);
(void)source_begin;
packed_size += AlignDownload(envelope_size);
batch.push_back(part);
copy.source_offset += bytes;
copy.address += bytes;
copy.size -= bytes;
if (packed_size == download.Size()) {
flush();
}
}
const auto [mapped, offset] = m_download_buffer.Map(total_size, 64);
if (mapped == nullptr) {
EXIT("BufferCache: download exceeds 32 MiB staging buffer capacity\n");
}
if (!batch.empty()) {
flush();
}
for (const auto& copy: copies) {
m_gpu_modified_ranges.Subtract(copy.address, copy.size);
m_download_buffer.Commit();
for (auto& copy: copies) {
copy.dstOffset += offset;
}
auto& command = m_scheduler.Current();
command.EndRendering();
const auto native = command.Handle();
vk::BufferMemoryBarrier before {};
before.srcAccessMask = vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite;
before.dstAccessMask = vk::AccessFlagBits::eTransferRead;
before.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
before.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
before.buffer = buffer.Handle();
before.offset = 0;
before.size = buffer.Size();
native.pipelineBarrier(vk::PipelineStageFlagBits::eAllCommands,
vk::PipelineStageFlagBits::eTransfer, {}, 0, nullptr, 1, &before, 0,
nullptr);
native.copyBuffer(buffer.Handle(), m_download_buffer.Handle(),
static_cast<uint32_t>(copies.size()), copies.data());
auto after = before;
after.srcAccessMask = vk::AccessFlagBits::eTransferWrite;
after.dstAccessMask = vk::AccessFlagBits::eHostRead;
after.buffer = m_download_buffer.Handle();
after.offset = offset;
after.size = total_size;
native.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer,
vk::PipelineStageFlagBits::eAllCommands |
vk::PipelineStageFlagBits::eHost,
{}, 0, nullptr, 1, &after, 0, nullptr);
m_scheduler.DeferPriorityOperation([this, mapped, offset, total_size, buffer_address,
copies = std::move(copies)] {
m_download_buffer.Invalidate(offset, total_size);
for (const auto& copy: copies) {
Libs::LibKernel::Memory::WriteBacking(buffer_address + copy.srcOffset,
mapped + (copy.dstOffset - offset), copy.size);
}
});
return true;
}
BufferCache::BufferCache(GraphicContext& graphics, CommandScheduler& scheduler,
PageManager& page_manager, TextureCache& texture_cache)
: m_graphics(graphics), m_scheduler(scheduler),
m_fault_manager(graphics, scheduler, *this, CACHING_PAGEBITS, CACHING_NUMPAGES),
m_gds_buffer(graphics, scheduler, MemoryUsage::Stream, 0, AllFlags, GdsBufferSize),
m_bda_pagetable_buffer(graphics, scheduler, MemoryUsage::DeviceLocal, 0, AllFlags,
BDA_PAGETABLE_SIZE),
m_memory_tracker(page_manager),
m_staging_buffer(graphics, scheduler, MemoryUsage::Upload, 512 * MiB),
m_stream_buffer(graphics, scheduler, MemoryUsage::Stream, 64 * MiB),
m_download_buffer(graphics, scheduler, MemoryUsage::Download, 32 * MiB),
m_device_buffer(graphics, scheduler, MemoryUsage::DeviceLocal, 128 * MiB),
m_texture_cache(texture_cache) {
: m_graphics(graphics), m_scheduler(scheduler), m_fault_manager(graphics, scheduler, *this),
m_gds_buffer(graphics, scheduler, MemoryUsage::Stream, 0, AllFlags, GdsBufferSize),
m_bda_pagetable_buffer(graphics, scheduler, MemoryUsage::DeviceLocal, 0, AllFlags,
BDA_PAGETABLE_SIZE),
m_memory_tracker(page_manager),
m_staging_buffer(graphics, scheduler, MemoryUsage::Upload, 512 * MiB),
m_stream_buffer(graphics, scheduler, MemoryUsage::Stream, 64 * MiB),
m_download_buffer(graphics, scheduler, MemoryUsage::Download, 32 * MiB),
m_device_buffer(graphics, scheduler, MemoryUsage::DeviceLocal, 128 * MiB),
m_texture_cache(texture_cache) {
std::memset(m_gds_buffer.Mapped().data(), 0, static_cast<size_t>(m_gds_buffer.Size()));
m_gds_buffer.Flush(0, m_gds_buffer.Size());
SetVulkanObjectNameF(m_graphics.device, m_bda_pagetable_buffer.Handle(),
@@ -254,59 +236,29 @@ void BufferCache::ReadMemory(uint64_t vaddr, uint64_t size, bool is_write) {
"addr=0x%016" PRIx64 " size=0x%016" PRIx64 "\n",
vaddr, size);
}
m_scheduler.Context().GetGpu().SendCommandSync(
[this, vaddr, size, is_write] { ReadMemoryOnGpu(vaddr, size, is_write); });
}
void BufferCache::ReadMemoryOnGpu(uint64_t vaddr, uint64_t size, bool is_write) {
// CPU invalidation reaches this point only for a GPU-owned tracker page. Resolve the exact
// Buffer owner on the GPU thread so the cache index remains single-thread-owned.
if (is_write && !IsRegionRegistered(vaddr, size)) {
return;
}
std::vector<DownloadCopy> copies;
m_memory_tracker.ForEachDownloadRange<false>(
vaddr, size,
[&](uint64_t address, uint64_t bytes) noexcept {
m_memory_tracker.ValidateGpuDirtyPages(m_gpu_modified_ranges, address, bytes,
"memory invalidation");
},
[&](uint64_t address, uint64_t bytes) noexcept {
for (const auto range: m_gpu_modified_ranges.Intersections(address, bytes)) {
for (uint64_t copied = 0; copied < range.size;) {
const auto copy_address = range.address + copied;
const auto* owner = m_page_table.Find(copy_address >> PageTable::kPageBits);
if (owner == nullptr || !*owner) {
EXIT("BufferCache: invalidation readback has no buffer owner\n");
}
auto& buffer = m_slot_buffers[*owner];
if (!buffer.IsInBounds(copy_address, 1)) {
EXIT("BufferCache: invalidation readback is outside its buffer owner\n");
}
const auto copy_size = std::min(
range.size - copied, buffer.CpuAddress() + buffer.Size() - copy_address);
copies.push_back(
{&buffer, buffer.Offset(copy_address), copy_address, copy_size});
copied += copy_size;
}
}
});
if (copies.empty()) {
if (!is_write) {
m_scheduler.Context().GetGpu().SendCommandSync([this, vaddr, size, is_write] {
if (is_write && !IsRegionRegistered(vaddr, size)) {
return;
}
// A preceding read fault can consume the last GPU-owned copy after this write invalidation
// has already chosen to flush. Complete the CPU ownership handoff even though this callback
// no longer has bytes to download.
m_memory_tracker.MarkRegionAsCpuModified(vaddr, size);
return;
}
DownloadBufferMemory(copies);
// The enumeration above covered whole dirty pages and every exact interval on them.
m_memory_tracker.UnmarkRegionAsGpuModified(vaddr, size);
if (is_write) {
m_memory_tracker.MarkRegionAsCpuModified(vaddr, size);
}
auto& buffer = m_slot_buffers[FindBuffer(vaddr, size)];
// Widen nearby CPU reads so they share one GPU drain.
constexpr uint64_t WindowSize = 512 * 1024;
const auto buffer_begin = buffer.CpuAddress();
const auto buffer_end = buffer_begin + buffer.Size();
const auto window_begin = std::max(Common::AlignDown(vaddr, WindowSize), buffer_begin);
const auto window_end = std::min(std::max(window_begin + WindowSize, vaddr + size), buffer_end);
if (DownloadBufferMemory(buffer, window_begin, window_end - window_begin)) {
const auto tick = m_scheduler.CurrentTick();
m_scheduler.Wait(tick);
m_scheduler.WaitPriorityOperations(tick);
m_memory_tracker.UnmarkRegionAsGpuModified(window_begin, window_end - window_begin);
}
if (is_write) {
m_memory_tracker.MarkRegionAsCpuModified(vaddr, size);
}
});
}
BufferId BufferCache::FindBuffer(uint64_t vaddr, uint64_t size) {
@@ -326,38 +278,83 @@ BufferId BufferCache::FindBuffer(uint64_t vaddr, uint64_t size) {
return CreateBuffer(vaddr, size);
}
BufferId BufferCache::CreateBuffer(uint64_t vaddr, uint64_t size) {
auto& command = m_scheduler.Current();
EXIT_IF(command.IsInvalid());
auto begin = vaddr & ~(CACHING_PAGESIZE - 1);
auto end = (vaddr + size + CACHING_PAGESIZE - 1) & ~(CACHING_PAGESIZE - 1);
auto first = m_buffers.lower_bound(begin);
if (first != m_buffers.begin()) {
const auto previous = std::prev(first);
if (const auto& buffer = m_slot_buffers[previous->second];
buffer.CpuAddress() + buffer.Size() > begin) {
first = previous;
BufferCache::OverlapResult BufferCache::ResolveOverlaps(uint64_t vaddr, uint64_t size) {
static constexpr int StreamLeapThreshold = 16;
static constexpr uint64_t StreamLeapSize = CACHING_PAGESIZE * 128;
auto begin = vaddr;
auto end = vaddr + size;
const auto find_first = [&](uint64_t address) {
auto first = m_buffers.lower_bound(address);
if (first != m_buffers.begin()) {
const auto previous = std::prev(first);
const auto& buffer = m_slot_buffers[previous->second];
if (buffer.CpuAddress() + buffer.Size() > address) {
first = previous;
}
}
return first;
};
auto first = find_first(begin);
auto last = first;
int stream_score = 0;
bool has_stream_leap = false;
for (; last != m_buffers.end() && last->first < end; ++last) {
const auto& buffer = m_slot_buffers[last->second];
const auto buffer_begin = buffer.CpuAddress();
const auto buffer_end = buffer_begin + buffer.Size();
const bool expands_left = buffer_begin < begin;
const bool expands_right = buffer_end > end;
begin = std::min(begin, buffer_begin);
end = std::max(end, buffer_end);
if (!has_stream_leap && (stream_score += buffer.StreamScore()) > StreamLeapThreshold) {
has_stream_leap = true;
// Reserve space in the incoming stream's direction of growth.
// The old buffer extending left of the request predicts growth to the right, and vice versa.
if (expands_left) {
end += std::min(StreamLeapSize, PageTable::kAddressSpaceSize - end);
}
if (expands_right) {
const auto minimum = CACHING_PAGESIZE * 2;
if (begin > minimum) {
begin -= std::min(StreamLeapSize, begin - minimum);
}
first = find_first(begin);
begin = std::min(begin, first->first);
}
}
}
auto last = first;
for (; last != m_buffers.end() && last->first < end; ++last) {
const auto& buffer = m_slot_buffers[last->second];
begin = std::min(begin, buffer.CpuAddress());
end = std::max(end, buffer.CpuAddress() + buffer.Size());
return {first, last, begin, end, has_stream_leap};
}
void BufferCache::JoinOverlap(BufferId new_id, BufferId overlap_id, bool accumulate_stream_score) {
auto& new_buffer = m_slot_buffers[new_id];
auto& overlap = m_slot_buffers[overlap_id];
if (accumulate_stream_score) {
new_buffer.IncreaseStreamScore(overlap.StreamScore() + 1);
}
new_buffer.CopyFrom(m_scheduler.Current(), overlap, 0,
overlap.CpuAddress() - new_buffer.CpuAddress(), overlap.Size());
DeleteBuffer(overlap_id);
}
BufferId BufferCache::CreateBuffer(uint64_t vaddr, uint64_t size) {
EXIT_IF(m_scheduler.Current().IsInvalid());
const auto end = Common::AlignUp(vaddr + size, CACHING_PAGESIZE);
vaddr = Common::AlignDown(vaddr, CACHING_PAGESIZE);
size = end - vaddr;
const auto overlap = ResolveOverlaps(vaddr, size);
const auto id = m_slot_buffers.insert(
m_graphics, m_scheduler, MemoryUsage::DeviceLocal, begin,
AllFlags | vk::BufferUsageFlagBits::eShaderDeviceAddress, end - begin);
auto& buffer = m_slot_buffers[id];
m_graphics, m_scheduler, MemoryUsage::DeviceLocal, overlap.begin,
AllFlags | vk::BufferUsageFlagBits::eShaderDeviceAddress, overlap.end - overlap.begin);
const auto& buffer = m_slot_buffers[id];
SetVulkanObjectNameF(m_graphics.device, buffer.Handle(),
"Kyty.GameBuffer[guest=0x{:016x} size=0x{:x}]", begin, end - begin);
for (auto overlap = first; overlap != last;) {
const auto current = overlap++;
const auto old_id = current->second;
const auto& old = m_slot_buffers[old_id];
buffer.CopyFrom(command, old, 0, old.CpuAddress() - begin, old.Size());
DeleteBuffer(old_id);
"Kyty.GameBuffer[guest=0x{:016x} size=0x{:x}]", overlap.begin,
overlap.end - overlap.begin);
for (auto it = overlap.first; it != overlap.last;) {
const auto old_id = (it++)->second;
JoinOverlap(id, old_id, !overlap.has_stream_leap);
}
Register(id);
return id;
@@ -380,7 +377,6 @@ bool BufferCache::SynchronizeBuffer(Buffer& buffer, uint64_t vaddr, uint64_t siz
command.EndRendering();
const auto native = command.Handle();
vk::BufferMemoryBarrier before {};
before.sType = vk::StructureType::eBufferMemoryBarrier;
before.srcAccessMask = vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite |
vk::AccessFlagBits::eTransferRead |
vk::AccessFlagBits::eTransferWrite;
@@ -458,54 +454,34 @@ std::pair<Buffer*, uint64_t> BufferCache::ObtainBuffer(uint64_t vaddr, uint64_t
}
}
auto* buffer = m_slot_buffers.try_get(id);
if (buffer == nullptr || buffer->is_deleted || !buffer->IsInBounds(vaddr, size)) {
id = FindBuffer(vaddr, size);
buffer = &m_slot_buffers[id];
if (IsBufferInvalid(id) || !m_slot_buffers[id].IsInBounds(vaddr, size)) {
id = FindBuffer(vaddr, size);
}
TouchBuffer(*buffer);
(void)SynchronizeBuffer(*buffer, vaddr, size, is_written, is_texel_buffer);
auto& buffer = m_slot_buffers[id];
TouchBuffer(buffer);
(void)SynchronizeBuffer(buffer, vaddr, size, is_written, is_texel_buffer);
if (is_written) {
m_gpu_modified_ranges.Add(vaddr, size);
}
return {buffer, buffer->Offset(vaddr)};
return {&buffer, buffer.Offset(vaddr)};
}
std::pair<Buffer*, uint64_t> BufferCache::ObtainBufferForImage(uint64_t vaddr, uint64_t size) {
if (!GuestRange {vaddr, size}.Valid()) {
EXIT("BufferCache: invalid image source\n");
}
auto find_owner = [&]() -> Buffer* {
const auto* owner = m_page_table.Find(vaddr >> PageTable::kPageBits);
if (owner == nullptr || !*owner) {
return nullptr;
}
const auto* owner = m_page_table.Find(vaddr >> PageTable::kPageBits);
if (owner != nullptr && *owner) {
auto& buffer = m_slot_buffers[*owner];
return buffer.IsInBounds(vaddr, size) ? &buffer : nullptr;
};
{
const bool cpu_modified = m_memory_tracker.IsRegionCpuModified(vaddr, size);
const bool gpu_modified = m_memory_tracker.IsRegionGpuModified(vaddr, size);
const bool has_dirty_buffer_source = m_gpu_modified_ranges.Intersects(vaddr, size);
m_memory_tracker.ValidateGpuDirtyOwnership(m_gpu_modified_ranges, vaddr, size,
"image source");
auto* owner = find_owner();
if (has_dirty_buffer_source && owner == nullptr) {
if (!IsRegionRegistered(vaddr, size)) {
EXIT("BufferCache: GPU-dirty image source has no native buffer\n");
}
owner = &m_slot_buffers[FindBuffer(vaddr, size)];
}
if (owner != nullptr && !cpu_modified && (!gpu_modified || has_dirty_buffer_source)) {
TouchBuffer(*owner);
return {owner, owner->Offset(vaddr)};
}
if (has_dirty_buffer_source && owner == nullptr) {
EXIT("BufferCache: GPU-dirty image source could not resolve its native owner\n");
if (buffer.IsInBounds(vaddr, size)) {
TouchBuffer(buffer);
(void)SynchronizeBuffer(buffer, vaddr, size, false, false);
return {&buffer, buffer.Offset(vaddr)};
}
}
if (IsRegionGpuModified(vaddr, size)) {
return ObtainBuffer(vaddr, size, false, false);
}
auto [staging, stage_offset] = m_staging_buffer.Map(size, 16);
if (staging == nullptr || (!Libs::LibKernel::Memory::TryReadBacking(vaddr, staging, size) &&
@@ -513,52 +489,7 @@ std::pair<Buffer*, uint64_t> BufferCache::ObtainBufferForImage(uint64_t vaddr, u
EXIT("BufferCache: failed to read mapped guest image backing\n");
}
m_staging_buffer.Commit();
const bool has_dirty_buffer_source = m_gpu_modified_ranges.Intersects(vaddr, size);
auto* owner = find_owner();
if (has_dirty_buffer_source && owner == nullptr) {
EXIT("BufferCache: GPU-dirty image source lost its native owner\n");
}
if (owner == nullptr ||
(m_memory_tracker.IsRegionGpuModified(vaddr, size) && !has_dirty_buffer_source)) {
return {&m_staging_buffer, stage_offset};
}
TouchBuffer(*owner);
std::vector<std::pair<uint64_t, uint64_t>> uploads;
m_memory_tracker.ForEachUploadRange(
vaddr, size, false,
[&](uint64_t address, uint64_t upload_size) noexcept {
uploads.emplace_back(address, upload_size);
},
[&]() noexcept {
for (const auto& [address, upload_size]: uploads) {
owner->CopyFrom(m_scheduler.Current(), m_staging_buffer,
stage_offset + address - vaddr, owner->Offset(address), upload_size,
vk::AccessFlagBits::eHostWrite);
}
});
return {owner, owner->Offset(vaddr)};
}
void BufferCache::WriteHostMemory(uint64_t vaddr, std::span<const uint8_t> data) {
if (vaddr == 0 || data.empty() || data.size() > UINT64_MAX - vaddr) {
EXIT("BufferCache: invalid host DMA write\n");
}
Libs::LibKernel::Memory::WriteBacking(vaddr, data.data(), data.size());
const auto end = vaddr + data.size();
for (const auto& [address, id]: m_buffers) {
auto& buffer = m_slot_buffers[id];
const auto buffer_end = address + buffer.Size();
const auto begin = std::max(vaddr, address);
const auto range_end = std::min(end, buffer_end);
if (begin >= range_end) {
continue;
}
WriteDataBuffer(buffer, begin, data.data() + begin - vaddr, range_end - begin);
TouchBuffer(buffer);
}
return {&m_staging_buffer, stage_offset};
}
void BufferCache::FillBuffer(uint64_t vaddr, uint64_t size, uint32_t value, bool is_gds) {
@@ -576,29 +507,15 @@ void BufferCache::FillBuffer(uint64_t vaddr, uint64_t size, uint32_t value, bool
EXIT("BufferCache: invalid fill memory address\n");
}
(void)m_texture_cache.ClearMeta(vaddr);
{
const auto region = m_texture_cache.QueryRegion(vaddr, size);
if (!HasGpuDirtyBytes(vaddr, size) && !region.gpu_image_bytes) {
if (region.image_bytes) {
m_texture_cache.InvalidateMemory(vaddr, size);
}
std::array<uint32_t, 4096> values;
values.fill(value);
const std::span<const uint8_t> bytes {reinterpret_cast<const uint8_t*>(values.data()),
sizeof(values)};
for (uint64_t offset = 0; offset < size;) {
const auto chunk = std::min<uint64_t>(size - offset, bytes.size());
WriteHostMemory(vaddr + offset, bytes.first(chunk));
offset += chunk;
}
return;
}
if (!IsRegionGpuModified(vaddr, size)) {
// Access the guest mapping so write faults invalidate cached buffers and images.
auto* destination = reinterpret_cast<uint32_t*>(vaddr);
std::fill(destination, destination + size / sizeof(uint32_t), value);
return;
}
m_texture_cache.InvalidateMemoryFromGPU(vaddr, size);
const auto id = FindBuffer(vaddr, size);
auto [dst, dst_offset] = ObtainBuffer(vaddr, size, true, true, id);
EXIT_IF(dst == nullptr);
auto [dst, dst_offset] = ObtainBuffer(vaddr, size, true, true);
dst->Fill(dst_offset, size, value);
}
@@ -615,29 +532,11 @@ void BufferCache::CopyBuffer(uint64_t dst_vaddr, uint64_t src_vaddr, uint64_t si
" size=0x%016" PRIx64 " src_gds=%d dst_gds=%d\n",
src_vaddr, dst_vaddr, size, static_cast<int>(src_gds), static_cast<int>(dst_gds));
}
if (src_memory || dst_memory) {
const auto src_region =
src_memory ? m_texture_cache.QueryRegion(src_vaddr, size) : TextureCache::RegionInfo {};
const auto dst_region =
dst_memory ? m_texture_cache.QueryRegion(dst_vaddr, size) : TextureCache::RegionInfo {};
if (src_memory && dst_memory && !HasGpuDirtyBytes(src_vaddr, size) &&
!HasGpuDirtyBytes(dst_vaddr, size) && !src_region.gpu_image_bytes &&
!dst_region.gpu_image_bytes) {
if (dst_region.image_bytes) {
m_texture_cache.InvalidateMemory(dst_vaddr, size);
}
std::array<uint8_t, 64 * 1024> bytes;
for (uint64_t offset = 0; offset < size;) {
const auto chunk = std::min<uint64_t>(size - offset, bytes.size());
if (!Libs::LibKernel::Memory::TryReadBacking(src_vaddr + offset, bytes.data(),
chunk)) {
EXIT("BufferCache: host DMA source has no direct backing\n");
}
WriteHostMemory(dst_vaddr + offset, std::span {bytes}.first(chunk));
offset += chunk;
}
return;
}
if (src_memory && dst_memory && !IsRegionGpuModified(dst_vaddr, size) &&
!IsRegionGpuModified(src_vaddr, size) && !m_texture_cache.FindImageFromRange(src_vaddr, size)) {
std::memcpy(reinterpret_cast<void*>(dst_vaddr), reinterpret_cast<const void*>(src_vaddr),
size);
return;
}
auto& command = m_scheduler.Current();
@@ -650,10 +549,6 @@ void BufferCache::CopyBuffer(uint64_t dst_vaddr, uint64_t src_vaddr, uint64_t si
: std::pair {&m_gds_buffer, src_vaddr};
auto [dst, dst_offset] = dst_memory ? ObtainBuffer(dst_vaddr, size, true, true, dst_id)
: std::pair {&m_gds_buffer, dst_vaddr};
EXIT_IF(src == nullptr || dst == nullptr);
if (src == dst && src_offset < dst_offset + size && dst_offset < src_offset + size) {
EXIT("BufferCache: resolved Vulkan copy ranges overlap\n");
}
dst->CopyFrom(command, *src, src_offset, dst_offset, size);
}
@@ -697,8 +592,7 @@ void BufferCache::RunGarbageCollector() {
const size_t limit = aggressive ? 64 : 32;
std::vector<BufferId> dirty_buffers;
std::vector<DownloadCopy> copies;
size_t retire_count = 0;
size_t retire_count = 0;
m_lru_cache.ForEachItemBelow(tick - age, [&](BufferId id) {
auto& buffer = m_slot_buffers[id];
EXIT_IF(buffer.is_deleted);
@@ -709,19 +603,7 @@ void BufferCache::RunGarbageCollector() {
return false;
}
if (dirty) {
m_memory_tracker.ForEachDownloadRange<false>(
buffer.CpuAddress(), buffer.Size(),
[&](uint64_t dirty_address, uint64_t dirty_size) noexcept {
m_memory_tracker.ValidateGpuDirtyPages(m_gpu_modified_ranges, dirty_address,
dirty_size, "garbage collection");
},
[&](uint64_t dirty_address, uint64_t dirty_size) noexcept {
m_gpu_modified_ranges.ForEachIntersection(
dirty_address, dirty_size, [&](RangeSet::Range range) {
copies.push_back({&buffer, range.address - buffer.CpuAddress(),
range.address, range.size});
});
});
EXIT_IF(!DownloadBufferMemory(buffer, buffer.CpuAddress(), buffer.Size()));
dirty_buffers.push_back(id);
} else {
m_memory_tracker.UntrackMemory(buffer.CpuAddress(), buffer.Size());
@@ -733,8 +615,10 @@ void BufferCache::RunGarbageCollector() {
return;
}
EXIT_IF(copies.empty());
DownloadBufferMemory(copies);
// Publish all queued downloads before releasing their tracked pages and owners.
const auto completion_tick = m_scheduler.CurrentTick();
m_scheduler.Wait(completion_tick);
m_scheduler.WaitPriorityOperations(completion_tick);
for (const auto id: dirty_buffers) {
auto& buffer = m_slot_buffers[id];
m_memory_tracker.UnmarkRegionAsGpuModified(buffer.CpuAddress(), buffer.Size());
+19 -10
View File
@@ -74,16 +74,26 @@ public:
private:
friend struct BufferCacheTestAccess;
struct DownloadCopy;
bool IsBufferInvalid(BufferId id) const {
const auto* buffer = m_slot_buffers.try_get(id);
return buffer == nullptr || buffer->is_deleted;
}
using BufferMap = std::map<uint64_t, BufferId>;
struct OverlapResult {
BufferMap::iterator first;
BufferMap::iterator last;
uint64_t begin;
uint64_t end;
bool has_stream_leap;
};
using PageTable = MultiLevelPageTable<BufferId, CACHING_PAGEBITS, 40, 16>;
static_assert(CACHING_PAGESIZE == (uint64_t {1} << PageTable::kPageBits));
static constexpr uint64_t DOWNLOAD_ALIGNMENT = 64;
[[nodiscard]] static constexpr uint64_t AlignDownload(uint64_t size) noexcept {
return (size + DOWNLOAD_ALIGNMENT - 1) & ~(DOWNLOAD_ALIGNMENT - 1);
}
[[nodiscard]] static std::pair<uint64_t, uint64_t> DownloadEnvelope(const DownloadCopy& copy);
void WriteDataBuffer(Buffer& buffer, uint64_t address, const void* source, uint64_t size);
void TouchBuffer(const Buffer& buffer);
[[nodiscard]] OverlapResult ResolveOverlaps(uint64_t vaddr, uint64_t size);
void JoinOverlap(BufferId new_id, BufferId overlap_id, bool accumulate_stream_score);
[[nodiscard]] BufferId CreateBuffer(uint64_t vaddr, uint64_t size);
void Register(BufferId id);
void Unregister(BufferId id);
@@ -95,9 +105,8 @@ private:
[[nodiscard]] vk::Buffer UploadCopies(Buffer& buffer, std::span<vk::BufferCopy> copies,
uint64_t total_size);
[[nodiscard]] bool SynchronizeBufferFromImage(Buffer& buffer, uint64_t vaddr, uint64_t size);
void DownloadBufferMemory(std::span<const DownloadCopy> copies);
void WriteHostMemory(uint64_t vaddr, std::span<const uint8_t> data);
void ReadMemoryOnGpu(uint64_t vaddr, uint64_t size, bool is_write);
// Queues backing publication; callers wait before clearing dirty pages or reusing their data.
[[nodiscard]] bool DownloadBufferMemory(Buffer& buffer, uint64_t vaddr, uint64_t size);
GraphicContext& m_graphics;
CommandScheduler& m_scheduler;
@@ -106,7 +115,7 @@ private:
Buffer m_bda_pagetable_buffer;
Common::SlotVector<Buffer> m_slot_buffers;
Common::LeastRecentlyUsedCache<BufferId, uint64_t> m_lru_cache;
std::map<uint64_t, BufferId> m_buffers;
BufferMap m_buffers;
PageTable m_page_table;
RangeSet m_gpu_modified_ranges;
MemoryTracker m_memory_tracker;
+9 -25
View File
@@ -23,13 +23,10 @@ constexpr size_t PageFaultAreaSize = MaxPageFaults * sizeof(uint64_t);
} // namespace
FaultManager::FaultManager(GraphicContext& graphics, CommandScheduler& scheduler,
BufferCache& buffer_cache, uint32_t caching_pagebits,
uint64_t caching_num_pages)
BufferCache& buffer_cache)
: m_graphics(graphics), m_scheduler(scheduler), m_buffer_cache(buffer_cache),
m_caching_pagesize(uint64_t {1} << caching_pagebits),
m_caching_num_pages(caching_num_pages), m_fault_buffer_size(caching_num_pages / 8),
m_fault_buffer(graphics, scheduler, MemoryUsage::DeviceLocal, 0, AllFlags,
m_fault_buffer_size),
BufferCache::CACHING_NUMPAGES / 8),
m_download_buffer(graphics, scheduler, MemoryUsage::Download, 0, AllFlags,
MaxPendingFaults * PageFaultAreaSize) {
SetVulkanObjectNameF(m_graphics.device, m_fault_buffer.Handle(), "Fault Buffer");
@@ -39,7 +36,6 @@ FaultManager::FaultManager(GraphicContext& graphics, CommandScheduler& scheduler
{1, vk::DescriptorType::eStorageBuffer, 1, vk::ShaderStageFlagBits::eCompute, nullptr},
};
vk::DescriptorSetLayoutCreateInfo layout_info {};
layout_info.sType = vk::StructureType::eDescriptorSetLayoutCreateInfo;
layout_info.flags = vk::DescriptorSetLayoutCreateFlagBits::ePushDescriptorKHR;
layout_info.bindingCount = std::size(bindings);
layout_info.pBindings = bindings;
@@ -48,16 +44,9 @@ FaultManager::FaultManager(GraphicContext& graphics, CommandScheduler& scheduler
&m_fault_process_desc_layout),
"create fault-buffer descriptor layout");
vk::ShaderModuleCreateInfo module_info {};
module_info.sType = vk::StructureType::eShaderModuleCreateInfo;
module_info.codeSize = std::size(FAULT_BUFFER_PROCESS_SPV) * sizeof(uint32_t);
module_info.pCode = FAULT_BUFFER_PROCESS_SPV;
vk::ShaderModule module = nullptr;
RequireVulkanSuccess(m_graphics.device.createShaderModule(&module_info, nullptr, &module),
"create fault-buffer shader module");
const auto module = CompileSPV(FAULT_BUFFER_PROCESS_SPV, m_graphics.device);
vk::PipelineLayoutCreateInfo pipeline_layout_info {};
pipeline_layout_info.sType = vk::StructureType::ePipelineLayoutCreateInfo;
pipeline_layout_info.setLayoutCount = 1;
pipeline_layout_info.pSetLayouts = &m_fault_process_desc_layout;
RequireVulkanSuccess(
@@ -66,12 +55,10 @@ FaultManager::FaultManager(GraphicContext& graphics, CommandScheduler& scheduler
"create fault-buffer pipeline layout");
vk::PipelineShaderStageCreateInfo stage {};
stage.sType = vk::StructureType::ePipelineShaderStageCreateInfo;
stage.stage = vk::ShaderStageFlagBits::eCompute;
stage.module = module;
stage.pName = "main";
vk::ComputePipelineCreateInfo pipeline_info {};
pipeline_info.sType = vk::StructureType::eComputePipelineCreateInfo;
pipeline_info.stage = stage;
pipeline_info.layout = m_fault_process_pipeline_layout;
const auto result = m_graphics.device.createComputePipelines(
@@ -99,14 +86,13 @@ void FaultManager::ProcessFaultBuffer() {
m_download_buffer.Flush(offset, PageFaultAreaSize);
vk::BufferMemoryBarrier2 pre_barrier {};
pre_barrier.sType = vk::StructureType::eBufferMemoryBarrier2;
pre_barrier.srcStageMask = vk::PipelineStageFlagBits2::eAllCommands;
pre_barrier.srcAccessMask = vk::AccessFlagBits2::eShaderWrite;
pre_barrier.dstStageMask = vk::PipelineStageFlagBits2::eComputeShader;
pre_barrier.dstAccessMask = vk::AccessFlagBits2::eShaderRead;
pre_barrier.buffer = m_fault_buffer.Handle();
pre_barrier.offset = 0;
pre_barrier.size = m_fault_buffer_size;
pre_barrier.size = m_fault_buffer.Size();
auto post_barrier = pre_barrier;
post_barrier.srcStageMask = vk::PipelineStageFlagBits2::eComputeShader;
post_barrier.srcAccessMask = vk::AccessFlagBits2::eShaderWrite;
@@ -114,12 +100,11 @@ void FaultManager::ProcessFaultBuffer() {
post_barrier.dstAccessMask = vk::AccessFlagBits2::eShaderWrite;
const vk::DescriptorBufferInfo infos[] {
{m_fault_buffer.Handle(), 0, m_fault_buffer_size},
{m_fault_buffer.Handle(), 0, m_fault_buffer.Size()},
{m_download_buffer.Handle(), offset, PageFaultAreaSize},
};
std::array<vk::WriteDescriptorSet, 2> writes {};
for (uint32_t index = 0; index < writes.size(); ++index) {
writes[index].sType = vk::StructureType::eWriteDescriptorSet;
writes[index].dstBinding = index;
writes[index].descriptorCount = 1;
writes[index].descriptorType = vk::DescriptorType::eStorageBuffer;
@@ -129,7 +114,6 @@ void FaultManager::ProcessFaultBuffer() {
m_scheduler.EndRendering();
auto command = m_scheduler.Current().Handle();
vk::DependencyInfo dependency {};
dependency.sType = vk::StructureType::eDependencyInfo;
dependency.dependencyFlags = vk::DependencyFlagBits::eByRegion;
dependency.bufferMemoryBarrierCount = 1;
dependency.pBufferMemoryBarriers = &pre_barrier;
@@ -137,7 +121,7 @@ void FaultManager::ProcessFaultBuffer() {
command.bindPipeline(vk::PipelineBindPoint::eCompute, m_fault_process_pipeline);
command.pushDescriptorSetKHR(vk::PipelineBindPoint::eCompute,
m_fault_process_pipeline_layout, 0, writes);
const auto num_threads = m_caching_num_pages / 32;
const auto num_threads = BufferCache::CACHING_NUMPAGES / 32;
const auto num_workgroups = (num_threads + 63) / 64;
command.dispatch(static_cast<uint32_t>(num_workgroups), 1, 1);
dependency.pBufferMemoryBarriers = &post_barrier;
@@ -146,14 +130,14 @@ void FaultManager::ProcessFaultBuffer() {
const auto area = m_current_area;
m_scheduler.DeferOperation([this, mapped, offset, area] {
m_download_buffer.Invalidate(offset, PageFaultAreaSize);
m_fault_ranges.Clear();
RangeSet fault_ranges;
const auto* faults = std::bit_cast<const uint64_t*>(mapped);
const auto count = static_cast<uint32_t>(faults[0]);
for (uint32_t index = 1; index <= count; ++index) {
m_fault_ranges.Add(faults[index], m_caching_pagesize);
fault_ranges.Add(faults[index], BufferCache::CACHING_PAGESIZE);
LOGF("Accessed non-GPU cached memory at 0x%016" PRIx64 "\n", faults[index]);
}
m_fault_ranges.ForEach([this](uint64_t start, uint64_t end) {
fault_ranges.ForEach([this](uint64_t start, uint64_t end) {
EXIT_IF(end - start > std::numeric_limits<uint32_t>::max());
(void)m_buffer_cache.FindBuffer(start, end - start);
});
+1 -7
View File
@@ -2,7 +2,6 @@
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_CACHE_FAULTMANAGER_H_
#include "common/abi.h"
#include "graphics/host_gpu/rangeSet.h"
#include "graphics/host_gpu/renderer/cache/streamBuffer.h"
#include <array>
@@ -16,8 +15,7 @@ class FaultManager {
static constexpr size_t MaxPendingFaults = 8;
public:
FaultManager(GraphicContext& graphics, CommandScheduler& scheduler, BufferCache& buffer_cache,
uint32_t caching_pagebits, uint64_t caching_num_pages);
FaultManager(GraphicContext& graphics, CommandScheduler& scheduler, BufferCache& buffer_cache);
~FaultManager();
KYTY_CLASS_NO_COPY(FaultManager);
@@ -28,10 +26,6 @@ private:
GraphicContext& m_graphics;
CommandScheduler& m_scheduler;
BufferCache& m_buffer_cache;
RangeSet m_fault_ranges;
uint64_t m_caching_pagesize;
uint64_t m_caching_num_pages;
uint64_t m_fault_buffer_size;
Buffer m_fault_buffer;
Buffer m_download_buffer;
std::array<uint64_t, MaxPendingFaults> m_fault_areas {};
@@ -1,96 +0,0 @@
#include "graphics/host_gpu/renderer/cache/gpuResourceManager.h"
#include "common/assert.h"
#include "graphics/guest_gpu/graphicsRun.h"
#include "graphics/host_gpu/renderer/commandScheduler.h"
namespace Libs::Graphics {
GpuResourceManager::GpuResourceManager(GraphicContext& graphics, CommandScheduler& scheduler)
: m_scheduler(scheduler), m_buffer_cache(graphics, scheduler, m_page_manager, m_texture_cache),
m_texture_cache(graphics, scheduler, m_page_manager, m_buffer_cache) {}
GpuResourceManager::~GpuResourceManager() = default;
bool GpuResourceManager::HandleFault(PageFaultAccess access, uint64_t fault_vaddr) noexcept {
constexpr uint64_t fault_size = 8;
if (!IsMapped(fault_vaddr, fault_size)) {
return false;
}
if (access == PageFaultAccess::Write) {
m_buffer_cache.InvalidateMemory(fault_vaddr, fault_size);
m_texture_cache.InvalidateMemory(fault_vaddr, fault_size);
} else {
m_buffer_cache.ReadMemory(fault_vaddr, fault_size);
}
return true;
}
bool GpuResourceManager::InvalidateMemory(uint64_t vaddr, uint64_t size) {
if (!IsMapped(vaddr, size)) {
return false;
}
m_buffer_cache.InvalidateMemory(vaddr, size);
m_texture_cache.InvalidateMemory(vaddr, size);
return true;
}
bool GpuResourceManager::IsMapped(uint64_t vaddr, uint64_t size) const noexcept {
if (!GuestRange {vaddr, size}.Valid()) {
return false;
}
std::shared_lock lock(m_mapped_ranges_mutex);
return m_mapped_ranges.Contains(vaddr, size);
}
void GpuResourceManager::MapMemory(uint64_t vaddr, uint64_t size) {
{
std::lock_guard lock(m_mapped_ranges_mutex);
m_mapped_ranges.Add(vaddr, size);
}
m_page_manager.OnGpuMap(vaddr, size);
}
void GpuResourceManager::UnmapMemory(uint64_t vaddr, uint64_t size) {
if (CommandScheduler::InDeferredOperation()) {
EXIT("unsupported memory unmap from an asynchronous GPU completion, "
"addr=0x%016" PRIx64 " size=0x%016" PRIx64 "\n",
vaddr, size);
}
const auto unmap = [this, vaddr, size] {
if (m_scheduler.Active()) {
const auto tick = m_scheduler.CurrentTick();
m_scheduler.Finish();
m_scheduler.WaitPriorityOperations(tick);
}
m_buffer_cache.InvalidateMemory(vaddr, size);
m_texture_cache.UnmapMemory(vaddr, size);
m_page_manager.OnGpuUnmap(vaddr, size);
std::lock_guard lock(m_mapped_ranges_mutex);
m_mapped_ranges.Subtract(vaddr, size);
};
if (m_gpu == nullptr) {
unmap();
return;
}
m_gpu->SendCommandSync(unmap);
}
void GpuResourceManager::PrepareBda() {
std::shared_lock lock(m_mapped_ranges_mutex);
m_mapped_ranges.ForEach([this](uint64_t start, uint64_t end) {
m_buffer_cache.SynchronizeBuffersInRange(start, end - start);
});
m_fault_process_pending = true;
}
void GpuResourceManager::RunGarbageCollector() {
if (m_fault_process_pending) {
m_fault_process_pending = false;
m_buffer_cache.ProcessFaultBuffer();
}
m_texture_cache.ProcessDownloadImages();
m_texture_cache.RunGarbageCollector();
m_buffer_cache.RunGarbageCollector();
}
} // namespace Libs::Graphics
@@ -1,49 +0,0 @@
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_GPURESOURCEMANAGER_H_
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_GPURESOURCEMANAGER_H_
#include "common/abi.h"
#include "common/common.h"
#include "graphics/host_gpu/pageManager.h"
#include "graphics/host_gpu/renderer/cache/bufferCache.h"
#include "graphics/host_gpu/renderer/cache/textureCache.h"
#include <cstdint>
#include <shared_mutex>
namespace Libs::Graphics {
class CommandScheduler;
class GuestGpu;
class GpuResourceManager {
public:
GpuResourceManager(GraphicContext& graphics, CommandScheduler& scheduler);
~GpuResourceManager();
KYTY_CLASS_NO_COPY(GpuResourceManager);
[[nodiscard]] BufferCache& GetBufferCache() { return m_buffer_cache; }
[[nodiscard]] TextureCache& GetTextureCache() { return m_texture_cache; }
void SetGpu(GuestGpu* gpu) noexcept { m_gpu = gpu; }
[[nodiscard]] bool HandleFault(PageFaultAccess access, uint64_t fault_vaddr) noexcept;
[[nodiscard]] bool InvalidateMemory(uint64_t vaddr, uint64_t size);
[[nodiscard]] bool IsMapped(uint64_t vaddr, uint64_t size) const noexcept;
void MapMemory(uint64_t vaddr, uint64_t size);
void UnmapMemory(uint64_t vaddr, uint64_t size);
void PrepareBda();
void RunGarbageCollector();
private:
PageManager m_page_manager;
CommandScheduler& m_scheduler;
BufferCache m_buffer_cache;
TextureCache m_texture_cache;
mutable std::shared_mutex m_mapped_ranges_mutex;
RangeSet m_mapped_ranges;
GuestGpu* m_gpu = nullptr;
bool m_fault_process_pending = false;
};
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_GPURESOURCEMANAGER_H_
@@ -256,17 +256,6 @@ private:
size_type m_inline_size = 0;
};
// Removes only the requested owner. Other owners in the same page entry remain intact.
template <typename Container, typename Value>
[[nodiscard]] bool EraseExact(Container& owners, const Value& owner) {
const auto it = std::find(owners.begin(), owners.end(), owner);
if (it == owners.end()) {
return false;
}
owners.erase(it);
return true;
}
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_MULTILEVELPAGETABLE_H_
-3
View File
@@ -116,9 +116,6 @@ vk::Sampler SamplerCache::GetSampler(const ShaderSamplerResource& r) {
default: EXIT("unknown border color: %d", static_cast<int>(r.BorderColorType()));
}
sampler_info.sType = vk::StructureType::eSamplerCreateInfo;
sampler_info.pNext = nullptr;
sampler_info.flags = {};
sampler_info.magFilter = to_vk_filter(mag_filter);
sampler_info.minFilter = to_vk_filter(min_filter);
sampler_info.mipmapMode =
+29 -67
View File
@@ -1,13 +1,12 @@
#include "graphics/host_gpu/renderer/cache/streamBuffer.h"
#include "common/alignment.h"
#include "common/assert.h"
#include "common/profiler.h"
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/renderer/commandScheduler.h"
#include "graphics/host_gpu/vma.h"
#include <cstring>
#include <limits>
#include <numeric>
#include <vk_mem_alloc.h>
@@ -46,16 +45,11 @@ constexpr size_t WATCHES_RESERVE_CHUNK = 0x1000;
result = value;
return true;
}
const auto remainder = value % alignment;
if (remainder == 0) {
result = value;
return true;
}
const auto increment = alignment - remainder;
if (value > std::numeric_limits<uint64_t>::max() - increment) {
const auto aligned = Common::AlignUp(value, alignment);
if (aligned < value) {
return false;
}
result = value + increment;
result = aligned;
return true;
}
@@ -64,14 +58,13 @@ constexpr size_t WATCHES_RESERVE_CHUNK = 0x1000;
Buffer::Buffer(GraphicContext& graphics, CommandScheduler& scheduler, MemoryUsage usage,
uint64_t cpu_address, vk::BufferUsageFlags flags, uint64_t size)
: m_graphics(&graphics), m_scheduler(&scheduler), m_usage(usage), m_cpu_address(cpu_address),
m_size(size), m_buffer(std::make_unique<VulkanBuffer>()) {
m_size(size) {
KYTY_PROFILER_FUNCTION();
EXIT_IF(graphics.allocator == nullptr || size == 0);
vk::BufferCreateInfo buffer_info {};
buffer_info.size = size;
buffer_info.usage = flags;
buffer_info.sharingMode = vk::SharingMode::eExclusive;
const bool with_bda = bool(flags & vk::BufferUsageFlagBits::eShaderDeviceAddress);
const VmaAllocationCreateFlags bda_flag =
@@ -88,93 +81,70 @@ Buffer::Buffer(GraphicContext& graphics, CommandScheduler& scheduler, MemoryUsag
VkBuffer native_buffer = VK_NULL_HANDLE;
const auto result = static_cast<vk::Result>(vmaCreateBuffer(
graphics.allocator, static_cast<const VkBufferCreateInfo*>(buffer_info), &allocation_info,
&native_buffer, &m_buffer->memory.allocation, &allocation_result));
&native_buffer, &m_allocation, &allocation_result));
if (result != vk::Result::eSuccess) {
graphics.LogMemoryBudget();
}
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
m_buffer->buffer = native_buffer;
m_buffer->usage = flags;
m_buffer->buffer_size = size;
m_buffer->memory.allocation_info = allocation_result;
m_buffer->memory.memory = allocation_result.deviceMemory;
m_buffer->memory.offset = allocation_result.offset;
m_buffer->memory.type = allocation_result.memoryType;
m_buffer->memory.unique_id = VulkanNextMemoryUniqueId();
graphics.device.getBufferMemoryRequirements(m_buffer->buffer, &m_buffer->memory.requirements);
if (static_cast<bool>(flags & vk::BufferUsageFlagBits::eShaderDeviceAddress)) {
m_buffer = native_buffer;
if (with_bda) {
vk::BufferDeviceAddressInfo address_info {};
address_info.sType = vk::StructureType::eBufferDeviceAddressInfo;
address_info.buffer = m_buffer->buffer;
address_info.buffer = m_buffer;
m_device_address = graphics.device.getBufferAddress(address_info);
EXIT_IF(m_device_address == 0);
}
VkMemoryPropertyFlags properties = 0;
vmaGetAllocationMemoryProperties(graphics.allocator, m_buffer->memory.allocation, &properties);
m_buffer->memory.property = vk::MemoryPropertyFlags(properties);
m_is_coherent = (properties & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) != 0;
vmaGetAllocationMemoryProperties(graphics.allocator, m_allocation, &properties);
m_coherent = (properties & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) != 0;
if (allocation_result.pMappedData != nullptr) {
m_mapped = {static_cast<uint8_t*>(allocation_result.pMappedData),
static_cast<size_t>(size)};
}
VulkanTrackAllocation(m_buffer->memory);
}
Buffer::~Buffer() {
if (m_buffer->buffer != nullptr) {
VulkanUntrackAllocation(m_buffer->memory);
vmaDestroyBuffer(m_graphics->allocator, m_buffer->buffer, m_buffer->memory.allocation);
if (m_buffer != nullptr) {
vmaDestroyBuffer(m_graphics->allocator, m_buffer, m_allocation);
}
}
vk::Buffer Buffer::Handle() const noexcept {
return m_buffer->buffer;
}
vk::DeviceAddress Buffer::BufferDeviceAddress() const noexcept {
EXIT_IF(m_device_address == 0);
return m_device_address;
}
bool Buffer::IsInBounds(uint64_t address, uint64_t size) const noexcept {
return address >= m_cpu_address && size <= m_size && address - m_cpu_address <= m_size - size;
}
void Buffer::Write(uint64_t offset, const void* source, uint64_t size) {
EXIT_IF(source == nullptr || m_mapped.empty() || offset > m_size || size > m_size - offset);
std::memcpy(m_mapped.data() + offset, source, static_cast<size_t>(size));
Flush(offset, size);
return address >= m_cpu_address && size <= Size() && address - m_cpu_address <= Size() - size;
}
void Buffer::Flush(uint64_t offset, uint64_t size) {
EXIT_IF(m_mapped.empty() || offset > m_size || size > m_size - offset);
if (!m_is_coherent && size != 0) {
EXIT_IF(m_mapped.empty() || offset > Size() || size > Size() - offset);
if (!IsCoherent() && size != 0) {
const auto result =
vmaFlushAllocation(m_graphics->allocator, m_buffer->memory.allocation, offset, size);
vmaFlushAllocation(m_graphics->allocator, m_allocation, offset, size);
EXIT_NOT_IMPLEMENTED(static_cast<vk::Result>(result) != vk::Result::eSuccess);
}
}
void Buffer::Invalidate(uint64_t offset, uint64_t size) {
EXIT_IF(m_usage != MemoryUsage::Download || offset > m_size || size > m_size - offset);
if (!m_is_coherent && size != 0) {
EXIT_IF(m_usage != MemoryUsage::Download || offset > Size() || size > Size() - offset);
if (!IsCoherent() && size != 0) {
const auto result =
vmaInvalidateAllocation(m_graphics->allocator, m_buffer->memory.allocation, offset, size);
vmaInvalidateAllocation(m_graphics->allocator, m_allocation, offset, size);
EXIT_NOT_IMPLEMENTED(static_cast<vk::Result>(result) != vk::Result::eSuccess);
}
}
vk::BufferMemoryBarrier Buffer::Barrier(uint64_t offset, uint64_t size, vk::AccessFlags source,
vk::AccessFlags destination) const {
if (Handle() == nullptr || size == 0 || offset > m_size || size > m_size - offset) {
if (Handle() == nullptr || size == 0 || offset > Size() || size > Size() - offset) {
EXIT("Buffer: invalid DMA barrier, handle=%p offset=0x%016" PRIx64 " size=0x%016" PRIx64
" capacity=0x%016" PRIx64 "\n",
static_cast<const void*>(Handle()), offset, size, m_size);
static_cast<const void*>(Handle()), offset, size, Size());
}
vk::BufferMemoryBarrier barrier {};
barrier.sType = vk::StructureType::eBufferMemoryBarrier;
barrier.srcAccessMask = source;
barrier.dstAccessMask = destination;
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
@@ -189,8 +159,8 @@ void Buffer::CopyFrom(CommandBuffer& command, const Buffer& source, uint64_t sou
uint64_t destination_offset, uint64_t size, vk::AccessFlags source_before,
vk::AccessFlags destination_before, vk::AccessFlags source_after,
vk::AccessFlags destination_after) {
if (size == 0 || source_offset > source.m_size || size > source.m_size - source_offset ||
destination_offset > m_size || size > m_size - destination_offset) {
if (size == 0 || source_offset > source.Size() || size > source.Size() - source_offset ||
destination_offset > Size() || size > Size() - destination_offset) {
EXIT("Buffer: invalid copy range\n");
}
if (source.Handle() == Handle() && source_offset < destination_offset + size &&
@@ -247,10 +217,8 @@ void Buffer::Fill(uint64_t offset, uint64_t size, uint32_t value) {
StreamBuffer::StreamBuffer(GraphicContext& graphics, CommandScheduler& scheduler, MemoryUsage usage,
uint64_t size)
: Buffer(graphics, scheduler, usage, 0, AllFlags, size) {
ReserveWatches(m_current_watches, WATCHES_INITIAL_RESERVE);
ReserveWatches(m_previous_watches, WATCHES_INITIAL_RESERVE);
}
: Buffer(graphics, scheduler, usage, 0, AllFlags, size),
m_current_watches(WATCHES_INITIAL_RESERVE), m_previous_watches(WATCHES_INITIAL_RESERVE) {}
bool StreamBuffer::NormalizeReservation(bool coherent, uint64_t atom, uint64_t& size,
uint64_t& alignment) {
@@ -315,10 +283,8 @@ std::pair<uint8_t*, uint64_t> StreamBuffer::Map(uint64_t size, uint64_t alignmen
}
void StreamBuffer::Commit() {
if (!IsCoherent() && Usage() != MemoryUsage::Download && m_mapped_size != 0) {
const auto result = vmaFlushAllocation(
Graphics().allocator, NativeBuffer().memory.allocation, m_offset, m_mapped_size);
EXIT_NOT_IMPLEMENTED(static_cast<vk::Result>(result) != vk::Result::eSuccess);
if (Usage() != MemoryUsage::Download && m_mapped_size != 0) {
Flush(m_offset, m_mapped_size);
}
m_offset += m_mapped_size;
@@ -328,7 +294,7 @@ void StreamBuffer::Commit() {
return;
}
if (m_current_watch_cursor + 1 >= m_current_watches.size()) {
ReserveWatches(m_current_watches, WATCHES_RESERVE_CHUNK);
m_current_watches.resize(m_current_watches.size() + WATCHES_RESERVE_CHUNK);
}
auto& watch = m_current_watches[m_current_watch_cursor++];
watch.upper_bound = m_offset;
@@ -344,10 +310,6 @@ uint64_t StreamBuffer::Copy(const void* source, uint64_t size, uint64_t alignmen
return offset;
}
void StreamBuffer::ReserveWatches(std::vector<Watch>& watches, size_t grow_size) {
watches.resize(watches.size() + grow_size);
}
bool StreamBuffer::WaitPendingOperations(const std::vector<Watch>& watches,
std::optional<size_t> invalidation_mark,
uint64_t requested_upper_bound, bool allow_wait,
+13 -12
View File
@@ -6,19 +6,19 @@
#include "graphics/host_gpu/vulkanCommon.h"
#include <cstdint>
#include <memory>
#include <optional>
#include <span>
#include <utility>
#include <vector>
VK_DEFINE_HANDLE(VmaAllocation)
namespace Libs::Graphics {
class CommandBuffer;
class CommandScheduler;
struct StreamBufferTestAccess;
struct GraphicContext;
struct VulkanBuffer;
enum class MemoryUsage : uint8_t {
DeviceLocal,
@@ -42,10 +42,10 @@ public:
~Buffer();
KYTY_CLASS_NO_COPY(Buffer);
[[nodiscard]] vk::Buffer Handle() const noexcept;
[[nodiscard]] vk::Buffer Handle() const noexcept { return m_buffer; }
[[nodiscard]] uint64_t Size() const noexcept { return m_size; }
[[nodiscard]] std::span<uint8_t> Mapped() const noexcept { return m_mapped; }
[[nodiscard]] bool IsCoherent() const noexcept { return m_is_coherent; }
[[nodiscard]] bool IsCoherent() const noexcept { return m_coherent; }
[[nodiscard]] MemoryUsage Usage() const noexcept { return m_usage; }
[[nodiscard]] uint64_t CpuAddress() const noexcept { return m_cpu_address; }
[[nodiscard]] vk::DeviceAddress BufferDeviceAddress() const noexcept;
@@ -53,7 +53,8 @@ public:
return address - m_cpu_address;
}
[[nodiscard]] bool IsInBounds(uint64_t address, uint64_t size) const noexcept;
void Write(uint64_t offset, const void* source, uint64_t size);
void IncreaseStreamScore(int score) noexcept { stream_score += score; }
[[nodiscard]] int StreamScore() const noexcept { return stream_score; }
void Flush(uint64_t offset, uint64_t size);
void Invalidate(uint64_t offset, uint64_t size);
void CopyFrom(CommandBuffer& command, const Buffer& source, uint64_t source_offset,
@@ -68,13 +69,13 @@ public:
void Fill(uint64_t offset, uint64_t size, uint32_t value);
// BufferCache state lives directly on the resource.
bool is_deleted = false;
size_t lru_id = 0;
bool is_deleted = false;
int stream_score = 0;
size_t lru_id = 0;
protected:
[[nodiscard]] GraphicContext& Graphics() const noexcept { return *m_graphics; }
[[nodiscard]] CommandScheduler& Scheduler() const noexcept { return *m_scheduler; }
[[nodiscard]] VulkanBuffer& NativeBuffer() noexcept { return *m_buffer; }
private:
[[nodiscard]] vk::BufferMemoryBarrier Barrier(uint64_t offset, uint64_t size,
@@ -85,11 +86,12 @@ private:
CommandScheduler* m_scheduler = nullptr;
MemoryUsage m_usage = MemoryUsage::DeviceLocal;
uint64_t m_cpu_address = 0;
uint64_t m_size = 0;
vk::DeviceAddress m_device_address = 0;
std::unique_ptr<VulkanBuffer> m_buffer;
vk::Buffer m_buffer = nullptr;
VmaAllocation m_allocation = nullptr;
uint64_t m_size;
bool m_coherent = false;
std::span<uint8_t> m_mapped;
bool m_is_coherent = false;
};
class StreamBuffer final: public Buffer {
@@ -110,7 +112,6 @@ private:
uint64_t upper_bound = 0;
};
void ReserveWatches(std::vector<Watch>& watches, size_t grow_size);
[[nodiscard]] static bool NormalizeReservation(bool coherent, uint64_t atom, uint64_t& size,
uint64_t& alignment);
[[nodiscard]] bool WaitPendingOperations(const std::vector<Watch>& watches,
File diff suppressed because it is too large. Load diff
+39 -33
View File
@@ -13,6 +13,7 @@
#include "graphics/host_gpu/renderer/image/tiler.h"
#include <map>
#include <type_traits>
#include <unordered_map>
#include <unordered_set>
#include <vector>
@@ -37,12 +38,6 @@ public:
BindingType type = BindingType::Texture;
};
struct RegionInfo {
bool image_pages = false;
bool image_bytes = false;
bool gpu_image_bytes = false;
};
TextureCache(GraphicContext& graphics, CommandScheduler& scheduler, PageManager& page_manager,
BufferCache& buffer_cache);
~TextureCache();
@@ -66,15 +61,11 @@ public:
uint32_t packed_clear);
void InvalidateMemory(uint64_t address, uint64_t size);
void InvalidateMemoryFromGPU(uint64_t address, uint64_t size);
[[nodiscard]] RegionInfo QueryRegion(uint64_t address, uint64_t size);
[[nodiscard]] bool IsRegionGpuModified(uint64_t address, uint64_t size);
[[nodiscard]] bool IsMeta(uint64_t address);
[[nodiscard]] bool IsMetaCleared(uint64_t address, uint32_t slice,
uint32_t* fill_value = nullptr);
[[nodiscard]] bool IsMetaCleared(uint64_t address, uint32_t slice);
[[nodiscard]] bool ClearMeta(uint64_t address);
// Returns true when registered DCC absorbed the fill and the caller may skip the dispatch.
// False may still record PendingDcc state, but the guest dispatch must execute.
[[nodiscard]] bool TryConsumeDccFill(uint64_t address, uint64_t size, uint32_t fill_value);
[[nodiscard]] bool TouchMeta(uint64_t address, uint32_t slice, bool is_clear);
void UnmapMemory(uint64_t address, uint64_t size);
@@ -83,21 +74,14 @@ public:
private:
enum class TransferDirection { Upload, Download };
struct ColorTransferPlan;
struct DownloadPlan;
struct TextureTransfer;
struct ImageDownload;
struct MetaDataInfo {
// A guest metadata-fill dispatch may initialize DCC before its render target is bound.
// PendingDcc retains that exact fill until FindRenderTarget classifies the address,
// without exposing an unconfirmed buffer address to the normal metadata heuristics.
// Keep all surface metadata in one entry so CMask/FMask can be
// registered beside HTile and DCC without introducing parallel tracking paths.
enum class Type : uint8_t { PendingDcc, CMask, FMask, HTile, Dcc };
enum class Type : uint8_t { CMask, FMask, HTile };
Type type = Type::PendingDcc;
uint32_t clear_mask = 0;
uint32_t fill_value = 0xffffffffu;
uint64_t fill_size = 0;
Type type;
uint32_t clear_mask = UINT32_MAX;
};
struct OverlapResult {
@@ -109,6 +93,23 @@ private:
using ImageIds = InlinePageOwnerList<ImageId, 16>;
using ImagePageTable = MultiLevelPageTable<ImageIds, 20, 40, 10>;
// Callers have validated the nonempty 40-bit range with TryGetPageRange.
template <typename Func>
static void ForEachPage(uint64_t address, size_t size, Func&& func) {
using FuncReturn = typename std::invoke_result<Func, uint64_t>::type;
static constexpr bool RETURNS_BOOL = std::is_same_v<FuncReturn, bool>;
const uint64_t page_end = (address + size - 1) >> ImagePageTable::kPageBits;
for (uint64_t page = address >> ImagePageTable::kPageBits; page <= page_end; ++page) {
if constexpr (RETURNS_BOOL) {
if (func(page)) {
break;
}
} else {
func(page);
}
}
}
[[nodiscard]] ImageId InsertImage(const ImageInfo& info);
[[nodiscard]] ImageId GetNullImage(const ImageDesc& desc);
void RegisterImage(ImageId id);
@@ -137,16 +138,21 @@ private:
[[nodiscard]] ImageId ResolveDepthOverlap(const ImageInfo& requested, BindingType binding,
ImageId cached);
[[nodiscard]] ImageId ExpandImage(const ImageInfo& info, ImageId source);
void RefreshImage(ImageId id, const ImageDesc& desc);
void InitializeImage(ImageId id, const ImageDesc& desc);
[[nodiscard]] ColorTransferPlan BuildColorTransfer(const Image& image, BindingType binding,
TransferDirection direction) const;
[[nodiscard]] DownloadPlan BuildDownload(const Image& image) const;
void UploadImage(Image& image, const ImageDesc& desc, Buffer& source, uint64_t source_offset);
void DownloadImageData(Image& image, Buffer& destination, uint64_t destination_offset,
uint64_t destination_size, DownloadPlan plan);
void RefreshImage(ImageId id);
void MaterializeDccClear(ImageId id, const ImageDesc& desc,
uint32_t metadata_base_layer);
void InitializeImage(ImageId id);
[[nodiscard]] TextureTransfer
BuildTextureTransfer(const Image& image, BindingType binding, TransferDirection direction) const;
[[nodiscard]] ImageDownload BuildDownload(const Image& image) const;
void UploadImage(Image& image, Buffer& source, uint64_t source_offset);
void DownloadImage(Image& image, Buffer& destination, uint64_t destination_offset,
uint64_t destination_size, ImageDownload transfer);
void DownloadDepth(Image& image, Buffer& destination, uint64_t destination_offset);
void CommitGpuWrite(Image& image);
// Caller holds m_lock. Volume layer ranges select depth slices.
void ClearImage(CommandBuffer& command, ImageId id, vk::Format format,
const vk::ImageSubresourceRange& range, const vk::ClearValue& clear);
void PrepareImageCopy(Image& image);
void RefreshCopySource(ImageId id);
[[nodiscard]] bool CopyD16(Image& destination, Image& source);
@@ -156,7 +162,7 @@ private:
void ValidateImageDesc(const ImageDesc& desc) const;
void InvalidateCpuAliases(uint64_t address, uint64_t size);
[[nodiscard]] bool TryDownloadImage(ImageId id);
[[nodiscard]] bool DownloadImageMemory(ImageId id);
GraphicContext& m_graphics;
CommandScheduler& m_scheduler;
@@ -4,6 +4,7 @@
#include "common/logging/log.h"
#include "common/profiler.h"
#include "graphics/guest_gpu/gpu_defs.h"
#include "graphics/guest_gpu/gpu_format.h"
#include "graphics/guest_gpu/hardwareContext.h"
#include "graphics/guest_gpu/tile.h"
#include "graphics/host_gpu/graphicContext.h"
@@ -21,47 +22,40 @@ namespace Libs::Graphics {
static std::atomic<uint32_t> g_render_color_log_count = 0;
static void ResolveDccClearInfo(RenderColorInfo& info, vk::Format format, bool has_dcc,
uint32_t packed_clear) {
// Register-backed DCC clears use the target's packed clear value. Decode one-word guest
// formats here; unsupported encodings remain tracked without unsafe materialization.
info.metadata_clear_supported =
has_dcc && DecodePackedColorClear(format, packed_clear, info.color_clear_value);
switch (format) {
case vk::Format::eR8G8B8A8Unorm:
case vk::Format::eR8G8B8A8Srgb:
case vk::Format::eB8G8R8A8Unorm:
case vk::Format::eB8G8R8A8Srgb:
case vk::Format::eA2B10G10R10UnormPack32:
case vk::Format::eA2R10G10B10UnormPack32:
info.metadata_fixed_clear_supported = has_dcc;
break;
default: info.metadata_fixed_clear_supported = false; break;
static bool DccAlphaOnMsb(const HW::ColorInfo& info) {
switch (info.format) {
case Prospero::ChannelLayout::k10_10_10_2:
case Prospero::ChannelLayout::k10_10_10_2Float:
case Prospero::ChannelLayout::k5_5_5_1: return true;
case Prospero::ChannelLayout::k2_10_10_10:
case Prospero::ChannelLayout::k1_5_5_5: return false;
default: break;
}
if (!info.metadata_clear_supported) {
info.color_clear_value = {};
const auto components =
Prospero::ResolveRenderTargetFormat(info.format, info.channel_type).components;
if (components == 1) {
return info.channel_order != Prospero::ChannelOrder::kStandard;
}
return components == 3 || info.channel_order == Prospero::ChannelOrder::kStandard ||
info.channel_order == Prospero::ChannelOrder::kAlt;
}
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, CommandBuffer& buffer,
RenderColorInfo& r,
void RenderExecutor::ResolveRenderColorTarget(CommandBuffer& buffer, RenderColorInfo& r,
uint32_t render_target_slice_offset,
uint32_t render_target_slot, bool ignore_target_mask,
uint32_t rt_slot, bool ignore_target_mask,
bool exact_format) {
KYTY_PROFILER_FUNCTION();
const auto& hw = buffer.GetRegisters();
const auto rt_slot = (render_target_slot == UINT32_MAX ? render_target_first_bound_slot(buffer)
: render_target_slot);
const auto& rt = hw.GetRenderTarget(rt_slot);
auto mask = render_target_mask_slot(hw.GetRenderTargetMask(), rt_slot);
if (ignore_target_mask && rt.base.addr != 0 && mask == 0) {
mask = 0x0f;
}
r.target_slot = rt_slot;
r.export_mapping = {};
r = {};
r.target_slot = rt_slot;
if (rt.base.addr == 0 || mask == 0) {
if (graphics_debug_dump_enabled()) {
@@ -76,23 +70,6 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, CommandBuffer&
}
}
// No color output
r.type = RenderColorType::NoColorOutput;
r.desc = {};
r.base_addr = 0;
r.image_id = {};
r.image_view = nullptr;
r.format = vk::Format::eUndefined;
r.extent = {};
r.base_mip_level = 0;
r.base_array_layer = 0;
r.buffer_size = 0;
r.samples = 1;
r.export_mapping = {};
r.color_clear_enable = false;
r.metadata_clear_supported = false;
r.metadata_fixed_clear_supported = false;
r.color_clear_value = {};
return;
}
const auto samples = render_sample_count(rt.attrib.num_fragments);
@@ -100,50 +77,11 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, CommandBuffer&
EXIT("unsupported render-target sample configuration: samples=%u fragments=%u\n",
rt.attrib.num_samples, rt.attrib.num_fragments);
}
const auto view = ResolveTargetViewInfo(
rt.view.base_array_slice_index, rt.view.last_array_slice_index, render_target_slice_offset);
switch (view.type) {
case TargetViewType::Image2D:
case TargetViewType::Image2DArray: break;
case TargetViewType::Unsupported:
EXIT("invalid render-target view: base=%u last=%u draw_offset=%u\n",
rt.view.base_array_slice_index, rt.view.last_array_slice_index,
render_target_slice_offset);
}
r.base_array_layer = view.base_layer;
const uint32_t levels = rt.attrib2.num_mip_levels + 1u;
if (levels == 0 || levels > 16 || rt.view.current_mip_level >= levels) {
EXIT("unsupported render-target mip range: current=%u levels=%u\n",
rt.view.current_mip_level, levels);
}
if (graphics_debug_dump_enabled()) {
static std::atomic_uint log_count = 0;
const auto log_id = log_count.fetch_add(1, std::memory_order_relaxed);
if (log_id < 128) {
LOGF("RenderColorTarget: inspect slot=%" PRIu32 " base=0x%010" PRIx64
" mask=0x%01" PRIx32 " attrib2_width=%" PRIu32 " attrib2_height=%" PRIu32
" attrib3_tile=0x%08" PRIx32 " attrib3_dim=0x%08" PRIx32 " fmt=0x%08" PRIx32
" nfmt=0x%08" PRIx32 " order=0x%08" PRIx32 "\n",
rt_slot, rt.base.addr, mask, rt.attrib2.width, rt.attrib2.height,
static_cast<uint32_t>(rt.attrib3.tile_mode), rt.attrib3.dimension,
static_cast<uint32_t>(rt.info.format), static_cast<uint32_t>(rt.info.channel_type),
static_cast<uint32_t>(rt.info.channel_order));
}
}
// Color-control state selects the color-buffer operation and logical blend operation.
// The normal copy operation is a regular color write, not an attachment clear.
// Nonlinear clear values are still stored as normalized components.
// Fast color clears are metadata driven and must be handled explicitly when
// that metadata path is implemented; render-pass load must preserve contents.
r.color_clear_enable = false;
r.color_clear_value = {};
uint32_t width = 0;
uint32_t height = 0;
uint32_t pitch = 0;
uint64_t size = 0;
bool tile = false;
static constexpr std::array image_types {Prospero::ImageType::kColor1D,
Prospero::ImageType::kColor2D,
Prospero::ImageType::kColor3D};
@@ -165,7 +103,48 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, CommandBuffer&
if (volume && samples != 1) {
EXIT("multisampled 3D render targets are unsupported\n");
}
const uint32_t depth = volume ? rt.attrib3.depth + 1u : 1u;
const uint32_t depth = volume ? rt.attrib3.depth + 1u : 1u;
// For volumes, CB_COLOR_VIEW bounds exported slices; ATTRIB3 defines storage depth.
// The host attachment contains only the selected slices that exist in this mip.
const uint32_t last_layer = volume
? std::min(rt.view.last_array_slice_index,
std::max(depth >> rt.view.current_mip_level, 1u) - 1u)
: rt.view.last_array_slice_index;
const auto view = ResolveTargetViewInfo(
rt.view.base_array_slice_index, last_layer, render_target_slice_offset);
switch (view.type) {
case TargetViewType::Image2D:
case TargetViewType::Image2DArray: break;
case TargetViewType::Unsupported:
EXIT("invalid render-target view: base=%u last=%u draw_offset=%u\n",
rt.view.base_array_slice_index, rt.view.last_array_slice_index,
render_target_slice_offset);
}
if (graphics_debug_dump_enabled()) {
static std::atomic_uint log_count = 0;
const auto log_id = log_count.fetch_add(1, std::memory_order_relaxed);
if (log_id < 128) {
LOGF("RenderColorTarget: inspect slot=%" PRIu32 " base=0x%010" PRIx64
" mask=0x%01" PRIx32 " attrib2_width=%" PRIu32 " attrib2_height=%" PRIu32
" attrib3_tile=0x%08" PRIx32 " attrib3_dim=0x%08" PRIx32 " fmt=0x%08" PRIx32
" nfmt=0x%08" PRIx32 " order=0x%08" PRIx32 "\n",
rt_slot, rt.base.addr, mask, rt.attrib2.width, rt.attrib2.height,
static_cast<uint32_t>(rt.attrib3.tile_mode), rt.attrib3.dimension,
static_cast<uint32_t>(rt.info.format), static_cast<uint32_t>(rt.info.channel_type),
static_cast<uint32_t>(rt.info.channel_order));
}
}
// Color-control state selects the color-buffer operation and logical blend operation.
// The normal copy operation is a regular color write, not an attachment clear.
// Nonlinear clear values are still stored as normalized components.
// Fast color clears are metadata driven and must be handled explicitly when
// that metadata path is implemented; render-pass load must preserve contents.
uint32_t width = 0;
uint32_t height = 0;
uint32_t pitch = 0;
uint64_t size = 0;
bool tile = false;
const bool standard4 = rt.attrib3.tile_mode == Prospero::TileMode::kStandard4KB;
const bool standard64 = rt.attrib3.tile_mode == Prospero::TileMode::kStandard64KB;
const bool depth_tile = rt.attrib3.tile_mode == Prospero::TileMode::kDepth;
@@ -224,19 +203,14 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, CommandBuffer&
" layer=%u/%u\n",
rt.attrib3.dimension, rt.attrib3.depth, view.base_layer, view.image_layers);
}
if (tile) {
if (volume) {
pitch = TileGetTexturePitch(transfer_format, width, rt.attrib3.tile_mode);
} else if (texture_tile) {
pitch = TileGetTexturePitch(transfer_format, width, rt.attrib3.tile_mode);
} else {
pitch = TileGetRenderTargetPitch(width, bytes_per_element, rt.attrib.num_fragments);
}
if (pitch == 0) {
EXIT("unsupported render-target pitch: width=%u bytes=%u\n", width, bytes_per_element);
}
// PPSA28068: the linear EULA target and its sampled view must share the padded pitch.
if (!tile || volume || texture_tile) {
pitch = TileGetTexturePitch(transfer_format, width, rt.attrib3.tile_mode);
} else {
pitch = width;
pitch = TileGetRenderTargetPitch(width, bytes_per_element, rt.attrib.num_fragments);
}
if (pitch == 0) {
EXIT("unsupported render-target pitch: width=%u bytes=%u\n", width, bytes_per_element);
}
TileSizeOffset mip_sizes[16] {};
@@ -305,12 +279,6 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, CommandBuffer&
const vk::Extent2D view_extent = {std::max(width >> rt.view.current_mip_level, 1u),
std::max(height >> rt.view.current_mip_level, 1u)};
const uint32_t view_depth = std::max(depth >> rt.view.current_mip_level, 1u);
if (volume &&
(view.base_layer >= view_depth || view.layer_count > view_depth - view.base_layer)) {
EXIT("3D render-target view exceeds mip depth: base=%u count=%u depth=%u mip=%u\n",
view.base_layer, view.layer_count, view_depth, rt.view.current_mip_level);
}
auto decision_log_id = g_render_color_log_count.fetch_add(1);
if (decision_log_id < 128) {
@@ -337,10 +305,14 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, CommandBuffer&
desc.info.tile_mode = rt.attrib3.tile_mode;
const bool has_dcc = rt.info.dcc_compression_enable && rt.dcc_addr.addr != 0;
if (has_dcc) {
// DCC uses a separate metadata allocation. Carry its address through ImageInfo so
// TextureCache can associate metadata fills observed before target registration.
desc.info.metadata.kind = ImageMetadataKind::Dcc;
desc.info.metadata.range.address = rt.dcc_addr.addr;
TileSizeAlign metadata_size {};
(void)TileGetDccSize(width, height, volume ? depth : view.image_layers, bytes_per_element,
levels, rt.attrib3.tile_mode, metadata_size, rt.attrib.num_fragments);
desc.info.metadata.kind = ImageMetadataKind::Dcc;
desc.info.metadata.range = {rt.dcc_addr.addr, metadata_size.size};
desc.info.metadata.dcc_clear_word = rt.clear_word0.word0;
desc.info.metadata.dcc_clear_register_valid = true;
desc.info.metadata.dcc_alpha_msb = DccAlphaOnMsb(rt.info);
}
for (uint32_t level = 0; level < levels; level++) {
if (volume) {
@@ -382,18 +354,10 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, CommandBuffer&
desc.view_info.usage = vk::ImageUsageFlagBits::eColorAttachment;
auto& texture_cache = m_context.GetTextureCache();
r.desc = std::move(desc);
r.guest_mip_level = rt.view.current_mip_level;
r.guest_array_layer = view.base_layer;
r.image_id = texture_cache.FindImage(r.desc, exact_format);
r.type = RenderColorType::RenderTexture;
r.base_addr = rt.base.addr;
r.image_view = nullptr;
r.format = r.desc.view_info.format;
r.extent = view_extent;
r.base_mip_level = rt.view.current_mip_level;
r.buffer_size = backing_size;
r.samples = samples;
r.export_mapping = target_format.export_mapping;
r.color_clear_enable = false;
ResolveDccClearInfo(r, target_format.format, has_dcc, rt.clear_word0.word0);
BindRenderTarget(r.image_id);
}
@@ -6,33 +6,24 @@
#include "graphics/host_gpu/renderer/renderTarget.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include <algorithm>
#include <cstdint>
namespace Libs::Graphics {
enum class RenderColorType {
NoColorOutput,
RenderTexture,
};
struct RenderColorInfo {
RenderColorType type = RenderColorType::NoColorOutput;
// Discovery keeps guest image information but can remap the view into a larger cache image.
TextureCache::ImageDesc desc;
ImageId image_id;
vk::ImageView image_view = nullptr;
vk::Format format = vk::Format::eUndefined;
vk::Extent2D extent = {};
uint32_t base_mip_level = 0;
uint32_t base_array_layer = 0;
uint64_t base_addr = 0;
uint64_t buffer_size = 0;
uint32_t target_slot = 0;
uint32_t samples = 1;
uint32_t guest_mip_level = 0;
uint32_t guest_array_layer = 0;
Prospero::ColorComponentMapping export_mapping;
bool color_clear_enable = false;
bool metadata_clear_supported = false;
bool metadata_fixed_clear_supported = false;
vk::ClearColorValue color_clear_value {};
[[nodiscard]] vk::Extent2D Extent() const {
return {std::max(desc.info.extent.width >> guest_mip_level, 1u),
std::max(desc.info.extent.height >> guest_mip_level, 1u)};
}
};
} // namespace Libs::Graphics
@@ -19,11 +19,11 @@ void ReportVulkanFatal(const char* what, vk::Result result, uint64_t tick, uint3
uint32_t arg3, uint64_t arg4) {
LOGF("%s failed: %s (%d), tick=%" PRIu64 " debug_op=%u debug_submit=%" PRIu64
" args=%u,%u,%u,%u,0x%016" PRIx64 "\n",
what, VulkanToString(result).c_str(), static_cast<int>(result), tick, debug_op,
what, vk::to_string(result).c_str(), static_cast<int>(result), tick, debug_op,
debug_submit, arg0, arg1, arg2, arg3, arg4);
std::printf("%s failed: %s (%d), tick=%" PRIu64 " debug_op=%u debug_submit=%" PRIu64
" args=%u,%u,%u,%u,0x%016" PRIx64 "\n",
what, VulkanToString(result).c_str(), static_cast<int>(result), tick, debug_op,
what, vk::to_string(result).c_str(), static_cast<int>(result), tick, debug_op,
debug_submit, arg0, arg1, arg2, arg3, arg4);
std::fflush(stdout);
}
@@ -34,7 +34,6 @@ CommandScheduler::CommandPool::CommandPool(GraphicContext& graphics, MasterSemap
: m_graphics(graphics), m_master(master) {
EXIT_IF(graphics.queue_family == static_cast<uint32_t>(-1));
vk::CommandPoolCreateInfo create {};
create.sType = vk::StructureType::eCommandPoolCreateInfo;
create.queueFamilyIndex = graphics.queue_family;
create.flags = vk::CommandPoolCreateFlagBits::eTransient |
vk::CommandPoolCreateFlagBits::eResetCommandBuffer;
@@ -52,7 +51,6 @@ size_t CommandScheduler::CommandPool::Grow() {
m_buffers.resize(first + GrowStep);
vk::CommandBufferAllocateInfo allocate {};
allocate.sType = vk::StructureType::eCommandBufferAllocateInfo;
allocate.commandPool = m_pool;
allocate.level = vk::CommandBufferLevel::ePrimary;
allocate.commandBufferCount = static_cast<uint32_t>(GrowStep);
@@ -87,10 +85,7 @@ vk::CommandBuffer CommandScheduler::CommandPool::Commit() {
m_ticks[*found] = m_master.CurrentTick();
}
m_hint = *found + 1;
if (m_hint == m_ticks.size()) {
m_hint = 0;
}
m_hint = (*found + 1) % m_ticks.size();
return m_buffers[*found];
}
@@ -153,14 +148,10 @@ void CommandScheduler::Begin(HW::Context& registers, HW::UserConfig& user_config
std::lock_guard lock(m_operation_mutex);
EXIT_IF(m_operation_state != OperationState::Open);
}
m_registers = &registers;
m_user_config = &user_config;
m_shaders = &shaders;
m_command.Bind(registers, user_config, shaders);
if (m_command.IsInvalid()) {
BeginNext();
} else {
BindCurrent();
}
}
@@ -217,13 +208,7 @@ void CommandScheduler::Wait(uint64_t tick) {
}
void CommandScheduler::PopPendingOperations() {
PopPendingOperations(true);
}
void CommandScheduler::PopPendingOperations(bool refresh_gpu_tick) {
if (refresh_gpu_tick) {
m_master.Refresh();
}
m_master.Refresh();
for (;;) {
PendingOperation operation;
{
@@ -351,11 +336,6 @@ CommandBuffer& CommandScheduler::Current() {
return m_command;
}
void CommandScheduler::BindCurrent() {
EXIT_IF(m_registers == nullptr || m_user_config == nullptr || m_shaders == nullptr);
m_command.Bind(*m_registers, *m_user_config, *m_shaders);
}
CommandBuffer& CommandScheduler::BeginCommand() {
EXIT_IF(!m_command.IsInvalid());
m_command.m_buffer = m_command_pool.Commit();
@@ -381,14 +361,12 @@ uint64_t CommandScheduler::Submit(SubmitInfo submit) {
submit.AddSignal(m_master.Handle(), tick);
vk::TimelineSemaphoreSubmitInfo timeline_info {};
timeline_info.sType = vk::StructureType::eTimelineSemaphoreSubmitInfo;
timeline_info.waitSemaphoreValueCount = submit.num_wait_semaphores;
timeline_info.pWaitSemaphoreValues = submit.wait_ticks.data();
timeline_info.signalSemaphoreValueCount = submit.num_signal_semaphores;
timeline_info.pSignalSemaphoreValues = submit.signal_ticks.data();
vk::SubmitInfo submit_info {};
submit_info.sType = vk::StructureType::eSubmitInfo;
submit_info.pNext = &timeline_info;
submit_info.waitSemaphoreCount = submit.num_wait_semaphores;
submit_info.pWaitSemaphores = submit.wait_semaphores.data();
@@ -414,8 +392,7 @@ uint64_t CommandScheduler::Submit(SubmitInfo submit) {
}
void CommandScheduler::BeginNext() {
EXIT_IF(!m_command.IsInvalid());
BindCurrent();
CheckActive();
BeginCommand();
}
@@ -42,7 +42,7 @@ public:
void DeferPriorityOperation(Common::UniqueFunction<void>&& operation);
[[nodiscard]] static bool InDeferredOperation() noexcept;
[[nodiscard]] bool Active() const noexcept { return m_registers != nullptr; }
[[nodiscard]] bool Active() const noexcept { return m_command.m_registers != nullptr; }
void CheckActive() const;
CommandBuffer& Current();
[[nodiscard]] uint64_t CurrentTick() const noexcept { return m_master.CurrentTick(); }
@@ -80,9 +80,7 @@ private:
uint64_t tick = 0;
};
void BindCurrent();
void BeginNext();
void PopPendingOperations(bool refresh_gpu_tick);
void PriorityOperationsThread(std::stop_token stop);
void RunOperation(Common::UniqueFunction<void>&& operation);
@@ -99,9 +97,6 @@ private:
bool m_priority_active = false;
uint64_t m_priority_active_tick = 0;
OperationState m_operation_state = OperationState::Open;
HW::Context* m_registers = nullptr;
HW::UserConfig* m_user_config = nullptr;
HW::Shader* m_shaders = nullptr;
};
} // namespace Libs::Graphics
+3 -11
View File
@@ -9,7 +9,6 @@
#include "graphics/host_gpu/renderer/image/imageView.h"
#include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/host_gpu/vma.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include <algorithm>
@@ -34,10 +33,7 @@ void CommandBuffer::Begin() {
auto buffer = Handle();
vk::CommandBufferBeginInfo begin_info {};
begin_info.sType = vk::StructureType::eCommandBufferBeginInfo;
begin_info.pNext = nullptr;
begin_info.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit;
begin_info.pInheritanceInfo = nullptr;
begin_info.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit;
auto result = buffer.begin(&begin_info);
@@ -65,17 +61,16 @@ void CommandBuffer::SetDebugInfo(uint32_t op, uint64_t submit_id, uint32_t arg0,
}
void CommandBuffer::BeginRendering(const RenderState& state) const {
EXIT_IF(state.width == 0 || state.height == 0 || state.num_layers == 0 ||
state.num_color_attachments > RENDER_COLOR_ATTACHMENTS_MAX);
if (m_rendering && m_render_state == state) {
return;
}
EXIT_IF(state.width == 0 || state.height == 0 || state.num_layers == 0 ||
state.num_color_attachments > RENDER_COLOR_ATTACHMENTS_MAX);
EndRendering();
std::array<vk::RenderingAttachmentInfo, RENDER_COLOR_ATTACHMENTS_MAX> colors {};
for (uint32_t i = 0; i < state.num_color_attachments; i++) {
const auto& attachment = state.color_attachments[i];
colors[i].sType = vk::StructureType::eRenderingAttachmentInfo;
colors[i].imageView = attachment.image_view;
colors[i].imageLayout = attachment.image_layout;
colors[i].loadOp =
@@ -86,7 +81,6 @@ void CommandBuffer::BeginRendering(const RenderState& state) const {
const auto& depth_stencil = state.depth_stencil_attachment;
vk::RenderingAttachmentInfo depth {};
depth.sType = vk::StructureType::eRenderingAttachmentInfo;
depth.imageView = depth_stencil.image_view;
depth.imageLayout = depth_stencil.image_layout;
depth.loadOp =
@@ -95,7 +89,6 @@ void CommandBuffer::BeginRendering(const RenderState& state) const {
depth.clearValue.depthStencil.depth = std::bit_cast<float>(depth_stencil.clear_value[0]);
vk::RenderingAttachmentInfo stencil {};
stencil.sType = vk::StructureType::eRenderingAttachmentInfo;
stencil.imageView = depth_stencil.image_view;
stencil.imageLayout = depth_stencil.image_layout;
stencil.loadOp =
@@ -104,7 +97,6 @@ void CommandBuffer::BeginRendering(const RenderState& state) const {
stencil.clearValue.depthStencil.stencil = depth_stencil.clear_value[1];
vk::RenderingInfo rendering {};
rendering.sType = vk::StructureType::eRenderingInfo;
rendering.renderArea.extent = {state.width, state.height};
rendering.layerCount = state.num_layers;
rendering.colorAttachmentCount = state.num_color_attachments;
+94 -279
View File
@@ -4,7 +4,6 @@
#include "common/common.h"
#include "common/emulatorConfig.h"
#include "common/logging/log.h"
#include "common/stringUtils.h"
#include "graphics/guest_gpu/gpu_defs.h"
#include "graphics/guest_gpu/hardwareContext.h"
#include "graphics/host_gpu/renderer/render.h"
@@ -17,8 +16,6 @@
namespace Libs::Graphics {
static std::atomic<uint32_t> g_scissor_default_log_count = 0;
uint32_t render_target_mask_slot(uint32_t mask, uint32_t slot) {
return (mask >> (slot * 4u)) & 0x0fu;
}
@@ -59,7 +56,7 @@ uint32_t render_target_first_bound_slot(const CommandBuffer& buffer) {
bool graphics_debug_dump_enabled() {
return Config::GraphicsDebugDumpEnabled() &&
Config::GetPrintfDirection() != Config::OutputDirection::Silent;
Config::GetPrintfDirection() != Config::LogDirection::Silent;
}
void uc_print(const char* func, const HW::UserConfig& uc) {
@@ -91,91 +88,75 @@ void uc_check(const HW::UserConfig& uc) {
EXIT_NOT_IMPLEMENTED(user_en.vgpr3 != false);
}
void sh_print(const char* func, const HW::Shader& /*uc*/) {
LOGF("%s\n", func);
}
std::string rt_print(const char* func, const HW::RenderTarget& rt) {
std::string dst;
dst.reserve(4096);
std::vector<std::string> rt_print(const char* func, const HW::RenderTarget& rt) {
std::vector<std::string> dst;
dst.reserve(40);
dst += fmt::format("{}\n", func);
dst.push_back(fmt::format("{}\n", func));
dst += fmt::format("\t base.addr = 0x{:016x}\n", rt.base.addr);
dst += fmt::format("\t view.base_array_slice_index = 0x{:08x}\n",
rt.view.base_array_slice_index);
dst += fmt::format("\t view.last_array_slice_index = 0x{:08x}\n",
rt.view.last_array_slice_index);
dst += fmt::format("\t view.current_mip_level = 0x{:08x}\n", rt.view.current_mip_level);
dst += fmt::format("\t info.fmask_compression_enable = {}\n",
rt.info.fmask_compression_enable ? "true" : "false");
dst.push_back(fmt::format("\t base.addr = 0x{:016x}\n", rt.base.addr));
dst.push_back(fmt::format("\t view.base_array_slice_index = 0x{:08x}\n",
rt.view.base_array_slice_index));
dst.push_back(fmt::format("\t view.last_array_slice_index = 0x{:08x}\n",
rt.view.last_array_slice_index));
dst.push_back(
fmt::format("\t view.current_mip_level = 0x{:08x}\n", rt.view.current_mip_level));
dst.push_back(fmt::format("\t info.fmask_compression_enable = {}\n",
rt.info.fmask_compression_enable ? "true" : "false"));
dst += fmt::format("\t info.fmask_data_compression_disable = {}\n",
rt.info.fmask_data_compression_disable ? "true" : "false");
dst += fmt::format("\t info.fmask_one_frag_mode = {}\n",
rt.info.fmask_one_frag_mode ? "true" : "false");
// dst.push_back(fmt::format("\t info.fmask_compression_mode = 0x{:08x}\n", //
// rt.info.fmask_compression_mode));
dst.push_back(fmt::format("\t info.fmask_data_compression_disable = {}\n",
rt.info.fmask_data_compression_disable ? "true" : "false"));
dst.push_back(fmt::format("\t info.fmask_one_frag_mode = {}\n",
rt.info.fmask_one_frag_mode ? "true" : "false"));
dst.push_back(fmt::format("\t info.cmask_fast_clear_enable = {}\n",
rt.info.cmask_fast_clear_enable ? "true" : "false"));
dst.push_back(fmt::format("\t info.dcc_compression_enable = {}\n",
rt.info.dcc_compression_enable ? "true" : "false"));
dst.push_back(fmt::format("\t info.format = 0x{:08x}\n",
static_cast<uint32_t>(rt.info.format)));
dst.push_back(fmt::format("\t info.channel_type = 0x{:08x}\n",
static_cast<uint32_t>(rt.info.channel_type)));
dst.push_back(fmt::format("\t info.channel_order = 0x{:08x}\n",
static_cast<uint32_t>(rt.info.channel_order)));
dst.push_back(fmt::format("\t info.blend_bypa = {}\n",
rt.info.blend_bypass ? "true" : "false"));
dst.push_back(fmt::format("\t info.blend_clamp = {}\n",
rt.info.blend_clamp ? "true" : "false"));
dst.push_back(fmt::format("\t info.round_mode = {}\n",
rt.info.round_mode ? "true" : "false"));
dst.push_back(fmt::format("\t attrib.force_dest_alpha_to_one = {}\n",
rt.attrib.force_dest_alpha_to_one ? "true" : "false"));
dst.push_back(
fmt::format("\t attrib.num_samples = 0x{:08x}\n", rt.attrib.num_samples));
dst.push_back(
fmt::format("\t attrib.num_fragments = 0x{:08x}\n", rt.attrib.num_fragments));
dst.push_back(fmt::format("\t attrib2.width = 0x{:08x}\n", rt.attrib2.width));
dst.push_back(
fmt::format("\t attrib2.height = 0x{:08x}\n", rt.attrib2.height));
dst.push_back(
fmt::format("\t attrib2.num_mip_levels = 0x{:08x}\n", rt.attrib2.num_mip_levels));
dst.push_back(fmt::format("\t attrib3.depth = 0x{:08x}\n", rt.attrib3.depth));
dst.push_back(fmt::format("\t attrib3.tile_mode = 0x{:08x}\n",
static_cast<uint32_t>(rt.attrib3.tile_mode)));
dst.push_back(
fmt::format("\t attrib3.dimension = 0x{:08x}\n", rt.attrib3.dimension));
dst.push_back(fmt::format("\t attrib3.metadata_pipe_aligned = {}\n",
rt.attrib3.metadata_pipe_aligned ? "true" : "false"));
dst.push_back(fmt::format("\t attrib3.write_vrs_rate_hint_to_cmask = {}\n",
rt.attrib3.write_vrs_rate_hint_to_cmask ? "true" : "false"));
dst.push_back(fmt::format("\t dcc.max_uncompressed_block_size = 0x{:08x}\n",
rt.dcc.max_uncompressed_block_size));
dst.push_back(fmt::format("\t dcc.max_compressed_block_size = 0x{:08x}\n",
rt.dcc.max_compressed_block_size));
dst.push_back(
fmt::format("\t dcc.color_transform = 0x{:08x}\n", rt.dcc.color_transform));
dst.push_back(fmt::format("\t dcc.overwrite_combiner_disable = {}\n",
rt.dcc.overwrite_combiner_disable ? "true" : "false"));
dst.push_back(fmt::format("\t dcc.independent_block_size = 0x{:02x}\n",
static_cast<uint8_t>(rt.dcc.independent_block_size)));
dst.push_back(fmt::format("\t data_write_on_dcc_clear_to_reg = {}\n",
rt.dcc.data_write_on_dcc_clear_to_reg ? "true" : "false"));
dst.push_back(fmt::format("\t dcc.dcc_clear_key_enable = {}\n",
rt.dcc.dcc_clear_key_enable ? "true" : "false"));
dst.push_back(fmt::format("\t cmask.addr = 0x{:016x}\n", rt.cmask.addr));
dst.push_back(fmt::format("\t fmask.addr = 0x{:016x}\n", rt.fmask.addr));
dst.push_back(
fmt::format("\t clear_word0.word0 = 0x{:08x}\n", rt.clear_word0.word0));
dst.push_back(
fmt::format("\t clear_word1.word1 = 0x{:08x}\n", rt.clear_word1.word1));
dst.push_back(
fmt::format("\t dcc_addr.addr = 0x{:016x}\n", rt.dcc_addr.addr));
dst += fmt::format("\t info.cmask_fast_clear_enable = {}\n",
rt.info.cmask_fast_clear_enable ? "true" : "false");
dst += fmt::format("\t info.dcc_compression_enable = {}\n",
rt.info.dcc_compression_enable ? "true" : "false");
dst += fmt::format("\t info.format = 0x{:08x}\n",
static_cast<uint32_t>(rt.info.format));
dst += fmt::format("\t info.channel_type = 0x{:08x}\n",
static_cast<uint32_t>(rt.info.channel_type));
dst += fmt::format("\t info.channel_order = 0x{:08x}\n",
static_cast<uint32_t>(rt.info.channel_order));
dst += fmt::format("\t info.blend_bypa = {}\n",
rt.info.blend_bypass ? "true" : "false");
dst += fmt::format("\t info.blend_clamp = {}\n",
rt.info.blend_clamp ? "true" : "false");
dst += fmt::format("\t info.round_mode = {}\n",
rt.info.round_mode ? "true" : "false");
dst += fmt::format("\t attrib.force_dest_alpha_to_one = {}\n",
rt.attrib.force_dest_alpha_to_one ? "true" : "false");
dst += fmt::format("\t attrib.num_samples = 0x{:08x}\n", rt.attrib.num_samples);
dst += fmt::format("\t attrib.num_fragments = 0x{:08x}\n", rt.attrib.num_fragments);
dst += fmt::format("\t attrib2.width = 0x{:08x}\n", rt.attrib2.width);
dst += fmt::format("\t attrib2.height = 0x{:08x}\n", rt.attrib2.height);
dst += fmt::format("\t attrib2.num_mip_levels = 0x{:08x}\n", rt.attrib2.num_mip_levels);
dst += fmt::format("\t attrib3.depth = 0x{:08x}\n", rt.attrib3.depth);
dst += fmt::format("\t attrib3.tile_mode = 0x{:08x}\n",
static_cast<uint32_t>(rt.attrib3.tile_mode));
dst += fmt::format("\t attrib3.dimension = 0x{:08x}\n", rt.attrib3.dimension);
dst += fmt::format("\t attrib3.metadata_pipe_aligned = {}\n",
rt.attrib3.metadata_pipe_aligned ? "true" : "false");
dst += fmt::format("\t attrib3.write_vrs_rate_hint_to_cmask = {}\n",
rt.attrib3.write_vrs_rate_hint_to_cmask ? "true" : "false");
dst += fmt::format("\t dcc.max_uncompressed_block_size = 0x{:08x}\n",
rt.dcc.max_uncompressed_block_size);
dst += fmt::format("\t dcc.max_compressed_block_size = 0x{:08x}\n",
rt.dcc.max_compressed_block_size);
dst += fmt::format("\t dcc.color_transform = 0x{:08x}\n", rt.dcc.color_transform);
dst += fmt::format("\t dcc.overwrite_combiner_disable = {}\n",
rt.dcc.overwrite_combiner_disable ? "true" : "false");
dst += fmt::format("\t dcc.independent_block_size = 0x{:02x}\n",
static_cast<uint8_t>(rt.dcc.independent_block_size));
dst += fmt::format("\t data_write_on_dcc_clear_to_reg = {}\n",
rt.dcc.data_write_on_dcc_clear_to_reg ? "true" : "false");
dst += fmt::format("\t dcc.dcc_clear_key_enable = {}\n",
rt.dcc.dcc_clear_key_enable ? "true" : "false");
dst += fmt::format("\t cmask.addr = 0x{:016x}\n", rt.cmask.addr);
dst += fmt::format("\t fmask.addr = 0x{:016x}\n", rt.fmask.addr);
dst += fmt::format("\t clear_word0.word0 = 0x{:08x}\n", rt.clear_word0.word0);
dst += fmt::format("\t clear_word1.word1 = 0x{:08x}\n", rt.clear_word1.word1);
dst += fmt::format("\t dcc_addr.addr = 0x{:016x}\n", rt.dcc_addr.addr);
return dst;
}
@@ -387,81 +368,6 @@ static void ZPrint(const char* func, const HW::DepthRenderTarget& z) {
z.size.valid ? "true" : "false");
}
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
static void ZCheck(const HW::DepthRenderTarget& z, const HW::DepthControl& dc,
const HW::RenderControl& rc) {
const bool depth_active = dc.z_enable || dc.z_write_enable || dc.depth_bounds_enable ||
rc.depth_clear_enable || rc.copy_depth_to_color;
const bool stencil_active =
dc.stencil_enable || rc.stencil_clear_enable || rc.copy_stencil_to_color;
if (!depth_active && !stencil_active) {
return;
}
EXIT_NOT_IMPLEMENTED(rc.copy_depth_to_color || rc.copy_stencil_to_color);
EXIT_NOT_IMPLEMENTED(!z.z_info.HasValidTextureCompatibility());
EXIT_NOT_IMPLEMENTED(!z.stencil_info.HasValidTextureCompatibility());
if (z.z_info.format == Prospero::DepthFormat::kInvalid) {
EXIT_NOT_IMPLEMENTED(z.z_info.format != Prospero::DepthFormat::kInvalid);
EXIT_NOT_IMPLEMENTED(z.z_info.num_samples != 0);
EXIT_NOT_IMPLEMENTED(z.z_info.htile_acceleration != false);
EXIT_NOT_IMPLEMENTED(z.z_info.expclear_enabled != false);
EXIT_NOT_IMPLEMENTED(z.z_info.partially_resident != false);
EXIT_NOT_IMPLEMENTED(z.z_info.max_mip_level != 0);
} else {
EXIT_NOT_IMPLEMENTED(z.z_info.format != Prospero::DepthFormat::kZ16 &&
z.z_info.format != Prospero::DepthFormat::kZ32F);
if (z.z_info.num_samples != 0x00000000) {
static bool logged = false;
if (!logged) {
LOGF("DepthTarget: using native num_samples=0x%08" PRIx32 "\n",
z.z_info.num_samples);
logged = true;
}
}
EXIT_NOT_IMPLEMENTED(z.z_info.expclear_enabled != false);
EXIT_NOT_IMPLEMENTED(z.z_info.partially_resident != false);
EXIT_NOT_IMPLEMENTED(z.z_info.max_mip_level != 0);
}
if (z.stencil_info.format == Prospero::StencilFormat::kInvalid) {
EXIT_NOT_IMPLEMENTED(z.stencil_info.expclear_enabled != false);
EXIT_NOT_IMPLEMENTED(z.stencil_info.partially_resident != false);
} else {
EXIT_NOT_IMPLEMENTED(z.stencil_info.format != Prospero::StencilFormat::k8UInt);
EXIT_NOT_IMPLEMENTED(z.stencil_info.expclear_enabled != false);
EXIT_NOT_IMPLEMENTED(z.stencil_info.partially_resident != false);
}
if (z.z_info.format != Prospero::DepthFormat::kInvalid ||
z.stencil_info.format != Prospero::StencilFormat::kInvalid) {
EXIT_NOT_IMPLEMENTED(z.shading_rate_encoding != 0);
if (z.depth_view.current_mip_level != 0x00000000) {
static std::atomic<uint32_t> log_count {0};
if (log_count.fetch_add(1, std::memory_order_relaxed) < 16) {
LOGF("DepthTarget: temporary: ignoring PS5 current mip level=0x%08" PRIx32 "\n",
z.depth_view.current_mip_level);
}
}
if (z.depth_view.depth_write_disable || z.depth_view.stencil_write_disable) {
static std::atomic<uint32_t> log_count {0};
if (log_count.fetch_add(1, std::memory_order_relaxed) < 16) {
LOGF("DepthTarget: honoring write disable depth=%s, stencil=%s\n",
z.depth_view.depth_write_disable ? "true" : "false",
z.depth_view.stencil_write_disable ? "true" : "false");
}
}
EXIT_NOT_IMPLEMENTED(!z.depth_view.depth_write_disable &&
z.z_read_base_addr != z.z_write_base_addr);
EXIT_NOT_IMPLEMENTED(!z.depth_view.stencil_write_disable &&
z.stencil_read_base_addr != z.stencil_write_base_addr);
EXIT_NOT_IMPLEMENTED(!z.depth_view.depth_write_disable && z.z_write_base_addr == 0);
// EXIT_NOT_IMPLEMENTED(z.htile_data_base_addr == 0);
EXIT_NOT_IMPLEMENTED(!z.size.valid);
}
}
static void ClipPrint(const char* func, const HW::ClipControl& c) {
LOGF("%s\n", func);
@@ -490,7 +396,7 @@ static void ClipCheck(const HW::ClipControl& c) {
// dx_linear_attr_clip_enable preserves linear (noperspective) attributes at clip-generated
// vertices, which Vulkan provides as part of clipping and interpolation.
EXIT_NOT_IMPLEMENTED(c.user_clip_planes != 0 || c.user_clip_plane_mode != 0 ||
c.vertex_kill_any || c.user_clip_plane_negate_y || c.clip_disable ||
c.vertex_kill_any || c.user_clip_plane_negate_y ||
c.user_clip_plane_cull_only || c.cull_on_clipping_error_disable ||
c.force_viewport_index_from_vs_enable);
}
@@ -546,16 +452,6 @@ static void McCheck(const HW::ModeControl& c) {
// EXIT_NOT_IMPLEMENTED(c.cull_front != false);
// EXIT_NOT_IMPLEMENTED(c.cull_back != false);
// EXIT_NOT_IMPLEMENTED(c.face != false);
if (c.poly_mode != 0) {
static bool logged = false;
if (!logged) {
LOGF("\t temporary: PA_SU_SC_MODE_CNTL.POLY_MODE is not fully implemented; continuing "
"with filled polygons\n");
logged = true;
}
}
EXIT_NOT_IMPLEMENTED(c.polymode_front_ptype != 0 && c.polymode_front_ptype != 2);
EXIT_NOT_IMPLEMENTED(c.polymode_back_ptype != 0 && c.polymode_back_ptype != 2);
if (c.vtx_window_offset_enable) {
static bool logged = false;
if (!logged) {
@@ -565,7 +461,6 @@ static void McCheck(const HW::ModeControl& c) {
logged = true;
}
}
EXIT_NOT_IMPLEMENTED(c.provoking_vtx_last != false);
EXIT_NOT_IMPLEMENTED(c.persp_corr_dis != false);
}
@@ -592,16 +487,7 @@ static void BcPrint(const char* func, const HW::BlendControl& c, const HW::Blend
color.red, color.green, color.blue, color.alpha, cc.mode, cc.op);
}
static void BcCheck(const HW::BlendControl& /*c*/, const HW::BlendColor& color,
const HW::ColorControl& cc) {
// EXIT_NOT_IMPLEMENTED(c.color_srcblend != 0);
// EXIT_NOT_IMPLEMENTED(c.color_comb_fcn != 0);
// EXIT_NOT_IMPLEMENTED(c.color_destblend != 0);
// EXIT_NOT_IMPLEMENTED(c.alpha_srcblend != 0);
// EXIT_NOT_IMPLEMENTED(c.alpha_comb_fcn != 0);
// EXIT_NOT_IMPLEMENTED(c.alpha_destblend != 0);
// EXIT_NOT_IMPLEMENTED(c.separate_alpha_blend != false);
// EXIT_NOT_IMPLEMENTED(c.enable != false);
static void BcCheck(const HW::BlendColor& color, const HW::ColorControl& cc) {
if (color.red != 0.0f || color.green != 0.0f || color.blue != 0.0f || color.alpha != 0.0f) {
static bool logged = false;
if (!logged) {
@@ -664,29 +550,6 @@ static void DPrint(const char* func, const HW::DepthControl& c, const HW::Stenci
sm.stencil_opval_bf);
}
static void DCheck(const HW::DepthControl& c, const HW::StencilControl& s,
const HW::StencilMask& /*sm*/) {
// EXIT_NOT_IMPLEMENTED(c.stencil_enable != false);
// EXIT_NOT_IMPLEMENTED(c.z_enable != false);
// EXIT_NOT_IMPLEMENTED(c.z_write_enable != false);
// EXIT_NOT_IMPLEMENTED(c.zfunc != 0);
// Back-face stencil state is handled separately when enabled.
// EXIT_NOT_IMPLEMENTED(c.stencilfunc != 0);
// EXIT_NOT_IMPLEMENTED(c.stencilfunc_bf != 0);
// EXIT_NOT_IMPLEMENTED(s.stencil_fail != 0);
// EXIT_NOT_IMPLEMENTED(s.stencil_zpass != 0);
// EXIT_NOT_IMPLEMENTED(s.stencil_zfail != 0);
// Back-face stencil ops may legitimately differ from the front-face ops.
// EXIT_NOT_IMPLEMENTED(sm.stencil_testval != 0);
// EXIT_NOT_IMPLEMENTED(sm.stencil_mask != 0);
// EXIT_NOT_IMPLEMENTED(sm.stencil_writemask != 0);
// EXIT_NOT_IMPLEMENTED(sm.stencil_opval != 0);
// EXIT_NOT_IMPLEMENTED(sm.stencil_testval_bf != 0);
// EXIT_NOT_IMPLEMENTED(sm.stencil_mask_bf != 0);
// EXIT_NOT_IMPLEMENTED(sm.stencil_writemask_bf != 0);
// EXIT_NOT_IMPLEMENTED(sm.stencil_opval_bf != 0);
}
static void EqaaPrint(const char* func, const HW::EqaaControl& c) {
LOGF("%s\n", func);
@@ -845,15 +708,6 @@ static void VpCheck(const HW::ScreenViewport& vp, const HW::ScanModeControl& smc
// EXIT_NOT_IMPLEMENTED(smc.vport_scissor_enable);
EXIT_NOT_IMPLEMENTED(smc.line_stipple_enable);
if (vp.viewports[0].zmin > 0.000000 || vp.viewports[0].zmax != 1.000000) {
static bool logged = false;
if (!logged) {
LOGF("\t warning: non-default viewport depth clamp zmin = %f, zmax = %f; using "
"viewport scale/offset for Vulkan depth range\n",
vp.viewports[0].zmin, vp.viewports[0].zmax);
logged = true;
}
}
// EXIT_NOT_IMPLEMENTED(vp.viewports[0].xscale != 960.000000);
// EXIT_NOT_IMPLEMENTED(vp.viewports[0].xoffset != 960.000000);
// EXIT_NOT_IMPLEMENTED(vp.viewports[0].yscale != -540.000000);
@@ -873,7 +727,7 @@ static void VpCheck(const HW::ScreenViewport& vp, const HW::ScanModeControl& smc
// EXIT_NOT_IMPLEMENTED(fabsf(vp.guard_band_horz_clip - 33.133327f) > 0.001f);
// EXIT_NOT_IMPLEMENTED(fabsf(vp.guard_band_vert_clip - 59.629623f) > 0.001f);
if (vp.guard_band_horz_discard != 0.0f || vp.guard_band_vert_discard != 0.0f) {
if (vp.guard_band_horz_discard != 1.0f || vp.guard_band_vert_discard != 1.0f) {
static std::atomic<uint32_t> log_count {0};
if (log_count.fetch_add(1) < 16) {
LOGF("\t warning: unsupported PS5 guard band discard = %f, %f, continuing\n",
@@ -893,10 +747,6 @@ static bool ScissorRectValid(const ScissorRect& r) {
return r.right > r.left && r.bottom > r.top;
}
static bool ScissorRectSet(const ScissorRect& r) {
return r.left != 0 || r.top != 0 || r.right != 0 || r.bottom != 0;
}
static ScissorRect ScissorRectOffset(ScissorRect r, int x, int y) {
r.left += x;
r.right += x;
@@ -906,8 +756,8 @@ static ScissorRect ScissorRectOffset(ScissorRect r, int x, int y) {
}
static ScissorRect ScissorRectIntersect(const ScissorRect& a, const ScissorRect& b) {
return {a.left > b.left ? a.left : b.left, a.top > b.top ? a.top : b.top,
a.right < b.right ? a.right : b.right, a.bottom < b.bottom ? a.bottom : b.bottom};
return {std::max(a.left, b.left), std::max(a.top, b.top),
std::min(a.right, b.right), std::min(a.bottom, b.bottom)};
}
static ScissorRect ScissorRectClamp(ScissorRect r, uint32_t width, uint32_t height) {
@@ -954,49 +804,26 @@ static bool ScissorClipRuleToIntersectionMask(uint16_t rule, uint8_t* mask) {
}
ScissorRect calc_final_scissor(const HW::ScreenViewport& vp, const HW::ScanModeControl& smc,
vk::Extent2D extent) {
ScissorRect screen {vp.screen_scissor_left, vp.screen_scissor_top, vp.screen_scissor_right,
vp.screen_scissor_bottom};
ScissorRect final = screen;
if (!ScissorRectSet(screen)) {
final = {0, 0, static_cast<int>(extent.width), static_cast<int>(extent.height)};
auto log_id = g_scissor_default_log_count.fetch_add(1);
if (log_id < 32) {
LOGF("temporary: default unset screen scissor to framebuffer extent %ux%u\n",
extent.width, extent.height);
vk::Extent2D extent, uint32_t viewport_index) {
EXIT_IF(viewport_index >= std::size(vp.viewports));
ScissorRect final {vp.screen_scissor_left, vp.screen_scissor_top, vp.screen_scissor_right,
vp.screen_scissor_bottom};
const auto intersect = [&](ScissorRect rect, bool window_offset) {
if (window_offset) {
rect = ScissorRectOffset(rect, vp.window_offset_x, vp.window_offset_y);
}
}
final = ScissorRectIntersect(final, rect);
};
intersect({vp.window_scissor_left, vp.window_scissor_top, vp.window_scissor_right,
vp.window_scissor_bottom}, vp.window_scissor_window_offset_enable);
intersect({vp.generic_scissor_left, vp.generic_scissor_top, vp.generic_scissor_right,
vp.generic_scissor_bottom}, vp.generic_scissor_window_offset_enable);
ScissorRect window {vp.window_scissor_left, vp.window_scissor_top, vp.window_scissor_right,
vp.window_scissor_bottom};
if (ScissorRectSet(window)) {
if (vp.window_scissor_window_offset_enable) {
window = ScissorRectOffset(window, vp.window_offset_x, vp.window_offset_y);
}
final = ScissorRectIntersect(final, window);
}
ScissorRect generic {vp.generic_scissor_left, vp.generic_scissor_top, vp.generic_scissor_right,
vp.generic_scissor_bottom};
if (ScissorRectSet(generic)) {
if (vp.generic_scissor_window_offset_enable) {
generic = ScissorRectOffset(generic, vp.window_offset_x, vp.window_offset_y);
}
final = ScissorRectIntersect(final, generic);
}
const auto& viewport = vp.viewports[0];
ScissorRect viewport_scissor {viewport.viewport_scissor_left, viewport.viewport_scissor_top,
viewport.viewport_scissor_right,
viewport.viewport_scissor_bottom};
if (smc.vport_scissor_enable && ScissorRectSet(viewport_scissor)) {
if (viewport.viewport_scissor_window_offset_enable) {
viewport_scissor =
ScissorRectOffset(viewport_scissor, vp.window_offset_x, vp.window_offset_y);
}
final = ScissorRectIntersect(final, viewport_scissor);
const auto& viewport = vp.viewports[viewport_index];
if (smc.vport_scissor_enable) {
intersect({viewport.viewport_scissor_left, viewport.viewport_scissor_top,
viewport.viewport_scissor_right, viewport.viewport_scissor_bottom},
viewport.viewport_scissor_window_offset_enable);
}
if (vp.clip_rect_rule == 0) {
@@ -1009,12 +836,8 @@ ScissorRect calc_final_scissor(const HW::ScreenViewport& vp, const HW::ScanModeC
continue;
}
ScissorRect clip {vp.clip_rect_left[i], vp.clip_rect_top[i], vp.clip_rect_right[i],
vp.clip_rect_bottom[i]};
if (vp.clip_rect_window_offset_enable[i]) {
clip = ScissorRectOffset(clip, vp.window_offset_x, vp.window_offset_y);
}
final = ScissorRectIntersect(final, clip);
intersect({vp.clip_rect_left[i], vp.clip_rect_top[i], vp.clip_rect_right[i],
vp.clip_rect_bottom[i]}, vp.clip_rect_window_offset_enable[i]);
}
} else {
static std::atomic<uint32_t> log_count {0};
@@ -1032,15 +855,10 @@ void hw_check(const CommandBuffer& buffer) {
const auto& hw = buffer.GetRegisters();
const auto rt_slot = render_target_first_bound_slot(buffer);
const auto& rt = hw.GetRenderTarget(rt_slot);
const auto& bc = hw.GetBlendControl(rt_slot);
const auto& bclr = hw.GetBlendColor();
const auto& vp = hw.GetScreenViewport();
const auto& z = hw.GetDepthRenderTarget();
const auto& c = hw.GetClipControl();
const auto& rc = hw.GetRenderControl();
const auto& d = hw.GetDepthControl();
const auto& s = hw.GetStencilControl();
const auto& sm = hw.GetStencilMask();
const auto& mc = hw.GetModeControl();
const auto& eqaa = hw.GetEqaaControl();
const auto& cc = hw.GetColorControl();
@@ -1061,25 +879,22 @@ void hw_check(const CommandBuffer& buffer) {
RtCheck(rt);
log_phase("vp");
VpCheck(vp, smc);
log_phase("z");
ZCheck(z, d, rc);
log_phase("clip");
ClipCheck(c);
log_phase("rc");
RcCheck(rc);
log_phase("depth");
DCheck(d, s, sm);
log_phase("mode");
McCheck(mc);
log_phase("blend");
BcCheck(bc, bclr, cc);
BcCheck(bclr, cc);
log_phase("eqaa");
EqaaCheck(eqaa, ac);
log_phase("aa");
AaCheck(aa, ac);
log_phase("done");
if (RenderTargetMaskHasBoundMrt(buffer)) {
if (graphics_debug_dump_enabled() && RenderTargetMaskHasBoundMrt(buffer)) {
LOGF("MRT render target mask: 0x%08" PRIx32 "\n", hw.GetRenderTargetMask());
for (uint32_t i = 0; i < 8; i++) {
const auto& mrt = hw.GetRenderTarget(i);
@@ -1131,7 +946,7 @@ void hw_print(const CommandBuffer& buffer) {
hw.GetRenderTargetMask(), hw.GetDepthClearValue(), hw.GetStencilClearValue(),
hw.GetLineWidth(), hw.GetPrimitiveResetIndex());
LOGF("%s", Common::Concat(rt_print("RenderTraget:", rt), "").c_str());
LOGF("%s", rt_print("RenderTraget:", rt).c_str());
ZPrint("DepthRenderTraget:", z);
VpPrint("ScreenViewport:", vp, smc);
+2 -5
View File
@@ -5,7 +5,6 @@
#include <cstdint>
#include <string>
#include <vector>
namespace Libs::Graphics {
@@ -13,7 +12,6 @@ class CommandBuffer;
namespace HW {
class Context;
class Shader;
class UserConfig;
struct RenderTarget;
struct ScanModeControl;
@@ -32,14 +30,13 @@ uint32_t render_target_first_bound_slot(const CommandBuffer& buf
bool graphics_debug_dump_enabled();
void uc_print(const char* func, const HW::UserConfig& uc);
void uc_check(const HW::UserConfig& uc);
void sh_print(const char* func, const HW::Shader& uc);
std::vector<std::string> rt_print(const char* func, const HW::RenderTarget& rt);
std::string rt_print(const char* func, const HW::RenderTarget& rt);
bool RenderIsColorTileModeLinear(Prospero::TileMode tile_mode);
void hw_print(const CommandBuffer& buffer);
void hw_check(const CommandBuffer& buffer);
void LogDrawPhase(const char* draw_name, const char* phase);
ScissorRect calc_final_scissor(const HW::ScreenViewport& vp, const HW::ScanModeControl& smc,
vk::Extent2D extent);
vk::Extent2D extent, uint32_t viewport_index);
} // namespace Libs::Graphics
@@ -10,6 +10,7 @@
#include "graphics/guest_gpu/hardwareContext.h"
#include "graphics/guest_gpu/tile.h"
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/renderer/commandScheduler.h"
#include "graphics/host_gpu/renderer/debug.h"
#include "graphics/host_gpu/renderer/image/imageView.h"
#include "graphics/host_gpu/renderer/image/textureCommon.h"
@@ -18,6 +19,7 @@
#include "graphics/host_gpu/vulkanCommon.h"
#include <algorithm>
#include <array>
#include <atomic>
#include <cmath>
#include <cstdarg>
@@ -44,8 +46,12 @@ static vk::StencilOp ConvertStencilOp(uint8_t value, uint8_t write_mask, uint8_t
switch (static_cast<Prospero::StencilOp>(value)) {
case Prospero::StencilOp::kKeep: return vk::StencilOp::eKeep;
case Prospero::StencilOp::kZero: return vk::StencilOp::eZero;
case Prospero::StencilOp::kReplaceTest:
case Prospero::StencilOp::kReplaceOp: return vk::StencilOp::eReplace;
case Prospero::StencilOp::kReplaceTest: return vk::StencilOp::eReplace;
case Prospero::StencilOp::kReplaceOp:
if ((op_value & write_mask) == 0) {
return vk::StencilOp::eZero;
}
return vk::StencilOp::eReplace;
case Prospero::StencilOp::kAddClamp: return vk::StencilOp::eIncrementAndClamp;
case Prospero::StencilOp::kSubClamp: return vk::StencilOp::eDecrementAndClamp;
case Prospero::StencilOp::kInvert: return vk::StencilOp::eInvert;
@@ -65,9 +71,38 @@ static vk::StencilOp ConvertStencilOp(uint8_t value, uint8_t write_mask, uint8_t
}
}
static bool UsesStencilOpValue(uint8_t fail, uint8_t pass, uint8_t depth_fail) {
constexpr auto replace_op = static_cast<uint8_t>(Prospero::StencilOp::kReplaceOp);
return fail == replace_op || pass == replace_op || depth_fail == replace_op;
static vk::StencilOpState ConvertStencilState(
uint8_t compare, const std::array<uint8_t, 3>& operations, uint8_t op_value,
const vk::StencilOpState& state) {
const auto test_value = state.reference;
auto reference = test_value;
auto required_bits = state.compareMask;
if (compare == static_cast<uint8_t>(vk::CompareOp::eAlways) ||
compare == static_cast<uint8_t>(vk::CompareOp::eNever)) {
required_bits = 0;
}
std::array<vk::StencilOp, 3> converted {};
for (size_t i = 0; i < operations.size(); i++) {
converted[i] = ConvertStencilOp(operations[i], state.writeMask, op_value);
if (converted[i] != vk::StencilOp::eReplace) {
continue;
}
auto replacement = test_value;
if (static_cast<Prospero::StencilOp>(operations[i]) == Prospero::StencilOp::kReplaceOp) {
replacement = op_value;
}
if (((reference ^ replacement) & required_bits & state.writeMask) != 0) {
DepthFatal("unsupported stencil replacement: compare=%u, compare mask=0x%02" PRIx32
", write mask=0x%02" PRIx32 ", operation value=0x%02" PRIx8
", test value=0x%02" PRIx32,
compare, state.compareMask, state.writeMask, op_value, test_value);
}
// Vulkan shares one reference between comparison and every replacement on this face.
reference = (reference & ~state.writeMask) | (replacement & state.writeMask);
required_bits |= state.writeMask;
}
return {converted[0], converted[1], converted[2], static_cast<vk::CompareOp>(compare),
state.compareMask, state.writeMask, reference};
}
[[nodiscard]] static vk::Format ResolveHostDepthAttachmentFormat(const CommandBuffer& buffer,
@@ -96,54 +131,20 @@ static bool UsesStencilOpValue(uint8_t fail, uint8_t pass, uint8_t depth_fail) {
return vk::Format::eUndefined;
}
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
void RenderExecutor::ResolveRenderDepthTarget(uint64_t submit_id, CommandBuffer& buffer,
RenderDepthInfo& r) {
KYTY_PROFILER_FUNCTION();
(void)submit_id;
const auto& hw = buffer.GetRegisters();
const auto& z = hw.GetDepthRenderTarget();
const auto& rc = hw.GetRenderControl();
const auto& dc = hw.GetDepthControl();
const auto& sc = hw.GetStencilControl();
const auto& sm = hw.GetStencilMask();
const bool has_stencil = z.stencil_info.format != Prospero::StencilFormat::kInvalid;
const bool depth_active = dc.z_enable || dc.z_write_enable || dc.depth_bounds_enable ||
rc.depth_clear_enable || rc.copy_depth_to_color;
const bool stencil_active =
has_stencil && (dc.stencil_enable || rc.stencil_clear_enable || rc.copy_stencil_to_color);
if (!depth_active && !stencil_active) {
return;
}
static TextureCache::ImageDesc MakeDepthTargetDesc(const CommandBuffer& buffer,
const HW::DepthRenderTarget& z,
bool write_buffer = false) {
const bool has_stencil = z.stencil_info.format != Prospero::StencilFormat::kInvalid;
const auto depth_address = write_buffer ? z.z_write_base_addr : z.z_read_base_addr;
const auto stencil_address =
write_buffer ? z.stencil_write_base_addr : z.stencil_read_base_addr;
if (!z.z_info.HasValidTextureCompatibility() ||
!z.stencil_info.HasValidTextureCompatibility()) {
DepthFatal("invalid PS5 depth texture-compatibility encoding");
}
const bool attachment_unbound =
z.z_info.format == Prospero::DepthFormat::kInvalid &&
z.stencil_info.format == Prospero::StencilFormat::kInvalid && z.z_info.num_samples == 0 &&
z.z_info.texture_compatibility == Prospero::TextureCompatiblePlaneCompression::kDisable &&
!z.z_info.expclear_enabled && !z.z_info.partially_resident && z.z_info.max_mip_level == 0 &&
z.stencil_info.texture_compatibility == Prospero::TextureCompatibleStencil::kDisable &&
!z.stencil_info.expclear_enabled && !z.stencil_info.partially_resident &&
z.depth_view.slice_start == 0 && z.depth_view.slice_max == 0 &&
z.depth_view.current_mip_level == 0 && !z.depth_view.depth_write_disable &&
!z.depth_view.stencil_write_disable && z.z_read_base_addr == 0 && z.z_write_base_addr == 0 &&
z.stencil_read_base_addr == 0 && z.stencil_write_base_addr == 0 &&
z.htile_data_base_addr == 0 &&
// DB_DEPTH_SIZE_XY is independent state and may remain programmed after the attachment
// formats and addresses are unbound. A zero encoding is the valid 1x1 value, so its
// presence alone must not manufacture a depth attachment.
!z.z_info.htile_acceleration && z.shading_rate_encoding == 0 && z.size.x_max == 0 &&
z.size.y_max == 0;
if (attachment_unbound) {
static std::atomic_bool logged = false;
if (!logged.exchange(true, std::memory_order_relaxed)) {
LOGF("DepthTarget: ignoring enabled depth/stencil state without a bound attachment\n");
}
return;
}
const bool has_htile = z.z_info.htile_acceleration;
const bool unsupported_shading_rate_encoding =
z.shading_rate_encoding > 1 || (z.shading_rate_encoding != 0 && !has_htile);
const auto samples = render_sample_count(z.z_info.num_samples);
if (samples == 0) {
DepthFatal("unsupported depth fragment count: %u", z.z_info.num_samples);
@@ -158,23 +159,16 @@ void RenderExecutor::ResolveRenderDepthTarget(uint64_t submit_id, CommandBuffer&
DepthFatal("invalid depth view: base=%u last=%u", z.depth_view.slice_start,
z.depth_view.slice_max);
}
if (rc.copy_depth_to_color || rc.copy_stencil_to_color || rc.copy_centroid ||
rc.copy_sample != 0 || z.z_info.expclear_enabled ||
z.stencil_info.expclear_enabled || z.z_info.partially_resident ||
// EXPCLEAR permits an HTile acceleration state; the host attachment is already expanded.
if (z.z_info.partially_resident ||
z.stencil_info.partially_resident || z.z_info.max_mip_level != 0 ||
z.depth_view.current_mip_level != 0 || z.shading_rate_encoding != 0 ||
z.z_read_base_addr == 0 ||
(!z.depth_view.depth_write_disable && z.z_write_base_addr != z.z_read_base_addr) ||
(z.z_read_base_addr & 0xffffu) != 0 ||
dc.zfunc > static_cast<uint8_t>(vk::CompareOp::eAlways)) {
z.depth_view.current_mip_level != 0 || unsupported_shading_rate_encoding ||
depth_address == 0 || (depth_address & 0xffffu) != 0) {
DepthFatal("unsupported depth register state");
}
if (has_stencil) {
if (z.stencil_info.format != Prospero::StencilFormat::k8UInt || !htile_stencil_compat ||
z.stencil_read_base_addr == 0 ||
(!z.depth_view.stencil_write_disable &&
z.stencil_write_base_addr != z.stencil_read_base_addr) ||
(z.stencil_read_base_addr & 0xffffu) != 0) {
stencil_address == 0 || (stencil_address & 0xffffu) != 0) {
DepthFatal("unsupported stencil attachment state");
}
} else if (z.stencil_read_base_addr != 0 || z.stencil_write_base_addr != 0) {
@@ -201,10 +195,10 @@ void RenderExecutor::ResolveRenderDepthTarget(uint64_t submit_id, CommandBuffer&
DepthFatal("unsupported depth/stencil format pair");
}
const auto ideal_format = DepthAttachmentFormat(*policy, has_stencil);
r.format = ResolveHostDepthAttachmentFormat(buffer, *policy, has_stencil, samples);
if (r.format == vk::Format::eUndefined) {
const auto format = ResolveHostDepthAttachmentFormat(buffer, *policy, has_stencil, samples);
if (format == vk::Format::eUndefined) {
DepthFatal("no host depth/stencil format supports required usage for %s",
VulkanToString(ideal_format).c_str());
vk::to_string(ideal_format).c_str());
}
const auto guest_format = policy->guest_format;
const uint32_t bytes = policy->bytes_per_element;
@@ -227,27 +221,98 @@ void RenderExecutor::ResolveRenderDepthTarget(uint64_t submit_id, CommandBuffer&
const auto depth_backing_size = depth_size.size * view.image_layers;
const auto stencil_backing_size = stencil_size.size * view.image_layers;
const auto htile_backing_size = htile_size.size * view.image_layers;
if (!GuestRange {z.z_read_base_addr, depth_backing_size}.Valid() ||
(has_stencil && !GuestRange {z.stencil_read_base_addr, stencil_backing_size}.Valid()) ||
if (!GuestRange {depth_address, depth_backing_size}.Valid() ||
(has_stencil && !GuestRange {stencil_address, stencil_backing_size}.Valid()) ||
(has_htile && !GuestRange {z.htile_data_base_addr, htile_backing_size}.Valid())) {
DepthFatal("layered depth backing range is invalid");
}
r.htile = has_htile;
r.width = width;
r.height = height;
r.samples = samples;
r.depth_buffer_size = depth_backing_size;
r.depth_buffer_vaddr = z.z_read_base_addr;
r.stencil_buffer_size = has_stencil ? stencil_backing_size : 0;
r.stencil_buffer_vaddr = has_stencil ? z.stencil_read_base_addr : 0;
r.htile_buffer_size = has_htile ? htile_backing_size : 0;
r.htile_buffer_vaddr = has_htile ? z.htile_data_base_addr : 0;
TextureCache::ImageDesc desc {};
desc.type = TextureCache::BindingType::DepthTarget;
desc.info.data = {depth_address, depth_backing_size};
desc.info.stencil =
has_stencil ? GuestRange {stencil_address, stencil_backing_size} : GuestRange {};
desc.info.pixel_format = format;
desc.info.guest_format = guest_format;
desc.info.type = Prospero::ImageType::kColor2D;
desc.info.extent = {width, height, 1};
desc.info.resources = {1, view.image_layers};
desc.info.pitch = pitch;
desc.info.bytes_per_block = bytes;
desc.info.samples = samples;
desc.info.tile_mode = Prospero::TileMode::kDepth;
desc.info.mip_layout[0] = {0, depth_backing_size, pitch, height};
desc.info.metadata.range =
has_htile ? GuestRange {z.htile_data_base_addr, htile_backing_size} : GuestRange {};
desc.info.metadata.kind = has_htile ? ImageMetadataKind::Htile : ImageMetadataKind::None;
desc.info.metadata.stencil_compressed =
has_stencil && has_htile && !z.stencil_info.htile_stencil_disabled;
desc.view_info.format = format;
desc.view_info.type =
view.layer_count == 1 ? vk::ImageViewType::e2D : vk::ImageViewType::e2DArray;
desc.view_info.aspect = ImageViewOps::DepthAspectMask(format);
desc.view_info.base_level = 0;
desc.view_info.level_count = 1;
desc.view_info.base_layer = view.base_layer;
desc.view_info.layer_count = view.layer_count;
desc.view_info.usage = vk::ImageUsageFlagBits::eDepthStencilAttachment;
return desc;
}
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
void RenderExecutor::ResolveRenderDepthTarget(CommandBuffer& buffer, RenderDepthInfo& r) {
KYTY_PROFILER_FUNCTION();
const auto& hw = buffer.GetRegisters();
const auto& z = hw.GetDepthRenderTarget();
const auto& rc = hw.GetRenderControl();
const auto& dc = hw.GetDepthControl();
const auto& sc = hw.GetStencilControl();
const auto& sm = hw.GetStencilMask();
const bool has_stencil = z.stencil_info.format != Prospero::StencilFormat::kInvalid;
const bool depth_active = dc.z_enable || dc.depth_bounds_enable ||
rc.depth_clear_enable || rc.copy_depth_to_color;
const bool stencil_active =
has_stencil && (dc.stencil_enable || rc.stencil_clear_enable || rc.copy_stencil_to_color);
if (!depth_active && !stencil_active) {
return;
}
const bool attachment_unbound =
z.z_info.format == Prospero::DepthFormat::kInvalid &&
z.stencil_info.format == Prospero::StencilFormat::kInvalid && z.z_info.num_samples == 0 &&
z.z_info.texture_compatibility == Prospero::TextureCompatiblePlaneCompression::kDisable &&
!z.z_info.expclear_enabled && !z.z_info.partially_resident && z.z_info.max_mip_level == 0 &&
z.stencil_info.texture_compatibility == Prospero::TextureCompatibleStencil::kDisable &&
!z.stencil_info.expclear_enabled && !z.stencil_info.partially_resident &&
z.depth_view.slice_start == 0 && z.depth_view.slice_max == 0 &&
z.depth_view.current_mip_level == 0 && !z.depth_view.depth_write_disable &&
!z.depth_view.stencil_write_disable && z.z_read_base_addr == 0 && z.z_write_base_addr == 0 &&
z.stencil_read_base_addr == 0 && z.stencil_write_base_addr == 0 &&
z.htile_data_base_addr == 0 &&
// DB_DEPTH_SIZE_XY is independent state and may remain programmed after the attachment
// formats and addresses are unbound. A zero encoding is the valid 1x1 value, so its
// presence alone must not manufacture a depth attachment.
!z.z_info.htile_acceleration && z.shading_rate_encoding == 0 && z.size.x_max == 0 &&
z.size.y_max == 0;
if (attachment_unbound) {
static std::atomic_bool logged = false;
if (!logged.exchange(true, std::memory_order_relaxed)) {
LOGF("DepthTarget: ignoring enabled depth/stencil state without a bound attachment\n");
}
return;
}
if (rc.copy_depth_to_color || rc.copy_stencil_to_color || rc.copy_centroid ||
rc.copy_sample != 0 || dc.zfunc > static_cast<uint8_t>(vk::CompareOp::eAlways) ||
(!z.depth_view.depth_write_disable && z.z_write_base_addr != z.z_read_base_addr) ||
(has_stencil && !z.depth_view.stencil_write_disable &&
z.stencil_write_base_addr != z.stencil_read_base_addr)) {
DepthFatal("unsupported depth register state");
}
r.desc = MakeDepthTargetDesc(buffer, z);
r.depth_clear_enable = rc.depth_clear_enable;
r.depth_meta_clear_enable = false;
r.depth_load_clear_enable = r.depth_clear_enable;
r.depth_clear_value = hw.GetDepthClearValue();
r.depth_test_enable = dc.z_enable;
r.depth_write_enable = dc.z_write_enable && !z.depth_view.depth_write_disable;
r.depth_write_enable = r.depth_test_enable && dc.z_write_enable &&
!z.depth_view.depth_write_disable && !r.depth_clear_enable;
r.depth_compare_op = static_cast<vk::CompareOp>(dc.zfunc);
r.depth_bounds_test_enable = dc.depth_bounds_enable;
@@ -265,75 +330,91 @@ void RenderExecutor::ResolveRenderDepthTarget(uint64_t submit_id, CommandBuffer&
const uint8_t back_write_mask = stencil_ops_disabled ? 0 : sm.stencil_writemask_bf;
if (dc.stencilfunc > static_cast<uint8_t>(vk::CompareOp::eAlways) ||
(dc.backface_enable &&
dc.stencilfunc_bf > static_cast<uint8_t>(vk::CompareOp::eAlways)) ||
(front_write_mask != 0 &&
UsesStencilOpValue(sc.stencil_fail, sc.stencil_zpass, sc.stencil_zfail) &&
sm.stencil_opval != sm.stencil_testval) ||
(dc.backface_enable && back_write_mask != 0 &&
UsesStencilOpValue(sc.stencil_fail_bf, sc.stencil_zpass_bf, sc.stencil_zfail_bf) &&
sm.stencil_opval_bf != sm.stencil_testval_bf)) {
DepthFatal("unsupported stencil compare or replacement state");
dc.stencilfunc_bf > static_cast<uint8_t>(vk::CompareOp::eAlways))) {
DepthFatal("unsupported stencil compare state");
}
r.stencil_static_front = {
ConvertStencilOp(sc.stencil_fail, front_write_mask, sm.stencil_opval),
ConvertStencilOp(sc.stencil_zpass, front_write_mask, sm.stencil_opval),
ConvertStencilOp(sc.stencil_zfail, front_write_mask, sm.stencil_opval),
static_cast<vk::CompareOp>(dc.stencilfunc)};
r.stencil_dynamic_front = {sm.stencil_mask, front_write_mask, sm.stencil_testval};
r.stencil_front = ConvertStencilState(
dc.stencilfunc, {sc.stencil_fail, sc.stencil_zpass, sc.stencil_zfail},
sm.stencil_opval, {.compareMask = sm.stencil_mask, .writeMask = front_write_mask,
.reference = sm.stencil_testval});
if (dc.backface_enable) {
r.stencil_static_back = {
ConvertStencilOp(sc.stencil_fail_bf, back_write_mask, sm.stencil_opval_bf),
ConvertStencilOp(sc.stencil_zpass_bf, back_write_mask, sm.stencil_opval_bf),
ConvertStencilOp(sc.stencil_zfail_bf, back_write_mask, sm.stencil_opval_bf),
static_cast<vk::CompareOp>(dc.stencilfunc_bf)};
r.stencil_dynamic_back = {sm.stencil_mask_bf, back_write_mask, sm.stencil_testval_bf};
r.stencil_back = ConvertStencilState(
dc.stencilfunc_bf, {sc.stencil_fail_bf, sc.stencil_zpass_bf, sc.stencil_zfail_bf},
sm.stencil_opval_bf, {.compareMask = sm.stencil_mask_bf, .writeMask = back_write_mask,
.reference = sm.stencil_testval_bf});
} else {
r.stencil_static_back = r.stencil_static_front;
r.stencil_dynamic_back = r.stencil_dynamic_front;
r.stencil_back = r.stencil_front;
}
}
r.vaddr_num = has_stencil ? 2 : 1;
r.vaddr[0] = r.depth_buffer_vaddr;
r.size[0] = r.depth_buffer_size;
if (has_stencil) {
r.vaddr[1] = r.stencil_buffer_vaddr;
r.size[1] = r.stencil_buffer_size;
}
TextureCache::ImageDesc desc {};
desc.type = TextureCache::BindingType::DepthTarget;
desc.info.data = {r.depth_buffer_vaddr, r.depth_buffer_size};
desc.info.stencil = {r.stencil_buffer_vaddr, r.stencil_buffer_size};
desc.info.pixel_format = r.format;
desc.info.guest_format = guest_format;
desc.info.type = Prospero::ImageType::kColor2D;
desc.info.extent = {width, height, 1};
desc.info.resources = {1, view.image_layers};
desc.info.pitch = pitch;
desc.info.bytes_per_block = bytes;
desc.info.samples = samples;
desc.info.tile_mode = Prospero::TileMode::kDepth;
desc.info.mip_layout[0] = {0, r.depth_buffer_size, pitch, height};
desc.info.metadata.range = {r.htile_buffer_vaddr, r.htile_buffer_size};
desc.info.metadata.kind = has_htile ? ImageMetadataKind::Htile : ImageMetadataKind::None;
desc.info.metadata.stencil_compressed =
has_stencil && has_htile && !z.stencil_info.htile_stencil_disabled;
desc.view_info.format = r.format;
desc.view_info.type =
view.layer_count == 1 ? vk::ImageViewType::e2D : vk::ImageViewType::e2DArray;
desc.view_info.aspect = ImageViewOps::DepthAspectMask(r.format);
desc.view_info.base_level = 0;
desc.view_info.level_count = 1;
desc.view_info.base_layer = view.base_layer;
desc.view_info.layer_count = view.layer_count;
desc.view_info.usage = vk::ImageUsageFlagBits::eDepthStencilAttachment;
r.desc = std::move(desc);
auto& cache = m_context.GetTextureCache();
r.image_id = cache.FindImage(r.desc);
r.image_view = nullptr;
auto& cache = m_context.GetTextureCache();
r.image_id = cache.FindImage(r.desc);
BindRenderTarget(r.image_id);
}
bool RenderExecutor::DepthStencilCopy(CommandBuffer& buffer) {
const auto& hw = buffer.GetRegisters();
const auto& z = hw.GetDepthRenderTarget();
const auto& override = hw.GetDepthRenderOverride();
if (hw.GetColorControl().mode != 0) {
return false;
}
const bool depth_copy = override.force_z_dirty && override.force_z_valid &&
z.z_info.format != Prospero::DepthFormat::kInvalid &&
z.z_read_base_addr != 0 && z.z_write_base_addr != 0 &&
z.z_read_base_addr != z.z_write_base_addr;
const bool stencil_copy = override.force_stencil_dirty && override.force_stencil_valid &&
z.stencil_info.format != Prospero::StencilFormat::kInvalid &&
z.stencil_read_base_addr != 0 && z.stencil_write_base_addr != 0 &&
z.stencil_read_base_addr != z.stencil_write_base_addr;
if (!depth_copy && !stencil_copy) {
return false;
}
auto read_desc = MakeDepthTargetDesc(buffer, z);
auto write_desc = MakeDepthTargetDesc(buffer, z, true);
auto& cache = m_context.GetTextureCache();
const auto read_id = cache.FindImage(read_desc);
BindRenderTarget(read_id);
const auto write_id = cache.FindImage(write_desc);
BindRenderTarget(write_id);
cache.UpdateImage(read_id);
cache.UpdateImage(write_id);
cache.MarkGpuWritten(write_id);
auto& source = cache.GetImage(read_id);
auto& destination = cache.GetImage(write_id);
EXIT_IF(read_id == write_id || source.backing.format != destination.backing.format);
auto& scheduler = m_context.GetCommandScheduler();
scheduler.EndRendering();
const auto command = scheduler.Current().Handle();
const ImageSubresourceRange range {read_desc.view_info.base_level, 1,
read_desc.view_info.base_layer,
read_desc.view_info.layer_count};
source.Transit(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits2::eTransferRead,
range, command);
destination.Transit(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits2::eTransferWrite,
range, command);
std::array<vk::ImageCopy, 2> regions {};
uint32_t count = 0;
for (const auto aspect: {vk::ImageAspectFlagBits::eDepth, vk::ImageAspectFlagBits::eStencil}) {
if ((aspect == vk::ImageAspectFlagBits::eDepth && !depth_copy) ||
(aspect == vk::ImageAspectFlagBits::eStencil && !stencil_copy)) {
continue;
}
auto& region = regions[count++];
region.srcSubresource = {aspect, range.base_level, range.base_layer,
range.layer_count};
region.dstSubresource = region.srcSubresource;
region.extent = write_desc.info.extent;
}
command.copyImage(source.backing.image, vk::ImageLayout::eTransferSrcOptimal,
destination.backing.image, vk::ImageLayout::eTransferDstOptimal,
count, regions.data());
return true;
}
vk::ImageAspectFlags RenderDepthInfo::AttachmentWriteAspects() const {
const auto format = desc.view_info.format;
if (format == vk::Format::eUndefined) {
return {};
}
@@ -341,21 +422,20 @@ vk::ImageAspectFlags RenderDepthInfo::AttachmentWriteAspects() const {
const auto available = ImageViewOps::DepthAspectMask(format);
vk::ImageAspectFlags writes {};
if ((available & vk::ImageAspectFlagBits::eDepth) &&
(depth_load_clear_enable || (depth_test_enable && depth_write_enable))) {
(depth_load_clear_enable || depth_write_enable)) {
writes |= vk::ImageAspectFlagBits::eDepth;
}
if (!(available & vk::ImageAspectFlagBits::eStencil)) {
return writes;
}
const auto face_writes = [&](const PipelineStencilStaticState& state,
const PipelineStencilDynamicState& dynamic) {
if (dynamic.writeMask == 0) {
const auto face_writes = [&](const vk::StencilOpState& state) {
if (state.writeMask == 0) {
return false;
}
bool can_pass = state.compareOp != vk::CompareOp::eNever;
bool can_fail = state.compareOp != vk::CompareOp::eAlways;
if (dynamic.compareMask == 0) {
if (state.compareMask == 0) {
switch (state.compareOp) {
case vk::CompareOp::eEqual:
case vk::CompareOp::eLessOrEqual:
@@ -381,8 +461,7 @@ vk::ImageAspectFlags RenderDepthInfo::AttachmentWriteAspects() const {
(can_pass && depth_fail && state.depthFailOp != vk::StencilOp::eKeep);
};
if (stencil_clear_enable ||
(stencil_test_enable && (face_writes(stencil_static_front, stencil_dynamic_front) ||
face_writes(stencil_static_back, stencil_dynamic_back)))) {
(stencil_test_enable && (face_writes(stencil_front) || face_writes(stencil_back)))) {
writes |= vk::ImageAspectFlagBits::eStencil;
}
return writes;
@@ -19,26 +19,13 @@ inline constexpr bool depth_htile_stencil_acceleration_compatible(bool has_stenc
}
struct RenderDepthInfo {
// Discovery keeps guest image information but can remap the view into a larger cache image.
TextureCache::ImageDesc desc;
vk::Format format = vk::Format::eUndefined;
uint32_t width = 0;
uint32_t height = 0;
uint32_t samples = 1;
bool htile = false;
uint64_t depth_buffer_size = 0;
uint64_t depth_buffer_vaddr = 0;
uint64_t depth_tile_swizzle = 0;
uint64_t stencil_buffer_size = 0;
uint64_t stencil_buffer_vaddr = 0;
uint64_t stencil_tile_swizzle = 0;
uint64_t htile_buffer_size = 0;
uint64_t htile_buffer_vaddr = 0;
uint64_t htile_tile_swizzle = 0;
bool depth_clear_enable = false;
bool depth_load_clear_enable = false;
bool depth_meta_clear_enable = false;
float depth_clear_value = 0.0f;
bool depth_test_enable = false;
// Effective draw writes; discovery applies test, target-write and clear controls.
bool depth_write_enable = false;
vk::CompareOp depth_compare_op = vk::CompareOp::eNever;
bool depth_bounds_test_enable = false;
@@ -47,29 +34,20 @@ struct RenderDepthInfo {
bool stencil_clear_enable = false;
uint8_t stencil_clear_value = 0;
bool stencil_test_enable = false;
PipelineStencilStaticState stencil_static_front;
PipelineStencilStaticState stencil_static_back;
PipelineStencilDynamicState stencil_dynamic_front;
PipelineStencilDynamicState stencil_dynamic_back;
vk::StencilOpState stencil_front;
vk::StencilOpState stencil_back;
ImageId image_id;
vk::ImageView image_view = nullptr;
uint64_t vaddr[3] = {};
uint64_t size[3] = {};
int vaddr_num = 0;
[[nodiscard]] vk::ImageAspectFlags AttachmentWriteAspects() const;
};
inline bool depth_attachment_read_only(const RenderDepthInfo& depth) {
return !depth.AttachmentWriteAspects();
}
inline vk::ImageLayout depth_attachment_layout(const RenderDepthInfo& depth) {
const auto available = ImageViewOps::DepthAspectMask(depth.format);
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);
const bool depth_write = static_cast<bool>(writes & vk::ImageAspectFlagBits::eDepth);
// LoadOp clears are separate from guest depth writes.
const bool depth_write = depth.depth_write_enable;
const bool stencil_write = static_cast<bool>(writes & vk::ImageAspectFlagBits::eStencil);
if (!has_stencil) {
return depth_write ? vk::ImageLayout::eDepthAttachmentOptimal
@@ -10,7 +10,6 @@
#include <algorithm>
#include <array>
#include <iterator>
namespace Libs::Graphics {
@@ -23,7 +22,6 @@ BlitHelper::BlitHelper(GraphicContext& graphics, CommandScheduler& scheduler)
texture_binding.stageFlags = vk::ShaderStageFlagBits::eFragment;
vk::DescriptorSetLayoutCreateInfo descriptor_info {};
descriptor_info.sType = vk::StructureType::eDescriptorSetLayoutCreateInfo;
descriptor_info.flags = vk::DescriptorSetLayoutCreateFlagBits::ePushDescriptorKHR;
descriptor_info.bindingCount = 1;
descriptor_info.pBindings = &texture_binding;
@@ -32,16 +30,14 @@ BlitHelper::BlitHelper(GraphicContext& graphics, CommandScheduler& scheduler)
"create BlitHelper descriptor layout");
vk::PipelineLayoutCreateInfo layout_info {};
layout_info.sType = vk::StructureType::ePipelineLayoutCreateInfo;
layout_info.setLayoutCount = 1;
layout_info.pSetLayouts = &m_descriptor_layout;
RequireVulkanSuccess(
m_graphics.device.createPipelineLayout(&layout_info, nullptr, &m_pipeline_layout),
"create BlitHelper pipeline layout");
m_vertex_shader = CreateShader(GPU_BLIT_FS_TRIANGLE_SPV, std::size(GPU_BLIT_FS_TRIANGLE_SPV));
m_fragment_shader =
CreateShader(GPU_BLIT_COLOR_TO_MS_DEPTH_SPV, std::size(GPU_BLIT_COLOR_TO_MS_DEPTH_SPV));
m_vertex_shader = CompileSPV(GPU_BLIT_FS_TRIANGLE_SPV, m_graphics.device);
m_fragment_shader = CompileSPV(GPU_BLIT_COLOR_TO_MS_DEPTH_SPV, m_graphics.device);
}
BlitHelper::~BlitHelper() {
@@ -62,18 +58,6 @@ BlitHelper::~BlitHelper() {
}
}
vk::ShaderModule BlitHelper::CreateShader(const uint32_t* code, size_t words) const {
EXIT_IF(code == nullptr || words == 0);
vk::ShaderModuleCreateInfo create {};
create.sType = vk::StructureType::eShaderModuleCreateInfo;
create.codeSize = words * sizeof(uint32_t);
create.pCode = code;
vk::ShaderModule module = nullptr;
RequireVulkanSuccess(m_graphics.device.createShaderModule(&create, nullptr, &module),
"create BlitHelper shader module");
return module;
}
vk::Pipeline BlitHelper::GetPipeline(PipelineKey key) {
const auto cached = std::ranges::find(m_pipelines, key, &Pipeline::key);
if (cached != m_pipelines.end()) {
@@ -84,51 +68,37 @@ vk::Pipeline BlitHelper::GetPipeline(PipelineKey key) {
EXIT_IF(samples == vk::SampleCountFlagBits {} || key.format == vk::Format::eUndefined);
std::array<vk::PipelineShaderStageCreateInfo, 2> stages {};
stages[0].sType = vk::StructureType::ePipelineShaderStageCreateInfo;
stages[0].stage = vk::ShaderStageFlagBits::eVertex;
stages[0].module = m_vertex_shader;
stages[0].pName = "main";
stages[1].sType = vk::StructureType::ePipelineShaderStageCreateInfo;
stages[1].stage = vk::ShaderStageFlagBits::eFragment;
stages[1].module = m_fragment_shader;
stages[1].pName = "main";
vk::PipelineVertexInputStateCreateInfo vertex_input {};
vertex_input.sType = vk::StructureType::ePipelineVertexInputStateCreateInfo;
vk::PipelineInputAssemblyStateCreateInfo input_assembly {};
input_assembly.sType = vk::StructureType::ePipelineInputAssemblyStateCreateInfo;
input_assembly.topology = vk::PrimitiveTopology::eTriangleList;
vk::PipelineViewportStateCreateInfo viewport {};
viewport.sType = vk::StructureType::ePipelineViewportStateCreateInfo;
viewport.viewportCount = 1;
viewport.scissorCount = 1;
vk::PipelineRasterizationStateCreateInfo rasterization {};
rasterization.sType = vk::StructureType::ePipelineRasterizationStateCreateInfo;
rasterization.polygonMode = vk::PolygonMode::eFill;
rasterization.cullMode = vk::CullModeFlagBits::eNone;
rasterization.lineWidth = 1.0f;
rasterization.lineWidth = 1.0f;
vk::PipelineMultisampleStateCreateInfo multisample {};
multisample.sType = vk::StructureType::ePipelineMultisampleStateCreateInfo;
multisample.rasterizationSamples = samples;
vk::PipelineDepthStencilStateCreateInfo depth {};
depth.sType = vk::StructureType::ePipelineDepthStencilStateCreateInfo;
depth.depthTestEnable = VK_TRUE;
depth.depthWriteEnable = VK_TRUE;
depth.depthCompareOp = vk::CompareOp::eAlways;
vk::PipelineColorBlendStateCreateInfo color_blend {};
color_blend.sType = vk::StructureType::ePipelineColorBlendStateCreateInfo;
const std::array dynamic_states {vk::DynamicState::eViewport, vk::DynamicState::eScissor};
vk::PipelineDynamicStateCreateInfo dynamic {};
dynamic.sType = vk::StructureType::ePipelineDynamicStateCreateInfo;
dynamic.dynamicStateCount = static_cast<uint32_t>(dynamic_states.size());
dynamic.pDynamicStates = dynamic_states.data();
vk::PipelineRenderingCreateInfo rendering {};
rendering.sType = vk::StructureType::ePipelineRenderingCreateInfo;
rendering.depthAttachmentFormat = key.format;
vk::GraphicsPipelineCreateInfo create {};
create.sType = vk::StructureType::eGraphicsPipelineCreateInfo;
create.pNext = &rendering;
create.stageCount = static_cast<uint32_t>(stages.size());
create.pStages = stages.data();
@@ -186,7 +156,6 @@ void BlitHelper::ReinterpretColorAsMsDepth(Image& source, Image& destination) {
command);
vk::RenderingAttachmentInfo depth_attachment {};
depth_attachment.sType = vk::StructureType::eRenderingAttachmentInfo;
depth_attachment.imageView = destination_view;
depth_attachment.imageLayout = ColorToMsDepthLayout;
depth_attachment.loadOp = vk::AttachmentLoadOp::eClear;
@@ -194,7 +163,6 @@ void BlitHelper::ReinterpretColorAsMsDepth(Image& source, Image& destination) {
depth_attachment.clearValue.depthStencil = {0.0f, 0};
vk::RenderingInfo rendering {};
rendering.sType = vk::StructureType::eRenderingInfo;
rendering.renderArea.extent = {destination_info.extent.width, destination_info.extent.height};
rendering.layerCount = 1;
rendering.pDepthAttachment = &depth_attachment;
@@ -204,7 +172,6 @@ void BlitHelper::ReinterpretColorAsMsDepth(Image& source, Image& destination) {
descriptor_image.imageView = source_view;
descriptor_image.imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
vk::WriteDescriptorSet descriptor_write {};
descriptor_write.sType = vk::StructureType::eWriteDescriptorSet;
descriptor_write.dstBinding = 0;
descriptor_write.descriptorCount = 1;
descriptor_write.descriptorType = vk::DescriptorType::eSampledImage;
@@ -37,8 +37,7 @@ private:
vk::Pipeline handle = nullptr;
};
[[nodiscard]] vk::ShaderModule CreateShader(const uint32_t* code, size_t words) const;
[[nodiscard]] vk::Pipeline GetPipeline(PipelineKey key);
[[nodiscard]] vk::Pipeline GetPipeline(PipelineKey key);
GraphicContext& m_graphics;
CommandScheduler& m_scheduler;
+48 -67
View File
@@ -26,12 +26,13 @@ namespace {
}
}
[[nodiscard]] vk::ImageCreateFlags ImageCreateFlags(const ImageInfo& info) {
[[nodiscard]] vk::ImageCreateFlags ImageCreateFlags(const GraphicContext& graphics,
const ImageInfo& info) {
vk::ImageCreateFlags flags {};
if (DepthAspectTransferFormat(info.pixel_format) == vk::Format::eUndefined) {
flags |= vk::ImageCreateFlagBits::eMutableFormat;
flags |= vk::ImageCreateFlagBits::eExtendedUsage;
if (Prospero::BlockCompressedBytesPerBlock(info.guest_format) != 0) {
if (info.IsBlock() && graphics.supports_block_texel_view) {
flags |= vk::ImageCreateFlagBits::eBlockTexelViewCompatible;
}
}
@@ -47,6 +48,10 @@ namespace {
}
[[nodiscard]] vk::ImageUsageFlags ImageUsageFlags(GraphicContext& graphics, const ImageInfo& info) {
if (info.IsBlock()) {
return vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst |
vk::ImageUsageFlagBits::eSampled;
}
const auto properties = graphics.GetFormatProperties(info.pixel_format);
auto usage = vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst;
if (HasFormatFeature(properties, vk::FormatFeatureFlagBits::eSampledImage)) {
@@ -54,21 +59,16 @@ namespace {
}
if (DepthAspectTransferFormat(info.pixel_format) != vk::Format::eUndefined) {
usage |= vk::ImageUsageFlagBits::eDepthStencilAttachment;
if (graphics.attachment_feedback_loop_enabled && (usage & vk::ImageUsageFlagBits::eSampled)) {
usage |= vk::ImageUsageFlagBits::eAttachmentFeedbackLoopEXT;
}
return usage;
}
if (HasFormatFeature(properties, vk::FormatFeatureFlagBits::eColorAttachment)) {
usage |= vk::ImageUsageFlagBits::eColorAttachment;
}
if (info.samples == 1 &&
HasFormatFeature(properties, vk::FormatFeatureFlagBits::eStorageImage)) {
if (info.samples == 1) {
usage |= vk::ImageUsageFlagBits::eStorage;
} else if (info.samples == 1) {
const auto compatible = SrgbStorageViewFormat(info.pixel_format);
if (compatible != vk::Format::eUndefined &&
HasFormatFeature(graphics.GetFormatProperties(compatible),
vk::FormatFeatureFlagBits::eStorageImage)) {
usage |= vk::ImageUsageFlagBits::eStorage;
}
}
return usage;
}
@@ -210,9 +210,7 @@ void Image::Transit(vk::ImageLayout destination_layout, vk::AccessFlags2 destina
if (barriers.empty()) {
return;
}
if (m_scheduler != nullptr) {
m_scheduler->EndRendering();
}
m_scheduler.EndRendering();
vk::DependencyInfo dependency {};
dependency.imageMemoryBarrierCount = static_cast<uint32_t>(barriers.size());
dependency.pImageMemoryBarriers = barriers.data();
@@ -221,8 +219,8 @@ void Image::Transit(vk::ImageLayout destination_layout, vk::AccessFlags2 destina
void Image::Upload(std::span<const vk::BufferImageCopy> copies, vk::Buffer buffer, uint64_t offset,
uint64_t size) {
EXIT_IF(m_scheduler == nullptr || copies.empty() || buffer == nullptr || size == 0);
m_scheduler->EndRendering();
EXIT_IF(copies.empty() || buffer == nullptr || size == 0);
m_scheduler.EndRendering();
vk::BufferMemoryBarrier2 buffer_barrier {};
buffer_barrier.srcStageMask = vk::PipelineStageFlagBits2::eAllCommands;
buffer_barrier.srcAccessMask = vk::AccessFlagBits2::eMemoryWrite;
@@ -242,7 +240,7 @@ void Image::Upload(std::span<const vk::BufferImageCopy> copies, vk::Buffer buffe
dependency.pBufferMemoryBarriers = &buffer_barrier;
dependency.imageMemoryBarrierCount = static_cast<uint32_t>(image_barriers.size());
dependency.pImageMemoryBarriers = image_barriers.data();
auto command = m_scheduler->Current().Handle();
auto command = m_scheduler.Current().Handle();
command.pipelineBarrier2(dependency);
command.copyBufferToImage(buffer, backing.image, vk::ImageLayout::eTransferDstOptimal,
static_cast<uint32_t>(copies.size()), copies.data());
@@ -260,8 +258,8 @@ void Image::Upload(std::span<const vk::BufferImageCopy> copies, vk::Buffer buffe
void Image::Download(std::span<const vk::BufferImageCopy> copies, vk::Buffer buffer,
uint64_t offset, uint64_t size) {
EXIT_IF(m_scheduler == nullptr || copies.empty() || buffer == nullptr || size == 0);
m_scheduler->EndRendering();
EXIT_IF(copies.empty() || buffer == nullptr || size == 0);
m_scheduler.EndRendering();
vk::BufferMemoryBarrier2 buffer_barrier {};
buffer_barrier.srcStageMask = vk::PipelineStageFlagBits2::eAllCommands;
buffer_barrier.srcAccessMask =
@@ -282,7 +280,7 @@ void Image::Download(std::span<const vk::BufferImageCopy> copies, vk::Buffer buf
dependency.pBufferMemoryBarriers = &buffer_barrier;
dependency.imageMemoryBarrierCount = static_cast<uint32_t>(image_barriers.size());
dependency.pImageMemoryBarriers = image_barriers.data();
auto command = m_scheduler->Current().Handle();
auto command = m_scheduler.Current().Handle();
command.pipelineBarrier2(dependency);
command.copyImageToBuffer(backing.image, vk::ImageLayout::eTransferSrcOptimal, buffer,
static_cast<uint32_t>(copies.size()), copies.data());
@@ -319,8 +317,8 @@ std::pair<uint32_t, uint32_t> Image::SanitizeCopyLayers(const Image& source,
}
void Image::CopyImage(Image& source) {
EXIT_IF(m_scheduler == nullptr || source.backing.samples != backing.samples);
m_scheduler->EndRendering();
EXIT_IF(source.backing.samples != backing.samples);
m_scheduler.EndRendering();
const uint32_t levels = std::min(source.backing.mip_levels, backing.mip_levels);
const uint32_t base_depth = backing.image_type == vk::ImageType::e3D
? backing.extent.depth
@@ -359,7 +357,7 @@ void Image::CopyImage(Image& source) {
if (copies.empty()) {
return;
}
auto command = m_scheduler->Current().Handle();
auto command = m_scheduler.Current().Handle();
source.Transit(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits2::eTransferRead, {},
command);
Transit(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits2::eTransferWrite, {}, command);
@@ -372,8 +370,7 @@ void Image::CopyImage(Image& source) {
void Image::Resolve(Image& source, const ImageSubresourceRange& source_range,
const ImageSubresourceRange& destination_range) {
EXIT_IF(m_scheduler == nullptr || backing.samples != 1 ||
source.backing.image_type != vk::ImageType::e2D ||
EXIT_IF(backing.samples != 1 || source.backing.image_type != vk::ImageType::e2D ||
backing.image_type != vk::ImageType::e2D || source_range.level_count != 1 ||
destination_range.level_count != 1 ||
source_range.base_level >= source.backing.mip_levels ||
@@ -401,8 +398,8 @@ void Image::Resolve(Image& source, const ImageSubresourceRange& source_range,
resolved_destination_range.layer_count = layers;
const vk::Extent3D resolve_extent {info.extent.width, info.extent.height, 1};
m_scheduler->EndRendering();
auto command = m_scheduler->Current().Handle();
m_scheduler.EndRendering();
auto command = m_scheduler.Current().Handle();
source.Transit(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits2::eTransferRead,
resolved_source_range, command);
Transit(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits2::eTransferWrite,
@@ -438,9 +435,8 @@ uint32_t Image::CopyRows(uint64_t row_size, uint32_t rows, uint64_t capacity) no
}
void Image::CopyImageWithBuffer(Image& source, Buffer& buffer) {
EXIT_IF(m_scheduler == nullptr || buffer.Handle() == nullptr || source.backing.samples != 1 ||
backing.samples != 1);
m_scheduler->EndRendering();
EXIT_IF(buffer.Handle() == nullptr || source.backing.samples != 1 || backing.samples != 1);
m_scheduler.EndRendering();
const uint32_t levels = std::min(source.backing.mip_levels, backing.mip_levels);
const auto source_aspect =
FullAspectMask(source.backing.format) & ~vk::ImageAspectFlagBits::eStencil;
@@ -470,7 +466,7 @@ void Image::CopyImageWithBuffer(Image& source, Buffer& buffer) {
dependency.dependencyFlags = vk::DependencyFlagBits::eByRegion;
dependency.bufferMemoryBarrierCount = 1;
dependency.pBufferMemoryBarriers = &barrier;
auto command = m_scheduler->Current().Handle();
auto command = m_scheduler.Current().Handle();
source.Transit(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits2::eTransferRead, {},
command);
Transit(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits2::eTransferWrite, {}, command);
@@ -530,9 +526,9 @@ void Image::CopyImageWithBuffer(Image& source, Buffer& buffer) {
}
void Image::CopyMip(Image& source, uint32_t mip, uint32_t layer) {
EXIT_IF(m_scheduler == nullptr || source.backing.samples != backing.samples ||
mip >= backing.mip_levels || layer >= backing.layers);
m_scheduler->EndRendering();
EXIT_IF(source.backing.samples != backing.samples || mip >= backing.mip_levels ||
layer >= backing.layers);
m_scheduler.EndRendering();
const auto width = std::max(backing.extent.width >> mip, 1u);
const auto height = std::max(backing.extent.height >> mip, 1u);
const auto depth = std::max(backing.extent.depth >> mip, 1u);
@@ -552,7 +548,7 @@ void Image::CopyMip(Image& source, uint32_t mip, uint32_t layer) {
copy.dstSubresource = {aspect, mip, layer, destination_layers};
copy.extent = {width, height, depth};
}
auto command = m_scheduler->Current().Handle();
auto command = m_scheduler.Current().Handle();
Transit(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits2::eTransferWrite, {}, command);
source.Transit(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits2::eTransferRead, {},
command);
@@ -656,7 +652,7 @@ Prospero::BufferFormat RenderTargetTransferFormat(uint32_t bytes_per_element) {
} // namespace ImageOps
Image::Image(GraphicContext& graphics, CommandScheduler& scheduler, const ImageInfo& image_info)
: info(image_info), m_graphics(&graphics), m_scheduler(&scheduler) {
: info(image_info), m_graphics(graphics), m_scheduler(scheduler) {
KYTY_PROFILER_FUNCTION();
ImageOps::Validate(info);
m_cpu_dirty =
@@ -665,29 +661,17 @@ Image::Image(GraphicContext& graphics, CommandScheduler& scheduler, const ImageI
return;
}
backing.format = info.pixel_format;
backing.image_type = HostImageType(info.type);
backing.extent = info.extent;
backing.guest_pitch = info.pitch;
backing.layers = info.IsVolume() ? 1u : info.resources.layers;
backing.mip_levels = info.resources.levels;
backing.samples = info.samples;
backing.flags = ImageCreateFlags(info);
backing.usage = ImageUsageFlags(graphics, info);
vk::ImageCreateInfo create {};
create.sType = vk::StructureType::eImageCreateInfo;
create.flags = backing.flags;
create.imageType = backing.image_type;
create.extent = backing.extent;
create.mipLevels = backing.mip_levels;
create.arrayLayers = backing.layers;
create.format = backing.format;
create.flags = ImageCreateFlags(graphics, info);
create.imageType = HostImageType(info.type);
create.extent = info.extent;
create.mipLevels = info.resources.levels;
create.arrayLayers = info.IsVolume() ? 1u : info.resources.layers;
create.format = info.pixel_format;
create.tiling = vk::ImageTiling::eOptimal;
create.initialLayout = backing.state.layout;
create.usage = backing.usage;
create.sharingMode = vk::SharingMode::eExclusive;
create.samples = vulkan_sample_count(backing.samples);
create.initialLayout = vk::ImageLayout::eUndefined;
create.usage = ImageUsageFlags(graphics, info);
create.samples = vulkan_sample_count(info.samples);
vk::ImageFormatProperties properties {};
if (graphics.GetImageFormatProperties(create.format, create.imageType, create.tiling,
@@ -698,10 +682,9 @@ Image::Image(GraphicContext& graphics, CommandScheduler& scheduler, const ImageI
"flags=0x%x samples=%u\n",
static_cast<int>(create.format), static_cast<int>(create.imageType),
static_cast<vk::ImageUsageFlags::MaskType>(create.usage),
static_cast<vk::ImageCreateFlags::MaskType>(create.flags), backing.samples);
static_cast<vk::ImageCreateFlags::MaskType>(create.flags), info.samples);
}
backing.memory.property = vk::MemoryPropertyFlagBits::eDeviceLocal;
if (!graphics.CreateImage(create, backing)) {
EXIT("failed to create image: extent=%ux%ux%u format=%d layers=%u levels=%u\n",
create.extent.width, create.extent.height, create.extent.depth,
@@ -710,11 +693,12 @@ Image::Image(GraphicContext& graphics, CommandScheduler& scheduler, const ImageI
}
uint64_t Image::HashGuestEdges() const {
constexpr uint64_t page_mask = TRACKER_PAGE_SIZE - 1;
std::array<uint8_t, TRACKER_PAGE_SIZE * 2> bytes {};
const auto range = info.data;
const uint64_t head_end = std::min(range.End(), (range.address + page_mask) & ~page_mask);
const uint64_t tail_begin = std::max(range.address, range.End() & ~page_mask);
const uint64_t head_end =
std::min(range.End(), Common::AlignUp(range.address, TRACKER_PAGE_SIZE));
const uint64_t tail_begin =
std::max(range.address, Common::AlignDown(range.End(), TRACKER_PAGE_SIZE));
const uint64_t head_size = head_end - range.address;
const uint64_t tail_address = tail_begin < head_end ? head_end : tail_begin;
const uint64_t tail_size = range.End() - tail_address;
@@ -729,16 +713,13 @@ uint64_t Image::HashGuestEdges() const {
Image::~Image() {
KYTY_PROFILER_FUNCTION();
if (m_graphics == nullptr) {
return;
}
for (const auto& cached: views) {
if (cached.view != nullptr) {
m_graphics->device.destroyImageView(cached.view, nullptr);
m_graphics.device.destroyImageView(cached.view, nullptr);
}
}
if (backing.image != nullptr) {
m_graphics->DeleteImage(backing);
m_graphics.DeleteImage(backing);
}
}
+11 -12
View File
@@ -1,6 +1,7 @@
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_IMAGE_H_
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_IMAGE_H_
#include "common/alignment.h"
#include "common/assert.h"
#include "common/slotVector.h"
#include "graphics/host_gpu/graphicContext.h"
@@ -35,11 +36,13 @@ struct ImageUsage {
};
struct ImageBinding {
bool is_bound = false;
bool is_target = false;
bool needs_rebind = false;
bool force_general = false;
bool shader_write = false;
vk::ImageLayout attachment_layout = vk::ImageLayout::eUndefined;
vk::AccessFlags2 attachment_access;
bool is_bound = false;
bool is_target = false;
bool needs_rebind = false;
bool force_general = false;
bool shader_write = false;
};
class Image final {
@@ -49,7 +52,6 @@ public:
KYTY_CLASS_NO_COPY(Image);
[[nodiscard]] vk::ImageView FindView(const ImageViewInfo& view_info);
void AssociateDepth(ImageId image_id) { depth_id = image_id; }
using Barriers = std::vector<vk::ImageMemoryBarrier2>;
[[nodiscard]] Barriers GetBarriers(vk::ImageLayout destination_layout,
vk::AccessFlags2 destination_access,
@@ -127,15 +129,12 @@ public:
return pages ? ImagePageRangesOverlap(info.data.address, info.data.size, address, size)
: ImageRangeOverlaps(info.data.address, info.data.size, address, size);
}
[[nodiscard]] bool GpuOverlaps(uint64_t address, uint64_t size) const noexcept {
return IsGpuModified() && Overlaps(address, size);
}
[[nodiscard]] bool SafeToDownload() const noexcept {
return IsGpuModified() && !IsBufferModified() && !IsCpuDirty();
}
[[nodiscard]] bool IsTracked() const noexcept { return track_addr != 0 && track_addr_end != 0; }
[[nodiscard]] uint64_t AccountedSize() const noexcept {
return backing.image == nullptr ? 0 : (info.data.size + 1023) & ~uint64_t {1023};
return backing.image == nullptr ? 0 : Common::AlignUp(info.data.size, 1024);
}
[[nodiscard]] uint64_t HashGuestEdges() const;
@@ -161,8 +160,8 @@ private:
[[nodiscard]] static std::pair<uint32_t, uint32_t>
SanitizeCopyLayers(const Image& source, const Image& destination, uint32_t depth);
GraphicContext* m_graphics = nullptr;
CommandScheduler* m_scheduler = nullptr;
GraphicContext& m_graphics;
CommandScheduler& m_scheduler;
uint64_t m_maybe_cpu_hash = 0;
bool m_cpu_dirty = false;
bool m_maybe_cpu_dirty = false;
+48 -103
View File
@@ -23,8 +23,11 @@ struct ImageMetadataInfo {
GuestRange range;
ImageMetadataKind kind = ImageMetadataKind::None;
uint32_t control = 0;
uint32_t dcc_clear_word = 0;
VideoOutCompression compression = VideoOutCompression::Uncompressed;
bool stencil_compressed = false;
bool dcc_clear_register_valid = false;
bool dcc_alpha_msb = true;
};
struct ImageSubresources {
@@ -44,6 +47,7 @@ struct ImageSubresourceRange {
struct ImageMipInfo {
uint64_t offset = 0;
uint64_t size = 0;
// Padded dimensions in storage elements (compressed blocks for BC formats).
uint32_t pitch = 0;
uint32_t height = 0;
auto operator<=>(const ImageMipInfo&) const = default;
@@ -78,9 +82,6 @@ struct ImageInfo {
[[nodiscard]] constexpr bool IsVolume() const noexcept {
return type == Prospero::ImageType::kColor3D;
}
[[nodiscard]] constexpr bool IsLayered() const noexcept {
return !IsVolume() && resources.layers > 1;
}
[[nodiscard]] constexpr uint32_t TransferLayers() const noexcept {
return IsVolume() ? extent.depth : resources.layers;
}
@@ -93,9 +94,24 @@ struct ImageInfo {
bytes_per_block == other.bytes_per_block;
}
[[nodiscard]] int32_t MipOf(const ImageInfo& container) const noexcept {
if (!IsCompatible(container) || tile_mode != container.tile_mode || resources.levels != 1 ||
container.resources.layers == 0 ||
container.resources.levels > container.mip_layout.size()) {
if (container.resources.levels > container.mip_layout.size()) {
return -1;
}
for (uint32_t level = 0; level < container.resources.levels; level++) {
if (SliceOf(container, static_cast<int32_t>(level)) >= 0) {
return static_cast<int32_t>(level);
}
}
return -1;
}
[[nodiscard]] int32_t SliceOf(const ImageInfo& container, int32_t mip) const noexcept {
if (!IsCompatible(container) || tile_mode != container.tile_mode || type != container.type ||
IsVolume() || resources.levels != 1 || resources.layers == 0 ||
container.resources.layers == 0 || mip < 0 ||
static_cast<uint32_t>(mip) >= container.resources.levels ||
container.resources.levels > container.mip_layout.size() ||
!data.Valid() || !container.data.Valid() || data.address < container.data.address ||
data.End() > container.data.End()) {
return -1;
}
if (HasStencil() != container.HasStencil() ||
@@ -104,75 +120,36 @@ struct ImageInfo {
return -1;
}
int32_t mip = -1;
for (uint32_t level = 0; level < container.resources.levels; level++) {
const auto& layout = container.mip_layout[level];
if (layout.size == 0 || layout.size % container.resources.layers != 0 ||
container.data.address > UINT64_MAX - layout.offset) {
continue;
}
const auto mip_base = container.data.address + layout.offset;
const auto slice_size = layout.size / container.resources.layers;
if (slice_size == 0 || mip_base > UINT64_MAX - layout.size) {
continue;
}
const auto mip_end = mip_base + layout.size;
if (data.address >= mip_base && data.address < mip_end &&
(data.address - mip_base) % slice_size == 0) {
mip = static_cast<int32_t>(level);
break;
}
}
if (mip < 0) {
const auto level = static_cast<uint32_t>(mip);
const auto& layout = container.mip_layout[level];
if (extent.width != std::max(container.extent.width >> level, 1u) ||
extent.height != std::max(container.extent.height >> level, 1u) ||
extent.depth != container.extent.depth || mip_layout[0].pitch != layout.pitch ||
mip_layout[0].height != layout.height || layout.size == 0 ||
layout.size % container.resources.layers != 0 ||
container.data.size % container.resources.layers != 0 ||
data.size % resources.layers != 0) {
return -1;
}
const auto level = static_cast<uint32_t>(mip);
if (extent.width != std::max(container.extent.width >> level, 1u) ||
extent.height != std::max(container.extent.height >> level, 1u)) {
// PS5 block slices contain the entire reversed mip chain.
const auto slice_stride = container.data.size / container.resources.layers;
const auto child_stride = data.size / resources.layers;
if (child_stride != layout.size / container.resources.layers ||
layout.offset >= slice_stride || child_stride > slice_stride - layout.offset ||
(resources.layers > 1 && child_stride != slice_stride)) {
return -1;
}
const auto mip_depth = std::max(container.extent.depth >> level, 1u);
if (container.type == Prospero::ImageType::kColor3D &&
type == Prospero::ImageType::kColor2D) {
if (resources.layers != mip_depth) {
return -1;
}
} else if (type != container.type) {
const auto address_delta = data.address - container.data.address;
if (address_delta < layout.offset || (address_delta - layout.offset) % slice_stride != 0) {
return -1;
}
return mip;
}
[[nodiscard]] int32_t SliceOf(const ImageInfo& container, int32_t mip) const noexcept {
if (!IsCompatible(container) || type != container.type || mip < 0 ||
static_cast<uint32_t>(mip) >= container.resources.levels ||
container.resources.levels > container.mip_layout.size() ||
container.resources.layers == 0 || data.size == 0) {
const auto layer = (address_delta - layout.offset) / slice_stride;
if (layer > INT32_MAX || layer >= container.resources.layers ||
resources.layers > container.resources.layers - layer) {
return -1;
}
const auto level = static_cast<uint32_t>(mip);
if (extent.width != std::max(container.extent.width >> level, 1u) ||
extent.height != std::max(container.extent.height >> level, 1u)) {
return -1;
}
const auto& layout = container.mip_layout[level];
if (layout.size == 0 || layout.size % container.resources.layers != 0 ||
container.data.address > UINT64_MAX - layout.offset) {
return -1;
}
const auto slice_size = layout.size / container.resources.layers;
if (slice_size == 0 || data.size % slice_size != 0) {
return -1;
}
const auto mip_base = container.data.address + layout.offset;
if (data.address < mip_base) {
return -1;
}
const auto address_delta = data.address - mip_base;
if (address_delta % data.size != 0 || address_delta / data.size > INT32_MAX) {
return -1;
}
return static_cast<int32_t>(address_delta / data.size);
return static_cast<int32_t>(layer);
}
};
@@ -182,6 +159,7 @@ struct ImageViewInfo {
vk::ImageAspectFlags aspect = vk::ImageAspectFlagBits::eColor;
uint32_t base_level = 0;
uint32_t level_count = 1;
uint32_t min_lod = 0; // U4.8 clamp relative to base_level.
uint32_t base_layer = 0;
uint32_t layer_count = 1;
vk::ComponentMapping mapping = {};
@@ -190,9 +168,10 @@ struct ImageViewInfo {
[[nodiscard]] bool operator==(const ImageViewInfo& rhs) const noexcept {
return format == rhs.format && type == rhs.type && aspect == rhs.aspect &&
base_level == rhs.base_level && level_count == rhs.level_count &&
base_layer == rhs.base_layer && layer_count == rhs.layer_count &&
mapping.r == rhs.mapping.r && mapping.g == rhs.mapping.g &&
mapping.b == rhs.mapping.b && mapping.a == rhs.mapping.a && usage == rhs.usage;
min_lod == rhs.min_lod && base_layer == rhs.base_layer &&
layer_count == rhs.layer_count && mapping.r == rhs.mapping.r &&
mapping.g == rhs.mapping.g && mapping.b == rhs.mapping.b &&
mapping.a == rhs.mapping.a && usage == rhs.usage;
}
};
@@ -338,14 +317,6 @@ ClassifyVideoOutCompression(bool compressed, uint64_t metadata_address, uint32_t
}
}
[[nodiscard]] inline constexpr bool
CanUseVideoOutNativeWithoutUpload(VideoOutCompression compression, bool render_target,
bool gpu_modified, bool guest_modified) noexcept {
return compression != VideoOutCompression::Uncompressed &&
compression != VideoOutCompression::Unsupported && !guest_modified &&
(render_target || gpu_modified);
}
struct VideoOutPixelFormatInfo {
vk::Format format = vk::Format::eUndefined;
Prospero::BufferFormat guest_format = Prospero::BufferFormat::kInvalid;
@@ -396,32 +367,6 @@ inline constexpr std::array<VideoOutFormatPolicy, 6> VIDEO_OUT_FORMAT_POLICIES {
return false;
}
[[nodiscard]] inline constexpr bool
IsSupportedDisplayRenderTargetTileMode(Prospero::TileMode tile_mode) noexcept {
return tile_mode == Prospero::TileMode::kRenderTarget;
}
[[nodiscard]] inline constexpr bool IsSupportedStandard64RenderTarget(const ImageInfo& info) {
if (info.tile_mode != Prospero::TileMode::kStandard64KB || info.data.address == 0 ||
(info.data.address & 0xffffu) != 0 || info.extent.width == 0 || info.extent.height == 0 ||
info.bytes_per_block != 4 || info.resources.levels != 1 || info.resources.layers != 1 ||
info.samples != 1) {
return false;
}
const auto expected_pitch =
(static_cast<uint64_t>(info.extent.width) + 127u) & ~uint64_t {127u};
const auto padded_height =
(static_cast<uint64_t>(info.extent.height) + 127u) & ~uint64_t {127u};
return expected_pitch <= UINT32_MAX && info.pitch == expected_pitch &&
expected_pitch <= UINT64_MAX / padded_height / info.bytes_per_block &&
info.data.size == expected_pitch * padded_height * info.bytes_per_block;
}
[[nodiscard]] inline constexpr bool IsTiledRenderTarget(const ImageInfo& info) noexcept {
return info.tile_mode == Prospero::TileMode::kRenderTarget ||
IsSupportedStandard64RenderTarget(info);
}
[[nodiscard]] inline bool DecodePackedColorClear(vk::Format format, uint32_t packed,
vk::ClearColorValue& clear) {
vk::ClearColorValue next {};
@@ -21,10 +21,6 @@ namespace {
}
}
[[nodiscard]] bool IsCompatibleViewFormat(vk::Format image_format, vk::Format view_format) {
return ImageViewOps::FormatsCompatible(image_format, view_format);
}
[[nodiscard]] bool IsStencilViewFormat(vk::Format format) {
switch (format) {
case vk::Format::eS8Uint:
@@ -326,8 +322,14 @@ vk::ImageView Image::FindView(const ImageViewInfo& view_info) {
normalized.aspect = vk::ImageAspectFlagBits::eStencil;
}
normalized.usage = is_storage ? vk::ImageUsageFlagBits::eStorage : vk::ImageUsageFlags {};
for (const auto& cached: views) {
if (cached.info == normalized) {
return cached.view;
}
}
const bool format_compatible = normalized.format != vk::Format::eUndefined &&
IsCompatibleViewFormat(image.format, normalized.format);
ImageViewOps::FormatsCompatible(image.format, normalized.format);
const bool slice_view =
image.image_type == vk::ImageType::e3D && (normalized.type == vk::ImageViewType::e2D ||
normalized.type == vk::ImageViewType::e2DArray);
@@ -355,20 +357,18 @@ vk::ImageView Image::FindView(const ImageViewInfo& view_info) {
image.layers);
}
for (const auto& cached: views) {
if (cached.info == normalized) {
return cached.view;
}
}
vk::ImageViewUsageCreateInfo usage {};
usage.sType = vk::StructureType::eImageViewUsageCreateInfo;
usage.usage = image.usage;
if (!is_storage) {
usage.usage &= ~vk::ImageUsageFlagBits::eStorage;
}
vk::ImageViewMinLodCreateInfoEXT min_lod {};
if (normalized.min_lod != 0) {
min_lod.minLod = static_cast<float>(normalized.base_level) +
static_cast<float>(normalized.min_lod) / 256.0f;
usage.pNext = &min_lod;
}
vk::ImageViewCreateInfo create {};
create.sType = vk::StructureType::eImageViewCreateInfo;
create.pNext = &usage;
create.image = image.image;
create.viewType = normalized.type;
@@ -381,7 +381,7 @@ vk::ImageView Image::FindView(const ImageViewInfo& view_info) {
create.subresourceRange.layerCount = normalized.layer_count;
vk::ImageView view = nullptr;
const auto result = m_graphics->device.createImageView(&create, nullptr, &view);
const auto result = m_graphics.device.createImageView(&create, nullptr, &view);
if (result != vk::Result::eSuccess || view == nullptr) {
EXIT("failed to create image view: result=%d image_format=%d view_format=%d type=%d "
"aspect=0x%x mip=%u+%u layer=%u+%u usage=0x%x\n",
@@ -92,29 +92,18 @@ SelectSampledColorView(vk::Format image_format, vk::Format view_format, uint32_t
}
}
[[nodiscard]] inline uint32_t
SelectSampledDepthView(vk::Format image_format, vk::Format view_format, uint32_t swizzle) noexcept {
if (IsSupportedSampledDepthView(image_format, view_format, swizzle)) {
return swizzle;
}
EXIT("unsupported sampled depth image view: image_format=%d view_format=%d swizzle=0x%03x\n",
static_cast<int>(image_format), static_cast<int>(view_format), swizzle);
}
[[nodiscard]] inline bool
IsSupportedSampledDepthResource(const ShaderRecompiler::IR::ImageResource& resource) noexcept {
if (resource.kind != ShaderRecompiler::IR::ResourceKind::Image &&
resource.kind != ShaderRecompiler::IR::ResourceKind::ImageUint) {
if (resource.resource_class != ShaderRecompiler::IR::ImageResourceClass::Sampled) {
return false;
}
if (resource.kind == ShaderRecompiler::IR::ResourceKind::ImageUint && resource.depth_compare) {
if (resource.numeric_class == Prospero::TextureNumericClass::Unsupported) {
return false;
}
return (resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2D ||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2DArray ||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2DMsaa ||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2DMsaaArray) &&
resource.mip_mode == ShaderRecompiler::IR::ImageMipMode::None && resource.read &&
if (resource.numeric_class != Prospero::TextureNumericClass::Float && resource.depth_compare) {
return false;
}
return resource.mip_mode == ShaderRecompiler::IR::ImageMipMode::None && resource.read &&
!resource.written && !resource.atomic;
}
@@ -128,8 +117,9 @@ inline void ValidateStorageColorView(vk::Format image_format, vk::Format view_fo
[[nodiscard]] inline bool
IsSupportedStorageImageResource(const ShaderRecompiler::IR::ImageResource& resource) noexcept {
return (resource.kind == ShaderRecompiler::IR::ResourceKind::StorageImage ||
resource.kind == ShaderRecompiler::IR::ResourceKind::StorageImageUint) &&
return resource.resource_class == ShaderRecompiler::IR::ImageResourceClass::Storage &&
(resource.numeric_class == Prospero::TextureNumericClass::Float ||
resource.numeric_class == Prospero::TextureNumericClass::Uint) &&
(resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim1D ||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim1DArray ||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2D ||
@@ -141,19 +131,19 @@ IsSupportedStorageImageResource(const ShaderRecompiler::IR::ImageResource& resou
resource.mip_count != 0u)) &&
resource.written &&
(!resource.atomic ||
(resource.kind == ShaderRecompiler::IR::ResourceKind::StorageImageUint &&
resource.read)) &&
(resource.numeric_class == Prospero::TextureNumericClass::Uint && resource.read)) &&
!resource.depth_compare;
}
inline void
ValidateStorageImageResource(const ShaderRecompiler::IR::ImageResource& resource) noexcept {
if (!IsSupportedStorageImageResource(resource)) {
EXIT("unsupported storage color image resource: kind=%u dimension=%u mip=%u "
EXIT("unsupported storage color image resource: class=%u numeric=%u dimension=%u mip=%u "
"read=%d written=%d atomic=%d depth_compare=%d\n",
static_cast<uint32_t>(resource.kind), static_cast<uint32_t>(resource.dimension),
static_cast<uint32_t>(resource.mip_mode), resource.read, resource.written,
resource.atomic, resource.depth_compare);
static_cast<uint32_t>(resource.resource_class),
static_cast<uint32_t>(resource.numeric_class),
static_cast<uint32_t>(resource.dimension), static_cast<uint32_t>(resource.mip_mode),
resource.read, resource.written, resource.atomic, resource.depth_compare);
}
}
@@ -1,15 +1,16 @@
#include "graphics/host_gpu/renderer/image/textureCommon.h"
#include "common/alignment.h"
#include "common/assert.h"
#include "graphics/guest_gpu/gpu_defs.h"
#include "graphics/guest_gpu/gpu_format.h"
#include "graphics/host_gpu/renderer/image/tiler.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include "graphics/shader/shader.h"
#include <algorithm>
#include <bit>
#include <array>
#include <cinttypes>
#include <cstring>
namespace Libs::Graphics {
namespace {
@@ -27,13 +28,13 @@ constexpr Prospero::ColorComponentMapping kRenderTargetColorMappings[4][4] = {
Prospero::ColorMappingArgb},
};
struct RenderTargetHostFormatInfo {
struct HostFormatInfo {
vk::Format format = vk::Format::eUndefined;
Prospero::ColorComponentMapping host_to_storage;
};
RenderTargetHostFormatInfo ResolveRenderTargetHostFormat(Prospero::BufferFormat guest_format,
Prospero::ChannelOrder order) {
HostFormatInfo ResolveHostFormat(Prospero::BufferFormat guest_format,
Prospero::ChannelOrder order) {
if (order == Prospero::ChannelOrder::kAlt) {
switch (guest_format) {
case Prospero::BufferFormat::k8_8_8_8UNorm:
@@ -47,17 +48,77 @@ RenderTargetHostFormatInfo ResolveRenderTargetHostFormat(Prospero::BufferFormat
default: break;
}
}
const auto format = VulkanFormat(guest_format);
switch (guest_format) {
case Prospero::BufferFormat::k5_5_5_1UNorm:
return {vk::Format::eA1R5G5B5UnormPack16, Prospero::ColorMappingBgra};
case Prospero::BufferFormat::k5_5_5_1UNorm: return {format, Prospero::ColorMappingBgra};
case Prospero::BufferFormat::k1_5_5_5UNorm:
return {vk::Format::eR5G5B5A1UnormPack16, Prospero::ColorMappingAbgr};
case Prospero::BufferFormat::k4_4_4_4UNorm:
return {vk::Format::eR4G4B4A4UnormPack16, Prospero::ColorMappingAbgr};
default: return {VulkanFormat(guest_format), {}};
case Prospero::BufferFormat::k4_4_4_4UNorm: return {format, Prospero::ColorMappingAbgr};
default: return {format, {}};
}
}
uint32_t GetTextureLevelDepth(uint32_t depth, uint32_t level, bool volume_texture) {
return volume_texture ? std::max(depth >> level, 1u) : depth;
}
size_t GetTextureRegionCount(uint32_t depth, uint32_t levels, bool volume_texture) {
size_t count = 0;
for (uint32_t level = 0; level < levels; level++) {
count += GetTextureLevelDepth(depth, level, volume_texture);
}
return count;
}
uint64_t CalcTextureSliceStride(const TextureUploadMipLayout* mips, uint32_t levels,
uint64_t total_size, uint32_t depth) {
uint64_t stride = 0;
for (uint32_t mip_level = 0; mip_level < levels; ++mip_level) {
stride = std::max(stride, mips[mip_level].offset + mips[mip_level].size);
}
if (depth > 1 && total_size != 0 && total_size % depth == 0) {
stride = std::max(stride, total_size / depth);
}
return stride;
}
uint64_t CalcLinearUploadLevelSize(const TileTextureElementLayout& element, uint32_t pitch,
uint32_t height) {
const uint32_t element_columns =
std::max((pitch + element.texel_width - 1u) / element.texel_width, 1u);
const uint32_t element_rows =
std::max((height + element.texel_height - 1u) / element.texel_height, 1u);
return static_cast<uint64_t>(element_columns) * element_rows * element.bytes;
}
uint64_t SetLinearUploadLevels(TextureUploadMipLayout* mips,
const TileTextureElementLayout& element, uint32_t height,
uint32_t levels, uint32_t base_pitch) {
uint64_t offset = 0;
auto mip_pitch = base_pitch;
auto mip_height = height;
for (uint32_t mip_level = 0; mip_level < levels; ++mip_level) {
const auto size = CalcLinearUploadLevelSize(element, mip_pitch, mip_height);
mips[mip_level] = {offset, size};
offset += size;
if (mip_pitch > 1) {
mip_pitch /= 2;
}
if (mip_height > 1) {
mip_height /= 2;
}
}
return offset;
}
bool FitsBufferRange(uint64_t offset, uint64_t size, uint64_t capacity) {
return offset <= capacity && size <= capacity - offset;
}
} // namespace
RenderTargetFormatInfo TextureGetRenderTargetFormat(Prospero::ChannelLayout layout,
@@ -65,135 +126,61 @@ RenderTargetFormatInfo TextureGetRenderTargetFormat(Prospero::ChannelLayout layo
Prospero::ChannelOrder order) {
const auto encoding = Prospero::ResolveRenderTargetFormat(layout, type);
if (encoding.IsValid() && encoding.SupportsOrder(order)) {
const auto host = ResolveRenderTargetHostFormat(encoding.buffer_format, order);
const auto bytes = Prospero::RenderTargetBytesPerElement(encoding.buffer_format);
if (host.format != vk::Format::eUndefined && bytes != 0) {
const auto host_format = ResolveHostFormat(encoding.buffer_format, order);
const auto bytes = Prospero::RenderTargetBytesPerElement(encoding.buffer_format);
if (host_format.format != vk::Format::eUndefined && bytes != 0) {
const auto order_mapping =
kRenderTargetColorMappings[static_cast<size_t>(order)][encoding.components - 1u];
return {host.format, bytes, host.host_to_storage.Then(order_mapping)};
return {host_format.format, bytes, host_format.host_to_storage.Then(order_mapping)};
}
}
EXIT("unsupported render-target format combination: layout=%u type=%u order=%u\n",
static_cast<uint32_t>(layout), static_cast<uint32_t>(type), static_cast<uint32_t>(order));
}
namespace {
static uint32_t GetTextureLevelDepth(uint32_t depth, uint32_t level, bool volume_texture) {
return volume_texture ? std::max(depth >> level, 1u) : depth;
}
static size_t GetTextureRegionCount(uint32_t depth, uint64_t levels, bool volume_texture) {
size_t count = 0;
for (uint32_t level = 0; level < levels; level++) {
count += GetTextureLevelDepth(depth, level, volume_texture);
vk::ComponentMapping TextureGetComponentMapping(uint32_t swizzle,
Prospero::ColorComponentMapping host_to_storage) {
constexpr std::array host_components {vk::ComponentSwizzle::eR, vk::ComponentSwizzle::eG,
vk::ComponentSwizzle::eB, vk::ComponentSwizzle::eA};
std::array<vk::ComponentSwizzle, 4> storage_components {};
for (uint32_t host_component = 0; host_component < host_components.size(); ++host_component) {
storage_components[host_to_storage.Map(host_component)] = host_components[host_component];
}
return count;
}
vk::ComponentSwizzle TextureGetComponentSwizzle(uint8_t s) {
switch (static_cast<Prospero::CompSwizzle>(s)) {
case Prospero::CompSwizzle::kZero: return vk::ComponentSwizzle::eZero;
case Prospero::CompSwizzle::kOne: return vk::ComponentSwizzle::eOne;
case Prospero::CompSwizzle::kRed: return vk::ComponentSwizzle::eR;
case Prospero::CompSwizzle::kGreen: return vk::ComponentSwizzle::eG;
case Prospero::CompSwizzle::kBlue: return vk::ComponentSwizzle::eB;
case Prospero::CompSwizzle::kAlpha: return vk::ComponentSwizzle::eA;
default: EXIT("unknown swizzle: %d\n", static_cast<int>(s));
}
return vk::ComponentSwizzle::eIdentity;
}
static uint32_t TextureGetDstSel(uint32_t swizzle, uint32_t channel) {
return (swizzle >> (channel * 3u)) & 0x7u;
}
} // namespace
vk::ComponentMapping TextureGetComponentMapping(uint32_t swizzle) {
vk::ComponentMapping components {};
components.r = TextureGetComponentSwizzle(static_cast<uint8_t>(TextureGetDstSel(swizzle, 0)));
components.g = TextureGetComponentSwizzle(static_cast<uint8_t>(TextureGetDstSel(swizzle, 1)));
components.b = TextureGetComponentSwizzle(static_cast<uint8_t>(TextureGetDstSel(swizzle, 2)));
components.a = TextureGetComponentSwizzle(static_cast<uint8_t>(TextureGetDstSel(swizzle, 3)));
return components;
const auto resolve = [&](uint32_t component) {
const auto selector = static_cast<Prospero::CompSwizzle>(GetDstSel(swizzle, component));
switch (selector) {
case Prospero::CompSwizzle::kZero: return vk::ComponentSwizzle::eZero;
case Prospero::CompSwizzle::kOne: return vk::ComponentSwizzle::eOne;
case Prospero::CompSwizzle::kRed: return storage_components[0];
case Prospero::CompSwizzle::kGreen: return storage_components[1];
case Prospero::CompSwizzle::kBlue: return storage_components[2];
case Prospero::CompSwizzle::kAlpha: return storage_components[3];
default: EXIT("unknown swizzle: %u\n", static_cast<uint32_t>(selector));
}
};
return {resolve(0), resolve(1), resolve(2), resolve(3)};
}
SurfaceFormatInfo TextureGetSurfaceFormatInfo(Prospero::BufferFormat format) {
const auto backing_format = Prospero::RemapTextureFormat(format);
const auto vk_format = VulkanFormat(backing_format);
const auto backing_format = Prospero::RemapTextureFormat(format);
const auto host_format = ResolveHostFormat(backing_format, Prospero::ChannelOrder::kStandard);
const auto conversion_format =
backing_format != format ? format : Prospero::BufferFormat::kInvalid;
if (vk_format != vk::Format::eUndefined) {
return SurfaceFormatInfo(vk_format, conversion_format);
if (host_format.format != vk::Format::eUndefined) {
return {host_format.format, conversion_format, host_format.host_to_storage};
}
EXIT("unknown format: fmt = %u\n", static_cast<uint32_t>(format));
return SurfaceFormatInfo(vk::Format::eUndefined, Prospero::BufferFormat::kInvalid);
}
namespace {
uint64_t CalcTextureSliceStride(const TextureUploadMipLayout* mips, uint64_t levels,
uint64_t total_size, uint32_t depth) {
uint64_t stride = 0;
for (uint32_t i = 0; i < levels; i++) {
stride = std::max(stride, mips[i].offset + mips[i].size);
}
if (depth > 1 && total_size != 0 && total_size % depth == 0) {
const auto guest_stride = total_size / depth;
if (guest_stride >= stride) {
stride = guest_stride;
}
}
return stride;
}
uint64_t CalcLinearUploadLevelSize(const TileTextureElementLayout& element, uint32_t pitch,
uint32_t height) {
const uint32_t elements_w =
std::max((pitch + element.texel_width - 1u) / element.texel_width, 1u);
const uint32_t elements_h =
std::max((height + element.texel_height - 1u) / element.texel_height, 1u);
return static_cast<uint64_t>(elements_w) * elements_h * element.bytes;
}
uint64_t SetLinearUploadLevels(TextureUploadMipLayout* mips,
const TileTextureElementLayout& element, uint64_t height,
uint64_t levels, uint32_t base_pitch) {
uint64_t offset = 0;
auto pitch = base_pitch;
auto h = static_cast<uint32_t>(height);
for (uint32_t i = 0; i < levels; i++) {
const auto size = CalcLinearUploadLevelSize(element, pitch, h);
mips[i].size = size;
mips[i].offset = offset;
offset += size;
if (pitch > 1) {
pitch /= 2;
}
if (h > 1) {
h /= 2;
}
}
return offset;
}
} // namespace
TextureUploadLayout TextureCalcUploadLayout(Prospero::BufferFormat format, uint32_t width,
uint32_t height, uint32_t levels, uint32_t depth,
Prospero::TileMode tile, uint64_t upload_size,
Prospero::TileMode tile_mode, uint64_t upload_size,
bool allow_depth_tile, bool volume_texture,
const char* owner) {
TextureUploadLayout layout {};
layout.surface.description = {
format,
tile,
tile_mode,
volume_texture ? TileSurfaceDimension::Dim3D : TileSurfaceDimension::Dim2D,
width,
height,
@@ -202,7 +189,6 @@ TextureUploadLayout TextureCalcUploadLayout(Prospero::BufferFormat format, uint3
volume_texture ? 1u : depth,
};
const auto& description = layout.surface.description;
const auto tile_mode = description.tile_mode;
TileTextureElementLayout element {};
if (format == Prospero::BufferFormat::kInvalid) {
@@ -212,34 +198,19 @@ TextureUploadLayout TextureCalcUploadLayout(Prospero::BufferFormat format, uint3
levels);
}
switch (tile_mode) {
case Prospero::TileMode::kLinear:
if (!TileGetTextureElementLayout(format, element)) {
EXIT("%s: unsupported linear texture format: fmt=%u\n", owner,
static_cast<uint32_t>(format));
}
break;
case Prospero::TileMode::kDepth:
if (!allow_depth_tile || !TileGetTiledTextureLayout(description, layout.surface)) {
EXIT("%s: unsupported typed tiled upload: fmt=%u tile=%u "
"size=%" PRIu64 " extent=%ux%u levels=%u\n",
owner, static_cast<uint32_t>(format),
static_cast<uint32_t>(description.tile_mode), upload_size, width, height,
levels);
}
break;
default:
if (!TileGetTiledTextureLayout(description, layout.surface)) {
EXIT("%s: unsupported typed tiled upload: fmt=%u tile=%u "
"size=%" PRIu64 " extent=%ux%u levels=%u\n",
owner, static_cast<uint32_t>(format),
static_cast<uint32_t>(description.tile_mode), upload_size, width, height,
levels);
}
break;
}
if (tile_mode != Prospero::TileMode::kLinear) {
if (tile_mode == Prospero::TileMode::kLinear) {
if (!TileGetTextureElementLayout(format, element)) {
EXIT("%s: unsupported linear texture format: fmt=%u\n", owner,
static_cast<uint32_t>(format));
}
} else {
if ((tile_mode == Prospero::TileMode::kDepth && !allow_depth_tile) ||
!TileGetTiledTextureLayout(description, layout.surface)) {
EXIT("%s: unsupported typed tiled upload: fmt=%u tile=%u "
"size=%" PRIu64 " extent=%ux%u levels=%u\n",
owner, static_cast<uint32_t>(format), static_cast<uint32_t>(tile_mode),
upload_size, width, height, levels);
}
element = {layout.surface.texture.block.bytes_per_element,
layout.surface.texture.texel_width, layout.surface.texture.texel_height};
}
@@ -272,40 +243,33 @@ TextureUploadLayout TextureCalcUploadLayout(Prospero::BufferFormat format, uint3
std::vector<vk::BufferImageCopy> TextureBuildImageCopies(const TextureUploadLayout& layout) {
const auto& description = layout.surface.description;
const bool volume_texture = description.dimension == TileSurfaceDimension::Dim3D;
const bool linear_texture = description.tile_mode == Prospero::TileMode::kLinear;
const auto depth = volume_texture ? description.depth : description.layers;
uint32_t mip_width = description.width;
uint32_t mip_height = description.height;
uint32_t mip_pitch = volume_texture && description.tile_mode != Prospero::TileMode::kLinear
? description.width
: layout.pitch;
uint32_t mip_pitch = volume_texture && !linear_texture ? description.width : layout.pitch;
std::vector<vk::BufferImageCopy> regions;
regions.reserve(GetTextureRegionCount(depth, description.levels, volume_texture));
for (uint32_t i = 0; i < description.levels; i++) {
EXIT_NOT_IMPLEMENTED(layout.mips[i].size == 0);
const auto mip_depth = GetTextureLevelDepth(depth, i, volume_texture);
for (uint32_t z = 0; z < mip_depth; z++) {
const auto slice_offset = z * layout.slice_stride;
vk::BufferImageCopy region {};
region.bufferOffset = layout.mips[i].offset + slice_offset;
region.imageSubresource = {vk::ImageAspectFlagBits::eColor, i, volume_texture ? 0u : z,
1};
region.imageOffset.z = volume_texture ? static_cast<int>(z) : 0;
region.imageExtent = {mip_width, mip_height, 1};
const bool linear = description.tile_mode == Prospero::TileMode::kLinear;
if (linear) {
region.bufferRowLength = layout.mips[i].row_length;
region.bufferImageHeight = layout.mips[i].image_height;
} else {
const auto texel_width = layout.surface.texture.texel_width;
const auto align = [](uint32_t value, uint32_t block) {
return ((value + block - 1u) / block) * block;
};
const auto pitch = align(mip_pitch, texel_width);
region.bufferRowLength = pitch > align(mip_width, texel_width) ? pitch : 0;
}
for (uint32_t mip_level = 0; mip_level < description.levels; ++mip_level) {
EXIT_NOT_IMPLEMENTED(layout.mips[mip_level].size == 0);
const auto mip_depth = GetTextureLevelDepth(depth, mip_level, volume_texture);
vk::BufferImageCopy region {};
region.imageSubresource = {vk::ImageAspectFlagBits::eColor, mip_level, 0, 1};
region.imageExtent = {mip_width, mip_height, 1};
if (linear_texture) {
region.bufferRowLength = layout.mips[mip_level].row_length;
region.bufferImageHeight = layout.mips[mip_level].image_height;
} else {
const auto texel_width = layout.surface.texture.texel_width;
const auto aligned_pitch = Common::AlignUp(mip_pitch, texel_width);
region.bufferRowLength =
aligned_pitch > Common::AlignUp(mip_width, texel_width) ? aligned_pitch : 0;
}
for (uint32_t slice_index = 0; slice_index < mip_depth; ++slice_index) {
region.bufferOffset = layout.mips[mip_level].offset + slice_index * layout.slice_stride;
region.imageSubresource.baseArrayLayer = volume_texture ? 0u : slice_index;
region.imageOffset.z = volume_texture ? static_cast<int>(slice_index) : 0;
regions.push_back(region);
}
@@ -323,17 +287,9 @@ std::vector<vk::BufferImageCopy> TextureBuildImageCopies(const TextureUploadLayo
return regions;
}
static bool SetGpuTileSize(uint64_t offset, uint64_t length, uint64_t capacity, uint64_t& size) {
if (offset > capacity || length > capacity - offset) {
return false;
}
size = length;
return true;
}
bool TextureBuildGpuTileInfos(uint64_t tiled_size, const std::vector<vk::BufferImageCopy>& regions,
const TextureUploadLayout& layout, uint32_t levels,
std::vector<GpuTileInfo>& out_infos) {
std::vector<GpuTileInfo>& out_tile_infos) {
const auto& description = layout.surface.description;
const bool volume_texture = description.dimension == TileSurfaceDimension::Dim3D;
const auto depth = volume_texture ? description.depth : description.layers;
@@ -351,111 +307,69 @@ bool TextureBuildGpuTileInfos(uint64_t tiled_size, const std::vector<vk::BufferI
return false;
}
std::vector<GpuTileInfo> infos;
infos.reserve(regions.size());
if (volume_texture) {
size_t region_base = 0;
for (uint32_t level = 0; level < levels; ++level) {
const uint32_t mip_depth = GetTextureLevelDepth(depth, level, true);
const bool tail = level >= surface.first_tail_level;
const uint64_t linear_stride = layout.slice_stride;
for (uint32_t z = 0; z < mip_depth; z += block.block_depth) {
const uint32_t copy_depth = std::min(block.block_depth, mip_depth - z);
const auto& region = regions[region_base + z];
const auto pitch =
region.bufferRowLength != 0 ? region.bufferRowLength : region.imageExtent.width;
const auto logical_height = region.bufferImageHeight != 0
? region.bufferImageHeight
: region.imageExtent.height;
GpuTileInfo info {};
info.family = block.family;
info.bytes_per_element = block.bytes_per_element;
info.linear_offset = region.bufferOffset;
info.tiled_offset =
static_cast<uint64_t>(z / block.block_depth) * surface.block_slice_size +
surface.mips[level].offset;
const uint64_t linear_span =
static_cast<uint64_t>(copy_depth - 1u) * linear_stride +
layout.mips[level].size;
if (!SetGpuTileSize(info.linear_offset, linear_span, UINT64_MAX,
info.linear_size) ||
!SetGpuTileSize(info.tiled_offset, surface.mips[level].size, tiled_size,
info.tiled_size)) {
return false;
}
info.linear_slice_stride = linear_stride;
info.width = std::max((region.imageExtent.width + texture.texel_width - 1u) /
texture.texel_width,
1u);
info.height = std::max(
(logical_height + texture.texel_height - 1u) / texture.texel_height, 1u);
info.depth = copy_depth;
info.surface_z =
std::vector<GpuTileInfo> tile_infos;
tile_infos.reserve(regions.size());
const auto slices_per_copy = volume_texture ? block.block_depth : 1u;
size_t region_base = 0;
for (uint32_t mip_level = 0; mip_level < levels; ++mip_level) {
const auto& mip = surface.mips[mip_level];
const auto mip_depth = GetTextureLevelDepth(depth, mip_level, volume_texture);
for (uint32_t slice_index = 0; slice_index < mip_depth; slice_index += slices_per_copy) {
const auto& region = regions[region_base + slice_index];
GpuTileInfo tile_info {};
tile_info.family = block.family;
tile_info.bytes_per_element = block.bytes_per_element;
tile_info.linear_offset = region.bufferOffset;
tile_info.linear_size = layout.mips[mip_level].size;
tile_info.tiled_size = mip.size;
if (volume_texture) {
tile_info.depth = std::min(slices_per_copy, mip_depth - slice_index);
tile_info.linear_slice_stride = layout.slice_stride;
tile_info.linear_size += (tile_info.depth - 1u) * layout.slice_stride;
tile_info.tiled_offset =
mip.offset +
static_cast<uint64_t>(slice_index / slices_per_copy) * surface.block_slice_size;
tile_info.surface_z =
block.block_depth == 1 ? static_cast<uint32_t>(region.imageOffset.z) : 0;
info.pitch = std::max((pitch + texture.texel_width - 1u) / texture.texel_width, 1u);
info.tail_x = tail ? surface.mips[level].tail_x : 0;
info.tail_y = tail ? surface.mips[level].tail_y : 0;
info.tail = tail;
info.tiled_width = surface.mips[level].padded_width;
info.tiled_height = surface.mips[level].padded_height;
infos.push_back(info);
}
region_base += mip_depth;
}
} else {
size_t region_index = 0;
for (uint32_t level = 0; level < levels; level++) {
const auto& level_size = layout.mips[level];
const auto& mip = surface.mips[level];
const bool tail = level >= surface.first_tail_level;
const auto level_depth = GetTextureLevelDepth(depth, level, false);
for (uint32_t z = 0; z < level_depth; z++) {
const auto& region = regions[region_index++];
const auto pitch =
region.bufferRowLength != 0 ? region.bufferRowLength : region.imageExtent.width;
const auto logical_height = region.bufferImageHeight != 0
? region.bufferImageHeight
: region.imageExtent.height;
GpuTileInfo info {};
info.family = block.family;
info.bytes_per_element = block.bytes_per_element;
info.linear_offset = region.bufferOffset;
} else {
const auto source_stride = layout.source_slice_stride != 0
? layout.source_slice_stride
: surface.block_slice_size;
if (z > (UINT64_MAX - mip.offset) / source_stride) {
if (slice_index > (UINT64_MAX - mip.offset) / source_stride) {
return false;
}
info.tiled_offset = mip.offset + static_cast<uint64_t>(z) * source_stride;
if (!SetGpuTileSize(info.linear_offset, level_size.size, UINT64_MAX,
info.linear_size) ||
!SetGpuTileSize(info.tiled_offset, mip.size, tiled_size, info.tiled_size)) {
return false;
}
info.width = std::max((region.imageExtent.width + texture.texel_width - 1u) /
texture.texel_width,
1u);
info.height = std::max(
(logical_height + texture.texel_height - 1u) / texture.texel_height, 1u);
info.surface_z = block.family == TileBlockFamily::RenderTarget64KB ||
block.family == TileBlockFamily::Depth64KB
? region.imageSubresource.baseArrayLayer
: 0;
info.pitch = std::max((pitch + texture.texel_width - 1u) / texture.texel_width, 1u);
info.tail = tail;
info.tail_x = tail ? mip.tail_x : 0;
info.tail_y = tail ? mip.tail_y : 0;
info.tiled_width = mip.padded_width;
info.tiled_height = mip.padded_height;
infos.push_back(info);
tile_info.tiled_offset =
mip.offset + static_cast<uint64_t>(slice_index) * source_stride;
tile_info.surface_z = block.family == TileBlockFamily::RenderTarget64KB ||
block.family == TileBlockFamily::Depth64KB
? region.imageSubresource.baseArrayLayer
: 0;
}
if (!FitsBufferRange(tile_info.linear_offset, tile_info.linear_size, UINT64_MAX) ||
!FitsBufferRange(tile_info.tiled_offset, tile_info.tiled_size, tiled_size)) {
return false;
}
const auto row_length =
region.bufferRowLength != 0 ? region.bufferRowLength : region.imageExtent.width;
const auto image_height = region.bufferImageHeight != 0 ? region.bufferImageHeight
: region.imageExtent.height;
tile_info.width = std::max(
(region.imageExtent.width + texture.texel_width - 1u) / texture.texel_width, 1u);
tile_info.height =
std::max((image_height + texture.texel_height - 1u) / texture.texel_height, 1u);
tile_info.pitch =
std::max((row_length + texture.texel_width - 1u) / texture.texel_width, 1u);
tile_info.tail = mip_level >= surface.first_tail_level;
tile_info.tail_x = tile_info.tail ? mip.tail_x : 0;
tile_info.tail_y = tile_info.tail ? mip.tail_y : 0;
tile_info.tiled_width = mip.padded_width;
tile_info.tiled_height = mip.padded_height;
tile_infos.push_back(tile_info);
}
region_base += mip_depth;
}
if (infos.empty()) {
return false;
}
out_infos = std::move(infos);
out_tile_infos = std::move(tile_infos);
return true;
}
@@ -1,9 +1,7 @@
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_HOST_GPU_RENDERER_IMAGE_TEXTURECOMMON_H_
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_HOST_GPU_RENDERER_IMAGE_TEXTURECOMMON_H_
#include "common/abi.h"
#include "common/common.h"
#include "graphics/guest_gpu/gpu_format.h"
#include "graphics/guest_gpu/gpu_defs.h"
#include "graphics/guest_gpu/tile.h"
#include "graphics/host_gpu/vulkanCommon.h"
@@ -20,12 +18,9 @@ struct RenderTargetFormatInfo {
};
struct SurfaceFormatInfo {
explicit constexpr SurfaceFormatInfo(vk::Format format,
Prospero::BufferFormat conversion_format)
: vk_format(format), conversion_format(conversion_format) {}
vk::Format vk_format;
Prospero::BufferFormat conversion_format;
vk::Format vk_format;
Prospero::BufferFormat conversion_format;
Prospero::ColorComponentMapping host_to_storage;
};
struct TextureUploadMipLayout {
@@ -43,20 +38,21 @@ struct TextureUploadLayout {
TextureUploadMipLayout mips[16] = {};
};
vk::ComponentMapping TextureGetComponentMapping(uint32_t swizzle);
vk::ComponentMapping TextureGetComponentMapping(uint32_t swizzle,
Prospero::ColorComponentMapping host_to_storage);
SurfaceFormatInfo TextureGetSurfaceFormatInfo(Prospero::BufferFormat format);
RenderTargetFormatInfo TextureGetRenderTargetFormat(Prospero::ChannelLayout layout,
Prospero::ChannelType type,
Prospero::ChannelOrder order);
TextureUploadLayout TextureCalcUploadLayout(Prospero::BufferFormat format, uint32_t width,
uint32_t height, uint32_t levels, uint32_t depth,
Prospero::TileMode tile, uint64_t upload_size,
Prospero::TileMode tile_mode, uint64_t upload_size,
bool allow_depth_tile, bool volume_texture,
const char* owner);
std::vector<vk::BufferImageCopy> TextureBuildImageCopies(const TextureUploadLayout& layout);
bool TextureBuildGpuTileInfos(uint64_t tiled_size, const std::vector<vk::BufferImageCopy>& regions,
const TextureUploadLayout& layout, uint32_t levels,
std::vector<GpuTileInfo>& infos);
std::vector<GpuTileInfo>& out_tile_infos);
} // namespace Libs::Graphics
+21 -55
View File
@@ -1,5 +1,6 @@
#include "graphics/host_gpu/renderer/image/tiler.h"
#include "common/alignment.h"
#include "common/assert.h"
#include "gpu_tiler_shaders/gpu_tiler_demote_d16_spv.h"
#include "gpu_tiler_shaders/gpu_tiler_depth_spv.h"
@@ -40,7 +41,6 @@ TileManager::TileManager(GraphicContext& graphics, CommandScheduler& scheduler,
nullptr};
vk::DescriptorSetLayoutCreateInfo descriptor_info {};
descriptor_info.sType = vk::StructureType::eDescriptorSetLayoutCreateInfo;
descriptor_info.flags = vk::DescriptorSetLayoutCreateFlagBits::ePushDescriptorKHR;
descriptor_info.bindingCount = static_cast<uint32_t>(bindings.size());
descriptor_info.pBindings = bindings.data();
@@ -50,7 +50,6 @@ TileManager::TileManager(GraphicContext& graphics, CommandScheduler& scheduler,
const vk::PushConstantRange push_range {vk::ShaderStageFlagBits::eCompute, 0, sizeof(Push)};
vk::PipelineLayoutCreateInfo layout_info {};
layout_info.sType = vk::StructureType::ePipelineLayoutCreateInfo;
layout_info.setLayoutCount = 1;
layout_info.pSetLayouts = &m_descriptor_layout;
layout_info.pushConstantRangeCount = 1;
@@ -92,11 +91,9 @@ TileManager::~TileManager() {
TileManager::Scratch TileManager::AllocateScratch(uint64_t size) {
EXIT_IF(size == 0);
vk::BufferCreateInfo create {};
create.sType = vk::StructureType::eBufferCreateInfo;
create.size = size;
create.usage = vk::BufferUsageFlagBits::eStorageBuffer | vk::BufferUsageFlagBits::eTransferSrc |
vk::BufferUsageFlagBits::eTransferDst;
create.sharingMode = vk::SharingMode::eExclusive;
VmaAllocationCreateInfo allocate {};
allocate.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
@@ -225,7 +222,7 @@ void TileManager::Prepare(bool tile, uint64_t tiled_capacity, uint64_t linear_ca
const uint64_t uniform_alignment =
std::max<uint64_t>(limits.minUniformBufferOffsetAlignment, 1);
const uint64_t stride = (sizeof(Push) + uniform_alignment - 1) & ~(uniform_alignment - 1);
const uint64_t stride = Common::AlignUp<uint64_t>(sizeof(Push), uniform_alignment);
EXIT_NOT_IMPLEMENTED(dispatches.size() > UINT64_MAX / stride);
const uint64_t bytes = dispatches.size() * stride;
auto [mapped, offset] = m_stream_buffer.Map(bytes, uniform_alignment);
@@ -263,21 +260,14 @@ vk::Pipeline TileManager::GetPipeline(uint32_t slot) {
const uint32_t values[] {1u << element_index, direction_index};
const vk::SpecializationMapEntry entries[] {{0, 0, 4}, {1, 4, 4}};
const vk::SpecializationInfo specialization {2, entries, sizeof(values), values};
vk::ShaderModuleCreateInfo module_info {};
module_info.sType = vk::StructureType::eShaderModuleCreateInfo;
module_info.codeSize = shaders[family_index].words * sizeof(uint32_t);
module_info.pCode = shaders[family_index].code;
vk::ShaderModule module = nullptr;
RequireVulkanSuccess(m_graphics.device.createShaderModule(&module_info, nullptr, &module),
"create TileManager shader module");
const auto module =
CompileSPV({shaders[family_index].code, shaders[family_index].words}, m_graphics.device);
vk::PipelineShaderStageCreateInfo stage {};
stage.sType = vk::StructureType::ePipelineShaderStageCreateInfo;
stage.stage = vk::ShaderStageFlagBits::eCompute;
stage.module = module;
stage.pName = "main";
stage.pSpecializationInfo = &specialization;
vk::ComputePipelineCreateInfo create {};
create.sType = vk::StructureType::eComputePipelineCreateInfo;
create.stage = stage;
create.layout = m_pipeline_layout;
const auto result =
@@ -287,19 +277,19 @@ vk::Pipeline TileManager::GetPipeline(uint32_t slot) {
return m_pipelines[slot];
}
void TileManager::Record(bool tile, vk::Buffer source, uint64_t source_offset,
void TileManager::Record(vk::Buffer source, uint64_t source_offset,
uint64_t source_capacity, vk::Buffer target, uint64_t target_offset,
uint64_t target_capacity, std::span<Dispatch> dispatches,
bool clear_target) {
const auto& limits = m_graphics.GetPhysicalDeviceProperties().limits;
const uint64_t descriptor_alignment =
std::max<uint64_t>(limits.minStorageBufferOffsetAlignment, 4);
const uint64_t source_descriptor_offset = source_offset & ~(descriptor_alignment - 1);
const uint64_t target_descriptor_offset = target_offset & ~(descriptor_alignment - 1);
const uint64_t source_descriptor_offset = Common::AlignDown(source_offset, descriptor_alignment);
const uint64_t target_descriptor_offset = Common::AlignDown(target_offset, descriptor_alignment);
const uint64_t source_base = source_offset - source_descriptor_offset;
const uint64_t target_base = target_offset - target_descriptor_offset;
const uint64_t source_range = (source_base + source_capacity + 3u) & ~uint64_t {3};
const uint64_t target_range = (target_base + target_capacity + 3u) & ~uint64_t {3};
const uint64_t source_range = Common::AlignUp(source_base + source_capacity, 4);
const uint64_t target_range = Common::AlignUp(target_base + target_capacity, 4);
EXIT_NOT_IMPLEMENTED(source_range > limits.maxStorageBufferRange ||
target_range > limits.maxStorageBufferRange || target_offset % 4 != 0 ||
target_capacity % 4 != 0);
@@ -307,7 +297,6 @@ void TileManager::Record(bool tile, vk::Buffer source, uint64_t source_offset,
m_scheduler.EndRendering();
auto command = m_scheduler.Current().Handle();
vk::BufferMemoryBarrier barriers[3] {};
barriers[0].sType = vk::StructureType::eBufferMemoryBarrier;
barriers[0].srcAccessMask = vk::AccessFlagBits::eMemoryWrite | vk::AccessFlagBits::eHostWrite;
barriers[0].dstAccessMask = vk::AccessFlagBits::eShaderRead;
barriers[0].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
@@ -316,7 +305,6 @@ void TileManager::Record(bool tile, vk::Buffer source, uint64_t source_offset,
barriers[0].offset = source_offset;
barriers[0].size = source_capacity;
barriers[1] = barriers[0];
barriers[1].srcAccessMask = vk::AccessFlagBits::eMemoryWrite | vk::AccessFlagBits::eHostWrite;
barriers[1].dstAccessMask =
clear_target ? vk::AccessFlagBits::eTransferWrite
: vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite;
@@ -352,7 +340,6 @@ void TileManager::Record(bool tile, vk::Buffer source, uint64_t source_offset,
const vk::DescriptorBufferInfo infos[] {source_info, target_info, params_info};
std::array<vk::WriteDescriptorSet, 3> writes {};
for (uint32_t index = 0; index < writes.size(); index++) {
writes[index].sType = vk::StructureType::eWriteDescriptorSet;
writes[index].dstBinding = index;
writes[index].descriptorCount = 1;
writes[index].descriptorType = index == 2 ? vk::DescriptorType::eUniformBuffer
@@ -382,9 +369,9 @@ TileManager::Result TileManager::Detile(vk::Buffer tiled, uint64_t tiled_offset,
const uint64_t source_base = tiled_offset & (descriptor_alignment - 1);
std::vector<Dispatch> dispatches;
Prepare(false, tiled_capacity, linear_capacity, infos, source_base, 0, dispatches);
auto scratch = AllocateScratch((linear_capacity + 3u) & ~uint64_t {3});
auto scratch = AllocateScratch(Common::AlignUp(linear_capacity, 4));
DeferDestroy(scratch);
Record(false, tiled, tiled_offset, tiled_capacity, scratch.buffer, 0, scratch.size, dispatches,
Record(tiled, tiled_offset, tiled_capacity, scratch.buffer, 0, scratch.size, dispatches,
true);
return {scratch.buffer, 0, linear_capacity};
}
@@ -399,7 +386,7 @@ void TileManager::Tile(vk::Buffer linear, uint64_t linear_offset, uint64_t linea
const uint64_t target_base = tiled_offset & (descriptor_alignment - 1);
std::vector<Dispatch> dispatches;
Prepare(true, tiled_capacity, linear_capacity, infos, source_base, target_base, dispatches);
Record(true, linear, linear_offset, linear_capacity, tiled, tiled_offset, tiled_capacity,
Record(linear, linear_offset, linear_capacity, tiled, tiled_offset, tiled_capacity,
dispatches, false);
}
@@ -416,19 +403,19 @@ void TileManager::TileImage(Image& image, std::span<const vk::BufferImageCopy> r
// Reserve all stream parameters before creating a scheduler-lived scratch dependency:
// StreamBuffer::Map is allowed to submit the current tick when it wraps.
Prepare(true, tiled_capacity, linear_capacity, infos, 0, target_base, dispatches);
auto linear = AllocateScratch((linear_capacity + 3u) & ~uint64_t {3});
auto linear = AllocateScratch(Common::AlignUp(linear_capacity, 4));
DeferDestroy(linear);
image.Download(regions, linear.buffer, 0, linear.size);
Result source {linear.buffer, 0, linear.size};
if (transform == ColorTransform::SwapBgra16) {
source = SwapBgra16(source);
}
Record(true, source.buffer, source.offset, linear_capacity, tiled, tiled_offset, tiled_capacity,
Record(source.buffer, source.offset, linear_capacity, tiled, tiled_offset, tiled_capacity,
dispatches, false);
}
TileManager::Result TileManager::GetScratchBuffer(uint64_t size) {
auto scratch = AllocateScratch((size + 3u) & ~uint64_t {3});
auto scratch = AllocateScratch(Common::AlignUp(size, 4));
DeferDestroy(scratch);
return {scratch.buffer, 0, scratch.size};
}
@@ -436,11 +423,11 @@ TileManager::Result TileManager::GetScratchBuffer(uint64_t size) {
TileManager::StorageBinding TileManager::BindStorage(Result buffer, uint64_t size) const {
const auto& limits = m_graphics.GetPhysicalDeviceProperties().limits;
const auto alignment = std::max<uint64_t>(limits.minStorageBufferOffsetAlignment, 4);
const auto descriptor_offset = buffer.offset - buffer.offset % alignment;
const auto descriptor_offset = Common::AlignDown(buffer.offset, alignment);
const auto base = buffer.offset - descriptor_offset;
EXIT_IF(buffer.buffer == nullptr || size == 0 || buffer.size < size || base > UINT32_MAX ||
size > UINT64_MAX - base || base + size > UINT64_MAX - 3);
const auto range = (base + size + 3) & ~uint64_t {3};
const auto range = Common::AlignUp(base + size, 4);
EXIT_IF(range > limits.maxStorageBufferRange || range > UINT32_MAX);
return {{buffer.buffer, descriptor_offset, range}, static_cast<uint32_t>(base)};
}
@@ -481,21 +468,13 @@ void TileManager::ConvertD16(Result source, Result target, D16Direction directio
code = GPU_TILER_DEMOTE_D16_SPV;
words = std::size(GPU_TILER_DEMOTE_D16_SPV);
}
vk::ShaderModuleCreateInfo module_info {};
module_info.sType = vk::StructureType::eShaderModuleCreateInfo;
module_info.codeSize = words * sizeof(uint32_t);
module_info.pCode = code;
vk::ShaderModule module = nullptr;
RequireVulkanSuccess(m_graphics.device.createShaderModule(&module_info, nullptr, &module),
"create D16 conversion shader module");
const auto module = CompileSPV({code, words}, m_graphics.device);
vk::PipelineShaderStageCreateInfo stage {};
stage.sType = vk::StructureType::ePipelineShaderStageCreateInfo;
stage.stage = vk::ShaderStageFlagBits::eCompute;
stage.module = module;
stage.pName = "main";
stage.pSpecializationInfo = &specialization;
vk::ComputePipelineCreateInfo create {};
create.sType = vk::StructureType::eComputePipelineCreateInfo;
create.stage = stage;
create.layout = m_pipeline_layout;
const auto result =
@@ -525,14 +504,13 @@ void TileManager::ConvertD16(Result source, Result target, D16Direction directio
const auto target_required = required(layout.height, layout.layers, layout.target_row_stride,
layout.target_slice_stride, target_active);
EXIT_IF(source_required > UINT64_MAX - 3 || target_required > UINT64_MAX - 3);
const auto source_barrier_size = (source_required + 3) & ~uint64_t {3};
const auto target_barrier_size = (target_required + 3) & ~uint64_t {3};
const auto source_barrier_size = Common::AlignUp(source_required, 4);
const auto target_barrier_size = Common::AlignUp(target_required, 4);
EXIT_IF(source.size < source_barrier_size || target.size < target_barrier_size);
m_scheduler.EndRendering();
auto command = m_scheduler.Current().Handle();
vk::BufferMemoryBarrier barriers[2] {};
barriers[0].sType = vk::StructureType::eBufferMemoryBarrier;
barriers[0].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barriers[0].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barriers[0].buffer = source.buffer;
@@ -542,7 +520,6 @@ void TileManager::ConvertD16(Result source, Result target, D16Direction directio
vk::AccessFlagBits::eTransferWrite |
vk::AccessFlagBits::eShaderWrite;
barriers[0].dstAccessMask = vk::AccessFlagBits::eShaderRead;
barriers[1].sType = vk::StructureType::eBufferMemoryBarrier;
barriers[1].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barriers[1].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barriers[1].buffer = target.buffer;
@@ -596,7 +573,6 @@ void TileManager::ConvertD16(Result source, Result target, D16Direction directio
};
std::array<vk::WriteDescriptorSet, 2> writes {};
for (uint32_t index = 0; index < writes.size(); index++) {
writes[index].sType = vk::StructureType::eWriteDescriptorSet;
writes[index].dstBinding = index;
writes[index].descriptorCount = 1;
writes[index].descriptorType = vk::DescriptorType::eStorageBuffer;
@@ -626,20 +602,12 @@ void TileManager::ConvertD16(Result source, Result target, D16Direction directio
void TileManager::SwapBgra16(Result input, Result output, uint32_t pixels) {
if (m_swap_bgra16 == nullptr) {
vk::ShaderModuleCreateInfo module_info {};
module_info.sType = vk::StructureType::eShaderModuleCreateInfo;
module_info.codeSize = std::size(GPU_TILER_SWAP_BGRA16_SPV) * sizeof(uint32_t);
module_info.pCode = GPU_TILER_SWAP_BGRA16_SPV;
vk::ShaderModule module = nullptr;
RequireVulkanSuccess(m_graphics.device.createShaderModule(&module_info, nullptr, &module),
"create BGRA16 swap shader module");
const auto module = CompileSPV(GPU_TILER_SWAP_BGRA16_SPV, m_graphics.device);
vk::PipelineShaderStageCreateInfo stage {};
stage.sType = vk::StructureType::ePipelineShaderStageCreateInfo;
stage.stage = vk::ShaderStageFlagBits::eCompute;
stage.module = module;
stage.pName = "main";
vk::ComputePipelineCreateInfo create {};
create.sType = vk::StructureType::eComputePipelineCreateInfo;
create.stage = stage;
create.layout = m_pipeline_layout;
const auto result =
@@ -658,7 +626,6 @@ void TileManager::SwapBgra16(Result input, Result output, uint32_t pixels) {
};
std::array<vk::WriteDescriptorSet, 2> writes {};
for (uint32_t index = 0; index < writes.size(); index++) {
writes[index].sType = vk::StructureType::eWriteDescriptorSet;
writes[index].dstBinding = index;
writes[index].descriptorCount = 1;
writes[index].descriptorType = vk::DescriptorType::eStorageBuffer;
@@ -666,7 +633,6 @@ void TileManager::SwapBgra16(Result input, Result output, uint32_t pixels) {
}
vk::BufferMemoryBarrier barriers[2] {};
for (uint32_t index = 0; index < 2; index++) {
barriers[index].sType = vk::StructureType::eBufferMemoryBarrier;
barriers[index].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barriers[index].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barriers[index].buffer = infos[index].buffer;
Loaded 100 of 329 files, more files were not shown because too many files have changed in this diff. Show more